[banshee/podcast-ng] Initial podcast-ng commit.
- From: Michael C. Urbanski <murbanski src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [banshee/podcast-ng] Initial podcast-ng commit.
- Date: Mon, 29 Mar 2010 23:30:18 +0000 (UTC)
commit 2865865346ddc555393d1ad9c63dd323ae0b9279
Author: Mike Urbanski <michael c urbanski gmail com>
Date: Mon Mar 29 18:29:05 2010 -0500
Initial podcast-ng commit.
src/Extensions/Banshee.Paas/Banshee.Paas.addin.xml | 26 +
src/Extensions/Banshee.Paas/Banshee.Paas.csproj | 205 +++++
.../Banshee.Paas.Aether/AetherClient.cs | 73 ++
.../Banshee.Paas.Aether/AetherClientID.cs | 36 +
.../Banshee.Paas.Aether/AetherClientState.cs | 36 +
.../AetherClientStateChangedEventArgs.cs | 45 ++
.../Banshee.Paas.Aether/AetherRequest.cs | 310 ++++++++
.../AetherRequestCompletedEventArgs.cs | 55 ++
.../Banshee.Paas.Aether/ChannelEventArgs.cs | 57 ++
.../Banshee.Paas.Aether/ChannelUpdateStatus.cs | 37 +
.../Banshee.Paas.Aether/ItemEventArgs.cs | 57 ++
.../GetCategoriesCompletedEventArgs.cs | 47 ++
.../MiroGuideClient/GetChannelsEventArgs.cs | 54 ++
.../MiroGuideClient/MiroGuideAccountInfo.cs | 107 +++
.../MiroGuideClient/MiroGuideCategoryInfo.cs | 51 ++
.../MiroGuideClient/MiroGuideChannelInfo.cs | 111 +++
.../MiroGuideClient/MiroGuideClient.cs | 750 ++++++++++++++++++
.../MiroGuideClient/MiroGuideClientError.cs | 36 +
.../MiroGuideClient/MiroGuideClientMethod.cs | 37 +
.../MiroGuideClient/MiroGuideFilterType.cs | 44 +
.../MiroGuideMethodCompletedEventArgs.cs | 46 ++
.../MiroGuideClient/MiroGuideRequestState.cs | 132 +++
.../MiroGuideClient/MiroGuideSortType.cs | 15 +
.../MiroGuideClient/RequestCompletedEventArgs.cs | 55 ++
.../MiroGuideClient/SearchContext.cs | 126 +++
.../MiroGuideClient/ServiceMethodFlags.cs | 37 +
.../SubscriptionRequestedEventArgs.cs | 55 ++
.../Banshee.Paas.Aether/RequestState.cs | 141 ++++
.../ChannelUpdateCompletedEventArgs.cs | 53 ++
.../SyndicationClient/ChannelUpdateManager.cs | 50 ++
.../SyndicationClient/ChannelUpdateTask.cs | 123 +++
.../SyndicationClient/ItmsPodcast.cs | 100 +++
.../SyndicationClient/RssParser.cs | 286 +++++++
.../SyndicationClient/SyndicationClient.cs | 532 +++++++++++++
.../Banshee.Paas.Data/CacheModelProvider.cs | 142 ++++
.../Banshee.Paas.Data/CacheableItem.cs | 54 ++
.../Banshee.Paas.Data/DownloadPreference.cs | 35 +
.../Banshee.Paas.Data/DownloadStatusFilterModel.cs | 86 ++
.../Banshee.Paas/Banshee.Paas.Data/ListModel.cs | 201 +++++
.../Banshee.Paas/Banshee.Paas.Data/PaasChannel.cs | 223 ++++++
.../Banshee.Paas.Data/PaasChannelModel.cs | 62 ++
.../Banshee.Paas/Banshee.Paas.Data/PaasItem.cs | 323 ++++++++
.../Banshee.Paas.Data/PaasTrackInfo.cs | 189 +++++
.../Banshee.Paas.Data/PaasTrackListModel.cs | 100 +++
.../Banshee.Paas.Data/PaasUnheardFilterModel.cs | 85 ++
.../Banshee.Paas.Data/SingletonSelection.cs | 47 ++
.../QueuedDownloadTask.cs | 86 ++
.../DownloadListView.cs | 125 +++
.../DownloadManagerInterface.cs | 191 +++++
.../DownloadSource.cs | 92 +++
.../DownloadSourceContents.cs | 81 ++
.../DownloadUserJob.cs | 147 ++++
.../DownloadListModel.cs | 74 ++
.../PaasDownloadManager.cs | 365 +++++++++
.../Banshee.Paas.Gui/ColumnCellChannel.cs | 197 +++++
.../Banshee.Paas.Gui/ColumnCellDownloadStatus.cs | 56 ++
.../ColumnCellPaasStatusIndicator.cs | 123 +++
.../Banshee.Paas.Gui/ColumnCellPublished.cs | 41 +
.../Banshee.Paas.Gui/ColumnCellUnheard.cs | 57 ++
.../Dialogs/ChannelPropertiesDialog.cs | 267 +++++++
.../Banshee.Paas.Gui/Dialogs/SubscribeDialog.cs | 186 +++++
.../Banshee.Paas.Gui/DownloadPreferenceComboBox.cs | 62 ++
.../Banshee.Paas.Gui/DownloadStatusFilterView.cs | 49 ++
.../Banshee.Paas.Gui/IColumnCellDataHelper.cs | 39 +
.../Banshee.Paas/Banshee.Paas.Gui/PaasActions.cs | 793 +++++++++++++++++++
.../Banshee.Paas.Gui/PaasChannelView.cs | 78 ++
.../Banshee.Paas.Gui/PaasColumnController.cs | 100 +++
.../Banshee.Paas/Banshee.Paas.Gui/PaasItemPage.cs | 127 +++
.../Banshee.Paas/Banshee.Paas.Gui/PaasItemView.cs | 93 +++
.../Banshee.Paas.Gui/PaasSourceContents.cs | 153 ++++
.../Banshee.Paas.Gui/PaasUnheardFilterView.cs | 49 ++
.../ChannelInfoPreview.cs | 88 ++
.../ColumnCellChannel.cs | 166 ++++
.../MiroGuideAccountDialog.cs | 153 ++++
.../Banshee.Paas.MiroGuide.Gui/MiroGuideActions.cs | 197 +++++
.../MiroGuideCategoryListView.cs | 51 ++
.../MiroGuideChannelListView.cs | 63 ++
.../MiroGuideLoginForm.cs | 143 ++++
.../MiroGuideSearchEntry.cs | 55 ++
.../ReflectionInfoWidget.cs | 164 ++++
.../SortPreferenceActionButton.cs | 79 ++
.../SortPreferenceChangedEventArgs.cs | 51 ++
.../SourceContents/BrowserSourceContents.cs | 98 +++
.../SourceContents/ChannelSourceContents.cs | 183 +++++
.../SourceContents/MiroGuideSourceContents.cs | 71 ++
.../MiroGuideCategoryListModel.cs | 41 +
.../MiroGuideChannelListModel.cs | 46 ++
.../MiroGuideImageFetchJob.cs | 86 ++
.../MiroGuideInterfaceManager.cs | 74 ++
.../MiroGuideSearchFilter.cs | 13 +
.../Sources/BrowseChannelsSource.cs | 135 ++++
.../Sources/ChannelSource.cs | 419 ++++++++++
.../Sources/FeaturedChannelsSource.cs | 61 ++
.../Sources/HDChannelsSource.cs | 62 ++
.../Sources/MiroGuideSource.cs | 50 ++
.../Sources/MiroGuideSourcePosition.cs | 41 +
.../Sources/PopularChannelsSource.cs | 61 ++
.../Sources/RecommendedChannelsSource.cs | 46 ++
.../Banshee.Paas.MiroGuide/Sources/SearchSource.cs | 156 ++++
.../Sources/TopRatedChannelsSource.cs | 61 ++
.../Banshee.Paas/Banshee.Paas.Utils/OpmlParser.cs | 93 +++
.../Banshee.Paas/Banshee.Paas.Utils/StringUtils.cs | 54 ++
.../Banshee.Paas/Banshee.Paas/PaasImageFetchJob.cs | 83 ++
.../Banshee.Paas/Banshee.Paas/PaasService.cs | 833 +++++++++++++++++++
.../Banshee.Paas/Banshee.Paas/PaasSource.cs | 277 +++++++
src/Extensions/Banshee.Paas/Makefile.am | 123 +++
.../Banshee.Paas/Resources/ActiveSourceUI.xml | 39 +
src/Extensions/Banshee.Paas/Resources/GlobalUI.xml | 43 +
.../Resources/MiroGuideActiveSourceUI.xml | 7 +
.../Banshee.Paas/Resources/MiroGuideUI.xml | 23 +
.../ThemeIcons/16x16/categories/podcast.png | Bin 0 -> 949 bytes
.../ThemeIcons/16x16/status/podcast-new.png | Bin 0 -> 533 bytes
.../ThemeIcons/22x22/categories/podcast.png | Bin 0 -> 1564 bytes
.../ThemeIcons/48x48/categories/podcast.png | Bin 0 -> 4779 bytes
.../ThemeIcons/scalable/categories/miro-browse.svg | 232 ++++++
.../ThemeIcons/scalable/categories/miro.svg | 188 +++++
.../categories/miroguide-default-channel.svg | 318 ++++++++
src/Libraries/Migo2/Makefile.am | 55 ++
.../Migo2/Migo2.Async/AsyncStateManager.cs | 127 +++
.../Migo2.Async/CommandQueue/CommandDelegate.cs | 32 +
.../Migo2/Migo2.Async/CommandQueue/CommandQueue.cs | 268 +++++++
.../Migo2.Async/CommandQueue/CommandWrapper.cs | 50 ++
.../Migo2/Migo2.Async/CommandQueue/EventWrapper.cs | 56 ++
.../Migo2/Migo2.Async/CommandQueue/ICommand.cs | 33 +
.../Migo2/Migo2.Async/Task/CancellationType.cs | 38 +
.../Migo2/Migo2.Async/Task/IWaitableTask.cs | 36 +
src/Libraries/Migo2/Migo2.Async/Task/Task.cs | 393 +++++++++
.../Migo2.Async/Task/TaskCompletedEventArgs.cs | 90 +++
.../Migo2/Migo2.Async/Task/TaskEventArgs.cs | 56 ++
src/Libraries/Migo2/Migo2.Async/Task/TaskState.cs | 46 ++
.../Migo2.Async/Task/TaskStateChangedEventArgs.cs | 73 ++
.../EventArgs/GroupStatusChangedEventArgs.cs | 58 ++
.../TaskGroup/EventArgs/ManipulatedEventArgs.cs | 78 ++
.../TaskGroup/EventArgs/ReorderedEventArgs.cs | 46 ++
.../TaskGroup/EventArgs/TaskAddedEventArgs.cs | 81 ++
.../EventArgs/TaskProgressChangedEventArgs.cs | 54 ++
.../TaskGroup/EventArgs/TaskRemovedEventArgs.cs | 79 ++
.../Migo2.Async/TaskGroup/GroupProgressManager.cs | 176 ++++
.../Migo2.Async/TaskGroup/GroupStatusManager.cs | 349 ++++++++
.../Migo2/Migo2.Async/TaskGroup/TaskGroup.cs | 690 ++++++++++++++++
.../Migo2.Async/TaskGroup/TaskGroup_Collection.cs | 536 +++++++++++++
.../Migo2/Migo2.Collections/OrderComparer.cs | 53 ++
src/Libraries/Migo2/Migo2.Collections/Pair.cs | 43 +
.../Migo2.DownloadService/DownloadStatusManager.cs | 72 ++
.../DownloadTaskStatusUpdatedEventArgs.cs | 53 ++
.../Migo2.DownloadService/HttpDownloadGroup.cs | 233 ++++++
.../HttpDownloadGroupStatusChangedEventArgs.cs | 49 ++
.../Migo2.DownloadService/HttpDownloadManager.cs | 164 ++++
.../HttpFileDownloadErrors.cs | 38 +
.../Migo2.DownloadService/HttpFileDownloadTask.cs | 436 ++++++++++
.../Migo2.Net/AsyncWebClient/AsyncWebClient.cs | 834 ++++++++++++++++++++
.../DownloadDataCompletedEventArgs.cs | 49 ++
.../DownloadProgressChangedEventArgs.cs | 61 ++
.../Migo2.Net/AsyncWebClient/DownloadStatus.cs | 75 ++
.../DownloadStatusUpdatedEventArgs.cs | 51 ++
.../DownloadStringCompletedEventArgs.cs | 53 ++
.../AsyncWebClient/RemoteFileModifiedException.cs | 59 ++
.../AsyncWebClient/TransferStatusManager.cs | 213 +++++
.../AsyncWebClient/TransferStatusManager_Rate.cs | 161 ++++
src/Libraries/Migo2/Migo2.Utils/Rfc822DateTime.cs | 199 +++++
src/Libraries/Migo2/Migo2.Utils/UnitUtils.cs | 70 ++
src/Libraries/Migo2/Migo2.Utils/XmlUtils.cs | 102 +++
src/Libraries/Migo2/Migo2.csproj | 98 +++
src/Libraries/Migo2/README.png | Bin 0 -> 5864 bytes
164 files changed, 20996 insertions(+), 0 deletions(-)
---
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas.addin.xml b/src/Extensions/Banshee.Paas/Banshee.Paas.addin.xml
new file mode 100644
index 0000000..2e089c8
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas.addin.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Addin
+ id="Banshee.Paas"
+ version="1.0"
+ compatVersion="1.0"
+ copyright="All copyrights held by respective authors / owners. Licensed under the MIT X11 license."
+ name="Podcasting"
+ category="User Interface"
+ description="Subscribe to and manage your podcasts!"
+ author="Aaron Bockover, Gabriel Burt, Brandan Lloyd, Bertrand Lorentz, Brian Lucas, John Millikin, Mike Urbanski"
+ url="http://banshee-project.org/"
+ defaultEnabled="true">
+
+ <Dependencies>
+ <Addin id="Banshee.Services" version="1.0"/>
+ <Addin id="Banshee.ThickClient" version="1.0"/>
+ </Dependencies>
+
+ <Extension path="/Banshee/ServiceManager/Service">
+ <Service class="Banshee.Paas.PaasService"/>
+ </Extension>
+
+ <Extension path="/Banshee/Gui/TrackEditor/NotebookPage">
+ <TrackEditorPage class="Banshee.Paas.Gui.PaasItemPage"/>
+ </Extension>
+</Addin>
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas.csproj b/src/Extensions/Banshee.Paas/Banshee.Paas.csproj
new file mode 100644
index 0000000..5c9a253
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas.csproj
@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.21022</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{EEA0F24F-3180-4C63-993C-84179711C7EF}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AssemblyName>Banshee.Paas</AssemblyName>
+ <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug</OutputPath>
+ <DefineConstants>DEBUG</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>none</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Release</OutputPath>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="gdk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+ <Reference Include="gtk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+ <Reference Include="Mono.Cairo" />
+ <Reference Include="pango-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+ <Reference Include="System.Xml.Linq">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+ <ProjectExtensions>
+ <MonoDevelop>
+ <Properties>
+ <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="true" RelativeMakefileName="Makefile.am" IsAutotoolsProject="true" RelativeConfigureInPath="../../..">
+ <BuildFilesVar Sync="true" Name="SOURCES" />
+ <DeployFilesVar />
+ <ResourcesVar Name="RESOURCES" />
+ <OthersVar />
+ <GacRefVar />
+ <AsmRefVar />
+ <ProjectRefVar />
+ <MessageRegex Name="Vala" />
+ </MonoDevelop.Autotools.MakefileInfo>
+ </Properties>
+ </MonoDevelop>
+ </ProjectExtensions>
+ <ItemGroup>
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\AetherClient.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\AetherClientID.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\AetherClientState.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\AetherClientStateChangedEventArgs.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\MiroGuideClient\MiroGuideClient.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\MiroGuideClient\ServiceMethodFlags.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\SyndicationClient\ChannelUpdateManager.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\SyndicationClient\ChannelUpdateTask.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\SyndicationClient\RssParser.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\SyndicationClient\SyndicationClient.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Data\PaasChannel.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Data\PaasChannelModel.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Data\PaasItem.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Data\PaasTrackInfo.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Data\PaasTrackListModel.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Gui\Dialogs\SubscribeDialog.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Gui\ColumnCellChannel.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Gui\ColumnCellPaasStatusIndicator.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Gui\ColumnCellPublished.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Gui\PaasActions.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Gui\PaasChannelView.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Gui\PaasColumnController.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Gui\PaasItemView.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Gui\PaasSourceContents.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Utils\StringUtils.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\SyndicationClient\ChannelUpdateCompletedEventArgs.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Gui\PaasItemPage.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Gui\Dialogs\ChannelPropertiesDialog.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Data\DownloadPreference.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.DownloadManager\PaasDownloadManager.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.DownloadManager.Gui\DownloadListView.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.DownloadManager.Gui\DownloadManagerInterface.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.DownloadManager.Gui\DownloadSource.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.DownloadManager.Gui\DownloadSourceContents.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.DownloadManager.Gui\DownloadUserJob.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Gui\IColumnCellDataHelper.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Data\PaasUnheardFilterModel.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Gui\PaasUnheardFilterView.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Gui\ColumnCellUnheard.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Gui\ColumnCellDownloadStatus.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Data\DownloadStatusFilterModel.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Gui\DownloadStatusFilterView.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas\PaasImageFetchJob.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas\PaasService.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas\PaasSource.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\SyndicationClient\ItmsPodcast.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.DownloadManager\DownloadListModel.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.DownloadManager.Data\QueuedDownloadTask.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\AetherRequest.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\AetherRequestCompletedEventArgs.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\RequestState.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\MiroGuideClient\MiroGuideRequestState.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\MiroGuideClient\MiroGuideClientMethod.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\ChannelEventArgs.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\MiroGuideClient\MiroGuideClientError.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\MiroGuideClient\MiroGuideAccountInfo.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\ItemEventArgs.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\ChannelUpdateStatus.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Gui\DownloadPreferenceComboBox.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Data\ListModel.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\MiroGuideClient\MiroGuideChannelInfo.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Utils\OpmlParser.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide.Gui\ColumnCellChannel.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide.Gui\MiroGuideAccountDialog.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide.Gui\MiroGuideActions.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide.Gui\MiroGuideChannelListView.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide.Gui\MiroGuideLoginForm.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide.Gui\MiroGuideSearchEntry.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\MiroGuideClient\MiroGuideFilterType.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\MiroGuideClient\MiroGuideSortType.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\MiroGuideClient\GetChannelsEventArgs.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\MiroGuideClient\MiroGuideMethodCompletedEventArgs.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\MiroGuideClient\RequestCompletedEventArgs.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\MiroGuideClient\SubscriptionRequestedEventArgs.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide\MiroGuideImageFetchJob.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide\MiroGuideChannelListModel.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\MiroGuideClient\SearchContext.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide\MiroGuideInterfaceManager.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide\Sources\SearchSource.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide\Sources\ChannelSource.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide\Sources\HDChannelsSource.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide\Sources\FeaturedChannelsSource.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide\Sources\MiroGuideSource.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide\Sources\PopularChannelsSource.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide\Sources\TopRatedChannelsSource.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide.Gui\SourceContents\ChannelSourceContents.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide.Gui\SourceContents\MiroGuideSourceContents.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide\Sources\MiroGuideSourcePosition.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide\Sources\BrowseChannelsSource.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide\Sources\RecommendedChannelsSource.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide.Gui\SortPreferenceActionButton.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide.Gui\SortPreferenceChangedEventArgs.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide.Gui\MiroGuideCategoryListView.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\MiroGuideClient\MiroGuideCategoryInfo.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Aether\MiroGuideClient\GetCategoriesCompletedEventArgs.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide\MiroGuideCategoryListModel.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide.Gui\SourceContents\BrowserSourceContents.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide\MiroGuideSearchFilter.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Data\SingletonSelection.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Data\CacheModelProvider.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.Data\CacheableItem.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide.Gui\ReflectionInfoWidget.cs" />
+ <Compile Include="Banshee.Paas\Banshee.Paas.MiroGuide.Gui\ChannelInfoPreview.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\Core\Banshee.Core\Banshee.Core.csproj">
+ <Project>{2ADB831A-A050-47D0-B6B9-9C19D60233BB}</Project>
+ <Name>Banshee.Core</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\Core\Banshee.Services\Banshee.Services.csproj">
+ <Project>{B28354F0-BA87-44E8-989F-B864A3C7C09F}</Project>
+ <Name>Banshee.Services</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\Libraries\Hyena.Gui\Hyena.Gui.csproj">
+ <Project>{C856EFD8-E812-4E61-8B76-E3583D94C233}</Project>
+ <Name>Hyena.Gui</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\Libraries\Migo2\Migo2.csproj">
+ <Project>{FC311410-8638-4A66-A8A5-1E900CDC6C7B}</Project>
+ <Name>Migo2</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\Core\Banshee.ThickClient\Banshee.ThickClient.csproj">
+ <Project>{AC839523-7BDF-4AB6-8115-E17921B96EC6}</Project>
+ <Name>Banshee.ThickClient</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\Libraries\Hyena\Hyena.csproj">
+ <Project>{95374549-9553-4C1E-9D89-667755F90E12}</Project>
+ <Name>Hyena</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="Resources\ActiveSourceUI.xml" />
+ <None Include="Resources\GlobalUI.xml" />
+ <None Include="Banshee.Paas.addin.xml" />
+ <None Include="Resources\MiroGuideUI.xml" />
+ <None Include="Resources\MiroGuideActiveSourceUI.xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Folder Include="Banshee.Paas\Banshee.Paas.DownloadManager\" />
+ <Folder Include="Banshee.Paas\Banshee.Paas\" />
+ <Folder Include="Banshee.Paas\Banshee.Paas.DownloadManager.Data\" />
+ <Folder Include="Banshee.Paas\Banshee.Paas.MiroGuide\" />
+ <Folder Include="Banshee.Paas\Banshee.Paas.MiroGuide.Gui\" />
+ <Folder Include="Banshee.Paas\Banshee.Paas.MiroGuide\Sources\" />
+ <Folder Include="Banshee.Paas\Banshee.Paas.MiroGuide.Gui\SourceContents\" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherClient.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherClient.cs
new file mode 100644
index 0000000..c19540d
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherClient.cs
@@ -0,0 +1,73 @@
+//
+// AetherClient.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Migo2.Async;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Aether
+{
+ public abstract class AetherClient : IDisposable
+ {
+ private CommandQueue event_queue;
+ private readonly object sync = new object ();
+
+ protected object SyncRoot {
+ get { return sync; }
+ }
+
+ protected CommandQueue EventQueue {
+ get { return event_queue; }
+ }
+
+ public event EventHandler<AetherClientStateChangedEventArgs> StateChanged;
+
+ public AetherClient ()
+ {
+ event_queue = new CommandQueue ();
+ }
+
+ public virtual void Dispose ()
+ {
+ event_queue.Dispose ();
+ event_queue = null;
+ }
+
+ protected virtual void OnStateChanged (AetherClientState oldState, AetherClientState newState)
+ {
+ var handler = StateChanged;
+
+ if (handler != null) {
+ event_queue.Register (new EventWrapper<AetherClientStateChangedEventArgs> (
+ handler, this, new AetherClientStateChangedEventArgs (oldState, newState)
+ ));
+ }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherClientID.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherClientID.cs
new file mode 100644
index 0000000..28a0c4c
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherClientID.cs
@@ -0,0 +1,36 @@
+//
+// AetherClientID.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Banshee.Paas.Aether
+{
+ public enum AetherClientID : int
+ {
+ Syndication = 1, // RSS, ATOM, etc.
+ MiroGuide = 2
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherClientState.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherClientState.cs
new file mode 100644
index 0000000..b5aef03
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherClientState.cs
@@ -0,0 +1,36 @@
+//
+// AetherClientState.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Banshee.Paas.Aether
+{
+ public enum AetherClientState
+ {
+ Busy,
+ Idle
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherClientStateChangedEventArgs.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherClientStateChangedEventArgs.cs
new file mode 100644
index 0000000..81fdce7
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherClientStateChangedEventArgs.cs
@@ -0,0 +1,45 @@
+//
+// AetherClientStateChangedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Banshee.Paas.Aether
+{
+ public class AetherClientStateChangedEventArgs : EventArgs
+ {
+ private readonly AetherClientState new_state;
+ private readonly AetherClientState old_state;
+
+ public AetherClientState NewState { get { return new_state; } }
+ public AetherClientState OldState { get { return old_state; } }
+
+ public AetherClientStateChangedEventArgs (AetherClientState oldState, AetherClientState newState)
+ {
+ old_state = oldState;
+ new_state = newState;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherRequest.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherRequest.cs
new file mode 100644
index 0000000..976bd36
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherRequest.cs
@@ -0,0 +1,310 @@
+//
+// AetherRequest.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.IO;
+using System.Net;
+using System.Text;
+
+using System.Collections;
+using System.Collections.Generic;
+
+using Hyena;
+using Migo2.Async;
+
+namespace Banshee.Paas.Aether
+{
+ public enum HttpMethod
+ {
+ GET = 0,
+ POST = 1
+ }
+
+ public sealed class AetherRequest
+ {
+ private int timeout;
+ private AsyncStateManager asm;
+
+ private HttpWebRequest request;
+ private CookieContainer cookie_container;
+
+ public EventHandler<AetherRequestCompletedEventArgs> Completed;
+
+ public CookieContainer CookieContainer {
+ get { return cookie_container; }
+ set { cookie_container = value; }
+ }
+
+ public string ContentType { get; set; }
+ public ICredentials Credentials { get; set; }
+
+ public int Timeout {
+ get { return timeout; }
+ set {
+ if (value < 0) {
+ throw new ArgumentOutOfRangeException ("Timeout", "Must be greater than 0.");
+ }
+
+ timeout = value;
+ }
+ }
+
+ public string UserAgent { get; set; }
+
+ public AetherRequest ()
+ {
+ asm = new AsyncStateManager ();
+ }
+
+ public void BeginGetRequest (Uri uri)
+ {
+ CryOutThroughTheAetherAsync (uri, HttpMethod.GET, null, null);
+ }
+
+ public void BeginGetRequest (Uri uri, object userState)
+ {
+ CryOutThroughTheAetherAsync (uri, HttpMethod.GET, null, userState);
+ }
+
+ public void BeginPostRequest (Uri uri, byte[] postData)
+ {
+ CryOutThroughTheAetherAsync (uri, HttpMethod.POST, postData, null);
+ }
+
+ public void BeginPostRequest (Uri uri, byte[] postData, object userState)
+ {
+ CryOutThroughTheAetherAsync (uri, HttpMethod.POST, postData, userState);
+ }
+
+ public void CancelAsync ()
+ {
+ if (asm.SetCancelled ()) {
+ Abort ();
+ }
+ }
+
+ private void Abort ()
+ {
+ HttpWebRequest req = request;
+
+ if (req != null) {
+ req.Abort ();
+ }
+ }
+
+ private void RequestCompleted (RequestState state)
+ {
+ try {
+ asm.SetCompleted ();
+ OnCompleted (CreateAetherArgs (state));
+ } finally {
+ request = null;
+ state.Dispose ();
+ asm.Reset ();
+ }
+ }
+
+ private void HandleException (RequestState state, Exception e)
+ {
+ state.Error = e;
+ RequestCompleted (state);
+ }
+
+ private void CryOutThroughTheAetherAsync (Uri uri, HttpMethod method, byte[] postData, object userState)
+ {
+ asm.SetBusy ();
+
+ RequestState state = new RequestState () {
+ UserState = new object[2] { null, userState }
+ };
+
+ try {
+ request = WebRequest.Create (uri) as HttpWebRequest;
+
+ if (cookie_container != null) {
+ request.CookieContainer = cookie_container;
+ }
+
+ request.Timeout = Timeout;
+ request.UserAgent = UserAgent;
+ request.Credentials = Credentials;
+
+ request.AllowAutoRedirect = true;
+
+ state.Request = request;
+
+ switch (method) {
+ case HttpMethod.GET:
+ request.Method = "GET";
+ GetResponse (state);
+ break;
+ case HttpMethod.POST:
+ request.Method = "POST";
+ request.ContentType = ContentType;
+ SendRequest (postData, state);
+ break;
+ }
+ } catch (Exception e) {
+ state.Error = e;
+ RequestCompleted (state);
+ }
+ }
+
+ private void SendRequest (byte[] data, RequestState state)
+ {
+ state.WriteBuffer = data;
+ state.AddTimeout (OnTimeout, timeout, true, state);
+ request.BeginGetRequestStream (FuckitIJustNeedAName, state);
+ }
+
+ private void GetResponse (RequestState state)
+ {
+ state.AddTimeout (OnTimeout, timeout, true, state);
+ request.BeginGetResponse (ThisIsCthulhuGoAheadCaller, state);
+ }
+
+ private void FuckitIJustNeedAName (IAsyncResult ar) {
+ RequestState state = ar.AsyncState as RequestState;
+
+ try {
+ state.RemoveTimeout (true);
+ state.RequestStream = state.Request.EndGetRequestStream (ar);
+
+ state.AddTimeout (OnTimeout, timeout, false, state);
+ state.RequestStream.BeginWrite (state.WriteBuffer, 0, state.WriteBuffer.Length, EndWrite, state);
+ } catch (Exception e) {
+ HandleException (state, e);
+ }
+ }
+
+ private void ThisIsCthulhuGoAheadCaller (IAsyncResult ar)
+ {
+ // Wow, ok, hi. Uhh, first time caller, longtime dreamer of mad dreams.
+ RequestState state = ar.AsyncState as RequestState;
+
+ try {
+ state.RemoveTimeout (true);
+ state.Response = state.Request.EndGetResponse (ar) as HttpWebResponse;
+ state.ResponseStream = state.Response.GetResponseStream ();
+
+ state.AddTimeout (OnTimeout, timeout, false, state);
+ state.ResponseStream.BeginRead (state.ReadBuffer, 0, RequestState.BufferSize, EndRead, state);
+ } catch (Exception e) {
+ HandleException (state, e);
+ }
+ }
+
+ private void EndRead (IAsyncResult ar)
+ {
+ int nread = -1;
+ RequestState state = ar.AsyncState as RequestState;
+
+ try {
+ state.SetTimeoutHandle ();
+ nread = state.ResponseStream.EndRead (ar);
+
+ if (nread != 0) {
+ state.ReadData.Write (state.ReadBuffer, 0, nread);
+ state.ResponseStream.BeginRead (state.ReadBuffer, 0, RequestState.BufferSize, EndRead, state);
+ } else {
+ ((object[])(state.UserState))[0] = state.ReadData.ToArray ();
+ RequestCompleted (state);
+ }
+ } catch (Exception e) {
+ HandleException (state, e);
+ }
+ }
+
+ private void EndWrite (IAsyncResult ar)
+ {
+ RequestState state = ar.AsyncState as RequestState;
+
+ try {
+ state.RemoveTimeout (true);
+ state.RequestStream.EndWrite (ar);
+
+ if (state.RequestStream != null) {
+ state.RequestStream.Close ();
+ state.RequestStream = null;
+ }
+
+ GetResponse (state);
+ } catch (Exception e) {
+ HandleException (state, e);
+ }
+ }
+
+ private AetherRequestCompletedEventArgs CreateAetherArgs (RequestState state)
+ {
+ byte[] data = null;
+ Exception err = null;
+ bool timedout = false;
+ bool cancelled = false;
+
+ object[] userState = state.UserState as object[];
+
+ if (asm.Cancelled) {
+ cancelled = true;
+ } else if (asm.Timedout) {
+ timedout = true;
+ } else {
+ if (state.Error != null) {
+ if (state.Error is WebException) {
+ WebException e = state.Error as WebException;
+
+ if (e.Status == WebExceptionStatus.Timeout) {
+ timedout = true;
+ }
+ }
+ }
+
+ if (!timedout) {
+ err = state.Error;
+ data = userState[0] as byte[];
+ }
+ }
+
+ return new AetherRequestCompletedEventArgs (data, err, cancelled, timedout, userState[1]);
+ }
+
+ private void OnTimeout (object userState, bool timedOut)
+ {
+ if (timedOut) {
+ if (asm.SetTimedout ()) {
+ Abort ();
+ }
+ }
+ }
+
+ private void OnCompleted (AetherRequestCompletedEventArgs e) {
+ var handler = Completed;
+
+ if (handler != null) {
+ handler (this, e);
+ }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherRequestCompletedEventArgs.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherRequestCompletedEventArgs.cs
new file mode 100644
index 0000000..93fe226
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/AetherRequestCompletedEventArgs.cs
@@ -0,0 +1,55 @@
+//
+// AetherAsyncCompletedEventArgs.cs - Way too fucking long.
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.ComponentModel;
+
+namespace Banshee.Paas.Aether
+{
+ public class AetherRequestCompletedEventArgs : AsyncCompletedEventArgs
+ {
+ private readonly byte[] data;
+ private readonly bool timedout;
+
+ public byte[] Data {
+ get { return data; }
+ }
+
+ public bool Timedout {
+ get { return timedout; }
+ }
+
+ public AetherRequestCompletedEventArgs (byte[] data,
+ Exception err,
+ bool cancelled,
+ bool timedout,
+ object userState) : base (err, cancelled, userState)
+ {
+ this.data = data;
+ this.timedout = timedout;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/ChannelEventArgs.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/ChannelEventArgs.cs
new file mode 100644
index 0000000..60a5d07
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/ChannelEventArgs.cs
@@ -0,0 +1,57 @@
+//
+// ChannelEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Aether
+{
+ public class ChannelEventArgs : EventArgs
+ {
+ private readonly PaasChannel channel;
+ private readonly IEnumerable<PaasChannel> channels;
+
+ public PaasChannel Channel {
+ get { return channel; }
+ }
+
+ public IEnumerable<PaasChannel> Channels {
+ get { return channels; }
+ }
+
+ public ChannelEventArgs (PaasChannel channel)
+ {
+ this.channel = channel;
+ }
+
+ public ChannelEventArgs (IEnumerable<PaasChannel> channels)
+ {
+ this.channels = channels;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/ChannelUpdateStatus.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/ChannelUpdateStatus.cs
new file mode 100644
index 0000000..031cb90
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/ChannelUpdateStatus.cs
@@ -0,0 +1,37 @@
+//
+// ChannelUpdateStatus.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Banshee.Paas.Aether
+{
+ public enum ChannelUpdateStatus
+ {
+ None,
+ Waiting,
+ Updating
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/ItemEventArgs.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/ItemEventArgs.cs
new file mode 100644
index 0000000..d70fe0f
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/ItemEventArgs.cs
@@ -0,0 +1,57 @@
+//
+// ItemEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Aether
+{
+ public class ItemEventArgs : EventArgs
+ {
+ private readonly PaasItem item;
+ private readonly IEnumerable<PaasItem> items;
+
+ public PaasItem Item {
+ get { return item; }
+ }
+
+ public IEnumerable<PaasItem> Items {
+ get { return items; }
+ }
+
+ public ItemEventArgs (PaasItem item)
+ {
+ this.item = item;
+ }
+
+ public ItemEventArgs (IEnumerable<PaasItem> items)
+ {
+ this.items = items;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/GetCategoriesCompletedEventArgs.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/GetCategoriesCompletedEventArgs.cs
new file mode 100644
index 0000000..04fb5de
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/GetCategoriesCompletedEventArgs.cs
@@ -0,0 +1,47 @@
+//
+// GetCategoriesCompletedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+namespace Banshee.Paas.Aether.MiroGuide
+{
+ public class GetCategoriesCompletedEventArgs : RequestCompletedEventArgs
+ {
+ private readonly IEnumerable<MiroGuideCategoryInfo> categories;
+
+ public IEnumerable<MiroGuideCategoryInfo> Categories {
+ get { return categories; }
+ }
+
+ public GetCategoriesCompletedEventArgs (IEnumerable<MiroGuideCategoryInfo> categories,
+ Exception error, bool cancelled, bool timedout, object userState
+ ) : base (error, cancelled, MiroGuideClientMethod.GetChannels, timedout, userState)
+ {
+ this.categories = categories;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/GetChannelsEventArgs.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/GetChannelsEventArgs.cs
new file mode 100644
index 0000000..7d7a6b2
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/GetChannelsEventArgs.cs
@@ -0,0 +1,54 @@
+//
+// GetChannelsCompletedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+namespace Banshee.Paas.Aether.MiroGuide
+{
+ public class GetChannelsCompletedEventArgs : RequestCompletedEventArgs
+ {
+ private readonly SearchContext context;
+ private readonly IEnumerable<MiroGuideChannelInfo> channels;
+
+ public SearchContext Context {
+ get { return context; }
+ }
+
+ public IEnumerable<MiroGuideChannelInfo> Channels {
+ get { return channels; }
+ }
+
+ public GetChannelsCompletedEventArgs (SearchContext context,
+ IEnumerable<MiroGuideChannelInfo> channels,
+ Exception error, bool cancelled, bool timedout, object userState
+ ) : base (error, cancelled, MiroGuideClientMethod.GetChannels, timedout, userState)
+ {
+ this.context = context;
+ this.channels = channels;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideAccountInfo.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideAccountInfo.cs
new file mode 100644
index 0000000..e26536b
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideAccountInfo.cs
@@ -0,0 +1,107 @@
+//
+// MiroGuideAccountInfo.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Banshee.Paas.Aether.MiroGuide
+{
+ public class MiroGuideAccountInfo
+ {
+ private string username, password_hash, service_uri, session_id;
+
+ public EventHandler<EventArgs> Updated;
+
+ public string SessionID {
+ get { return session_id; }
+
+ set {
+ if (!String.IsNullOrEmpty (value) && value != session_id) {
+ session_id = value;
+ Notify ();
+ }
+ }
+ }
+
+ public string PasswordHash {
+ get { return password_hash; }
+
+ set {
+ if (!String.IsNullOrEmpty (value) && value != password_hash) {
+ password_hash = value;
+ Notify ();
+ }
+ }
+ }
+
+ public string ServiceUri {
+ get { return service_uri; }
+
+ set {
+ if (!String.IsNullOrEmpty (value) && value != service_uri) {
+ service_uri = value;
+ Notify ();
+ }
+ }
+ }
+
+ public string Username {
+ get { return username; }
+
+ set {
+ if (!String.IsNullOrEmpty (value) && value != username) {
+ username = value;
+ Notify ();
+ }
+ }
+ }
+
+ public MiroGuideAccountInfo (string serviceUri,
+ string sessionID,
+ string username,
+ string passwordHash)
+ {
+ session_id = sessionID;
+
+ password_hash = passwordHash;
+ this.username = username;
+ service_uri = serviceUri;
+ }
+
+ public void Notify ()
+ {
+ OnUpdated ();
+ }
+
+ protected virtual void OnUpdated ()
+ {
+ var handler = Updated;
+
+ if (handler != null) {
+ handler (this, EventArgs.Empty);
+ }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideCategoryInfo.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideCategoryInfo.cs
new file mode 100644
index 0000000..cbf38b8
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideCategoryInfo.cs
@@ -0,0 +1,51 @@
+//
+// MiroGuideCategoryInfo.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Hyena.Json;
+
+namespace Banshee.Paas.Aether.MiroGuide
+{
+ public class MiroGuideCategoryInfo
+ {
+ private JsonObject category_info;
+
+ public MiroGuideCategoryInfo (JsonObject categoryInfo)
+ {
+ category_info = categoryInfo;
+ }
+
+ public string Name {
+ get { return category_info["name"].ToString (); }
+ }
+
+ public string Url {
+ get { return category_info["url"].ToString (); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideChannelInfo.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideChannelInfo.cs
new file mode 100644
index 0000000..46436e3
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideChannelInfo.cs
@@ -0,0 +1,111 @@
+//
+// MiroGuideChannelInfo.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Hyena.Json;
+
+namespace Banshee.Paas.Aether.MiroGuide
+{
+ public class MiroGuideChannelInfo
+ {
+ private JsonObject channel_info;
+
+ public MiroGuideChannelInfo (JsonObject channelInfo)
+ {
+ channel_info = channelInfo;
+ }
+
+ public long ID {
+ get { return Int32.Parse (channel_info["id"].ToString ()); }
+ }
+
+ public IEnumerable<string> Categories {
+ get {
+ if (channel_info.ContainsKey ("category")) {
+ foreach (string j in channel_info["category"] as JsonArray) {
+ if (j != null) {
+ yield return j;
+ }
+ }
+ }
+ }
+ }
+
+ public string Description {
+ get { return channel_info["description"].ToString (); }
+ }
+
+ public bool IsHD {
+ get { return Boolean.Parse (channel_info["hi_def"].ToString ()); }
+ }
+
+ public IEnumerable<string> Languages {
+ get {
+ if (channel_info.ContainsKey ("language")) {
+ foreach (string j in channel_info["language"] as JsonArray) {
+ if (j != null) {
+ yield return j;
+ }
+ }
+ }
+ }
+ }
+
+ public string Name {
+ get { return channel_info["name"].ToString (); }
+ }
+
+ public string Publisher {
+ get { return channel_info["publisher"].ToString (); }
+ }
+
+ public IEnumerable<string> Tags {
+ get {
+ if (channel_info.ContainsKey ("tag")) {
+ foreach (string j in channel_info["tag"] as JsonArray) {
+ if (j != null) {
+ yield return j;
+ }
+ }
+ }
+ }
+ }
+
+ public string ThumbUrl { // HACK UNTIL FIX'd BY MG GUYS!!!
+ get { return channel_info["thumbnail_url"].ToString ().Replace ("/370x247/", "/original/"); }
+ }
+
+ public string Url {
+ get { return channel_info["url"].ToString (); }
+ }
+
+ public string WebsiteUrl {
+ get { return channel_info["website_url"].ToString (); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideClient.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideClient.cs
new file mode 100644
index 0000000..7c5259f
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideClient.cs
@@ -0,0 +1,750 @@
+//
+// MiroGuideClient.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// This and AetherRequest are still a bit of an experiment. This should be
+// implemented as a more formal state machine. This will not be used by users for sometime.
+
+using System;
+using System.IO;
+using System.Net;
+using System.Web;
+
+using System.Linq;
+using System.Text;
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+
+using Hyena;
+using Hyena.Json;
+
+using Migo2.Async;
+
+using Banshee.ServiceStack;
+
+using Banshee.Base;
+using Banshee.Paas.Data;
+using Banshee.Paas.Aether;
+
+namespace Banshee.Paas.Aether.MiroGuide
+{
+ public sealed class MiroGuideClient : AetherClient
+ {
+ private MiroGuideAccountInfo account;
+
+ private string session_id;
+ private Uri aether_service_uri;
+
+ private AsyncStateManager asm;
+
+ private AetherRequest request;
+
+ public EventHandler<RequestCompletedEventArgs> Completed;
+
+ public EventHandler<GetChannelsCompletedEventArgs> GetChannelsCompleted;
+ public EventHandler<GetCategoriesCompletedEventArgs> GetCategoriesCompleted;
+
+ public EventHandler<SubscriptionRequestedEventArgs> SubscriptionRequested;
+
+ public MiroGuideAccountInfo Account {
+ get { return Account; }
+ }
+
+ public ICredentials Credentials { get; set; }
+
+ public string ServiceUri {
+ get { return aether_service_uri.AbsoluteUri; }
+
+ set {
+ Uri tmp = new Uri (value.Trim ().TrimEnd ('/'));
+
+ if (tmp.Scheme != "http" && tmp.Scheme != "https") {
+ throw new UriFormatException ("Scheme must be either http or https.");
+ }
+
+ aether_service_uri = tmp;
+ }
+ }
+
+ public string SessionID {
+ get {
+ return session_id;
+ }
+ }
+
+ public string UserAgent { get; set; }
+
+ public MiroGuideClient (MiroGuideAccountInfo account)
+ {
+ if (account == null) {
+ throw new ArgumentNullException ("account");
+ }
+
+ asm = new AsyncStateManager ();
+
+ this.account = account;
+ session_id = account.SessionID;
+
+ this.account.Updated += (sender, e) => {
+ lock (SyncRoot) {
+ if (!String.IsNullOrEmpty (account.ServiceUri)) {
+ ServiceUri = account.ServiceUri;
+ }
+
+ session_id = account.SessionID;
+ }
+ };
+
+ if (!String.IsNullOrEmpty (account.ServiceUri)) {
+ ServiceUri = account.ServiceUri;
+ }
+ }
+
+ public void CancelAsync ()
+ {
+ lock (SyncRoot) {
+ if (asm.Busy && asm.SetCancelled ()) {
+ if (request != null) {
+ request.CancelAsync ();
+ }
+ }
+ }
+ }
+
+ public void GetCategoriesAsync ()
+ {
+ GetCategoriesAsync (null);
+ }
+
+ public void GetCategoriesAsync (object userState)
+ {
+ lock (SyncRoot) {
+ if (asm.Busy) {
+ return;
+ }
+
+ NameValueCollection nvc = new NameValueCollection ();
+ nvc.Add ("datatype", "json");
+
+ BeginRequest (
+ CreateGetRequestState (
+ MiroGuideClientMethod.GetCategories, "/api/list_categories", nvc,
+ ServiceFlags.None, null, userState
+ ), true
+ );
+ }
+ }
+
+ public void GetChannelsAsync (MiroGuideFilterType filterType,
+ string filterValue,
+ MiroGuideSortType sortType,
+ bool reverse,
+ uint limit, uint offset)
+ {
+ GetChannelsAsync (filterType, filterValue, sortType, reverse, limit, offset, null);
+ }
+
+ public void GetChannelsAsync (MiroGuideFilterType filterType,
+ string filterValue,
+ MiroGuideSortType sortType,
+ bool reverse,
+ uint limit,
+ uint offset,
+ object userState)
+ {
+ if (String.IsNullOrEmpty (filterValue)) {
+ return;
+ }
+
+ GetChannelsAsync (new SearchContext (filterType, filterValue, sortType, reverse, limit, offset), userState);
+ }
+
+ public void GetChannelsAsync (SearchContext context)
+ {
+ GetChannelsAsync (context, null);
+ }
+
+ public void GetChannelsAsync (SearchContext context, object userState)
+ {
+ if (context == null) {
+ throw new ArgumentNullException ("context");
+ }
+
+ NameValueCollection nvc = new NameValueCollection ();
+
+ nvc.Add ("datatype", "json");
+
+ nvc.Add ("filter", ToQueryPart (context.FilterType));
+ nvc.Add ("filter_value", context.FilterValue);
+
+ if (context.SortType != MiroGuideSortType.Relevance) {
+ nvc.Add (
+ "sort", String.Format ("{0}{1}", ((context.Reverse) ? "-" : String.Empty),
+ ToQueryPart (context.SortType))
+ );
+ }
+
+ nvc.Add ("limit", context.Limit.ToString ());
+ nvc.Add ("offset", (context.Offset+context.Count).ToString ());
+
+ lock (SyncRoot) {
+ if (asm.Busy) { // Remove!!! Allow requests to be queued in the future.
+ return;
+ }
+
+ BeginRequest (
+ CreateGetRequestState (
+ MiroGuideClientMethod.GetChannels, "/api/get_channels", nvc,
+ ServiceFlags.None, context, userState
+ ), true
+ );
+ }
+ }
+
+ public void RequestSubsubscription (Uri uri)
+ {
+ if (uri == null) {
+ throw new ArgumentNullException ("uri");
+ }
+
+ OnSubscriptionRequested (uri);
+ }
+
+ public void RequestSubsubscription (IEnumerable<Uri> uris)
+ {
+ if (uris == null) {
+ throw new ArgumentNullException ("uris");
+ }
+
+ OnSubscriptionRequested (uris);
+ }
+
+ private void GetSessionAsync (MiroGuideRequestState callingMethodState)
+ {
+ if (callingMethodState == null) {
+ throw new ArgumentNullException ("callingMethodState");
+ }
+
+ NameValueCollection nvc = new NameValueCollection ();
+ nvc.Add ("datatype", "json");
+
+ lock (SyncRoot) {
+ BeginRequest (
+ CreateGetRequestState (
+ MiroGuideClientMethod.GetSession, "/api/get_session", nvc,
+ ServiceFlags.None, null, null, callingMethodState
+ ), false
+ );
+ }
+ }
+
+ public void AddSubscriptionsAsync (IEnumerable<string> urls)
+ {
+ AddSubscriptionsAsync (urls, null);
+ }
+
+ public void AddSubscriptionsAsync (IEnumerable<string> urls, object userState)
+ {
+ ManageSubscriptionsAsync (MiroGuideClientMethod.AddSubscriptions, urls, userState);
+ }
+
+ public void DeleteSubscriptionsAsync (IEnumerable<string> urls)
+ {
+ DeleteSubscriptionsAsync (urls, null);
+ }
+
+ public void DeleteSubscriptionsAsync (IEnumerable<string> urls, object userState)
+ {
+ ManageSubscriptionsAsync (MiroGuideClientMethod.DelSubscriptions, urls, userState);
+ }
+
+ private void ManageSubscriptionsAsync (MiroGuideClientMethod method, IEnumerable<string> urls, object userState)
+ {
+ lock (SyncRoot) {
+ if (asm.Busy) {
+ return;
+ }
+
+ string fragment;
+
+ if (method == MiroGuideClientMethod.AddSubscriptions) {
+ fragment = "/api/add_subscriptions";
+ } else {
+ fragment = "/api/del_subscriptions";
+ }
+
+ JsonObject json_data = new JsonObject ();
+ JsonArray request_data = new JsonArray ();
+
+ request_data.AddRange (urls.Select (u => HttpUtility.UrlEncode (u)).Cast<object>());
+ json_data["urls"] = request_data;
+
+ BeginRequest (
+ CreatePostRequestState (
+ method, fragment,
+ "application/x-www-form-urlencoded", null,
+ String.Format ("urls={0}", SerializeJson (json_data)),
+ ServiceFlags.RequireAuth, null, null
+ ), true
+ );
+ }
+ }
+
+ public void GetSubscriptionsAsync ()
+ {
+ GetSubscriptionsAsync (null);
+ }
+
+ public void GetSubscriptionsAsync (object userState)
+ {
+ lock (SyncRoot) {
+ if (asm.Busy) {
+ return;
+ }
+
+ NameValueCollection nvc = new NameValueCollection ();
+ nvc.Add ("datatype", "json");
+
+ BeginRequest (
+ CreateGetRequestState (
+ MiroGuideClientMethod.GetSubscriptions, "/api/get_subscriptions", nvc,
+ ServiceFlags.RequireAuth, null, userState
+ ), true
+ );
+ }
+ }
+
+ private AetherRequest CreateRequest ()
+ {
+ AetherRequest req = new AetherRequest () {
+ Timeout = (30 * 1000),
+ Credentials = Credentials,
+ UserAgent = UserAgent
+ };
+
+ req.Completed += OnRequestCompletedHandler;
+
+ return req;
+ }
+
+ private void BeginRequest (MiroGuideRequestState state, bool changeState)
+ {
+ if (changeState) {
+ asm.SetBusy ();
+ OnStateChanged (AetherClientState.Idle, AetherClientState.Busy);
+ }
+
+ if (asm.Cancelled) {
+ state = GetHead (state);
+ state.Cancelled = true;
+ Complete (state);
+ return;
+ }
+
+ try {
+ if (state.ServiceFlags != ServiceFlags.None) {
+ if ((state.ServiceFlags & ServiceFlags.RequireAuth) != 0) {
+ if (String.IsNullOrEmpty (SessionID)) {
+ GetSessionAsync (state);
+ return;
+ } else {
+ state.AddParameter ("session", SessionID);
+ }
+ }
+ }
+
+ request = CreateRequest ();
+
+ switch (state.HttpMethod) {
+ case HttpMethod.GET:
+ request.BeginGetRequest (state.GetFullUri (), state);
+ break;
+ case HttpMethod.POST:
+ request.ContentType = state.ContentType;
+ request.BeginPostRequest (
+ state.GetFullUri (), Encoding.UTF8.GetBytes (state.RequestData), state
+ );
+ break;
+ }
+ } catch (Exception e) {
+ state = GetHead (state);
+ state.Error = e;
+ Hyena.Log.Exception (e);
+ Complete (state);
+ }
+ }
+
+ private void Complete (MiroGuideRequestState state)
+ {
+ try {
+ switch (state.Method) {
+ case MiroGuideClientMethod.GetSession:
+ HandleGetSessionResponse (state.ResponseData);
+ break;
+ }
+ } catch (Exception e) {
+ state = GetHead (state);
+ state.Error = e;
+ Hyena.Log.Exception (e);
+ }
+
+ if (state.CallingState != null) {
+ BeginRequest (state.CallingState, false);
+ return;
+ } else {
+ switch (state.Method) {
+ case MiroGuideClientMethod.GetChannels:
+ HandleGetChannelsResponse (state);
+ break;
+ case MiroGuideClientMethod.GetCategories:
+ HandleGetCategoriesResponse (state);
+ break;
+ case MiroGuideClientMethod.GetSubscriptions:
+ HandleGetSubscriptionsResponse (state);
+ break;
+ }
+
+ asm.Reset ();
+ OnStateChanged (AetherClientState.Busy, AetherClientState.Idle);
+ OnCompleted (state);
+ }
+ }
+
+ private MiroGuideRequestState CreateGetRequestState (MiroGuideClientMethod acm,
+ string path,
+ NameValueCollection parameters,
+ ServiceFlags flags,
+ object internalState,
+ object userState)
+ {
+ return CreateGetRequestState (
+ acm, path, parameters, flags, internalState, userState, null
+ );
+ }
+
+ private MiroGuideRequestState CreateGetRequestState (MiroGuideClientMethod acm,
+ string path,
+ NameValueCollection parameters,
+ ServiceFlags flags,
+ object internalState,
+ object userState,
+ MiroGuideRequestState callingState)
+ {
+ return CreateRequestState (
+ acm, path, HttpMethod.GET, null, parameters, null,
+ flags, internalState, userState, callingState
+ );
+ }
+
+ private MiroGuideRequestState CreatePostRequestState (MiroGuideClientMethod acm,
+ string path,
+ string contentType,
+ NameValueCollection parameters,
+ string requestData,
+ ServiceFlags flags,
+ object internalState,
+ object userState)
+ {
+ return CreatePostRequestState (
+ acm, path, contentType, parameters, requestData,
+ flags, internalState, userState, null
+ );
+ }
+
+ private MiroGuideRequestState CreatePostRequestState (MiroGuideClientMethod acm,
+ string path,
+ string contentType,
+ NameValueCollection parameters,
+ string requestData,
+ ServiceFlags flags,
+ object internalState,
+ object userState,
+ MiroGuideRequestState callingState)
+ {
+ return CreateRequestState (
+ acm, path, HttpMethod.POST, contentType, parameters, requestData,
+ flags, internalState, userState, callingState
+ );
+ }
+
+ private MiroGuideRequestState CreateRequestState (MiroGuideClientMethod acm,
+ string path,
+ HttpMethod method,
+ string contentType,
+ NameValueCollection parameters,
+ string requestData,
+ ServiceFlags flags,
+ object internalState,
+ object userState,
+ MiroGuideRequestState callingState)
+ {
+ MiroGuideRequestState state = new MiroGuideRequestState () {
+ Method = acm,
+ RequestData = requestData,
+ HttpMethod = method,
+ ContentType = contentType,
+ ServiceFlags = flags,
+ UserState = userState,
+ InternalState = internalState,
+ CallingState = callingState,
+ BaseUri = ServiceUri+path
+ };
+
+ if (parameters != null) {
+ state.AddParameters (parameters);
+ }
+
+ return state;
+ }
+
+ private MiroGuideRequestState GetHead (MiroGuideRequestState state)
+ {
+ while (state.CallingState != null) {
+ state = state.CallingState;
+ }
+
+ return state;
+ }
+
+ private object DeserializeJson (string response)
+ {
+ Deserializer d = new Deserializer ();
+ d.SetInput (response);
+ return d.Deserialize ();
+ }
+
+ private string SerializeJson (object json)
+ {
+ return new Serializer (json).Serialize ();
+ }
+
+ private void HandleGetSessionResponse (string response)
+ {
+ object session;
+ JsonObject resp = DeserializeJson (response) as JsonObject;
+
+ if (resp.TryGetValue ("session", out session) && !String.IsNullOrEmpty (session as string)) {
+ session_id = session as string;
+ account.SessionID = session_id;
+
+ ThreadAssist.ProxyToMain (delegate {
+ Banshee.Web.Browser.Open (
+ account.ServiceUri+String.Format ("/api/authenticate?session={0}", session)
+ );
+ });
+ } else {
+ throw new Exception ("Response did not contain session id");
+ }
+ }
+
+ private void HandleGetCategoriesResponse (MiroGuideRequestState state)
+ {
+ List<MiroGuideCategoryInfo> categories = null;
+
+ try {
+ if (state.Succeeded) {
+ categories = new List<MiroGuideCategoryInfo> ();
+
+ foreach (JsonObject o in DeserializeJson (state.ResponseData) as JsonArray) {
+ try {
+ categories.Add (new MiroGuideCategoryInfo (o));
+ } catch { continue; }
+ }
+ }
+ } catch (Exception e) {
+ state.Error = e;
+ } finally {
+ OnGetCategoriesCompleted (state, categories);
+ }
+ }
+
+ private void HandleGetChannelsResponse (MiroGuideRequestState state)
+ {
+ List<MiroGuideChannelInfo> channels = new List<MiroGuideChannelInfo> ();
+
+ try {
+ if (state.Succeeded) {
+ foreach (JsonObject o in DeserializeJson (state.ResponseData) as JsonArray) {
+ try {
+ channels.Add (new MiroGuideChannelInfo (o));
+ } catch { continue; }
+ }
+
+ SearchContext context = state.InternalState as SearchContext;
+ context.IncrementResultCount ((uint)channels.Count);
+ }
+ } catch (Exception e) {
+ state.Error = e;
+ } finally {
+ OnGetChannelsCompleted (state, channels);
+ }
+ }
+
+ private void HandleGetSubscriptionsResponse (MiroGuideRequestState state)
+ {
+ List<Uri> urls = null;
+
+ try {
+ if (state.Succeeded) {
+ urls = new List<Uri> ();
+ JsonArray ary = (DeserializeJson (state.ResponseData) as JsonObject)["urls"] as JsonArray;
+
+ foreach (var o in ary) {
+ try {
+ urls.Add (new Uri (o.ToString ()));
+ } catch { continue; }
+ }
+
+ if (urls.Count > 0) {
+ RequestSubsubscription (urls);
+ }
+ }
+ } catch (Exception e) {
+ state.Error = e;
+ }
+ }
+
+ private void OnRequestCompletedHandler (object sender, AetherRequestCompletedEventArgs e)
+ {
+ lock (SyncRoot) {
+ request.Completed -= OnRequestCompletedHandler;
+ MiroGuideRequestState state = e.UserState as MiroGuideRequestState;
+
+ state.Completed = true;
+ state.ResponseData = (e.Data != null) ? Encoding.UTF8.GetString (e.Data) : String.Empty;
+
+ if (e.Cancelled || asm.Cancelled) {
+ state = GetHead (state);
+ state.Cancelled = true;
+ } else if (e.Timedout) {
+ state = GetHead (state);
+ state.Timedout = true;
+ } else if (e.Error != null) {
+ state = GetHead (state);
+ state.Error = e.Error;
+ Hyena.Log.Exception (e.Error);
+ }
+
+ Complete (state);
+ }
+ }
+
+ private void OnCompleted (MiroGuideRequestState state)
+ {
+ var handler = Completed;
+
+ RequestCompletedEventArgs e = new RequestCompletedEventArgs (
+ state.Error, state.Cancelled, state.Method, state.Timedout, state.UserState
+ );
+
+ if (handler != null) {
+ EventQueue.Register (new EventWrapper<RequestCompletedEventArgs> (handler, this, e));
+ }
+ }
+
+ private void OnGetCategoriesCompleted (MiroGuideRequestState state, IEnumerable<MiroGuideCategoryInfo> categories)
+ {
+ var handler = GetCategoriesCompleted;
+
+ GetCategoriesCompletedEventArgs e = new GetCategoriesCompletedEventArgs (
+ categories, state.Error, state.Cancelled, state.Timedout, state.UserState
+ );
+
+ if (handler != null) {
+ EventQueue.Register (new EventWrapper<GetCategoriesCompletedEventArgs> (handler, this, e));
+ }
+ }
+
+ private void OnGetChannelsCompleted (MiroGuideRequestState state, IEnumerable<MiroGuideChannelInfo> channels)
+ {
+ var handler = GetChannelsCompleted;
+
+ GetChannelsCompletedEventArgs e = new GetChannelsCompletedEventArgs (
+ state.InternalState as SearchContext, channels,
+ state.Error, state.Cancelled, state.Timedout, state.UserState
+ );
+
+ if (handler != null) {
+ EventQueue.Register (new EventWrapper<GetChannelsCompletedEventArgs> (handler, this, e));
+ }
+ }
+
+ private void OnSubscriptionRequested (Uri uri)
+ {
+ OnSubscriptionRequested (new SubscriptionRequestedEventArgs (uri));
+ }
+
+ private void OnSubscriptionRequested (IEnumerable<Uri> uris)
+ {
+ OnSubscriptionRequested (new SubscriptionRequestedEventArgs (uris));
+ }
+
+ private void OnSubscriptionRequested (SubscriptionRequestedEventArgs e)
+ {
+ var handler = SubscriptionRequested;
+
+ if (handler != null) {
+ EventQueue.Register (
+ new EventWrapper<SubscriptionRequestedEventArgs> (handler, this, e)
+ );
+ }
+ }
+
+ private string ToQueryPart (MiroGuideFilterType type)
+ {
+ switch (type)
+ {
+ case MiroGuideFilterType.Category: return "category";
+ case MiroGuideFilterType.Language: return "language";
+ case MiroGuideFilterType.Name: return "name";
+ case MiroGuideFilterType.Search: return "search";
+ case MiroGuideFilterType.Tag: return "tag";
+ case MiroGuideFilterType.HD: return "hd";
+ case MiroGuideFilterType.Featured: return "featured";
+ case MiroGuideFilterType.TopRated: return "feed";
+ case MiroGuideFilterType.Popular: goto case MiroGuideFilterType.TopRated;
+ default:
+ goto case MiroGuideFilterType.Search;
+ }
+ }
+
+ private string ToQueryPart (MiroGuideSortType type)
+ {
+ switch (type)
+ {
+ case MiroGuideSortType.Age: return "age";
+ case MiroGuideSortType.ID: return "id";
+ case MiroGuideSortType.Name: return "name";
+ case MiroGuideSortType.Popular: return "popular";
+ case MiroGuideSortType.Rating: return "rating";
+ case MiroGuideSortType.Relevance: return String.Empty;
+ default:
+ goto case MiroGuideSortType.Name;
+ }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideClientError.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideClientError.cs
new file mode 100644
index 0000000..8c25ddc
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideClientError.cs
@@ -0,0 +1,36 @@
+//
+// MiroGuideClientError.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Banshee.Paas.Aether.MiroGuide
+{
+ public enum MiroGuideClientError
+ {
+ ConnectionError,
+ InvalidCredentials
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideClientMethod.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideClientMethod.cs
new file mode 100644
index 0000000..feb0df2
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideClientMethod.cs
@@ -0,0 +1,37 @@
+//
+// MiroGuideClientMethod.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+namespace Banshee.Paas.Aether.MiroGuide
+{
+ public enum MiroGuideClientMethod {
+ GetChannels,
+ GetSession,
+ GetCategories,
+ AddSubscriptions,
+ DelSubscriptions,
+ GetSubscriptions
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideFilterType.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideFilterType.cs
new file mode 100644
index 0000000..97d7403
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideFilterType.cs
@@ -0,0 +1,44 @@
+//
+// MiroGuideFilterType.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Banshee.Paas.Aether.MiroGuide
+{
+ public enum MiroGuideFilterType : int
+ {
+ None,
+ Category,
+ Language,
+ Name,
+ Search,
+ Tag,
+ HD,
+ Featured,
+ TopRated,
+ Popular,
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideMethodCompletedEventArgs.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideMethodCompletedEventArgs.cs
new file mode 100644
index 0000000..6037219
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideMethodCompletedEventArgs.cs
@@ -0,0 +1,46 @@
+//
+// MiroGuideMethodCompletedEventArgs.cs - Way too fucking long.
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.ComponentModel;
+
+namespace Banshee.Paas.Aether.MiroGuide
+{
+ public class MiroGuideMethodCompletedEventArgs : AsyncCompletedEventArgs
+ {
+ private readonly bool timedout;
+
+ public bool Timedout {
+ get { return timedout; }
+ }
+
+ public MiroGuideMethodCompletedEventArgs (Exception err, bool cancelled, bool timedout, object userState)
+ : base (err, cancelled, userState)
+ {
+ this.timedout = timedout;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideRequestState.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideRequestState.cs
new file mode 100644
index 0000000..0b8abf3
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideRequestState.cs
@@ -0,0 +1,132 @@
+//
+// MiroGuideRequestState.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Text;
+
+using System.Collections.Generic;
+using System.Collections.Specialized;
+
+namespace Banshee.Paas.Aether.MiroGuide
+{
+ class MiroGuideRequestState
+ {
+ private Dictionary<string, string> parameters;
+
+ public bool Timedout { get; set; }
+ public bool Cancelled { get; set; }
+ public bool Completed { get; set; }
+
+ public string BaseUri { get; set; }
+ public Exception Error { get; set; }
+
+ public string ContentType { get; set; }
+ public HttpMethod HttpMethod { get; set; }
+
+ public MiroGuideClientMethod Method { get; set; }
+
+ public string RequestData { get; set; }
+ public string ResponseData { get; set; }
+
+ public ServiceFlags ServiceFlags { get; set; }
+
+ public object UserState { get; set; }
+ public object InternalState { get; set; }
+
+ public MiroGuideRequestState CallingState { get; set; }
+
+ public bool Succeeded {
+ get { return Completed & !Timedout & !Cancelled & (Error == null); }
+ }
+
+ public MiroGuideRequestState ()
+ {
+ parameters = new Dictionary<string, string> ();
+ }
+
+ public Uri GetFullUri ()
+ {
+ return new Uri (String.Format ("{0}{1}", BaseUri, GetParameterString ()));
+ }
+
+ public void AddParameter (string key, string val)
+ {
+ if (parameters.ContainsKey (key)) {
+ parameters[key] = val;
+ } else {
+ parameters.Add (key, val);
+ }
+ }
+
+ public void AddParameters (NameValueCollection nvc)
+ {
+ if (nvc != null) {
+ foreach (string key in nvc) {
+ AddParameter (key, nvc[key]);
+ }
+ }
+ }
+
+ public void ClearParameters ()
+ {
+ parameters.Clear ();
+ }
+
+ public void RemoveParameter (string key)
+ {
+ if (parameters.ContainsKey (key)) {
+ parameters.Remove (key);
+ }
+ }
+
+ private string GetParameterString ()
+ {
+ if (parameters.Count == 0) {
+ return String.Empty;
+ }
+
+ bool first = true;
+ StringBuilder sb = new StringBuilder ();
+
+ sb.Append ('?');
+
+ foreach (KeyValuePair<string, string> kvp in parameters) {
+ if (!first) {
+ sb.Append ('&');
+ }
+
+ sb.AppendFormat ("{0}={1}",
+ System.Web.HttpUtility.UrlEncode (kvp.Key),
+ System.Web.HttpUtility.UrlEncode (kvp.Value)
+ );
+
+ first = false;
+ }
+
+ return sb.ToString ();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideSortType.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideSortType.cs
new file mode 100644
index 0000000..d072625
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideSortType.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Banshee.Paas
+{
+ public enum MiroGuideSortType
+ {
+ None = 0,
+ Age = 1,
+ ID = 2,
+ Name = 3,
+ Popular = 4,
+ Rating = 5,
+ Relevance = 6
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/RequestCompletedEventArgs.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/RequestCompletedEventArgs.cs
new file mode 100644
index 0000000..0851925
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/RequestCompletedEventArgs.cs
@@ -0,0 +1,55 @@
+//
+// RequestCompletedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.ComponentModel;
+
+namespace Banshee.Paas.Aether.MiroGuide
+{
+ public class RequestCompletedEventArgs : AsyncCompletedEventArgs
+ {
+ private readonly MiroGuideClientMethod method;
+ private readonly bool timedout;
+
+ public MiroGuideClientMethod Method {
+ get { return method; }
+ }
+
+ public bool Timedout {
+ get { return timedout; }
+ }
+
+ public RequestCompletedEventArgs (Exception err,
+ bool cancelled,
+ MiroGuideClientMethod method,
+ bool timedout,
+ object userState) : base (err, cancelled, userState)
+ {
+ this.method = method;
+ this.timedout = timedout;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/SearchContext.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/SearchContext.cs
new file mode 100644
index 0000000..35cfda9
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/SearchContext.cs
@@ -0,0 +1,126 @@
+//
+// SearchContext.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Banshee.Paas.Aether.MiroGuide
+{
+ public class SearchContext
+ {
+ private int page;
+ private uint count;
+ private uint limit;
+ private uint offset;
+ private bool reverse;
+ private bool channels_available;
+
+ private MiroGuideSortType sort_type;
+ private readonly string filter_value;
+ private readonly MiroGuideFilterType filter_type;
+
+ public uint Count {
+ get { return count; }
+ }
+
+ public uint Limit {
+ get { return limit; }
+ set { limit = value; }
+ }
+
+ public int Page {
+ get { return page; }
+ }
+
+ public uint Offset {
+ get { return offset; }
+ }
+
+ public bool Reverse {
+ get { return reverse; }
+ set { reverse = value; }
+ }
+
+ public bool ChannelsAvailable {
+ get { return channels_available; }
+ }
+
+ public MiroGuideFilterType FilterType {
+ get { return filter_type; }
+ }
+
+ public string FilterValue {
+ get { return filter_value; }
+ }
+
+ public MiroGuideSortType SortType {
+ get { return sort_type; }
+ set { sort_type = value; }
+ }
+
+ public SearchContext (MiroGuideFilterType filterType,
+ string filterValue,
+ MiroGuideSortType sortType,
+ bool reverse, uint limit, uint offset)
+ {
+ if (String.IsNullOrEmpty (filterValue)) {
+ return;
+ }
+
+ page = -1;
+ count = 0;
+
+ channels_available = true;
+
+ this.limit = limit;
+ this.offset = offset;
+ this.reverse = reverse;
+
+ sort_type = sortType;
+
+ filter_type = filterType;
+ filter_value = filterValue;
+ }
+
+ public void IncrementResultCount (uint results)
+ {
+ ++page;
+ count += results;
+
+ if (results < limit) {
+ channels_available = false;
+ }
+ }
+
+ public void Reset ()
+ {
+ page = -1;
+ count = 0;
+ offset = 0;
+
+ channels_available = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/ServiceMethodFlags.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/ServiceMethodFlags.cs
new file mode 100644
index 0000000..b604419
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/ServiceMethodFlags.cs
@@ -0,0 +1,37 @@
+//
+// ServiceMethodFlags.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Banshee.Paas.Aether.MiroGuide
+{
+ [Flags]
+ enum ServiceFlags
+ {
+ None = 0x00,
+ RequireAuth = 0x01
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/SubscriptionRequestedEventArgs.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/SubscriptionRequestedEventArgs.cs
new file mode 100644
index 0000000..2d568a1
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/SubscriptionRequestedEventArgs.cs
@@ -0,0 +1,55 @@
+//
+// SubscriptionRequestedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+namespace Banshee.Paas.Aether.MiroGuide
+{
+ public class SubscriptionRequestedEventArgs : EventArgs
+ {
+ private readonly Uri uri;
+ private readonly IEnumerable<Uri> uris;
+
+ public Uri Uri {
+ get { return uri; }
+ }
+
+ public IEnumerable<Uri> Uris {
+ get { return uris; }
+ }
+
+ public SubscriptionRequestedEventArgs (Uri uri)
+ {
+ this.uri = uri;
+ }
+
+ public SubscriptionRequestedEventArgs (IEnumerable<Uri> uris)
+ {
+ this.uris = uris;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/RequestState.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/RequestState.cs
new file mode 100644
index 0000000..c2a790f
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/RequestState.cs
@@ -0,0 +1,141 @@
+//
+// RequestState.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.IO;
+using System.Net;
+using System.Text;
+using System.Threading;
+
+// This needs to be added to Migo and used in the AsyncWebClient.
+
+namespace Banshee.Paas.Aether
+{
+ class RequestState : IDisposable
+ {
+ public const int BufferSize = 8192;
+
+ private AutoResetEvent timeout_handle;
+ private RegisteredWaitHandle registered_timeout_handle;
+
+ public byte[] ReadBuffer { get; set; }
+ public byte[] WriteBuffer { get; set; }
+
+ public Exception Error { get; set; }
+
+ public HttpWebRequest Request { get; set; }
+ public HttpWebResponse Response { get; set; }
+
+ public MemoryStream ReadData { get; set; }
+
+ public Stream RequestStream {get; set; }
+ public Stream ResponseStream {get; set; }
+
+ public object UserState { get; set; }
+
+ public RequestState ()
+ {
+ ReadBuffer = new byte[BufferSize];
+ ReadData = new MemoryStream ();
+ }
+
+ public void Dispose ()
+ {
+ RemoveTimeout ();
+
+ if (ReadData != null) {
+ ReadData.Close ();
+ ReadData = null;
+ }
+
+ if (Response != null) {
+ Response.Close ();
+ Response = null;
+ }
+
+ if (RequestStream != null) {
+ RequestStream.Close ();
+ RequestStream = null;
+ }
+
+ if (ResponseStream != null) {
+ ResponseStream.Close ();
+ ResponseStream = null;
+ }
+
+ Request = null;
+ UserState = null;
+
+ ReadBuffer = null;
+ WriteBuffer = null;
+ }
+
+ public void AddTimeout (WaitOrTimerCallback callback, int timeout, bool executeOnlyOnce, object state)
+ {
+ if (timeout_handle != null) {
+ throw new InvalidOperationException ("Cannot nest timeouts.");
+ }
+
+ timeout_handle = new AutoResetEvent (false);
+
+ if (executeOnlyOnce) {
+ ThreadPool.RegisterWaitForSingleObject (
+ timeout_handle, callback, state, timeout, true
+ );
+ } else {
+ registered_timeout_handle = ThreadPool.RegisterWaitForSingleObject (
+ timeout_handle, callback, state, timeout, false
+ );
+ }
+ }
+
+ public void RemoveTimeout ()
+ {
+ RemoveTimeout (false);
+ }
+
+ public void RemoveTimeout (bool flag)
+ {
+ if (flag) {
+ SetTimeoutHandle ();
+ }
+
+ if (registered_timeout_handle != null) {
+ registered_timeout_handle.Unregister (null);
+ registered_timeout_handle = null;
+ }
+
+ timeout_handle = null;
+ }
+
+ public void SetTimeoutHandle ()
+ {
+ if (timeout_handle != null) {
+ timeout_handle.Set ();
+ }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/ChannelUpdateCompletedEventArgs.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/ChannelUpdateCompletedEventArgs.cs
new file mode 100644
index 0000000..2865dd8
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/ChannelUpdateCompletedEventArgs.cs
@@ -0,0 +1,53 @@
+//
+// ChannelUpdateCompletedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Aether.Syndication
+{
+ public class ChannelUpdateCompletedEventArgs : ChannelEventArgs
+ {
+ private readonly Exception err;
+ private readonly bool succeeded;
+
+ public Exception Error {
+ get { return err; }
+ }
+
+ public bool Succeeded {
+ get { return succeeded; }
+ }
+
+ public ChannelUpdateCompletedEventArgs (PaasChannel channel, bool succeeded, Exception err) : base (channel)
+ {
+ this.err = err;
+ this.succeeded = succeeded;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/ChannelUpdateManager.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/ChannelUpdateManager.cs
new file mode 100644
index 0000000..04f4ac5
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/ChannelUpdateManager.cs
@@ -0,0 +1,50 @@
+//
+// ChannelUpdateManager.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
+using Migo2.Async;
+
+namespace Banshee.Paas.Aether.Syndication
+{
+ public class ChannelUpdateManager : TaskGroup<ChannelUpdateTask>
+ {
+ public ChannelUpdateManager (int maxConcurrentUpdates) : base (maxConcurrentUpdates)
+ {
+ }
+
+ public override void Dispose ()
+ {
+ if (SetDisposing ()) {
+ CancelAsync ();
+ Handle.WaitOne ();
+ base.Dispose ();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/ChannelUpdateTask.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/ChannelUpdateTask.cs
new file mode 100644
index 0000000..2edf306
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/ChannelUpdateTask.cs
@@ -0,0 +1,123 @@
+//
+// ChannelUpdateTask.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Migo2.Net;
+using Migo2.Async;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Aether.Syndication
+{
+ public class ChannelUpdateTask : Task
+ {
+ private AsyncWebClient wc;
+ private PaasChannel channel;
+ private AsyncStateManager state_manager;
+
+ private string result;
+
+ public PaasChannel Channel {
+ get { return channel; }
+ }
+
+ public string Result {
+ get { return result; }
+ }
+
+ public ChannelUpdateTask (PaasChannel channel) : base (null, channel)
+ {
+ this.channel = channel;
+ state_manager = new AsyncStateManager ();
+ }
+
+ public override void CancelAsync ()
+ {
+ lock (SyncRoot) {
+ if (state_manager.SetCancelled ()) {
+ SetState (TaskState.Cancelled);
+
+ if (wc == null) {
+ EmitCompletionEvent (null);
+ } else {
+ wc.CancelAsync ();
+ }
+ }
+ }
+ }
+
+ public override void ExecuteAsync ()
+ {
+ lock (SyncRoot) {
+ if (state_manager.SetBusy ()) {
+ SetState (TaskState.Running);
+
+ OnStarted ();
+
+ try {
+ wc = new AsyncWebClient ();
+ wc.Timeout = (30 * 1000); // 30 Seconds
+
+ wc.DownloadStringCompleted += OnDownloadDataReceived;
+ wc.DownloadStringAsync (new Uri (channel.Url));
+ } catch (Exception e) {
+ EmitCompletionEvent (e);
+ }
+ }
+ }
+ }
+
+ private void OnDownloadDataReceived (object sender, DownloadStringCompletedEventArgs args)
+ {
+ Exception error = null;
+
+ lock (SyncRoot) {
+ if (!state_manager.Cancelled) {
+ state_manager.SetCompleted ();
+
+ if (args.Error != null) {
+ error = args.Error;
+ } else {
+ result = args.Result;
+ }
+ }
+
+ EmitCompletionEvent (error);
+ }
+ }
+
+ private void EmitCompletionEvent (Exception e)
+ {
+ if (wc != null) {
+ wc.DownloadStringCompleted -= OnDownloadDataReceived;
+ }
+
+ OnTaskCompleted (e, state_manager.Cancelled);
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/ItmsPodcast.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/ItmsPodcast.cs
new file mode 100644
index 0000000..e2fa108
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/ItmsPodcast.cs
@@ -0,0 +1,100 @@
+//
+// ItmsPodcast.cs
+//
+// Authors:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Text.RegularExpressions;
+
+using Banshee.Web;
+
+namespace Banshee.Paas.Aether.Syndication
+{
+ public class ItmsPodcast
+ {
+ private string itms_uri;
+ private string feed_url;
+ private string xml;
+
+ public ItmsPodcast (string itmsUri)
+ {
+ Fetch (itmsUri, 2);
+ feed_url = GetString ("feedURL");
+ }
+
+ public string FeedUrl {
+ get { return feed_url; }
+ }
+
+ private void Fetch (string url, int tries)
+ {
+ url = url.Replace ("itms://", "http://");
+
+ // Get rid of all the url variables except the id
+ int args_start = url.IndexOf ("?");
+ Regex id_regex = new Regex ("[?&]id=(\\d+)", RegexOptions.IgnoreCase);
+ Match match = id_regex.Match (url, args_start);
+ url = String.Format ("{0}?id={1}",
+ url.Substring (0, args_start),
+ match.Groups[1]
+ );
+
+ using (HttpRequest req = new HttpRequest (url)) {
+ req.Request.KeepAlive = true;
+ req.Request.Accept = "*/*";
+ req.GetResponse ();
+
+ if (req.Response.ContentType.StartsWith ("text/html")) {
+ if (tries > 0) {
+ string start = "onload=\"return itmsOpen('";
+ string rsp_body = req.ResponseBody;
+ int value_start = rsp_body.IndexOf (start) + start.Length;
+ int value_end = rsp_body.IndexOf ("','", value_start);
+ string new_url = rsp_body.Substring (value_start, value_end - value_start);
+ new_url = System.Web.HttpUtility.HtmlDecode (new_url);
+ Fetch (new_url, tries--);
+ }
+ } else {
+ xml = req.ResponseBody;
+ itms_uri = url;
+ }
+ }
+ }
+
+ private string GetString (string key_name)
+ {
+ try {
+ int entry_start = xml.IndexOf (String.Format ("<key>{0}</key>", key_name));
+ int value_start = xml.IndexOf ("<string>", entry_start) + 8;
+ int value_end = xml.IndexOf ("</string>", value_start);
+ return xml.Substring (value_start, value_end - value_start);
+ } catch (Exception) {
+ throw new Exception (String.Format (
+ "Unable to get value for {0} from {1}", key_name, itms_uri));
+ }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/RssParser.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/RssParser.cs
new file mode 100644
index 0000000..97b6da6
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/RssParser.cs
@@ -0,0 +1,286 @@
+//
+// RssParser.cs
+//
+// Authors:
+// Mike Urbanski <michael c urbanski gmail com>
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2007 Mike Urbanski
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Xml;
+using System.Text;
+using System.Collections.Generic;
+
+using Hyena;
+
+using Migo2.Utils;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Aether.Syndication
+{
+ public class RssParser
+ {
+ private XmlDocument doc;
+ private XmlNamespaceManager mgr;
+
+ public RssParser (string xml)
+ {
+ xml = xml.TrimStart ();
+ doc = new XmlDocument ();
+ try {
+ doc.LoadXml (xml);
+ } catch (XmlException e) {
+ bool have_stripped_control = false;
+ StringBuilder sb = new StringBuilder ();
+
+ foreach (char c in xml) {
+ if (Char.IsControl (c) && c != '\n') {
+ have_stripped_control = true;
+ } else {
+ sb.Append (c);
+ }
+ }
+
+ bool loaded = false;
+ if (have_stripped_control) {
+ try {
+ doc.LoadXml (sb.ToString ());
+ loaded = true;
+ } catch {}
+ }
+
+ if (!loaded) {
+ Hyena.Log.Exception (e);
+ throw new FormatException ("Invalid XML document.");
+ }
+ }
+ CheckRss ();
+ }
+
+ public RssParser (XmlDocument doc)
+ {
+ this.doc = doc;
+ CheckRss ();
+ }
+
+ public void UpdateChannel (PaasChannel channel)
+ {
+ try {
+ channel.Name = StringUtil.RemoveNewlines (XmlUtils.GetXmlNodeText (doc, "/rss/channel/title", mgr));
+ channel.Description = StringUtil.RemoveNewlines (XmlUtils.GetXmlNodeText (doc, "/rss/channel/description", mgr));
+ channel.Copyright = XmlUtils.GetXmlNodeText (doc, "/rss/channel/copyright", mgr);
+ channel.ImageUrl = XmlUtils.GetXmlNodeText (doc, "/rss/channel/itunes:image/@href", mgr);
+
+ if (String.IsNullOrEmpty (channel.ImageUrl)) {
+ channel.ImageUrl = XmlUtils.GetXmlNodeText (doc, "/rss/channel/image/url", mgr);
+ }
+
+ channel.Language = XmlUtils.GetXmlNodeText (doc, "/rss/channel/language", mgr);
+ channel.LastBuildDate = XmlUtils.GetRfc822DateTime (doc, "/rss/channel/lastBuildDate");
+ channel.Link = XmlUtils.GetXmlNodeText (doc, "/rss/channel/link", mgr);
+ channel.PubDate = XmlUtils.GetRfc822DateTime (doc, "/rss/channel/pubDate");
+ channel.Keywords = XmlUtils.GetXmlNodeText (doc, "/rss/channel/itunes:keywords", mgr);
+ channel.Category = XmlUtils.GetXmlNodeText (doc, "/rss/channel/itunes:category/@text", mgr);
+
+ channel.LastDownloadTime = DateTime.Now;
+ } catch (Exception e) {
+ Hyena.Log.Exception ("Caught error parsing RSS feed", e);
+ throw;
+ }
+ }
+
+ public IEnumerable<PaasItem> GetItems ()
+ {
+ XmlNodeList nodes = null;
+
+ try {
+ nodes = doc.SelectNodes ("//item");
+ } catch (Exception e) {
+ Hyena.Log.Exception ("Unable to get any RSS items", e);
+ }
+
+ if (nodes != null) {
+ foreach (XmlNode node in nodes) {
+ PaasItem item = null;
+
+ try {
+ item = ParseItem (node);
+ } catch (Exception e) {
+ Hyena.Log.Exception (e);
+ }
+
+ if (item != null) {
+ yield return item;
+ }
+ }
+ }
+ }
+
+ private PaasItem ParseItem (XmlNode node)
+ {
+ try {
+ PaasItem item = new PaasItem ();
+
+ if (!TryParseMediaContent (node, item) && !TryParseEnclosure (node, item)) {
+ return null;
+ }
+
+ item.Description = StringUtil.RemoveNewlines (XmlUtils.GetXmlNodeText (node, "description", mgr));
+ item.Name = StringUtil.RemoveNewlines (XmlUtils.GetXmlNodeText (node, "title", mgr));
+
+ if (String.IsNullOrEmpty (item.Description) && String.IsNullOrEmpty (item.Name)) {
+ throw new FormatException ("node: Either 'title' or 'description' node must exist.");
+ }
+
+ item.Author = XmlUtils.GetXmlNodeText (node, "author", mgr);
+ item.Comments = XmlUtils.GetXmlNodeText (node, "comments", mgr);
+
+ item.Link = XmlUtils.GetXmlNodeText (node, "link", mgr);
+ item.PubDate = XmlUtils.GetRfc822DateTime (node, "pubDate");
+ item.Modified = XmlUtils.GetRfc822DateTime (node, "dcterms:modified");
+ item.LicenseUri = XmlUtils.GetXmlNodeText (node, "creativeCommons:license", mgr);
+
+ return item;
+ } catch (Exception e) {
+ Hyena.Log.Exception ("Caught error parsing RSS item", e);
+ }
+
+ return null;
+ }
+
+ private bool TryParseEnclosure (XmlNode node, PaasItem item)
+ {
+ try {
+ item.Url = XmlUtils.GetXmlNodeText (node, "enclosure/@url", mgr);
+
+ if (String.IsNullOrEmpty (item.Url)) {
+ return false;
+ }
+
+ item.Size = Math.Max (0, XmlUtils.GetInt64 (node, "enclosure/@length"));
+ item.MimeType = XmlUtils.GetXmlNodeText (node, "enclosure/@type", mgr);
+ item.Duration = GetITunesDuration (node);
+ item.Keywords = XmlUtils.GetXmlNodeText (node, "itunes:keywords", mgr);
+
+ return true;
+ } catch (Exception e) {
+ Hyena.Log.Exception ("Caught error parsing RSS enclosure", e);
+ }
+
+ return false;
+ }
+
+ // Parse one Media RSS media:content node
+ // http://search.yahoo.com/mrss/
+ private bool TryParseMediaContent (XmlNode item_node, PaasItem item)
+ {
+ try {
+ XmlNode node = null;
+
+ // Get the highest bitrate "full" content item
+ // TODO allow a user-preference for a feed to decide what quality to get, if there
+ // are options?
+ int max_bitrate = 0;
+ foreach (XmlNode test_node in item_node.SelectNodes ("media:content", mgr)) {
+ string expr = XmlUtils.GetXmlNodeText (test_node, "@expression", mgr);
+ if (!(String.IsNullOrEmpty (expr) || expr == "full"))
+ continue;
+
+ int bitrate = XmlUtils.GetInt32 (test_node, "@bitrate");
+ if (node == null || bitrate > max_bitrate) {
+ node = test_node;
+ max_bitrate = bitrate;
+ }
+ }
+
+ if (node == null)
+ return false;
+
+ item.Url = XmlUtils.GetXmlNodeText (node, "@url", mgr);
+
+ if (item.Url == null) {
+ return false;
+ }
+
+ item.Size = Math.Max (0, XmlUtils.GetInt64 (node, "@fileSize"));
+ item.MimeType = XmlUtils.GetXmlNodeText (node, "@type", mgr);
+ item.Duration = TimeSpan.FromSeconds (XmlUtils.GetInt64 (node, "@duration", mgr));
+ item.Keywords = XmlUtils.GetXmlNodeText (node, "itunes:keywords", mgr);
+
+ return true;
+ } catch (Exception e) {
+ Hyena.Log.Exception ("Caught error parsing RSS media:content", e);
+ }
+
+ return false;
+ }
+
+ private void CheckRss ()
+ {
+ if (doc.SelectSingleNode ("/rss") == null) {
+ throw new FormatException ("Invalid RSS document.");
+ }
+
+ if (XmlUtils.GetXmlNodeText (doc, "/rss/channel/title", mgr) == String.Empty) {
+ throw new FormatException (
+ "node: 'title', 'description', and 'link' nodes must exist."
+ );
+ }
+
+ mgr = new XmlNamespaceManager (doc.NameTable);
+ mgr.AddNamespace ("itunes", "http://www.itunes.com/dtds/podcast-1.0.dtd");
+ mgr.AddNamespace ("creativeCommons", "http://backend.userland.com/creativeCommonsRssModule");
+ mgr.AddNamespace ("media", "http://search.yahoo.com/mrss/");
+ mgr.AddNamespace ("dcterms", "http://purl.org/dc/terms/");
+ }
+
+ private TimeSpan GetITunesDuration (XmlNode node)
+ {
+ return GetITunesDuration (XmlUtils.GetXmlNodeText (node, "itunes:duration", mgr));
+ }
+
+ private TimeSpan GetITunesDuration (string duration)
+ {
+ if (String.IsNullOrEmpty (duration)) {
+ return TimeSpan.Zero;
+ }
+
+ int hours = 0, minutes = 0, seconds = 0;
+ string [] parts = duration.Split (':');
+
+ if (parts.Length > 0)
+ seconds = Int32.Parse (parts[parts.Length - 1]);
+
+ if (parts.Length > 1)
+ minutes = Int32.Parse (parts[parts.Length - 2]);
+
+ if (parts.Length > 2)
+ hours = Int32.Parse (parts[parts.Length - 3]);
+
+ return TimeSpan.FromSeconds (hours * 3600 + minutes * 60 + seconds);
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/SyndicationClient.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/SyndicationClient.cs
new file mode 100644
index 0000000..8fa5326
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/SyndicationClient.cs
@@ -0,0 +1,532 @@
+//
+// SyndicationClient.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
+using Migo2.Async;
+
+using Banshee.Base;
+using Banshee.ServiceStack;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Aether.Syndication
+{
+ public sealed class SyndicationClient : AetherClient
+ {
+ private bool disposed;
+
+ private PaasSource source;
+ private ChannelUpdateManager channel_manager;
+
+ private Dictionary<long, DeletedChannelInfo> deleted;
+ private Dictionary<long, ChannelUpdateTask> updating;
+
+ public event EventHandler<ItemEventArgs> ItemsAdded;
+ public event EventHandler<ItemEventArgs> ItemsRemoved;
+
+ public event EventHandler<ChannelEventArgs> ChannelsAdded;
+ public event EventHandler<ChannelEventArgs> ChannelsRemoved;
+
+ public event EventHandler<ChannelEventArgs> ChannelUpdating;
+ public event EventHandler<ChannelUpdateCompletedEventArgs> ChannelUpdateCompleted;
+
+ public SyndicationClient (PaasSource source)
+ {
+ this.source = source;
+ channel_manager = new ChannelUpdateManager (2);
+
+ channel_manager.Started += (sender, e) => {
+ OnStateChanged (AetherClientState.Idle, AetherClientState.Busy);
+ };
+
+ channel_manager.Stopped += (sender, e) => {
+ OnStateChanged (AetherClientState.Busy, AetherClientState.Idle);
+ };
+
+ channel_manager.TaskStarted += (sender, e) => {
+ OnChannelUpdating (e.Task.UserState as PaasChannel);
+ };
+
+ channel_manager.TaskAdded += (sender, e) => {
+ if (e.Task != null) {
+ OnChannelUpdating (e.Task.UserState as PaasChannel);
+ } else {
+ foreach (Task t in e.Tasks) {
+ OnChannelUpdating (t.UserState as PaasChannel);
+ }
+ }
+ };
+
+ channel_manager.TaskCompleted += TaskCompletedHandler;
+
+ deleted = new Dictionary<long, DeletedChannelInfo> ();
+ updating = new Dictionary<long, ChannelUpdateTask> ();
+ }
+
+ public override void Dispose ()
+ {
+ lock (SyncRoot) {
+ disposed = true;
+ }
+
+ channel_manager.Dispose ();
+ channel_manager.TaskCompleted -= TaskCompletedHandler;
+
+ source = null;
+ channel_manager = null;
+
+ base.Dispose ();
+ }
+
+ public ChannelUpdateStatus GetUpdateStatus (PaasChannel channel)
+ {
+ lock (SyncRoot) {
+ ChannelUpdateTask task;
+
+ if (channel != null && updating.TryGetValue (channel.DbId, out task)) {
+ return (task.State == TaskState.Running) ?
+ ChannelUpdateStatus.Updating : ChannelUpdateStatus.Waiting;
+ }
+
+ return ChannelUpdateStatus.None;
+ }
+ }
+
+
+ public void DeleteChannel (PaasChannel channel)
+ {
+ DeleteChannel (channel, false);
+ }
+
+ public void DeleteChannel (PaasChannel channel, bool deleteFiles)
+ {
+ if (channel == null) {
+ throw new ArgumentNullException ("channel");
+ }
+
+ lock (SyncRoot) {
+ if (!disposed) {
+ if (updating.ContainsKey (channel.DbId)) {
+ deleted.Add (
+ channel.DbId,
+ new DeletedChannelInfo { DeleteFiles = deleteFiles, Channel = channel }
+ );
+
+ updating[channel.DbId].CancelAsync ();
+ } else {
+ DeleteChannelImpl (channel, deleteFiles);
+ }
+ }
+ }
+ }
+
+ public void DeleteChannels (IEnumerable<PaasChannel> channels, bool deleteFiles)
+ {
+ if (channels == null) {
+ throw new ArgumentNullException ("channels");
+ }
+
+ foreach (PaasChannel channel in channels) {
+ DeleteChannel (channel, deleteFiles);
+ }
+ }
+
+ private void DeleteChannelImpl (PaasChannel channel, bool deleteFiles)
+ {
+ List<PaasItem> items = new List<PaasItem> (channel.Items);
+
+ ServiceManager.DbConnection.BeginTransaction ();
+
+ try {
+ if (items != null) {
+ DeleteItems (items, deleteFiles, true);
+ }
+
+ PaasChannel.Provider.Delete (channel);
+ ServiceManager.DbConnection.CommitTransaction ();
+ } catch {
+ ServiceManager.DbConnection.RollbackTransaction ();
+ throw;
+ }
+
+ OnChannelRemoved (channel);
+ }
+
+ private void DeleteItems (IEnumerable<PaasItem> items, bool deleteFiles, bool notify)
+ {
+ if (items == null) {
+ throw new ArgumentNullException ("items");
+ }
+
+ lock (SyncRoot) {
+ if (!disposed) {
+ source.RemoveItems (items);
+ PaasItem.Provider.Delete (items);
+
+ foreach (var item in items) {
+ if (deleteFiles && item.IsDownloaded) {
+ try {
+ Banshee.IO.Utilities.DeleteFileTrimmingParentDirectories (new SafeUri (item.LocalPath));
+ } catch {}
+ }
+ }
+
+ if (notify) {
+ OnItemsRemoved (items);
+ }
+ }
+ }
+ }
+
+ public void RemoveItem (PaasItem item)
+ {
+ RemoveItem (item, false);
+ }
+
+ public void RemoveItem (PaasItem item, bool keepFile)
+ {
+ if (item == null) {
+ throw new ArgumentNullException ("item");
+ }
+
+ lock (SyncRoot) {
+ if (!disposed) {
+ item.Active = false;
+ item.Save ();
+
+ OnItemRemoved (item);
+ }
+ }
+ }
+
+ public void RemoveItems (IEnumerable<PaasItem> items)
+ {
+ RemoveItems (items, true);
+ }
+
+ public void RemoveItems (IEnumerable<PaasItem> items, bool deleteFiles)
+ {
+ if (items == null) {
+ throw new ArgumentNullException ("items");
+ }
+
+ lock (SyncRoot) {
+ if (!disposed) {
+ ServiceManager.DbConnection.BeginTransaction ();
+
+ try {
+ foreach (PaasItem item in items) {
+ item.Active = false;
+ item.Save ();
+
+ if (deleteFiles && item.IsDownloaded) {
+ try {
+ Banshee.IO.Utilities.DeleteFileTrimmingParentDirectories (new SafeUri (item.LocalPath));
+ } catch {}
+ }
+ }
+
+ ServiceManager.DbConnection.CommitTransaction ();
+ OnItemsRemoved (items);
+ } catch (Exception e) {
+ Hyena.Log.Exception (e);
+ ServiceManager.DbConnection.RollbackTransaction ();
+ }
+ }
+ }
+ }
+
+ public void SubscribeToChannel (string url, DownloadPreference download_pref)
+ {
+ if (!IsValidUrl (url)) {
+ throw new ArgumentException ("Invalid URL!", "url");
+ }
+
+ lock (SyncRoot) {
+ if (disposed) {
+ return;
+ }
+
+ PaasChannel channel = PaasChannel.Provider.FetchFirstMatching ("Url = ?", url);
+
+ if (channel == null) {
+ channel = new PaasChannel () {
+ Url = url,
+ DownloadPreference = download_pref,
+ ClientID = (long) AetherClientID.Syndication
+ };
+
+ channel.Save ();
+ OnChannelAdded (channel);
+ }
+ }
+ }
+
+ public void UpdateAsync ()
+ {
+ lock (SyncRoot) {
+ if (disposed) {
+ return;
+ }
+
+ QueueUpdate (
+ PaasChannel.Provider.FetchAllMatching (
+ "ClientID = ? ORDER BY HYENA_COLLATION_KEY(Name), Url", (long) AetherClientID.Syndication
+ )
+ );
+ }
+ }
+
+ public void QueueUpdate (PaasChannel channel)
+ {
+ lock (SyncRoot) {
+ if (disposed) {
+ return;
+ }
+
+ if (!updating.ContainsKey (channel.DbId)) {
+ ChannelUpdateTask task = new ChannelUpdateTask (channel);
+ updating[channel.DbId] = task;
+ channel_manager.Add (task);
+ }
+ }
+ }
+
+ public void QueueUpdate (IEnumerable<PaasChannel> channels)
+ {
+ lock (SyncRoot) {
+ if (disposed) {
+ return;
+ }
+
+ List<ChannelUpdateTask> tasks = new List<ChannelUpdateTask> ();
+
+ foreach (PaasChannel channel in channels.Where (
+ (channel) =>
+ channel.ClientID == (long) AetherClientID.Syndication &&
+ !updating.ContainsKey (channel.DbId)
+ )) {
+
+ ChannelUpdateTask task = new ChannelUpdateTask (channel);
+ updating[channel.DbId] = task;
+ tasks.Add (task);
+ }
+
+ channel_manager.Add (tasks);
+ }
+ }
+
+ private bool IsValidUrl (string url)
+ {
+ try {
+ Uri uri = new Uri (url);
+
+ if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) {
+ return true;
+ }
+ } catch {}
+
+ return false;
+ }
+
+ private void TaskCompletedHandler (object sender, TaskCompletedEventArgs<ChannelUpdateTask> e)
+ {
+ // TODO - Check encoding
+ ChannelUpdateTask task = e.Task;
+ PaasChannel channel = task.Channel;
+
+ List<PaasItem> new_items = null;
+ List<PaasItem> removed_items = null;
+
+ lock (SyncRoot) {
+ if (disposed) {
+ return;
+ }
+
+ if (deleted.ContainsKey (channel.DbId)) {
+ DeleteChannelImpl (channel, deleted[channel.DbId].DeleteFiles);
+
+ deleted.Remove (channel.DbId);
+ updating.Remove (channel.DbId);
+ OnChannelUpdateCompleted (channel, false, null);
+ return;
+ }
+
+ try {
+ if (e.Error == null && e.State == TaskState.Succeeded) {
+ RssParser parser = new RssParser (task.Result);
+
+ ServiceManager.DbConnection.BeginTransaction ();
+
+ try {
+ parser.UpdateChannel (channel);
+ channel.Save ();
+
+ var cmp = new PaasItemEqualityComparer ();
+
+ IEnumerable<PaasItem> local_items = channel.Items;
+ IEnumerable<PaasItem> remote_items = parser.GetItems ().Distinct (cmp);
+
+ new_items = new List<PaasItem> (remote_items.Except (local_items, cmp));
+ removed_items = new List<PaasItem> (
+ local_items.Except (remote_items, cmp).Where ((i) => (!i.IsDownloaded || !i.Active))
+ );
+
+ if (new_items.Count > 0) {
+ foreach (PaasItem item in new_items) {
+ item.IsNew = true;
+ item.Channel = channel;
+ item.ClientID = (int)AetherClientID.Syndication;
+ item.Save ();
+ }
+
+ source.AddItems (new_items);
+ }
+
+ if (removed_items.Count > 0) {
+ DeleteItems (removed_items, false, false);
+ }
+ } catch {
+ ServiceManager.DbConnection.RollbackTransaction ();
+ throw;
+ }
+
+ ServiceManager.DbConnection.CommitTransaction ();
+
+ if (new_items != null && new_items.Count > 0) {
+ OnItemsAdded (new_items);
+ }
+
+ if (removed_items != null && removed_items.Count > 0) {
+ OnItemsRemoved (removed_items);
+ }
+ }
+
+ OnChannelUpdateCompleted (channel, e.Error);
+ } catch (Exception ex) {
+ Hyena.Log.Exception (ex);
+ OnChannelUpdateCompleted (channel, ex);
+ } finally {
+ updating.Remove (channel.DbId);
+ }
+ }
+ }
+
+ private void OnChannelAdded (PaasChannel channel)
+ {
+ var handler = ChannelsAdded;
+
+ if (handler != null) {
+ EventQueue.Register (
+ new EventWrapper<ChannelEventArgs> (handler, this, new ChannelEventArgs (channel))
+ );
+ }
+ }
+
+ private void OnChannelRemoved (PaasChannel channel)
+ {
+ var handler = ChannelsRemoved;
+
+ if (handler != null) {
+ EventQueue.Register (
+ new EventWrapper<ChannelEventArgs> (handler, this, new ChannelEventArgs (channel))
+ );
+ }
+ }
+
+ private void OnChannelUpdating (PaasChannel channel)
+ {
+ var handler = ChannelUpdating;
+
+ if (handler != null) {
+ EventQueue.Register (
+ new EventWrapper<ChannelEventArgs> (handler, this, new ChannelEventArgs (channel))
+ );
+ }
+ }
+
+ private void OnChannelUpdateCompleted (PaasChannel channel, Exception err)
+ {
+ OnChannelUpdateCompleted (channel, err == null, err);
+ }
+
+ private void OnChannelUpdateCompleted (PaasChannel channel, bool succeeded, Exception e)
+ {
+ var handler = ChannelUpdateCompleted;
+
+ if (handler != null) {
+ EventQueue.Register (
+ new EventWrapper<ChannelUpdateCompletedEventArgs> (
+ handler, this, new ChannelUpdateCompletedEventArgs (channel, succeeded, e)
+ )
+ );
+ }
+ }
+
+ private void OnItemsAdded (IEnumerable<PaasItem> items)
+ {
+ var handler = ItemsAdded;
+
+ if (handler != null) {
+ EventQueue.Register (
+ new EventWrapper<ItemEventArgs> (handler, this, new ItemEventArgs (items))
+ );
+ }
+ }
+
+ private void OnItemRemoved (PaasItem item)
+ {
+ var handler = ItemsRemoved;
+
+ if (handler != null) {
+ EventQueue.Register (
+ new EventWrapper<ItemEventArgs> (handler, this, new ItemEventArgs (item))
+ );
+ }
+ }
+
+ private void OnItemsRemoved (IEnumerable<PaasItem> items)
+ {
+ var handler = ItemsRemoved;
+
+ if (handler != null) {
+ EventQueue.Register (
+ new EventWrapper<ItemEventArgs> (handler, this, new ItemEventArgs (items))
+ );
+ }
+ }
+
+
+ private class DeletedChannelInfo
+ {
+ public bool DeleteFiles { get; set; }
+ public PaasChannel Channel { get; set; }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/CacheModelProvider.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/CacheModelProvider.cs
new file mode 100644
index 0000000..802a4ff
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/CacheModelProvider.cs
@@ -0,0 +1,142 @@
+//
+// CacheModelProvider.cs
+//
+// Authors:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Threading;
+using System.Collections.Generic;
+
+using Hyena;
+using Hyena.Data;
+using Hyena.Data.Sqlite;
+
+namespace Banshee.Paas.Data
+{
+ // Caches all results retrieved from the database, such that any subsequent retrieval will
+ // return the same instance.
+ public class CacheModelProvider<T> : SqliteModelProvider<T> where T : CacheableItem<T>, new()
+ {
+ private ReaderWriterLock rw_lock = new ReaderWriterLock ();
+ private Dictionary<long, T> full_cache = new Dictionary<long, T> ();
+
+ public CacheModelProvider (HyenaSqliteConnection connection, string table_name) : base (connection, table_name)
+ {
+ }
+
+ #region Overrides
+
+ public override T FetchSingle (long id)
+ {
+ return GetCached (id) ?? CacheResult (base.FetchSingle (id));
+ }
+
+ public override void Save (T target)
+ {
+ base.Save (target);
+ rw_lock.AcquireWriterLock (-1);
+
+ try {
+ if (!full_cache.ContainsKey (target.DbId)) {
+ full_cache[target.DbId] = target;
+ }
+ } finally {
+ rw_lock.ReleaseWriterLock ();
+ }
+ }
+
+ public override T Load (System.Data.IDataReader reader)
+ {
+ return GetCached (PrimaryKeyFor (reader)) ?? CacheResult (base.Load (reader));
+ }
+
+ public override void Delete (long id)
+ {
+ base.Delete (id);
+ rw_lock.AcquireWriterLock (-1);
+
+ try {
+ full_cache.Remove (id);
+ } finally {
+ rw_lock.ReleaseWriterLock ();
+ }
+ }
+
+ public override void Delete (IEnumerable<T> items)
+ {
+ base.Delete (items);
+ rw_lock.AcquireWriterLock (-1);
+
+ try {
+ foreach (T item in items) {
+ if (item != null) {
+ full_cache.Remove (PrimaryKeyFor (item));
+ }
+ }
+ } finally {
+ rw_lock.ReleaseWriterLock ();
+ }
+ }
+
+ #endregion
+
+#region Utility Methods
+
+ private T GetCached (long id)
+ {
+ rw_lock.AcquireReaderLock (-1);
+
+ try {
+ if (full_cache.ContainsKey (id)) {
+ return full_cache[id];
+ } else {
+ return null;
+ }
+ } finally {
+ rw_lock.ReleaseReaderLock ();
+ }
+ }
+
+ private T CacheResult (T item)
+ {
+ if (item == null) {
+ return null;
+ }
+
+ rw_lock.AcquireWriterLock (-1);
+
+ try {
+ full_cache[item.DbId] = item;
+ return item;
+ } finally {
+ rw_lock.ReleaseWriterLock ();
+ }
+ }
+
+#endregion
+
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/CacheableItem.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/CacheableItem.cs
new file mode 100644
index 0000000..0005b4f
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/CacheableItem.cs
@@ -0,0 +1,54 @@
+//
+// CacheableItem.cs
+//
+// Authors:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+using Hyena;
+using Hyena.Data;
+using Hyena.Data.Sqlite;
+
+namespace Banshee.Paas.Data
+{
+ // This class is generic b/c of some ideas that I didn't implement yet...could be made non-generic
+ public abstract class CacheableItem<T> : ICacheableItem
+ {
+ private object cache_entry_id;
+ public object CacheEntryId {
+ get { return cache_entry_id; }
+ set { cache_entry_id = value; }
+ }
+
+ private long cache_model_id;
+ public long CacheModelId {
+ get { return cache_model_id; }
+ set { cache_model_id = value; }
+ }
+
+ public abstract long DbId { get; protected set;}
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/DownloadPreference.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/DownloadPreference.cs
new file mode 100644
index 0000000..af157d1
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/DownloadPreference.cs
@@ -0,0 +1,35 @@
+//
+// DownloadPreference.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+namespace Banshee.Paas.Data
+{
+ public enum DownloadPreference : int
+ {
+ All = 0,
+ One = 1,
+ None = 2
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/DownloadStatusFilterModel.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/DownloadStatusFilterModel.cs
new file mode 100644
index 0000000..17a6682
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/DownloadStatusFilterModel.cs
@@ -0,0 +1,86 @@
+//
+// DownloadStatusFilterModel.cs
+//
+// Authors:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using Banshee.Collection;
+using Banshee.Collection.Database;
+
+namespace Banshee.Paas.Gui
+{
+ public enum DownloadedStatusFilter
+ {
+ Both,
+ Downloaded,
+ NotDownloaded
+ }
+
+ public class DownloadStatusFilterModel : FilterListModel<DownloadedStatusFilter>
+ {
+ public DownloadStatusFilterModel (DatabaseTrackListModel trackModel) : base (trackModel)
+ {
+ Selection.Clear (false);
+ Selection.QuietSelect (0);
+ }
+
+ public override void Reload (bool notify)
+ {
+ if (notify)
+ OnReloaded ();
+ }
+
+ public override void Clear ()
+ {
+ }
+
+ public override DownloadedStatusFilter this [int index] {
+ get {
+ switch (index) {
+ case 1: return DownloadedStatusFilter.Downloaded;
+ case 2: return DownloadedStatusFilter.NotDownloaded;
+ case 0:
+ default: return DownloadedStatusFilter.Both;
+ }
+ }
+ }
+
+ public override int Count {
+ get { return 3; }
+ }
+
+ public override string GetSqlFilter ()
+ {
+ if (Selection.AllSelected) {
+ return null;
+ } else if (Selection.Contains (1)) {
+ return "PaasItems.LocalPath NOT NULL";
+ } else {
+ return "PaasItems.LocalPath IS NULL";
+ }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/ListModel.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/ListModel.cs
new file mode 100644
index 0000000..bc25f01
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/ListModel.cs
@@ -0,0 +1,201 @@
+//
+// ListModel.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+using Migo2.Collections;
+
+using Hyena.Data;
+using Hyena.Collections;
+
+using Banshee.Base;
+using Banshee.Paas.Utils;
+
+namespace Banshee.Paas.Data
+{
+ public class ListModel<T> : IListModel<T>
+ {
+ public event EventHandler Cleared;
+ public event EventHandler Reloaded;
+
+ private List<T> items;
+ private Selection selection;
+
+ public virtual bool CanReorder {
+ get { return false; }
+ }
+
+ public virtual int Count {
+ get { return items.Count; }
+ }
+
+ public Selection Selection {
+ get { return selection; }
+ }
+
+ public T this[int index] {
+ get { return GetIndex (index); }
+ }
+
+ protected List<T> Items {
+ get { return items; }
+ }
+
+ public ListModel () : this (null)
+ {
+ }
+
+ public ListModel (Selection selection)
+ {
+ items = new List<T> ();
+ this.selection = selection ?? new Selection ();
+ }
+
+ public void Add (T item)
+ {
+ if (!item.Equals (default (T))) {
+ items.Add (item);
+ OnReloaded ();
+ }
+ }
+
+ public void Add (IEnumerable<T> items)
+ {
+ if (items != null) {
+ foreach (T item in items) {
+ if (item != null) {
+ this.items.Add (item);
+ }
+ }
+
+ OnReloaded ();
+ }
+ }
+
+ public IEnumerable<T> GetSelected ()
+ {
+ T item = default (T);
+ List<T> selected = new List<T> ();
+
+ foreach (int i in selection) {
+ item = GetIndex (i);
+
+ if (item != null) {
+ selected.Add (item);
+ }
+ }
+
+ return selected;
+ }
+
+ public void Remove (T item)
+ {
+ if (item != null) {
+ items.Remove (item);
+ OnReloaded ();
+ }
+ }
+
+ public void Remove (IEnumerable<T> items)
+ {
+ if (items != null) {
+ foreach (T item in items) {
+ if (item != null) {
+ this.items.Remove (item);
+ }
+ }
+
+ OnReloaded ();
+ }
+ }
+
+ public void Clear ()
+ {
+ items.Clear ();
+ OnCleared ();
+ }
+
+ public void Reload ()
+ {
+ OnReloaded ();
+ }
+
+ public void Reorder (int[] newWorldOrder)
+ {
+ int len = newWorldOrder.Length;
+ int[] order = new int[len];
+ Dictionary<T, int> positions = new Dictionary<T, int> (len);
+
+ int i = 0;
+ for (; i < order.Length; ++i) {
+ order[newWorldOrder[i]] = i;
+ }
+
+ i = 0;
+ foreach (var t in items) {
+ positions.Add (t, order[i++]);
+ }
+
+ items.Sort (new OrderComparer<T> (positions));
+ selection.Clear ();
+
+ OnReloaded ();
+ }
+
+ private T GetIndex (int index)
+ {
+ if (index >= 0 && index < items.Count) {
+ return items[index];
+ }
+
+ return default (T);
+ }
+
+ protected virtual void OnReloaded ()
+ {
+ ThreadAssist.ProxyToMain (delegate {
+ EventHandler handler = Reloaded;
+
+ if (handler != null) {
+ handler (this, EventArgs.Empty);
+ }
+ });
+ }
+
+ protected virtual void OnCleared ()
+ {
+ ThreadAssist.ProxyToMain (delegate {
+ EventHandler handler = Cleared;
+
+ if (handler != null) {
+ handler (this, EventArgs.Empty);
+ }
+ });
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasChannel.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasChannel.cs
new file mode 100644
index 0000000..5525340
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasChannel.cs
@@ -0,0 +1,223 @@
+//
+// PaasChannel.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Banshee.ServiceStack;
+
+using Hyena;
+using Hyena.Data;
+using Hyena.Data.Sqlite;
+
+using Banshee.Paas.Utils;
+
+namespace Banshee.Paas.Data
+{
+ public class PaasChannelProvider : CacheModelProvider<PaasChannel>
+ {
+ public PaasChannelProvider (HyenaSqliteConnection connection) : base (connection, "PaasChannels")
+ {
+ }
+ }
+
+ public class PaasChannel : CacheableItem<PaasChannel>
+ {
+ private static PaasChannel empty;
+ private static PaasChannelProvider provider;
+
+ static PaasChannel () {
+ empty = new PaasChannel ();
+ provider = new PaasChannelProvider (ServiceManager.DbConnection);
+ }
+
+ public static PaasChannel Empty {
+ get { return empty; }
+ }
+ public static PaasChannelProvider Provider {
+ get { return provider; }
+ }
+
+ private long dbid;
+ [DatabaseColumn ("ID", Constraints = DatabaseColumnConstraints.PrimaryKey)]
+ public override long DbId {
+ get { return dbid; }
+ protected set { dbid = value; }
+ }
+
+ private long client_id;
+ [DatabaseColumn ("ClientID", Index = "PaasChannelClientIDIndex")]
+ public long ClientID {
+ get { return client_id; }
+ set { client_id = value; }
+ }
+
+ private long external_id;
+ [DatabaseColumn ("ExternalID", Index = "PaasChannelExternalIDIndex")]
+ public long ExternalID {
+ get { return external_id; }
+ set { external_id = value; }
+ }
+
+ private string category;
+ [DatabaseColumn]
+ public string Category {
+ get { return category; }
+ set { category = value; }
+ }
+
+ private string copyright;
+ [DatabaseColumn]
+ public string Copyright {
+ get { return copyright; }
+ set { copyright = value; }
+ }
+
+ private string description;
+ [DatabaseColumn]
+ public string Description {
+ get { return description; }
+ set {
+ description = value;
+ StrippedDescription = StringUtils.StripHtml (value);
+ }
+ }
+
+ private DownloadPreference download_preference;
+ [DatabaseColumn]
+ public DownloadPreference DownloadPreference {
+ get { return download_preference; }
+ set { download_preference = value; }
+ }
+
+ private string image_url;
+ [DatabaseColumn]
+ public string ImageUrl {
+ get { return image_url; }
+ set { image_url = value; }
+ }
+
+ private string language;
+ [DatabaseColumn]
+ public string Language {
+ get { return language; }
+ set { language = value; }
+ }
+
+ private DateTime last_build_date;
+ [DatabaseColumn]
+ public DateTime LastBuildDate {
+ get { return last_build_date; }
+ set { last_build_date = value; }
+ }
+
+ private DateTime last_download_time;
+ [DatabaseColumn]
+ public DateTime LastDownloadTime {
+ get { return last_download_time; }
+ set { last_download_time = value; }
+ }
+
+ private string license;
+ [DatabaseColumn]
+ public string License {
+ get { return license; }
+ set { license = value; }
+ }
+
+ private string link;
+ [DatabaseColumn]
+ public string Link {
+ get { return link; }
+ set { link = value; }
+ }
+
+ private string local_enclosure_path;
+ [DatabaseColumn]
+ public string LocalEnclosurePath {
+ get { return local_enclosure_path; }
+ set { local_enclosure_path = value; }
+ }
+
+ private string name;
+ [DatabaseColumn (Index = "PaasChannelNameIndex")]
+ public string Name {
+ get {
+ return String.IsNullOrEmpty (name) ? url : name;
+ }
+
+ set { name = value; }
+ }
+
+ private DateTime pub_date;
+ [DatabaseColumn]
+ public DateTime PubDate {
+ get { return pub_date; }
+ set { pub_date = value; }
+ }
+
+ private string publisher;
+ [DatabaseColumn]
+ public string Publisher {
+ get { return publisher; }
+ set { publisher = value; }
+ }
+
+ private string stripped_description;
+ [DatabaseColumn]
+ public string StrippedDescription {
+ get { return stripped_description; }
+ set { stripped_description = value; }
+ }
+
+ private string keywords;
+ [DatabaseColumn]
+ public string Keywords {
+ get { return keywords; }
+ set { keywords = value; }
+ }
+
+ private string url;
+ [DatabaseColumn]
+ public string Url {
+ get { return url; }
+ set { url = value; }
+ }
+
+ public IEnumerable<PaasItem> Items {
+ get {
+ foreach (PaasItem item in PaasItem.Provider.FetchAllMatching ("ChannelID = ?", DbId)) {
+ yield return item;
+ }
+ }
+ }
+
+ public void Save ()
+ {
+ Provider.Save (this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasChannelModel.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasChannelModel.cs
new file mode 100644
index 0000000..4ddc6ec
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasChannelModel.cs
@@ -0,0 +1,62 @@
+//
+// PaasChannelModel.cs
+//
+// Authors:
+// Mike Urbanski <michael c urbanski gmail com>
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Michael C. Urbanski
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Mono.Unix;
+
+using Hyena.Data;
+
+using Banshee.Database;
+using Banshee.Collection.Database;
+
+namespace Banshee.Paas.Data
+{
+ public class PaasChannelModel : DatabaseFilterListModel<PaasChannel, PaasChannel>
+ {
+ public PaasChannelModel (Banshee.Sources.DatabaseSource source, DatabaseTrackListModel trackModel, BansheeDbConnection connection, string uuid)
+ : base ("paas", Catalog.GetString ("Paas"), source, trackModel, connection, PaasChannel.Provider, new PaasChannel (), uuid)
+ {
+ ReloadFragmentFormat = "FROM PaasChannels ORDER BY HYENA_COLLATION_KEY(Name)";
+ }
+
+ public override string FilterColumn {
+ get { return PaasChannel.Provider.PrimaryKey; }
+ }
+
+ protected override string ItemToFilterValue (object item)
+ {
+ return (item != select_all_item && item is PaasChannel) ? (item as PaasChannel).DbId.ToString () : null;
+ }
+
+ public override void UpdateSelectAllItem (long count)
+ {
+ select_all_item.Name = String.Format (Catalog.GetString ("All Channels ({0})"), count);
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasItem.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasItem.cs
new file mode 100644
index 0000000..e25a03f
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasItem.cs
@@ -0,0 +1,323 @@
+//
+// PaasItem.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Data;
+using System.Collections.Generic;
+
+using Hyena;
+using Hyena.Data;
+using Hyena.Data.Sqlite;
+
+using Banshee.ServiceStack;
+using Banshee.Collection.Database;
+
+using Banshee.Paas.Utils;
+using Banshee.Paas.DownloadManager.Data;
+
+namespace Banshee.Paas.Data
+{
+ public class PaasItemProvider : CacheModelProvider<PaasItem>
+ {
+ public PaasItemProvider (HyenaSqliteConnection connection) : base (connection, "PaasItems")
+ {
+ }
+
+ public IEnumerable<PaasItem> FetchQueued (long primarySourceID)
+ {
+ string restore_command = String.Format (
+ @"SELECT {0} FROM {1}
+ JOIN {2} ON {1}.ID = {2}.ExternalID
+ WHERE PrimarySourceID = ? AND {1}.LocalPath IS NULL
+ ORDER BY {2}.Position ASC", Select, From,
+ QueuedDownloadTask.Provider.From
+ );
+
+ using (IDataReader reader = Connection.Query (restore_command, primarySourceID)) {
+ while (reader.Read ()) {
+ yield return Load (reader);
+ }
+ }
+ }
+ }
+
+ public class PaasItem : CacheableItem<PaasItem>
+ {
+ private long dbid;
+
+ private static PaasItem empty;
+ private static PaasItemProvider provider;
+
+ static PaasItem () {
+ empty = new PaasItem ();
+ provider = new PaasItemProvider (ServiceManager.DbConnection);
+ }
+
+ public static PaasItem Empty {
+ get { return empty; }
+ }
+
+ public static PaasItemProvider Provider {
+ get { return provider; }
+ }
+
+ [DatabaseColumn ("ID", Constraints = DatabaseColumnConstraints.PrimaryKey)]
+ public override long DbId {
+ get { return dbid; }
+ protected set { dbid = value; }
+ }
+
+ private long client_id;
+ [DatabaseColumn ("ClientID", Index = "PaasItemsClientIDIndex")]
+ public long ClientID {
+ get { return client_id; }
+ set { client_id = value; }
+ }
+
+ private long external_id;
+ [DatabaseColumn ("ExternalID", Index = "PaasItemsExternalIDIndex")]
+ public long ExternalID {
+ get { return external_id; }
+ set { external_id = value; }
+ }
+
+ private long external_channel_id;
+ [DatabaseColumn ("ExternalChannelID", Index = "PaasItemsExternalChannelIDIndex")]
+ public long ExternalChannelID {
+ get { return external_channel_id; }
+ set { external_channel_id = value; }
+ }
+
+ private long channel_id;
+ [DatabaseColumn ("ChannelID", Index = "PaasItemChannelIDIndex")]
+ public long ChannelID {
+ get { return channel_id; }
+ set {
+ channel = null;
+ channel_id = value;
+ }
+ }
+
+ private bool active = true;
+ [DatabaseColumn (Index = "PaasItemActiveIndex")]
+ public bool Active {
+ get { return active; }
+ set { active = value; }
+ }
+
+ private DateTime date;
+ [DatabaseColumn (Index = "PaasItemPubDateIndex")]
+ public DateTime PubDate {
+ get { return date; }
+ set { date = value; }
+ }
+
+ private string author;
+ [DatabaseColumn]
+ public string Author {
+ get { return author; }
+ set { author = value; }
+ }
+
+ private string comments;
+ [DatabaseColumn]
+ public string Comments {
+ get { return comments; }
+ set { comments = value; }
+ }
+
+ private string description;
+ [DatabaseColumn]
+ public string Description {
+ get { return description; }
+ set {
+ description = value;
+ StrippedDescription = StringUtils.StripHtml (value);
+ }
+ }
+
+ private DateTime downloaded_at;
+ [DatabaseColumn (Index = "PaasItemDownloadedAtIndex")]
+ public DateTime DownloadedAt {
+ get { return downloaded_at; }
+ internal set { downloaded_at = value; }
+ }
+
+ private TimeSpan duration;
+ [DatabaseColumn]
+ public TimeSpan Duration {
+ get { return duration; }
+ set { duration = value; }
+ }
+
+ private bool error;
+ [DatabaseColumn]
+ public bool Error {
+ get { return error; }
+ set { error = value; }
+ }
+
+ public bool IsDownloaded {
+ get {
+ return !String.IsNullOrEmpty (LocalPath);
+ }
+ }
+
+ private bool is_new;
+ [DatabaseColumn (Index = "PaasItemIsNewIndex")]
+ public bool IsNew {
+ get { return is_new; }
+ set { is_new = value; }
+ }
+
+ private string image;
+ [DatabaseColumn]
+ public string ImageUrl {
+ get { return image; }
+ set { image = value; }
+ }
+
+ private string keywords;
+ [DatabaseColumn]
+ public string Keywords {
+ get { return keywords; }
+ set { keywords = value; }
+ }
+
+ private string license_uri;
+ [DatabaseColumn]
+ public string LicenseUri {
+ get { return license_uri; }
+ set { license_uri = value; }
+ }
+
+ private string link;
+ [DatabaseColumn]
+ public string Link {
+ get { return link; }
+ set { link = value; }
+ }
+
+ private string local_path;
+ [DatabaseColumn (Index = "PaasItemLocalPathIndex")]
+ public string LocalPath {
+ get { return local_path; }
+ set { local_path = value; }
+ }
+
+ private string mime_type;
+ [DatabaseColumn]
+ public string MimeType {
+ get { return mime_type; }
+ set {
+ mime_type = value;
+
+ if (String.IsNullOrEmpty (mime_type)) {
+ mime_type = "application/octet-stream";
+ }
+ }
+ }
+
+ private DateTime modified;
+ [DatabaseColumn]
+ public DateTime Modified {
+ get { return modified; }
+ set { modified = value; }
+ }
+
+ private string name;
+ [DatabaseColumn (Index = "PaasItemNameIndex")]
+ public string Name {
+ get { return name; }
+ set { name = value; }
+ }
+
+ private long size;
+ [DatabaseColumn]
+ public long Size {
+ get { return size; }
+ set { size = value; }
+ }
+
+ private string stripped_description;
+ public string StrippedDescription {
+ get { return stripped_description; }
+ protected set {
+ stripped_description = value;
+ }
+ }
+
+ private string url;
+ [DatabaseColumn]
+ public string Url {
+ get { return url; }
+ set { url = value; }
+ }
+
+ private PaasChannel channel;
+ public PaasChannel Channel {
+ get {
+ if (channel == null && ChannelID > 0) {
+ channel = PaasChannel.Provider.FetchSingle (ChannelID);
+ }
+
+ return channel ?? PaasChannel.Empty;
+ }
+
+ set {
+ if (value != null) {
+ channel = value;
+ channel_id = channel.DbId;
+ }
+ }
+ }
+
+ public void Save ()
+ {
+ if (this != empty) {
+ Provider.Save (this);
+ }
+ }
+
+ public void Delete (bool delete_file)
+ {
+ Provider.Delete (this);
+ }
+ }
+
+ public class PaasItemEqualityComparer : IEqualityComparer<PaasItem>
+ {
+ public bool Equals (PaasItem lhs, PaasItem rhs)
+ {
+ return (lhs.Url == rhs.Url || lhs.Name == rhs.Name) && lhs.PubDate == rhs.PubDate;
+ }
+
+ public int GetHashCode (PaasItem obj)
+ {
+ return base.GetHashCode ();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasTrackInfo.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasTrackInfo.cs
new file mode 100644
index 0000000..b5f7757
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasTrackInfo.cs
@@ -0,0 +1,189 @@
+//
+// PaasTrackInfo.cs
+//
+// Authors:
+// Gabriel Burt <gburt novell com>
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (C) 2009 Michael C. Urbanski
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Banshee.Collection;
+using Banshee.Collection.Database;
+
+namespace Banshee.Paas.Data
+{
+ public class PaasTrackInfo
+ {
+ private static readonly object sync = new object ();
+
+ public static PaasTrackInfo From (TrackInfo track)
+ {
+ if (track != null) {
+ PaasTrackInfo pi = track.ExternalObject as PaasTrackInfo;
+ PaasItem item = (pi != null) ? pi.Item : null;
+
+ if (pi != null && item != null) {
+ track.ReleaseDate = item.PubDate;
+ }
+
+ return pi;
+ }
+
+ return null;
+ }
+
+ public static IEnumerable<PaasTrackInfo> From (IEnumerable<TrackInfo> tracks)
+ {
+ lock (sync) {
+ foreach (TrackInfo track in tracks) {
+ PaasTrackInfo pi = From (track);
+
+ if (pi != null) {
+ yield return pi;
+ }
+ }
+ }
+ }
+
+ private int position;
+ private DatabaseTrackInfo track;
+
+ private PaasItem item;
+
+ public DatabaseTrackInfo Track {
+ get { return track; }
+ }
+
+ public PaasChannel Channel {
+ get { return Item.Channel; }
+ }
+
+ public PaasItem Item {
+ get {
+ if (item == null && track.ExternalId > 0) {
+ item = PaasItem.Provider.FetchSingle (track.ExternalId);
+ }
+
+ return item ?? PaasItem.Empty;
+ }
+
+ set {
+ item = value;
+ track.ExternalId = value.DbId;
+ }
+ }
+
+ public DateTime PubDate {
+ get { return Item.PubDate; }
+ }
+
+ public string Description {
+ get { return Item.StrippedDescription; }
+ }
+
+ public bool IsNew {
+ get { return IsDownloaded && Item.IsNew; }
+ }
+
+ public bool IsDownloaded {
+ get { return !String.IsNullOrEmpty (Item.LocalPath); }
+ }
+
+ public int Position {
+ get { return position; }
+ set { position = value; }
+ }
+
+ public DateTime ReleaseDate {
+ get { return Item.PubDate; }
+ }
+
+ public PaasTrackInfo (DatabaseTrackInfo track) : base ()
+ {
+ this.track = track;
+ }
+
+ public PaasTrackInfo (DatabaseTrackInfo track, PaasItem item) : this (track)
+ {
+ Item = item;
+ SyncWithItem ();
+ }
+
+ static PaasTrackInfo ()
+ {
+ TrackInfo.PlaybackFinished += OnPlaybackFinished;
+ }
+
+ private static void OnPlaybackFinished (TrackInfo track, double percentCompleted)
+ {
+ if (percentCompleted > 0.5 && track.PlayCount > 0) {
+ PaasTrackInfo pi = PaasTrackInfo.From (track);
+ if (pi != null && pi.Item != PaasItem.Empty && pi.Item.IsNew) {
+ pi.Item.IsNew = false;
+ pi.Item.Save ();
+ }
+ }
+ }
+
+ public void SyncWithItem ()
+ {
+ PaasItem item = Item;
+
+ if (item == null || item == PaasItem.Empty || item.Channel == PaasChannel.Empty) {
+ return;
+ }
+
+ if (track.ExternalId != item.DbId) {
+ throw new Exception (String.Format (
+ "PLEASE REPORT! Track.TrackID: {0} - Track.ExternalID: {1} - Track.CacheEntryID: {2} - Item.DbId = {3} - Item.CacheEntryID = {4}",
+ track.TrackId, track.ExternalId, track.CacheEntryId, item.DbId, item.CacheEntryId
+ ));
+ }
+
+ track.ArtistName = item.Channel.Publisher;
+ track.AlbumTitle = item.Channel.Name;
+ track.TrackTitle = item.Name;
+ track.Year = item.PubDate.Year;
+ track.CanPlay = true;
+ track.Genre = track.Genre ?? "Podcast";
+ track.ReleaseDate = item.PubDate;
+ track.MimeType = item.MimeType;
+ track.Duration = item.Duration;
+ track.FileSize = item.Size;
+ track.LicenseUri = item.Channel.License;
+
+ track.Uri = new Banshee.Base.SafeUri (item.LocalPath ?? item.Url);
+
+ if (!String.IsNullOrEmpty (item.LocalPath)) {
+ try {
+ TagLib.File file = Banshee.Streaming.StreamTagger.ProcessUri (track.Uri);
+ Banshee.Streaming.StreamTagger.TrackInfoMerge (track, file, true);
+ } catch {}
+ }
+
+ track.MediaAttributes |= TrackMediaAttributes.Podcast;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasTrackListModel.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasTrackListModel.cs
new file mode 100644
index 0000000..df55523
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasTrackListModel.cs
@@ -0,0 +1,100 @@
+//
+// PaasTrackListModel.cs
+//
+// Authors:
+// Gabriel Burt <gburt novell com>
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (C) 2009 Michael C. Urbanski
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Hyena.Data.Sqlite;
+
+using Banshee.Sources;
+using Banshee.Database;
+
+using Banshee.Collection;
+using Banshee.Collection.Database;
+
+using Banshee.Paas.Gui;
+
+namespace Banshee.Paas.Data
+{
+ public class PaasTrackListModel : DatabaseTrackListModel
+ {
+ public PaasTrackListModel (BansheeDbConnection conn, IDatabaseTrackModelProvider provider, DatabaseSource source) : base (conn, provider, source)
+ {
+ From = String.Format ("{0}, {1}, {2}", provider.From, PaasChannel.Provider.TableName, PaasItem.Provider.TableName);
+
+ AddCondition (From, String.Format (
+ @"{1}.ID = CoreTracks.ExternalID AND
+ {0}.ID = {1}.ChannelID AND
+ {1}.Active = 1",
+ PaasChannel.Provider.TableName, PaasItem.Provider.TableName
+ ));
+ }
+
+ protected override void GenerateSortQueryPart ()
+ {
+ SortQuery = (SortColumn == null)
+ ? GetSort ("album", true)
+ : GetSort (SortColumn.SortKey, SortColumn.SortType == Hyena.Data.SortType.Ascending);
+ }
+
+ public override void UpdateUnfilteredAggregates ()
+ {
+ HyenaSqliteCommand count_command = new HyenaSqliteCommand (String.Format (
+ "SELECT COUNT(*) {0} AND PaasItems.DownloadedAt NOT NULL", UnfilteredQuery
+ ));
+
+ UnfilteredCount = Connection.Query<int> (count_command);
+ }
+
+ public static string GetSort (string key, bool asc)
+ {
+ string ascDesc = asc ? "ASC" : "DESC";
+ string sort_query = null;
+
+ switch (key) {
+ case "PubDate":
+ sort_query = String.Format ("PaasItems.PubDate {0}", ascDesc);
+ break;
+ case "IsNew":
+ sort_query = String.Format ("-PaasItems.IsNew {0}, PaasItems.PubDate DESC", ascDesc);
+ break;
+ case "IsDownloaded":
+ sort_query = String.Format (@"
+ PaasItems.LocalPath IS NOT NULL {0}, PaasItems.PubDate DESC", ascDesc);
+ break;
+ case "album":
+ sort_query = String.Format (
+ "HYENA_COLLATION_KEY(PaasChannels.Name) {0}, PaasItems.PubDate DESC", ascDesc
+ );
+ break;
+ }
+
+ return sort_query ?? Banshee.Query.BansheeQuery.GetSort (key, asc);
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasUnheardFilterModel.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasUnheardFilterModel.cs
new file mode 100644
index 0000000..c67a627
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/PaasUnheardFilterModel.cs
@@ -0,0 +1,85 @@
+//
+// PaasUnheardFilterModel.cs
+//
+// Authors:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using Banshee.Collection;
+using Banshee.Collection.Database;
+
+namespace Banshee.Paas.Data
+{
+ public enum OldNewFilter
+ {
+ Both,
+ New,
+ Old
+ }
+
+ public class PaasUnheardFilterModel : FilterListModel<OldNewFilter>
+ {
+ public PaasUnheardFilterModel (DatabaseTrackListModel trackModel) : base (trackModel)
+ {
+ Selection.Clear (false);
+ Selection.QuietSelect (0);
+ }
+
+ public override void Reload (bool notify)
+ {
+ if (notify)
+ OnReloaded ();
+ }
+
+ public override void Clear ()
+ {
+ }
+
+ public override OldNewFilter this [int index] {
+ get {
+ switch (index) {
+ case 1: return OldNewFilter.Old;
+ case 2: return OldNewFilter.New;
+ case 0:
+ default: return OldNewFilter.Both;
+ }
+ }
+ }
+
+ public override int Count {
+ get { return 3; }
+ }
+
+ public override string GetSqlFilter ()
+ {
+ if (Selection.AllSelected) {
+ return null;
+ } else if (Selection.Contains (2)) {
+ return "PaasItems.IsNew = 1";
+ } else {
+ return "PaasItems.IsNew = 0";
+ }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/SingletonSelection.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/SingletonSelection.cs
new file mode 100644
index 0000000..4d0ef7a
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Data/SingletonSelection.cs
@@ -0,0 +1,47 @@
+//
+// SingletonSelection.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Hyena.Collections;
+
+namespace Banshee.Paas.Data
+{
+ public class SingletonSelection : Selection
+ {
+ public SingletonSelection ()
+ {
+ }
+
+ protected override void OnChanged ()
+ {
+ int fi = FirstIndex;
+ Clear (false);
+ QuietSelect (fi);
+ base.OnChanged ();
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Data/QueuedDownloadTask.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Data/QueuedDownloadTask.cs
new file mode 100644
index 0000000..351ed81
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Data/QueuedDownloadTask.cs
@@ -0,0 +1,86 @@
+//
+// QueuedDownloadTask.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Hyena;
+using Hyena.Data;
+using Hyena.Data.Sqlite;
+
+using Banshee.ServiceStack;
+using Banshee.Collection.Database;
+
+namespace Banshee.Paas.DownloadManager.Data
+{
+ public class QueuedDownloadTaskProvider : SqliteModelProvider<QueuedDownloadTask>
+ {
+ public QueuedDownloadTaskProvider (HyenaSqliteConnection connection) : base (connection, "QueuedDownloadTasks")
+ {
+ }
+ }
+
+ public class QueuedDownloadTask
+ {
+ private static QueuedDownloadTaskProvider provider;
+
+ public static QueuedDownloadTaskProvider Provider {
+ get { return provider; }
+ }
+
+ static QueuedDownloadTask () {
+ provider = new QueuedDownloadTaskProvider (ServiceManager.DbConnection);
+ }
+
+ private long dbid;
+ [DatabaseColumn ("ID", Constraints = DatabaseColumnConstraints.PrimaryKey)]
+ public long DbId {
+ get { return dbid; }
+ protected set { dbid = value; }
+ }
+
+ private long primary_source_id;
+ [DatabaseColumn ("PrimarySourceID", Index = "QueuedDownloadTaskPrimarySourceIDIndex")]
+ public long PrimarySourceID {
+ get { return primary_source_id; }
+ set { primary_source_id = value; }
+ }
+
+ private long external_id;
+ [DatabaseColumn ("ExternalID", Index = "QueuedDownloadTaskExternalIDIndex")]
+ public long ExternalID {
+ get { return external_id; }
+ set { external_id = value; }
+ }
+
+ private long position;
+ [DatabaseColumn ("Position", Index = "QueuedDownloadTaskPositionIndex")]
+ public long Position {
+ get { return position; }
+ set { position = value; }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadListView.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadListView.cs
new file mode 100644
index 0000000..c624dca
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadListView.cs
@@ -0,0 +1,125 @@
+//
+// DownloadListView.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Text;
+
+using Gtk;
+using Mono.Unix;
+
+using Hyena.Data.Gui;
+
+using Migo2.DownloadService;
+
+using Banshee.Gui;
+using Banshee.ServiceStack;
+using Banshee.Collection.Gui;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.DownloadManager.Gui
+{
+ public class DownloadListView : ListView<HttpFileDownloadTask>
+ {
+ private PaasDownloadManager manager;
+
+ public DownloadListView (PaasDownloadManager manager)
+ {
+ this.manager = manager;
+
+ IsReorderable = true;
+ IsEverReorderable = true;
+
+ ColumnCellText name_renderer = new ColumnCellText ("Name", true);
+ ColumnCellText progress_renderer = new ColumnCellText ("Progress", true);
+
+ ColumnController = new ColumnController ();
+
+ ColumnController.Add (new Column (Catalog.GetString ("Downloads"), name_renderer, 0.7));
+ ColumnController.Add (new Column (Catalog.GetString ("Progress"), progress_renderer, 0.3));
+
+ //ColumnController = column_controller;
+ //RowHeightProvider = renderer.ComputeRowHeight;
+ }
+/*
+ protected override bool OnPopupMenu ()
+ {
+ ServiceManager.Get<InterfaceActionService> ().FindAction ("Paas.PaasChannelPopupAction").Activate ();
+ return true;
+ }
+*/
+
+#region D&D
+// Dragon-Drop
+
+// _ _
+// _ //` `\
+// _,-"\% // /``\`\
+// ~^~ >__^ |% // / } `\`\
+// ) )%// / } } }`\`\
+// / (%/'/.\_/\_/\_/\`/
+// ( ' `-._`
+// \ , ( \ _`-.__.-;%>
+// /_`\ \ `\ \." `-..-'`
+// ``` /_/`"-=-'`/_/
+//
+ protected override void OnDragSourceSet ()
+ {
+ base.OnDragSourceSet ();
+ }
+
+ protected override bool OnDragDrop (Gdk.DragContext context, int x, int y, uint time_)
+ {
+ y = TranslateToListY (y);
+
+ if (Gtk.Drag.GetSourceWidget (context) == this) {
+ DownloadSource source = ServiceManager.SourceManager.ActiveSource as DownloadSource;
+
+ if (source != null) {
+ int row = GetRowAtY (y);
+ DownloadListModel model = source.DownloadListModel;
+
+ if (row != GetRowAtY (y + RowHeight / 2)) {
+ row += 1;
+ }
+
+ if (model.Selection.Contains (row)) {
+ return false;
+ }
+
+ try {
+ manager.Move (row, model.GetSelected ());
+ } catch {}
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+#endregion
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadManagerInterface.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadManagerInterface.cs
new file mode 100644
index 0000000..170bb48
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadManagerInterface.cs
@@ -0,0 +1,191 @@
+//
+// DownloadManagerInterface.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.ComponentModel;
+
+using Gtk;
+
+using Migo2.Async;
+using Migo2.DownloadService;
+
+using Banshee.Base;
+using Banshee.ServiceStack;
+
+using Banshee.Paas.Gui;
+
+namespace Banshee.Paas.DownloadManager.Gui
+{
+ public class DownloadManagerInterface : IDisposable
+ {
+ private bool cancelled;
+ private PaasSource source;
+ private PaasDownloadManager manager;
+ private DownloadUserJob downloadJob;
+
+ private DownloadSource downloadSource;
+ private DownloadListModel download_model;
+
+ public DownloadManagerInterface (PaasSource source, PaasDownloadManager manager)
+ {
+ if (manager == null) {
+ throw new ArgumentNullException ("manager");
+ }
+
+ this.source = source;
+ this.manager = manager;
+
+ download_model = new DownloadListModel ();
+ DownloadListView download_view = new DownloadListView (manager);
+
+ downloadSource = new DownloadSource (download_model, download_view);
+
+ manager.Started += OnManagerStartedHandler;
+ manager.Stopped += OnManagerStoppedHandler;
+ manager.ProgressChanged += OnManagerProgressChangedHandler;
+ manager.StatusChanged += OnManagerStatusChangedHandler;
+
+ manager.TaskAdded += OnManagerTaskAddedHandler;
+ manager.TaskProgressChanged += (sender, e) => {
+ ThreadAssist.ProxyToMain (delegate {
+ if (cancelled) {
+ return;
+ }
+
+ download_model.Reload ();
+ });
+ };
+
+ manager.TaskCompleted += (sender, e) => {
+ ThreadAssist.ProxyToMain (delegate {
+ if (e.Task.State != TaskState.Paused) {
+ lock (this) {
+ if (cancelled) {
+ return;
+ }
+ }
+
+ download_model.Remove (e.Task);
+ }
+ });
+ };
+
+ manager.Reordered += (sender, e) => {
+ ThreadAssist.ProxyToMain (delegate {
+ download_model.Reorder (e.NewOrder);
+ });
+ };
+ }
+
+ public void Dispose ()
+ {
+ ThreadAssist.AssertInMainThread ();
+
+ if (manager != null) {
+ manager.Started -= OnManagerStartedHandler;
+ manager.Stopped -= OnManagerStoppedHandler;
+ manager.ProgressChanged -= OnManagerProgressChangedHandler;
+ manager.StatusChanged -= OnManagerStatusChangedHandler;
+
+ manager = null;
+ }
+
+ if (downloadJob != null) {
+ downloadJob.CancelRequested -= OnCancelRequested;
+ downloadJob.Finish ();
+ downloadJob = null;
+ source.RemoveChildSource (downloadSource);
+ downloadSource = null;
+ }
+ }
+
+ private void OnManagerStartedHandler (object sender, EventArgs e)
+ {
+ ThreadAssist.ProxyToMain (delegate {
+ cancelled = false;
+
+ if (downloadJob == null) {
+ source.AddChildSource (downloadSource);
+
+ downloadJob = new DownloadUserJob ();
+ downloadJob.CancelRequested += OnCancelRequested;
+ downloadJob.Register ();
+ }
+ });
+ }
+
+ private void OnManagerStoppedHandler (object sender, EventArgs e)
+ {
+ ThreadAssist.ProxyToMain (delegate {
+ download_model.Clear ();
+
+ if (downloadJob != null) {
+ downloadJob.CancelRequested -= OnCancelRequested;
+ downloadJob.Finish ();
+ downloadJob = null;
+ source.RemoveChildSource (downloadSource);
+ }
+ });
+ }
+
+ private void OnManagerProgressChangedHandler (object sender, ProgressChangedEventArgs e)
+ {
+ ThreadAssist.ProxyToMain (delegate {
+ if (downloadJob != null) {
+ downloadJob.UpdateProgress (e.ProgressPercentage);
+ }
+ });
+ }
+
+ private void OnManagerStatusChangedHandler (object sender, GroupStatusChangedEventArgs e)
+ {
+ HttpDownloadGroupStatusChangedEventArgs args = e as HttpDownloadGroupStatusChangedEventArgs;
+
+ ThreadAssist.ProxyToMain (delegate {
+ if (downloadJob != null) {
+ downloadJob.UpdateStatus (args.RunningTasks, args.RemainingTasks, args.CompletedTasks, args.BytesPerSecond);
+ }
+ });
+ }
+
+ private void OnManagerTaskAddedHandler (object sender, TaskAddedEventArgs<HttpFileDownloadTask> e)
+ {
+ ThreadAssist.ProxyToMain (delegate {
+ if (e.TaskPair != null) {
+ download_model.AddTaskPair (e.TaskPair);
+ } else if (e.TaskPairs != null) {
+ download_model.AddTaskPairs (e.TaskPairs);
+ }
+ });
+ }
+
+ private void OnCancelRequested (object sender, EventArgs e)
+ {
+ cancelled = true;
+ manager.CancelAsync ();
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadSource.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadSource.cs
new file mode 100644
index 0000000..53aee1e
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadSource.cs
@@ -0,0 +1,92 @@
+//
+// DownloadSource.cs
+//
+// Authors:
+// Mike Urbanski <michael c urbanski gmail com>
+// Aaron Bockover <abockover novell com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+// Copyright (C) 2007 Novell, Inc. (ErrorSource.cs)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Mono.Unix;
+
+using Hyena.Data;
+using Hyena.Collections;
+
+using Banshee.Base;
+using Banshee.Sources;
+using Banshee.Sources.Gui;
+
+using Migo2.Async;
+using Migo2.Collections;
+using Migo2.DownloadService;
+
+namespace Banshee.Paas.DownloadManager.Gui
+{
+ public class DownloadSource : Source
+ {
+ private ISourceContents contents;
+ private DownloadListModel download_model;
+
+ public DownloadListModel DownloadListModel
+ {
+ get { return download_model; }
+ }
+
+ public DownloadSource (DownloadListModel model, DownloadListView view) : base (Catalog.GetString ("Downloads"), Catalog.GetString ("Downloads"), 0)
+ {
+ if (model == null) {
+ throw new ArgumentNullException ("model");
+ }
+
+ TypeUniqueId = "DownloadSource";
+
+ download_model = model;
+ download_model.Cleared += (sender, e) => { OnUpdated (); };
+ download_model.Reloaded += (sender, e) => { QueueDraw (); OnUpdated (); };
+
+ Properties.SetStringList ("Icon.Name", "gtk-network", "network");
+
+ contents = new DownloadSourceContents (view);
+ Properties.Set<ISourceContents> ("Nereid.SourceContents", contents);
+ Properties.Set<bool> ("Nereid.SourceContentsPropagate", false);
+ }
+
+ public override void Activate ()
+ {
+ download_model.Reload ();
+ }
+
+ public void QueueDraw ()
+ {
+ ThreadAssist.ProxyToMain (delegate {
+ contents.Widget.QueueDraw ();
+ });
+ }
+
+ public override int Count {
+ get { return download_model.Count; }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadSourceContents.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadSourceContents.cs
new file mode 100644
index 0000000..fe24301
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadSourceContents.cs
@@ -0,0 +1,81 @@
+//
+// DownloadSourceContents.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Gtk;
+
+using Banshee.Sources;
+using Banshee.Sources.Gui;
+
+namespace Banshee.Paas.DownloadManager.Gui
+{
+ public class DownloadSourceContents : ISourceContents
+ {
+ private ScrolledWindow sw;
+ private DownloadSource download_source;
+ private DownloadListView download_view;
+
+ public ISource Source {
+ get { return download_source; }
+ }
+
+ public Widget Widget {
+ get { return sw as Widget; }
+ }
+
+ public DownloadSourceContents (DownloadListView view)
+ {
+ download_view = view;
+
+ sw = new ScrolledWindow ();
+ sw.Add (download_view);
+ sw.ShowAll ();
+ }
+
+ public bool SetSource (ISource source)
+ {
+ download_source = source as DownloadSource;
+
+ if (download_source == null) {
+ return false;
+ }
+
+ download_view.HeaderVisible = true;
+ download_view.SetModel (download_source.DownloadListModel);
+
+ return true;
+ }
+
+ public void ResetSource ()
+ {
+ download_source = null;
+
+ download_view.SetModel (null);
+ download_view.HeaderVisible = false;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadUserJob.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadUserJob.cs
new file mode 100644
index 0000000..d6c4ac7
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadUserJob.cs
@@ -0,0 +1,147 @@
+//
+// DownloadUserJob.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Gtk;
+using Mono.Unix;
+
+using Migo2.Utils;
+
+using Banshee.ServiceStack;
+
+namespace Banshee.Paas.DownloadManager.Gui
+{
+ public class DownloadUserJob : UserJob
+ {
+ private bool disposed = false;
+ private bool canceled = false;
+ private bool cancelRequested = false;
+
+ private readonly object sync = new object ();
+
+ public DownloadUserJob () : base (Catalog.GetString ("Downloads"), String.Empty, String.Empty)
+ {
+ CancelRequested += OnCancelRequested;
+
+ Title = Catalog.GetString ("Downloading");
+ Status = Catalog.GetString ("Initializing...");
+ CancelMessage = Catalog.GetString ("Cancel all downloads?");
+
+ this.IconNames = new string[1] { Stock.Network };
+
+ CanCancel = true;
+ }
+
+ public void Dispose ()
+ {
+ lock (sync) {
+ if (disposed) {
+ throw new ObjectDisposedException (GetType ().FullName);
+ } else if (cancelRequested) {
+ throw new InvalidOperationException ("Cannot dispose object while canceling.");
+ } else {
+ disposed = true;
+ }
+
+ CancelRequested -= OnCancelRequested;
+ }
+ }
+
+ private bool SetCanceled ()
+ {
+ bool ret = false;
+
+ lock (sync) {
+ if (!cancelRequested && !canceled && !disposed) {
+ CanCancel = false;
+ ret = cancelRequested = true;
+ }
+ }
+
+ return ret;
+ }
+
+ public void UpdateProgress (int progress)
+ {
+ if (progress < 0 || progress > 100) {
+ throw new ArgumentException ("progress: Must be between 0 and 100.");
+ }
+
+ lock (sync) {
+ if (canceled || cancelRequested || disposed) {
+ return;
+ }
+
+ Progress = (double) progress / 100;
+ }
+ }
+
+ public void UpdateStatus (int downloading, int remaining, int completed, long bytesPerSecond)
+ {
+ if (downloading < 0) {
+ throw new ArgumentException ("Must be positive.", "downloading");
+ } else if (bytesPerSecond < 0) {
+ bytesPerSecond = 0;
+ }
+
+ lock (sync) {
+ if (canceled || cancelRequested || disposed) {
+ return;
+ }
+
+ string status;
+
+ if (downloading > 0) {
+ status = Catalog.GetPluralString (
+ "Currently transferring {0} file at {1}/s",
+ "Currently transferring {0} files at {1}/s", downloading
+ );
+ } else {
+ status = Catalog.GetString ("All downloads are currently idle");
+ }
+
+ Status = String.Format (status, downloading, UnitUtils.ToString (bytesPerSecond));
+ }
+ }
+
+ private void OnCancelRequested (object sender, EventArgs e)
+ {
+ if (SetCanceled ()) {
+ lock (sync) {
+ Progress = 0.0;
+ Title = Catalog.GetString ("Canceling Downloads");
+ Status = Catalog.GetString (
+ "Waiting for downloads to terminate..."
+ );
+
+ cancelRequested = false;
+ canceled = true;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager/DownloadListModel.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager/DownloadListModel.cs
new file mode 100644
index 0000000..2f3a993
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager/DownloadListModel.cs
@@ -0,0 +1,74 @@
+//
+// DownloadListModel.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+using Mono.Unix;
+
+using Hyena.Data;
+using Hyena.Collections;
+
+using Migo2.Async;
+using Migo2.Collections;
+using Migo2.DownloadService;
+
+using Banshee.Paas.Utils;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.DownloadManager
+{
+ public class DownloadListModel : ListModel<HttpFileDownloadTask>
+ {
+ public override bool CanReorder {
+ get { return true; }
+ }
+
+ public void AddTaskPair (Pair<int,HttpFileDownloadTask> pair)
+ {
+ if (pair != null && pair.Second != null) {
+ Items.Insert (pair.First, pair.Second);
+ }
+
+ OnReloaded ();
+ }
+
+ public void AddTaskPairs (IEnumerable<Pair<int,HttpFileDownloadTask>> pairs)
+ {
+ if (pairs != null) {
+ foreach (Pair<int,HttpFileDownloadTask> pair in pairs) {
+ if (pair != null && pair.Second != null) {
+ Items.Insert (pair.First, pair.Second);
+ }
+ }
+ }
+
+ OnReloaded ();
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager/PaasDownloadManager.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager/PaasDownloadManager.cs
new file mode 100644
index 0000000..3816a61
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.DownloadManager/PaasDownloadManager.cs
@@ -0,0 +1,365 @@
+//
+// PaasDownloadManager.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
+using Hyena.Collections;
+
+using Migo2.Async;
+using Migo2.Collections;
+using Migo2.DownloadService;
+
+using Banshee.ServiceStack;
+
+using Banshee.Paas.Data;
+using Banshee.Paas.DownloadManager.Data;
+
+namespace Banshee.Paas.DownloadManager
+{
+ public class PaasDownloadManager : HttpDownloadManager
+ {
+ long primary_source_id;
+ private Dictionary<long,HttpFileDownloadTask> downloads;
+
+ public PaasDownloadManager (long primarySourceID, int maxDownloads, string tmpDir) : base (maxDownloads, tmpDir)
+ {
+ primary_source_id = primarySourceID;
+ downloads = new Dictionary<long,HttpFileDownloadTask> ();
+
+ TaskCompleted += (sender, e) => {
+ if (e.Task.State != TaskState.Paused) {
+ lock (SyncRoot) {
+ downloads.Remove ((e.Task.UserState as PaasItem).DbId);
+ }
+ }
+ };
+ }
+
+ public override void CancelAsync ()
+ {
+ lock (SyncRoot) {
+ base.CancelAsync ();
+ ClearQueuedDownloads ();
+ }
+ }
+
+ public TaskState CheckActiveDownloadStatus (PaasItem item)
+ {
+ return CheckActiveDownloadStatus (item.DbId);
+ }
+
+ public TaskState CheckActiveDownloadStatus (long itemID)
+ {
+ lock (SyncRoot) {
+ if (downloads.ContainsKey (itemID)) {
+ return downloads[itemID].State;
+ }
+
+ return TaskState.None;
+ }
+ }
+
+ public bool Contains (PaasItem item)
+ {
+ return Contains (item.DbId);
+ }
+
+ public bool Contains (long itemID)
+ {
+ lock (SyncRoot) {
+ return downloads.ContainsKey (itemID);
+ }
+ }
+
+ public void QueueDownload (PaasItem item)
+ {
+ lock (SyncRoot) {
+ if (!IsDisposing) {
+ if (item != null && item.Active && !downloads.ContainsKey (item.DbId)) {
+ HttpFileDownloadTask task = CreateDownloadTask (item.Url, item);
+ task.Name = String.Format ("{0} - {1}", item.Channel.Name, item.Name);
+
+ downloads.Add (item.DbId, task);
+ try {
+ Add (task);
+ } catch { downloads.Remove (item.DbId); }
+ }
+ }
+ }
+ }
+
+ public void QueueDownload (IEnumerable<PaasItem> items)
+ {
+ lock (SyncRoot) {
+ if (!IsDisposing) {
+ HttpFileDownloadTask task = null;
+ List<HttpFileDownloadTask> tasks = new List<HttpFileDownloadTask> ();
+
+ foreach (PaasItem item in items.Where (i => i.Active && !i.IsDownloaded && !downloads.ContainsKey (i.DbId))) {
+ task = CreateDownloadTask (item.Url, item);
+ task.Name = String.Format ("{0} - {1}", item.Channel.Name, item.Name);
+
+ tasks.Add (task);
+ downloads.Add (item.DbId, task);
+ }
+
+ try {
+ if (tasks.Count > 0) {
+ Add (tasks);
+ }
+ } catch {
+ foreach (var t in tasks) {
+ downloads.Remove (((PaasItem)t.UserState).DbId);
+ }
+ }
+ }
+ }
+ }
+
+ public void CancelDownload (PaasItem item)
+ {
+ lock (SyncRoot) {
+ if (!IsDisposing) {
+ if (downloads.ContainsKey (item.DbId)) {
+ downloads[item.DbId].CancelAsync ();
+ //RemoveQueuedDownload (item.DbId);
+ }
+ }
+ }
+ }
+
+ public void CancelDownload (IEnumerable<PaasItem> items)
+ {
+ lock (SyncRoot) {
+ if (!IsDisposing) {
+ //RangeCollection rc = new RangeCollection ();
+
+ foreach (PaasItem item in items) {
+ if (downloads.ContainsKey (item.DbId)) {
+ //rc.Add ((int)item.DbId);
+ downloads[item.DbId].CancelAsync ();
+ }
+ }
+
+ //RemoveQueuedDownloadRange (rc);
+ }
+ }
+ }
+
+ public void PauseDownload (PaasItem item)
+ {
+ lock (SyncRoot) {
+ if (!IsDisposing) {
+ if (downloads.ContainsKey (item.DbId)) {
+ downloads[item.DbId].PauseAsync ();
+ }
+ }
+ }
+ }
+
+ public void PauseDownload (IEnumerable<PaasItem> items)
+ {
+ lock (SyncRoot) {
+ if (!IsDisposing) {
+ foreach (PaasItem item in items) {
+ if (downloads.ContainsKey (item.DbId)) {
+ downloads[item.DbId].PauseAsync ();
+ }
+ }
+ }
+ }
+ }
+
+ public void ResumeDownload (PaasItem item)
+ {
+ lock (SyncRoot) {
+ if (!IsDisposing) {
+ if (downloads.ContainsKey (item.DbId)) {
+ downloads[item.DbId].ResumeAsync ();
+ }
+ }
+ }
+ }
+
+ public void ResumeDownload (IEnumerable<PaasItem> items)
+ {
+ lock (SyncRoot) {
+ if (!IsDisposing) {
+ foreach (PaasItem item in items) {
+ if (downloads.ContainsKey (item.DbId)) {
+ downloads[item.DbId].ResumeAsync ();
+ }
+ }
+ }
+ }
+ }
+
+ public void RestoreQueuedDownloads ()
+ {
+ lock (SyncRoot) {
+ if (IsDisposing) {
+ return;
+ }
+
+ QueueDownload (PaasItem.Provider.FetchQueued (primary_source_id));
+ }
+ }
+
+ private void SaveQueuedDownloads ()
+ {
+ lock (SyncRoot) {
+ ServiceManager.DbConnection.BeginTransaction ();
+
+ try {
+ ClearQueuedDownloads ();
+
+ int i = 0;
+ foreach (var task in Tasks) {
+ AddQueuedDownload ((task.UserState as PaasItem).DbId, i++);
+ }
+
+ ServiceManager.DbConnection.CommitTransaction ();
+ } catch {
+ ServiceManager.DbConnection.RollbackTransaction ();
+ throw;
+ }
+ }
+ }
+
+ private void ClearQueuedDownloads ()
+ {
+ ServiceManager.DbConnection.Execute (
+ String.Format (
+ "DELETE FROM {0} WHERE PrimarySourceID = ?",
+ QueuedDownloadTask.Provider.From
+ ), primary_source_id
+ );
+ }
+
+ private void AddQueuedDownload (long item_id, long position)
+ {
+ QueuedDownloadTask.Provider.Save (new QueuedDownloadTask () {
+ PrimarySourceID = primary_source_id,
+ ExternalID = item_id,
+ Position = position
+ });
+ }
+
+// private void AddQueuedDownloads (IEnumerable<Pair<int, HttpFileDownloadTask>> pairs)
+// {
+// PaasItem item = null;
+// ServiceManager.DbConnection.BeginTransaction ();
+//
+// try {
+// foreach (Pair<int,HttpFileDownloadTask> pair in pairs) {
+// item = pair.Second.UserState as PaasItem;
+//
+// if (item != null) {
+// AddQueuedDownload (item.DbId, (int)pair.First);
+// }
+// }
+//
+// ServiceManager.DbConnection.CommitTransaction ();
+// } catch {
+// ServiceManager.DbConnection.RollbackTransaction ();
+// throw;
+// }
+// }
+
+// private void RemoveQueuedDownload (long item_id)
+// {
+// ServiceManager.DbConnection.Execute (
+// String.Format (
+// "DELETE FROM {0} WHERE PrimarySourceID = ? AND ExternalID = ?",
+// QueuedDownloadTask.Provider.From
+// ), primary_source_id, item_id
+// );
+// }
+//
+// private void RemoveQueuedDownloadRange (IEnumerable<int> ids)
+// {
+// RangeCollection rc = new RangeCollection ();
+//
+// foreach (int i in ids) {
+// rc.Add (i);
+// }
+//
+// RemoveQueuedDownloadRange (rc);
+// }
+//
+// private void RemoveQueuedDownloadRange (RangeCollection collection)
+// {
+// ServiceManager.DbConnection.BeginTransaction ();
+//
+// try {
+// foreach (RangeCollection.Range range in collection.Ranges) {
+// ServiceManager.DbConnection.Execute (
+// String.Format (
+// "DELETE FROM {0} WHERE PrimarySourceID = ? AND ExternalID >= ? AND ExternalID <= ?",
+// QueuedDownloadTask.Provider.From
+// ), primary_source_id, range.Start, range.End
+// );
+// }
+//
+// ServiceManager.DbConnection.CommitTransaction ();
+// } catch {
+// ServiceManager.DbConnection.RollbackTransaction ();
+// throw;
+// }
+// }
+
+ public override void Dispose ()
+ {
+ if (SetDisposing ()) {
+ SaveQueuedDownloads ();
+ base.Dispose ();
+ }
+ }
+
+// protected override void OnTaskAdded (int pos, HttpFileDownloadTask task)
+// {
+// AddQueuedDownload ((task.UserState as PaasItem).DbId, pos);
+// base.OnTaskAdded (pos, task);
+// }
+//
+// protected override void OnTasksAdded (ICollection<Migo2.Collections.Pair<int, HttpFileDownloadTask>> pairs)
+// {
+// AddQueuedDownloads (pairs);
+// base.OnTasksAdded (pairs);
+// }
+
+// protected override void OnTaskCompleted (HttpFileDownloadTask task, TaskCompletedEventArgs e)
+// {
+// if (e.State == TaskState.Succeeded) {
+// RemoveQueuedDownload ((task.UserState as PaasItem).DbId);
+// }
+//
+// base.OnTaskCompleted (task, e);
+// }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/ColumnCellChannel.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/ColumnCellChannel.cs
new file mode 100644
index 0000000..72ac206
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/ColumnCellChannel.cs
@@ -0,0 +1,197 @@
+//
+// ColumnCellChannel.cs
+//
+// Authors:
+// Gabriel Burt <gburt novell com>
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (C) 2009 Michael C. Urbanski
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using Gtk;
+using Cairo;
+
+using Mono.Unix;
+
+using Hyena.Gui;
+using Hyena.Gui.Theming;
+using Hyena.Data.Gui;
+
+using Banshee.Gui;
+using Banshee.ServiceStack;
+using Banshee.Collection.Gui;
+
+using Banshee.Paas.Data;
+using Banshee.Paas.Aether;
+
+namespace Banshee.Paas.Gui
+{
+ public class ColumnCellChannel : ColumnCell, IColumnCellDataHelper
+ {
+ private static int image_spacing = 4;
+ private static int image_size = 48;
+
+ // TODO replace this w/ new icon installation etc
+ private static ImageSurface default_cover_image = new PixbufImageSurface (
+ IconThemeUtils.LoadIcon (image_size, "podcast")
+ );
+
+ private static ImageSurface updating_image = PixbufImageSurface.Create (
+ IconThemeUtils.LoadIcon (image_size, Stock.Refresh), true
+ );
+
+ private ArtworkManager artwork_manager;
+
+ public ColumnCellDataHelper DataHelper { get; set; }
+
+ public ColumnCellChannel () : base (null, true)
+ {
+ artwork_manager = ServiceManager.Get<ArtworkManager> ();
+ }
+
+ public override void Render (CellContext context, StateType state, double cellWidth, double cellHeight)
+ {
+ if (BoundObject == null) {
+ return;
+ }
+
+ if (!(BoundObject is PaasChannel)) {
+ throw new InvalidCastException("ColumnCellChannel can only bind to PaasChannel objects");
+ }
+
+ PaasChannel channel = (PaasChannel)BoundObject;
+
+ bool disable_border = false;
+
+ ImageSurface image = (artwork_manager == null) ? null
+ : artwork_manager.LookupScaleSurface (PaasService.ArtworkIdFor (channel), image_size, true);
+
+ bool waiting = false;
+
+ if (DataHelper != null) {
+ switch ((ChannelUpdateStatus)DataHelper (this, channel)) {
+ case ChannelUpdateStatus.Waiting:
+ waiting = true;
+ goto case ChannelUpdateStatus.Updating;
+ case ChannelUpdateStatus.Updating:
+ image = updating_image;
+ disable_border = true;
+ break;
+ }
+ }
+
+ if (image == null) {
+ image = default_cover_image;
+ disable_border = true;
+ }
+
+ // int image_render_size = is_default ? image.Height : (int)cellHeight - 8;
+ int image_render_size = image_size;
+ int x = image_spacing;
+ int y = ((int)cellHeight - image_render_size) / 2;
+
+ ArtworkRenderer.RenderThumbnail (context.Context, image, false, x, y,
+ image_render_size, image_render_size, !disable_border, context.Theme.Context.Radius, !waiting
+ );
+
+ int fl_width = 0, fl_height = 0, sl_width = 0, sl_height = 0;
+ Cairo.Color text_color = context.Theme.Colors.GetWidgetColor (GtkColorClass.Text, state);
+ text_color.A = 0.75;
+
+ Pango.Layout layout = context.Layout;
+ layout.Width = (int)((cellWidth - cellHeight - x - 10) * Pango.Scale.PangoScale);
+ layout.Ellipsize = Pango.EllipsizeMode.End;
+ layout.FontDescription.Weight = Pango.Weight.Bold;
+
+ // Compute the layout sizes for both lines for centering on the cell
+ int old_size = layout.FontDescription.Size;
+
+ layout.SetText (channel.Name ?? String.Empty);
+ layout.GetPixelSize (out fl_width, out fl_height);
+
+ if (channel.DbId > 0) {
+ layout.FontDescription.Weight = Pango.Weight.Normal;
+ layout.FontDescription.Size = (int)(old_size * Pango.Scale.Small);
+ layout.FontDescription.Style = Pango.Style.Italic;
+
+ if (channel.LastDownloadTime == DateTime.MinValue) {
+ layout.SetText (Catalog.GetString ("New!"));
+ } else if (channel.LastDownloadTime.Date == DateTime.Now.Date) {
+ layout.SetText (String.Format (Catalog.GetString ("Last updated at {0}"), channel.LastDownloadTime.ToShortTimeString ()));
+ } else {
+ layout.SetText (String.Format (Catalog.GetString ("Last updated on {0}"), channel.LastDownloadTime.ToLongDateString ()));
+ }
+
+ layout.GetPixelSize (out sl_width, out sl_height);
+ }
+
+ // Calculate the layout positioning
+ x = ((int)cellHeight - x) + 10;
+ y = (int)((cellHeight - (fl_height + sl_height)) / 2);
+
+ // Render the second line first since we have that state already
+ if (channel.DbId > 0) {
+ context.Context.MoveTo (x, y + fl_height);
+ context.Context.Color = text_color;
+ PangoCairoHelper.ShowLayout (context.Context, layout);
+ }
+
+ // Render the first line, resetting the state
+ //layout.SetText (channel.Name ?? String.Empty);
+ layout.FontDescription.Weight = Pango.Weight.Bold;
+ layout.FontDescription.Size = old_size;
+ layout.FontDescription.Style = Pango.Style.Normal;
+
+ layout.SetText (channel.Name ?? String.Empty);
+
+ context.Context.MoveTo (x, y);
+ text_color.A = 1;
+ context.Context.Color = text_color;
+ PangoCairoHelper.ShowLayout (context.Context, layout);
+ }
+
+ public int ComputeRowHeight (Widget widget)
+ {
+ int height;
+ int text_w, text_h;
+
+ Pango.Layout layout = new Pango.Layout (widget.PangoContext);
+ layout.FontDescription = widget.PangoContext.FontDescription.Copy ();
+
+ layout.FontDescription.Weight = Pango.Weight.Bold;
+ layout.SetText ("W");
+ layout.GetPixelSize (out text_w, out text_h);
+ height = text_h;
+
+ layout.FontDescription.Weight = Pango.Weight.Normal;
+ layout.FontDescription.Size = (int)(layout.FontDescription.Size * Pango.Scale.Small);
+ layout.FontDescription.Style = Pango.Style.Italic;
+ layout.SetText ("W");
+ layout.GetPixelSize (out text_w, out text_h);
+ height += text_h;
+
+ layout.Dispose ();
+
+ return (height < image_size ? image_size : height) + 6;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/ColumnCellDownloadStatus.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/ColumnCellDownloadStatus.cs
new file mode 100644
index 0000000..f84cc79
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/ColumnCellDownloadStatus.cs
@@ -0,0 +1,56 @@
+//
+// ColumnCellDownloadStatus.cs
+//
+// Authors:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+using Mono.Unix;
+
+using Hyena.Data.Gui;
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Gui
+{
+ public class ColumnCellDownloadStatus : ColumnCellText
+ {
+ public ColumnCellDownloadStatus () : base (null, true)
+ {
+ }
+
+ protected override string GetText (object obj)
+ {
+ DownloadedStatusFilter val = (DownloadedStatusFilter) obj;
+
+ switch (val) {
+ case DownloadedStatusFilter.Downloaded: return Catalog.GetString ("Episodes I've Downloaded");
+ case DownloadedStatusFilter.Both: return Catalog.GetString ("All");
+ case DownloadedStatusFilter.NotDownloaded: return Catalog.GetString ("Episodes I Haven't Downloaded");
+ }
+ return String.Empty;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/ColumnCellPaasStatusIndicator.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/ColumnCellPaasStatusIndicator.cs
new file mode 100644
index 0000000..4f93995
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/ColumnCellPaasStatusIndicator.cs
@@ -0,0 +1,123 @@
+//
+// ColumnCellPaasStatusIndicator.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using Gtk;
+
+using Hyena.Data.Gui;
+
+using Banshee.Gui;
+using Banshee.Collection;
+using Banshee.Collection.Gui;
+
+using Migo2.Async;
+
+using Banshee.Paas;
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Gui
+{
+ public class ColumnCellPaasStatusIndicator : ColumnCellStatusIndicator, IColumnCellDataHelper
+ {
+ protected enum Offset : int {
+ New = 0,
+ Downloading = 1,
+ Video = 2,
+ Count = 3,
+ }
+
+ private int icon_index = -1;
+ private bool sensitive = true;
+
+ public ColumnCellDataHelper DataHelper { get; set; }
+
+ protected override int PixbufCount {
+ get { return base.PixbufCount + (int)Offset.Count; }
+ }
+
+ public ColumnCellPaasStatusIndicator (string property) : base (property)
+ {
+ }
+
+ public ColumnCellPaasStatusIndicator (string property, bool expand) : base (property, expand)
+ {
+ }
+
+ protected override void LoadPixbufs ()
+ {
+ base.LoadPixbufs ();
+
+ Pixbufs[base.PixbufCount + (int)Offset.New] = IconThemeUtils.LoadIcon (PixbufSize, "podcast-new");
+ Pixbufs[base.PixbufCount + (int)Offset.Downloading] = IconThemeUtils.LoadIcon (PixbufSize, "document-save");
+ Pixbufs[base.PixbufCount + (int)Offset.Video] = IconThemeUtils.LoadIcon (PixbufSize, "video-x-generic");
+ }
+
+ protected override int GetIconIndex (TrackInfo track)
+ {
+ return icon_index;
+ }
+
+ public override void Render (CellContext context, StateType state, double cellWidth, double cellHeight)
+ {
+ sensitive = true;
+
+ PaasTrackInfo pti = PaasTrackInfo.From (BoundTrack);
+ PaasItem item = (pti != null) ? pti.Item : null;
+
+ if (pti == null || item == null) {
+ icon_index = -1;
+ } else {
+ TaskState task_state = (DataHelper != null) ? (TaskState)DataHelper (this, item) : TaskState.None;
+
+ switch (task_state) {
+ case TaskState.Ready:
+ sensitive = pti.Track.IsPlaying;
+ goto case TaskState.Running;
+ case TaskState.Running:
+ icon_index = base.PixbufCount + (int)Offset.Downloading;
+ break;
+ case TaskState.Paused:
+ icon_index = (int)Icon.Paused;
+ break;
+ case TaskState.Failed:
+ icon_index = (int)Icon.Error;
+ break;
+ default:
+ if (item.Error) {
+ icon_index = (int)Icon.Error;
+ } else {
+ icon_index = pti.IsNew ? base.PixbufCount + (int)Offset.New : -1;
+ }
+
+ break;
+ }
+ }
+
+ context.Opaque = sensitive;
+ base.Render (context, state, cellWidth, cellHeight);
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/ColumnCellPublished.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/ColumnCellPublished.cs
new file mode 100644
index 0000000..f7ecf17
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/ColumnCellPublished.cs
@@ -0,0 +1,41 @@
+//
+// ColumnCellPublished.cs
+//
+// Authors:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Banshee.Paas.Gui
+{
+ public class ColumnCellPublished : Banshee.Collection.Gui.ColumnCellDateTime
+ {
+ public ColumnCellPublished (string property, bool expand) : base (property, expand)
+ {
+ Format = Banshee.Collection.Gui.DateTimeFormat.ShortDate;
+ SetMinMaxStrings (new DateTime (2007, 12, 30));
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/ColumnCellUnheard.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/ColumnCellUnheard.cs
new file mode 100644
index 0000000..b1ba376
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/ColumnCellUnheard.cs
@@ -0,0 +1,57 @@
+//
+// ColumnCellUnheard.cs
+//
+// Authors:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+using Mono.Unix;
+
+using Hyena.Data.Gui;
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Gui
+{
+ public class ColumnCellUnheard : ColumnCellText
+ {
+ public ColumnCellUnheard () : base (null, true)
+ {
+ }
+
+ protected override string GetText (object obj)
+ {
+ OldNewFilter val = (OldNewFilter) obj;
+
+ switch (val) {
+ case OldNewFilter.New: return Catalog.GetString ("New Episodes");
+ case OldNewFilter.Both: return Catalog.GetString ("All");
+ case OldNewFilter.Old: return Catalog.GetString ("Reruns");
+ }
+
+ return String.Empty;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/Dialogs/ChannelPropertiesDialog.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/Dialogs/ChannelPropertiesDialog.cs
new file mode 100644
index 0000000..df4048a
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/Dialogs/ChannelPropertiesDialog.cs
@@ -0,0 +1,267 @@
+//
+// PodcastFeedPropertiesDialog.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2007-09 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// People have been asking for the ability to select a specific directory for
+// podcasts for almost four years. I've been hesitant because it raises a number
+// of issues related to migrating existing tracks (notice that iTunes doesn't even
+// allow this.) Right now you can select a directory for FUTURE downloads while
+// previously downloaded files remain in the previous directory.
+
+// I'm putting this in to satisfy certain people. Honestly I don't plan to include
+// it when it comes time for a larger release (too many people are going to bitch
+// about it not moving existing existing files. I may implement that at some point
+// just not now.)
+
+#undef EDIT_DIR_TEST
+
+using System;
+
+using Mono.Unix;
+
+using Gtk;
+using Pango;
+
+using Banshee.Base;
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Gui
+{
+ internal class ChannelPropertiesDialog : Dialog
+ {
+ private PaasChannel channel;
+#if EDIT_DIR_TEST
+ private FileChooserButton chooser;
+#endif
+ private DownloadPreferenceComboBox download_preference_combo;
+
+ public ChannelPropertiesDialog (PaasChannel channel)
+ {
+ this.channel = channel;
+
+ Title = channel.Name;
+
+ BuildWindow ();
+ }
+
+ private void BuildWindow ()
+ {
+ BorderWidth = 6;
+ VBox.Spacing = 12;
+ HasSeparator = false;
+
+ HBox box = new HBox ();
+ box.BorderWidth = 6;
+ box.Spacing = 12;
+
+ Button save_button = new Button ("gtk-save");
+ save_button.CanDefault = true;
+ save_button.Show();
+
+ // For later additions to the dialog. (I.E. Feed art)
+ HBox content_box = new HBox ();
+ content_box.Spacing = 12;
+
+ Table table = new Table (2, 4, false);
+ table.RowSpacing = 6;
+ table.ColumnSpacing = 12;
+
+ Label description_label = new Label (Catalog.GetString ("Description:"));
+ description_label.SetAlignment (0f, 0f);
+ description_label.Justify = Justification.Left;
+
+ Label last_updated_label = new Label (Catalog.GetString ("Last updated:"));
+ last_updated_label.SetAlignment (0f, 0f);
+ last_updated_label.Justify = Justification.Left;
+
+ Label name_label = new Label (Catalog.GetString ("Name:"));
+ name_label.SetAlignment (0f, 0f);
+ name_label.Justify = Justification.Left;
+
+ Label name_label_ = new Label ();
+ name_label_.SetAlignment (0f, 0f);
+ name_label_.Text = channel.Name;
+
+ Label channel_url_label = new Label (Catalog.GetString ("URL:"));
+ channel_url_label.SetAlignment (0f, 0f);
+ channel_url_label.Justify = Justification.Left;
+
+ Label new_episode_option_label = new Label (Catalog.GetString ("When channel is updated:"));
+ new_episode_option_label.SetAlignment (0f, 0.5f);
+ new_episode_option_label.Justify = Justification.Left;
+
+#if EDIT_DIR_TEST
+ Label enclosure_folder_label = new Label (Catalog.GetString ("Folder:"));
+ enclosure_folder_label.SetAlignment (0f, 0.5f);
+ enclosure_folder_label.Justify = Justification.Left;
+#endif
+
+ Label last_updated_text = new Label (channel.LastDownloadTime.ToString ("f"));
+ last_updated_text.Justify = Justification.Left;
+ last_updated_text.SetAlignment (0f, 0f);
+
+ Label channel_url_text = new Label (channel.Url.ToString ());
+ channel_url_text.Wrap = false;
+ channel_url_text.Selectable = true;
+ channel_url_text.SetAlignment (0f, 0f);
+ channel_url_text.Justify = Justification.Left;
+ channel_url_text.Ellipsize = Pango.EllipsizeMode.End;
+
+ string description_string = String.IsNullOrEmpty (channel.Description) ?
+ Catalog.GetString ("No description available") :
+ channel.Description;
+
+ Label descrition_text = new Label (description_string);
+ descrition_text.Justify = Justification.Left;
+ descrition_text.SetAlignment (0f, 0f);
+ descrition_text.Wrap = true;
+ descrition_text.Selectable = true;
+
+ Viewport description_viewport = new Viewport ();
+ description_viewport.SetSizeRequest (-1, 150);
+ description_viewport.ShadowType = ShadowType.None;
+
+ ScrolledWindow description_scroller = new ScrolledWindow () {
+ HscrollbarPolicy = PolicyType.Never,
+ VscrollbarPolicy = PolicyType.Automatic
+ };
+
+ description_viewport.Add (descrition_text);
+ description_scroller.Add (description_viewport);
+
+#if EDIT_DIR_TEST
+ chooser = new FileChooserButton (Catalog.GetString ("File..."), FileChooserAction.SelectFolder);
+ chooser.SetCurrentFolder (channel.LocalEnclosurePath);
+#endif
+ download_preference_combo = new DownloadPreferenceComboBox (channel.DownloadPreference);
+
+ // First column
+ uint i = 0;
+ table.Attach (
+ name_label, 0, 1, i, ++i,
+ AttachOptions.Fill, AttachOptions.Fill, 0, 0
+ );
+
+ table.Attach (
+ channel_url_label, 0, 1, i, ++i,
+ AttachOptions.Fill, AttachOptions.Fill, 0, 0
+ );
+
+ table.Attach (
+ last_updated_label, 0, 1, i, ++i,
+ AttachOptions.Fill, AttachOptions.Fill, 0, 0
+ );
+
+#if EDIT_DIR_TEST
+ table.Attach (
+ enclosure_folder_label, 0, 1, i, ++i,
+ AttachOptions.Fill, AttachOptions.Fill, 0, 0
+ );
+#endif
+ table.Attach (
+ new_episode_option_label, 0, 1, i, ++i,
+ AttachOptions.Fill, AttachOptions.Fill, 0, 0
+ );
+
+ table.Attach (
+ description_label, 0, 1, i, ++i,
+ AttachOptions.Fill, AttachOptions.Fill, 0, 0
+ );
+
+ // Second column
+ i = 0;
+ table.Attach (
+ name_label_, 1, 2, i, ++i,
+ AttachOptions.Fill, AttachOptions.Fill, 0, 0
+ );
+
+ table.Attach (
+ channel_url_text, 1, 2, i, ++i,
+ AttachOptions.Fill, AttachOptions.Fill, 0, 0
+ );
+
+ table.Attach (
+ last_updated_text, 1, 2, i, ++i,
+ AttachOptions.Fill, AttachOptions.Fill, 0, 0
+ );
+
+#if EDIT_DIR_TEST
+ table.Attach (chooser, 1, 2, i, ++i,
+ AttachOptions.Fill, AttachOptions.Fill, 0, 0
+ );
+#endif
+ table.Attach (
+ download_preference_combo, 1, 2, i, ++i,
+ AttachOptions.Fill, AttachOptions.Fill, 0, 0
+ );
+
+ table.Attach (description_scroller, 1, 2, i, ++i,
+ AttachOptions.Expand | AttachOptions.Fill,
+ AttachOptions.Expand | AttachOptions.Fill, 0, 0
+ );
+
+ content_box.PackStart (table, true, true, 0);
+ box.PackStart (content_box, true, true, 0);
+
+ Button cancel_button = new Button("gtk-cancel");
+ cancel_button.CanDefault = true;
+ cancel_button.Show ();
+
+ AddActionWidget (cancel_button, ResponseType.Cancel);
+ AddActionWidget (save_button, ResponseType.Ok);
+
+ DefaultResponse = Gtk.ResponseType.Cancel;
+ ActionArea.Layout = Gtk.ButtonBoxStyle.End;
+
+ box.ShowAll ();
+ VBox.Add (box);
+
+ Response += OnResponse;
+ }
+
+ private void OnResponse (object sender, ResponseArgs args)
+ {
+ if (args.ResponseId == Gtk.ResponseType.Ok) {
+
+#if EDIT_DIR_TEST
+ if (channel.DownloadPreference != download_preference_combo.ActiveDownloadPreference ||
+ channel.LocalEnclosurePath != chooser.CurrentFolder) {
+ channel.LocalEnclosurePath = chooser.CurrentFolder;
+ channel.DownloadPreference = download_preference_combo.ActiveDownloadPreference;
+ channel.Save ();
+ }
+#else
+ if (channel.DownloadPreference != download_preference_combo.ActiveDownloadPreference) {
+ channel.DownloadPreference = download_preference_combo.ActiveDownloadPreference;
+ channel.Save ();
+ }
+#endif
+ }
+
+ (sender as Dialog).Response -= OnResponse;
+ (sender as Dialog).Destroy ();
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/Dialogs/SubscribeDialog.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/Dialogs/SubscribeDialog.cs
new file mode 100644
index 0000000..c31ebf1
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/Dialogs/SubscribeDialog.cs
@@ -0,0 +1,186 @@
+//
+// SubscribeDialog.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Gtk;
+using Mono.Unix;
+
+using Hyena.Widgets;
+
+using Banshee.Gui;
+using Banshee.Base;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Gui
+{
+ internal class SubscribeDialog : Dialog
+ {
+ private Entry url_entry;
+ private Gtk.AccelGroup accel_group;
+ private DownloadPreferenceComboBox download_pref_combo;
+
+ public string Url {
+ get { return url_entry.Text; }
+ set { url_entry.Text = value; }
+ }
+
+ public DownloadPreference DownloadPreference
+ {
+ get { return download_pref_combo.ActiveDownloadPreference; }
+ }
+
+ public SubscribeDialog () : base (Catalog.GetString("Subscribe"), null, DialogFlags.Modal | DialogFlags.NoSeparator)
+ {
+ accel_group = new Gtk.AccelGroup();
+ AddAccelGroup (accel_group);
+ BuildWindow ();
+ }
+
+ private void BuildWindow ()
+ {
+ DefaultWidth = 475;
+
+ BorderWidth = 6;
+ VBox.Spacing = 12;
+ ActionArea.Layout = Gtk.ButtonBoxStyle.End;
+
+ HBox box = new HBox();
+ box.BorderWidth = 6;
+ box.Spacing = 12;
+
+ Image image = new Image (IconThemeUtils.LoadIcon (48, "podcast"));
+
+ image.Yalign = 0.0f;
+
+ box.PackStart(image, false, true, 0);
+
+ VBox contentBox = new VBox();
+ contentBox.Spacing = 12;
+
+ Label header = new Label();
+ header.Markup = String.Format (
+ "<big><b>{0}</b></big>",
+ GLib.Markup.EscapeText (Catalog.GetString ("Subscribe to New Podcast"))
+ );
+
+ header.Justify = Justification.Left;
+ header.SetAlignment (0.0f, 0.0f);
+
+ WrapLabel message = new WrapLabel ();
+ message.Markup = Catalog.GetString (
+ "Please enter the URL of the podcast to which you would like to subscribe."
+ );
+
+ message.Wrap = true;
+
+ VBox sync_vbox = new VBox ();
+
+ VBox expander_children = new VBox();
+ //expander_children.BorderWidth = 6;
+ expander_children.Spacing = 6;
+
+ Label sync_text = new Label (
+ Catalog.GetString ("When new episodes are available: ")
+ );
+
+ sync_text.SetAlignment (0.0f, 0.0f);
+ sync_text.Justify = Justification.Left;
+
+ download_pref_combo = new DownloadPreferenceComboBox ();
+
+ expander_children.PackStart (sync_text, true, true, 0);
+ expander_children.PackStart (download_pref_combo, true, true, 0);
+
+ sync_vbox.Add (expander_children);
+
+ url_entry = new Entry ();
+ url_entry.ActivatesDefault = true;
+
+ // If the user has copied some text to the clipboard that starts with http, set
+ // our url entry to it and select it
+ Clipboard clipboard = Clipboard.Get (Gdk.Atom.Intern ("CLIPBOARD", false));
+ if (clipboard != null) {
+ string pasted = clipboard.WaitForText ();
+ if (!String.IsNullOrEmpty (pasted)) {
+ if (pasted.StartsWith ("http")) {
+ url_entry.Text = pasted.Trim ();
+ url_entry.SelectRegion (0, url_entry.Text.Length);
+ }
+ }
+ }
+
+ Table table = new Table (1, 2, false);
+ table.RowSpacing = 6;
+ table.ColumnSpacing = 12;
+
+ table.Attach (
+ new Label (Catalog.GetString ("URL:")), 0, 1, 0, 1,
+ AttachOptions.Shrink, AttachOptions.Shrink, 0, 0
+ );
+
+ table.Attach (
+ url_entry, 1, 2, 0, 1,
+ AttachOptions.Expand | AttachOptions.Fill,
+ AttachOptions.Shrink, 0, 0
+ );
+
+ table.Attach (
+ sync_vbox, 0, 2, 1, 2,
+ AttachOptions.Expand | AttachOptions.Fill,
+ AttachOptions.Shrink, 0, 0
+ );
+
+ contentBox.PackStart (header, true, true, 0);
+ contentBox.PackStart (message, true, true, 0);
+
+ contentBox.PackStart (table, true, true, 0);
+
+ box.PackStart (contentBox, true, true, 0);
+
+ AddButton (Gtk.Stock.Cancel, Gtk.ResponseType.Cancel, true);
+ AddButton (Catalog.GetString ("Subscribe"), ResponseType.Ok, true);
+
+ box.ShowAll ();
+ VBox.Add (box);
+ }
+
+ private void AddButton (string stock_id, Gtk.ResponseType response, bool is_default)
+ {
+ Gtk.Button button = new Gtk.Button (stock_id);
+ button.CanDefault = true;
+ button.Show ();
+
+ AddActionWidget (button, response);
+
+ if (is_default) {
+ DefaultResponse = response;
+ button.AddAccelerator ( "activate", accel_group, (uint) Gdk.Key.Escape, 0, Gtk.AccelFlags.Visible);
+ }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/DownloadPreferenceComboBox.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/DownloadPreferenceComboBox.cs
new file mode 100644
index 0000000..fe9b146
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/DownloadPreferenceComboBox.cs
@@ -0,0 +1,62 @@
+//
+// DownloadPreferenceComboBox.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using Gtk;
+using System;
+using Mono.Unix;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Gui
+{
+ public class DownloadPreferenceComboBox : Gtk.ComboBox
+ {
+ private static readonly string [] combo_text_entries = {
+ Catalog.GetString ("Download all episodes"),
+ Catalog.GetString ("Download the most recent episode"),
+ Catalog.GetString ("Let me decide which episodes to download")
+ };
+
+ public DownloadPreference ActiveDownloadPreference
+ {
+ get { return (DownloadPreference) Active; }
+ }
+
+ public DownloadPreferenceComboBox (DownloadPreference download_pref) : base (combo_text_entries)
+ {
+ if ((int) download_pref >= (int) DownloadPreference.All &&
+ (int) download_pref <= (int) DownloadPreference.None) {
+ Active = (int) download_pref;
+ } else {
+ Active = (int) DownloadPreference.One;
+ }
+ }
+
+ public DownloadPreferenceComboBox (): this (DownloadPreference.One)
+ {
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/DownloadStatusFilterView.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/DownloadStatusFilterView.cs
new file mode 100644
index 0000000..689c779
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/DownloadStatusFilterView.cs
@@ -0,0 +1,49 @@
+//
+// DownloadStatusFilterView.cs
+//
+// Authors:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using Hyena.Data.Gui;
+using Hyena.Collections;
+
+using Banshee.Gui;
+using Banshee.ServiceStack;
+using Banshee.Collection.Gui;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Gui
+{
+ public class DownloadStatusFilterView : TrackFilterListView<DownloadedStatusFilter>
+ {
+ public DownloadStatusFilterView () : base ()
+ {
+ ColumnCellDownloadStatus renderer = new ColumnCellDownloadStatus ();
+ column_controller.Add (new Column ("Download Status Filter", renderer, 1.0));
+ ColumnController = column_controller;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/IColumnCellDataHelper.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/IColumnCellDataHelper.cs
new file mode 100644
index 0000000..3e9f2bf
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/IColumnCellDataHelper.cs
@@ -0,0 +1,39 @@
+//
+// IColumnCellDataHelper.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using Hyena.Data.Gui;
+
+// I. Hate. This.
+namespace Banshee.Paas.Gui
+{
+ public delegate object ColumnCellDataHelper (ColumnCell cell, object state);
+
+ public interface IColumnCellDataHelper
+ {
+ ColumnCellDataHelper DataHelper { get; set; }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasActions.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasActions.cs
new file mode 100644
index 0000000..ef1a522
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasActions.cs
@@ -0,0 +1,793 @@
+//
+// PaasActions.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
+using Mono.Unix;
+
+using Gtk;
+
+using Migo2.Async;
+using Hyena.Collections;
+
+using Banshee.Gui;
+using Banshee.Widgets;
+
+using Banshee.Sources;
+using Banshee.ServiceStack;
+
+using Banshee.Collection;
+using Banshee.Collection.Database;
+
+using Banshee.Paas.Data;
+using Banshee.Paas.Aether;
+
+namespace Banshee.Paas.Gui
+{
+ enum SelectionInfo {
+ None,
+ One,
+ Multiple
+ }
+
+ [Flags]
+ enum SelectionOldNewInfo {
+ Zero = 0x00,
+ ShowNew = 0x01,
+ ShowOld = 0x02
+ }
+
+ public class PaasActions : BansheeActionGroup
+ {
+ private uint actions_id;
+ private PaasService service;
+
+ private DatabaseSource last_source;
+
+ public PaasActions (PaasService service) : base (ServiceManager.Get<InterfaceActionService> (), "Paas")
+ {
+ this.service = service;
+
+ AddImportant (
+ new ActionEntry (
+ "PaasUpdateAllAction", Stock.Refresh,
+ Catalog.GetString ("Update Channels"), "<control><shift>U",
+ null, OnPaasUpdateHandler
+ ),
+ new ActionEntry (
+ "PaasSubscribeAction", Stock.Add,
+ Catalog.GetString ("Subscribe to Channel"), null,
+ null, OnPaasSubscribeHandler
+ )
+ );
+
+ Add (new ActionEntry [] {
+ new ActionEntry (
+ "PaasImportOpmlAction", null,
+ Catalog.GetString ("Import Channels from OPML"), null,
+ null, OnPaasImportOpmlHandler
+ ),
+ new ActionEntry (
+ "PaasExportAllOpmlAction", null,
+ Catalog.GetString ("Export Channels as OPML"), null,
+ null, OnPaasExportAllOpmlHandler
+ ),
+ new ActionEntry (
+ "PaasExportOpmlAction", null,
+ Catalog.GetString ("Export Selected as OPML"), null,
+ null, OnPaasExportOpmlHandler
+ ),
+// new ActionEntry (
+// "PaasMiroGuideSubscribeAction", null,
+// Catalog.GetString ("Export Selected to Miro Guide"), null,
+// null, OnPaasMiroguideSubscribeHandler
+// ),
+// new ActionEntry (
+// "PaasMiroGuideGetSubscriptionsAction", null,
+// Catalog.GetString ("Import Channels from Miro Guide"), null,
+// null, OnPaasGetMiroGuideSubscriptionsHandler
+// ),
+ new ActionEntry (
+ "PaasItemDownloadAction", Stock.SaveAs,
+ Catalog.GetString ("Download"), null,
+ null, OnPaasItemDownloadHandler
+ ),
+ new ActionEntry (
+ "PaasItemCancelAction", Stock.Cancel,
+ Catalog.GetString ("Cancel"), null,
+ null, OnPaasItemCancelHandler
+ ),
+ new ActionEntry (
+ "PaasItemPauseAction", Stock.MediaPause,
+ Catalog.GetString ("Pause"), null,
+ null, OnPaasItemPauseHandler
+ ),
+ new ActionEntry (
+ "PaasItemResumeAction", Stock.Redo,
+ Catalog.GetString ("Resume"), null,
+ Catalog.GetString ("Resume"), OnPaasItemResumeHandler
+ ),
+ new ActionEntry (
+ "PaasItemMarkNewAction", null,
+ Catalog.GetString ("Mark as New"), null,
+ null, OnPaasItemMarkedNewHandler
+ ),
+ new ActionEntry (
+ "PaasItemMarkOldAction", null,
+ Catalog.GetString ("Mark as Old"), null,
+ null, OnPaasItemMarkedOldHandler
+ ),
+ new ActionEntry (
+ "PaasItemRemoveAction", Stock.Remove,
+ Catalog.GetString ("Remove From Library"), null,
+ null, OnPaasItemRemovedHandler
+ ),
+ new ActionEntry (
+ "PaasItemDeleteAction", null,
+ Catalog.GetString ("Delete From Drive"), null,
+ null, OnPaasItemDeletedHandler
+ ),
+ new ActionEntry (
+ "PaasItemLinkAction", Stock.JumpTo,
+ Catalog.GetString ("Visit Homepage"), null,
+ null, OnPaasItemHomepageHandler
+ ),
+ new ActionEntry (
+ "PaasChannelPopupAction", null, null, null, null, OnChannelPopup
+ ),
+ new ActionEntry (
+ "PaasChannelUpdateAction", Stock.Refresh,
+ Catalog.GetString ("Update"), null,
+ null, OnPaasChannelUpdateHandler
+ ),
+ new ActionEntry (
+ "PaasChannelDeleteAction", Stock.Delete,
+ Catalog.GetString ("Unsubscribe and Delete"), null,
+ null, OnPaasChannelDeleteHandler
+ ),
+ new ActionEntry (
+ "PaasChannelHomepageAction", Stock.JumpTo,
+ Catalog.GetString ("Visit Homepage"), null,
+ null, OnPaasChannelHomepageHandler
+ ),
+ new ActionEntry (
+ "PaasDownloadAllAction", Stock.SaveAs,
+ Catalog.GetString ("Download All Episodes"), null,
+ null, OnPaasDownloadAllHandler
+ ),
+ new ActionEntry (
+ "PaasChannelDownloadAllAction", Stock.SaveAs,
+ Catalog.GetString ("Download All Episodes"), null,
+ null, OnPaasChannelDownloadAllHandler
+ ), new ActionEntry (
+ "PaasChannelPropertiesAction", Stock.Preferences,
+ Catalog.GetString ("Properties"), null,
+ null, OnPaasChannelPropertiesHandler
+ )
+ });
+
+ actions_id = Actions.UIManager.AddUiFromResource ("GlobalUI.xml");
+ Actions.AddActionGroup (this);
+
+ ServiceManager.SourceManager.ActiveSourceChanged += HandleActiveSourceChanged;
+ }
+
+ public override void Dispose ()
+ {
+ Actions.UIManager.RemoveUi (actions_id);
+ Actions.RemoveActionGroup (this);
+ base.Dispose ();
+ }
+
+ private void HandleActiveSourceChanged (SourceEventArgs args)
+ {
+ last_source = args.Source as DatabaseSource;
+ }
+
+ private DatabaseSource ActiveDbSource {
+ get { return last_source; }
+ }
+
+ private bool IsPaasSource {
+ get {
+ return ActiveDbSource != null && (ActiveDbSource is PaasSource || ActiveDbSource.Parent is PaasSource);
+ }
+ }
+
+ public PaasChannelModel ActiveChannelModel {
+ get {
+ if (ActiveDbSource == null) {
+ return null;
+ } else if (ActiveDbSource is PaasSource) {
+ return (ActiveDbSource as PaasSource).ChannelModel;
+ } else {
+ PaasChannelModel model = null;
+
+ foreach (IFilterListModel filter in ActiveDbSource.AvailableFilters) {
+ model = filter as PaasChannelModel;
+
+ if (model != null) {
+ break;
+ }
+ }
+
+ return model;
+ }
+ }
+ }
+
+ private bool GetItemDownloadSelectionStatus (IEnumerable<PaasTrackInfo> items)
+ {
+ return GetItemDownloadSelectionStatus (items.Where (i => !i.IsDownloaded), TaskState.None) != SelectionInfo.None;
+ }
+
+ private SelectionInfo GetItemDownloadSelectionStatus (IEnumerable<PaasTrackInfo> items, TaskState state)
+ {
+ int cnt = -1;
+
+ foreach (PaasTrackInfo ti in items) {
+ if (CheckStatus (ti.Item, state)) {
+ if (++cnt == 1) {
+ break;
+ }
+ }
+ }
+
+ switch (cnt) {
+ case 0:
+ return SelectionInfo.One;
+ case 1:
+ return SelectionInfo.Multiple;
+ default:
+ return SelectionInfo.None;
+ }
+ }
+
+ private SelectionOldNewInfo GetItemOldNewSelectionStatus (IEnumerable<PaasTrackInfo> items)
+ {
+ // C# needs multiple returns
+ bool show_new = false, show_old = false;
+ SelectionOldNewInfo info = SelectionOldNewInfo.Zero;
+
+ foreach (PaasItem i in items.Select (ti => ti.Item)) {
+ if (!i.IsDownloaded) {
+ continue;
+ }
+
+ if (!show_old && i.IsNew) {
+ show_old = true;
+ info |= SelectionOldNewInfo.ShowOld;
+ } else if (!show_new && !i.IsNew) {
+ show_new = true;
+ info |= SelectionOldNewInfo.ShowNew;
+ }
+
+ if (show_new && show_old) {
+ break;
+ }
+ }
+
+ return info;
+ }
+
+ private bool CheckStatus (PaasItem item, TaskState flags)
+ {
+ return (service.DownloadManager.CheckActiveDownloadStatus (item.DbId) & flags) != TaskState.Zero;
+ }
+
+ public void UpdateItemActions ()
+ {
+ UpdateItemActions (GetSelectedItems ());
+ }
+
+ public void UpdateItemActions (IEnumerable<PaasTrackInfo> items)
+ {
+ if (!IsPaasSource) {
+ return;
+ }
+
+ bool show_download = GetItemDownloadSelectionStatus (items);
+ bool show_cancel = GetItemDownloadSelectionStatus (items, TaskState.CanCancel) != SelectionInfo.None;
+ bool show_resume = GetItemDownloadSelectionStatus (items, TaskState.Paused) != SelectionInfo.None;
+ bool show_pause = GetItemDownloadSelectionStatus (items, TaskState.CanPause) != SelectionInfo.None;
+
+ SelectionOldNewInfo selection = GetItemOldNewSelectionStatus (items);
+ bool show_mark_new = ((selection & SelectionOldNewInfo.ShowNew) != SelectionOldNewInfo.Zero);
+ bool show_mark_old = ((selection & SelectionOldNewInfo.ShowOld) != SelectionOldNewInfo.Zero);
+
+ UpdateAction ("PaasItemDownloadAction", show_download);
+ UpdateAction ("PaasItemCancelAction", show_cancel);
+ UpdateAction ("PaasItemPauseAction", show_pause);
+ UpdateAction ("PaasItemResumeAction", show_resume);
+
+ UpdateAction ("PaasItemMarkNewAction", show_mark_new);
+ UpdateAction ("PaasItemMarkOldAction", show_mark_old);
+
+ UpdateAction ("PaasItemLinkAction", (ActiveDbSource.TrackModel.Selection.Count == 1));
+ }
+
+ public void UpdateChannelActions ()
+ {
+ if (!IsPaasSource) {
+ return;
+ }
+
+ UpdateActions (
+ true,
+ (ActiveChannelModel.Selection.Count == 1 &&
+ !ActiveChannelModel.Selection.AllSelected),
+ "PaasChannelHomepageAction",
+ "PaasChannelPropertiesAction"
+ );
+ }
+
+ private IEnumerable<PaasChannel> GetSelectedChannels ()
+ {
+ return new List<PaasChannel> (ActiveChannelModel.SelectedItems);
+ }
+
+ private IEnumerable<PaasTrackInfo> GetSelectedItems ()
+ {
+ return new List<PaasTrackInfo> (
+ PaasTrackInfo.From (ActiveDbSource.TrackModel.SelectedItems)
+ );
+ }
+
+ private void RunSubscribeDialog ()
+ {
+ SubscribeDialog dialog = new SubscribeDialog ();
+ ResponseType response = (ResponseType) dialog.Run ();
+ dialog.Destroy ();
+
+ if (response == ResponseType.Ok) {
+ if (String.IsNullOrEmpty (dialog.Url)) {
+ return;
+ }
+
+ string url = dialog.Url.Trim ().Trim ('/');
+ DownloadPreference download_pref = dialog.DownloadPreference;;
+
+ try {
+ service.SyndicationClient.SubscribeToChannel (url, download_pref);
+ } catch (Exception e) {
+ Hyena.Log.Exception (e);
+
+ HigMessageDialog.RunHigMessageDialog (
+ null,
+ DialogFlags.Modal,
+ MessageType.Warning,
+ ButtonsType.Ok,
+ Catalog.GetString ("Invalid URL"),
+ Catalog.GetString ("Podcast URL is invalid.")
+ );
+ }
+ }
+ }
+
+ private void MarkItems (IEnumerable<PaasTrackInfo> items, bool _new)
+ {
+ PaasSource s = ActiveDbSource as PaasSource;
+
+ if (s == null) {
+ return;
+ }
+
+ RangeCollection rc = new RangeCollection ();
+
+ foreach (var i in items.Select (i => i.Item)) {
+ if (!i.IsDownloaded) {
+ continue;
+ }
+
+ if (_new && !i.IsNew) {
+ i.IsNew = true;
+ rc.Add ((int)i.DbId);
+ } else if (i.IsNew) {
+ i.IsNew = false;
+ rc.Add ((int)i.DbId);
+ }
+ }
+
+ foreach (var range in rc.Ranges) {
+ ServiceManager.DbConnection.Execute (
+ String.Format ("UPDATE PaasItems SET IsNew = ? WHERE ID >= ? AND ID <= ?"),
+ (_new ? 1 : 0), range.Start, range.End
+ );
+ }
+
+ s.Reload ();
+ }
+
+ private void RunConfirmDeleteDialog (bool channel, int selCount, out bool delete, out bool deleteFiles)
+ {
+
+ delete = false;
+ deleteFiles = false;
+ string header = null;
+ int plural = (channel | (selCount > 1)) ? 2 : 1;
+ int channel_count = 0;
+
+ if (channel) {
+ channel_count = GetSelectedChannels ().Count ();
+ header = Catalog.GetPluralString ("Delete Channel?", "Delete Channels?", channel_count);
+ } else {
+ header = Catalog.GetPluralString ("Delete Item?", "Delete Items?", selCount);
+ }
+
+ HigMessageDialog md = null;
+
+ if (selCount > 0) {
+ md = new HigMessageDialog (
+ ServiceManager.Get<GtkElementsService> ("GtkElementsService").PrimaryWindow,
+ DialogFlags.DestroyWithParent,
+ MessageType.Question,
+ ButtonsType.None, header,
+ Catalog.GetPluralString (
+ "Would you like to delete the associated file?",
+ "Would you like to delete the associated files?", plural
+ )
+ );
+
+ md.AddButton (Stock.Cancel, ResponseType.Cancel, true);
+ md.AddButton (Catalog.GetPluralString ("Keep File", "Keep Files", plural), ResponseType.No, false);
+ md.AddButton (Stock.Delete, ResponseType.Yes, false);
+ } else {
+ md = new HigMessageDialog (
+ ServiceManager.Get<GtkElementsService> ("GtkElementsService").PrimaryWindow,
+ DialogFlags.DestroyWithParent,
+ MessageType.Question,
+ ButtonsType.None, header,
+ Catalog.GetPluralString (
+ "Would you like to delete the selected channel?",
+ "Would you like to delete the selected channels?", channel_count
+ )
+ );
+
+ md.AddButton (Stock.Cancel, ResponseType.Cancel, true);
+ md.AddButton (Stock.Delete, ResponseType.Yes, false);
+ }
+
+ try {
+ switch ((ResponseType)md.Run ()) {
+ case ResponseType.Yes:
+ deleteFiles = true;
+ goto case ResponseType.No;
+ case ResponseType.No:
+ delete = true;
+ break;
+ }
+ } finally {
+ md.Destroy ();
+ }
+ }
+
+ private ResponseType RunConfirmRemoveDeleteDialog (bool delete, int itemCount)
+ {
+ ResponseType response;
+
+ HigMessageDialog md = new HigMessageDialog (
+ ServiceManager.Get<GtkElementsService> ("GtkElementsService").PrimaryWindow,
+ DialogFlags.DestroyWithParent,
+ MessageType.Question,
+ ButtonsType.None,
+ String.Format (
+ "{0} {1}",
+ delete ? Catalog.GetString ("Delete") : Catalog.GetString ("Remove"),
+ Catalog.GetPluralString ("Item?", "Items?", itemCount)
+ ),
+ String.Format (
+ Catalog.GetString ("Are you sure that you want to {0} the selected {1}?"),
+ delete ? Catalog.GetString ("delete") : Catalog.GetString ("remove"),
+ Catalog.GetPluralString ("item", "items", itemCount)
+ )
+ );
+
+ md.AddButton (Stock.Cancel, ResponseType.Cancel, true);
+ md.AddButton ((delete ? Stock.Delete : Stock.Remove), ResponseType.Ok, false);
+
+ response = (ResponseType) md.Run ();
+ md.Destroy ();
+
+ return response;
+ }
+
+// private void ExportToMiroGuide (IEnumerable<PaasChannel> channels)
+// {
+// service.MiroGuideClient.AddSubscriptionsAsync (channels.Select (c => c.Url));
+// }
+
+ private void ExportToOpml (IEnumerable<PaasChannel> channels)
+ {
+ if (channels.Count () == 0) {
+ HigMessageDialog.RunHigMessageDialog (
+ null, DialogFlags.Modal, MessageType.Warning, ButtonsType.Ok,
+ Catalog.GetString ("Error Exporting Channels!"),
+ Catalog.GetString ("No channels to export.")
+ );
+
+ return;
+ }
+
+ FileChooserDialog chooser = new FileChooserDialog (
+ Catalog.GetString ("Save As..."), null, FileChooserAction.Save,
+ new object[] {
+ Stock.Cancel, ResponseType.Cancel,
+ Stock.Save, ResponseType.Ok
+ }
+ );
+
+ chooser.Response += (o, ea) => {
+ if (ea.ResponseId == Gtk.ResponseType.Ok) {
+ try {
+ service.ExportChannelsToOpml (chooser.Filename, channels);
+ } catch (Exception ex) {
+ HigMessageDialog.RunHigMessageDialog (
+ null, DialogFlags.Modal, MessageType.Warning, ButtonsType.Ok,
+ Catalog.GetString ("Error Exporting Channels!"), ex.Message
+ );
+ }
+ }
+
+ (o as Dialog).Destroy ();
+ };
+
+ chooser.Run ();
+ }
+
+// private void OnPaasMiroguideSubscribeHandler (object sender, EventArgs e)
+// {
+// ExportToMiroGuide (GetSelectedChannels ());
+// }
+//
+// private void OnPaasGetMiroGuideSubscriptionsHandler (object sender, EventArgs e)
+// {
+// service.MiroGuideClient.GetSubscriptionsAsync ();
+// }
+
+ private void OnPaasExportOpmlHandler (object sender, EventArgs e)
+ {
+ ExportToOpml (GetSelectedChannels ());
+ }
+
+ private void OnPaasExportAllOpmlHandler (object sender, EventArgs e)
+ {
+ ExportToOpml (PaasChannel.Provider.FetchAll ());
+ }
+
+ private void OnPaasImportOpmlHandler (object sender, EventArgs e)
+ {
+ FileChooserDialog chooser = new FileChooserDialog (
+ Catalog.GetString ("Please Select a File to Import..."), null, FileChooserAction.Open,
+ new object[] {
+ Stock.Cancel, ResponseType.Cancel,
+ Catalog.GetString ("Import"), ResponseType.Ok
+ }
+ );
+
+ FileFilter filter = new FileFilter () {
+ Name = "OPML"
+ };
+
+ FileFilter unfiltered = new FileFilter () {
+ Name = Catalog.GetString ("All")
+ };
+
+ unfiltered.AddPattern ("*");
+ filter.AddPattern ("*.opml");
+ filter.AddPattern ("*.miro");
+
+ chooser.AddFilter (filter);
+ chooser.AddFilter (unfiltered);
+
+ chooser.Response += (o, ea) => {
+ if (ea.ResponseId == Gtk.ResponseType.Ok) {
+ try {
+ service.ImportOpml (chooser.Filename);
+ } catch (Exception ex) {
+ HigMessageDialog.RunHigMessageDialog (
+ null, DialogFlags.Modal, MessageType.Warning, ButtonsType.Ok,
+ Catalog.GetString ("Error Importing Channels!"), ex.Message
+ );
+
+ chooser.Run ();
+ }
+ }
+
+ (o as Dialog).Destroy ();
+ };
+
+ chooser.Run ();
+ }
+
+ private void OnPaasSubscribeHandler (object sender, EventArgs e)
+ {
+ RunSubscribeDialog ();
+ }
+
+ private void OnPaasUpdateHandler (object sender, EventArgs e)
+ {
+ service.UpdateAsync ();
+ }
+ private void OnPaasItemDownloadHandler (object sender, EventArgs e)
+ {
+ var items = GetSelectedItems ();
+ service.QueueDownload (items.Select (ti => ti.Item).Where (i => !i.IsDownloaded));
+ }
+
+ private void OnPaasItemCancelHandler (object sender, EventArgs e)
+ {
+ var items = GetSelectedItems ();
+ service.DownloadManager.CancelDownload (
+ items.Select (t => t.Item).Where (i => CheckStatus (i, TaskState.CanCancel))
+ );
+ }
+
+ private void OnPaasItemResumeHandler (object sender, EventArgs e)
+ {
+ var items = GetSelectedItems ();
+ service.DownloadManager.ResumeDownload (
+ items.Select (t => t.Item).Where (i => CheckStatus (i, TaskState.Paused))
+ );
+ }
+
+ private void OnPaasItemPauseHandler (object sender, EventArgs e)
+ {
+ var items = GetSelectedItems ();
+ service.DownloadManager.PauseDownload (
+ items.Select (t => t.Item).Where (i => CheckStatus (i, TaskState.CanPause))
+ );
+ }
+
+ private void OnPaasItemHomepageHandler (object sender, EventArgs e)
+ {
+ PaasItem item = PaasTrackInfo.From (ActiveDbSource.TrackModel.FocusedItem).Item;
+ if (item != null && !String.IsNullOrEmpty (item.Link)) {
+ Banshee.Web.Browser.Open (item.Link);
+ }
+ }
+
+ private void OnPaasItemDeletedHandler (object sender, EventArgs e)
+ {
+ var items = GetSelectedItems ();
+ if (RunConfirmRemoveDeleteDialog (true, items.Count ()) == ResponseType.Ok) {
+ service.SyndicationClient.RemoveItems (items.Select (t => t.Item), true);
+ }
+ }
+
+ private void OnPaasItemRemovedHandler (object sender, EventArgs e)
+ {
+ bool delete, delete_files;
+
+ var items = GetSelectedItems ().Select (t => t.Item);
+ int cnt = GetSelectedItems ().Select (t => t.Item).Where (i => i.IsDownloaded).Count ();
+
+ if (cnt > 0) {
+ RunConfirmDeleteDialog (false, cnt, out delete, out delete_files);
+
+ if (delete) {
+ service.SyndicationClient.RemoveItems (items, delete_files);
+ }
+ } else {
+ if (RunConfirmRemoveDeleteDialog (false, items.Count ()) == ResponseType.Ok) {
+ service.SyndicationClient.RemoveItems (items);
+ }
+ }
+ }
+
+ private void OnPaasItemMarkedNewHandler (object sender, EventArgs e)
+ {
+ var items = GetSelectedItems ();
+ MarkItems (items, true);
+ }
+
+ private void OnPaasItemMarkedOldHandler (object sender, EventArgs e)
+ {
+ var items = GetSelectedItems ();
+ MarkItems (items, false);
+ }
+
+ private void OnChannelPopup (object o, EventArgs e)
+ {
+ if (ActiveChannelModel.Selection.AllSelected) {
+ ShowContextMenu ("/PaasAllChannelsContextMenu");
+ } else {
+ ShowContextMenu ("/PaasChannelPopup");
+ }
+ }
+
+ private void OnPaasChannelUpdateHandler (object sender, EventArgs e)
+ {
+ var channels = GetSelectedChannels ();
+ service.SyndicationClient.QueueUpdate (channels);
+ }
+
+ private void OnPaasChannelDeleteHandler (object sender, EventArgs e)
+ {
+ int cnt = 0;
+ bool delete = true, delete_files = false;
+
+ var channels = GetSelectedChannels ();
+
+ foreach (var channel in channels) {
+ foreach (var item in channel.Items) {
+ if (item.Active && item.IsDownloaded) {
+ if (++cnt == 2) {
+ break;
+ }
+ }
+ }
+
+ if (cnt > 0) {
+ RunConfirmDeleteDialog (true, cnt, out delete, out delete_files);
+ break;
+ }
+ }
+
+ if (cnt == 0) {
+ RunConfirmDeleteDialog (true, 0, out delete, out delete_files);
+ }
+
+ if (delete) {
+ service.DeleteChannels (channels, delete_files);
+ }
+ }
+
+ private void OnPaasChannelHomepageHandler (object sender, EventArgs e)
+ {
+ PaasChannel channel = ActiveChannelModel.FocusedItem;
+ if (channel != null && !String.IsNullOrEmpty (channel.Link)) {
+ Banshee.Web.Browser.Open (channel.Link);
+ }
+ }
+
+ private void OnPaasDownloadAllHandler (object sender, EventArgs e)
+ {
+ var channels = new List<PaasChannel> (PaasChannel.Provider.FetchAll ().OrderBy (c => c.Name));
+ foreach (var c in channels) {
+ service.QueueDownload (c.Items.Where (i => i.Active && !i.IsDownloaded));
+ }
+ }
+
+ private void OnPaasChannelDownloadAllHandler (object sender, EventArgs e)
+ {
+ var channels = GetSelectedChannels ();
+ foreach (var c in channels) {
+ service.QueueDownload (c.Items.Where (i => i.Active && !i.IsDownloaded));
+ }
+ }
+
+ private void OnPaasChannelPropertiesHandler (object sender, EventArgs e)
+ {
+ PaasChannel channel = ActiveChannelModel.FocusedItem;
+
+ if (channel != null) {
+ new ChannelPropertiesDialog (channel).Run ();
+ }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasChannelView.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasChannelView.cs
new file mode 100644
index 0000000..ead8fce
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasChannelView.cs
@@ -0,0 +1,78 @@
+//
+// PaasChannelView.cs
+//
+// Authors:
+// Mike Urbanski <michael c urbanski gmail com>
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Michael C. Urbanski
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Hyena.Data.Gui;
+
+using Banshee.Gui;
+using Banshee.ServiceStack;
+using Banshee.Collection.Gui;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Gui
+{
+ public class PaasChannelView : TrackFilterListView<PaasChannel>
+ {
+ // Awful, dirty, filthy hack.
+ // I'm having a similar problem, probably just need to tinker with the event flags... Need to move on for now...
+ // http://lists.ximian.com/archives/public/gtk-sharp-list/2006-June/007247.html
+ public EventHandler<EventArgs> FuckedPopupMenu;
+
+ private ColumnCellChannel renderer;
+
+ public PaasChannelView ()
+ {
+ renderer = new ColumnCellChannel ();
+
+ column_controller.Add (new Column ("Channels", renderer, 1.0));
+
+ ColumnController = column_controller;
+ RowHeightProvider = renderer.ComputeRowHeight;
+ }
+
+ public void SetChannelDataHelper (ColumnCellDataHelper dataHelper)
+ {
+ renderer.DataHelper = dataHelper;
+ }
+
+ protected override bool OnPopupMenu ()
+ {
+ EventHandler<EventArgs> handler = FuckedPopupMenu;
+
+ if (handler != null) {
+ handler (this, EventArgs.Empty);
+ }
+
+ ServiceManager.Get<InterfaceActionService> ().FindAction ("Paas.PaasChannelPopupAction").Activate ();
+
+ return true;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasColumnController.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasColumnController.cs
new file mode 100644
index 0000000..cfab0a7
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasColumnController.cs
@@ -0,0 +1,100 @@
+//
+// PaasColumnController.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Mono.Unix;
+
+using Hyena.Data;
+using Hyena.Data.Gui;
+using Banshee.Collection.Gui;
+
+namespace Banshee.Paas.Gui
+{
+ public class PaasColumnController : XmlColumnController
+ {
+ private static readonly string ColumnXml = String.Format (@"
+ <column-controller>
+ <add-all-defaults/>
+ <column modify-default=""IndicatorColumn"">
+ <renderer type=""Banshee.Paas.Gui.ColumnCellPaasStatusIndicator"" />
+ </column>
+ <remove-default column=""TrackColumn"" />
+ <remove-default column=""DiscColumn"" />
+ <remove-default column=""ComposerColumn"" />
+ <remove-default column=""ArtistColumn"" />
+ <column modify-default=""AlbumColumn"">
+ <title>{0}</title>
+ <long-title>{0}</long-title>
+ </column>
+ <column>
+ <visible>false</visible>
+ <title>{4}</title>
+ <renderer type=""Hyena.Data.Gui.ColumnCellText"" property=""ExternalObject.Description"" />
+ <sort-key>Description</sort-key>
+ </column>
+ <column modify-default=""FileSizeColumn"">
+ <visible>true</visible>
+ </column>
+ <!--
+ <column>
+ <visible>false</visible>
+ <title>{2}</title>
+ <renderer type=""Banshee.Podcasting.Gui.ColumnCellYesNo"" property=""ExternalObject.IsNew"" />
+ <sort-key>IsNew</sort-key>
+ </column>
+ <column>
+ <visible>false</visible>
+ <title>{3}</title>
+ <renderer type=""Banshee.Podcasting.Gui.ColumnCellYesNo"" property=""ExternalObject.IsDownloaded"" />
+ <sort-key>IsDownloaded</sort-key>
+ </column>
+ -->
+
+ <column>
+ <visible>true</visible>
+ <title>{1}</title>
+ <renderer type=""Banshee.Paas.Gui.ColumnCellPublished"" property=""ExternalObject.PubDate"" />
+ <sort-key>PubDate</sort-key>
+ </column>
+ <sort-column direction=""desc"">published_date</sort-column>
+ </column-controller>
+ ",
+ Catalog.GetString ("Channel"), Catalog.GetString ("Published"), Catalog.GetString ("New"),
+ Catalog.GetString ("Downloaded"), Catalog.GetString ("Description")
+ );
+
+ public PaasColumnController () : base (ColumnXml)
+ {
+ }
+
+ public void SetIndicatorColumnDataHelper (ColumnCellDataHelper dataHelper)
+ {
+ ColumnCellPaasStatusIndicator indicator = IndicatorColumn.GetCell (0) as ColumnCellPaasStatusIndicator;
+ indicator.DataHelper = dataHelper;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasItemPage.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasItemPage.cs
new file mode 100644
index 0000000..c2ff273
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasItemPage.cs
@@ -0,0 +1,127 @@
+//
+// PaasItemPage.cs
+//
+// Author:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+
+using Mono.Unix;
+using Gtk;
+
+using Hyena.Widgets;
+
+using Banshee.Gui.TrackEditor;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Gui
+{
+ public class PaasItemPage : Gtk.ScrolledWindow, ITrackEditorPage
+ {
+ private VBox box;
+
+ private WrapLabel podcast = new WrapLabel ();
+ private WrapLabel author = new WrapLabel ();
+ private WrapLabel published = new WrapLabel ();
+ private WrapLabel description = new WrapLabel ();
+
+ public PaasItemPage ()
+ {
+ BorderWidth = 2;
+ ShadowType = ShadowType.None;
+ HscrollbarPolicy = PolicyType.Never;
+ VscrollbarPolicy = PolicyType.Automatic;
+
+ box = new VBox ();
+ box.BorderWidth = 6;
+ box.Spacing = 12;
+
+ box.PackStart (podcast, false, false, 0);
+ box.PackStart (author, false, false, 0);
+ box.PackStart (published, false, false, 0);
+ box.PackStart (description, true, true, 0);
+
+ AddWithViewport (box);
+ ShowAll ();
+ }
+
+ public void Initialize (TrackEditorDialog dialog)
+ {
+ }
+
+ public void LoadTrack (EditorTrackInfo track)
+ {
+ BorderWidth = 2;
+
+ PaasTrackInfo info = PaasTrackInfo.From (track.SourceTrack);
+
+ if (info == null) {
+ Hide ();
+ return;
+ }
+ // HACK to keep the save TrackInfo dialog from appearing when closing the TED.
+ // Investigate / talk to Gabe / Aaron about this later.
+ track.ReleaseDate = info.PubDate;
+
+ podcast.Markup = SetInfo (Catalog.GetString ("Podcast"), track.SourceTrack.AlbumTitle);
+ author.Markup = SetInfo (Catalog.GetString ("Author"), track.SourceTrack.ArtistName);
+ published.Markup = SetInfo (Catalog.GetString ("Published"), info.PubDate.ToLongDateString ());
+ description.Markup = SetInfo (Catalog.GetString ("Description"), info.Description);
+
+ Show ();
+ }
+
+ private static string info_str = "<b>{0}</b>\n{1}";
+ private static string SetInfo (string title, string info)
+ {
+ return String.Format (info_str,
+ GLib.Markup.EscapeText (title),
+ GLib.Markup.EscapeText (info)
+ );
+ }
+
+ public int Order {
+ get { return 40; }
+ }
+
+ public string Title {
+ get { return Catalog.GetString ("Episode Details"); }
+ }
+
+ public PageType PageType {
+ get { return PageType.View; }
+ }
+
+ public Gtk.Widget TabWidget {
+ get { return null; }
+ }
+
+ public Gtk.Widget Widget {
+ get { return this; }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasItemView.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasItemView.cs
new file mode 100644
index 0000000..3dbe809
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasItemView.cs
@@ -0,0 +1,93 @@
+//
+// PaasItemView.cs
+//
+// Authors:
+// Mike Urbanski <michael c urbanski gmail com>
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Michael C. Urbanski
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Gtk;
+
+using Hyena.Data.Gui;
+
+using Banshee.Gui;
+using Banshee.ServiceStack;
+
+using Banshee.Collection;
+using Banshee.Collection.Gui;
+using Banshee.Collection.Database;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Gui
+{
+ public class PaasItemView : TrackListView
+ {
+ // Awful, dirty, filthy hack.
+ // I'm having a similar problem, probably just need to tinker with the event flags... Need to move on for now...
+ // http://lists.ximian.com/archives/public/gtk-sharp-list/2006-June/007247.html
+ public EventHandler<EventArgs> FuckedPopupMenu;
+
+ public PaasItemView ()
+ {
+ }
+
+ protected override bool OnPopupMenu ()
+ {
+ EventHandler<EventArgs> handler = FuckedPopupMenu;
+
+ if (handler != null) {
+ handler (this, EventArgs.Empty);
+ }
+
+ return base.OnPopupMenu ();
+ }
+
+ protected override void ColumnCellDataProvider (ColumnCell cell, object boundItem)
+ {
+ ColumnCellText text_cell = cell as ColumnCellText;
+
+ if (text_cell == null) {
+ return;
+ }
+
+ DatabaseTrackInfo track = boundItem as DatabaseTrackInfo;
+
+ if (track != null) {
+ PaasTrackInfo pti = PaasTrackInfo.From (track);
+
+ if (pti == null) {
+ return;
+ }
+
+ if (track.IsPlaying || pti.IsDownloaded) {
+ text_cell.Sensitive = true;
+ } else {
+ text_cell.Sensitive = false;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasSourceContents.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasSourceContents.cs
new file mode 100644
index 0000000..2139bf2
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasSourceContents.cs
@@ -0,0 +1,153 @@
+//
+// PaasSourceContents.cs
+//
+// Authors:
+// Gabriel Burt <gburt novell com>
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (C) 2009 Michael C. Urbanski
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#define SHOW_EXTRA_FILTERS
+
+using System;
+
+using Hyena.Data;
+using Hyena.Data.Gui;
+
+using Banshee.Sources;
+using Banshee.Sources.Gui;
+
+using Banshee.Collection;
+using Banshee.ServiceStack;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Gui
+{
+ public class PaasSourceContents : FilteredListSourceContents, ITrackModelSourceContents
+ {
+ private PaasItemView item_view;
+ private PaasChannelView channel_view;
+
+#if SHOW_EXTRA_FILTERS
+ private PaasUnheardFilterView unheard_view;
+ private DownloadStatusFilterView download_view;
+#endif
+
+ public PaasChannelView ChannelView
+ {
+ get { return channel_view; }
+ }
+
+ public PaasSourceContents () : base ("paas")
+ {
+ }
+
+ protected override void InitializeViews ()
+ {
+ SetupMainView (item_view = new PaasItemView ());
+ SetupFilterView (channel_view = new PaasChannelView ());
+#if SHOW_EXTRA_FILTERS
+ SetupFilterView (unheard_view = new PaasUnheardFilterView ());
+ SetupFilterView (download_view = new DownloadStatusFilterView ());
+#endif
+ }
+
+ protected override void ClearFilterSelections ()
+ {
+
+ if (channel_view.Model != null) {
+ channel_view.Selection.Clear ();
+#if SHOW_EXTRA_FILTERS
+ unheard_view.Selection.Clear ();
+ download_view.Selection.Clear ();
+#endif
+ }
+ }
+
+ protected override bool ActiveSourceCanHasBrowser {
+ get {
+ DatabaseSource db_src = ServiceManager.SourceManager.ActiveSource as DatabaseSource;
+ return db_src != null && db_src.ShowBrowser;
+ }
+ }
+
+ #region Implement ISourceContents
+
+ public override bool SetSource (ISource source)
+ {
+ DatabaseSource track_source = source as DatabaseSource;
+
+ if (track_source == null) {
+ return false;
+ }
+
+ this.source = source;
+
+ SetModel (item_view, track_source.TrackModel);
+
+ foreach (IListModel model in track_source.CurrentFilters) {
+ if (model is PaasChannelModel) {
+ SetModel (channel_view, (model as IListModel<PaasChannel>));
+ }
+#if SHOW_EXTRA_FILTERS
+ else if (model is PaasUnheardFilterModel) {
+ SetModel (unheard_view, (model as IListModel<OldNewFilter>));
+ } else if (model is DownloadStatusFilterModel) {
+ SetModel (download_view, (model as IListModel<DownloadedStatusFilter>));
+ }
+#endif
+ else {
+ Hyena.Log.DebugFormat ("PaasSourceContents got non-channel filter model: {0}", model);
+ }
+ }
+
+ item_view.HeaderVisible = true;
+
+ return true;
+ }
+
+ public override void ResetSource ()
+ {
+ source = null;
+
+ SetModel (item_view, null);
+ SetModel (channel_view, null);
+
+#if SHOW_EXTRA_FILTERS
+ SetModel (download_view, null);
+ SetModel (unheard_view, null);
+#endif
+ item_view.HeaderVisible = false;
+ }
+
+ #endregion
+
+ #region ITrackModelSourceContents implementation
+
+ public IListView<TrackInfo> TrackView {
+ get { return item_view; }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasUnheardFilterView.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasUnheardFilterView.cs
new file mode 100644
index 0000000..61bf10b
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Gui/PaasUnheardFilterView.cs
@@ -0,0 +1,49 @@
+//
+// PaasUnheardFilterView.cs
+//
+// Authors:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using Hyena.Data.Gui;
+using Hyena.Collections;
+
+using Banshee.Gui;
+using Banshee.ServiceStack;
+using Banshee.Collection.Gui;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.Gui
+{
+ public class PaasUnheardFilterView : TrackFilterListView<OldNewFilter>
+ {
+ public PaasUnheardFilterView () : base ()
+ {
+ ColumnCellUnheard renderer = new ColumnCellUnheard ();
+ column_controller.Add (new Column ("Unheard Filter", renderer, 1.0));
+ ColumnController = column_controller;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/ChannelInfoPreview.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/ChannelInfoPreview.cs
new file mode 100644
index 0000000..eee8241
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/ChannelInfoPreview.cs
@@ -0,0 +1,88 @@
+//
+// ChannelInfoPreview.cs
+//
+// Authors:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Cairo;
+
+using Hyena.Gui;
+using Banshee.Gui;
+
+using Banshee.ServiceStack;
+using Banshee.Collection.Gui;
+
+using Banshee.Paas;
+using Banshee.Paas.Aether.MiroGuide;
+
+namespace Banshee.Paas.MiroGuide.Gui
+{
+ // This needs a good default state...
+ public class ChannelInfoPreview : ReflectionInfoWidget
+ {
+ private ArtworkManager artwork_manager;
+ private MiroGuideChannelInfo channel_info;
+
+ private static ImageSurface default_channel_image = new PixbufImageSurface (
+ IconThemeUtils.LoadIcon ("miroguide-default-channel", 256)
+ );
+
+ public MiroGuideChannelInfo ChannelInfo {
+ get { return channel_info; }
+ set {
+ if (value != channel_info) {
+ channel_info = value;
+ QueueDraw ();
+ }
+ }
+ }
+
+ public ChannelInfoPreview ()
+ {
+ artwork_manager = ServiceManager.Get<ArtworkManager> ();
+ }
+
+ protected override bool OnExposeEvent (Gdk.EventExpose evnt)
+ {
+ // Reset the text / image data on draw in case the image has changed since
+ // the ChannelInfo was set. (e.g. the image was just downloaded.)
+
+ // There is a flicker on draw, but it was here before I added this.
+
+ if (channel_info != null) {
+ /* Set renderer info... */
+ ImageSurface image = (artwork_manager == null) ? null
+ : artwork_manager.LookupSurface (PaasService.ArtworkIdFor (channel_info.Name));
+
+ ReflectionImage = image ?? default_channel_image;
+ } else {
+ ReflectionImage = default_channel_image;
+ /* Clear renderer info, show default image / text */
+ }
+
+ return base.OnExposeEvent (evnt);
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/ColumnCellChannel.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/ColumnCellChannel.cs
new file mode 100644
index 0000000..1a799c6
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/ColumnCellChannel.cs
@@ -0,0 +1,166 @@
+//
+// ColumnCellChannel.cs
+//
+// Authors:
+// Gabriel Burt <gburt novell com>
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (C) 2009 Michael C. Urbanski
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using Gtk;
+using Cairo;
+
+using Mono.Unix;
+
+using Hyena.Gui;
+using Hyena.Gui.Theming;
+using Hyena.Data.Gui;
+
+using Banshee.Gui;
+using Banshee.ServiceStack;
+using Banshee.Collection.Gui;
+
+using Banshee.Paas.Aether.MiroGuide;
+
+// This needs to be fixed! Still has code to support two lines of text.
+
+namespace Banshee.Paas.MiroGuide.Gui
+{
+ public class ColumnCellChannel : ColumnCell
+ {
+ private static int image_spacing = 4;
+ private static int image_size = 48;
+
+ // TODO replace this w/ new icon installation etc
+ private static ImageSurface default_cover_image = new PixbufImageSurface (
+ IconThemeUtils.LoadIcon (image_size, "miro")
+ );
+
+ private ArtworkManager artwork_manager;
+
+ public ColumnCellChannel () : base (null, true)
+ {
+ artwork_manager = ServiceManager.Get<ArtworkManager> ();
+ }
+
+ public override void Render (CellContext context, StateType state, double cellWidth, double cellHeight)
+ {
+ if (BoundObject == null) {
+ return;
+ }
+
+ if (!(BoundObject is MiroGuideChannelInfo)) {
+ throw new InvalidCastException("ColumnCellChannel can only bind to MiroGuideChannelInfo objects");
+ }
+
+ MiroGuideChannelInfo channel = (MiroGuideChannelInfo)BoundObject;
+
+ bool disable_border = false;
+
+ ImageSurface image = (artwork_manager == null) ? null
+ : artwork_manager.LookupScaleSurface (PaasService.ArtworkIdFor (channel.Name), image_size, true);
+
+ if (image == null) {
+ image = default_cover_image;
+ disable_border = true;
+ }
+
+ int image_render_size = image_size;
+ int x = image_spacing;
+ int y = ((int)cellHeight - image_render_size) / 2;
+
+ ArtworkRenderer.RenderThumbnail (context.Context, image, false, x, y,
+ image_render_size, image_render_size, !disable_border, context.Theme.Context.Radius, true
+ );
+
+ int fl_width = 0, fl_height = 0, sl_width = 0, sl_height = 0;
+ Cairo.Color text_color = context.Theme.Colors.GetWidgetColor (GtkColorClass.Text, state);
+ text_color.A = 0.75;
+
+ Pango.Layout layout = context.Layout;
+ layout.Width = (int)((cellWidth - cellHeight - x - 10) * Pango.Scale.PangoScale);
+ layout.Ellipsize = Pango.EllipsizeMode.End;
+ layout.FontDescription.Weight = Pango.Weight.Bold;
+
+ // Compute the layout sizes for both lines for centering on the cell
+ int old_size = layout.FontDescription.Size;
+
+ layout.SetText (channel.Name ?? String.Empty);
+ layout.GetPixelSize (out fl_width, out fl_height);
+
+ layout.FontDescription.Weight = Pango.Weight.Normal;
+ layout.FontDescription.Size = (int)(old_size * Pango.Scale.Small);
+ layout.FontDescription.Style = Pango.Style.Italic;
+
+ layout.SetText (channel.Publisher);
+
+ layout.GetPixelSize (out sl_width, out sl_height);
+
+ // Calculate the layout positioning
+ x = ((int)cellHeight - x) + 10;
+ y = (int)((cellHeight - (fl_height + sl_height)) / 2);
+
+ // Render the second line first since we have that state already
+ context.Context.MoveTo (x, y + fl_height);
+ context.Context.Color = text_color;
+ PangoCairoHelper.ShowLayout (context.Context, layout);
+
+ // Render the first line, resetting the state
+ //layout.SetText (channel.Name ?? String.Empty);
+ layout.FontDescription.Weight = Pango.Weight.Bold;
+ layout.FontDescription.Size = old_size;
+ layout.FontDescription.Style = Pango.Style.Normal;
+
+ layout.SetText (channel.Name ?? String.Empty);
+
+ context.Context.MoveTo (x, y);
+ text_color.A = 1;
+ context.Context.Color = text_color;
+ PangoCairoHelper.ShowLayout (context.Context, layout);
+ }
+
+ public int ComputeRowHeight (Widget widget)
+ {
+ int height;
+ int text_w, text_h;
+
+ Pango.Layout layout = new Pango.Layout (widget.PangoContext);
+ layout.FontDescription = widget.PangoContext.FontDescription.Copy ();
+
+ layout.SetText ("W");
+ layout.GetPixelSize (out text_w, out text_h);
+ height = text_h;
+
+ layout.FontDescription.Weight = Pango.Weight.Normal;
+ layout.FontDescription.Size = (int)(layout.FontDescription.Size * Pango.Scale.Small);
+ layout.FontDescription.Style = Pango.Style.Italic;
+ layout.SetText ("W");
+ layout.GetPixelSize (out text_w, out text_h);
+ height += text_h;
+
+ layout.Dispose ();
+
+ return (height < image_size ? image_size : height) + 6;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideAccountDialog.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideAccountDialog.cs
new file mode 100644
index 0000000..32a2be5
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideAccountDialog.cs
@@ -0,0 +1,153 @@
+//
+// MiroGuideAccountDialog.cs
+//
+// Authors:
+// Aaron Bockover <abockover novell com>
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (C) 2006 Novell, Inc.
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// This code has been copied/pasted so many times that it should probably just
+// be abstracted.
+
+using System;
+using Mono.Unix;
+
+using Gtk;
+using Banshee.Gui;
+
+using Banshee.Paas.Aether.MiroGuide;
+
+namespace Banshee.Paas.MiroGuide.Gui
+{
+ public class MiroGuideAccountDialog : Gtk.Dialog
+ {
+ private AccelGroup accel_group;
+ private MiroGuideLoginForm login_form;
+ private Label message;
+
+ public MiroGuideAccountDialog (MiroGuideAccountInfo accountInfo) : this (accountInfo, true)
+ {
+ }
+
+ public MiroGuideAccountDialog (MiroGuideAccountInfo accountInfo, bool addCloseButton) : base ()
+ {
+ Title = Catalog.GetString ("Miro Guide Account");
+ HasSeparator = false;
+ BorderWidth = 5;
+
+ IconName = "gtk-dialog-authentication";
+
+ accel_group = new AccelGroup ();
+ AddAccelGroup (accel_group);
+
+ HBox hbox = new HBox (false, 12);
+ VBox vbox = new VBox (false, 0);
+ hbox.BorderWidth = 5;
+ vbox.Spacing = 5;
+ hbox.Show ();
+ vbox.Show ();
+
+ Image image = new Image ();
+ image.Yalign = 0.0f;
+ image.Pixbuf = IconThemeUtils.LoadIcon (48, "miroguide");
+ image.IconSize = (int)IconSize.Dialog;
+ image.Show ();
+
+ hbox.PackStart (image, false, false, 0);
+ hbox.PackStart (vbox, true, true, 0);
+
+ Label header = new Label ();
+ header.Xalign = 0.0f;
+ header.Markup = String.Format ("<big><b>{0}</b></big>", Title);
+ header.Show ();
+
+ message = new Label (Catalog.GetString ("Please enter your Miro Guide account information."));
+ message.Xalign = 0.0f;
+ message.Show ();
+
+ vbox.PackStart (header, false, false, 0);
+ vbox.PackStart (message, false, false, 0);
+
+ login_form = new MiroGuideLoginForm (accountInfo);
+ login_form.Show ();
+
+ vbox.PackStart (login_form, true, true, 0);
+
+ VBox.PackStart (hbox, true, true, 0);
+ VBox.Remove (ActionArea);
+ VBox.Spacing = 10;
+
+ HBox bottom_box = new HBox ();
+
+ bottom_box.PackStart (ActionArea, true, true, 0);
+ bottom_box.ShowAll ();
+
+ VBox.PackEnd (bottom_box, false, false, 0);
+
+ if (addCloseButton) {
+ AddButton (Stock.Cancel, ResponseType.Cancel);
+ Button button = new Button (Stock.Save);
+ button.ShowAll ();
+
+ button.Activated += delegate {
+ login_form.Save ();
+ };
+
+ button.Clicked += delegate {
+ login_form.Save ();
+ };
+
+ AddActionWidget (button, ResponseType.Ok);
+ login_form.SaveOnEnter (this);
+ }
+ }
+
+ public void AddButton (string message, ResponseType response, bool isDefault)
+ {
+ Button button = (Button)AddButton (message, response);
+
+ if (isDefault) {
+ DefaultResponse = response;
+ button.AddAccelerator ("activate", accel_group, (uint)Gdk.Key.Return, 0, Gtk.AccelFlags.Visible);
+ }
+ }
+
+ public string Message {
+ get { return message.Text; }
+ set { message.Text = value; }
+ }
+
+ public bool SaveOnEdit {
+ get { return login_form.SaveOnEdit; }
+ set { login_form.SaveOnEdit = value; }
+ }
+
+ public string Username {
+ get { return login_form.Username; }
+ }
+
+ public string Password {
+ get { return login_form.Password; }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideActions.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideActions.cs
new file mode 100644
index 0000000..1e3f791
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideActions.cs
@@ -0,0 +1,197 @@
+//
+// MiroGuideActions.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
+using Mono.Unix;
+
+using Gtk;
+
+using Banshee.Gui;
+using Banshee.Base;
+using Banshee.Sources;
+using Banshee.ServiceStack;
+using Banshee.Configuration;
+
+using Banshee.Paas.Aether.MiroGuide;
+
+namespace Banshee.Paas.MiroGuide.Gui
+{
+ public class MiroGuideActions : BansheeActionGroup
+ {
+ private uint actions_id;
+ private MiroGuideClient client;
+
+ private ISource source;
+
+ private RadioAction active_action;
+ public RadioAction ActiveSortAction {
+ get { return active_action; }
+ }
+
+ public EventHandler<SortPreferenceChangedEventArgs> SortPreferenceChanged;
+
+ private ChannelSource ChannelSource {
+ get { return source as ChannelSource; }
+ }
+
+ public MiroGuideChannelListModel ActiveModel {
+ get { return (ChannelSource != null) ? ChannelSource.ChannelModel : null; }
+ }
+
+ public MiroGuideActions (MiroGuideClient client) : base (ServiceManager.Get<InterfaceActionService> (), "MiroGuide")
+ {
+ this.client = client;
+
+ AddImportant (
+ new ActionEntry (
+ "MiroGuideRefreshChannelsAction", Stock.Refresh,
+ Catalog.GetString ("Refresh"), null, null,
+ (sender, e) => { ChannelSource.Refresh (); }
+ )
+ );
+
+ Add (new ActionEntry [] {
+ new ActionEntry (
+ "MiroGuideChannelPopupAction", null, null, null, null, OnChannelPopup
+ ), new ActionEntry (
+ "MiroGuideChannelSubscribeAction", Stock.Add,
+ Catalog.GetString ("Subscribe"), null,
+ null, OnMiroGuideChannelSubscribeHandler
+ ), new ActionEntry (
+ "PaasEditMiroGuidePropertiesAction", Stock.Properties,
+ Catalog.GetString ("Edit Miro Guide Settings"), "<control>M",
+ null, (sender, e) => {
+ MiroGuideAccountDialog mgad = new MiroGuideAccountDialog (PaasService.MiroGuideAccount);
+ mgad.Run ();
+ mgad.Destroy ();
+ }
+ )
+ });
+
+ Add (new RadioActionEntry [] {
+ new RadioActionEntry ("MiroGuideSortByNameAction", null,
+ Catalog.GetString ("Sort Channels by Name"), null,
+ Catalog.GetString ("Order results by name."),
+ (int)MiroGuideSortType.Name),
+
+ new RadioActionEntry ("MiroGuideSortByRelevanceAction", null,
+ Catalog.GetString ("Sort Channels by Relevance"), null,
+ Catalog.GetString ("Order results by relevance."),
+ (int)MiroGuideSortType.Relevance),
+
+ new RadioActionEntry ("MiroGuideSortByRatingAction", null,
+ Catalog.GetString ("Sort Channels by Rating"), null,
+ Catalog.GetString ("Order results by rating."),
+ (int)MiroGuideSortType.Rating),
+
+ new RadioActionEntry ("MiroGuideSortByPopularityAction", null,
+ Catalog.GetString ("Sort Channels by Popularity"), null,
+ Catalog.GetString ("Order results by popularity."),
+ (int)MiroGuideSortType.Popular)
+ }, 0, OnActionChangedHandler);
+
+ SetActiveSortPreference (MiroGuideSortType.Name);
+
+ actions_id = Actions.UIManager.AddUiFromResource ("MiroGuideUI.xml");
+ Actions.AddActionGroup (this);
+
+ ServiceManager.SourceManager.ActiveSourceChanged += (e) => {
+ source = e.Source;
+ };
+ }
+
+ public override void Dispose ()
+ {
+ Actions.UIManager.RemoveUi (actions_id);
+ Actions.RemoveActionGroup (this);
+ base.Dispose ();
+ }
+
+ public void SetActiveSortPreference (MiroGuideSortType sort)
+ {
+ SetActiveSortPreference (sort, true);
+ }
+
+ public void SetActiveSortPreference (MiroGuideSortType sort, bool raise)
+ {
+ if (active_action == null || (int)sort != active_action.Value) {
+ active_action = GetSortPreferenceAction (sort);
+ active_action.Active = true;
+
+ if (raise) {
+ OnSortPreferenceChanged (sort);
+ }
+ }
+ }
+
+ private IEnumerable<MiroGuideChannelInfo> GetSelectedChannels ()
+ {
+ return new List<MiroGuideChannelInfo> (ChannelSource.ChannelModel.GetSelected ());
+ }
+
+ private void OnMiroGuideChannelSubscribeHandler (object sender, EventArgs e)
+ {
+ client.RequestSubsubscription (GetSelectedChannels ().Select (c => new Uri (c.Url)));
+ }
+
+ private void OnChannelPopup (object sender, EventArgs e)
+ {
+ ShowContextMenu ("/MiroGuideChannelPopup");
+ }
+
+ private RadioAction GetSortPreferenceAction (MiroGuideSortType sort)
+ {
+ switch (sort) {
+ case MiroGuideSortType.Name: return GetAction ("MiroGuideSortByNameAction") as RadioAction;
+ case MiroGuideSortType.Rating: return GetAction ("MiroGuideSortByRatingAction") as RadioAction;
+ case MiroGuideSortType.Popular: return GetAction ("MiroGuideSortByPopularityAction") as RadioAction;
+ case MiroGuideSortType.Relevance: return GetAction ("MiroGuideSortByRelevanceAction") as RadioAction;
+ default:
+ goto case MiroGuideSortType.Name;
+ }
+ }
+
+ private void OnSortPreferenceChanged (MiroGuideSortType sort)
+ {
+ var handler = SortPreferenceChanged;
+
+ if (handler != null) {
+ handler (null, new SortPreferenceChangedEventArgs (ChannelSource, sort));
+ }
+ }
+
+ private void OnActionChangedHandler (object o, ChangedArgs args)
+ {
+ if (active_action != args.Current) {
+ active_action = args.Current;
+ OnSortPreferenceChanged ((MiroGuideSortType)active_action.Value);
+ }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideCategoryListView.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideCategoryListView.cs
new file mode 100644
index 0000000..2f15565
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideCategoryListView.cs
@@ -0,0 +1,51 @@
+//
+// MiroGuideCategoryListView.cs
+//
+// Authors:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (C) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Mono.Unix;
+
+using Hyena.Data.Gui;
+
+using Banshee.Gui;
+using Banshee.ServiceStack;
+using Banshee.Collection.Gui;
+
+using Banshee.Paas.Aether.MiroGuide;
+
+namespace Banshee.Paas.MiroGuide.Gui
+{
+ public class MiroGuideCategoryListView : ListView<MiroGuideCategoryInfo>
+ {
+ public MiroGuideCategoryListView ()
+ {
+ ColumnCellText name_renderer = new ColumnCellText ("Name", true);
+
+ ColumnController = new ColumnController ();
+ ColumnController.Add (new Column (Catalog.GetString ("Categories"), name_renderer, 1.0));
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideChannelListView.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideChannelListView.cs
new file mode 100644
index 0000000..cb64f85
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideChannelListView.cs
@@ -0,0 +1,63 @@
+//
+// MiroGuideChannelListView.cs
+//
+// Authors:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (C) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Hyena.Data.Gui;
+
+using Banshee.Gui;
+using Banshee.ServiceStack;
+using Banshee.Collection.Gui;
+
+using Banshee.Paas.Aether.MiroGuide;
+
+namespace Banshee.Paas.MiroGuide.Gui
+{
+ public class MiroGuideChannelListView : ListView<MiroGuideChannelInfo>
+ {
+ private ColumnCellChannel renderer;
+
+ public MiroGuideChannelListView ()
+ {
+ renderer = new ColumnCellChannel ();
+
+ ColumnController column_controller = new ColumnController ();
+ column_controller.Add (new Column ("Channels", renderer, 1.0));
+
+ ColumnController = column_controller;
+ RowHeightProvider = renderer.ComputeRowHeight;
+ }
+
+ protected override bool OnPopupMenu ()
+ {
+ ServiceManager.Get<InterfaceActionService> ().FindAction (
+ "MiroGuide.MiroGuideChannelPopupAction"
+ ).Activate ();
+
+ return true;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideLoginForm.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideLoginForm.cs
new file mode 100644
index 0000000..a1c36eb
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideLoginForm.cs
@@ -0,0 +1,143 @@
+//
+// MiroGuideLoginForm.cs
+//
+// Authors:
+// Aaron Bockover <abockover novell com>
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (C) 2006 Novell, Inc.
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Mono.Unix;
+
+using Gtk;
+
+using Banshee.Paas.Aether.MiroGuide;
+
+namespace Banshee.Paas.MiroGuide.Gui
+{
+ public class MiroGuideLoginForm : Gtk.Table
+ {
+ private MiroGuideAccountInfo account_info;
+ private Entry username_entry;
+ private Entry password_entry;
+
+ private bool save_on_edit = false;
+
+ private bool save_on_enter = false;
+ private Gtk.Dialog parentDialog;
+
+ public MiroGuideLoginForm (MiroGuideAccountInfo accountInfo) : base (2, 2, false)
+ {
+ this.account_info = accountInfo;
+
+ BorderWidth = 5;
+ RowSpacing = 5;
+ ColumnSpacing = 5;
+
+ Label username_label = new Label (Catalog.GetString ("Username:"));
+ username_label.Xalign = 1.0f;
+ username_label.Show ();
+
+ username_entry = new Entry ();
+ username_entry.Show ();
+
+ Label password_label = new Label (Catalog.GetString ("Password:"));
+ password_label.Xalign = 1.0f;
+ password_label.Show ();
+
+ password_entry = new Entry ();
+ password_entry.Visibility = false;
+
+ //When the user presses enter in the password field: save, and then destroy the AcountLoginDialog
+ password_entry.Activated += delegate {
+ if (save_on_enter) {
+ this.Save ();
+ parentDialog.Destroy ();
+ }
+ };
+
+ password_entry.Show ();
+
+ Attach (username_label, 0, 1, 0, 1, AttachOptions.Fill,
+ AttachOptions.Shrink, 0, 0);
+
+ Attach (username_entry, 1, 2, 0, 1, AttachOptions.Fill | AttachOptions.Expand,
+ AttachOptions.Shrink, 0, 0);
+
+ Attach (password_label, 0, 1, 1, 2, AttachOptions.Fill,
+ AttachOptions.Shrink, 0, 0);
+
+ Attach (password_entry, 1, 2, 1, 2, AttachOptions.Fill | AttachOptions.Expand,
+ AttachOptions.Shrink, 0, 0);
+
+ username_entry.Text = account_info.Username ?? String.Empty;
+ password_entry.Text = account_info.PasswordHash ?? String.Empty;
+ }
+
+ protected override void OnDestroyed ()
+ {
+ if (save_on_edit) {
+ Save ();
+ }
+
+ base.OnDestroyed ();
+ }
+
+ public void Save ()
+ {
+ string trimmed_user = username_entry.Text.Trim ();
+ string trimmed_password_hash = password_entry.Text.Trim ();
+
+ if (!Hyena.CryptoUtil.IsMd5Encoded (trimmed_password_hash)) {
+ trimmed_password_hash = Hyena.CryptoUtil.Md5Encode (trimmed_password_hash);
+ }
+
+ if (account_info.Username != trimmed_user || account_info.PasswordHash != trimmed_password_hash) {
+ account_info.Username = trimmed_user;
+ account_info.PasswordHash = trimmed_password_hash;
+ account_info.Notify ();
+ }
+ }
+
+ public bool SaveOnEdit {
+ get { return save_on_edit; }
+ set { save_on_edit = value; }
+ }
+
+ //enable save on Enter and destruction of the parentDialog.
+ public void SaveOnEnter (Gtk.Dialog parentDialog)
+ {
+ save_on_enter = true;
+ this.parentDialog = parentDialog;
+ }
+
+ public string Username {
+ get { return username_entry.Text; }
+ }
+
+ public string Password {
+ get { return password_entry.Text; }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideSearchEntry.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideSearchEntry.cs
new file mode 100644
index 0000000..29a4ac1
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideSearchEntry.cs
@@ -0,0 +1,55 @@
+//
+// MiroGuideSearchEntry.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Mono.Unix;
+
+using Banshee.Widgets;
+
+using Banshee.Paas.Aether.MiroGuide;
+
+namespace Banshee.Paas.MiroGuide.Gui
+{
+ public class MiroGuideSearchEntry : SearchEntry
+ {
+ public MiroGuideSearchEntry ()
+ {
+ string default_str = Catalog.GetString ("Search Miro Guide");
+ EmptyMessage = default_str;
+
+// MG doesn't support search filtering by any type other than hd
+/*
+ AddFilterOption ((int)MiroGuideSearchFilter.Default, default_str);
+
+ AddFilterSeparator ();
+ AddFilterOption ((int)MiroGuideSearchFilter.HD, Catalog.GetString ("HD Channels"));
+ AddFilterOption ((int)MiroGuideSearchFilter.Video, Catalog.GetString ("Video Channels"));
+ AddFilterOption ((int)MiroGuideSearchFilter.Audio, Catalog.GetString ("Audio Channels"));
+*/
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/ReflectionInfoWidget.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/ReflectionInfoWidget.cs
new file mode 100644
index 0000000..8e72c36
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/ReflectionInfoWidget.cs
@@ -0,0 +1,164 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Cairo;
+using Gdk;
+using Gtk;
+
+namespace Banshee.Paas.MiroGuide.Gui
+{
+ public abstract class AbstractInfoRenderer
+ {
+ int maxWidth;
+ Style style;
+
+ public int MaxWidth {
+ get { return maxWidth; }
+ set {
+ maxWidth = value;
+ OnMaxWidthSet (maxWidth);
+ }
+ }
+
+ public Style Style {
+ get { return Style; }
+ set {
+ style = value;
+ OnStyleSet (style);
+ }
+ }
+
+ public virtual int HeightRequest {
+ get { return 0; }
+ }
+
+ public virtual int WidthRequest {
+ get { return MaxWidth; }
+ }
+
+ protected virtual void OnMaxWidthSet (int width)
+ {
+ // do shit here
+ }
+
+ protected virtual void OnStyleSet (Style style)
+ {
+ // do shit here
+ }
+
+ public virtual void RenderOntoContext (Context cr, int x, int y)
+ {
+ }
+
+ }
+
+ public class TitleInfoRenderer : AbstractInfoRenderer
+ {
+ string text;
+
+ public override int HeightRequest {
+ get { return 32; }
+ }
+
+ public TitleInfoRenderer (string text)
+ {
+ this.text = text;
+ }
+
+ public override void RenderOntoContext (Context cr, int x, int y)
+ {
+ Pango.Layout layout = Pango.CairoHelper.CreateLayout (cr);
+ layout.FontDescription = Style.FontDescription;
+ layout.FontDescription.AbsoluteSize = 30;
+ layout.FontDescription.Weight = Pango.Weight.Bold;
+
+ layout.Ellipsize = Pango.EllipsizeMode.End;
+ layout.SetText (text);
+ layout.Width = Pango.Units.ToPixels (MaxWidth);
+ cr.MoveTo (x, y);
+ Pango.CairoHelper.LayoutPath (cr, layout);
+ cr.Color = new Cairo.Color (1, 1, 1);
+ cr.Fill ();
+ }
+ }
+
+ public class ReflectionInfoWidget : DrawingArea
+ {
+ const double TopBorder = .2;
+ const double ImageSize = .8;
+ const double LeftRightSeparation = .5;
+ const int Separation = 10;
+
+ public double YAlign { get; set; }
+ public double XAlign { get; set; }
+
+ public ImageSurface ReflectionImage { get; set; }
+
+ IEnumerable<AbstractInfoRenderer> renderers;
+ public IEnumerable<AbstractInfoRenderer> Renderers {
+ get { return renderers; }
+ set {
+ renderers = value.ToArray (); // prevent it from changing due to lazy eval
+ if (Style == null)
+ return;
+ foreach (AbstractInfoRenderer renderer in renderers) {
+ renderer.Style = Style;
+ }
+ }
+ }
+
+ public ReflectionInfoWidget ()
+ {
+ renderers = new List<AbstractInfoRenderer> ();
+
+ AppPaintable = true;
+ DoubleBuffered = true;
+ }
+
+ protected override void OnStyleSet (Gtk.Style previous_style)
+ {
+ foreach (AbstractInfoRenderer renderer in Renderers) {
+ renderer.Style = Style;
+ }
+ base.OnStyleSet (previous_style);
+ }
+
+ protected override bool OnExposeEvent (Gdk.EventExpose evnt)
+ {
+ if (!IsRealized || ReflectionImage == null)
+ return base.OnExposeEvent (evnt);
+
+ using (Cairo.Context cr = Gdk.CairoHelper.Create (evnt.Window)) {
+ cr.Operator = Operator.Source;
+ cr.Color = new Cairo.Color (0, 0, 0);
+ cr.Paint ();
+ cr.Operator = Operator.Over;
+
+ int midline = Allocation.Width / 2;
+ int topline = (int) (Allocation.Height * TopBorder);
+ int leftWidth = midline - 2 * Separation;
+
+ cr.SetSource (ReflectionImage, midline + Separation, topline);
+ cr.Paint ();
+
+ foreach (AbstractInfoRenderer renderer in Renderers) {
+ renderer.MaxWidth = leftWidth;
+ }
+
+ int totalHeight = Renderers.Sum (r => r.HeightRequest);
+
+ int y = (int) (topline + (ReflectionImage.Height - totalHeight) * YAlign);
+
+ foreach (AbstractInfoRenderer renderer in Renderers) {
+ int x = (int) (Separation + (leftWidth - renderer.WidthRequest) * XAlign);
+ renderer.RenderOntoContext (cr, x, y);
+ y += renderer.HeightRequest;
+ }
+ }
+
+ return true;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SortPreferenceActionButton.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SortPreferenceActionButton.cs
new file mode 100644
index 0000000..7951aec
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SortPreferenceActionButton.cs
@@ -0,0 +1,79 @@
+//
+// SortPreferenceMenuButton.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using Gtk;
+using System;
+
+using Mono.Unix;
+
+using Hyena.Widgets;
+
+using Banshee.Gui;
+using Banshee.ServiceStack;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas.MiroGuide.Gui
+{
+ public class SortPreferenceActionButton : EventBox
+ {
+ private MenuButton button;
+ private HBox box = new HBox ();
+ private Label label = new Label ();
+ private MiroGuideActions actions;
+
+ public SortPreferenceActionButton ()
+ {
+ UIManager ui_manager = ServiceManager.Get <InterfaceActionService> ().UIManager;
+ actions = ServiceManager.Get <InterfaceActionService> ().FindActionGroup ("MiroGuide") as MiroGuideActions;
+
+ actions.SortPreferenceChanged += OnActionChanged;
+
+ box.Spacing = 0;
+
+ label.UseUnderline = true;
+ box.PackStart (label, true, true, 6);
+
+ button = new MenuButton (box, ui_manager.GetWidget ("/MiroGuideSortPreferencePopup") as Menu, true);
+ Add (button);
+
+ UpdateButton ();
+
+ ShowAll ();
+ }
+
+ private void UpdateButton ()
+ {
+ button.Sensitive = label.Sensitive = actions.Sensitive;
+ label.TextWithMnemonic = actions.ActiveSortAction.Label;
+ }
+
+ private void OnActionChanged (object o, EventArgs args)
+ {
+ UpdateButton ();
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SortPreferenceChangedEventArgs.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SortPreferenceChangedEventArgs.cs
new file mode 100644
index 0000000..7ccac5c
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SortPreferenceChangedEventArgs.cs
@@ -0,0 +1,51 @@
+//
+// SortPreferenceChangedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using Banshee.Sources;
+
+namespace Banshee.Paas.MiroGuide.Gui
+{
+ public class SortPreferenceChangedEventArgs : EventArgs
+ {
+ private readonly MiroGuideSortType sort;
+ private readonly ChannelSource active_source;
+
+ public ChannelSource ActiveSource {
+ get { return active_source; }
+ }
+
+ public MiroGuideSortType Sort {
+ get { return sort; }
+ }
+
+ public SortPreferenceChangedEventArgs (ChannelSource source, MiroGuideSortType sort)
+ {
+ active_source = source;
+ this.sort = sort;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SourceContents/BrowserSourceContents.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SourceContents/BrowserSourceContents.cs
new file mode 100644
index 0000000..16831a0
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SourceContents/BrowserSourceContents.cs
@@ -0,0 +1,98 @@
+//
+// BrowserSourceContents.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Gtk;
+
+using Banshee.Sources;
+using Banshee.Configuration;
+
+namespace Banshee.Paas.MiroGuide.Gui
+{
+ public class BrowserSourceContents : ChannelSourceContents
+ {
+ private VPaned browser_pane;
+ private double category_view_vadjustment;
+ private MiroGuideCategoryListView category_view;
+
+ public MiroGuideCategoryListView CategoryListView {
+ get { return category_view; }
+ }
+
+ public BrowserSourceContents ()
+ {
+ browser_pane = new VPaned ();
+ category_view = new MiroGuideCategoryListView ();
+
+ ScrolledWindow category_sw = new ScrolledWindow () {
+ HscrollbarPolicy = PolicyType.Automatic,
+ VscrollbarPolicy = PolicyType.Automatic
+ };
+
+ category_sw.Add (category_view);
+
+ browser_pane.Add1 (ScrolledWindow);
+ browser_pane.Add2 (category_sw);
+
+ browser_pane.Position = BrowserSourceVPanedPosition.Get ();
+
+ browser_pane.SizeAllocated += (sender, e) => {
+ BrowserSourceVPanedPosition.Set (browser_pane.Position);
+ };
+ }
+
+ public override void Initialize ()
+ {
+ FilterBox.Add (browser_pane);
+ Widget.ShowAll ();
+ }
+
+ public override bool SetSource (ISource source)
+ {
+ BrowseChannelsSource s = source as BrowseChannelsSource;
+
+ if (base.SetSource (source) && s != null) {
+ category_view.SetModel (s.CategoryModel, category_view_vadjustment);
+ browser_pane.Position = BrowserSourceVPanedPosition.Get ();
+ return true;
+ }
+
+ return false;
+ }
+
+ public override void ResetSource ()
+ {
+ category_view_vadjustment = category_view.Vadjustment.Value;
+ category_view.SetModel (null);
+ base.ResetSource ();
+ }
+
+ public static readonly SchemaEntry<int> BrowserSourceVPanedPosition = new SchemaEntry<int> (
+ "plugins.paas.miroguide.ui", "browser_source_vpaned_pos", 250, "", ""
+ );
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SourceContents/ChannelSourceContents.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SourceContents/ChannelSourceContents.cs
new file mode 100644
index 0000000..35eb621
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SourceContents/ChannelSourceContents.cs
@@ -0,0 +1,183 @@
+//
+// ChannelSourceContents.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
+using Gtk;
+
+using Banshee.Gui;
+using Banshee.Sources.Gui;
+
+using Banshee.Base;
+using Banshee.Sources;
+using Banshee.ServiceStack;
+using Banshee.Configuration;
+
+using Banshee.Paas.Data;
+using Banshee.Paas.Aether.MiroGuide;
+
+namespace Banshee.Paas.MiroGuide.Gui
+{
+ public class ChannelSourceContents : ISourceContents
+ {
+ private HPaned hp;
+ private VBox filter_box;
+ private ScrolledWindow sw;
+
+ private ChannelSource source;
+ private SingletonSelection selection;
+ private ChannelInfoPreview channel_preview;
+ private MiroGuideChannelListView channel_view;
+
+ private double channel_view_vadjustment;
+
+ public MiroGuideChannelListView ChannelView {
+ get { return channel_view; }
+ }
+
+ public ISource Source {
+ get { return source; }
+ }
+
+ public Widget Widget {
+ get { return hp as Widget; }
+ }
+
+ public VBox FilterBox {
+ get { return filter_box; }
+ }
+
+ public ScrolledWindow ScrolledWindow {
+ get { return sw; }
+ }
+
+ private static SortPreferenceActionButton sb;
+
+ static ChannelSourceContents ()
+ {
+ sb = new SortPreferenceActionButton ();
+ sb.Hide ();
+
+ ServiceManager.Get<InterfaceActionService> ().PopulateToolbarPlaceholder (
+ (Toolbar)ServiceManager.Get<InterfaceActionService> ().UIManager.GetWidget ("/FooterToolbar"),
+ "/FooterToolbar/Extensions/MiroGuideChannelSortButton",
+ sb
+ );
+ }
+
+ public ChannelSourceContents ()
+ {
+ channel_view = new MiroGuideChannelListView ();
+
+ sw = new ScrolledWindow () {
+ HscrollbarPolicy = PolicyType.Automatic,
+ VscrollbarPolicy = PolicyType.Automatic
+ };
+
+ sw.Add (channel_view);
+
+ hp = new HPaned ();
+
+ hp.SizeAllocated += (sender, e) => {
+ HPanedPosition.Set (hp.Position);
+ };
+
+ filter_box = new VBox ();
+ channel_preview = new ChannelInfoPreview ();
+
+ hp.Add1 (filter_box);
+ hp.Add2 (channel_preview);
+ }
+
+ public virtual void Initialize ()
+ {
+ filter_box.Add (sw);
+ Widget.ShowAll ();
+ }
+
+ public virtual bool SetSource (ISource source)
+ {
+ this.source = source as ChannelSource;
+
+ if (this.source == null) {
+ return false;
+ }
+
+ selection = this.source.ChannelModel.Selection as SingletonSelection;
+ selection.Changed += OnSelectionChanged;
+
+ channel_view.HeaderVisible = true;
+ hp.Position = HPanedPosition.Get ();
+ channel_view.SetModel (this.source.ChannelModel, channel_view_vadjustment);
+
+ if (this.source.Properties.Get<bool> ("MiroGuide.Gui.Source.ShowSortPreference")) {
+ ShowSortPreferenceButton ();
+ }
+
+ return true;
+ }
+
+ public virtual void ResetSource ()
+ {
+ selection.Changed -= OnSelectionChanged;
+ selection = null;
+
+ source = null;
+ channel_view_vadjustment = channel_view.Vadjustment.Value;
+
+ channel_view.SetModel (null);
+ channel_view.HeaderVisible = false;
+
+ HideSortPreferenceButton ();
+ }
+
+ private void OnSelectionChanged (object sender, EventArgs e)
+ {
+ channel_preview.ChannelInfo = source.ChannelModel.Selected;
+ }
+
+ public static void ShowSortPreferenceButton ()
+ {
+ sb.Show ();
+ }
+
+ public static void HideSortPreferenceButton ()
+ {
+ sb.Hide ();
+ }
+
+ public static bool SortPreferenceButtonSensitive {
+ get { return sb.Sensitive; }
+ set { sb.Sensitive = value; }
+ }
+
+ public static readonly SchemaEntry<int> HPanedPosition = new SchemaEntry<int> (
+ "plugins.paas.miroguide.ui", "channel_source_hpaned_pos", 255, "", ""
+ );
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SourceContents/MiroGuideSourceContents.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SourceContents/MiroGuideSourceContents.cs
new file mode 100644
index 0000000..96b6974
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SourceContents/MiroGuideSourceContents.cs
@@ -0,0 +1,71 @@
+//
+// MiroGuideSourceContents.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Gdk;
+using Gtk;
+
+using Banshee.Sources;
+using Banshee.Sources.Gui;
+
+namespace Banshee.Paas.MiroGuide.Gui
+{
+ public class MiroGuideSourceContents : ISourceContents
+ {
+ private VBox widget;
+ private MiroGuideSource source;
+
+ public ISource Source {
+ get { return source; }
+ }
+
+ public Widget Widget {
+ get { return widget as Widget; }
+ }
+
+ public MiroGuideSourceContents ()
+ {
+ widget = new VBox ();
+ }
+
+ public bool SetSource (ISource source)
+ {
+ this.source = source as MiroGuideSource;
+
+ if (source == null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public void ResetSource ()
+ {
+ source = null;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideCategoryListModel.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideCategoryListModel.cs
new file mode 100644
index 0000000..69dda88
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideCategoryListModel.cs
@@ -0,0 +1,41 @@
+//
+// MiroGuideCategoryListModel.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Banshee.Paas.Data;
+using Banshee.Paas.Aether.MiroGuide;
+
+namespace Banshee.Paas.MiroGuide
+{
+ public class MiroGuideCategoryListModel : ListModel<MiroGuideCategoryInfo>
+ {
+ public MiroGuideCategoryListModel () : base (new SingletonSelection ())
+ {
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideChannelListModel.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideChannelListModel.cs
new file mode 100644
index 0000000..6c2e848
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideChannelListModel.cs
@@ -0,0 +1,46 @@
+//
+// MiroGuideChannelListModel.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
+using Banshee.Paas.Data;
+using Banshee.Paas.Aether.MiroGuide;
+
+namespace Banshee.Paas.MiroGuide
+{
+ public class MiroGuideChannelListModel : ListModel<MiroGuideChannelInfo>
+ {
+ public MiroGuideChannelInfo Selected {
+ get { return GetSelected ().DefaultIfEmpty ().First (); }
+ }
+
+ public MiroGuideChannelListModel () : base (new SingletonSelection ())
+ {
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideImageFetchJob.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideImageFetchJob.cs
new file mode 100644
index 0000000..db0ee75
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideImageFetchJob.cs
@@ -0,0 +1,86 @@
+//
+// MiroGuideImageFetchJob.cs
+//
+// Authors:
+// Mike Urbanski <michael c urbanski gmail com>
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Michael C. Urbanski
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+
+using System;
+
+using Hyena;
+
+using Banshee.Base;
+using Banshee.Metadata;
+using Banshee.ServiceStack;
+
+using Banshee.Paas.MiroGuide.Gui;
+using Banshee.Paas.Aether.MiroGuide;
+
+// This could be abstracted and shared with PaasImageFetchJob...
+
+namespace Banshee.Paas.MiroGuide
+{
+ public class MiroGuideImageFetchJob : MetadataServiceJob
+ {
+ private MiroGuideChannelInfo channel;
+
+ public MiroGuideImageFetchJob (MiroGuideChannelInfo channel) : base ()
+ {
+ this.channel = channel;
+ }
+
+ public override void Run ()
+ {
+ Fetch ();
+ }
+
+ public void Fetch ()
+ {
+ if (String.IsNullOrEmpty (channel.ThumbUrl)) {
+ return;
+ }
+
+ string cover_art_id = PaasService.ArtworkIdFor (channel.Name);
+
+ if (cover_art_id == null) {
+ return;
+ } else if (CoverArtSpec.CoverExists (cover_art_id)) {
+ return;
+ } else if (!InternetConnected) {
+ return;
+ }
+
+ if (SaveHttpStreamCover (new Uri (channel.ThumbUrl), cover_art_id, null)) {
+ Banshee.Sources.Source src = ServiceManager.SourceManager.ActiveSource;
+
+ if (src != null && (src is ChannelSource || src.Parent is ChannelSource)) {
+ (src as ChannelSource).QueueDraw ();
+ }
+
+ return;
+ }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideInterfaceManager.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideInterfaceManager.cs
new file mode 100644
index 0000000..4e78e11
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideInterfaceManager.cs
@@ -0,0 +1,74 @@
+//
+// MiroGuideInterfaceManager.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Banshee.ServiceStack;
+using Banshee.Paas.Aether.MiroGuide;
+
+using Banshee.Paas.MiroGuide.Gui;
+
+namespace Banshee.Paas.MiroGuide
+{
+ public class MiroGuideInterfaceManager : IDisposable
+ {
+ private MiroGuideActions actions;
+ private MiroGuideSource mg_source;
+
+ public MiroGuideInterfaceManager ()
+ {
+ }
+
+ public void Initialize (MiroGuideClient client)
+ {
+ actions = new MiroGuideActions (client);
+
+ mg_source = new MiroGuideSource (client);
+ mg_source.AddChildSource (new SearchSource (client));
+ mg_source.AddChildSource (new HDChannelsSource (client));
+ mg_source.AddChildSource (new FeaturedChannelsSource (client));
+ mg_source.AddChildSource (new PopularChannelsSource (client));
+ mg_source.AddChildSource (new TopRatedChannelsSource (client));
+ mg_source.AddChildSource (new BrowseChannelsSource (client));
+ //mg_source.AddChildSource (new RecommendedChannelsSource (client));
+
+ ServiceManager.SourceManager.AddSource (mg_source);
+ }
+
+ public void Dispose ()
+ {
+ if (mg_source != null) {
+ ServiceManager.SourceManager.RemoveSource (mg_source);
+ mg_source = null;
+ }
+
+ if (actions != null) {
+ actions.Dispose ();
+ actions = null;
+ }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideSearchFilter.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideSearchFilter.cs
new file mode 100644
index 0000000..3c77b98
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideSearchFilter.cs
@@ -0,0 +1,13 @@
+
+using System;
+
+namespace Banshee.Paas.MiroGuide
+{
+ public enum MiroGuideSearchFilter : int
+ {
+ Default = 0,
+ HD,
+ Video,
+ Audio
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/BrowseChannelsSource.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/BrowseChannelsSource.cs
new file mode 100644
index 0000000..a34a521
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/BrowseChannelsSource.cs
@@ -0,0 +1,135 @@
+//
+// BrowseChannelsSource.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Linq;
+
+using Mono.Unix;
+
+using Gtk;
+
+using Banshee.Base;
+
+using Banshee.Paas.Data;
+using Banshee.Paas.MiroGuide.Gui;
+using Banshee.Paas.Aether.MiroGuide;
+
+namespace Banshee.Paas.MiroGuide
+{
+ public class BrowseChannelsSource : ChannelSource
+ {
+ private bool categories_received;
+ private MiroGuideCategoryListModel category_model;
+
+ public MiroGuideCategoryListModel CategoryModel {
+ get { return category_model; }
+ }
+
+ public BrowseChannelsSource (MiroGuideClient client) : base (client,
+ MiroGuideFilterType.Category,
+ "MiroGuideBrowseChannels",
+ Catalog.GetString ("Browse"),
+ (int)MiroGuideSourcePosition.Browse)
+ {
+ ActiveSortType = MiroGuideSortType.Rating;
+ category_model = new MiroGuideCategoryListModel ();
+
+ Properties.SetStringList ("Icon.Name", "miro-browse");
+ Properties.Set<bool> ("MiroGuide.Gui.Source.ShowSortPreference", true);
+ }
+
+ public override void Activate ()
+ {
+ base.Activate ();
+
+ Client.GetCategoriesCompleted += OnGetCategoriesCompletedHandler;
+ category_model.Selection.Changed += OnSelectionChangedHandler;
+
+ if (!categories_received) {
+ Client.GetCategoriesAsync (this);
+ }
+ }
+
+ public override void Deactivate ()
+ {
+ Client.GetCategoriesCompleted -= OnGetCategoriesCompletedHandler;
+ category_model.Selection.Changed -= OnSelectionChangedHandler;
+
+ base.Deactivate ();
+ }
+
+ protected override ChannelSourceContents CreateChannelSourceContents ()
+ {
+ return new BrowserSourceContents ();
+ }
+
+ public override void Refresh ()
+ {
+ ThreadAssist.ProxyToMain (delegate {
+ if (!categories_received) {
+ Client.GetCategoriesAsync (this);
+ }
+
+ base.Refresh ();
+ });
+ }
+
+ private MiroGuideCategoryInfo selected_category = null;
+ protected virtual void OnSelectionChangedHandler (object sender, EventArgs e)
+ {
+ MiroGuideCategoryInfo category = category_model.GetSelected ().FirstOrDefault ();
+
+ if (category != null && category != selected_category) {
+ Client.CancelAsync ();
+ ClientHandle.WaitOne ();
+
+ GetChannelsAsync (category.Name);
+ selected_category = category;
+ }
+ }
+
+ protected virtual void OnGetCategoriesCompletedHandler (object sender, GetCategoriesCompletedEventArgs e)
+ {
+ if (e.UserState != this) {
+ return;
+ }
+
+ ThreadAssist.ProxyToMain (delegate {
+ if (e.Cancelled) {
+ return;
+ } else if (e.Error != null) {
+ SetErrorStatus ();
+ return;
+ }
+
+ if (e.Categories != null) {
+ CategoryModel.Add (e.Categories);
+ categories_received = true;
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/ChannelSource.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/ChannelSource.cs
new file mode 100644
index 0000000..7d95cfa
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/ChannelSource.cs
@@ -0,0 +1,419 @@
+//
+// ChannelSource.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Threading;
+
+using System.Linq;
+using System.Collections.Generic;
+
+using Gtk;
+
+using Mono.Unix;
+
+using Banshee.Gui;
+using Banshee.Base;
+using Banshee.Sources;
+using Banshee.Sources.Gui;
+using Banshee.ServiceStack;
+
+using Banshee.Paas.Aether;
+using Banshee.Paas.Aether.MiroGuide;
+
+using Banshee.Paas.MiroGuide.Gui;
+
+namespace Banshee.Paas.MiroGuide
+{
+ public abstract class ChannelSource : Source, IDisposable
+ {
+ private bool ignore_scroll;
+ private MiroGuideClient client;
+ private MiroGuideActions actions;
+
+ private SourceMessage status_message;
+ private SourceMessage error_status_message;
+
+ private ChannelSourceContents contents;
+ private MiroGuideChannelListModel channel_model;
+
+ private MiroGuideSortType active_sort_type;
+ private readonly MiroGuideFilterType filter_type;
+
+ protected MiroGuideSortType ActiveSortType {
+ get { return active_sort_type; }
+ set { active_sort_type = value; }
+ }
+
+ protected MiroGuideActions Actions {
+ get { return actions; }
+ }
+
+ protected MiroGuideClient Client {
+ get { return client; }
+ }
+
+ protected ChannelSourceContents Contents {
+ get { return contents; }
+ }
+
+ protected string BusyStatusMessage { get; set; }
+ protected SearchContext Context { get; set; }
+
+ public MiroGuideChannelListModel ChannelModel {
+ get { return channel_model; }
+ }
+
+ private static ManualResetEvent client_handle = new ManualResetEvent (true);
+ protected static ManualResetEvent ClientHandle {
+ get { return client_handle; }
+ }
+
+ public ChannelSource (MiroGuideClient client,
+ MiroGuideFilterType filterType,
+ string genericName, string name, int order) : base (genericName, name, order)
+ {
+ if (client == null) {
+ throw new ArgumentNullException ("client");
+ }
+
+ this.client = client;
+ this.filter_type = filterType;
+
+ active_sort_type = MiroGuideSortType.Name;
+ actions = ServiceManager.Get <InterfaceActionService> ().FindActionGroup ("MiroGuide") as MiroGuideActions;
+
+ BusyStatusMessage = Catalog.GetString ("Updating");
+
+ channel_model = new MiroGuideChannelListModel ();
+
+ channel_model.Cleared += (sender, e) => { OnUpdated (); };
+ channel_model.Reloaded += (sender, e) => { QueueDraw (); OnUpdated (); };
+
+ this.client.StateChanged += OnMiroGuideClientStateChanged;
+
+ TypeUniqueId = String.Concat (genericName, "ChannelSource");
+
+ Properties.SetString ("ActiveSourceUIResource", "MiroGuideActiveSourceUI.xml");
+ Properties.Set<bool> ("ActiveSourceUIResourcePropagate", false);
+ Properties.Set<System.Reflection.Assembly> ("ActiveSourceUIResource.Assembly", typeof(ChannelSource).Assembly);
+
+ Properties.SetString ("GtkActionPath", "/MiroGuideChannelSourcePopup");
+
+ contents = CreateChannelSourceContents ();
+ contents.Initialize ();
+
+ (contents.ScrolledWindow.VScrollbar as VScrollbar).ValueChanged += OnVScrollbarValueChangedHandler;
+
+ Properties.Set<ISourceContents> ("Nereid.SourceContents", contents);
+ Properties.Set<bool> ("Nereid.SourceContentsPropagate", false);
+ }
+
+ public override void Activate ()
+ {
+ ClientHandle.WaitOne ();
+
+ if (active_sort_type != MiroGuideSortType.None) {
+ actions.SetActiveSortPreference (active_sort_type);
+ }
+
+ client.GetChannelsCompleted += OnGetChannelsCompletedHandler;
+ actions.SortPreferenceChanged += OnSortPreferenceChangedHandler;
+ }
+
+ public override void Deactivate ()
+ {
+ client.GetChannelsCompleted -= OnGetChannelsCompletedHandler;
+ actions.SortPreferenceChanged -= OnSortPreferenceChangedHandler;
+ client.CancelAsync ();
+ }
+
+ public virtual void Dispose ()
+ {
+ }
+
+ public virtual void QueueDraw ()
+ {
+ ThreadAssist.ProxyToMain (delegate {
+ contents.Widget.QueueDraw ();
+ });
+ }
+
+ public virtual void Refresh ()
+ {
+ ThreadAssist.ProxyToMain (delegate {
+ if (Context != null) {
+ Context.Reset ();
+ GetChannelsAsync (Context);
+ } else {
+ GetChannelsAsync ();
+ }
+ });
+ }
+
+ protected virtual void GetChannelsAsync ()
+ {
+ }
+
+ protected virtual void GetChannelsAsync (string filterValue)
+ {
+ Client.GetChannelsAsync (
+ filter_type, filterValue, active_sort_type,
+ !(active_sort_type == MiroGuideSortType.Name), 50, 0, this
+ );
+ }
+
+ protected virtual void GetChannelsAsync (SearchContext context)
+ {
+ if (context != null) {
+ Client.GetChannelsAsync (context, this);
+ }
+ }
+
+ protected virtual void CheckVScrollbarValue (VScrollbar vsb)
+ {
+ if (!ignore_scroll && (vsb.Value == vsb.Adjustment.Upper-vsb.Adjustment.PageSize ||
+ vsb.Adjustment.Upper-vsb.Adjustment.PageSize < 0)) {
+ if (Context != null && Context.ChannelsAvailable) {
+ GetChannelsAsync (Context);
+ }
+ }
+ }
+
+ protected virtual void ClearModel ()
+ {
+ ChannelModel.Selection.Clear ();
+ ChannelModel.Clear ();
+ }
+
+ protected virtual ChannelSourceContents CreateChannelSourceContents ()
+ {
+ return new ChannelSourceContents ();
+ }
+
+ protected virtual void RefreshArtworkFor (MiroGuideChannelInfo channel)
+ {
+ if (!CoverArtSpec.CoverExists (PaasService.ArtworkIdFor (channel.Name))) {
+ Banshee.Kernel.Scheduler.Schedule (
+ new MiroGuideImageFetchJob (channel), Banshee.Kernel.JobPriority.AboveNormal
+ );
+ }
+ }
+
+ protected SourceMessage CreateStatusMessage (string messageStr, string iconName)
+ {
+ return CreateStatusMessage (messageStr, iconName, null, null);
+ }
+
+ protected SourceMessage CreateStatusMessage (string messageStr, string iconName, string actionStr, EventHandler action)
+ {
+ SourceMessage message = new SourceMessage (this) {
+ Text = messageStr
+ };
+
+ message.SetIconName (iconName);
+
+ if (action != null) {
+ message.AddAction (new MessageAction (actionStr, false, action));
+ }
+
+ return message;
+ }
+
+ protected void SetStatus (string messageStr)
+ {
+ SetStatus (CreateStatusMessage (messageStr, Stock.Info));
+ }
+
+ protected void SetStatus (SourceMessage message)
+ {
+ ThreadAssist.AssertInMainThread ();
+ status_message = message;
+ }
+
+ protected void SetErrorStatus ()
+ {
+ SetErrorStatus (Catalog.GetString ("An error occurred while communicating with Miro Guide."));
+ }
+
+ protected void SetErrorStatus (string message_str)
+ {
+ SetErrorStatus (CreateStatusMessage (
+ message_str, Stock.DialogError, Catalog.GetString ("Retry"), delegate { Refresh (); }
+ ));
+ }
+
+ protected void SetErrorStatus (SourceMessage message)
+ {
+ ThreadAssist.AssertInMainThread ();
+ error_status_message = message;
+ }
+
+ // Terrible name.
+ protected void SetFetchStatus ()
+ {
+ ThreadAssist.AssertInMainThread ();
+
+ SetStatus (CreateStatusMessage (
+ Catalog.GetString ("Fetch additional channels?"), Stock.DialogQuestion,
+ Catalog.GetString ("Fetch"), delegate { GetChannelsAsync (Context); }
+ ));
+ }
+
+ protected virtual void SetRequestStatus (string message)
+ {
+ SetRequestStatus (message, null);
+ }
+
+ protected virtual void SetRequestStatus (string message, string iconName)
+ {
+ ThreadAssist.AssertInMainThread ();
+
+ SourceMessage m = new SourceMessage (this) {
+ Text = message,
+ CanClose = true,
+ IsSpinning = true
+ };
+
+ m.Updated += (sender, e) => {
+ if (m.IsHidden) {
+ Client.CancelAsync ();
+ }
+ };
+
+ PushMessage (m);
+ }
+
+ protected virtual void OnMiroGuideClientStateChanged (object sender, AetherClientStateChangedEventArgs e)
+ {
+ if (e.NewState == AetherClientState.Busy) {
+ ClientHandle.Reset ();
+ ThreadAssist.ProxyToMain (delegate {
+ ClearMessages ();
+ status_message = null;
+ error_status_message = null;
+ actions["MiroGuideRefreshChannelsAction"].Sensitive = false;
+ ChannelSourceContents.SortPreferenceButtonSensitive = false;
+ SetRequestStatus (String.Format ("{0}...", BusyStatusMessage));
+ });
+ } else {
+ ClientHandle.Set ();
+ ThreadAssist.ProxyToMain (delegate {
+ actions["MiroGuideRefreshChannelsAction"].Sensitive = true;
+ ChannelSourceContents.SortPreferenceButtonSensitive = true;
+ PopMessage ();
+
+ if (status_message != null) {
+ PushMessage (status_message);
+ } else if (error_status_message != null) {
+ PushMessage (error_status_message);
+ }
+ });
+ }
+ }
+
+ protected virtual void OnVScrollbarValueChangedHandler (object sender, EventArgs e)
+ {
+ CheckVScrollbarValue (sender as VScrollbar);
+ }
+
+ protected virtual void OnGetChannelsCompletedHandler (object sender, GetChannelsCompletedEventArgs e)
+ {
+ if (e.UserState != this) {
+ return;
+ }
+
+ ThreadAssist.ProxyToMain (delegate {
+ if (e.Cancelled) {
+ if (Context != null && Context.ChannelsAvailable) {
+ SetFetchStatus ();
+ }
+ return;
+ } else if (e.Error != null) {
+ SetErrorStatus ();
+ return;
+ }
+
+ Context = e.Context;
+
+ ignore_scroll = true;
+ bool check_sb_pos = false;
+
+ foreach (MiroGuideChannelInfo channel in e.Channels.Reverse ()) {
+ RefreshArtworkFor (channel);
+ }
+
+ if (Context.Page == 0) {
+ ClearModel ();
+ }
+
+ if (Context.Count > 0) {
+ ChannelModel.Add (e.Channels);
+
+ if (Context.ChannelsAvailable) {
+ Contents.ScrolledWindow.VscrollbarPolicy = PolicyType.Always;
+ check_sb_pos = true;
+ }
+ } else {
+ if (Context.Page == 0) {
+ SetStatus (Catalog.GetString ("No matches found."));
+ }
+
+ Contents.ScrolledWindow.VscrollbarPolicy = PolicyType.Automatic;
+ ChannelModel.Reload ();
+ }
+
+ ignore_scroll = false;
+
+ if (check_sb_pos) {
+ SetFetchStatus ();
+ CheckVScrollbarValue (Contents.ScrolledWindow.VScrollbar as VScrollbar);
+ }
+ });
+ }
+
+ protected virtual void OnSortPreferenceChangedHandler (object sender, SortPreferenceChangedEventArgs e)
+ {
+ if (e.ActiveSource != this) {
+ return;
+ }
+
+ ThreadAssist.ProxyToMain (delegate {
+ if (Context != null && Context.SortType != e.Sort) {
+ Context.Reset ();
+ Context.SortType = e.Sort;
+ active_sort_type = e.Sort;
+
+ Context.Reverse = !(active_sort_type == MiroGuideSortType.Name);
+
+ ClearModel ();
+ GetChannelsAsync (Context);
+ } else {
+ GetChannelsAsync ();
+ }
+ });
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/FeaturedChannelsSource.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/FeaturedChannelsSource.cs
new file mode 100644
index 0000000..2418e24
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/FeaturedChannelsSource.cs
@@ -0,0 +1,61 @@
+//
+// FeaturedChannelsSource.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using Mono.Unix;
+using Banshee.Paas.MiroGuide.Gui;
+using Banshee.Paas.Aether.MiroGuide;
+
+namespace Banshee.Paas.MiroGuide
+{
+ public class FeaturedChannelsSource : ChannelSource
+ {
+ public FeaturedChannelsSource (MiroGuideClient client) : base (client,
+ MiroGuideFilterType.Featured,
+ "MiroGuideFeaturedChannels",
+ Catalog.GetString ("Featured"),
+ (int)MiroGuideSourcePosition.Featured)
+ {
+ Properties.SetStringList ("Icon.Name", "emblem-favorite");
+ Properties.Set<bool> ("MiroGuide.Gui.Source.ShowSortPreference", true);
+ BusyStatusMessage = Catalog.GetString ("Receiving Featured Channels from Miro Guide");
+ }
+
+ public override void Activate ()
+ {
+ base.Activate ();
+
+ if (Context == null) {
+ GetChannelsAsync ();
+ }
+ }
+
+ protected override void GetChannelsAsync ()
+ {
+ GetChannelsAsync ("1");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/HDChannelsSource.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/HDChannelsSource.cs
new file mode 100644
index 0000000..d45d329
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/HDChannelsSource.cs
@@ -0,0 +1,62 @@
+//
+// HDChannelsSource.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using Mono.Unix;
+using Banshee.Paas.MiroGuide.Gui;
+using Banshee.Paas.Aether.MiroGuide;
+
+namespace Banshee.Paas.MiroGuide
+{
+ public class HDChannelsSource : ChannelSource
+ {
+ public HDChannelsSource (MiroGuideClient client) : base (client,
+ MiroGuideFilterType.HD,
+ "MiroGuideHDChannels",
+ Catalog.GetString ("HD Channels"),
+ (int)MiroGuideSourcePosition.HD)
+ {
+ ActiveSortType = MiroGuideSortType.Rating;
+ Properties.SetStringList ("Icon.Name", "video-x-generic");
+ Properties.Set<bool> ("MiroGuide.Gui.Source.ShowSortPreference", true);
+ BusyStatusMessage = Catalog.GetString ("Receiving HD Channels from Miro Guide");
+ }
+
+ public override void Activate ()
+ {
+ base.Activate ();
+
+ if (Context == null) {
+ GetChannelsAsync ();
+ }
+ }
+
+ protected override void GetChannelsAsync ()
+ {
+ GetChannelsAsync ("1");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/MiroGuideSource.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/MiroGuideSource.cs
new file mode 100644
index 0000000..5753dfd
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/MiroGuideSource.cs
@@ -0,0 +1,50 @@
+//
+// MiroGuideSource.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Banshee.Sources;
+using Banshee.Sources.Gui;
+
+using Banshee.Paas.MiroGuide.Gui;
+using Banshee.Paas.Aether.MiroGuide;
+
+namespace Banshee.Paas.MiroGuide
+{
+ public class MiroGuideSource : Source
+ {
+ public MiroGuideSource (MiroGuideClient client) : base ("Miro Guide", "Miro Guide", 201)
+ {
+ Properties.SetStringList ("Icon.Name", "miro");
+ Properties.SetString ("GtkActionPath", "/MiroGuideSourcePopup");
+
+ Properties.Set<ISourceContents> ("Nereid.SourceContents", new MiroGuideSourceContents ());
+ Properties.Set<bool> ("Nereid.SourceContentsPropagate", false);
+
+ Properties.Set<bool> ("Nereid.SourceContents.HeaderVisible", false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/MiroGuideSourcePosition.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/MiroGuideSourcePosition.cs
new file mode 100644
index 0000000..6c7e484
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/MiroGuideSourcePosition.cs
@@ -0,0 +1,41 @@
+//
+// MiroGuideSourcePosition.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Banshee.Paas.MiroGuide
+{
+ public enum MiroGuideSourcePosition : int
+ {
+ Search = 0,
+ Browse,
+ Featured,
+ TopRated,
+ Popular,
+ Recommended,
+ HD
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/PopularChannelsSource.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/PopularChannelsSource.cs
new file mode 100644
index 0000000..fef8532
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/PopularChannelsSource.cs
@@ -0,0 +1,61 @@
+//
+// PopularChannelsSource.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using Mono.Unix;
+using Banshee.Paas.MiroGuide.Gui;
+using Banshee.Paas.Aether.MiroGuide;
+
+namespace Banshee.Paas.MiroGuide
+{
+ public class PopularChannelsSource : ChannelSource
+ {
+ public PopularChannelsSource (MiroGuideClient client) : base (client,
+ MiroGuideFilterType.Popular,
+ "MiroGuidePopularChannels",
+ Catalog.GetString ("Most Popular"),
+ (int)MiroGuideSourcePosition.Popular)
+ {
+ ActiveSortType = MiroGuideSortType.Popular;
+ Properties.SetStringList ("Icon.Name", "system-users");
+ BusyStatusMessage = Catalog.GetString ("Receiving Popular Channels from Miro Guide");
+ }
+
+ public override void Activate ()
+ {
+ base.Activate ();
+
+ if (Context == null) {
+ GetChannelsAsync ();
+ }
+ }
+
+ protected override void GetChannelsAsync ()
+ {
+ GetChannelsAsync ("1");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/RecommendedChannelsSource.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/RecommendedChannelsSource.cs
new file mode 100644
index 0000000..7625d8c
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/RecommendedChannelsSource.cs
@@ -0,0 +1,46 @@
+//
+// RecommendedChannelsSource.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using Mono.Unix;
+using Banshee.Paas.MiroGuide.Gui;
+using Banshee.Paas.Aether.MiroGuide;
+
+namespace Banshee.Paas.MiroGuide
+{
+ public class RecommendedChannelsSource : ChannelSource
+ {
+ public RecommendedChannelsSource (MiroGuideClient client) : base (client,
+ MiroGuideFilterType.Featured,
+ "MiroGuideRecommendedChannels",
+ Catalog.GetString ("Recommended"),
+ (int)MiroGuideSourcePosition.Recommended)
+ {
+ Properties.SetStringList ("Icon.Name", "recommended");
+ Properties.Set<bool> ("MiroGuide.Gui.Source.ShowSortPreference", true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/SearchSource.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/SearchSource.cs
new file mode 100644
index 0000000..a606f7a
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/SearchSource.cs
@@ -0,0 +1,156 @@
+//
+// SearchSource.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Mono.Unix;
+
+using Gtk;
+
+using Banshee.Base;
+using Banshee.Widgets;
+
+using Banshee.Sources.Gui;
+
+using Banshee.Paas.Aether;
+using Banshee.Paas.Aether.MiroGuide;
+
+using Banshee.Paas.MiroGuide.Gui;
+
+namespace Banshee.Paas.MiroGuide
+{
+ public class SearchSource : ChannelSource, IImplementsCustomSearch
+ {
+ private MiroGuideSearchEntry search_entry;
+
+ public SearchEntry SearchEntry
+ {
+ get { return search_entry; }
+ }
+
+ public SearchSource (MiroGuideClient client) : base (client,
+ MiroGuideFilterType.Search,
+ "MiroGuideSearch",
+ Catalog.GetString ("Search"),
+ (int)MiroGuideSourcePosition.Search)
+ {
+ BuildSearchEntry ();
+
+ ActiveSortType = MiroGuideSortType.Relevance;
+
+ BusyStatusMessage = "Searching Miro Guide";
+ Properties.SetStringList ("Icon.Name", "find");
+ Properties.Set<bool> ("MiroGuide.Gui.Source.ShowSortPreference", true);
+ }
+
+ public override void Activate ()
+ {
+ base.Activate ();
+ Actions["MiroGuideRefreshChannelsAction"].Visible = false;
+ Actions["MiroGuideSortByRelevanceAction"].Visible = true;
+ }
+
+ public override void Deactivate ()
+ {
+ base.Deactivate ();
+ Actions["MiroGuideRefreshChannelsAction"].Visible = true;
+ Actions["MiroGuideSortByRelevanceAction"].Visible = false;
+ }
+
+ protected override void GetChannelsAsync ()
+ {
+ string search = GetSearchString ();
+
+ if (!String.IsNullOrEmpty (search)) {
+ GetChannelsAsync (search);
+ }
+ }
+
+ private void BuildSearchEntry ()
+ {
+ search_entry = new MiroGuideSearchEntry ();
+ search_entry.SetSizeRequest (200, -1);
+
+ search_entry.Activated += OnSearchEntryActivated;
+ search_entry.Changed += OnSearchEntryChanged;
+
+ search_entry.Cleared += (sender, e) => { Reset (false); };
+ search_entry.FilterChanged += (sender, e) => { Reset (true); };
+
+ search_entry.Show ();
+ }
+
+ private string GetSearchString ()
+ {
+ string ret = search_entry.InnerEntry.Text.Trim ();
+
+ switch ((MiroGuideSearchFilter)search_entry.ActiveFilterID) {
+ case MiroGuideSearchFilter.HD: ret += " hd"; break;
+ case MiroGuideSearchFilter.Audio: ret += " audio"; break;
+ case MiroGuideSearchFilter.Video: ret += " video"; break;
+ }
+
+ return ret;
+ }
+
+ private void Reset (bool refresh)
+ {
+ ThreadAssist.ProxyToMain (delegate {
+ Context = null;
+ Contents.ScrolledWindow.VscrollbarPolicy = PolicyType.Automatic;
+
+ ClearMessages ();
+
+ ChannelModel.Selection.Clear ();
+ ChannelModel.Clear ();
+
+ if (refresh) {
+ Refresh ();
+ }
+ });
+ }
+
+ protected override void OnMiroGuideClientStateChanged (object sender, AetherClientStateChangedEventArgs e)
+ {
+ ThreadAssist.ProxyToMain (delegate {
+ search_entry.Ready = (e.NewState == AetherClientState.Idle);
+ search_entry.Sensitive = (e.NewState == AetherClientState.Idle);
+ });
+
+ base.OnMiroGuideClientStateChanged (sender, e);
+ }
+
+ private void OnSearchEntryActivated (object sender, EventArgs e)
+ {
+ GetChannelsAsync ();
+ }
+
+ private void OnSearchEntryChanged (object sender, EventArgs e)
+ {
+ FilterQuery = search_entry.InnerEntry.Text;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/TopRatedChannelsSource.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/TopRatedChannelsSource.cs
new file mode 100644
index 0000000..c6509c8
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.MiroGuide/Sources/TopRatedChannelsSource.cs
@@ -0,0 +1,61 @@
+//
+// TopRatedChannelsSource.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using Mono.Unix;
+using Banshee.Paas.MiroGuide.Gui;
+using Banshee.Paas.Aether.MiroGuide;
+
+namespace Banshee.Paas.MiroGuide
+{
+ public class TopRatedChannelsSource : ChannelSource
+ {
+ public TopRatedChannelsSource (MiroGuideClient client) : base (client,
+ MiroGuideFilterType.TopRated,
+ "MiroGuideTopRatedChannels",
+ Catalog.GetString ("Top Rated"),
+ (int)MiroGuideSourcePosition.TopRated)
+ {
+ ActiveSortType = MiroGuideSortType.Rating;
+ Properties.SetStringList ("Icon.Name", "system-users");
+ BusyStatusMessage = Catalog.GetString ("Receiving Top Rated Channels from Miro Guide");
+ }
+
+ public override void Activate ()
+ {
+ base.Activate ();
+
+ if (Context == null) {
+ GetChannelsAsync ();
+ }
+ }
+
+ protected override void GetChannelsAsync ()
+ {
+ GetChannelsAsync ("1");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Utils/OpmlParser.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Utils/OpmlParser.cs
new file mode 100644
index 0000000..d123114
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Utils/OpmlParser.cs
@@ -0,0 +1,93 @@
+//
+// OpmlParser.cs
+//
+// Authors:
+// Gabriel Burt <gburt novell com>
+// Brandan Lloyd
+//
+// Copyright (C) 2008 Novell, Inc.
+// Copyright (C) 2008 Brandan Lloyd
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Xml;
+using System.Collections.Generic;
+
+namespace Banshee.Paas.Utils
+{
+ public class OpmlParser
+ {
+ private XmlDocument doc;
+ private System.Collections.Generic.List<string> feeds = null;
+
+ public OpmlParser (string xml, bool fromFile)
+ {
+ doc = new XmlDocument ();
+ try {
+ if (fromFile) {
+ doc.Load (xml);
+ } else {
+ doc.LoadXml (xml);
+ }
+ } catch (XmlException) {
+ throw new FormatException ("Invalid xml document.");
+ }
+ VerifyOpml ();
+ }
+
+ public IEnumerable<string> Feeds {
+ get {
+ if (null == feeds) {
+ feeds = new List<string> ();
+ GetFeeds (doc.SelectSingleNode ("/opml/body"));
+ }
+ return feeds;
+ }
+ }
+
+ private void GetFeeds (XmlNode baseNode)
+ {
+ XmlNodeList nodes = baseNode.SelectNodes ("./outline");
+ foreach (XmlNode node in nodes) {
+ if (node.Attributes["xmlUrl"] != null) {
+ feeds.Add (node.Attributes["xmlUrl"].Value);
+ } else if (node.Attributes["url"] != null) {
+ feeds.Add (node.Attributes["url"].Value);
+ }
+
+ GetFeeds (node);
+ }
+ }
+
+ private void VerifyOpml ()
+ {
+ if (doc.SelectSingleNode ("/opml") == null)
+ throw new FormatException ("Invalid OPML document.");
+
+ if (doc.SelectSingleNode ("/opml/head") == null)
+ throw new FormatException ("Invalid OPML document.");
+
+ if (doc.SelectSingleNode ("/opml/body") == null)
+ throw new FormatException ("Invalid OPML document.");
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Utils/StringUtils.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Utils/StringUtils.cs
new file mode 100644
index 0000000..9bb9fbc
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas.Utils/StringUtils.cs
@@ -0,0 +1,54 @@
+//
+// StringUtils.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Web;
+
+namespace Banshee.Paas.Utils
+{
+ public static class StringUtils
+ {
+ public static string StripHtml (string str)
+ {
+ str = Hyena.StringUtil.RemoveHtml (str);
+
+ if (str != null) {
+ str = HttpUtility.HtmlDecode (str);
+ }
+
+ return str;
+ }
+
+ public static string DecodeUrl (string str)
+ {
+ if (str != null) {
+ str = Uri.UnescapeDataString (str);
+ }
+
+ return str;
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas/PaasImageFetchJob.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas/PaasImageFetchJob.cs
new file mode 100644
index 0000000..d6ec9a4
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas/PaasImageFetchJob.cs
@@ -0,0 +1,83 @@
+//
+// PaasImageFetchJob.cs
+//
+// Authors:
+// Mike Urbanski <michael c urbanski gmail com>
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Michael C. Urbanski
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+
+using System;
+
+using Hyena;
+
+using Banshee.Base;
+using Banshee.Metadata;
+using Banshee.ServiceStack;
+
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas
+{
+ public class PaasImageFetchJob : MetadataServiceJob
+ {
+ private PaasChannel channel;
+
+ public PaasImageFetchJob (PaasChannel channel) : base ()
+ {
+ this.channel = channel;
+ }
+
+ public override void Run()
+ {
+ Fetch ();
+ }
+
+ public void Fetch ()
+ {
+ if (channel.ImageUrl == null) {
+ return;
+ }
+
+ string cover_art_id = PaasService.ArtworkIdFor (channel);
+
+ if (cover_art_id == null) {
+ return;
+ } else if (CoverArtSpec.CoverExists (cover_art_id)) {
+ return;
+ } else if (!InternetConnected) {
+ return;
+ }
+
+ if (SaveHttpStreamCover (new Uri (channel.ImageUrl), cover_art_id, null)) {
+ Banshee.Sources.Source src = ServiceManager.SourceManager.ActiveSource;
+
+ if (src != null && (src is PaasSource || src.Parent is PaasSource)) {
+ (src as PaasSource).QueueDraw ();
+ }
+
+ return;
+ }
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas/PaasService.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas/PaasService.cs
new file mode 100644
index 0000000..a47dcd4
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas/PaasService.cs
@@ -0,0 +1,833 @@
+//
+// PaasService.cs
+//
+// Authors:
+// Mike Urbanski <michael c urbanski gmail com>
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Michael C. Urbanski
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.IO;
+using System.Net;
+
+using System.Linq;
+using System.Xml.Linq;
+
+using System.Threading;
+using System.Collections.Generic;
+
+using Mono.Unix;
+
+using Hyena;
+
+using Banshee.IO;
+using Banshee.Web;
+using Banshee.Base;
+using Banshee.Sources;
+using Banshee.Networking;
+using Banshee.MediaEngine;
+using Banshee.ServiceStack;
+using Banshee.Configuration;
+
+using Banshee.Collection;
+using Banshee.Collection.Database;
+
+using Banshee.Paas.Gui;
+using Banshee.Paas.Data;
+using Banshee.Paas.Utils;
+
+using Banshee.Paas.MiroGuide;
+using Banshee.Paas.MiroGuide.Gui;
+
+using Banshee.Paas.Aether;
+using Banshee.Paas.Aether.MiroGuide;
+
+using Banshee.Paas.Aether.Syndication;
+
+using Banshee.Paas.DownloadManager;
+using Banshee.Paas.DownloadManager.Gui;
+
+using Migo2.Async;
+using Migo2.DownloadService;
+
+namespace Banshee.Paas
+{
+ delegate void Updater ();
+
+ public partial class PaasService : IExtensionService, IDisposable, IDelayedInitializeService
+ {
+ private int update_count;
+ private bool disposing, disposed;
+
+ private uint refresh_timeout_id = 0;
+
+ private readonly string tmp_download_path = Paths.Combine (
+ Paths.ExtensionCacheRoot, "paas", "partial-downloads"
+ );
+
+ private PaasSource source;
+
+ private MiroGuideClient mg_client;
+ private MiroGuideInterfaceManager mg_interface_manager;
+ public static MiroGuideAccountInfo MiroGuideAccount;
+
+ private SyndicationClient syndication_client;
+
+ private AutoResetEvent client_handle;
+
+ private PaasDownloadManager download_manager;
+ private DownloadManagerInterface download_manager_interface;
+
+ private Updater redraw, reload;
+
+ private readonly object sync = new object ();
+
+ // There needs to be a DownloadManager service in the service manager.
+ // There are issues adding a service to the service manager from an extension...
+ // It shouldn't be in the extension anyway.
+ public PaasDownloadManager DownloadManager {
+ get { return download_manager; }
+ }
+
+ public string ServiceName {
+ get { return "PaasService"; }
+ }
+
+ public PaasSource Source {
+ get { return source; }
+ }
+
+ public MiroGuideClient MiroGuideClient {
+ get { return mg_client; }
+ }
+
+ public SyndicationClient SyndicationClient {
+ get { return syndication_client; }
+ }
+
+ private bool Disposed {
+ get {
+ return (disposed | disposing);
+ }
+ }
+
+ static PaasService ()
+ {
+ MiroGuideAccount = new MiroGuideAccountInfo (
+ MiroGuideServiceUri.Get (), MiroGuideSessionID.Get (),
+ MiroGuideUsername.Get (), MiroGuidePasswordHash.Get ()
+ );
+
+ MiroGuideAccount.Updated += (sender, e) => {
+ MiroGuideUsername.Set (MiroGuideAccount.Username);
+ MiroGuidePasswordHash.Set (MiroGuideAccount.PasswordHash);
+ MiroGuideSessionID.Set (MiroGuideAccount.SessionID);
+ MiroGuideServiceUri.Set (MiroGuideAccount.ServiceUri);
+ };
+
+// https://bugzilla.novell.com/show_bug.cgi?id=346561
+// ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => {
+// return true;
+// };
+ }
+
+ public PaasService ()
+ {
+ reload = () => {
+ if (!Disposed) {
+ source.Reload ();
+ }
+ };
+
+ redraw = () => {
+ if (!Disposed) {
+ source.QueueDraw ();
+ }
+ };
+
+ source = new PaasSource (this);
+
+ if (source.DbId < 1) {
+ source.Save ();
+ }
+
+ client_handle = new AutoResetEvent (true);
+
+ mg_client = new MiroGuideClient (MiroGuideAccount) {
+ UserAgent = Banshee.Web.Browser.UserAgent,
+ };
+
+ mg_client.StateChanged += (sender, e) => {
+ if (e.NewState == AetherClientState.Idle) {
+ DecrementUpdateCount ();
+ client_handle.Set ();
+ } else {
+ IncrementUpdateCount ();
+ client_handle.Reset ();
+ }
+ };
+
+ mg_client.SubscriptionRequested += (sender, e) => {
+ if (e.Uri != null) {
+ SubscribeToChannel (e.Uri);
+ } else if (e.Uris != null) {
+ SubscribeToChannels (e.Uris);
+ }
+ };
+
+ syndication_client = new SyndicationClient (source);
+
+ syndication_client.StateChanged += (sender, e) => {
+ if (e.NewState == AetherClientState.Idle) {
+ DecrementUpdateCount ();
+ } else {
+ IncrementUpdateCount ();
+ }
+ };
+
+ syndication_client.ChannelsAdded += (sender, e) => {
+ lock (sync) {
+ if (Disposed) {
+ return;
+ }
+
+ syndication_client.QueueUpdate (e.Channel);
+ reload ();
+ }
+ };
+
+ syndication_client.ChannelsRemoved += (sender, e) => { reload (); };
+ syndication_client.ChannelUpdating += (sender, e) => { redraw (); };
+ syndication_client.ChannelUpdateCompleted += OnChannelUpdatedHandler;
+
+ syndication_client.ItemsAdded += OnItemsAddedHandler;
+ syndication_client.ItemsRemoved += OnItemsRemovedHandler;
+
+ download_manager = new PaasDownloadManager (source.DbId, 2, tmp_download_path);
+ download_manager.TaskAdded += (sender, e) => { redraw (); };
+
+ download_manager.TaskCompleted += OnDownloadTaskCompletedHandler;
+ download_manager.TaskStateChanged += (sender, e) => { redraw ();; };
+ }
+
+ public void UpdateAsync ()
+ {
+ lock (sync) {
+ if (!Disposed) {
+ syndication_client.UpdateAsync ();
+ }
+ }
+ }
+
+ public void Initialize ()
+ {
+ InitializeInterface ();
+ }
+
+ public void DelayedInitialize ()
+ {
+ ServiceManager.Get<DBusCommandService> ().ArgumentPushed += OnCommandLineArgument;
+
+ //download_manager.RestoreQueuedDownloads ();
+ RefreshFeeds (true);
+ refresh_timeout_id = Application.RunTimeout (1000 * 60 * 10, RefreshFeeds); // 10 minutes
+ }
+
+ public void Dispose ()
+ {
+ lock (sync) {
+ if (Disposed) {
+ return;
+ }
+
+ disposing = true;
+ }
+
+ Application.IdleTimeoutRemove (refresh_timeout_id);
+ refresh_timeout_id = 0;
+
+ DisposeInterface ();
+ ServiceManager.Get<DBusCommandService> ().ArgumentPushed -= OnCommandLineArgument;
+
+ mg_client.CancelAsync ();
+ client_handle.WaitOne ();
+
+ mg_client = null;
+
+ client_handle.Close ();
+ client_handle = null;
+
+ syndication_client.Dispose ();
+ syndication_client.ItemsAdded -= OnItemsAddedHandler;
+ syndication_client.ItemsRemoved -= OnItemsRemovedHandler;
+ syndication_client.ChannelUpdateCompleted -= OnChannelUpdatedHandler;
+
+ syndication_client = null;
+
+ download_manager.Dispose ();
+ download_manager.TaskCompleted -= OnDownloadTaskCompletedHandler;
+ download_manager = null;
+
+ lock (sync) {
+ disposing = false;
+ disposed = true;
+ }
+ }
+
+ public void DeleteChannels (IEnumerable<PaasChannel> channels, bool deleteFiles)
+ {
+ lock (sync) {
+ if (Disposed) {
+ return;
+ }
+
+ syndication_client.DeleteChannels (
+ channels.Where (c => c.ClientID == (int)AetherClientID.Syndication), deleteFiles
+ );
+ }
+ }
+
+ public void ExportChannelsToOpml (string path, IEnumerable<PaasChannel> channels)
+ {
+ if (String.IsNullOrEmpty (path)) {
+ throw new ArgumentNullException ("path");
+ } else if (channels == null) {
+ throw new ArgumentNullException ("channels");
+ }
+
+ if (channels.Count () == 0) {
+ return;
+ }
+
+ try {
+ // Move this to a helper class
+ XDocument doc = new XDocument (
+ new XDeclaration ("1.0", "utf-8", "yes"),
+ new XElement ("opml",
+ new XAttribute ("version", "2.0"),
+ new XElement ("head",
+ new XElement ("title", Catalog.GetString ("Banshee Podcast Subscriptions")),
+ new XElement ("dateCreated", DateTime.UtcNow.ToString ("ddd, dd MMM yyyy HH:mm:ss K"))
+ ), new XElement ("body",
+ from channel in channels select new XElement (
+ "outline",
+ new XAttribute ("type", "rss"),
+ new XAttribute ("text", channel.Name),
+ new XAttribute ("xmlUrl", channel.Url)
+ )
+ )
+ )
+ );
+
+ doc.Save (path);
+ } catch (Exception e) {
+ Log.Exception (e);
+ throw;
+ }
+ }
+
+ public void ImportOpml (string path)
+ {
+ if (String.IsNullOrEmpty (path)) {
+ throw new ArgumentNullException ("path");
+ }
+
+ lock (sync) {
+ if (Disposed) {
+ return;
+ }
+
+ try {
+ OpmlParser opml_parser = new OpmlParser (path, true);
+
+ foreach (string channel in opml_parser.Feeds) {
+ try {
+ syndication_client.SubscribeToChannel (channel, DownloadPreference.One);
+ } catch (Exception e) {
+ Log.Exception (e);
+ }
+ }
+
+ source.NotifyUser ();
+ } catch (Exception e) {
+ Log.Exception (e);
+ throw;
+ }
+ }
+ }
+
+ public void SubscribeToChannels (IEnumerable<Uri> uris)
+ {
+ if (uris == null) {
+ throw new ArgumentNullException ("uris");
+ }
+
+ lock (sync) {
+ if (Disposed) {
+ return;
+ }
+
+ int sub_count = 0;
+
+ foreach (Uri uri in uris) {
+ try {
+ if (uri != null) {
+ syndication_client.SubscribeToChannel (uri.ToString (), DownloadPreference.One);
+ sub_count++;
+ }
+ } catch (Exception e) {
+ Log.Exception (e);
+ continue;
+ }
+ }
+
+ if (sub_count > 0) {
+ source.NotifyUser ();
+ }
+ }
+ }
+
+ public void SubscribeToChannel (Uri uri)
+ {
+ if (uri == null) {
+ throw new ArgumentNullException ("uri");
+ }
+
+ lock (sync) {
+ if (Disposed) {
+ return;
+ }
+
+ try {
+ syndication_client.SubscribeToChannel (uri.ToString (), DownloadPreference.One);
+ source.NotifyUser ();
+ } catch (Exception e) {
+ Log.Exception (e);
+ }
+ }
+ }
+
+ public void QueueDownload (PaasItem item)
+ {
+ if (item.Error) {
+ item.Error = false;
+ item.Save ();
+ redraw ();
+ }
+
+ download_manager.QueueDownload (item);
+ }
+
+ public void QueueDownload (IEnumerable<PaasItem> items)
+ {
+ ServiceManager.DbConnection.BeginTransaction ();
+
+ try {
+ bool redraw_requested = false;
+
+ foreach (var item in items.Where (i => i.Error)) {
+ item.Error = false;
+ item.Save ();
+ redraw_requested = true;
+ }
+
+ ServiceManager.DbConnection.CommitTransaction ();
+
+ if (redraw_requested) {
+ redraw ();
+ }
+ } catch {
+ ServiceManager.DbConnection.RollbackTransaction ();
+ throw;
+ }
+
+ download_manager.QueueDownload (items);
+ }
+
+ private void InitializeInterface ()
+ {
+ ServiceManager.SourceManager.AddSource (source);
+
+ mg_interface_manager = new MiroGuideInterfaceManager ();
+ mg_interface_manager.Initialize (mg_client);
+
+ download_manager_interface = new DownloadManagerInterface (source, download_manager);
+ }
+
+ private void DisposeInterface ()
+ {
+ if (source != null) {
+ ServiceManager.SourceManager.RemoveSource (source);
+ source.Dispose ();
+ }
+
+ if (mg_interface_manager != null) {
+ mg_interface_manager.Dispose ();
+ mg_interface_manager = null;
+ }
+
+ if (download_manager_interface != null) {
+ download_manager_interface.Dispose ();
+ download_manager_interface = null;
+ }
+ }
+
+ private DatabaseTrackInfo GetTrackByItemId (long item_id)
+ {
+ return DatabaseTrackInfo.Provider.FetchFirstMatching (
+ "PrimarySourceID = ? AND ExternalID = ?", source.DbId, item_id
+ );
+ }
+
+ private bool RefreshFeeds ()
+ {
+ return RefreshFeeds (false);
+ }
+
+ private bool RefreshFeeds (bool forceRefresh)
+ {
+ Hyena.Log.Debug ("Refreshing any podcasts that haven't been updated in over an hour");
+
+ Banshee.Kernel.Scheduler.Schedule (new Banshee.Kernel.DelegateJob (delegate {
+ try {
+ DateTime now = DateTime.Now;
+
+ syndication_client.QueueUpdate (
+ PaasChannel.Provider.FetchAll ().Where (
+ c => (now - c.LastDownloadTime).TotalHours > 1 || forceRefresh
+ )
+ );
+ } catch (Exception e) {
+ Hyena.Log.Exception (e);
+ }
+ }));
+
+ return true;
+ }
+
+ private void OnChannelUpdatedHandler (object sender, ChannelUpdateCompletedEventArgs e)
+ {
+ lock (sync) {
+ if (Disposed) {
+ return;
+ }
+
+ if (e.Succeeded) {
+ PaasChannel channel = e.Channel;
+#if EDIT_DIR_TEST
+ if (String.IsNullOrEmpty (channel.LocalEnclosurePath)) {
+ string escaped = Hyena.StringUtil.EscapeFilename (channel.Name);
+
+ channel.LocalEnclosurePath = Path.Combine (source.BaseDirectory, escaped);
+ channel.Save ();
+
+ try {
+ Banshee.IO.Directory.Create (channel.LocalEnclosurePath);
+ } catch (Exception ex) {
+ Hyena.Log.Exception (ex);
+ }
+ }
+#endif
+ IEnumerable<PaasItem> items = channel.Items.OrderByDescending (i => i.PubDate);
+
+ switch (e.Channel.DownloadPreference) {
+ case DownloadPreference.One:
+ items = items.Take (1);
+ break;
+ case DownloadPreference.None:
+ items = items.Take (0);
+ break;
+ }
+
+ RefreshArtworkFor (channel);
+ QueueDownload (items.Where (i => i.Active && !i.IsDownloaded));
+ } else if (e.Error != null) {
+ Log.Exception (e.Error);
+
+ source.ErrorSource.AddMessage (
+ String.Format (Catalog.GetString (@"Error while updating channel ""{0}"""), e.Channel.Name),
+ e.Error.Message
+ );
+ }
+
+ redraw ();
+ //reload ();
+ }
+ }
+
+ private void OnItemsAddedHandler (object sender, ItemEventArgs e)
+ {
+ reload ();
+ }
+
+ private void OnItemsRemovedHandler (object sender, ItemEventArgs e)
+ {
+ lock (sync) {
+ if (Disposed) {
+ return;
+ }
+
+ if (e.Item != null) {
+ if (download_manager.Contains (e.Item)) {
+ download_manager.CancelDownload (e.Item);
+ }
+ } else {
+ foreach (PaasItem item in e.Items) {
+ if (download_manager.Contains (item)) {
+ download_manager.CancelDownload (item);
+ }
+ }
+ }
+
+ reload ();
+ }
+ }
+
+ private void OnDownloadTaskCompletedHandler (object sender, TaskCompletedEventArgs<HttpFileDownloadTask> e)
+ {
+ PaasItem item = e.UserState as PaasItem;
+
+ if (item == null) {
+ return;
+ }
+
+ lock (sync) {
+ if (Disposed) {
+ return;
+ } else if (e.State != TaskState.Succeeded) {
+ if (e.Error != null) {
+ source.ErrorSource.AddMessage (
+ String.Format (
+ Catalog.GetString ("Error Downloading: {0}"), (e.Task as HttpFileDownloadTask).Name
+ ), e.Error.Message
+ );
+
+ if (ServiceManager.Get<Network> ().Connected) {
+ item.Error = true;
+ item.Save ();
+ redraw ();
+ }
+ }
+
+ return;
+ }
+
+ string path = Path.GetDirectoryName (e.Task.LocalPath);
+ string filename = Path.GetFileName (e.Task.LocalPath);
+ string full_path = path;
+ string tmp_local_path;
+
+#if EDIT_DIR_TEST
+ string local_enclosure_path = item.Channel.LocalEnclosurePath;
+#else
+
+ string escaped = Hyena.StringUtil.EscapeFilename (item.Channel.Name);
+ string local_enclosure_path = Path.Combine (source.BaseDirectory, escaped);
+#endif
+
+ if (!local_enclosure_path.EndsWith (Path.DirectorySeparatorChar.ToString ())) {
+ local_enclosure_path += Path.DirectorySeparatorChar;
+ }
+
+ if (!full_path.EndsWith (Path.DirectorySeparatorChar.ToString ())) {
+ full_path += Path.DirectorySeparatorChar;
+ }
+
+ full_path += filename;
+ tmp_local_path = local_enclosure_path+StringUtils.DecodeUrl (filename);
+
+ try {
+ // This is crap, non-i18n-iz-iz-ized strings will be displayed to the users.
+ if (!Banshee.IO.Directory.Exists (path)) {
+ throw new InvalidOperationException ("Directory specified by path does not exist");
+ } else if (!Banshee.IO.File.Exists (new SafeUri (full_path))) {
+ throw new InvalidOperationException (
+ String.Format ("File: {0}, does not exist", full_path)
+ );
+ }
+
+ if (!Banshee.IO.Directory.Exists (local_enclosure_path)) {
+ Banshee.IO.Directory.Create (local_enclosure_path);
+ }
+
+ if (Banshee.IO.File.Exists (new SafeUri (tmp_local_path))) {
+ int last_dot = tmp_local_path.LastIndexOf (".");
+
+ if (last_dot == -1) {
+ last_dot = tmp_local_path.Length-1;
+ }
+
+ string rep = String.Format (
+ "-{0}",
+ Guid.NewGuid ().ToString ()
+ .Replace ("-", String.Empty)
+ .ToLower ()
+ );
+
+ tmp_local_path = tmp_local_path.Insert (last_dot, rep);
+ }
+
+ Banshee.IO.File.Move (new SafeUri (full_path), new SafeUri (tmp_local_path));
+
+ try {
+ Banshee.IO.Directory.Delete (path, true);
+ } catch {}
+
+ item.LocalPath = tmp_local_path;
+ item.MimeType = e.Task.MimeType;
+ item.DownloadedAt = DateTime.Now;
+
+ DatabaseTrackInfo track = GetTrackByItemId (item.DbId);
+
+ if (track != null) {
+ new PaasTrackInfo (track, item).Track.Save ();
+ item.IsNew = (track.PlayCount < 1);
+ item.Save ();
+ }
+
+ source.NotifyUser ();
+ } catch (Exception ex) {
+ source.ErrorSource.AddMessage (
+ String.Format (Catalog.GetString ("Error Saving File: {0}"), tmp_local_path), ex.Message
+ );
+
+ item.Error = true;
+ item.Save ();
+
+ Hyena.Log.Exception (ex);
+ Hyena.Log.Error (ex.StackTrace);
+ }
+
+ reload ();
+ }
+ }
+
+ private void OnCommandLineArgument (string uri, object value, bool isFile)
+ {
+ if (!isFile || String.IsNullOrEmpty (uri)) {
+ return;
+ }
+
+ lock (sync) {
+ if (Disposed) {
+ return;
+ }
+
+ if (uri.Contains ("opml") || uri.EndsWith (".miro") || uri.EndsWith (".democracy")) {
+ try {
+ OpmlParser opml_parser = new OpmlParser (uri, true);
+
+ foreach (string channel in opml_parser.Feeds) {
+ ServiceManager.Get<DBusCommandService> ().PushFile (channel);
+ }
+ } catch (Exception e) {
+ Log.Exception (e);
+ }
+ } else if (uri.Contains ("xml") || uri.Contains ("rss") || uri.Contains ("feed") || uri.StartsWith ("itpc") || uri.StartsWith ("pcast")) {
+ if (uri.StartsWith ("feed://") || uri.StartsWith ("itpc://")) {
+ uri = String.Format ("http://{0}", uri.Substring (7));
+ } else if (uri.StartsWith ("pcast://")) {
+ uri = String.Format ("http://{0}", uri.Substring (8));
+ }
+
+ syndication_client.SubscribeToChannel (uri, DownloadPreference.One);
+ source.NotifyUser ();
+ } else if (uri.StartsWith ("itms://")) {
+ System.Threading.ThreadPool.QueueUserWorkItem (delegate {
+ try {
+ string feed_url = new ItmsPodcast (uri).FeedUrl;
+
+ if (feed_url != null) {
+ ThreadAssist.ProxyToMain (delegate {
+ syndication_client.SubscribeToChannel (feed_url, DownloadPreference.None);
+ source.NotifyUser ();
+ });
+ }
+ } catch (Exception e) {
+ Hyena.Log.Exception (e);
+ }
+ });
+ }
+ }
+ }
+
+ private void IncrementUpdateCount ()
+ {
+ lock (sync) {
+ if (!Disposed) {
+ if (update_count++ == 0) {
+ ThreadAssist.ProxyToMain (delegate {
+ source.SetStatus ("Updating...", false, true, null);
+ });
+ }
+ }
+ }
+ }
+
+ private void DecrementUpdateCount ()
+ {
+ lock (sync) {
+ if (!Disposed) {
+ if (--update_count == 0) {
+ ThreadAssist.ProxyToMain (delegate {
+ source.HideStatus ();
+ });
+ }
+ }
+ }
+ }
+
+ private void RefreshArtworkFor (PaasChannel channel)
+ {
+ if (channel.LastDownloadTime != DateTime.MinValue && !CoverArtSpec.CoverExists (ArtworkIdFor (channel))) {
+ Banshee.Kernel.Scheduler.Schedule (new PaasImageFetchJob (channel), Banshee.Kernel.JobPriority.BelowNormal);
+ }
+ }
+
+ public static string ArtworkIdFor (PaasChannel channel)
+ {
+ return ArtworkIdFor (channel.Name);
+ }
+
+ public static string ArtworkIdFor (string id)
+ {
+ return String.Format ("paas-{0}", Banshee.Base.CoverArtSpec.EscapePart (id));
+ }
+
+ public static readonly SchemaEntry<string> MiroGuideUsername = new SchemaEntry<string> (
+ "plugins.paas.miroguide", "username", String.Empty, "Miro Guide Username", ""
+ );
+
+ public static readonly SchemaEntry<string> MiroGuidePasswordHash = new SchemaEntry<string> (
+ "plugins.paas.miroguide", "password_hash", String.Empty, "Miro Guide Password Hash", ""
+ );
+
+ public static readonly SchemaEntry<string> MiroGuideSessionID = new SchemaEntry<string> (
+ "plugins.paas.miroguide", "session_id", String.Empty, "Miro Guide Session ID", ""
+ );
+
+ public static readonly SchemaEntry<string> MiroGuideServiceUri = new SchemaEntry<string> (
+ "plugins.paas.miroguide", "service_uri", "http://miroguide.com", "Miro Guide Service URI", ""
+ );
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas/PaasSource.cs b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas/PaasSource.cs
new file mode 100644
index 0000000..387cef3
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Banshee.Paas/Banshee.Paas/PaasSource.cs
@@ -0,0 +1,277 @@
+//
+// PaasSource.cs
+//
+// Authors:
+// Mike Urbanski <michael c urbanski gmail com>
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Michael C. Urbanski
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Mono.Unix;
+
+using Hyena.Collections;
+
+using Banshee.Base;
+using Banshee.Sources;
+using Banshee.Sources.Gui;
+using Banshee.ServiceStack;
+
+using Banshee.Collection;
+using Banshee.Collection.Gui;
+using Banshee.Collection.Database;
+
+using Migo2.Async;
+
+using Banshee.Paas.Gui;
+using Banshee.Paas.Data;
+
+namespace Banshee.Paas
+{
+ public class PaasSource : Banshee.Library.LibrarySource
+ {
+ private RateLimiter redraw_limiter;
+
+ private PaasActions actions = null;
+ private PaasSourceContents contents;
+ private PaasChannelModel channel_model;
+
+ protected override bool HasArtistAlbum {
+ get { return false; }
+ }
+
+ public override bool AcceptsInputFromSource (Source source)
+ {
+ return false;
+ }
+
+ public override string DefaultBaseDirectory {
+ get {
+ // HACK there isn't an XDG_PODCASTS_DIR; propose it?
+ return XdgBaseDirectorySpec.GetUserDirectory ("XDG_PODCASTS_DIR", "Podcasts");
+ }
+ }
+
+ public override bool CanRename {
+ get { return false; }
+ }
+
+ public override bool CanAddTracks {
+ get { return true; }
+ }
+
+ public override bool CanRemoveTracks {
+ get { return false; }
+ }
+
+ public override bool CanDeleteTracks {
+ get { return false; }
+ }
+
+ public PaasChannelModel ChannelModel {
+ get { return channel_model; }
+ }
+
+ public override string PreferencesPageId {
+ get { return UniqueId; }
+ }
+
+ public override bool ShowBrowser {
+ get { return true; }
+ }
+
+ public override SourceMergeType SupportedMergeTypes {
+ get { return SourceMergeType.None; }
+ }
+
+#region Constructors
+ public PaasSource (PaasService service) : base (Catalog.GetString ("Podcasts"), "PaasLibrary", 200)
+ {
+ actions = new PaasActions (service);
+
+ SupportsPlaylists = false;
+ TrackExternalObjectHandler = GetPaasTrackInfo;
+ TrackArtworkIdHandler = GetTrackArtworkId;
+ MediaTypes = TrackMediaAttributes.Podcast;
+ NotMediaTypes = TrackMediaAttributes.AudioBook;
+ SyncCondition = "(substr(CoreTracks.Uri, 0, 4) != 'http' AND CoreTracks.PlayCount = 0)";
+
+ Properties.SetString ("Icon.Name", "podcast");
+
+ Properties.SetString ("ActiveSourceUIResource", "ActiveSourceUI.xml");
+ Properties.Set<bool> ("ActiveSourceUIResourcePropagate", false);
+ Properties.Set<System.Reflection.Assembly> ("ActiveSourceUIResource.Assembly", typeof(PaasSource).Assembly);
+
+ Properties.SetString ("GtkActionPath", "/PaasSourcePopup");
+
+ contents = new PaasSourceContents ();
+
+ (contents.TrackView as PaasItemView).PopupMenu += OnItemViewPopupMenuHandler;
+ (contents.TrackView as PaasItemView).FuckedPopupMenu += OnItemViewFuckedPopupMenuHandler;
+
+ Properties.Set<ISourceContents> ("Nereid.SourceContents", contents);
+ Properties.Set<bool> ("Nereid.SourceContentsPropagate", false);
+
+ PaasColumnController column_controller = new PaasColumnController ();
+
+ column_controller.SetIndicatorColumnDataHelper (
+ (cell, item) => {
+ PaasItem pi = item as PaasItem;
+
+ return (pi != null) ?
+ service.DownloadManager.CheckActiveDownloadStatus (pi.DbId) :
+ TaskState.None;
+ }
+ );
+
+ contents.ChannelView.SetChannelDataHelper (
+ (cell, channel) => service.SyndicationClient.GetUpdateStatus (channel as PaasChannel)
+ );
+
+ Properties.Set<PaasColumnController> ("TrackView.ColumnController", column_controller);
+
+ redraw_limiter = new RateLimiter (() => {
+ ThreadAssist.ProxyToMain (() => {
+ contents.QueueDraw ();
+ });
+ });
+ }
+
+#endregion
+
+ public void AddItem (PaasItem item)
+ {
+ if (item != null) {
+ PaasTrackInfo pti = new PaasTrackInfo (new DatabaseTrackInfo (), item);
+ pti.Track.PrimarySource = this;
+
+ pti.Track.Save (false);
+ }
+ }
+
+ public void AddItems (IEnumerable<PaasItem> items)
+ {
+ if (items != null) {
+ foreach (PaasItem item in items) {
+ AddItem (item);
+ }
+ }
+ }
+
+ public void RemoveItem (PaasItem item)
+ {
+ if (item != null) {
+ RemoveTrack ((int)item.DbId);
+ }
+ }
+
+ public void RemoveItems (IEnumerable<PaasItem> items)
+ {
+ if (items != null) {
+ RangeCollection rc = new RangeCollection ();
+
+ foreach (PaasItem item in items) {
+ rc.Add ((int)item.DbId);
+ }
+
+ foreach (RangeCollection.Range range in rc.Ranges) {
+ RemoveTrackRange (TrackModel as DatabaseTrackListModel, range);
+ }
+ }
+ }
+
+ public void QueueDraw ()
+ {
+ redraw_limiter.Execute ();
+ }
+
+ protected override IEnumerable<IFilterListModel> CreateFiltersFor (DatabaseSource src)
+ {
+ PaasChannelModel channel_model = new PaasChannelModel (
+ src, src.DatabaseTrackModel, ServiceManager.DbConnection,
+ String.Format ("PaasChannels-{0}", src.UniqueId)
+ );
+
+ channel_model.Selection.Changed += (sender, e) => { actions.UpdateChannelActions (); };
+
+ yield return channel_model;
+
+ yield return new PaasUnheardFilterModel (src.DatabaseTrackModel);
+ yield return new DownloadStatusFilterModel (src.DatabaseTrackModel);
+
+ if (src == this) {
+ this.channel_model = channel_model;
+ AfterInitialized ();
+ }
+ }
+
+ public override void Dispose ()
+ {
+ if (actions != null) {
+ actions.Dispose ();
+ actions = null;
+ }
+
+ (contents.TrackView as PaasItemView).PopupMenu -= OnItemViewPopupMenuHandler;
+ (contents.TrackView as PaasItemView).FuckedPopupMenu -= OnItemViewFuckedPopupMenuHandler;
+
+ base.Dispose ();
+ }
+
+ protected override void RemoveTrackRange (DatabaseTrackListModel model, RangeCollection.Range range)
+ {
+ ServiceManager.DbConnection.Execute (
+ String.Format (remove_range_sql,
+ "TrackID FROM CoreTracks WHERE PrimarySourceID = ? AND ExternalID >= ? AND ExternalID <= ?"
+ ), DateTime.Now, DbId, range.Start, range.End, DbId, range.Start, range.End
+ );
+ }
+
+ private object GetPaasTrackInfo (DatabaseTrackInfo track)
+ {
+ return new PaasTrackInfo (track);
+ }
+
+ protected override DatabaseTrackListModel CreateTrackModelFor (DatabaseSource src)
+ {
+ return new PaasTrackListModel (ServiceManager.DbConnection, DatabaseTrackInfo.Provider, src);
+ }
+
+ [GLib.ConnectBefore]
+ private void OnItemViewPopupMenuHandler (object sender, Gtk.PopupMenuArgs e)
+ {
+ actions.UpdateItemActions ();
+ }
+
+ private void OnItemViewFuckedPopupMenuHandler (object sender, EventArgs e)
+ {
+ actions.UpdateItemActions ();
+ }
+
+ private string GetTrackArtworkId (DatabaseTrackInfo track)
+ {
+ return PaasService.ArtworkIdFor (PaasTrackInfo.From (track).Channel);
+ }
+ }
+}
diff --git a/src/Extensions/Banshee.Paas/Makefile.am b/src/Extensions/Banshee.Paas/Makefile.am
new file mode 100644
index 0000000..80b04f5
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Makefile.am
@@ -0,0 +1,123 @@
+ASSEMBLY = Banshee.Paas
+TARGET = library
+LINK = $(REF_EXTENSION_PAAS)
+INSTALL_DIR = $(EXTENSIONS_INSTALL_DIR)
+
+SOURCES = \
+ Banshee.Paas/Banshee.Paas.Aether/AetherClient.cs \
+ Banshee.Paas/Banshee.Paas.Aether/AetherClientID.cs \
+ Banshee.Paas/Banshee.Paas.Aether/AetherClientState.cs \
+ Banshee.Paas/Banshee.Paas.Aether/AetherClientStateChangedEventArgs.cs \
+ Banshee.Paas/Banshee.Paas.Aether/AetherRequest.cs \
+ Banshee.Paas/Banshee.Paas.Aether/AetherRequestCompletedEventArgs.cs \
+ Banshee.Paas/Banshee.Paas.Aether/ChannelEventArgs.cs \
+ Banshee.Paas/Banshee.Paas.Aether/ChannelUpdateStatus.cs \
+ Banshee.Paas/Banshee.Paas.Aether/ItemEventArgs.cs \
+ Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/GetCategoriesCompletedEventArgs.cs \
+ Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/GetChannelsEventArgs.cs \
+ Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideAccountInfo.cs \
+ Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideCategoryInfo.cs \
+ Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideChannelInfo.cs \
+ Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideClient.cs \
+ Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideClientError.cs \
+ Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideClientMethod.cs \
+ Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideFilterType.cs \
+ Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideMethodCompletedEventArgs.cs \
+ Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideRequestState.cs \
+ Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/MiroGuideSortType.cs \
+ Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/RequestCompletedEventArgs.cs \
+ Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/SearchContext.cs \
+ Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/ServiceMethodFlags.cs \
+ Banshee.Paas/Banshee.Paas.Aether/MiroGuideClient/SubscriptionRequestedEventArgs.cs \
+ Banshee.Paas/Banshee.Paas.Aether/RequestState.cs \
+ Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/ChannelUpdateCompletedEventArgs.cs \
+ Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/ChannelUpdateManager.cs \
+ Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/ChannelUpdateTask.cs \
+ Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/ItmsPodcast.cs \
+ Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/RssParser.cs \
+ Banshee.Paas/Banshee.Paas.Aether/SyndicationClient/SyndicationClient.cs \
+ Banshee.Paas/Banshee.Paas.Data/CacheableItem.cs \
+ Banshee.Paas/Banshee.Paas.Data/CacheModelProvider.cs \
+ Banshee.Paas/Banshee.Paas.Data/DownloadPreference.cs \
+ Banshee.Paas/Banshee.Paas.Data/DownloadStatusFilterModel.cs \
+ Banshee.Paas/Banshee.Paas.Data/ListModel.cs \
+ Banshee.Paas/Banshee.Paas.Data/PaasChannel.cs \
+ Banshee.Paas/Banshee.Paas.Data/PaasChannelModel.cs \
+ Banshee.Paas/Banshee.Paas.Data/PaasItem.cs \
+ Banshee.Paas/Banshee.Paas.Data/PaasTrackInfo.cs \
+ Banshee.Paas/Banshee.Paas.Data/PaasTrackListModel.cs \
+ Banshee.Paas/Banshee.Paas.Data/PaasUnheardFilterModel.cs \
+ Banshee.Paas/Banshee.Paas.Data/SingletonSelection.cs \
+ Banshee.Paas/Banshee.Paas.DownloadManager.Data/QueuedDownloadTask.cs \
+ Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadListView.cs \
+ Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadManagerInterface.cs \
+ Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadSource.cs \
+ Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadSourceContents.cs \
+ Banshee.Paas/Banshee.Paas.DownloadManager.Gui/DownloadUserJob.cs \
+ Banshee.Paas/Banshee.Paas.DownloadManager/DownloadListModel.cs \
+ Banshee.Paas/Banshee.Paas.DownloadManager/PaasDownloadManager.cs \
+ Banshee.Paas/Banshee.Paas.Gui/ColumnCellChannel.cs \
+ Banshee.Paas/Banshee.Paas.Gui/ColumnCellDownloadStatus.cs \
+ Banshee.Paas/Banshee.Paas.Gui/ColumnCellPaasStatusIndicator.cs \
+ Banshee.Paas/Banshee.Paas.Gui/ColumnCellPublished.cs \
+ Banshee.Paas/Banshee.Paas.Gui/ColumnCellUnheard.cs \
+ Banshee.Paas/Banshee.Paas.Gui/Dialogs/ChannelPropertiesDialog.cs \
+ Banshee.Paas/Banshee.Paas.Gui/Dialogs/SubscribeDialog.cs \
+ Banshee.Paas/Banshee.Paas.Gui/DownloadPreferenceComboBox.cs \
+ Banshee.Paas/Banshee.Paas.Gui/DownloadStatusFilterView.cs \
+ Banshee.Paas/Banshee.Paas.Gui/IColumnCellDataHelper.cs \
+ Banshee.Paas/Banshee.Paas.Gui/PaasActions.cs \
+ Banshee.Paas/Banshee.Paas.Gui/PaasChannelView.cs \
+ Banshee.Paas/Banshee.Paas.Gui/PaasColumnController.cs \
+ Banshee.Paas/Banshee.Paas.Gui/PaasItemPage.cs \
+ Banshee.Paas/Banshee.Paas.Gui/PaasItemView.cs \
+ Banshee.Paas/Banshee.Paas.Gui/PaasSourceContents.cs \
+ Banshee.Paas/Banshee.Paas.Gui/PaasUnheardFilterView.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide.Gui/ChannelInfoPreview.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide.Gui/ColumnCellChannel.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideAccountDialog.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideActions.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideCategoryListView.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideChannelListView.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideLoginForm.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide.Gui/MiroGuideSearchEntry.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide.Gui/ReflectionInfoWidget.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SortPreferenceActionButton.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SortPreferenceChangedEventArgs.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SourceContents/BrowserSourceContents.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SourceContents/ChannelSourceContents.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide.Gui/SourceContents/MiroGuideSourceContents.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideCategoryListModel.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideChannelListModel.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideImageFetchJob.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideInterfaceManager.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide/MiroGuideSearchFilter.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide/Sources/BrowseChannelsSource.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide/Sources/ChannelSource.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide/Sources/FeaturedChannelsSource.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide/Sources/HDChannelsSource.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide/Sources/MiroGuideSource.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide/Sources/MiroGuideSourcePosition.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide/Sources/PopularChannelsSource.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide/Sources/RecommendedChannelsSource.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide/Sources/SearchSource.cs \
+ Banshee.Paas/Banshee.Paas.MiroGuide/Sources/TopRatedChannelsSource.cs \
+ Banshee.Paas/Banshee.Paas.Utils/OpmlParser.cs \
+ Banshee.Paas/Banshee.Paas.Utils/StringUtils.cs \
+ Banshee.Paas/Banshee.Paas/PaasImageFetchJob.cs \
+ Banshee.Paas/Banshee.Paas/PaasService.cs \
+ Banshee.Paas/Banshee.Paas/PaasSource.cs
+
+RESOURCES = \
+ Banshee.Paas.addin.xml \
+ Resources/ActiveSourceUI.xml \
+ Resources/MiroGuideActiveSourceUI.xml \
+ Resources/GlobalUI.xml \
+ Resources/MiroGuideUI.xml
+
+if ENABLE_PAAS
+include $(top_srcdir)/build/build.mk
+else
+EXTRA_DIST = $(SOURCES) $(RESOURCES)
+endif
+
diff --git a/src/Extensions/Banshee.Paas/Resources/ActiveSourceUI.xml b/src/Extensions/Banshee.Paas/Resources/ActiveSourceUI.xml
new file mode 100644
index 0000000..1e5206e
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Resources/ActiveSourceUI.xml
@@ -0,0 +1,39 @@
+<ui>
+ <toolbar name="HeaderToolbar">
+ <placeholder name="SourceActions">
+ <toolitem name="PaasSubscribe" action="PaasSubscribeAction" />
+ <toolitem name="PaasUpdateAll" action="PaasUpdateAllAction" />
+ </placeholder>
+ </toolbar>
+
+ <menubar name="MainMenu" action="MainMenuAction">
+ <menu name="MediaMenu" action="MediaMenuAction">
+ <placeholder name="BelowOpenLocation">
+ <separator />
+ <menuitem name="PaasSubscribe" action="PaasSubscribeAction" />
+ <separator />
+ <menuitem name="PaasExportAllOpml" action="PaasExportAllOpmlAction" />
+ <menuitem name="PaasImportOpml" action="PaasImportOpmlAction" />
+ </placeholder>
+ </menu>
+ </menubar>
+
+ <popup name="TrackContextMenu" action="TrackContextMenuAction">
+ <placeholder name="BelowAddToPlaylist">
+ <separator />
+ <menuitem name="PaastItemLink" action="PaasItemLinkAction" />
+ <separator />
+ <menuitem name="PaasItemPause" action="PaasItemPauseAction" />
+ <menuitem name="PaasItemResume" action="PaasItemResumeAction" />
+ <menuitem name="PaasItemCancel" action="PaasItemCancelAction" />
+ <menuitem name="PaasItemDownload" action="PaasItemDownloadAction" />
+ <separator />
+ <menuitem name="PaasItemRemove" action="PaasItemRemoveAction" />
+ <menuitem name="PaasItemDelete" action="PaasItemDeleteAction" />
+ <separator />
+ <menuitem name="PaasItemMarkNew" action="PaasItemMarkNewAction" />
+ <menuitem name="PaasItemMarkOld" action="PaasItemMarkOldAction" />
+ <separator />
+ </placeholder>
+ </popup>
+</ui>
diff --git a/src/Extensions/Banshee.Paas/Resources/GlobalUI.xml b/src/Extensions/Banshee.Paas/Resources/GlobalUI.xml
new file mode 100644
index 0000000..893c7b3
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Resources/GlobalUI.xml
@@ -0,0 +1,43 @@
+<ui>
+ <popup name="PaasSourcePopup" action="PaasSourcePopupAction">
+ <menuitem name="PaasSubscribe" action="PaasSubscribeAction" />
+ <separator />
+ <menuitem name="PaasExportAllOpml" action="PaasExportAllOpmlAction" />
+ <menuitem name="PaasImportOpml" action="PaasImportOpmlAction" />
+ <separator />
+<!-- <menuitem name="PaasMiroGuideSubscribeAll" action="PaasMiroGuideSubscribAlleAction" />
+ <menuitem name="PaasMiroGuideGetSubscriptions" action="PaasMiroGuideGetSubscriptionsAction" /> -->
+ <separator />
+ <menuitem name="PaasUpdateAll" action="PaasUpdateAllAction" />
+ <menuitem name="PaasDownloadAll" action="PaasDownloadAllAction" />
+ <separator />
+ <menuitem name="SourcePreferences" action="SourcePreferencesAction"/>
+ </popup>
+
+ <popup name="PaasChannelPopup" action="PaasChannelPopupAction">
+ <menuitem name="PaasChannelUpdate" action="PaasChannelUpdateAction" />
+ <separator />
+ <menuitem name="PaasChannelHomepage" action="PaasChannelHomepageAction" />
+ <separator />
+ <menuitem name="PaasExportOpml" action="PaasExportOpmlAction" />
+<!-- <menuitem name="PaasMiroGuideSubscribe" action="PaasMiroGuideSubscribeAction" /> -->
+ <separator />
+ <menuitem name="PaasChannelDownloadAll" action="PaasChannelDownloadAllAction"/>
+ <menuitem name="PaasChannelDelete" action="PaasChannelDeleteAction" />
+ <separator />
+ <menuitem name="PaasChannelProperties" action="PaasChannelPropertiesAction" />
+ </popup>
+
+ <popup name="PaasAllChannelsContextMenu" action="PaasAllChannelsContextMenuAction">
+ <menuitem name="PaasSubscribe" action="PaasSubscribeAction" />
+ <separator />
+ <menuitem name="PaasExportAllOpml" action="PaasExportAllOpmlAction" />
+ <menuitem name="PaasImportOpml" action="PaasImportOpmlAction" />
+ <separator />
+<!-- <menuitem name="PaasMiroGuideSubscribeAll" action="PaasMiroGuideSubscribAlleAction" />
+ <menuitem name="PaasMiroGuideGetSubscriptions" action="PaasMiroGuideGetSubscriptionsAction" /> -->
+ <separator />
+ <menuitem name="PaasUpdateAll" action="PaasUpdateAllAction" />
+ <menuitem name="PaasDownloadAll" action="PaasDownloadAllAction" />
+ </popup>
+</ui>
diff --git a/src/Extensions/Banshee.Paas/Resources/MiroGuideActiveSourceUI.xml b/src/Extensions/Banshee.Paas/Resources/MiroGuideActiveSourceUI.xml
new file mode 100644
index 0000000..74801cd
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Resources/MiroGuideActiveSourceUI.xml
@@ -0,0 +1,7 @@
+<ui>
+ <toolbar name="HeaderToolbar">
+ <placeholder name="SourceActions">
+ <toolitem name="MiroGuideRefreshChannels" action="MiroGuideRefreshChannelsAction" />
+ </placeholder>
+ </toolbar>
+</ui>
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Paas/Resources/MiroGuideUI.xml b/src/Extensions/Banshee.Paas/Resources/MiroGuideUI.xml
new file mode 100644
index 0000000..aaff589
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/Resources/MiroGuideUI.xml
@@ -0,0 +1,23 @@
+<ui>
+ <toolbar name="FooterToolbar">
+ <placeholder name="Extensions">
+ <placeholder name="MiroGuideChannelSortButton" />
+ </placeholder>
+ </toolbar>
+<!--
+ <popup name="MiroGuideSourcePopup" action="MiroGuideSourcePopupAction">
+ <menuitem name="PaasEditMiroGuideProperties" action="PaasEditMiroGuidePropertiesAction" />
+ </popup>
+-->
+ <popup name="MiroGuideChannelPopup" action="MiroGuideChannelPopupAction">
+ <menuitem name="MiroGuideChannelSubscribe" action="MiroGuideChannelSubscribeAction" />
+ </popup>
+
+ <popup name="MiroGuideSortPreferencePopup" action="MiroGuideSortPreferencePopupAction">
+ <menuitem name="MiroGuideSortByName" action="MiroGuideSortByNameAction" />
+ <separator />
+ <menuitem name="MiroGuideSortByRating" action="MiroGuideSortByRatingAction" />
+ <menuitem name="MiroGuideSortByPopularity" action="MiroGuideSortByPopularityAction" />
+ <menuitem name="MiroGuideSortByRelevance" action="MiroGuideSortByRelevanceAction" />
+ </popup>
+</ui>
diff --git a/src/Extensions/Banshee.Paas/ThemeIcons/16x16/categories/podcast.png b/src/Extensions/Banshee.Paas/ThemeIcons/16x16/categories/podcast.png
new file mode 100644
index 0000000..f07b8ed
Binary files /dev/null and b/src/Extensions/Banshee.Paas/ThemeIcons/16x16/categories/podcast.png differ
diff --git a/src/Extensions/Banshee.Paas/ThemeIcons/16x16/status/podcast-new.png b/src/Extensions/Banshee.Paas/ThemeIcons/16x16/status/podcast-new.png
new file mode 100644
index 0000000..8cf4dea
Binary files /dev/null and b/src/Extensions/Banshee.Paas/ThemeIcons/16x16/status/podcast-new.png differ
diff --git a/src/Extensions/Banshee.Paas/ThemeIcons/22x22/categories/podcast.png b/src/Extensions/Banshee.Paas/ThemeIcons/22x22/categories/podcast.png
new file mode 100644
index 0000000..98344a1
Binary files /dev/null and b/src/Extensions/Banshee.Paas/ThemeIcons/22x22/categories/podcast.png differ
diff --git a/src/Extensions/Banshee.Paas/ThemeIcons/48x48/categories/podcast.png b/src/Extensions/Banshee.Paas/ThemeIcons/48x48/categories/podcast.png
new file mode 100644
index 0000000..7c85469
Binary files /dev/null and b/src/Extensions/Banshee.Paas/ThemeIcons/48x48/categories/podcast.png differ
diff --git a/src/Extensions/Banshee.Paas/ThemeIcons/scalable/categories/miro-browse.svg b/src/Extensions/Banshee.Paas/ThemeIcons/scalable/categories/miro-browse.svg
new file mode 100644
index 0000000..744931d
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/ThemeIcons/scalable/categories/miro-browse.svg
@@ -0,0 +1,232 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.1"
+ width="48"
+ height="48"
+ id="svg3033">
+ <defs
+ id="defs3035">
+ <linearGradient
+ x1="22.885227"
+ y1="17.628952"
+ x2="22.885227"
+ y2="30.889549"
+ id="linearGradient2967"
+ xlink:href="#linearGradient3202"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0395395,0,0,0.9504295,-44.269588,47.458983)" />
+ <linearGradient
+ id="linearGradient3202">
+ <stop
+ id="stop3204"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3206"
+ style="stop-color:#d3eefc;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="25.579119"
+ y1="-25.736307"
+ x2="25.579119"
+ y2="41.953453"
+ id="linearGradient2922"
+ xlink:href="#linearGradient3211"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.96423685,0,0,0.97292031,-0.65526887,1.99877)" />
+ <linearGradient
+ id="linearGradient3211">
+ <stop
+ id="stop3213"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3215"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ cx="39.651478"
+ cy="18.618757"
+ r="20.714195"
+ fx="39.651478"
+ fy="18.618757"
+ id="radialGradient2926"
+ xlink:href="#linearGradient3242-2"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.04588502,2.9168208,-4.3002473,3.3179656e-7,75.324517,-119.13082)" />
+ <linearGradient
+ id="linearGradient3242-2">
+ <stop
+ id="stop3244"
+ style="stop-color:#f8b17e;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3246-9"
+ style="stop-color:#e35d4f;stop-opacity:1"
+ offset="0.31209752" />
+ <stop
+ id="stop3248-3"
+ style="stop-color:#c6262e;stop-opacity:1"
+ offset="0.57054454" />
+ <stop
+ id="stop3250-9"
+ style="stop-color:#690b54;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="24.008854"
+ y1="38.246284"
+ x2="24.008854"
+ y2="0.99999988"
+ id="linearGradient2928"
+ xlink:href="#linearGradient2490"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient2490">
+ <stop
+ id="stop2492"
+ style="stop-color:#791235;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2494"
+ style="stop-color:#dd3b27;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="21.13592"
+ y1="40.884956"
+ x2="21.13592"
+ y2="35.298134"
+ id="linearGradient2932"
+ xlink:href="#linearGradient2346"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.89432531,0,0,0.71689518,3.0387027,11.694937)" />
+ <linearGradient
+ id="linearGradient2346">
+ <stop
+ id="stop2348"
+ style="stop-color:#eeeeee;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2350"
+ style="stop-color:#d9d9da;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="302.85715"
+ y1="366.64789"
+ x2="302.85715"
+ y2="609.50507"
+ id="linearGradient3012"
+ xlink:href="#linearGradient5048"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)" />
+ <linearGradient
+ id="linearGradient5048">
+ <stop
+ id="stop5050"
+ style="stop-color:#000000;stop-opacity:0"
+ offset="0" />
+ <stop
+ id="stop5056"
+ style="stop-color:#000000;stop-opacity:1"
+ offset="0.5" />
+ <stop
+ id="stop5052"
+ style="stop-color:#000000;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ cx="605.71429"
+ cy="486.64789"
+ r="117.14286"
+ fx="605.71429"
+ fy="486.64789"
+ id="radialGradient3014"
+ xlink:href="#linearGradient5060"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)" />
+ <linearGradient
+ id="linearGradient5060">
+ <stop
+ id="stop5062"
+ style="stop-color:#000000;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop5064"
+ style="stop-color:#000000;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ cx="605.71429"
+ cy="486.64789"
+ r="117.14286"
+ fx="605.71429"
+ fy="486.64789"
+ id="radialGradient3031"
+ xlink:href="#linearGradient5060"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)" />
+ </defs>
+ <g
+ id="layer1">
+ <g
+ transform="matrix(0.02146165,0,0,0.01463898,43.088761,43.203389)"
+ id="g8875">
+ <rect
+ width="1339.6335"
+ height="478.35718"
+ x="-1559.2523"
+ y="-150.69685"
+ id="rect8877"
+ style="opacity:0.40206185;fill:url(#linearGradient3012);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+ <path
+ d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
+ id="path8879"
+ style="opacity:0.40206185;fill:url(#radialGradient3014);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+ <path
+ d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
+ id="path8881"
+ style="opacity:0.40206185;fill:url(#radialGradient3031);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ <path
+ d="m 43.271179,44.500125 -37.4219627,0 c -0.347147,0 -0.6266189,-0.317795 -0.6266189,-0.712548 L 4.4998733,38.285815 c 0,-0.394753 0.2794721,-0.712547 0.6266191,-0.712547 l 38.1037726,0"
+ id="rect8840"
+ style="fill:url(#linearGradient2932);fill-opacity:1;fill-rule:nonzero;stroke:#7a1235;stroke-width:0.99974567;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.06;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ d="m 42.811302,43.623944 c -0.444161,-0.581595 -0.26312,-3.849289 0.601434,-5.306799"
+ id="path9017"
+ style="fill:none;stroke:#666666;stroke-width:0.99974567;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 7.0900094,1.4998728 c -0.3552903,0 -0.6318528,0.4060416 -0.6318524,0.9204463 L 4.5175814,36.825966 c 0,0.514406 0.3040344,0.920448 0.6593242,0.920447 l 37.6638974,0 c 0.355288,1e-6 0.659324,-0.406041 0.659324,-0.920447 l -2.1702,-34.4056469 c 1e-6,-0.5144047 -0.276562,-0.9204463 -0.631853,-0.9204463 l -33.6080646,0 z"
+ id="rect8064"
+ style="fill:url(#radialGradient2926);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2928);stroke-width:0.99974567;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.06;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ d="M 6.2186139,43.454184 5.5983146,38.823012"
+ id="path3267"
+ style="opacity:0.2;fill:none;stroke:#000000;stroke-width:0.99974567px;stroke-linecap:square;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 8.0546969,2.4733527 c -0.3552905,0 -0.6318528,0.4060416 -0.6318524,0.9204463 L 5.4822687,35.85311 c 0,0.514405 0.3040346,0.920448 0.6593244,0.920447 l 35.7349339,0 c 0.355288,1e-6 0.659323,-0.406042 0.659323,-0.920447 L 40.36565,3.393799 c 10e-7,-0.5144047 -0.276561,-0.9204463 -0.631852,-0.9204463 l -31.6791011,0 z"
+ id="path2868"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient2922);stroke-width:0.99974567;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.06;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <g
+ transform="translate(43.198023,-50.872728)"
+ id="g2963">
+ <path
+ d="m -30.690913,76.872727 c 1.225378,0 3.774622,0 5,0 0.02003,-2.69044 -0.08349,-5.382579 0,-8.07176 0,-1.434446 0.209448,-2.71704 1.896799,-2.71704 1.935845,0 2.089684,0.969817 2.089684,2.817071 0,1.688236 0,6.283493 0,7.971729 1.225377,0 3.78814,10e-7 5.013517,10e-7 0.01437,-2.645706 -0.05881,-5.292395 0,-7.937385 0.0089,-1.27218 -0.113326,-2.851416 1.992422,-2.851416 1.894438,0 2.006642,1.246514 2.006642,2.872766 0,1.66967 -1e-6,6.246363 0,7.916035 1.225376,0 3.775558,0 5.000936,0 -0.01422,-2.951874 0.02968,-5.904782 -0.02429,-8.855952 -0.07686,-1.784335 -0.762887,-3.755827 -2.587981,-4.659627 -1.044123,-0.342811 -1.342666,-0.472622 -2.644984,-0.472622 -2.682008,0 -13.090101,-0.0118 -17.742745,-0.0118 0,4.666666 0,9.333334 0,13.999999 z"
+ id="path2438"
+ style="font-size:26.70637703px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#a1ceef;fill-opacity:1;stroke:none;font-family:URW Gothic L;-inkscape-font-specification:URW Gothic L Book" />
+ <path
+ d="m -30.690913,77.872727 c 1.225378,0 3.774622,0 5,0 0.02003,-2.69044 -0.08349,-5.382579 0,-8.07176 0,-1.434446 0.209448,-2.71704 1.896799,-2.71704 1.935845,0 2.089684,0.969817 2.089684,2.817071 0,1.688236 0,6.283493 0,7.971729 1.225377,0 3.78814,10e-7 5.013517,10e-7 0.01437,-2.645706 -0.05881,-5.292395 0,-7.937385 0.0089,-1.27218 -0.113326,-2.851416 1.992422,-2.851416 1.894438,0 2.006642,1.246514 2.006642,2.872766 0,1.66967 -1e-6,6.246363 0,7.916035 1.225376,0 3.775558,0 5.000936,0 -0.01422,-2.951874 0.02968,-5.904782 -0.02429,-8.855952 -0.07686,-1.784335 -0.762887,-3.755827 -2.587981,-4.659627 -1.044123,-0.342811 -1.342666,-0.472622 -2.644984,-0.472622 -2.682008,0 -13.090101,-0.0118 -17.742745,-0.0118 0,4.666666 0,9.333334 0,13.999999 z"
+ id="text3190"
+ style="font-size:26.70637703px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient2967);fill-opacity:1;stroke:none;font-family:URW Gothic L;-inkscape-font-specification:URW Gothic L Book" />
+ </g>
+ </g>
+</svg>
diff --git a/src/Extensions/Banshee.Paas/ThemeIcons/scalable/categories/miro.svg b/src/Extensions/Banshee.Paas/ThemeIcons/scalable/categories/miro.svg
new file mode 100644
index 0000000..be1c328
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/ThemeIcons/scalable/categories/miro.svg
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ width="48"
+ height="48"
+ id="svg3266"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="miro.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <metadata
+ id="metadata33">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ inkscape:window-height="794"
+ inkscape:window-width="1440"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="6.8648283"
+ inkscape:cx="-9.6420969"
+ inkscape:cy="24"
+ inkscape:window-x="0"
+ inkscape:window-y="24"
+ inkscape:current-layer="svg3266" />
+ <defs
+ id="defs3268">
+ <linearGradient
+ id="linearGradient8838">
+ <stop
+ id="stop8840"
+ style="stop-color:#000000;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop8842"
+ style="stop-color:#000000;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2490">
+ <stop
+ id="stop2492"
+ style="stop-color:#791235;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2494"
+ style="stop-color:#dd3b27;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3242">
+ <stop
+ id="stop3244"
+ style="stop-color:#f8b17e;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3246"
+ style="stop-color:#e35d4f;stop-opacity:1"
+ offset="0.26238" />
+ <stop
+ id="stop3248"
+ style="stop-color:#c6262e;stop-opacity:1"
+ offset="0.66093999" />
+ <stop
+ id="stop3250"
+ style="stop-color:#690b54;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3202">
+ <stop
+ id="stop3204"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3206"
+ style="stop-color:#d3eefc;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3230">
+ <stop
+ id="stop3232"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3234"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3230"
+ id="linearGradient2408"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,0.9999998)"
+ x1="26.153599"
+ y1="4.9999995"
+ x2="26.153599"
+ y2="44.233311" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3202"
+ id="linearGradient2411"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0395395,0,0,0.9504295,2.4213254,1.5862551)"
+ x1="22.885227"
+ y1="17.628952"
+ x2="22.885227"
+ y2="30.889549" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3242"
+ id="radialGradient2415"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0,1.6958206,-1.7757718,0,30.273358,-47.575099)"
+ cx="32.806725"
+ cy="3.5327499"
+ fx="32.806725"
+ fy="3.5327499"
+ r="23" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2490"
+ id="linearGradient2417"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(51.417219,1.3502817)"
+ x1="-23.916132"
+ y1="43.707703"
+ x2="-23.916132"
+ y2="4.6497173" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient8838"
+ id="radialGradient2420"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2.1647059,0,0,0.8470576,-111.5647,35.082353)"
+ cx="62.625"
+ cy="4.625"
+ fx="62.625"
+ fy="4.625"
+ r="10.625" />
+ </defs>
+ <path
+ style="opacity:0.3;fill:url(#radialGradient2420);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999987999999995;stroke-linecap:butt;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="path8836"
+ d="M 47.000007,39 C 47.000007,43.970562 36.702555,47.999999 24.000007,47.999999 C 11.297459,47.999999 1.0000068,43.970562 1.0000068,39 C 1.0000068,34.029437 11.297459,30 24.000007,30 C 36.702555,30 47.000007,34.029437 47.000007,39 L 47.000007,39 z" />
+ <path
+ style="fill:url(#radialGradient2415);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient2417);stroke-width:0.99999988px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path2421"
+ d="M 1.5000006,31.761226 C 1.5000006,31.761226 10.115608,15.647729 22.458894,9.2294409 C 29.653279,5.4884898 41.244893,4.5285552 46.43013,13.320438 C 46.43013,13.320438 48.176874,36.419951 34.202924,42.922755 C 34.202924,42.922755 19.743769,50.687298 1.5000006,31.761226 z" />
+ <path
+ style="font-size:26.70637703px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#a1ceef;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:URW Gothic L;-inkscape-font-specification:URW Gothic L Book"
+ id="path2438"
+ d="M 16,30.999999 C 17.225378,30.999999 19.774622,30.999999 21,30.999999 C 21.020028,28.309559 20.916511,25.61742 21,22.928239 C 21,21.493793 21.209448,20.211199 22.896799,20.211199 C 24.832644,20.211199 24.986483,21.181016 24.986483,23.02827 C 24.986483,24.716506 24.986483,29.311763 24.986483,30.999999 C 26.21186,30.999999 28.774623,31 30,31 C 30.014374,28.354294 29.941186,25.707605 30,23.062615 C 30.008937,21.790435 29.886674,20.211199 31.992422,20.211199 C 33.88686,20.211199 33.999064,21.457713 33.999064,23.083965 C 33.999064,24.753635 33.999063,29.330328 33.999064,31 C 35.22444,31 37.774622,31 39,31 C 38.985776,28.048126 39.029681,25.095218 38.975706,22.144048 C 38.898843,20.359713 38.212819,18.388221 36.387725,17.484421 C 35.343606,17.14161 35.045063,17.011799 33.742745,17.011799 C 31.060737,17.011799 20.652644,17 16,17 C 16,21.666666 16,26.333334 16,30.999999 z" />
+ <path
+ style="font-size:26.70637703px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient2411);fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:URW Gothic L;-inkscape-font-specification:URW Gothic L Book"
+ id="text3190"
+ d="M 16,31.999999 C 17.225378,31.999999 19.774622,31.999999 21,31.999999 C 21.020028,29.309559 20.916511,26.61742 21,23.928239 C 21,22.493793 21.209448,21.211199 22.896799,21.211199 C 24.832644,21.211199 24.986483,22.181016 24.986483,24.02827 C 24.986483,25.716506 24.986483,30.311763 24.986483,31.999999 C 26.21186,31.999999 28.774623,32 30,32 C 30.014374,29.354294 29.941186,26.707605 30,24.062615 C 30.008937,22.790435 29.886674,21.211199 31.992422,21.211199 C 33.88686,21.211199 33.999064,22.457713 33.999064,24.083965 C 33.999064,25.753635 33.999063,30.330328 33.999064,32 C 35.22444,32 37.774622,32 39,32 C 38.985776,29.048126 39.029681,26.095218 38.975706,23.144048 C 38.898843,21.359713 38.212819,19.388221 36.387725,18.484421 C 35.343606,18.14161 35.045063,18.011799 33.742745,18.011799 C 31.060737,18.011799 20.652644,18 16,18 C 16,22.666666 16,27.333334 16,31.999999 z" />
+ <path
+ style="opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient2408);stroke-width:0.99999988px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path3238"
+ d="M 31.90625,7.4999998 C 28.681328,7.7425666 25.48596,8.7210967 22.90625,10.0625 C 11.707044,15.885887 3.6907335,29.825628 2.71875,31.5625 C 11.484066,40.437302 19.207332,43.094404 24.75,43.5 C 30.407274,43.913983 33.75,42.09375 33.75,42.09375 C 33.760173,42.083092 33.770592,42.072673 33.78125,42.0625 C 40.364812,38.998827 43.337844,31.95008 44.625,25.5 C 45.868154,19.27042 45.525212,13.993747 45.5,13.625 C 42.448467,8.6273583 37.213852,7.1007814 31.90625,7.4999998 z" />
+</svg>
diff --git a/src/Extensions/Banshee.Paas/ThemeIcons/scalable/categories/miroguide-default-channel.svg b/src/Extensions/Banshee.Paas/ThemeIcons/scalable/categories/miroguide-default-channel.svg
new file mode 100644
index 0000000..a2b31c4
--- /dev/null
+++ b/src/Extensions/Banshee.Paas/ThemeIcons/scalable/categories/miroguide-default-channel.svg
@@ -0,0 +1,318 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ width="512"
+ height="512"
+ id="svg2"
+ inkscape:version="0.47pre4 r22446"
+ sodipodi:docname="podcast.svg">
+ <metadata
+ id="metadata78">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1280"
+ inkscape:window-height="689"
+ id="namedview76"
+ showgrid="false"
+ inkscape:zoom="0.859375"
+ inkscape:cx="256"
+ inkscape:cy="256"
+ inkscape:window-x="0"
+ inkscape:window-y="24"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2" />
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient4386">
+ <stop
+ id="stop4388"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="0" />
+ <stop
+ id="stop4390"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="0.60000008" />
+ <stop
+ id="stop4392"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4332">
+ <stop
+ id="stop4334"
+ style="stop-color:#000000;stop-opacity:0"
+ offset="0" />
+ <stop
+ id="stop4336"
+ style="stop-color:#000000;stop-opacity:0"
+ offset="0.60000008" />
+ <stop
+ id="stop4338"
+ style="stop-color:#000000;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3308-4-6-931-761">
+ <stop
+ id="stop2919"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2921"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2490-654-721">
+ <stop
+ id="stop2913"
+ style="stop-color:#545454;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2915"
+ style="stop-color:#a4a4a4;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3242-52-665">
+ <stop
+ id="stop2903"
+ style="stop-color:#d3d3d3;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2905"
+ style="stop-color:#b1b1b1;stop-opacity:1"
+ offset="0.26238" />
+ <stop
+ id="stop2907"
+ style="stop-color:#8e8e8e;stop-opacity:1"
+ offset="0.66093999" />
+ <stop
+ id="stop2909"
+ style="stop-color:#525252;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ cx="256"
+ cy="541.36218"
+ r="255"
+ fx="256"
+ fy="541.36218"
+ id="radialGradient4189"
+ xlink:href="#linearGradient3308-4-6-931-761"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0,0.11202816,-1.6231217,0,1134.6968,512.68298)" />
+ <radialGradient
+ cx="256"
+ cy="540.36218"
+ r="256"
+ fx="256"
+ fy="540.36218"
+ id="radialGradient4192"
+ xlink:href="#linearGradient3242-52-665"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0,2.2078871,-2.3371329,0,1518.8982,-24.856905)" />
+ <linearGradient
+ x1="96.581818"
+ y1="1052.8691"
+ x2="96.581818"
+ y2="540.36218"
+ id="linearGradient4194"
+ xlink:href="#linearGradient2490-654-721"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ color-interpolation-filters="sRGB"
+ id="filter4196">
+ <feGaussianBlur
+ id="feGaussianBlur4198"
+ stdDeviation="2.53" />
+ </filter>
+ <radialGradient
+ cx="-206.96829"
+ cy="173.49565"
+ r="33.625023"
+ fx="-206.96829"
+ fy="173.49565"
+ id="radialGradient4330"
+ xlink:href="#linearGradient4332"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(4.8817448,0,0,5.549978,1266.3664,-4.3531002)" />
+ <radialGradient
+ cx="-206.96829"
+ cy="173.49565"
+ r="33.625023"
+ fx="-206.96829"
+ fy="173.49565"
+ id="radialGradient4330-9"
+ xlink:href="#linearGradient4386"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(4.8817431,-2.0310808e-8,2.3379004e-8,5.6191901,1266.3661,-16.36111)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3242-52-665"
+ id="radialGradient2907"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0,2.2078871,-2.3371329,0,1518.8982,-24.856905)"
+ cx="256"
+ cy="540.36218"
+ fx="256"
+ fy="540.36218"
+ r="256" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2490-654-721"
+ id="linearGradient2909"
+ gradientUnits="userSpaceOnUse"
+ x1="96.581818"
+ y1="1052.8691"
+ x2="96.581818"
+ y2="540.36218" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3308-4-6-931-761"
+ id="radialGradient2911"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0,0.11202816,-1.6231217,0,1134.6968,512.68298)"
+ cx="256"
+ cy="541.36218"
+ fx="256"
+ fy="541.36218"
+ r="255" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4386"
+ id="radialGradient2913"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(4.8817431,-2.0310808e-8,2.3379004e-8,5.6191901,1266.3661,-16.36111)"
+ cx="-206.96829"
+ cy="173.49565"
+ fx="-206.96829"
+ fy="173.49565"
+ r="33.625023" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4332"
+ id="radialGradient2915"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(4.8817448,0,0,5.549978,1266.3664,-4.3531002)"
+ cx="-206.96829"
+ cy="173.49565"
+ fx="-206.96829"
+ fy="173.49565"
+ r="33.625023" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3308-4-6-931-761"
+ id="radialGradient2926"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0,0.11202816,-1.6231217,0,1134.6968,-27.6792)"
+ cx="256"
+ cy="541.36218"
+ fx="256"
+ fy="541.36218"
+ r="255" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3242-52-665"
+ id="radialGradient2929"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0,2.2078871,-2.3371329,0,1518.8982,-565.21908)"
+ cx="256"
+ cy="540.36218"
+ fx="256"
+ fy="540.36218"
+ r="256" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2490-654-721"
+ id="linearGradient2931"
+ gradientUnits="userSpaceOnUse"
+ x1="96.581818"
+ y1="1052.8691"
+ x2="96.581818"
+ y2="540.36218"
+ gradientTransform="translate(0,-540.36218)" />
+ </defs>
+ <rect
+ width="511"
+ height="511"
+ rx="8"
+ ry="8"
+ x="0.5"
+ y="0.50000262"
+ id="rect2816"
+ style="color:#000000;fill:url(#radialGradient2929);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2931);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.06;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <rect
+ width="506"
+ height="506"
+ rx="7.9999995"
+ ry="7.9999995"
+ x="3"
+ y="3.0000026"
+ id="rect2816-3"
+ style="opacity:0.59999999999999998;color:#000000;fill:none;stroke:url(#radialGradient2926);stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.06000000000000000;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <g
+ transform="translate(0,-538.36218)"
+ id="g4323-4"
+ style="opacity:0.4">
+ <path
+ d="m -216.43637,207.12727 a 32.58182,32.58182 0 1 1 -65.16364,0 32.58182,32.58182 0 1 1 65.16364,0 z"
+ transform="matrix(1.0128348,0,0,1.0128348,508.21433,557.48554)"
+ id="path4200-6"
+ style="color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ d="m 413.14879,796.36216 c 0,85.70454 -70.35792,155.18183 -157.14879,155.18183 -86.79087,0 -157.14879,-69.47729 -157.14879,-155.18183 0,-85.70456 70.35792,-155.18179 157.14879,-155.18179 86.79087,0 157.14879,69.47723 157.14879,155.18179 z m -54.3306,-17.45456 c 0,57.4276 -46.0333,103.98183 -102.81819,103.98183 -56.78494,0 -102.81819,-46.55423 -102.81819,-103.98183 0,-57.42755 46.03325,-103.98179 102.81819,-103.98179 56.78489,0 102.81819,46.55424 102.81819,103.98179 z M 316.9273,767.27123 c 0,33.6492 -27.27807,60.92729 -60.9273,60.92729 -33.64918,0 -60.92725,-27.27809 -60.92725,-60.92729 0,-33.64918 27.27807,-60.92723 60.92725,-60.92723 33.64923,0 60.9273,27.27805 60.9273,60.92723 z"
+ id="path4200-9-4-1-3"
+ style="color:#000000;fill:none;stroke:url(#radialGradient2913);stroke-width:14;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ d="m 255.43754,802.59343 c -0.29561,0 -0.581,0.0276 -0.875,0.0312 -18.6857,0.23681 -34.06588,7.93265 -37,18 l -0.53125,0 c -11.16683,29.24646 13.39985,111.58698 17.75,125.6875 0.004,0.0131 -0.004,0.0184 0,0.0312 0.017,3.20857 2.63105,5.78125 5.84375,5.78125 l 1.09375,0 28.5625,0 1.09375,0 c 3.2127,0 5.82675,-2.57268 5.84375,-5.78125 0.004,-0.0129 -0.004,-0.0181 0,-0.0312 4.35015,-14.10052 28.91683,-96.44104 17.75,-125.6875 l -0.53125,0 c -2.93577,-10.07299 -18.33134,-17.77146 -37.03125,-18 -0.28357,-0.003 -0.55869,-0.0312 -0.84375,-0.0312 -0.18635,0 -0.37678,-10e-4 -0.5625,0 -0.18866,-0.002 -0.37319,0 -0.5625,0 z"
+ id="path4280-5"
+ style="color:#000000;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:10;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ </g>
+ <g
+ id="g4323"
+ style="opacity:0.4"
+ transform="translate(0,-540.36218)">
+ <path
+ d="m -216.43637,207.12727 a 32.58182,32.58182 0 1 1 -65.16364,0 32.58182,32.58182 0 1 1 65.16364,0 z"
+ transform="matrix(1.0128348,0,0,1.0128348,508.21433,557.48554)"
+ id="path4200"
+ style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ d="m 413.14879,796.36216 c 0,85.70454 -70.35792,155.18183 -157.14879,155.18183 -86.79087,0 -157.14879,-69.47729 -157.14879,-155.18183 0,-85.70456 70.35792,-155.18179 157.14879,-155.18179 86.79087,0 157.14879,69.47723 157.14879,155.18179 z m -54.3306,-17.45456 c 0,57.4276 -46.0333,103.98183 -102.81819,103.98183 -56.78494,0 -102.81819,-46.55423 -102.81819,-103.98183 0,-57.42755 46.03325,-103.98179 102.81819,-103.98179 56.78489,0 102.81819,46.55424 102.81819,103.98179 z M 316.9273,767.27123 c 0,33.6492 -27.27807,60.92729 -60.9273,60.92729 -33.64918,0 -60.92725,-27.27809 -60.92725,-60.92729 0,-33.64918 27.27807,-60.92723 60.92725,-60.92723 33.64923,0 60.9273,27.27805 60.9273,60.92723 z"
+ id="path4200-9-4-1"
+ style="color:#000000;fill:none;stroke:url(#radialGradient2915);stroke-width:14;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ d="m 255.43754,802.59343 c -0.29561,0 -0.581,0.0276 -0.875,0.0312 -18.6857,0.23681 -34.06588,7.93265 -37,18 l -0.53125,0 c -11.16683,29.24646 13.39985,111.58698 17.75,125.6875 0.004,0.0131 -0.004,0.0184 0,0.0312 0.017,3.20857 2.63105,5.78125 5.84375,5.78125 l 1.09375,0 28.5625,0 1.09375,0 c 3.2127,0 5.82675,-2.57268 5.84375,-5.78125 0.004,-0.0129 -0.004,-0.0181 0,-0.0312 4.35015,-14.10052 28.91683,-96.44104 17.75,-125.6875 l -0.53125,0 c -2.93577,-10.07299 -18.33134,-17.77146 -37.03125,-18 -0.28357,-0.003 -0.55869,-0.0312 -0.84375,-0.0312 -0.18635,0 -0.37678,-10e-4 -0.5625,0 -0.18866,-0.002 -0.37319,0 -0.5625,0 z"
+ id="path4280"
+ style="color:#000000;fill:#000000;fill-opacity:1;stroke:none;stroke-width:10;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ </g>
+</svg>
diff --git a/src/Libraries/Migo2/Makefile.am b/src/Libraries/Migo2/Makefile.am
new file mode 100644
index 0000000..a5f9db1
--- /dev/null
+++ b/src/Libraries/Migo2/Makefile.am
@@ -0,0 +1,55 @@
+ASSEMBLY = Migo2
+TARGET = library
+LINK = $(REF_MIGO2)
+SOURCES = \
+ Migo2.Async/AsyncStateManager.cs \
+ Migo2.Async/CommandQueue/CommandDelegate.cs \
+ Migo2.Async/CommandQueue/CommandQueue.cs \
+ Migo2.Async/CommandQueue/CommandWrapper.cs \
+ Migo2.Async/CommandQueue/EventWrapper.cs \
+ Migo2.Async/CommandQueue/ICommand.cs \
+ Migo2.Async/Task/CancellationType.cs \
+ Migo2.Async/Task/IWaitableTask.cs \
+ Migo2.Async/Task/Task.cs \
+ Migo2.Async/Task/TaskCompletedEventArgs.cs \
+ Migo2.Async/Task/TaskEventArgs.cs \
+ Migo2.Async/Task/TaskState.cs \
+ Migo2.Async/Task/TaskStateChangedEventArgs.cs \
+ Migo2.Async/TaskGroup/EventArgs/GroupStatusChangedEventArgs.cs \
+ Migo2.Async/TaskGroup/EventArgs/ManipulatedEventArgs.cs \
+ Migo2.Async/TaskGroup/EventArgs/ReorderedEventArgs.cs \
+ Migo2.Async/TaskGroup/EventArgs/TaskAddedEventArgs.cs \
+ Migo2.Async/TaskGroup/EventArgs/TaskProgressChangedEventArgs.cs \
+ Migo2.Async/TaskGroup/EventArgs/TaskRemovedEventArgs.cs \
+ Migo2.Async/TaskGroup/GroupProgressManager.cs \
+ Migo2.Async/TaskGroup/GroupStatusManager.cs \
+ Migo2.Async/TaskGroup/TaskGroup.cs \
+ Migo2.Async/TaskGroup/TaskGroup_Collection.cs \
+ Migo2.Collections/OrderComparer.cs \
+ Migo2.Collections/Pair.cs \
+ Migo2.DownloadService/DownloadStatusManager.cs \
+ Migo2.DownloadService/DownloadTaskStatusUpdatedEventArgs.cs \
+ Migo2.DownloadService/HttpDownloadGroup.cs \
+ Migo2.DownloadService/HttpDownloadGroupStatusChangedEventArgs.cs \
+ Migo2.DownloadService/HttpDownloadManager.cs \
+ Migo2.DownloadService/HttpFileDownloadErrors.cs \
+ Migo2.DownloadService/HttpFileDownloadTask.cs \
+ Migo2.Net/AsyncWebClient/AsyncWebClient.cs \
+ Migo2.Net/AsyncWebClient/DownloadDataCompletedEventArgs.cs \
+ Migo2.Net/AsyncWebClient/DownloadProgressChangedEventArgs.cs \
+ Migo2.Net/AsyncWebClient/DownloadStatus.cs \
+ Migo2.Net/AsyncWebClient/DownloadStatusUpdatedEventArgs.cs \
+ Migo2.Net/AsyncWebClient/DownloadStringCompletedEventArgs.cs \
+ Migo2.Net/AsyncWebClient/RemoteFileModifiedException.cs \
+ Migo2.Net/AsyncWebClient/TransferStatusManager.cs \
+ Migo2.Net/AsyncWebClient/TransferStatusManager_Rate.cs \
+ Migo2.Utils/Rfc822DateTime.cs \
+ Migo2.Utils/UnitUtils.cs \
+ Migo2.Utils/XmlUtils.cs
+
+if ENABLE_PAAS
+include $(top_srcdir)/build/build.mk
+else
+EXTRA_DIST = $(SOURCES) $(RESOURCES)
+endif
+
diff --git a/src/Libraries/Migo2/Migo2.Async/AsyncStateManager.cs b/src/Libraries/Migo2/Migo2.Async/AsyncStateManager.cs
new file mode 100644
index 0000000..d2dbacc
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/AsyncStateManager.cs
@@ -0,0 +1,127 @@
+//
+// AsyncStateManager.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Migo2.Async
+{
+ public class AsyncStateManager
+ {
+ private bool busy;
+ private bool completed;
+ private bool cancelled;
+ private bool timedout;
+
+ private readonly object sync = new object ();
+
+ public bool Busy {
+ get { lock (sync) { return busy; } }
+ }
+
+ public bool Cancelled {
+ get { lock (sync) { return cancelled; } }
+ }
+
+ public bool Completed {
+ get { lock (sync) { return completed; } }
+ }
+
+ public bool Timedout {
+ get { lock (sync) { return timedout; } }
+ }
+
+ public AsyncStateManager ()
+ {
+ }
+
+ public void Reset ()
+ {
+ lock (sync) {
+ busy = false;
+ completed = false;
+ cancelled = false;
+ timedout = false;
+ }
+ }
+
+ public void ResetBusy () { lock (sync) { busy = false; } }
+ public void ResetCancelled () { lock (sync) { cancelled = false; } }
+ public void ResetCompleted () { lock (sync) { completed = false; } }
+ public void ResetTimedOut () { lock (sync) { timedout = false; } }
+
+ public bool SetBusy ()
+ {
+ lock (sync) {
+ if (busy) {
+ throw new InvalidOperationException (
+ "Concurrent operations are not supported. Sorry."
+ );
+ } else {
+ busy = true;
+ return true;
+ }
+ }
+ }
+
+ public bool SetCancelled ()
+ {
+ lock (sync) {
+ if (!cancelled && !completed && !timedout) {
+ cancelled = true;
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ public bool SetCompleted ()
+ {
+ lock (sync) {
+ if (busy && !cancelled && !completed && !timedout) {
+ completed = true;
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ public bool SetTimedout ()
+ {
+ lock (sync) {
+ if (busy && !cancelled && !completed && !timedout) {
+ timedout = true;
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/CommandQueue/CommandDelegate.cs b/src/Libraries/Migo2/Migo2.Async/CommandQueue/CommandDelegate.cs
new file mode 100644
index 0000000..82798c1
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/CommandQueue/CommandDelegate.cs
@@ -0,0 +1,32 @@
+//
+// CommandDelegate.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Migo2.Async
+{
+ public delegate void CommandDelegate ();
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/CommandQueue/CommandQueue.cs b/src/Libraries/Migo2/Migo2.Async/CommandQueue/CommandQueue.cs
new file mode 100644
index 0000000..2ce0143
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/CommandQueue/CommandQueue.cs
@@ -0,0 +1,268 @@
+//
+// CommandQueue.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Threading;
+using System.Collections.Generic;
+
+namespace Migo2.Async
+{
+ delegate void ExecuteCommand (ICommand command);
+
+ public class CommandQueue : IDisposable
+ {
+ private bool disposed;
+ private bool executing;
+
+ private Thread queueThread;
+ private Queue<ICommand> eventQueue;
+ private RegisteredWaitHandle registeredHandle;
+ private AutoResetEvent are = new AutoResetEvent (false);
+ private ManualResetEvent executingHandle = new ManualResetEvent (true);
+
+ private readonly ExecuteCommand execCommand;
+
+ private readonly object userSync;
+ private readonly object sync = new object ();
+
+ public EventHandler<EventArgs> QueueProcessed;
+ public EventHandler<EventArgs> QueueProcessing;
+
+ private bool IsProcessed {
+ get {
+ bool ret = false;
+
+ lock (sync) {
+ if (eventQueue.Count == 0) {
+ ret = true;
+ }
+ }
+
+ return ret;
+ }
+ }
+
+ public virtual WaitHandle Handle {
+ get {
+ return executingHandle;
+ }
+ }
+
+ public CommandQueue () : this (null)
+ {
+ }
+
+ public CommandQueue (object sync)
+ {
+ userSync = sync;
+
+ if (userSync == null) {
+ execCommand = delegate (ICommand command) {
+ command.Execute ();
+ };
+ } else {
+ execCommand = delegate (ICommand command) {
+ lock (userSync) {
+ command.Execute ();
+ }
+ };
+ }
+
+ eventQueue = new Queue<ICommand> ();
+ registeredHandle = ThreadPool.RegisterWaitForSingleObject (are, ProcessEventQueue, null, -1, false);
+ }
+
+ public virtual void Dispose ()
+ {
+ if (queueThread == Thread.CurrentThread) {
+ throw new InvalidOperationException ("Cannot call 'Dispose' from a command queue thread.");
+ }
+
+ executingHandle.WaitOne ();
+
+ if (SetDisposed ()) {
+ if (registeredHandle != null) {
+ registeredHandle.Unregister (null);
+ registeredHandle = null;
+ }
+
+ if (are != null) {
+ are.Close ();
+ are = null;
+ }
+
+ if (executingHandle != null) {
+ executingHandle.Close ();
+ executingHandle = null;
+ }
+
+ eventQueue = null;
+ }
+ }
+
+ public virtual bool Register (CommandDelegate d)
+ {
+ return Register (new CommandWrapper (d));
+ }
+
+ public virtual bool Register (ICommand command)
+ {
+ lock (sync) {
+ if (disposed) {
+ return false;
+ }
+
+ return Register (command, true);
+ }
+ }
+
+ protected virtual bool Register (ICommand command, bool pumpQueue)
+ {
+ if (command == null) {
+ throw new ArgumentNullException ("command");
+ }
+
+ eventQueue.Enqueue (command);
+
+ if (!executing && pumpQueue) {
+ SetExecuting (true);
+ }
+
+ return true;
+ }
+
+ public virtual bool Register (IEnumerable<ICommand> commands)
+ {
+ if (commands == null) {
+ throw new ArgumentNullException ("commands");
+ }
+
+ lock (sync) {
+ if (disposed) {
+ return false;
+ }
+
+ foreach (ICommand c in commands) {
+ Register (c, false);
+ }
+
+ if (!executing) {
+ SetExecuting (true);
+ }
+ }
+
+ return true;
+ }
+
+ protected virtual void SetExecuting (bool exec)
+ {
+ if (exec) {
+ executing = true;
+ are.Set ();
+ executingHandle.Reset ();
+ } else {
+ executing = false;
+ executingHandle.Set ();
+ }
+ }
+
+ protected virtual bool SetDisposed ()
+ {
+ bool ret = false;
+
+ lock (sync) {
+ if (!disposed) {
+ ret = disposed = true;
+ }
+ }
+
+ return ret;
+ }
+
+ protected virtual void ProcessEventQueue (object state, bool timedOut)
+ {
+ ICommand e;
+ bool done = false;
+
+ queueThread = Thread.CurrentThread;
+
+ while (!done) {
+ RaiseEvent (QueueProcessing);
+
+ while (true) {
+ lock (sync) {
+ e = eventQueue.Dequeue ();
+ if (disposed) {
+ throw new InvalidOperationException (
+ String.Format ("{0}: tis' executing whilist disposed.", GetType ().Name)
+ );
+ }
+ }
+
+ if (e != null) {
+ try {
+ execCommand (e);
+ } catch (Exception ex) {
+ Console.WriteLine (ex.Message);
+ Console.WriteLine (ex.StackTrace);
+ //Hyena.Log.Exception (ex);
+ }
+ }
+
+ if (IsProcessed) {
+ RaiseEvent (QueueProcessed);
+ done = true;
+ }
+
+ if (done) {
+ lock (sync) {
+ if (eventQueue.Count == 0) {
+ SetExecuting (false);
+ } else {
+ done = false;
+ }
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ private void RaiseEvent (EventHandler<EventArgs> eve)
+ {
+ if (eve != null) {
+ try {
+ eve (this, new EventArgs ());
+ } catch (Exception ex) {
+ Console.WriteLine (ex.Message);
+ //Hyena.Log.Exception (ex);
+ }
+ }
+ }
+ }
+}
+
diff --git a/src/Libraries/Migo2/Migo2.Async/CommandQueue/CommandWrapper.cs b/src/Libraries/Migo2/Migo2.Async/CommandQueue/CommandWrapper.cs
new file mode 100644
index 0000000..35618f7
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/CommandQueue/CommandWrapper.cs
@@ -0,0 +1,50 @@
+//
+// CommandWrapper.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Migo2.Async
+{
+ public class CommandWrapper : ICommand
+ {
+ protected readonly CommandDelegate d;
+
+ public CommandWrapper (CommandDelegate del)
+ {
+ if (del == null) {
+ throw new ArgumentNullException ("del");
+ }
+
+ this.d = del;
+ }
+
+
+ public void Execute ()
+ {
+ d ();
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/CommandQueue/EventWrapper.cs b/src/Libraries/Migo2/Migo2.Async/CommandQueue/EventWrapper.cs
new file mode 100644
index 0000000..c5e0f9a
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/CommandQueue/EventWrapper.cs
@@ -0,0 +1,56 @@
+//
+// EventWrapper.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Migo2.Async
+{
+ public class EventWrapper<T> : ICommand where T : EventArgs
+ {
+ private readonly T e;
+ private readonly object sender;
+ private readonly EventHandler<T> handler;
+
+ public EventWrapper (EventHandler<T> handler, object sender, T e)
+ {
+ if (handler == null) {
+ throw new ArgumentNullException ("handler");
+ } else if (e == null) {
+ throw new ArgumentNullException ("e");
+ }
+
+ this.e = e;
+ this.sender = sender;
+ this.handler = handler;
+ }
+
+
+ public void Execute ()
+ {
+ handler (sender, e);
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/CommandQueue/ICommand.cs b/src/Libraries/Migo2/Migo2.Async/CommandQueue/ICommand.cs
new file mode 100644
index 0000000..d33fb6a
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/CommandQueue/ICommand.cs
@@ -0,0 +1,33 @@
+//
+// ICommand.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+namespace Migo2.Async
+{
+ public interface ICommand
+ {
+ void Execute ();
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/Task/CancellationType.cs b/src/Libraries/Migo2/Migo2.Async/Task/CancellationType.cs
new file mode 100644
index 0000000..54bc267
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/Task/CancellationType.cs
@@ -0,0 +1,38 @@
+//
+// CancellationType.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Migo2.Async
+{
+ public enum CancellationType : int
+ {
+ None = 0,
+ Aborted, // Cancel task, remove persistent task data (if any), task is marked as completed, remove task from group.
+ Paused, // Cancel task, keep persistent task data (if any), leave task in group.
+ Stopped, // Cancel task, keep persistent task data (if any), task is marked as completed, remove task from group.
+ }
+}
\ No newline at end of file
diff --git a/src/Libraries/Migo2/Migo2.Async/Task/IWaitableTask.cs b/src/Libraries/Migo2/Migo2.Async/Task/IWaitableTask.cs
new file mode 100644
index 0000000..2a7322f
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/Task/IWaitableTask.cs
@@ -0,0 +1,36 @@
+//
+// IWaitableTask.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Threading;
+
+namespace Migo2.Async
+{
+ public interface IWaitableTask
+ {
+ WaitHandle WaitHandle { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Libraries/Migo2/Migo2.Async/Task/Task.cs b/src/Libraries/Migo2/Migo2.Async/Task/Task.cs
new file mode 100644
index 0000000..a8e1308
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/Task/Task.cs
@@ -0,0 +1,393 @@
+//
+// Task.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Threading;
+using System.ComponentModel;
+
+namespace Migo2.Async
+{
+ public abstract class Task
+ {
+ private bool busy;
+ private bool completed;
+ private TaskState state;
+
+ private int progress;
+ private string name;
+ private object userState;
+
+ private CancellationType cancellationType;
+ private CancellationType requestedCancellationType;
+
+ private Guid groupID;
+ private CommandQueue commandQueue;
+
+ private readonly object syncRoot = new object ();
+
+ public EventHandler<TaskEventArgs> Started;
+ public EventHandler<TaskCompletedEventArgs> Completed;
+
+ public EventHandler<TaskEventArgs> Updated; // General update, name, other properties for re-draw.
+
+ public EventHandler<TaskStateChangedEventArgs> StateChanged;
+ public EventHandler<ProgressChangedEventArgs> ProgressChanged;
+
+ public bool IsBusy
+ {
+ get {
+ lock (syncRoot) {
+ return busy;
+ }
+ }
+ }
+
+ public bool IsFinished
+ {
+ get {
+ lock (syncRoot) {
+ return completed;
+ }
+ }
+ }
+
+ protected CancellationType RequestedCancellationType
+ {
+ get { return requestedCancellationType; }
+ }
+
+ public CommandQueue EventQueue
+ {
+ get {
+ return commandQueue;
+ }
+
+ set {
+ lock (syncRoot) {
+ commandQueue = value;
+ }
+ }
+ }
+
+ public string Name
+ {
+ get {
+ return name;
+ }
+
+ set {
+ lock (syncRoot) {
+ if (value != name) {
+ name = value;
+ OnUpdated ();
+ }
+ }
+ }
+ }
+
+ public int Progress
+ {
+ get {
+ return progress;
+ }
+
+ protected set {
+ SetProgress (value);
+ }
+ }
+
+ public virtual TaskState State {
+ get {
+ lock (syncRoot) {
+ return state;
+ }
+ }
+
+ protected set {
+ lock (syncRoot) {
+ SetState (value);
+ }
+ }
+ }
+
+ public object SyncRoot
+ {
+ get {
+ return syncRoot;
+ }
+ }
+
+ public object UserState {
+ get {
+ return userState;
+ }
+ }
+
+ internal Guid GroupID {
+ get {
+ return groupID;
+ }
+
+ set {
+ groupID = value;
+ }
+ }
+
+ protected Task () : this (String.Empty, null)
+ {
+ }
+
+ protected Task (string name, object userState)
+ {
+ progress = 0;
+ GroupID = Guid.Empty;
+ state = TaskState.Ready;
+
+ this.name = name;
+ this.userState = userState;
+ }
+
+ public abstract void CancelAsync ();
+ public abstract void ExecuteAsync ();
+
+ public virtual void StopAsync ()
+ {
+ CancelAsync ();
+ }
+
+ public virtual void PauseAsync ()
+ {
+ throw new NotImplementedException ("PauseAsync");
+ }
+
+ public virtual void ResumeAsync ()
+ {
+ lock (syncRoot) {
+ if (SetResumeRequested ()) {
+ SetState (TaskState.Ready);
+ }
+ }
+ }
+
+ public override string ToString ()
+ {
+ return !String.IsNullOrEmpty (name) ? name : GetType ().ToString ();
+ }
+
+ protected virtual bool SetBusy ()
+ {
+ lock (syncRoot) {
+ if (busy) {
+ throw new InvalidOperationException ("Concurrent operations are not supported.");
+ } else if (completed) {
+ throw new InvalidOperationException ("Task executed previously.");
+ } else if (requestedCancellationType == CancellationType.None) {
+ busy = true;
+ SetState (TaskState.Running);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected virtual bool SetCompleted ()
+ {
+ return SetCompleted (false);
+ }
+
+ protected virtual bool SetCompleted (bool paused)
+ {
+ lock (syncRoot) {
+ if (!completed) {
+ busy = false;
+
+ if (!paused) {
+ completed = true;
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected virtual void SetProgress (int progress)
+ {
+ lock (syncRoot) {
+ if (progress < 0 || progress > 100) {
+ throw new ArgumentOutOfRangeException ("progress");
+ } else if (this.progress != progress) {
+ this.progress = progress;
+ OnProgressChanged (progress);
+ }
+ }
+ }
+
+ protected virtual bool SetRequestedCancellationType (CancellationType type)
+ {
+ lock (syncRoot) {
+ if (requestedCancellationType == CancellationType.None ||
+ requestedCancellationType == CancellationType.Paused) {
+ requestedCancellationType = type;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected virtual bool SetResumeRequested ()
+ {
+ lock (syncRoot) {
+ if (!completed && !busy && cancellationType == CancellationType.Paused) {
+ cancellationType = CancellationType.None;
+ requestedCancellationType = CancellationType.None;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected virtual void SetState (TaskState state)
+ {
+ if (this.state != state) {
+ TaskState oldState = this.state;
+ this.state = state;
+ OnStateChanged (oldState, state);
+ }
+ }
+
+ protected virtual void OnProgressChanged (int progress)
+ {
+ OnProgressChanged (
+ new ProgressChangedEventArgs (progress, userState)
+ );
+ }
+
+ protected virtual void OnProgressChanged (ProgressChangedEventArgs e)
+ {
+ EventHandler<ProgressChangedEventArgs> handler = ProgressChanged;
+
+ if (handler != null) {
+ CommandQueue queue = commandQueue;
+
+ if (queue != null) {
+ queue.Register (delegate { handler (this, e); });
+ } else {
+ ThreadPool.QueueUserWorkItem (delegate { handler (this, e); });
+ }
+ }
+ }
+
+ protected virtual void OnUpdated ()
+ {
+ EventHandler<TaskEventArgs> handler = Updated;
+
+ if (handler != null) {
+ CommandQueue queue = commandQueue;
+
+ if (queue != null) {
+ queue.Register (delegate { handler (this, new TaskEventArgs ()); });
+ } else {
+ ThreadPool.QueueUserWorkItem (delegate { handler (this, new TaskEventArgs ()); });
+ }
+ }
+ }
+
+ protected virtual void OnStarted ()
+ {
+ EventHandler<TaskEventArgs> handler = Started;
+
+ if (handler != null) {
+ CommandQueue queue = commandQueue;
+
+ if (queue != null) {
+ queue.Register (delegate { handler (this, new TaskEventArgs ()); });
+ } else {
+ ThreadPool.QueueUserWorkItem (delegate { handler (this, new TaskEventArgs ()); });
+ }
+ }
+ }
+
+ protected virtual void OnStateChanged (TaskState oldState, TaskState newState)
+ {
+ CommandQueue queue = commandQueue;
+ EventHandler<TaskStateChangedEventArgs> handler = StateChanged;
+
+ if (handler != null) {
+ if (queue != null) {
+ queue.Register (
+ delegate { handler (this, new TaskStateChangedEventArgs (oldState, newState)); }
+ );
+ } else {
+ ThreadPool.QueueUserWorkItem (
+ delegate { handler (this, new TaskStateChangedEventArgs (oldState, newState)); }
+ );
+ }
+ }
+ }
+
+ protected virtual void OnTaskCompleted (Exception error, bool cancelled)
+ {
+ TaskCompletedEventArgs e = null;
+ EventHandler<TaskCompletedEventArgs> handler = Completed;
+
+ lock (syncRoot) {
+ cancellationType = (cancelled) ? requestedCancellationType : CancellationType.None;
+
+ if (error != null) {
+ SetState (TaskState.Failed);
+ } else if (!cancelled) {
+ SetState (TaskState.Succeeded);
+ } else {
+ switch (cancellationType) {
+ case CancellationType.Aborted:
+ SetState (TaskState.Cancelled); break;
+ case CancellationType.Paused:
+ SetState (TaskState.Paused); break;
+ case CancellationType.Stopped:
+ SetState (TaskState.Stopped); break;
+ }
+ }
+
+ e = new TaskCompletedEventArgs (error, state, userState);
+ }
+
+ if (handler != null) {
+ CommandQueue queue = commandQueue;
+
+ if (queue != null) {
+ queue.Register (delegate { handler (this, e); });
+ } else {
+ ThreadPool.QueueUserWorkItem (delegate { handler (this, e); });
+ }
+ }
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/Task/TaskCompletedEventArgs.cs b/src/Libraries/Migo2/Migo2.Async/Task/TaskCompletedEventArgs.cs
new file mode 100644
index 0000000..7b75db0
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/Task/TaskCompletedEventArgs.cs
@@ -0,0 +1,90 @@
+//
+// TaskCompletedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Migo2.Async
+{
+ public class TaskCompletedEventArgs : EventArgs
+ {
+ private readonly TaskState state;
+ private readonly Exception error;
+ private readonly object userState;
+
+ public bool Cancelled {
+ get { return (state == TaskState.Cancelled || state == TaskState.Stopped); }
+ }
+
+ public TaskState State
+ {
+ get { return state; }
+ }
+
+ public Exception Error
+ {
+ get { return error; }
+ }
+
+ public object UserState {
+ get {
+ return userState;
+ }
+ }
+
+ public TaskCompletedEventArgs (Exception error, TaskState state)
+ : this (error, state, null)
+ {
+ }
+
+ public TaskCompletedEventArgs (Exception error, TaskState state, object userState)
+ {
+ this.error = error;
+ this.state = state;
+ this.userState = userState;
+ }
+ }
+
+ public class TaskCompletedEventArgs<T> : TaskCompletedEventArgs where T : Task
+ {
+ private readonly T task;
+
+ public T Task {
+ get { return task; }
+ }
+
+ public TaskCompletedEventArgs (T task, TaskCompletedEventArgs args)
+ : base (args.Error, args.State, args.UserState)
+ {
+ if (task == null) {
+ throw new ArgumentNullException ("task");
+ } else if (args == null) {
+ throw new ArgumentNullException ("args");
+ }
+
+ this.task = task;
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/Task/TaskEventArgs.cs b/src/Libraries/Migo2/Migo2.Async/Task/TaskEventArgs.cs
new file mode 100644
index 0000000..fab768d
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/Task/TaskEventArgs.cs
@@ -0,0 +1,56 @@
+//
+// TaskEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Migo2.Async
+{
+ public class TaskEventArgs : EventArgs
+ {
+ public TaskEventArgs ()
+ {
+ }
+ }
+
+ public class TaskEventArgs<T> : TaskEventArgs where T : Task
+ {
+ private readonly T task;
+
+ public T Task {
+ get { return task; }
+ }
+
+ public TaskEventArgs (T task)
+ : base ()
+ {
+ if (task == null) {
+ throw new ArgumentNullException ("task");
+ }
+
+ this.task = task;
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/Task/TaskState.cs b/src/Libraries/Migo2/Migo2.Async/Task/TaskState.cs
new file mode 100644
index 0000000..63359a5
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/Task/TaskState.cs
@@ -0,0 +1,46 @@
+//
+// TaskState.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Migo2.Async
+{
+ [Flags]
+ public enum TaskState
+ {
+ Zero = 0x0000,
+ None = 0x0001,
+ Ready = 0x0002,
+ Running = 0x0004,
+ Paused = 0x0008,
+ Stopped = 0x0016,
+ Failed = 0x0032,
+ Cancelled = 0x0064,
+ Succeeded = 0x0128,
+ CanCancel = (Ready | Running | Paused),
+ CanPause = (Ready | Running)
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/Task/TaskStateChangedEventArgs.cs b/src/Libraries/Migo2/Migo2.Async/Task/TaskStateChangedEventArgs.cs
new file mode 100644
index 0000000..800be91
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/Task/TaskStateChangedEventArgs.cs
@@ -0,0 +1,73 @@
+//
+// TaskStateChangedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Migo2.Async
+{
+ public class TaskStateChangedEventArgs : EventArgs
+ {
+ private readonly TaskState old_state;
+ private readonly TaskState new_state;
+
+ public TaskState OldState
+ {
+ get { return old_state; }
+ }
+
+ public TaskState NewState
+ {
+ get { return new_state; }
+ }
+
+ public TaskStateChangedEventArgs (TaskState oldState, TaskState newState)
+ {
+ old_state = oldState;
+ new_state = newState;
+ }
+ }
+
+ public class TaskStateChangedEventArgs<T> : TaskStateChangedEventArgs where T : Task
+ {
+ private readonly T task;
+
+ public T Task {
+ get { return task; }
+ }
+
+ public TaskStateChangedEventArgs (T task, TaskStateChangedEventArgs args)
+ : base (args.OldState, args.NewState)
+ {
+ if (task == null) {
+ throw new ArgumentNullException ("task");
+ } else if (args == null) {
+ throw new ArgumentNullException ("args");
+ }
+
+ this.task = task;
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/GroupStatusChangedEventArgs.cs b/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/GroupStatusChangedEventArgs.cs
new file mode 100644
index 0000000..a614f25
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/GroupStatusChangedEventArgs.cs
@@ -0,0 +1,58 @@
+//
+// GroupStatusChangedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Migo2.Async
+{
+ public class GroupStatusChangedEventArgs : EventArgs
+ {
+ private readonly int remainingTasks;
+ private readonly int runningTasks;
+ private readonly int completedTasks;
+
+ public int RemainingTasks {
+ get { return remainingTasks; }
+ }
+
+ public int RunningTasks {
+ get { return runningTasks; }
+ }
+
+ public int CompletedTasks {
+ get { return completedTasks; }
+ }
+
+ public GroupStatusChangedEventArgs (int remainingTasks,
+ int runningTasks,
+ int completedTasks)
+ {
+ this.remainingTasks = remainingTasks;
+ this.runningTasks = runningTasks;
+ this.completedTasks = completedTasks;
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/ManipulatedEventArgs.cs b/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/ManipulatedEventArgs.cs
new file mode 100644
index 0000000..e54a8dd
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/ManipulatedEventArgs.cs
@@ -0,0 +1,78 @@
+//
+// ManipulatedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+namespace Migo2.Async
+{
+ public class ManipulatedEventArgs<T> : EventArgs
+ {
+ private T task;
+ private ICollection<T> tasks;
+
+ public T Task {
+ get { return task; }
+
+ protected internal set {
+ TestCombination (value, tasks);
+ task = value;
+ }
+ }
+
+ public ICollection<T> Tasks {
+ get { return tasks; }
+
+ protected internal set {
+ TestCombination (task, value);
+ tasks = value;
+ }
+ }
+
+ protected internal ManipulatedEventArgs () {}
+
+ protected ManipulatedEventArgs (T task, ICollection<T> tasks)
+ {
+ TestCombination (task, tasks);
+ this.task = task;
+ this.tasks = tasks;
+ }
+
+ private void TestCombination (T task, ICollection<T> tasks)
+ {
+ if (task != null && tasks != null) {
+ throw new ArgumentException (
+ "Either task or tasks must be null"
+ );
+ } else if (task == null && tasks == null) {
+ throw new InvalidOperationException (
+ "Both task and tasks may not be null"
+ );
+ }
+ }
+ }
+}
+
diff --git a/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/ReorderedEventArgs.cs b/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/ReorderedEventArgs.cs
new file mode 100644
index 0000000..1d6c7be
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/ReorderedEventArgs.cs
@@ -0,0 +1,46 @@
+//
+// ReorderedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Migo2.Async
+{
+ public class ReorderedEventArgs : EventArgs
+ {
+ private readonly int[] order;
+
+ public int[] NewOrder
+ {
+ get { return order; }
+ }
+
+ // order[i] = old position of element previously located at position i;
+ public ReorderedEventArgs (int[] order)
+ {
+ this.order = order;
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/TaskAddedEventArgs.cs b/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/TaskAddedEventArgs.cs
new file mode 100644
index 0000000..f53888b
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/TaskAddedEventArgs.cs
@@ -0,0 +1,81 @@
+//
+// TaskAddedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Migo2.Collections;
+
+namespace Migo2.Async
+{
+ public class TaskAddedEventArgs<T> : ManipulatedEventArgs<T> where T : Task
+ {
+ private readonly Pair<int,T> taskPair;
+ private readonly ICollection<Pair<int,T>> taskPairs;
+
+ public Pair<int,T> TaskPair
+ {
+ get { return taskPair; }
+ }
+
+ // All indices are listed in ascending order from the start of the
+ // list so that in order addition will not affect indices.
+ public ICollection<Pair<int,T>> TaskPairs
+ {
+ get { return taskPairs; }
+ }
+
+ public TaskAddedEventArgs (int pos, T task) : base (task, null)
+ {
+ if (task == null) {
+ throw new ArgumentNullException ("task");
+ }
+
+ this.taskPair = new Pair<int,T> (pos, task);
+ }
+
+ public TaskAddedEventArgs (ICollection<Pair<int,T>> taskPairs)
+ {
+ if (taskPairs == null) {
+ throw new ArgumentNullException ("taskPairs");
+ }
+
+ List<T> tsks = new List<T> (taskPairs.Count);
+
+ foreach (Pair<int,T> kvp in taskPairs) {
+ if (kvp.Second == null) {
+ throw new ArgumentNullException ("No task in tasks may be null");
+ }
+
+ tsks.Add (kvp.Second);
+ }
+
+ this.Tasks = tsks;
+ this.taskPairs = taskPairs;
+ this.taskPair = default (Pair<int,T>);
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/TaskProgressChangedEventArgs.cs b/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/TaskProgressChangedEventArgs.cs
new file mode 100644
index 0000000..fff5b93
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/TaskProgressChangedEventArgs.cs
@@ -0,0 +1,54 @@
+//
+// TaskProgressChangedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.ComponentModel;
+
+namespace Migo2.Async
+{
+ public class TaskProgressChangedEventArgs<T> : ProgressChangedEventArgs where T : Task
+ {
+ private readonly T task;
+
+ public T Task
+ {
+ get { return task; }
+ }
+
+ public TaskProgressChangedEventArgs (T task, int progress) : this (task, progress, null)
+ {
+ }
+
+ public TaskProgressChangedEventArgs (T task, int progress, object userState) : base (progress, userState)
+ {
+ if (task == null) {
+ throw new ArgumentNullException ("task");
+ }
+
+ this.task = task;
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/TaskRemovedEventArgs.cs b/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/TaskRemovedEventArgs.cs
new file mode 100644
index 0000000..1316f1d
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/TaskGroup/EventArgs/TaskRemovedEventArgs.cs
@@ -0,0 +1,79 @@
+//
+// TaskRemovedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Migo2.Collections;
+
+namespace Migo2.Async
+{
+ public class TaskRemovedEventArgs<T> : ManipulatedEventArgs<T> where T : Task
+ {
+ private readonly int index;
+ private readonly IEnumerable<Pair<int,int>> indices;
+
+ public int Index
+ {
+ get { return index; }
+ }
+
+ // All indices are listed in descending order from the end of the
+ // list so that in order removal will not affect indices.
+
+ // int - 0: Index
+ // int - 1: Count
+ public IEnumerable <Pair<int,int>> Indices
+ {
+ get { return indices; }
+ }
+
+ protected TaskRemovedEventArgs (int index, T task,
+ IEnumerable<Pair<int,int>> indices,
+ ICollection<T> tasks)
+ : base (task, tasks)
+ {
+ if (indices == null && index < 0) {
+ throw new InvalidOperationException (
+ "indices may not be null if index is < 0"
+ );
+ }
+
+ this.index = index;
+ this.indices = indices;
+ }
+
+ public TaskRemovedEventArgs (int index, T task)
+ : this (index, task, null, null)
+ {
+ }
+
+ public TaskRemovedEventArgs (IEnumerable<Pair<int,int>> indices, ICollection<T> tasks)
+ : this (-1, null, indices, tasks)
+ {
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/TaskGroup/GroupProgressManager.cs b/src/Libraries/Migo2/Migo2.Async/TaskGroup/GroupProgressManager.cs
new file mode 100644
index 0000000..285ce4e
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/TaskGroup/GroupProgressManager.cs
@@ -0,0 +1,176 @@
+//
+// GroupProgressManager.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.ComponentModel;
+using System.Collections.Generic;
+
+namespace Migo2.Async
+{
+ public class GroupProgressManager<T> where T : Task
+ {
+ private int progress;
+ private int oldProgress;
+
+ private int totalTicks;
+ private int currentTicks;
+
+ private Dictionary<T, int> progDict;
+
+ public event EventHandler<ProgressChangedEventArgs> ProgressChanged;
+
+ public GroupProgressManager ()
+ {
+ progDict = new Dictionary<T, int> ();
+ }
+
+ public virtual void Add (T task)
+ {
+ Add (task, true);
+ }
+
+ public virtual void Add (IEnumerable<T> tasks)
+ {
+ foreach (T task in tasks) {
+ Add (task, false);
+ }
+
+ OnProgressChanged ();
+ }
+
+ protected virtual void Add (T task, bool update)
+ {
+ if (progDict.ContainsKey (task)) {
+ throw new ArgumentException ("Task was added previously");
+ }
+
+ totalTicks += 100;
+ progDict.Add (task, 0);
+
+ if (update) {
+ OnProgressChanged ();
+ }
+ }
+
+ public virtual void Remove (T task)
+ {
+ Remove (task, true);
+ }
+
+ public virtual void Remove (IEnumerable<T> tasks)
+ {
+ foreach (T task in tasks) {
+ try {
+ Remove (task, false);
+ } catch { continue; }
+ }
+
+ OnProgressChanged ();
+ }
+
+ protected virtual void Remove (T task, bool update)
+ {
+ if (task.Progress == 100) {
+ if (progDict.ContainsKey (task)) {
+ progDict.Remove (task);
+ }
+ } else {
+ int prog = 0;
+
+ if (progDict.ContainsKey (task)) {
+ prog = progDict[task];
+ progDict.Remove (task);
+
+ currentTicks -= prog;
+ totalTicks -= 100;
+ }
+
+ if (update) {
+ OnProgressChanged ();
+ }
+ }
+ }
+
+ public virtual void Reset ()
+ {
+ progress = 0;
+ oldProgress = 0;
+
+ totalTicks = 0;
+ currentTicks = 0;
+
+ progDict.Clear ();
+ }
+
+ public virtual void Update (T task, int newProg)
+ {
+ if (newProg < 0) {
+ throw new ArgumentOutOfRangeException (
+ "newProg must be greater than or equal to 0"
+ );
+ }
+
+ int delta = 0;
+
+ if (progDict.ContainsKey (task)) {
+ int prog = progDict[task];
+
+ if (prog != newProg) {
+ progDict[task] = newProg;
+ delta = newProg - prog;
+ }
+ }
+
+ if (delta != 0) {
+ currentTicks += delta;
+ OnProgressChanged ();
+ }
+ }
+
+ protected virtual void OnProgressChanged ()
+ {
+ if (totalTicks == 0) {
+ progress = 0;
+ } else {
+ progress = Convert.ToInt32 (
+ (currentTicks * 100) / totalTicks
+ );
+ }
+
+ if (progress != oldProgress) {
+ oldProgress = progress;
+
+ EventHandler<ProgressChangedEventArgs> handler = ProgressChanged;
+
+ if (handler != null) {
+ handler (
+ this, new ProgressChangedEventArgs (progress, null)
+ );
+ }
+ }
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/TaskGroup/GroupStatusManager.cs b/src/Libraries/Migo2/Migo2.Async/TaskGroup/GroupStatusManager.cs
new file mode 100644
index 0000000..3f85430
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/TaskGroup/GroupStatusManager.cs
@@ -0,0 +1,349 @@
+//
+// GroupStatusManager.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Threading;
+using System.Collections.Generic;
+
+namespace Migo2.Async
+{
+ public class GroupStatusManager : IDisposable
+ {
+ private bool disposed;
+ private bool suspendUpdate;
+
+ private int runningTasks;
+ private int completedTasks;
+ private int remainingTasks;
+ private int maxRunningTasks = 0;
+
+ private ManualResetEvent mre;
+ private List<Task> pendingTasks; // Should be a Dictionary
+
+ public event EventHandler<GroupStatusChangedEventArgs> StatusChanged;
+
+ public virtual int CompletedTasks
+ {
+ get {
+ CheckDisposed ();
+ return completedTasks;
+ }
+ }
+
+ public virtual int RunningTasks
+ {
+ get {
+ CheckDisposed ();
+ return runningTasks;
+ }
+ }
+
+ public virtual bool SuspendUpdate
+ {
+ get {
+ CheckDisposed ();
+ return suspendUpdate;
+ }
+
+ set {
+ CheckDisposed ();
+ suspendUpdate = value;
+ }
+ }
+
+ public virtual int RemainingTasks
+ {
+ get {
+ CheckDisposed ();
+ return remainingTasks;
+ }
+
+ set {
+ CheckDisposed ();
+ SetRemainingTasks (value);
+ }
+ }
+
+ public virtual int MaxRunningTasks
+ {
+ get {
+ CheckDisposed ();
+ return maxRunningTasks;
+ }
+
+ set {
+ CheckDisposed ();
+ SetMaxRunningTasks (value);
+ }
+ }
+
+ public virtual WaitHandle Handle {
+ get {
+ CheckDisposed ();
+ return mre;
+ }
+ }
+
+ public GroupStatusManager () : this (0, 0)
+ {
+ }
+
+ public GroupStatusManager (int totalTasks) : this (totalTasks, 0)
+ {
+ }
+
+ public GroupStatusManager (int maxRunningTasks, int totalTasks)
+ {
+ pendingTasks = new List<Task> (maxRunningTasks);
+ mre = new ManualResetEvent (false);
+
+ SetRemainingTasks (totalTasks);
+ SetMaxRunningTasks (maxRunningTasks);
+ }
+
+ public virtual void Dispose ()
+ {
+ if (!disposed) {
+ disposed = true;
+
+ if (mre != null) {
+ mre.Close ();
+ mre = null;
+ }
+ }
+ }
+
+ public virtual bool DropPendingTask (Task task)
+ {
+ if (task == null) {
+ throw new ArgumentNullException ("task");
+ }
+
+ return pendingTasks.Remove (task);
+ }
+
+ public virtual bool RegisterPendingTask (Task task)
+ {
+ if (task == null) {
+ throw new ArgumentNullException ("task");
+ }
+
+ if (!pendingTasks.Contains (task)) {
+ pendingTasks.Add (task);
+ return true;
+ }
+
+ return false;
+ }
+
+ public virtual int IncrementCompletedTaskCount ()
+ {
+ CheckDisposed ();
+
+ ++completedTasks;
+ OnStatusChanged ();
+
+ return completedTasks;
+ }
+
+ public virtual int DecrementCompletedTaskCount ()
+ {
+ CheckDisposed ();
+
+ if (completedTasks == 0) {
+ throw new InvalidOperationException ("Completed task count cannot be less than 0");
+ }
+
+ --completedTasks;
+ OnStatusChanged ();
+
+ return completedTasks;
+ }
+
+ public virtual int IncrementRunningTaskCount ()
+ {
+ CheckDisposed ();
+
+ if (runningTasks >= remainingTasks) {
+ throw new InvalidOperationException ("Running task count cannot be > remaining task count");
+ }
+
+ ++runningTasks;
+ OnStatusChanged ();
+
+ Evaluate ();
+
+ return runningTasks;
+ }
+
+ public virtual int DecrementRunningTaskCount ()
+ {
+ CheckDisposed ();
+
+ if (runningTasks == 0) {
+ throw new InvalidOperationException ("Runing task count cannot be less than 0");
+ }
+
+ --runningTasks;
+ OnStatusChanged ();
+ Evaluate ();
+
+ return runningTasks;
+ }
+
+ public virtual int IncrementRemainingTaskCount ()
+ {
+ CheckDisposed ();
+
+ SetRemainingTasks (remainingTasks + 1);
+ Evaluate ();
+
+ return remainingTasks;
+ }
+
+ public virtual int DecrementRemainingTaskCount ()
+ {
+ CheckDisposed ();
+
+ if (remainingTasks == 0) {
+ throw new InvalidOperationException ("Remaining task count cannot be less than 0");
+ }
+
+ SetRemainingTasks (remainingTasks - 1);
+ Evaluate ();
+
+ return remainingTasks;
+ }
+
+ public virtual void ResetWait ()
+ {
+ CheckDisposed ();
+ mre.Reset ();
+ }
+
+ public virtual void Reset ()
+ {
+ completedTasks = 0;
+ suspendUpdate = false;
+ }
+
+ public virtual bool SetRemainingTasks (int newRemainingTasks)
+ {
+ CheckDisposed ();
+ if (newRemainingTasks < 0) {
+ throw new ArgumentException ("newRemainingTasks must be >= 0");
+ }
+
+ bool ret = false;
+
+ if (remainingTasks != newRemainingTasks) {
+ ret = true;
+ remainingTasks = newRemainingTasks;
+ Evaluate ();
+ OnStatusChanged ();
+ }
+
+ return ret;
+ }
+
+ public virtual void Update ()
+ {
+ CheckDisposed ();
+ OnStatusChanged ();
+ Evaluate ();
+ }
+
+ public virtual void Wait ()
+ {
+ CheckDisposed ();
+ mre.WaitOne ();
+ }
+
+ protected virtual void CheckDisposed ()
+ {
+ if (disposed) {
+ throw new ObjectDisposedException (GetType ().FullName);
+ }
+ }
+
+ public virtual void Evaluate ()
+ {
+ if (suspendUpdate) {
+ return;
+ }
+/*
+ if ((remainingTasks == 0 && maxRunningTasks > 0) ||
+ (runningTasks < maxRunningTasks && (remainingTasks - runningTasks) > 0)) {
+ mre.Set ();
+ }
+*/
+ int pendingAndRunningTasks = pendingTasks.Count + runningTasks;
+
+ if ((remainingTasks == 0 && maxRunningTasks > 0) ||
+ (pendingAndRunningTasks < maxRunningTasks && (remainingTasks - pendingAndRunningTasks) > 0)) {
+ mre.Set ();
+ }
+ }
+
+ protected virtual void SetMaxRunningTasks (int newMaxTasks)
+ {
+ CheckDisposed ();
+
+ if (newMaxTasks < 0) {
+ throw new ArgumentException ("newMaxTasks must be >= 0");
+ }
+
+ if (maxRunningTasks != newMaxTasks) {
+ maxRunningTasks = newMaxTasks;
+ Evaluate ();
+ }
+ }
+
+ protected virtual void OnStatusChanged (GroupStatusChangedEventArgs e)
+ {
+ if (suspendUpdate) {
+ return;
+ }
+
+ EventHandler<GroupStatusChangedEventArgs> handler = StatusChanged;
+
+ if (handler != null) {
+ handler (this, e);
+ }
+ }
+
+ protected virtual void OnStatusChanged ()
+ {
+ if (suspendUpdate) {
+ return;
+ }
+
+ OnStatusChanged (
+ new GroupStatusChangedEventArgs (remainingTasks, runningTasks, completedTasks)
+ );
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/TaskGroup/TaskGroup.cs b/src/Libraries/Migo2/Migo2.Async/TaskGroup/TaskGroup.cs
new file mode 100644
index 0000000..04b1478
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/TaskGroup/TaskGroup.cs
@@ -0,0 +1,690 @@
+//
+// TaskGroup.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Threading;
+using System.ComponentModel;
+
+using System.Collections;
+using System.Collections.Generic;
+
+using Migo2.Collections;
+
+namespace Migo2.Async
+{
+ public partial class TaskGroup<T> where T : Task
+ {
+ private bool disposed;
+ private bool is_disposing;
+
+ private bool executing;
+ private bool cancelRequested;
+
+ private List<T> currentTasks;
+ private CommandQueue commandQueue;
+
+ private GroupStatusManager gsm;
+ private GroupProgressManager<T> gpm;
+
+ private ManualResetEvent mre;
+
+ private readonly Guid id;
+ private readonly object sync = new object ();
+
+ public event EventHandler<TaskEventArgs<T>> TaskStarted;
+ public event EventHandler<TaskCompletedEventArgs<T>> TaskCompleted;
+
+ public event EventHandler<TaskEventArgs<T>> TaskUpdated;
+ public event EventHandler<TaskStateChangedEventArgs<T>> TaskStateChanged;
+ public event EventHandler<TaskProgressChangedEventArgs<T>> TaskProgressChanged;
+
+ public event EventHandler<EventArgs> Started;
+ public event EventHandler<EventArgs> Stopped;
+ public event EventHandler<ProgressChangedEventArgs> ProgressChanged;
+ public event EventHandler<GroupStatusChangedEventArgs> StatusChanged;
+
+ public virtual int CompletedTasks
+ {
+ get {
+ lock (sync) {
+ CheckDisposed ();
+ return gsm.CompletedTasks;
+ }
+ }
+ }
+
+ protected virtual bool IsDisposed
+ {
+ get {
+ lock (sync) { return disposed; }
+ }
+ }
+
+ protected virtual bool IsDisposing
+ {
+ get {
+ lock (sync) { return disposed | is_disposing; }
+ }
+ }
+
+ public virtual bool IsBusy
+ {
+ get {
+ lock (sync) {
+ CheckDisposed ();
+ return executing;
+ }
+ }
+ }
+
+ public virtual int RunningTasks
+ {
+ get {
+ lock (sync) {
+ CheckDisposed ();
+ return gsm.RunningTasks;
+ }
+ }
+ }
+
+ public virtual int RemainingTasks
+ {
+ get {
+ lock (sync) {
+ CheckDisposed ();
+ return gsm.RemainingTasks;
+ }
+ }
+ }
+
+ public WaitHandle Handle {
+ get { return mre; }
+ }
+
+ protected CommandQueue EventQueue
+ {
+ get { return commandQueue; }
+ }
+
+ protected List<T> CurrentTasks {
+ get { return currentTasks; }
+ }
+
+ protected GroupProgressManager<T> ProgressManager
+ {
+ get { return gpm; }
+ set { SetProgressManager (value); }
+ }
+
+ protected GroupStatusManager StatusManager
+ {
+ get {
+ if (gsm == null) {
+ SetStatusManager (new GroupStatusManager ());
+ }
+
+ return gsm;
+ }
+
+ set { SetStatusManager (value); }
+ }
+
+ public int MaxRunningTasks
+ {
+ get {
+ lock (sync) {
+ CheckDisposed ();
+ return gsm.MaxRunningTasks;
+ }
+ }
+
+ set {
+ lock (sync) {
+ CheckDisposed ();
+ gsm.MaxRunningTasks = value;
+ }
+ }
+ }
+
+ public object SyncRoot
+ {
+ get { return sync; }
+ }
+
+ private bool IsDone
+ {
+ get { return (gsm.RemainingTasks == 0); }
+ }
+
+ public TaskGroup (int maxRunningTasks)
+ : this (maxRunningTasks, null, null)
+ {
+ }
+
+ protected TaskGroup (int maxRunningTasks, GroupStatusManager statusManager)
+ : this (maxRunningTasks, statusManager, null)
+ {
+ }
+
+ protected TaskGroup (int maxRunningTasks,
+ GroupStatusManager statusManager,
+ GroupProgressManager<T> progressManager)
+ {
+ if (maxRunningTasks < 0) {
+ throw new ArgumentException ("maxRunningTasks must be >= 0");
+ }
+
+ mre = new ManualResetEvent (true);
+
+ InitTaskGroup_Collection ();
+ currentTasks = new List<T> (maxRunningTasks);
+
+ id = Guid.NewGuid ();
+ commandQueue = new CommandQueue ();
+
+ SetStatusManager (statusManager ?? new GroupStatusManager ());
+ SetProgressManager (progressManager ?? new GroupProgressManager<T> ());
+
+ gsm.MaxRunningTasks = maxRunningTasks;
+ }
+
+ public virtual void CancelAsync ()
+ {
+ if (SetCancelled ()) {
+ lock (sync) {
+ foreach (T task in list) {
+ task.CancelAsync ();
+ }
+ }
+ }
+ }
+
+ public virtual void StopAsync ()
+ {
+ if (SetCancelled ()) {
+ lock (sync) {
+ foreach (T task in list) {
+ task.StopAsync ();
+ }
+ }
+ }
+ }
+
+ public virtual void Dispose ()
+ {
+ if (SetDisposing ()) {
+ commandQueue.Dispose ();
+
+ if (SetDisposed ()) {
+ DisposeImpl ();
+ }
+ }
+ }
+
+ // Most subclasses will only need to override DisposeImpl
+ protected virtual void DisposeImpl ()
+ {
+ gsm.StatusChanged -= OnStatusChangedHandler;
+ gpm.ProgressChanged -= OnProgressChangedHandler;
+
+ if (gsm is IDisposable) {
+ gsm.Dispose ();
+ }
+
+ mre.Close ();
+
+ gpm = null;
+ gsm = null;
+ mre = null;
+ }
+
+ public void Execute ()
+ {
+ if (SetExecuting (true)) {
+ OnStarted ();
+ SpawnExecutionThread ();
+ }
+ }
+
+ protected virtual bool SetCancelled ()
+ {
+ lock (sync) {
+ CheckDisposed ();
+
+ if (executing && !cancelRequested) {
+ cancelRequested = true;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected bool SetDisposed ()
+ {
+ lock (sync) {
+ if (executing) {
+ throw new InvalidOperationException ("Unable to dispose while executing");
+ }
+
+ if (!disposed) {
+ disposed = true;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected bool SetDisposing ()
+ {
+ lock (SyncRoot) {
+ // It's ok for sub-classes to call this in their dispose methods too.
+ // Thank you past man, you saved me a lot of time.
+ if (!disposed) {
+ is_disposing = true;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected virtual bool SetExecuting (bool exec)
+ {
+ lock (sync) {
+ CheckDisposed ();
+
+ if (exec) {
+ if (!executing && !cancelRequested) {
+ executing = true;
+ return true;
+ }
+ } else {
+ executing = false;
+ cancelRequested = false;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected virtual void SetProgressManager (GroupProgressManager<T> progressManager)
+ {
+ CheckDisposed ();
+
+ if (progressManager == null) {
+ throw new ArgumentNullException ("progressManager");
+ } else if (gpm != null) {
+ throw new InvalidOperationException ("ProgressManager already set");
+ }
+
+ gpm = progressManager;
+ gpm.ProgressChanged += OnProgressChangedHandler;
+ }
+
+ protected virtual void SetStatusManager (GroupStatusManager statusManager)
+ {
+ CheckDisposed ();
+
+ if (statusManager == null) {
+ throw new ArgumentNullException ("statusManager");
+ } else if (gsm != null) {
+ throw new InvalidOperationException ("StatusManager already set");
+ }
+
+ gsm = statusManager;
+ gsm.StatusChanged += OnStatusChangedHandler;
+ }
+
+ protected virtual void CheckDisposed ()
+ {
+ if (disposed) {
+ throw new ObjectDisposedException (GetType ().FullName);
+ }
+ }
+
+ protected virtual void Associate (T task)
+ {
+ Associate (task, true);
+ }
+
+ protected virtual void Associate (IEnumerable<T> tasks)
+ {
+ foreach (T task in tasks) {
+ if (task != null) {
+ Associate (task, false);
+ }
+ }
+
+ gpm.Add (tasks);
+ }
+
+ protected virtual void Associate (T task, bool addToProgressGroup)
+ {
+ CheckDisposed ();
+
+ if (task.GroupID != Guid.Empty) {
+ throw new ApplicationException ("task: Already associated with a group");
+ }
+
+ task.GroupID = id;
+ task.EventQueue = commandQueue;
+
+ task.Started += OnTaskStartedHandler;
+ task.Completed += OnTaskCompletedHandler;
+
+ task.Updated += OnTaskUpdatedHandler;
+ task.StateChanged += OnTaskStateChangedHandler;
+ task.ProgressChanged += OnTaskProgressChangedHandler;
+
+ if (addToProgressGroup) {
+ gpm.Add (task);
+ }
+ }
+
+ protected virtual bool CheckID (T task)
+ {
+ return (task.GroupID == id);
+ }
+
+ protected virtual void Disassociate (IEnumerable<T> tasks)
+ {
+ foreach (T task in tasks) {
+ Disassociate (task, false);
+ }
+
+ gpm.Remove (tasks);
+ }
+
+ protected virtual void Disassociate (T task)
+ {
+ Disassociate (task, true);
+ }
+
+ protected virtual void Disassociate (T task, bool removeFromGroup)
+ {
+ if (CheckID (task)) {
+ task.EventQueue = null;
+ task.GroupID = Guid.Empty;
+
+ task.Started -= OnTaskStartedHandler;
+ task.Completed -= OnTaskCompletedHandler;
+
+ task.Updated -= OnTaskUpdatedHandler;
+ task.StateChanged -= OnTaskStateChangedHandler;
+ task.ProgressChanged -= OnTaskProgressChangedHandler;
+
+ if (removeFromGroup) {
+ list.Remove (task);
+ gpm.Remove (task);
+ }
+ }
+ }
+
+ protected virtual void Reset ()
+ {
+ lock (sync) {
+ gsm.Reset ();
+ gpm.Reset ();
+ }
+ }
+
+ protected virtual void OnStarted ()
+ {
+ OnTaskGroupStarted ();
+ mre.Reset ();
+ }
+
+ protected virtual void OnStopped ()
+ {
+ OnTaskGroupStopped ();
+ mre.Set ();
+ }
+
+ protected virtual void OnTaskStarted (T task)
+ {
+ OnTaskEvent (task, TaskStarted);
+ }
+
+ protected virtual void OnTaskCompleted (T task, TaskCompletedEventArgs e)
+ {
+ EventHandler<TaskCompletedEventArgs<T>> handler = TaskCompleted;
+
+ if (handler != null) {
+ commandQueue.Register (
+ new EventWrapper<TaskCompletedEventArgs<T>> (
+ handler, this, new TaskCompletedEventArgs<T> (task, e)
+ )
+ );
+ }
+ }
+
+ protected virtual void OnTaskStateChanged (T task, TaskStateChangedEventArgs e)
+ {
+ EventHandler<TaskStateChangedEventArgs<T>> handler = TaskStateChanged;
+
+ if (handler != null) {
+ commandQueue.Register (
+ new EventWrapper<TaskStateChangedEventArgs<T>> (
+ handler, this, new TaskStateChangedEventArgs<T> (task, e)
+ )
+ );
+ }
+ }
+
+ protected virtual void OnTaskStateChangedHandler (object sender, TaskStateChangedEventArgs e)
+ {
+ lock (sync) {
+ if (e.OldState == TaskState.Paused && e.NewState == TaskState.Ready) {
+ gsm.Evaluate ();
+ }
+
+ OnTaskStateChanged (sender as T, e);
+ }
+ }
+
+ protected virtual void OnTaskUpdatedHandler (object sender, TaskEventArgs e)
+ {
+ OnTaskEvent (sender as T, TaskUpdated);
+ }
+
+ protected virtual void OnStatusChangedHandler (object sender, GroupStatusChangedEventArgs e)
+ {
+ lock (sync) {
+ EventHandler<GroupStatusChangedEventArgs> handler = StatusChanged;
+
+ if (handler != null) {
+ commandQueue.Register (new EventWrapper<GroupStatusChangedEventArgs> (handler, this, e));
+ }
+ }
+ }
+
+ protected virtual void OnTaskCompletedHandler (object sender, TaskCompletedEventArgs e)
+ {
+ lock (sync) {
+ T t = sender as T;
+ gsm.SuspendUpdate = true;
+
+ try {
+ gsm.DropPendingTask (t);
+
+ if (currentTasks.Contains (t)) {
+ currentTasks.Remove (t);
+
+ if (e.State != TaskState.Paused && !e.Cancelled) {
+ gsm.IncrementCompletedTaskCount ();
+ }
+
+ gsm.DecrementRunningTaskCount ();
+ }
+ } finally {
+ if (e.State != TaskState.Paused) {
+ gsm.DecrementRemainingTaskCount ();
+ Disassociate (t);
+ }
+
+ OnTaskCompleted (t, e);
+
+ gsm.SuspendUpdate = false;
+ gsm.Update ();
+ }
+ }
+ }
+
+ protected virtual void OnTaskProgressChangedHandler (object sender, ProgressChangedEventArgs e)
+ {
+ EventHandler<TaskProgressChangedEventArgs<T>> handler = TaskProgressChanged;
+
+ lock (sync) {
+ gpm.Update (sender as T, e.ProgressPercentage);
+ }
+
+ if (handler != null) {
+ handler (this, new TaskProgressChangedEventArgs<T> (sender as T, e.ProgressPercentage, e.UserState));
+ }
+ }
+
+ protected virtual void OnTaskStartedHandler (object sender, EventArgs e)
+ {
+ lock (sync) {
+ T task = sender as T;
+
+ currentTasks.Add (task);
+
+ gsm.DropPendingTask (task);
+ gsm.IncrementRunningTaskCount ();
+
+ OnTaskStarted (task);
+ }
+ }
+
+ protected virtual void OnProgressChangedHandler (object sender, ProgressChangedEventArgs e)
+ {
+ lock (sync) {
+ EventHandler<ProgressChangedEventArgs> handler = ProgressChanged;
+
+ if (handler != null) {
+ commandQueue.Register (
+ new EventWrapper<ProgressChangedEventArgs> (handler, this, e)
+ );
+ }
+ }
+ }
+
+ private void OnTaskGroupStarted ()
+ {
+ EventHandler<EventArgs> handler = Started;
+
+ if (handler != null) {
+ commandQueue.Register (
+ new EventWrapper<EventArgs> (handler, this, EventArgs.Empty)
+ );
+ }
+ }
+
+ private void OnTaskGroupStopped ()
+ {
+ EventHandler<EventArgs> handler = Stopped;
+
+ if (handler != null) {
+ commandQueue.Register (
+ new EventWrapper<EventArgs> (handler, this, EventArgs.Empty)
+ );
+ }
+ }
+
+ private void OnTaskEvent (T task, EventHandler<TaskEventArgs<T>> eventHandler)
+ {
+ if (eventHandler != null) {
+ commandQueue.Register (
+ new EventWrapper<TaskEventArgs<T>> (eventHandler, this, new TaskEventArgs<T> (task))
+ );
+ }
+ }
+
+ private void PumpQueue ()
+ {
+ try {
+ PumpQueueImpl ();
+ } catch (Exception e) {
+ Console.WriteLine (e.Message);
+ Console.WriteLine (e.StackTrace);
+ throw;
+ }
+ }
+
+ private void PumpQueueImpl ()
+ {
+ T task;
+
+ while (true) {
+ gsm.Wait ();
+
+ lock (sync) {
+ gsm.ResetWait ();
+
+ if (IsDone) {
+ if (SetExecuting (false)) {
+ Reset ();
+ OnStopped ();
+ return;
+ }
+ } else if (cancelRequested) {
+ continue;
+ }
+
+ task = list.Find (
+ delegate (T t) {
+ return (!t.IsBusy && !t.IsFinished && t.State == TaskState.Ready);
+ }
+ );
+
+ if (task != null) {
+ try {
+ if (gsm.RegisterPendingTask (task)) {
+ task.ExecuteAsync ();
+ }
+ } catch /*(Exception e)*/ {
+ // Add an exception logging system that allows Hyena.Logging to hook in.
+ // Implementations of 'ExecuteAsync' should never throw an exception.
+ gsm.DropPendingTask (task);
+ }
+ }
+ }
+ }
+ }
+
+ private void SpawnExecutionThread ()
+ {
+ Thread t = new Thread (new ThreadStart (PumpQueue));
+ t.Priority = ThreadPriority.Normal;
+ t.IsBackground = true;
+ t.Start ();
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Async/TaskGroup/TaskGroup_Collection.cs b/src/Libraries/Migo2/Migo2.Async/TaskGroup/TaskGroup_Collection.cs
new file mode 100644
index 0000000..6f145a4
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Async/TaskGroup/TaskGroup_Collection.cs
@@ -0,0 +1,536 @@
+//
+// TaskGroup_Collection.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Migo2.Collections;
+
+namespace Migo2.Async
+{
+ public partial class TaskGroup<T> where T : Task
+ {
+ private List<T> list;
+
+ public EventHandler<ReorderedEventArgs> Reordered;
+ public EventHandler<TaskAddedEventArgs<T>> TaskAdded;
+
+ public int Count
+ {
+ get {
+ lock (sync) {
+ return list.Count;
+ }
+ }
+ }
+
+ protected ICollection<T> Tasks
+ {
+ get { return list; }
+ }
+
+ private void InitTaskGroup_Collection ()
+ {
+ list = new List<T> ();
+ }
+
+ public T this [int index]
+ {
+ get {
+ lock (sync) {
+ CheckDisposed ();
+ CheckIndex (index);
+ return list[index];
+ }
+ }
+
+ set {
+ lock (sync) {
+ CheckDisposed ();
+ CheckIndex (index);
+ list[index] = value;
+ }
+ }
+ }
+
+ public void Add (T task)
+ {
+ CheckTask (task);
+
+ lock (sync) {
+ CheckDisposed ();
+
+ if (!CanAdd ()) {
+ return;
+ }
+
+ int index = list.Count;
+
+ list.Add (task);
+ Associate (task);
+ gsm.IncrementRemainingTaskCount ();
+
+ OnTaskAdded (index, task);
+ }
+
+ Execute ();
+ }
+
+ public void Add (IEnumerable<T> tasks)
+ {
+ bool execute = false;
+ CheckTasks (tasks);
+
+ lock (sync) {
+ CheckDisposed ();
+
+ if (!CanAdd ()) {
+ return;
+ }
+
+ int index = list.Count;
+ ICollection<Pair<int,T>> coll = CreatePairs (index, tasks);
+
+ if (coll.Count > 0) {
+ execute = true;
+ list.AddRange (tasks);
+ Associate (tasks);
+ gsm.RemainingTasks += coll.Count;
+
+ OnTasksAdded (coll);
+ }
+ }
+
+ if (execute) {
+ Execute ();
+ }
+ }
+
+ public bool Contains (T task)
+ {
+ lock (sync) {
+ CheckDisposed ();
+
+ if (task == null) {
+ return false;
+ } else {
+ lock (sync) {
+ return list.Contains (task);
+ }
+ }
+ }
+ }
+
+ public void CopyTo (T[] array, int index)
+ {
+ lock (sync) {
+ CheckDisposed ();
+ CheckCopyArgs (array, index);
+ list.CopyTo (array, index);
+ }
+ }
+
+ public void CopyTo (Array array, int index)
+ {
+ lock (sync) {
+ CheckDisposed ();
+ CheckCopyArgs (array, index);
+ Array.Copy (list.ToArray (), 0, array, index, list.Count);
+ }
+ }
+
+ // I'd like to make the TaskGroup's SyncRoot private in the future, this would be a problem for GE.
+ public IEnumerator<T> GetEnumerator ()
+ {
+ foreach (T task in list) {
+ yield return task;
+ }
+ }
+
+ public int IndexOf (T task)
+ {
+ lock (sync) {
+ CheckDisposed ();
+
+ if (task == null) {
+ return -1;
+ } else {
+ lock (sync) {
+ return list.IndexOf (task);
+ }
+ }
+ }
+ }
+
+ public void Insert (int index, T task)
+ {
+ CheckTask (task);
+
+ lock (sync) {
+ CheckDisposed ();
+
+ if (!CanAdd ()) {
+ return;
+ }
+
+ CheckDestIndex (index);
+
+ list.Insert (index, task);
+
+ OnTaskAdded (index, task);
+ }
+
+ Execute ();
+ }
+
+ public void InsertRange (int index, IEnumerable<T> tasks)
+ {
+ if (!CanAdd ()) {
+ return;
+ }
+
+ CheckTasks (tasks);
+
+ bool execute = false;
+
+ lock (sync) {
+ CheckDisposed ();
+ CheckDestIndex (index);
+
+ ICollection<Pair<int,T>> coll = CreatePairs (index, tasks);
+
+ if (coll.Count > 0) {
+ list.InsertRange (index, tasks);
+
+ Associate (tasks);
+ gsm.RemainingTasks += coll.Count;
+
+ OnTasksAdded (coll);
+ execute = true;
+ }
+ }
+
+ if (execute) {
+ Execute ();
+ }
+ }
+
+ public void Move (int destIndex, int sourceIndex)
+ {
+ if (sourceIndex == destIndex) {
+ return;
+ }
+
+ lock (sync) {
+ CheckDisposed ();
+
+ if (sourceIndex >= list.Count || sourceIndex < 0) {
+ throw new ArgumentOutOfRangeException ("sourceIndex");
+ } else if (destIndex > list.Count || destIndex < 0) {
+ throw new ArgumentOutOfRangeException ("destIndex");
+ }
+
+ Dictionary<Task,int> oldOrder = SaveOrder ();
+
+ T tmpTask;
+
+ tmpTask = list[sourceIndex];
+ list.RemoveAt (sourceIndex);
+
+ list.Insert (Math.Min (list.Count, destIndex), tmpTask);
+
+ OnReordered (NewOrder (oldOrder));
+ }
+ }
+
+ public void Move (int destIndex, int[] sourceIndices)
+ {
+ if (sourceIndices == null) {
+ throw new ArgumentNullException ("sourceIndices");
+ }
+
+ lock (sync) {
+ CheckDisposed ();
+ int maxIndex = list.Count;
+
+ if (destIndex > maxIndex || destIndex < 0) {
+ throw new ArgumentOutOfRangeException ("destIndex");
+ }
+
+ List<T> tmpList = new List<T> (sourceIndices.Length);
+
+ foreach (int i in sourceIndices) {
+ if (i < 0 || i > maxIndex) {
+ throw new ArgumentOutOfRangeException ("sourceIndices");
+ }
+
+ // A possible performance enhancement is to check for
+ // contiguous regions in the source and remove those regions
+ // at once.
+ tmpList.Add (list[i]);
+ }
+
+ Dictionary<Task,int> oldOrder = SaveOrder ();
+
+ if (tmpList.Count > 0) {
+ int offset = 0;
+ Array.Sort (sourceIndices);
+
+ foreach (int i in sourceIndices)
+ {
+ try {
+ list.RemoveAt (i-offset);
+ } catch { continue; }
+
+ ++offset;
+ }
+
+ list.InsertRange (Math.Min (destIndex, list.Count), tmpList);
+ OnReordered (NewOrder (oldOrder));
+ }
+ }
+ }
+
+ public void Move (int destIndex, T task)
+ {
+ if (task == null) {
+ throw new ArgumentNullException ("task");
+ }
+
+ int index;
+
+ lock (sync) {
+ CheckDisposed ();
+ index = list.IndexOf (task);
+
+ if (index != -1) {
+ Move (destIndex, index);
+ }
+ }
+ }
+
+ public void Move (int destIndex, IEnumerable<T> tasks)
+ {
+ if (tasks == null) {
+ throw new ArgumentNullException ("tasks");
+ }
+
+ int index;
+ List<int> indices = new List<int> ();
+
+ lock (sync) {
+ CheckDisposed ();
+
+ foreach (T task in tasks) {
+ index = list.IndexOf (task);
+ if (index != -1) {
+ indices.Add (index);
+ }
+ }
+
+ if (indices.Count > 0) {
+ Move (destIndex, indices.ToArray ());
+ }
+ }
+ }
+
+ public void Reverse ()
+ {
+ lock (sync) {
+ CheckDisposed ();
+ Dictionary<Task,int> oldOrder = SaveOrder ();
+ list.Reverse ();
+ OnReordered (NewOrder (oldOrder));
+ }
+ }
+
+ protected virtual IEnumerable<Pair<int,int>> DetectContinuity (int[] indices)
+ {
+ if (indices == null) {
+ throw new ArgumentNullException ("indices");
+ } else if (indices.Length == 0) {
+ return null;
+ }
+
+ int cnt;
+ int len = indices.Length;
+ List<Pair<int,int>> ret = new List<Pair<int,int>>();
+
+ int i = len-1;
+
+ while (i > 0) {
+ cnt = 1;
+ while (indices[i] == indices[i-1]+1)
+ {
+ ++cnt;
+ if (--i == 0) {
+ break;
+ }
+ }
+
+ ret.Add (new Pair<int,int>(indices[i--], cnt));
+ }
+
+ return ret;
+ }
+
+ protected virtual void OnReordered (int[] newOrder)
+ {
+ EventHandler<ReorderedEventArgs> handler = Reordered;
+
+ if (handler != null) {
+ ReorderedEventArgs e = new ReorderedEventArgs (newOrder);
+ commandQueue.Register (new EventWrapper<ReorderedEventArgs> (handler, this, e));
+ }
+ }
+
+ protected virtual void OnTaskAdded (int pos, T task)
+ {
+ EventHandler<TaskAddedEventArgs<T>> handler = TaskAdded;
+
+ if (handler != null) {
+ TaskAddedEventArgs<T> e = new TaskAddedEventArgs<T> (pos, task);
+ commandQueue.Register (new EventWrapper<TaskAddedEventArgs<T>> (handler, this, e));
+ }
+ }
+
+ protected virtual void OnTasksAdded (ICollection<Pair<int,T>> pairs)
+ {
+ EventHandler<TaskAddedEventArgs<T>> handler = TaskAdded;
+
+ if (handler != null) {
+ TaskAddedEventArgs<T> e = new TaskAddedEventArgs<T> (pairs);
+ commandQueue.Register (new EventWrapper<TaskAddedEventArgs<T>> (handler, this, e));
+ }
+ }
+
+ private void CheckCopyArgs (Array array, int index)
+ {
+ if (array == null) {
+ throw new ArgumentNullException ("array");
+ } else if (index < 0) {
+ throw new ArgumentOutOfRangeException (
+ "Value of index must be greater than or equal to 0"
+ );
+ } else if (array.Rank > 1) {
+ throw new ArgumentException (
+ "array must not be multidimensional"
+ );
+ } else if (index >= array.Length) {
+ throw new ArgumentException (
+ "index exceeds array length"
+ );
+ } else if (list.Count > (array.Length-index)) {
+ throw new ArgumentException (
+ "index and count exceed length of array"
+ );
+ }
+ }
+
+ private bool CanAdd ()
+ {
+ if (disposed) {
+ throw new ObjectDisposedException (GetType ().ToString ());
+ } else if (cancelRequested) {
+ throw new OperationCanceledException ();
+ }
+
+ return true;
+ }
+
+ private void CheckDestIndex (int index)
+ {
+ if (index < 0 || index > list.Count) {
+ throw new ArgumentOutOfRangeException ("index");
+ }
+ }
+
+ private void CheckIndex (int index)
+ {
+ if (index < 0 || index >= list.Count) {
+ throw new ArgumentOutOfRangeException ("index");
+ }
+ }
+
+ private void CheckTask (T task)
+ {
+ if (task == null) {
+ throw new ArgumentNullException ("task");
+ }
+ }
+
+ private void CheckTasks (IEnumerable<T> tasks)
+ {
+ if (tasks == null) {
+ throw new ArgumentNullException ("tasks");
+ }
+
+ foreach (Task t in tasks) {
+ if (t == null) {
+ throw new ArgumentException (
+ "No task in tasks may be null"
+ );
+ }
+ }
+ }
+
+ private ICollection<Pair<int,T>> CreatePairs (int index, IEnumerable<T> tasks)
+ {
+ List<Pair<int,T>> pairs = new List<Pair<int,T>> ();
+
+ foreach (T task in tasks) {
+ pairs.Add (new Pair<int,T> (index++, task));
+ }
+
+ return pairs;
+ }
+
+ private int[] NewOrder (Dictionary<Task,int> oldOrder)
+ {
+ int i = -1;
+ int[] newOrder = new int[list.Count];
+
+ foreach (Task t in list) {
+ newOrder[++i] = oldOrder[t];
+ }
+
+ return newOrder;
+ }
+
+ private Dictionary<Task,int> SaveOrder ()
+ {
+ Dictionary<Task,int> ret = new Dictionary<Task,int> (list.Count);
+
+ int i = -1;
+
+ foreach (Task t in list) {
+ ret[t] = ++i;
+ }
+
+ return ret;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Libraries/Migo2/Migo2.Collections/OrderComparer.cs b/src/Libraries/Migo2/Migo2.Collections/OrderComparer.cs
new file mode 100644
index 0000000..4052ec8
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Collections/OrderComparer.cs
@@ -0,0 +1,53 @@
+//
+// OrderComparer.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System.Collections.Generic;
+
+namespace Migo2.Collections
+{
+ public class OrderComparer<T> : IComparer<T>
+ {
+ Dictionary<T, int> pos_dict;
+
+ public OrderComparer (Dictionary<T, int> posDict)
+ {
+ pos_dict = posDict;
+ }
+
+ public int Compare (T lhs, T rhs)
+ {
+ int lhs_pos = pos_dict[lhs], rhs_pos = pos_dict[rhs];
+
+ if (lhs_pos < rhs_pos) {
+ return -1;
+ } else if (lhs_pos > rhs_pos) {
+ return 1;
+ }
+
+ return 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Libraries/Migo2/Migo2.Collections/Pair.cs b/src/Libraries/Migo2/Migo2.Collections/Pair.cs
new file mode 100644
index 0000000..bdb66db
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Collections/Pair.cs
@@ -0,0 +1,43 @@
+//
+// Pair.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+namespace Migo2.Collections
+{
+ public class Pair<F,S>
+ {
+ private readonly F first;
+ private readonly S second;
+
+ public F First { get { return first; } }
+ public S Second { get { return second; } }
+
+ public Pair (F first, S second)
+ {
+ this.first = first;
+ this.second = second;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Libraries/Migo2/Migo2.DownloadService/DownloadStatusManager.cs b/src/Libraries/Migo2/Migo2.DownloadService/DownloadStatusManager.cs
new file mode 100644
index 0000000..b4e51f1
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.DownloadService/DownloadStatusManager.cs
@@ -0,0 +1,72 @@
+//
+// DownloadStatusManager.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Timers;
+using System.Collections.Generic;
+
+using Migo2.Async;
+
+namespace Migo2.DownloadService
+{
+ public class DownloadStatusManager : GroupStatusManager
+ {
+ private long bytesPerSecond;
+
+ public DownloadStatusManager () : this (0,0)
+ {
+ }
+
+ public DownloadStatusManager (int totalDownloads, int maxRunningDownloads)
+ : base (totalDownloads, maxRunningDownloads)
+ {
+ }
+
+ public override void Reset ()
+ {
+ bytesPerSecond = 0;
+ base.Reset ();
+ }
+
+ public virtual void SetTransferRate (long bytesPerSecond)
+ {
+ if (this.bytesPerSecond != bytesPerSecond) {
+ this.bytesPerSecond = bytesPerSecond;
+ OnStatusChanged ();
+ }
+ }
+
+ protected override void OnStatusChanged ()
+ {
+ base.OnStatusChanged (
+ new HttpDownloadGroupStatusChangedEventArgs (
+ RemainingTasks, RunningTasks,
+ CompletedTasks, bytesPerSecond
+ ) as GroupStatusChangedEventArgs
+ );
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.DownloadService/DownloadTaskStatusUpdatedEventArgs.cs b/src/Libraries/Migo2/Migo2.DownloadService/DownloadTaskStatusUpdatedEventArgs.cs
new file mode 100644
index 0000000..60d9d78
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.DownloadService/DownloadTaskStatusUpdatedEventArgs.cs
@@ -0,0 +1,53 @@
+//
+// DownloadTaskStatusUpdatedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Migo2.Net;
+
+namespace Migo2.DownloadService
+{
+ public class DownloadTaskStatusUpdatedEventArgs : EventArgs
+ {
+ private readonly DownloadStatus status;
+ private readonly HttpFileDownloadTask task;
+
+ public HttpFileDownloadTask Task
+ {
+ get { return task; }
+ }
+
+ public DownloadStatus Status {
+ get { return status; }
+ }
+
+ public DownloadTaskStatusUpdatedEventArgs (HttpFileDownloadTask task, DownloadStatus status)
+ {
+ this.task = task;
+ this.status = status;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Libraries/Migo2/Migo2.DownloadService/HttpDownloadGroup.cs b/src/Libraries/Migo2/Migo2.DownloadService/HttpDownloadGroup.cs
new file mode 100644
index 0000000..2d39e83
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.DownloadService/HttpDownloadGroup.cs
@@ -0,0 +1,233 @@
+//
+// HttpDownloadGroup.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.IO;
+using System.Timers; // migrate to System.Threading.Timer
+using System.Threading;
+
+using System.ComponentModel;
+using System.Collections.Generic;
+
+using Hyena;
+
+using Migo2.Net;
+using Migo2.Async;
+
+namespace Migo2.DownloadService
+{
+ public class HttpDownloadGroup : TaskGroup<HttpFileDownloadTask>
+ {
+ private long transferRate;
+ private long transferRatePreviously;
+ private long bytesThisInterval = 0;
+
+ private DateTime lastTick;
+ private DownloadStatusManager dsm;
+ private System.Timers.Timer transferTimer;
+
+ private Dictionary<HttpFileDownloadTask,long> transferRateDict;
+
+ public EventHandler<DownloadTaskStatusUpdatedEventArgs> TaskStatusUpdated;
+
+ public HttpDownloadGroup (int maxDownloads) : base (maxDownloads, new DownloadStatusManager ())
+ {
+ dsm = StatusManager as DownloadStatusManager;
+ transferRateDict = new Dictionary<HttpFileDownloadTask,long> (dsm.MaxRunningTasks);
+
+ InitTransferTimer ();
+ }
+
+ // already locked at this point
+ protected override void Associate (HttpFileDownloadTask task, bool addToProgressGroup)
+ {
+ base.Associate (task, addToProgressGroup);
+ task.StatusUpdated += OnDownloadTaskStatusUpdatedHandler;
+ }
+
+ // already locked at this point
+ protected override void Disassociate (HttpFileDownloadTask task, bool removeFromGroup)
+ {
+ base.Disassociate (task, removeFromGroup);
+ task.StatusUpdated -= OnDownloadTaskStatusUpdatedHandler;
+ }
+
+ protected override void DisposeImpl ()
+ {
+ lock (SyncRoot) {
+ if (transferTimer != null) {
+ transferTimer.Enabled = false;
+ transferTimer.Elapsed -= OnTransmissionTimerElapsedHandler;
+ transferTimer.Dispose ();
+ transferTimer = null;
+ }
+
+ base.DisposeImpl ();
+ }
+ }
+
+ protected override void OnStarted ()
+ {
+ lock (SyncRoot) {
+ transferTimer.Enabled = true;
+ lastTick = DateTime.Now;
+ base.OnStarted ();
+ }
+ }
+
+ protected override void OnStopped ()
+ {
+ lock (SyncRoot) {
+ transferTimer.Enabled = false;
+ base.OnStopped ();
+ }
+ }
+
+ protected override void OnTaskStarted (HttpFileDownloadTask task)
+ {
+ lock (SyncRoot) {
+ transferRateDict.Add (task, task.BytesReceived);
+ base.OnTaskStarted (task);
+ }
+ }
+
+ protected override void OnTaskCompleted (HttpFileDownloadTask task, TaskCompletedEventArgs e)
+ {
+ lock (SyncRoot) {
+ if (transferRateDict.ContainsKey (task)) {
+ long bytesLastCheck = transferRateDict[task];
+
+ if (task.BytesReceived > bytesLastCheck) {
+ bytesThisInterval += (task.BytesReceived - bytesLastCheck);
+ }
+
+ transferRateDict.Remove (task);
+ }
+
+ base.OnTaskCompleted (task, e);
+ }
+ }
+
+ protected virtual void SetTransferRate (long bytesPerSecond)
+ {
+ lock (SyncRoot) {
+ dsm.SetTransferRate (bytesPerSecond);
+ }
+ }
+
+ protected override void Reset ()
+ {
+ lastTick = DateTime.Now;
+
+ transferRate = -1;
+ transferRatePreviously = -1;
+
+ base.Reset ();
+ }
+
+ protected virtual void OnTransmissionTimerElapsedHandler (object source, ElapsedEventArgs e)
+ {
+ lock (SyncRoot) {
+ UpdateTransferRate ();
+ }
+ }
+
+ protected virtual void UpdateTransferRate ()
+ {
+ transferRate = CalculateTransferRate ();
+
+ if (transferRatePreviously == 0) {
+ transferRatePreviously = transferRate;
+ }
+
+ transferRate = ((transferRate + transferRatePreviously) / 2);
+
+ SetTransferRate (transferRate);
+ transferRatePreviously = transferRate;
+ }
+
+ private void InitTransferTimer ()
+ {
+ transferTimer = new System.Timers.Timer ();
+
+ transferTimer.Elapsed += OnTransmissionTimerElapsedHandler;
+ transferTimer.Interval = (1.5 * 1000); // 1.5 seconds
+ transferTimer.Enabled = false;
+ }
+
+ private long CalculateTransferRate ()
+ {
+ long bytesPerSecond;
+
+ TimeSpan duration = (DateTime.Now - lastTick);
+ double secondsElapsed = duration.TotalSeconds;
+
+ if ((int)secondsElapsed == 0) {
+ return 0;
+ }
+
+ long tmpCur;
+ long tmpPrev;
+
+ foreach (HttpFileDownloadTask dt in CurrentTasks) {
+ tmpCur = dt.BytesReceived;
+ tmpPrev = transferRateDict[dt];
+ transferRateDict[dt] = tmpCur;
+
+ bytesThisInterval += (tmpCur - tmpPrev);
+ }
+
+ bytesPerSecond = (long) ((bytesThisInterval / secondsElapsed));
+
+ lastTick = DateTime.Now;
+ bytesThisInterval = 0;
+
+ return bytesPerSecond;
+ }
+
+ protected virtual void OnDownloadTaskStatusUpdatedHandler (object sender, DownloadStatusUpdatedEventArgs e)
+ {
+ HttpFileDownloadTask task = sender as HttpFileDownloadTask;
+ OnDownloadTaskStatusUpdated (task, e.Status);
+ }
+
+ protected virtual void OnDownloadTaskStatusUpdated (HttpFileDownloadTask task, DownloadStatus status)
+ {
+ CommandQueue queue = EventQueue;
+ EventHandler<DownloadTaskStatusUpdatedEventArgs> handler = TaskStatusUpdated;
+
+ if (queue != null && handler != null) {
+ queue.Register (delegate { handler (this, new DownloadTaskStatusUpdatedEventArgs (task, status)); });
+ } else if (handler != null) {
+ ThreadPool.QueueUserWorkItem (
+ delegate {
+ handler (this, new DownloadTaskStatusUpdatedEventArgs (task, status));
+ }
+ );
+ }
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.DownloadService/HttpDownloadGroupStatusChangedEventArgs.cs b/src/Libraries/Migo2/Migo2.DownloadService/HttpDownloadGroupStatusChangedEventArgs.cs
new file mode 100644
index 0000000..cb0e9a4
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.DownloadService/HttpDownloadGroupStatusChangedEventArgs.cs
@@ -0,0 +1,49 @@
+//
+// HttpDownloadGroupStatusChangedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Migo2.Async;
+
+namespace Migo2.DownloadService
+{
+ // Way too fucking long.
+ public class HttpDownloadGroupStatusChangedEventArgs : GroupStatusChangedEventArgs
+ {
+ private readonly long bytesPerSecond;
+
+ public long BytesPerSecond
+ {
+ get { return bytesPerSecond; }
+ }
+
+ public HttpDownloadGroupStatusChangedEventArgs (int totalTasks, int runningTasks, int completedTasks, long bytesPerSecond)
+ : base (totalTasks, runningTasks, completedTasks)
+ {
+ this.bytesPerSecond = bytesPerSecond;
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.DownloadService/HttpDownloadManager.cs b/src/Libraries/Migo2/Migo2.DownloadService/HttpDownloadManager.cs
new file mode 100644
index 0000000..0ec4f89
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.DownloadService/HttpDownloadManager.cs
@@ -0,0 +1,164 @@
+//
+// HttpDownloadManager.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.IO;
+using System.Web;
+
+using System.Text;
+using System.Threading;
+using System.ComponentModel;
+using System.Collections.Generic;
+
+using System.Security.Cryptography;
+
+using Migo2.Async;
+using Migo2.Collections;
+
+namespace Migo2.DownloadService
+{
+ public class HttpDownloadManager : HttpDownloadGroup
+ {
+ private string tmp_dir;
+
+ public string TempDownloadDirectory {
+ get {
+ lock (SyncRoot) {
+ return tmp_dir;
+ }
+ }
+
+ set {
+ if (String.IsNullOrEmpty (value)) {
+ return;
+ }
+
+ lock (SyncRoot) {
+ if (IsDisposing) {
+ return;
+ }
+
+ if (!value.EndsWith (Convert.ToString (Path.DirectorySeparatorChar))) {
+ value += Path.DirectorySeparatorChar;
+ }
+
+ if (!Directory.Exists (value)) {
+ try {
+ Directory.CreateDirectory (value);
+ } catch (Exception e) {
+ throw new ApplicationException (String.Format (
+ "Unable to create temporary download directory: {0}",
+ e.Message
+ ));
+ }
+ }
+
+ tmp_dir = value;
+ }
+ }
+ }
+
+ public HttpDownloadManager (int maxDownloads, string tmpDownloadDir) : base (maxDownloads)
+ {
+ TempDownloadDirectory = tmpDownloadDir;
+ }
+
+ public HttpFileDownloadTask CreateDownloadTask (string url)
+ {
+ return CreateDownloadTask (url, null);
+ }
+
+ public HttpFileDownloadTask CreateDownloadTask (string url, object userState)
+ {
+ Uri uri;
+
+ if (String.IsNullOrEmpty (url)) {
+ throw new ArgumentException ("Cannot be null or empty", "url");
+ } else if (!Uri.TryCreate (url, UriKind.Absolute, out uri)) {
+ throw new UriFormatException ("url: Is not a well formed Uri.");
+ }
+
+ string[] segments = uri.Segments;
+ string fileName = segments[segments.Length-1].Trim ('/');
+
+ MD5 hasher = MD5.Create ();
+ byte[] hash = hasher.ComputeHash (Encoding.UTF8.GetBytes (url));
+
+ string urlHash = BitConverter.ToString (hash)
+ .Replace ("-", String.Empty)
+ .ToLower ();
+
+ string localPath = String.Concat (
+ Path.Combine (TempDownloadDirectory, urlHash), Path.DirectorySeparatorChar,
+ Hyena.StringUtil.EscapeFilename (fileName)
+ );
+
+ return new HttpFileDownloadTask (
+ System.Web.HttpUtility.UrlDecode (fileName), url, localPath, userState
+ );
+ }
+
+
+ public override void Dispose ()
+ {
+ if (SetDisposing ()) {
+ StopAsync ();
+ Handle.WaitOne ();
+ base.Dispose ();
+ }
+ }
+
+ public void QueueDownload (HttpFileDownloadTask task)
+ {
+ if (task == null) {
+ throw new ArgumentNullException ("task");
+ }
+
+ lock (SyncRoot) {
+ if (IsDisposing) {
+ return;
+ }
+
+ Add (task);
+ }
+ }
+
+ public void QueueDownload (IEnumerable<HttpFileDownloadTask> tasks)
+ {
+ if (tasks == null) {
+ throw new ArgumentNullException ("tasks");
+ }
+
+ lock (SyncRoot) {
+ if (IsDisposing) {
+ return;
+ }
+
+ Add (tasks);
+ }
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.DownloadService/HttpFileDownloadErrors.cs b/src/Libraries/Migo2/Migo2.DownloadService/HttpFileDownloadErrors.cs
new file mode 100644
index 0000000..f8e1a3f
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.DownloadService/HttpFileDownloadErrors.cs
@@ -0,0 +1,38 @@
+//
+// HttpFileDownloadErrors.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+namespace Migo2.DownloadService
+{
+ // This is stupid.
+ public enum HttpFileDownloadErrors
+ {
+ None = 0,
+ HttpError,
+ SharingViolation,
+ UnauthorizedFileAccess,
+ Unknown
+ }
+}
\ No newline at end of file
diff --git a/src/Libraries/Migo2/Migo2.DownloadService/HttpFileDownloadTask.cs b/src/Libraries/Migo2/Migo2.DownloadService/HttpFileDownloadTask.cs
new file mode 100644
index 0000000..6c8283a
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.DownloadService/HttpFileDownloadTask.cs
@@ -0,0 +1,436 @@
+//
+// HttpFileDownloadTask.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.IO;
+using System.Net;
+using System.Text;
+using System.Threading;
+using System.ComponentModel;
+using System.Security.Cryptography;
+
+using Migo2.Net;
+using Migo2.Async;
+
+namespace Migo2.DownloadService
+{
+ public class HttpFileDownloadTask : Task
+ {
+ private bool preexistingFile;
+
+ private int modified;
+ private int rangeError;
+
+ private string mimeType;
+ private Uri remoteUri;
+ private string localPath;
+
+ private long lastTotalBytes;
+ private long lastTotalBytesReceived;
+
+ private int timeout = (60 * 1000); // One minute
+ private string userAgent;
+
+ private HttpStatusCode httpStatus;
+ private HttpFileDownloadErrors error;
+
+ private AsyncWebClient wc;
+ private FileStream localStream;
+ private ICredentials credentials;
+
+ public EventHandler<DownloadStatusUpdatedEventArgs> StatusUpdated;
+
+ public long BytesReceived {
+ get {
+ lock (SyncRoot) {
+ if (wc != null) {
+ return wc.Status.BytesReceived;
+ }
+ }
+
+ return 0;
+ }
+ }
+
+ public long TotalBytes {
+ get {
+ lock (SyncRoot) {
+ if (wc != null) {
+ if (wc.Status.TotalBytes > 0) {
+ return wc.Status.TotalBytes;
+ }
+ }
+ }
+
+ return lastTotalBytes;
+ }
+ }
+
+ public long TotalBytesReceived {
+ get {
+ lock (SyncRoot) {
+ if (wc != null) {
+ if (wc.Status.TotalBytesReceived > 0) {
+ return wc.Status.TotalBytesReceived;
+ }
+ }
+ }
+
+ return lastTotalBytesReceived;
+ }
+ }
+
+ public long TransferRate {
+ get {
+ lock (SyncRoot) {
+ if (wc != null) {
+ return wc.Status.TransferRate;
+ }
+ }
+
+ return 0;
+ }
+ }
+
+ public ICredentials Credentials {
+ get { return credentials; }
+ set { credentials = value; }
+ }
+
+ public HttpFileDownloadErrors Error
+ {
+ get { return error; }
+ }
+
+ public HttpStatusCode HttpStatusCode
+ {
+ get { return httpStatus; }
+ }
+
+ public string LocalPath
+ {
+ get { return localPath; }
+ }
+
+ public string MimeType
+ {
+ get { return mimeType; }
+ }
+
+ public Uri RemoteUri
+ {
+ get { return remoteUri; }
+ }
+
+ public int Timeout {
+ get { return timeout; }
+ set { timeout = value; }
+ }
+
+ public string UserAgent {
+ get { return userAgent; }
+ set { userAgent = value; }
+ }
+
+ public HttpFileDownloadTask (string name, string remoteUri, string localPath, object userState)
+ : base (!String.IsNullOrEmpty (name) ? name : remoteUri, userState)
+ {
+ this.remoteUri = new Uri (remoteUri);
+ this.localPath = localPath;
+ }
+
+ public HttpFileDownloadTask (string remoteUri, string localPath) : this (null, remoteUri, localPath, null)
+ {
+ }
+
+ public override void CancelAsync ()
+ {
+ Cancel (CancellationType.Aborted);
+ }
+
+ public override void ExecuteAsync ()
+ {
+ lock (SyncRoot) {
+ if (SetBusy ()) {
+ modified = 0;
+ rangeError = 0;
+ preexistingFile = false;
+
+ OnStarted ();
+ ExecuteImpl ();
+ }
+ }
+ }
+
+ public override void PauseAsync ()
+ {
+ Cancel (CancellationType.Paused);
+ }
+
+ public override void StopAsync ()
+ {
+ Cancel (CancellationType.Stopped);
+ }
+
+ private void Cancel (CancellationType type)
+ {
+ lock (SyncRoot) {
+ if (SetRequestedCancellationType (type)) {
+ if (!IsBusy &&
+ !IsFinished &&
+ ((RequestedCancellationType == CancellationType.Paused) ? SetCompleted (true) : SetCompleted ())) {
+ OnTaskCompleted (null, true);
+ } else if (IsBusy) {
+ wc.CancelAsync ();
+ }
+ }
+ }
+ }
+
+ private void DestroyWebClient ()
+ {
+ if (wc != null) {
+ wc.DownloadFileCompleted -= OnDownloadFileCompletedHandler;
+ wc.DownloadProgressChanged -= OnDownloadProgressChangedHandler;
+ wc.ResponseReceived -= OnResponseReceivedHandler;
+ wc = null;
+ }
+ }
+
+ private void CloseLocalStream (bool removeFile)
+ {
+ try {
+ if (localStream != null) {
+ localStream.Close ();
+ localStream = null;
+ }
+ } catch {}
+
+ if (removeFile) {
+ RemoveFile ();
+ }
+ }
+
+ private void ExecuteImpl ()
+ {
+ Exception err = null;
+ bool fileOpenError = false;
+
+ try {
+ OpenLocalStream ();
+ } catch (Exception e) {
+ err = e;
+ fileOpenError = true;
+
+ if (e is UnauthorizedAccessException) {
+ error = HttpFileDownloadErrors.UnauthorizedFileAccess;
+ } else if (e is IOException) {
+ error = HttpFileDownloadErrors.SharingViolation; // Probably
+ } else {
+ error = HttpFileDownloadErrors.Unknown;
+ }
+ }
+
+ if (err == null) {
+ try {
+ InitWebClient ();
+ wc.DownloadFileAsync (remoteUri, localStream);
+ } catch (Exception e) {
+ err = e;
+ }
+ }
+
+ if (err != null) {
+ if (!fileOpenError) {
+ CloseLocalStream (true);
+ }
+
+ if (SetCompleted ()) {
+ DestroyWebClient ();
+ OnTaskCompleted (err, false);
+ }
+ }
+ }
+
+ private void InitWebClient ()
+ {
+ wc = new AsyncWebClient ();
+
+ if (localStream.Length > 0) {
+ wc.Range = Convert.ToInt32 (localStream.Length);
+ }
+
+ wc.Timeout = timeout;
+
+ if (credentials != null) {
+ wc.Credentials = credentials;
+ }
+
+ if (!String.IsNullOrEmpty (userAgent)) {
+ wc.UserAgent = userAgent;
+ }
+
+ wc.DownloadFileCompleted += OnDownloadFileCompletedHandler;
+ wc.DownloadProgressChanged += OnDownloadProgressChangedHandler;
+ wc.ResponseReceived += OnResponseReceivedHandler;
+ wc.StatusUpdated += OnWebClientStatusUpdatedHandler;
+ }
+
+ private void OpenLocalStream ()
+ {
+ if (File.Exists (localPath)) {
+ localStream = File.Open (localPath, FileMode.Append, FileAccess.Write, FileShare.None);
+
+ preexistingFile = true;
+ } else {
+ preexistingFile = false;
+
+ if (!Directory.Exists (Path.GetDirectoryName (localPath))) {
+ Directory.CreateDirectory (Path.GetDirectoryName (localPath));
+ }
+
+ localStream = File.Open (localPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
+ }
+ }
+
+ private void RemoveFile ()
+ {
+ if (File.Exists (localPath)) {
+ try {
+ preexistingFile = false;
+ File.Delete (localPath);
+ Directory.Delete (Path.GetDirectoryName (localPath));
+ } catch {}
+ }
+ }
+
+ private void OnDownloadFileCompletedHandler (object sender, AsyncCompletedEventArgs e)
+ {
+ bool retry = false;
+
+ lock (SyncRoot) {
+ try {
+ if (e.Error != null) {
+ WebException we = e.Error as WebException;
+
+ if (we != null) {
+ if(we.Status == WebExceptionStatus.ProtocolError) {
+ HttpWebResponse resp = we.Response as HttpWebResponse;
+
+ if (resp != null) {
+ httpStatus = resp.StatusCode;
+ // This is going to get triggered if the file on disk is complete.
+ // Maybe request range-1 and see if a content length of 0 is returned.
+ if (resp.StatusCode == HttpStatusCode.RequestedRangeNotSatisfiable) {
+ if (rangeError++ == 0) {
+ retry = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (!retry) {
+ error = HttpFileDownloadErrors.HttpError;
+ }
+ } else if (modified++ == 1) {
+ retry = true;
+ }
+ } catch (Exception ex) {
+ Console.WriteLine (ex.Message);
+ Console.WriteLine (ex.StackTrace);
+ } finally {
+ if (retry) {
+ CloseLocalStream (true);
+ DestroyWebClient ();
+ ExecuteImpl ();
+ } else if (
+ ((RequestedCancellationType == CancellationType.Paused) ? SetCompleted (true) : SetCompleted ())
+ ) {
+ if (e.Cancelled && RequestedCancellationType == CancellationType.Aborted) {
+ CloseLocalStream (true);
+ } else {
+ CloseLocalStream (false);
+ }
+
+ DestroyWebClient ();
+ OnTaskCompleted (e.Error, e.Cancelled);
+ }
+ }
+ }
+ }
+
+ private void OnDownloadProgressChangedHandler (object sender, Migo2.Net.DownloadProgressChangedEventArgs e)
+ {
+ lock (SyncRoot) {
+ if (e.ProgressPercentage != 0) {
+ SetProgress (e.ProgressPercentage);
+ }
+ }
+ }
+
+ private void OnResponseReceivedHandler (object sender, EventArgs e)
+ {
+ lock (SyncRoot) {
+ if (wc != null && wc.ResponseHeaders != null) {
+ mimeType = wc.ResponseHeaders.Get ("Content-Type");
+
+ httpStatus = wc.Response.StatusCode;
+
+ if (preexistingFile &&
+ wc.Response.LastModified.ToUniversalTime () > File.GetLastWriteTimeUtc (localPath)) {
+ ++modified;
+ wc.CancelAsync ();
+ }
+ }
+ }
+ }
+
+ protected virtual void OnWebClientStatusUpdatedHandler (object sender, DownloadStatusUpdatedEventArgs e)
+ {
+ lock (SyncRoot) {
+ lastTotalBytes = e.Status.TotalBytes;
+ lastTotalBytesReceived = e.Status.TotalBytesReceived;
+ }
+
+ OnDownloadStatusUpdated (e.Status);
+ }
+
+ protected virtual void OnDownloadStatusUpdated (DownloadStatus status)
+ {
+ CommandQueue queue = EventQueue;
+ EventHandler<DownloadStatusUpdatedEventArgs> handler = StatusUpdated;
+
+ if (queue != null && handler != null) {
+ queue.Register (delegate { handler (this, new DownloadStatusUpdatedEventArgs (status, UserState)); });
+ } else if (handler != null) {
+ ThreadPool.QueueUserWorkItem (
+ delegate { handler (this, new DownloadStatusUpdatedEventArgs (status, UserState)); }
+ );
+ }
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/AsyncWebClient.cs b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/AsyncWebClient.cs
new file mode 100644
index 0000000..59d9e7a
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/AsyncWebClient.cs
@@ -0,0 +1,834 @@
+//
+// AsyncWebClient.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.IO;
+using System.Net;
+using System.Text;
+using System.Threading;
+using System.ComponentModel;
+
+namespace Migo2.Net
+{
+ enum DownloadType
+ {
+ None = 0,
+ Data = 1,
+ File = 2,
+ String = 3
+ };
+
+ public sealed class AsyncWebClient
+ {
+ private int range = 0;
+ private int timeout = (120 * 1000); // 2 minutes
+ private DateTime ifModifiedSince = DateTime.MinValue;
+
+ private string fileName;
+
+ private Exception error;
+
+ private Uri uri;
+ private object userState;
+ private DownloadType type;
+
+ private IWebProxy proxy;
+ private string user_agent;
+ private Encoding encoding;
+ private ICredentials credentials;
+
+ private HttpWebRequest request;
+ private HttpWebResponse response;
+
+ private WebHeaderCollection headers;
+ private WebHeaderCollection responseHeaders;
+
+ private byte[] result;
+ private Stream localFile;
+ private MemoryStream memoryStream;
+
+ private TransferStatusManager tsm;
+
+ private AutoResetEvent readTimeoutHandle;
+ private RegisteredWaitHandle registeredTimeoutHandle;
+
+ private bool busy;
+ private bool completed;
+ private bool cancelled;
+
+ private readonly object cancelBusySync = new object ();
+
+ public event EventHandler<EventArgs> ResponseReceived;
+ public event EventHandler<DownloadProgressChangedEventArgs> DownloadProgressChanged;
+ public event EventHandler<DownloadStatusUpdatedEventArgs> StatusUpdated;
+
+ public event EventHandler<AsyncCompletedEventArgs> DownloadFileCompleted;
+ public event EventHandler<DownloadDataCompletedEventArgs> DownloadDataCompleted;
+ public event EventHandler<DownloadStringCompletedEventArgs> DownloadStringCompleted;
+
+ public ICredentials Credentials
+ {
+ get { return credentials; }
+ set { credentials = value; }
+ }
+
+ public Encoding Encoding
+ {
+ get { return encoding; }
+
+ set {
+ if (value == null) {
+ throw new ArgumentNullException ("encoding");
+ } else {
+ encoding = Encoding.Default;
+ }
+ }
+ }
+
+ public WebHeaderCollection Headers
+ {
+ get { return headers; }
+ set {
+ if (value == null) {
+ headers = new WebHeaderCollection ();
+ } else {
+ headers = value;
+ }
+ }
+ }
+
+ public DateTime IfModifiedSince
+ {
+ get { return ifModifiedSince; }
+ set { ifModifiedSince = value; }
+ }
+
+ public bool IsBusy
+ {
+ get { lock (cancelBusySync) { return busy; } }
+ }
+
+ public IWebProxy Proxy
+ {
+ get { return proxy; }
+ set { proxy = value; }
+ }
+
+ public int Range
+ {
+ get { return range; }
+ set {
+ if (range > -1) {
+ range = value;
+ } else {
+ throw new ArgumentOutOfRangeException ("Range");
+ }
+ }
+ }
+
+ public HttpWebResponse Response {
+ get { return response; }
+ }
+
+ public WebHeaderCollection ResponseHeaders {
+ get { return responseHeaders; }
+ }
+
+ public DownloadStatus Status
+ {
+ get {
+ if (type == DownloadType.String) {
+ throw new InvalidOperationException (
+ "Status cannot be reported for string downloads"
+ );
+ }
+
+ lock (tsm.SyncRoot) {
+ return new DownloadStatus (
+ tsm.Progress, tsm.BytesReceived, tsm.TotalBytes, tsm.TotalBytesReceived, tsm.TransferRate
+ );
+ }
+ }
+ }
+
+ public int Timeout {
+ get { return timeout; }
+ set {
+ if (value < -1) {
+ throw new ArgumentOutOfRangeException (
+ "Value must be greater than or equal to -1"
+ );
+ }
+
+ timeout = value;
+ }
+ }
+
+ private static string default_user_agent;
+ public static string DefaultUserAgent {
+ get { return default_user_agent; }
+ set { default_user_agent = value; }
+ }
+
+ public string UserAgent {
+ get { return user_agent ?? DefaultUserAgent; }
+ set { user_agent = value; }
+ }
+
+ private bool Cancelled {
+ get { lock (cancelBusySync) { return cancelled; } }
+ }
+
+ public AsyncWebClient ()
+ {
+ encoding = Encoding.Default;
+ tsm = new TransferStatusManager ();
+ }
+
+ public void DownloadDataAsync (Uri address)
+ {
+ DownloadDataAsync (address, null);
+ }
+
+ public void DownloadDataAsync (Uri address, object userState)
+ {
+ if (address == null) {
+ throw new ArgumentNullException ("address");
+ }
+
+ SetBusy ();
+ DownloadAsync (address, DownloadType.Data, userState);
+ }
+
+ public void DownloadFileAsync (Uri address, string fileName)
+ {
+ DownloadFileAsync (address, fileName, null, null);
+ }
+
+ public void DownloadFileAsync (Uri address, Stream file)
+ {
+ DownloadFileAsync (address, null, file, null);
+ }
+
+ public void DownloadFileAsync (Uri address, string file, object userState)
+ {
+ DownloadFileAsync (address, file, null, userState);
+ }
+
+ public void DownloadFileAsync (Uri address, Stream file, object userState)
+ {
+ DownloadFileAsync (address, null, file, userState);
+ }
+
+ private void DownloadFileAsync (Uri address, string filePath, Stream fileStream, object userState)
+ {
+ if (String.IsNullOrEmpty (filePath) && fileStream == null || fileStream == Stream.Null) {
+ throw new ArgumentNullException ("file");
+ } else if (address == null) {
+ throw new ArgumentNullException ("address");
+ } else if (fileStream != null) {
+ if (!fileStream.CanWrite) {
+ throw new ArgumentException ("Cannot write to stream");
+ } else {
+ localFile = fileStream;
+ }
+ } else {
+ this.fileName = filePath;
+ }
+
+
+ SetBusy ();
+ DownloadAsync (address, DownloadType.File, userState);
+ }
+
+ public void DownloadStringAsync (Uri address)
+ {
+ DownloadStringAsync (address, null);
+ }
+
+ public void DownloadStringAsync (Uri address, object userState)
+ {
+ if (address == null) {
+ throw new ArgumentNullException ("address");
+ }
+
+ SetBusy ();
+ DownloadAsync (address, DownloadType.String, userState);
+ }
+
+ public void CancelAsync ()
+ {
+ CancelAsync (true);
+ }
+
+ public void CancelAsync (bool deleteFile)
+ {
+ if (SetCancelled ()) {
+ AbortDownload ();
+ }
+ }
+
+ private void AbortDownload ()
+ {
+ AbortDownload (null);
+ }
+
+ private void AbortDownload (Exception e)
+ {
+ error = e;
+
+ try {
+ HttpWebRequest req = request;
+
+ if (req != null) {
+ req.Abort();
+ }
+ } catch {}
+ }
+
+ private void Completed ()
+ {
+ Completed (null);
+ }
+
+ private void Completed (Exception e)
+ {
+ Exception err = (SetCompleted ()) ? e : error;
+
+ object statePtr = userState;
+ byte[] resultPtr = result;
+ bool cancelledCpy = Cancelled;
+
+ CleanUp ();
+
+ DownloadCompleted (resultPtr, err, cancelledCpy, statePtr);
+
+ Reset ();
+ }
+
+ private void CleanUp ()
+ {
+ if (localFile != null) {
+ localFile.Close ();
+ localFile = null;
+ }
+
+ if (memoryStream != null) {
+ memoryStream.Close ();
+ memoryStream = null;
+ }
+
+ if (response != null) {
+ response.Close ();
+ response = null;
+ }
+
+ result = null;
+ request = null;
+
+ CleanUpHandles ();
+ }
+
+ private void CleanUpHandles ()
+ {
+ if (registeredTimeoutHandle != null) {
+ registeredTimeoutHandle.Unregister (readTimeoutHandle);
+ readTimeoutHandle = null;
+ }
+
+ if (readTimeoutHandle != null) {
+ readTimeoutHandle.Close ();
+ readTimeoutHandle = null;
+ }
+ }
+
+ private bool SetBusy ()
+ {
+ bool ret = false;
+
+ lock (cancelBusySync) {
+ if (busy) {
+ throw new InvalidOperationException (
+ "Concurrent transfer operations are not supported."
+ );
+ } else {
+ ret = busy = true;
+ }
+ }
+
+ return ret;
+ }
+
+ private bool SetCancelled ()
+ {
+ bool ret = false;
+
+ lock (cancelBusySync) {
+ if (busy && !completed && !cancelled) {
+ ret = cancelled = true;
+ }
+ }
+
+ return ret;
+ }
+
+ private bool SetCompleted ()
+ {
+ bool ret = false;
+
+ lock (cancelBusySync) {
+ if (busy && !completed && !cancelled) {
+ ret = completed = true;
+ }
+ }
+
+ return ret;
+ }
+
+ private void DownloadAsync (Uri uri, DownloadType type, object state)
+ {
+ this.uri = uri;
+ this.type = type;
+ this.userState = state;
+
+ ImplDownloadAsync ();
+ }
+
+ private void ImplDownloadAsync ()
+ {
+ try {
+ tsm.Reset ();
+ request = PrepRequest (uri);
+ IAsyncResult ar = request.BeginGetResponse (OnResponseCallback, null);
+
+ ThreadPool.RegisterWaitForSingleObject (
+ ar.AsyncWaitHandle,
+ new WaitOrTimerCallback(OnTimeout),
+ request, timeout, true
+ );
+ } catch (Exception e) {
+ Completed (e);
+ }
+ }
+
+ private HttpWebRequest PrepRequest (Uri address)
+ {
+ responseHeaders = null;
+ HttpWebRequest req = HttpWebRequest.Create (address) as HttpWebRequest;
+
+ req.AllowAutoRedirect = true;
+ req.Credentials = credentials;
+
+ if (proxy != null) {
+ req.Proxy = proxy;
+ }
+
+ if (headers != null && headers.Count != 0) {
+
+ int rangeHdr = -1;
+ string expect = headers ["Expect"];
+ string contentType = headers ["Content-Type"];
+ string accept = headers ["Accept"];
+ string connection = headers ["Connection"];
+ string userAgent = headers ["User-Agent"];
+ string referer = headers ["Referer"];
+ string rangeStr = headers ["Range"];
+ string ifModifiedSince = headers ["If-Modified-Since"];
+
+ if (!String.IsNullOrEmpty (rangeStr)) {
+ Int32.TryParse (rangeStr, out rangeHdr);
+ }
+
+ headers.Remove ("Expect");
+ headers.Remove ("Content-Type");
+ headers.Remove ("Accept");
+ headers.Remove ("Connection");
+ headers.Remove ("Referer");
+ headers.Remove ("User-Agent");
+ headers.Remove ("Range");
+ headers.Remove ("If-Modified-Since");
+
+ req.Headers = headers;
+
+ if (!String.IsNullOrEmpty (expect)) {
+ req.Expect = expect;
+ }
+
+ if (!String.IsNullOrEmpty (accept)) {
+ req.Accept = accept;
+ }
+
+ if (!String.IsNullOrEmpty (contentType)) {
+ req.ContentType = contentType;
+ }
+
+ if (!String.IsNullOrEmpty (connection)) {
+ req.Connection = connection;
+ }
+
+ if (!String.IsNullOrEmpty (userAgent)) {
+ req.UserAgent = userAgent;
+ }
+
+ if (!String.IsNullOrEmpty (referer)) {
+ req.Referer = referer;
+ }
+
+ if (rangeHdr > 0) {
+ req.AddRange (range);
+ }
+
+ if (!String.IsNullOrEmpty (ifModifiedSince)) {
+ DateTime modDate;
+
+ if (DateTime.TryParse (ifModifiedSince, out modDate)) {
+ req.IfModifiedSince = modDate;
+ }
+ }
+ } else {
+ if (!String.IsNullOrEmpty (UserAgent)) {
+ req.UserAgent = UserAgent;
+ }
+
+ if (this.range > 0) {
+ req.AddRange (this.range);
+ }
+
+ if (this.ifModifiedSince > DateTime.MinValue) {
+ req.IfModifiedSince = this.ifModifiedSince;
+ }
+ }
+
+ responseHeaders = null;
+
+ return req;
+ }
+
+ private void OnResponseCallback (IAsyncResult ar)
+ {
+ Exception err = null;
+ bool redirect_workaround = false;
+
+ try {
+ response = request.EndGetResponse (ar) as HttpWebResponse;
+
+ responseHeaders = response.Headers;
+ OnResponseReceived ();
+
+ if (type != DownloadType.String) {
+ tsm.StatusUpdated += OnDownloadStatusUpdatedHandler;
+ tsm.ProgressChanged += OnDownloadProgressChangedHandler;
+ tsm.EnableStatusReporting = true;
+ }
+
+ Download (response.GetResponseStream ());
+ } catch (ObjectDisposedException) {
+ } catch (WebException we) {
+ if (we.Status != WebExceptionStatus.RequestCanceled) {
+ err = we;
+ HttpWebResponse response = we.Response as HttpWebResponse;
+
+ if (response != null &&
+ response.StatusCode == HttpStatusCode.BadRequest &&
+ response.ResponseUri != request.RequestUri) {
+ redirect_workaround = true;
+ uri = response.ResponseUri;
+ ImplDownloadAsync ();
+ }
+ }
+ } catch (Exception e) {
+ err = e;
+ } finally {
+ if (type != DownloadType.String) {
+ tsm.EnableStatusReporting = false;
+ tsm.ProgressChanged -= OnDownloadProgressChangedHandler;
+ tsm.StatusUpdated -= OnDownloadStatusUpdatedHandler;
+ }
+
+ if (!redirect_workaround) {
+ Completed (err);
+ }
+ }
+ }
+
+ // All of this download code could be abstracted
+ // and put into a helper class.
+ private void Download (Stream st)
+ {
+ long cLength = (response.ContentLength + range);
+
+ if (cLength == 0) {
+ return;
+ }
+
+ int nread = -1;
+ int offset = 0;
+
+ int length = (cLength == -1 || cLength > 8192) ? 8192 : (int) cLength;
+
+ Stream dest = null;
+ readTimeoutHandle = new AutoResetEvent (false);
+
+ byte[] buffer = null;
+
+ bool dataDownload = false;
+ bool writeToStream = false;
+
+ if (type != DownloadType.String) {
+ tsm.TotalBytes = cLength;
+ tsm.BytesReceivedPreviously = range;
+ }
+
+ switch (type) {
+ case DownloadType.String:
+ case DownloadType.Data:
+ dataDownload = true;
+
+ if (cLength != -1) {
+ length = (int) cLength;
+ buffer = new byte[cLength];
+ } else {
+ writeToStream = true;
+ buffer = new byte[length];
+ dest = OpenMemoryStream ();
+ }
+ break;
+ case DownloadType.File:
+ writeToStream = true;
+ buffer = new byte [length];
+
+ if (localFile == null) {
+ dest = OpenLocalFile (fileName);
+ } else {
+ dest = localFile;
+ }
+
+ break;
+ }
+
+ registeredTimeoutHandle = ThreadPool.RegisterWaitForSingleObject (
+ readTimeoutHandle, new WaitOrTimerCallback (OnTimeout), null, timeout, false
+ );
+
+ IAsyncResult ar;
+ while (nread != 0) {
+ // <hack>
+ // Yeah, Yeah, Yeah, I'll change this later,
+ // it's here to get around abort issues.
+
+ ar = st.BeginRead (buffer, offset, length, null, null);
+ nread = st.EndRead (ar);
+
+ // need an auxiliary downloader class to replace this.
+ // </hack>
+
+ readTimeoutHandle.Set ();
+
+ if (writeToStream) {
+ dest.Write (buffer, 0, nread);
+ } else {
+ offset += nread;
+ length -= nread;
+ }
+
+ if (type != DownloadType.String) {
+ tsm.AddBytes (nread);
+ }
+ }
+
+ CleanUpHandles ();
+
+ if (type != DownloadType.String) {
+ if (tsm.TotalBytes == -1) {
+ tsm.TotalBytes = tsm.BytesReceived;
+ }
+ }
+
+ if (dataDownload) {
+ if (writeToStream) {
+ result = memoryStream.ToArray ();
+ } else {
+ result = buffer;
+ }
+ }
+ }
+
+ private Stream OpenLocalFile (string filePath)
+ {
+ return File.Open (filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
+ }
+
+ private MemoryStream OpenMemoryStream ()
+ {
+ return memoryStream = new MemoryStream ();
+ }
+
+ private void Reset ()
+ {
+ lock (cancelBusySync) {
+ busy = false;
+ cancelled = false;
+ completed = false;
+ error = null;
+ fileName = String.Empty;
+ ifModifiedSince = DateTime.MinValue;
+ range = 0;
+ type = DownloadType.None;
+ uri = null;
+ userState = null;
+ }
+ }
+
+ private void DownloadCompleted (byte[] resultPtr, Exception errPtr, bool cancelledCpy, object userStatePtr)
+ {
+ switch (type) {
+ case DownloadType.Data:
+ OnDownloadDataCompleted (resultPtr, errPtr, cancelledCpy, userStatePtr);
+ break;
+ case DownloadType.File:
+ OnDownloadFileCompleted (errPtr, cancelledCpy, userStatePtr);
+ break;
+ case DownloadType.String:
+ string s;
+ try {
+ s = Encoding.GetString (resultPtr);
+ } catch {
+ s = String.Empty;
+ }
+
+ OnDownloadStringCompleted (s, errPtr, cancelledCpy, userStatePtr);
+ break;
+ }
+ }
+
+ private void OnTimeout (object state, bool timedOut)
+ {
+ if (timedOut) {
+ if (SetCompleted ()) {
+ try {
+ AbortDownload (
+ new WebException ("The operation timed out", null, WebExceptionStatus.Timeout, response)
+ );
+ } finally {
+ Completed ();
+ }
+ }
+ }
+ }
+
+ private void OnResponseReceived ()
+ {
+ EventHandler<EventArgs> handler = ResponseReceived;
+
+ if (handler != null) {
+ handler (this, new EventArgs ());
+ }
+ }
+
+ private void OnDownloadProgressChanged (long bytesReceived,
+ long BytesToReceive,
+ int progressPercentage,
+ object userState)
+ {
+ OnDownloadProgressChanged (
+ new DownloadProgressChangedEventArgs (
+ progressPercentage, userState,
+ bytesReceived, BytesToReceive
+ )
+ );
+ }
+
+ private void OnDownloadProgressChanged (DownloadProgressChangedEventArgs args)
+ {
+ EventHandler <DownloadProgressChangedEventArgs> handler = DownloadProgressChanged;
+
+ if (handler != null) {
+ handler (this, args);
+ }
+ }
+
+ private void OnDownloadProgressChangedHandler (object sender, DownloadProgressChangedEventArgs e)
+ {
+ OnDownloadProgressChanged (e.BytesReceived, e.TotalBytesToReceive, e.ProgressPercentage, userState);
+ }
+
+ private void OnDownloadStatusUpdatedHandler (object sender, DownloadStatusUpdatedEventArgs e)
+ {
+ OnDownloadStatusUpdated (e.Status);
+ }
+
+ private void OnDownloadDataCompleted (byte[] bytes, Exception error, bool cancelled, object userState)
+ {
+ OnDownloadDataCompleted (new DownloadDataCompletedEventArgs (bytes, error, cancelled, userState));
+ }
+
+ private void OnDownloadDataCompleted (DownloadDataCompletedEventArgs args)
+ {
+ EventHandler <DownloadDataCompletedEventArgs> handler = DownloadDataCompleted;
+
+ if (handler != null) {
+ handler (this, args);
+ }
+ }
+
+ private void OnDownloadFileCompleted (Exception error, bool cancelled, object userState)
+ {
+ OnDownloadFileCompleted (new AsyncCompletedEventArgs (error, cancelled, userState));
+ }
+
+ private void OnDownloadFileCompleted (AsyncCompletedEventArgs args)
+ {
+ EventHandler <AsyncCompletedEventArgs> handler = DownloadFileCompleted;
+
+ if (handler != null) {
+ handler (this, args);
+ }
+ }
+
+ private void OnDownloadStatusUpdated (DownloadStatus status)
+ {
+ EventHandler <DownloadStatusUpdatedEventArgs> handler = StatusUpdated;
+
+ if (handler != null) {
+ handler (this, new DownloadStatusUpdatedEventArgs (status, userState));
+ }
+ }
+
+ private void OnDownloadStringCompleted (string resultStr, Exception error, bool cancelled, object userState)
+ {
+ OnDownloadStringCompleted (new DownloadStringCompletedEventArgs (resultStr, error, cancelled, userState));
+ }
+
+ private void OnDownloadStringCompleted (DownloadStringCompletedEventArgs args)
+ {
+ EventHandler <DownloadStringCompletedEventArgs> handler = DownloadStringCompleted;
+
+ if (handler != null) {
+ handler (this, args);
+ }
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/DownloadDataCompletedEventArgs.cs b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/DownloadDataCompletedEventArgs.cs
new file mode 100644
index 0000000..c8e7e68
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/DownloadDataCompletedEventArgs.cs
@@ -0,0 +1,49 @@
+//
+// DownloadDataCompletedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.ComponentModel;
+
+namespace Migo2.Net
+{
+ public class DownloadDataCompletedEventArgs : AsyncCompletedEventArgs
+ {
+ private readonly byte[] result;
+
+ public byte[] Result {
+ get {
+ RaiseExceptionIfNecessary ();
+ return result;
+ }
+ }
+
+ internal DownloadDataCompletedEventArgs (byte[] result, Exception error, bool cancelled, object userState)
+ : base (error, cancelled, userState)
+ {
+ this.result = result;
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/DownloadProgressChangedEventArgs.cs b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/DownloadProgressChangedEventArgs.cs
new file mode 100644
index 0000000..8f83b03
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/DownloadProgressChangedEventArgs.cs
@@ -0,0 +1,61 @@
+//
+// DownloadProgressChangedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.ComponentModel;
+
+namespace Migo2.Net
+{
+ public class DownloadProgressChangedEventArgs : ProgressChangedEventArgs
+ {
+ private readonly long totalBytes;
+ private readonly long bytesReceived;
+
+ public long BytesReceived
+ {
+ get {
+ return bytesReceived;
+ }
+ }
+
+ public long TotalBytesToReceive
+ {
+ get {
+ return totalBytes;
+ }
+ }
+
+ public DownloadProgressChangedEventArgs (int progressPercentage,
+ object userState,
+ long bytesReceived,
+ long totalBytes)
+ : base (progressPercentage, userState)
+ {
+ this.totalBytes = totalBytes;
+ this.bytesReceived = bytesReceived;
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/DownloadStatus.cs b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/DownloadStatus.cs
new file mode 100644
index 0000000..3f93c78
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/DownloadStatus.cs
@@ -0,0 +1,75 @@
+//
+// DownloadStatus.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+namespace Migo2.Net
+{
+ public struct DownloadStatus
+ {
+ private readonly int progress;
+ private readonly long bytesReceived;
+ private readonly long totalBytes;
+ private readonly long totalBytesReceived;
+ private readonly long transferRate;
+
+ public long BytesReceived
+ {
+ get { return bytesReceived; }
+ }
+
+ public int Progress
+ {
+ get { return progress; }
+ }
+
+ public long TotalBytes
+ {
+ get { return totalBytes; }
+ }
+
+ public long TotalBytesReceived
+ {
+ get { return totalBytesReceived; }
+ }
+
+ public long TransferRate {
+ get { return transferRate; }
+ }
+
+ public DownloadStatus (int progress,
+ long bytesReceived,
+ long totalBytes,
+ long totalBytesReceived,
+ long transferRate)
+ {
+ this.progress = progress;
+ this.bytesReceived = bytesReceived;
+ this.totalBytes = totalBytes;
+ this.totalBytesReceived = totalBytesReceived;
+ this.transferRate = transferRate;
+ }
+ }
+}
+
diff --git a/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/DownloadStatusUpdatedEventArgs.cs b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/DownloadStatusUpdatedEventArgs.cs
new file mode 100644
index 0000000..d0a801a
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/DownloadStatusUpdatedEventArgs.cs
@@ -0,0 +1,51 @@
+//
+// DownloadStatusUpdatedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Migo2.Net
+{
+ public class DownloadStatusUpdatedEventArgs : EventArgs
+ {
+ private readonly DownloadStatus status;
+ private readonly object userState;
+
+ public DownloadStatus Status {
+ get { return status; }
+ }
+
+ public object UserState
+ {
+ get { return userState; }
+ }
+
+ public DownloadStatusUpdatedEventArgs (DownloadStatus status, object userState)
+ {
+ this.status = status;
+ this.userState = userState;
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/DownloadStringCompletedEventArgs.cs b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/DownloadStringCompletedEventArgs.cs
new file mode 100644
index 0000000..8267645
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/DownloadStringCompletedEventArgs.cs
@@ -0,0 +1,53 @@
+//
+// DownloadStringCompletedEventArgs.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.IO;
+using System.ComponentModel;
+
+namespace Migo2.Net
+{
+ public class DownloadStringCompletedEventArgs : AsyncCompletedEventArgs
+ {
+ private readonly string result;
+
+ public string Result {
+ get {
+ RaiseExceptionIfNecessary ();
+ return result;
+ }
+ }
+
+ internal DownloadStringCompletedEventArgs (string result,
+ Exception error,
+ bool cancelled,
+ object userState)
+ : base (error, cancelled, userState)
+ {
+ this.result = result;
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/RemoteFileModifiedException.cs b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/RemoteFileModifiedException.cs
new file mode 100644
index 0000000..c43440e
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/RemoteFileModifiedException.cs
@@ -0,0 +1,59 @@
+//
+// RemoteFileModifiedException.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Migo2.Net
+{
+ public class RemoteFileModifiedException : Exception
+ {
+ private readonly DateTime localFileMod;
+ private readonly DateTime remoteFileMod;
+
+ public DateTime LocalFileModified {
+ get {
+ return remoteFileMod;
+ }
+ }
+
+ public DateTime RemoteFileModified {
+ get {
+ return localFileMod;
+ }
+ }
+
+ public RemoteFileModifiedException (string message, DateTime localFileMod, DateTime remoteFileMod)
+ : base (message)
+ {
+ this.localFileMod = localFileMod;
+ this.remoteFileMod = remoteFileMod;
+ }
+
+ public RemoteFileModifiedException (string message) : this (message, DateTime.MinValue, DateTime.MinValue)
+ {
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/TransferStatusManager.cs b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/TransferStatusManager.cs
new file mode 100644
index 0000000..ce790a8
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/TransferStatusManager.cs
@@ -0,0 +1,213 @@
+//
+// TransferStatusManager.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Migo2.Net
+{
+ internal sealed partial class TransferStatusManager
+ {
+ private int progress;
+ private long progressStep;
+ private long progressThisStep;
+
+ private long bytesReceived;
+ private long bytesReceivedPreviously;
+
+ private long totalBytes = -1;
+
+ private readonly object sync = new object ();
+
+ public event EventHandler<DownloadProgressChangedEventArgs> ProgressChanged;
+
+ public long BytesReceived {
+ get {
+ return bytesReceived;
+ }
+ set {
+ SetBytesReceived (value);
+ }
+ }
+
+ public long BytesReceivedPreviously {
+ get {
+ return bytesReceivedPreviously;
+ }
+ set {
+ if (value < 0) {
+ throw new ArgumentOutOfRangeException ("BytesReceivedPreviously", "Must be > 0");
+ }
+
+ bytesReceivedPreviously = value;
+ progressThisStep += value;
+ UpdateProgress ();
+ }
+ }
+
+ public int Progress {
+ get {
+ return progress;
+ }
+ }
+
+ public object SyncRoot {
+ get {
+ return sync;
+ }
+ }
+
+ public long TotalBytesReceived {
+ get {
+ return bytesReceived + bytesReceivedPreviously;
+ }
+ }
+
+ public long TotalBytes {
+ get {
+ return totalBytes;
+ }
+ set {
+ SetTotalBytes (value);
+ }
+ }
+
+ public TransferStatusManager ()
+ {
+ Reset ();
+ }
+
+ public void AddBytes (long bytes)
+ {
+ if (bytes < 0) {
+ throw new ArgumentOutOfRangeException ("bytes cannot be less than 0");
+ }
+
+ lock (sync) {
+ bytesReceived += bytes;
+ progressThisStep += bytes;
+ bytesThisInterval += bytes;
+ }
+
+ UpdateProgress ();
+ }
+
+ public void Reset ()
+ {
+ lock (sync) {
+ progress = 0;
+
+ progressStep = 0;
+ progressThisStep = 0;
+
+ totalBytes = -1;
+ bytesReceived = 0;
+ bytesReceivedPreviously = 0;
+
+ ResetTransferRates ();
+ }
+
+ UpdateProgress ();
+ }
+
+ private void SetBytesReceived (long bytesReceived)
+ {
+ if (bytesReceived < 0) {
+ throw new ArgumentOutOfRangeException ("bytesReceived cannot be less than 0");
+ }
+
+ lock (sync) {
+ if (totalBytes > -1 && totalBytes < bytesReceived) {
+ throw new ArgumentOutOfRangeException ("bytesReceived cannot be greater than TotalBytes");
+ }
+
+ progressThisStep = 0;
+ this.bytesReceived = bytesReceived;
+ }
+
+ UpdateProgress ();
+ }
+
+ private void SetTotalBytes (long totalBytes)
+ {
+ bool update = false;
+
+ if (totalBytes < -1 || totalBytes == 0) {
+ throw new ArgumentOutOfRangeException ("totalBytes cannot be less than -1 or equal to 0");
+ }
+
+ lock (sync) {
+ if (totalBytes != -1 && totalBytes < bytesReceived) {
+ throw new ArgumentOutOfRangeException ("totalBytes cannot be less than bytesReceived");
+ }
+
+ if (this.totalBytes != totalBytes) {
+ this.totalBytes = totalBytes;
+ progressStep = (totalBytes / 100);
+ update = true;
+ }
+ }
+
+ if (update) {
+ UpdateProgress ();
+ }
+ }
+
+ private void UpdateProgress ()
+ {
+ DownloadProgressChangedEventArgs args = null;
+
+ lock (sync) {
+ long totalBytesReceived = TotalBytesReceived;
+
+ if (totalBytes > 0 &&
+ progressThisStep >= progressStep ||
+ (totalBytesReceived == totalBytes && progress != 100)) {
+ progress = Convert.ToInt32 ((totalBytesReceived * 100) / totalBytes);
+
+ if (progress >= 0) {
+ args = new DownloadProgressChangedEventArgs (progress, null, totalBytesReceived, totalBytes);
+ }
+
+ progressThisStep = 0;
+ }
+ }
+
+ if (args != null) {
+ OnProgressChanged (args);
+ }
+ }
+
+ private void OnProgressChanged (DownloadProgressChangedEventArgs args)
+ {
+ EventHandler <DownloadProgressChangedEventArgs>
+ handler = ProgressChanged;
+
+ if (handler != null) {
+ handler (this, args);
+ }
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/TransferStatusManager_Rate.cs b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/TransferStatusManager_Rate.cs
new file mode 100644
index 0000000..3b34d40
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Net/AsyncWebClient/TransferStatusManager_Rate.cs
@@ -0,0 +1,161 @@
+//
+// TransferStatusManager_Rate.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Timers;
+
+namespace Migo2.Net
+{
+ internal sealed partial class TransferStatusManager
+ {
+ private DateTime lastTick;
+
+ private long transferRate = -1;
+ private long transferRatePreviously = -1;
+
+ private long bytesThisInterval;
+ private int interval = (1500 * 1); // 1.5 seconds
+
+ private Timer statusTimer;
+ private bool enableTransferRateReporting;
+
+ public EventHandler<DownloadStatusUpdatedEventArgs> StatusUpdated;
+
+ public bool EnableStatusReporting {
+ get { return enableTransferRateReporting; }
+ set {
+ lock (sync) {
+ if (value != enableTransferRateReporting) {
+ enableTransferRateReporting = !enableTransferRateReporting;
+
+ if (enableTransferRateReporting) {
+ statusTimer = new Timer ();
+
+ statusTimer.Elapsed += OnstatusTimerElapsedHandler;
+ statusTimer.Interval = interval;
+ statusTimer.Enabled = true;
+ lastTick = DateTime.Now;
+ } else {
+ if (statusTimer != null) {
+ statusTimer.Enabled = false;
+ statusTimer.Elapsed -= OnstatusTimerElapsedHandler;
+ statusTimer.Dispose ();
+ statusTimer = null;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public int Interval {
+ get { return interval; }
+ set {
+ lock (sync) {
+ interval = value;
+
+ if (statusTimer != null) {
+ statusTimer.Interval = interval;
+ }
+ }
+ }
+ }
+
+ public long TransferRate {
+ get {
+ lock (sync) {
+ return transferRate;
+ }
+ }
+ }
+
+ private void ResetTransferRates ()
+ {
+ lock (sync) {
+ bytesThisInterval = 0;
+ transferRate = -1;
+ transferRatePreviously = -1;
+ }
+ }
+
+ private long CalculateTransferRate ()
+ {
+ long bytesPerSecond;
+
+ TimeSpan duration = (DateTime.Now - lastTick);
+ double secondsElapsed = duration.TotalSeconds;
+
+ if ((int)secondsElapsed == 0) {
+ return 0;
+ }
+
+ bytesPerSecond = (long) ((bytesThisInterval / secondsElapsed));
+
+ lastTick = DateTime.Now;
+ bytesThisInterval = 0;
+
+ return bytesPerSecond;
+ }
+
+ private void UpdateTransferRate ()
+ {
+ transferRate = CalculateTransferRate ();
+
+ if (transferRatePreviously == 0) {
+ if (transferRate > 0) {
+ transferRatePreviously = transferRate;
+ return;
+ }
+ }
+
+ if (transferRate != transferRatePreviously) {
+ transferRate = ((transferRate + transferRatePreviously) / 2);
+ transferRatePreviously = transferRate;
+ }
+ }
+
+ private void OnstatusTimerElapsedHandler (object source, System.Timers.ElapsedEventArgs e)
+ {
+ DownloadStatus status;
+
+ lock (sync) {
+ UpdateTransferRate ();
+ status = new DownloadStatus (progress, bytesReceived, totalBytes, TotalBytesReceived, transferRate);
+ }
+
+ OnStatusUpdated (status);
+ }
+
+ private void OnStatusUpdated (DownloadStatus status)
+ {
+ EventHandler<DownloadStatusUpdatedEventArgs> handler = StatusUpdated;
+
+ if (handler != null) {
+ handler (this, new DownloadStatusUpdatedEventArgs (status, null));
+ }
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Utils/Rfc822DateTime.cs b/src/Libraries/Migo2/Migo2.Utils/Rfc822DateTime.cs
new file mode 100644
index 0000000..211ac12
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Utils/Rfc822DateTime.cs
@@ -0,0 +1,199 @@
+//
+// Rfc822DateTime.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2006-09 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Text.RegularExpressions;
+
+namespace Migo2.Utils
+{
+ public static class Rfc822DateTime
+ {
+ private const string monthsStr =
+ "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|" +
+ "January|February|March|April|May|June|July|August|" +
+ "September|October|November|December";
+
+ private const string daysOfWeek =
+ "Mon|Tue|Wed|Thu|Fri|Sat|Sun|" +
+ "Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday";
+
+ private const string rfc822DTExp =
+ @"^(?<dayofweek>(" + daysOfWeek + "), )?" +
+ @"(?<day>\d\d?) " +
+ @"(?<month>" + monthsStr + ") " +
+ @"(?<year>\d\d(\d\d)?) " +
+ @"(?<hours>[0-2]?\d):(?<minutes>[0-5]\d)(:(?<seconds>[0-5]\d))?" +
+ @"( (?<timezone>[A-I]|[K-Z]|GMT|UT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|([+-]\d\d\d\d))$)?";
+
+ private static readonly string[] months;
+ private static readonly Regex rfc822DTRegex;
+
+ static Rfc822DateTime()
+ {
+ months = monthsStr.Split ('|');
+ rfc822DTRegex = new Regex (rfc822DTExp,
+ RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ }
+
+ public static DateTime Parse (string dateTime)
+ {
+ if (dateTime == null) {
+ throw new ArgumentNullException ("dateTime");
+ }
+
+ Match m = rfc822DTRegex.Match (dateTime);
+
+ if (m.Success) {
+ DateTime ret;
+ GroupCollection groups = m.Groups;
+
+ int day = Convert.ToInt32 (groups ["day"].Value);
+ int month = MonthToInt32 (groups ["month"].Value);
+ int year = Convert.ToInt32 (groups ["year"].Value);
+
+ int hours = Convert.ToInt32 (groups ["hours"].Value);
+ int minutes = Convert.ToInt32 (groups ["minutes"].Value);
+
+ int seconds = 0;
+ string secondsStr = groups ["seconds"].Value;
+ string timeZone = groups ["timezone"].Value;
+
+ if (secondsStr != String.Empty) {
+ seconds = Convert.ToInt32 (secondsStr);
+ }
+
+ if (year < 100) {
+ int curYear = DateTime.Now.Year;
+ year = curYear - (curYear % 100) + year;
+ }
+
+ ret = new DateTime (year, month, day, hours, minutes, seconds);
+
+ if (timeZone != String.Empty) {
+ ret -= ParseGmtOffset (timeZone);
+ }
+
+ return ret.ToLocalTime ();
+ }
+
+ throw new FormatException ("'dateTime' does not represent a valid RFC 822 date-time");
+ }
+
+ public static bool TryParse (string dateTime, out DateTime result)
+ {
+ bool ret = false;
+ result = DateTime.MinValue;
+
+ try {
+ result = Parse (dateTime);
+ ret = true;
+ } catch {}
+
+ return ret;
+ }
+
+ private static int MonthToInt32 (string month)
+ {
+ int i = 1;
+
+ foreach (string s in months) {
+ if (month == s) {
+ break;
+ }
+
+ if (++i % 13 == 0) {
+ i = 1;
+ }
+ }
+
+ return i;
+ }
+
+ private static TimeSpan ParseGmtOffset (string offset)
+ {
+ int offsetHours = 0;
+ int offsetMinutes = 0;
+
+ if (offset.Length == 5) {
+ offsetHours = Convert.ToInt32 (offset.Substring (1,2));
+ offsetMinutes = Convert.ToInt32 (offset.Substring (3,2));
+
+ if (offset [0] == '-') {
+ offsetHours *= -1;
+ offsetMinutes *= -1;
+ }
+ } else {
+ switch (offset)
+ {
+ case "GMT": case "UT": break;
+ case "EDT": offsetHours = -4; break;
+ case "EST": case "CDT": offsetHours = -5; break;
+ case "CST": case "MDT": offsetHours = -6; break;
+ case "MST": case "PDT": offsetHours = -7; break;
+ case "PST": offsetHours = -8; break;
+
+ case "Z": offsetHours = 0; break;
+
+ case "A": offsetHours = -1; break;
+ case "B": offsetHours = -2; break;
+ case "C": offsetHours = -3; break;
+ case "D": offsetHours = -4; break;
+ case "E": offsetHours = -5; break;
+ case "F": offsetHours = -6; break;
+ case "G": offsetHours = -7; break;
+ case "H": offsetHours = -8; break;
+ case "I": offsetHours = -9; break;
+
+ // Q. Why was 'J' left out of Z-Time?
+ // A. http://www.maybeck.com/ztime/
+
+ // That's what I like about this job, you learn stuff.
+
+ case "K": offsetHours = -10; break;
+ case "L": offsetHours = -11; break;
+ case "M": offsetHours = -12; break;
+ case "N": offsetHours = 1; break;
+ case "O": offsetHours = 2; break;
+ case "P": offsetHours = 3; break;
+ case "Q": offsetHours = 4; break;
+ case "R": offsetHours = 5; break;
+ case "S": offsetHours = 6; break;
+ case "T": offsetHours = 7; break;
+ case "U": offsetHours = 8; break;
+ case "V": offsetHours = 9; break;
+ case "W": offsetHours = 10; break;
+ case "X": offsetHours = 11; break;
+ case "Y": offsetHours = 12; break;
+ }
+ }
+
+ return TimeSpan.FromTicks (
+ (offsetHours * TimeSpan.TicksPerHour ) +
+ (offsetMinutes * TimeSpan.TicksPerMinute)
+ );
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.Utils/UnitUtils.cs b/src/Libraries/Migo2/Migo2.Utils/UnitUtils.cs
new file mode 100644
index 0000000..07cf6f7
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Utils/UnitUtils.cs
@@ -0,0 +1,70 @@
+//
+// UnitUtils.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Migo2.Utils
+{
+ public static class UnitUtils
+ {
+ public static string ToString (long bytes)
+ {
+ if (bytes == -1) {
+ return String.Empty;
+ } else if (bytes < -1) {
+ throw new ArgumentException ("Must be >= 0", "bytes");
+ }
+
+ int divisor = 1;
+ string formatString = String.Empty;
+ string unit = String.Empty;
+
+ if (bytes >= 0 && bytes < 1000) {
+ unit = "B";
+ formatString = "{0:F0} {1}";
+ } else if (bytes > 1000 && bytes < 1000000) {
+ // Not all those who wander are lost.
+
+ // 1 kB == 1000 bytes. 1 KiB == 1024 bytes.
+ // Do not, change this unless you also want to change the abbreviations!
+
+ unit = "kB";
+ divisor = 1000;
+ formatString = "{0:F0} {1}";
+ } else if (bytes > 1000000 && bytes < 1000000000) {
+ unit = "MB";
+ divisor = 1000000;
+ formatString = "{0:F1} {1}";
+ } else if (bytes > 1000000000) {
+ unit = "GB";
+ divisor = 1000000000;
+ formatString = "{0:F2} {1}";
+ }
+
+ return String.Format (formatString, ((double)bytes/divisor), unit);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Libraries/Migo2/Migo2.Utils/XmlUtils.cs b/src/Libraries/Migo2/Migo2.Utils/XmlUtils.cs
new file mode 100644
index 0000000..6174ef2
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.Utils/XmlUtils.cs
@@ -0,0 +1,102 @@
+//
+// XmlUtils.cs
+//
+// Author:
+// Mike Urbanski <michael c urbanski gmail com>
+//
+// Copyright (c) 2009 Michael C. Urbanski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Xml;
+
+namespace Migo2.Utils
+{
+ public static class XmlUtils
+ {
+ public static string GetXmlNodeText (XmlNode node, string tag)
+ {
+ return GetXmlNodeText (node, tag, null);
+ }
+
+ public static string GetXmlNodeText (XmlNode node, string tag, XmlNamespaceManager mgr)
+ {
+ XmlNode n = node.SelectSingleNode (tag, mgr);
+ return (n == null) ? null : n.InnerText.Trim ();
+ }
+
+ public static int GetInt32 (XmlNode node, string tag)
+ {
+ return GetInt32 (node, tag, null);
+ }
+
+ public static int GetInt32 (XmlNode node, string tag, XmlNamespaceManager mgr)
+ {
+ int ret = 0;
+ string result = GetXmlNodeText (node, tag, mgr);
+
+ if (!String.IsNullOrEmpty (result)) {
+ Int32.TryParse (result, out ret);
+ }
+
+ return ret;
+ }
+
+ public static long GetInt64 (XmlNode node, string tag)
+ {
+ return GetInt64 (node, tag, null);
+ }
+
+ public static long GetInt64 (XmlNode node, string tag, XmlNamespaceManager mgr)
+ {
+ long ret = 0;
+ string result = GetXmlNodeText (node, tag, mgr);
+
+ if (!String.IsNullOrEmpty (result)) {
+ Int64.TryParse (result, out ret);
+ }
+
+ return ret;
+ }
+
+ public static DateTime GetRfc822DateTime (XmlNode node, string tag)
+ {
+ return GetRfc822DateTime (node, tag, null);
+ }
+
+ public static DateTime GetRfc822DateTime (XmlNode node, string tag, XmlNamespaceManager mgr)
+ {
+ DateTime ret;
+ string result = GetXmlNodeText (node, tag, mgr);
+
+ if (!String.IsNullOrEmpty (result)) {
+ if (Rfc822DateTime.TryParse (result, out ret)) {
+ return ret;
+ }
+
+ if (DateTime.TryParse (result, out ret)) {
+ return ret;
+ }
+ }
+
+ return DateTime.MinValue;
+ }
+ }
+}
diff --git a/src/Libraries/Migo2/Migo2.csproj b/src/Libraries/Migo2/Migo2.csproj
new file mode 100644
index 0000000..e142b4c
--- /dev/null
+++ b/src/Libraries/Migo2/Migo2.csproj
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.21022</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{FC311410-8638-4A66-A8A5-1E900CDC6C7B}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AssemblyName>Migo2</AssemblyName>
+ <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug</OutputPath>
+ <DefineConstants>DEBUG</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>none</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Release</OutputPath>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Web" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Migo2.Async\CommandQueue\CommandDelegate.cs" />
+ <Compile Include="Migo2.Async\CommandQueue\CommandQueue.cs" />
+ <Compile Include="Migo2.Async\CommandQueue\CommandWrapper.cs" />
+ <Compile Include="Migo2.Async\CommandQueue\EventWrapper.cs" />
+ <Compile Include="Migo2.Async\CommandQueue\ICommand.cs" />
+ <Compile Include="Migo2.Async\Task\CancellationType.cs" />
+ <Compile Include="Migo2.Async\Task\Task.cs" />
+ <Compile Include="Migo2.Async\Task\TaskCompletedEventArgs.cs" />
+ <Compile Include="Migo2.Async\Task\TaskEventArgs.cs" />
+ <Compile Include="Migo2.Async\Task\TaskState.cs" />
+ <Compile Include="Migo2.Async\Task\TaskStateChangedEventArgs.cs" />
+ <Compile Include="Migo2.Async\Task\IWaitableTask.cs" />
+ <Compile Include="Migo2.Async\TaskGroup\EventArgs\GroupStatusChangedEventArgs.cs" />
+ <Compile Include="Migo2.Async\TaskGroup\EventArgs\ManipulatedEventArgs.cs" />
+ <Compile Include="Migo2.Async\TaskGroup\EventArgs\ReorderedEventArgs.cs" />
+ <Compile Include="Migo2.Async\TaskGroup\EventArgs\TaskAddedEventArgs.cs" />
+ <Compile Include="Migo2.Async\TaskGroup\EventArgs\TaskProgressChangedEventArgs.cs" />
+ <Compile Include="Migo2.Async\TaskGroup\EventArgs\TaskRemovedEventArgs.cs" />
+ <Compile Include="Migo2.Async\TaskGroup\GroupProgressManager.cs" />
+ <Compile Include="Migo2.Async\TaskGroup\GroupStatusManager.cs" />
+ <Compile Include="Migo2.Async\TaskGroup\TaskGroup.cs" />
+ <Compile Include="Migo2.Async\TaskGroup\TaskGroup_Collection.cs" />
+ <Compile Include="Migo2.Async\AsyncStateManager.cs" />
+ <Compile Include="Migo2.Collections\Pair.cs" />
+ <Compile Include="Migo2.DownloadService\DownloadStatusManager.cs" />
+ <Compile Include="Migo2.DownloadService\DownloadTaskStatusUpdatedEventArgs.cs" />
+ <Compile Include="Migo2.DownloadService\HttpDownloadGroup.cs" />
+ <Compile Include="Migo2.DownloadService\HttpDownloadGroupStatusChangedEventArgs.cs" />
+ <Compile Include="Migo2.DownloadService\HttpDownloadManager.cs" />
+ <Compile Include="Migo2.DownloadService\HttpFileDownloadErrors.cs" />
+ <Compile Include="Migo2.DownloadService\HttpFileDownloadTask.cs" />
+ <Compile Include="Migo2.Net\AsyncWebClient\AsyncWebClient.cs" />
+ <Compile Include="Migo2.Net\AsyncWebClient\DownloadDataCompletedEventArgs.cs" />
+ <Compile Include="Migo2.Net\AsyncWebClient\DownloadProgressChangedEventArgs.cs" />
+ <Compile Include="Migo2.Net\AsyncWebClient\DownloadStatus.cs" />
+ <Compile Include="Migo2.Net\AsyncWebClient\DownloadStatusUpdatedEventArgs.cs" />
+ <Compile Include="Migo2.Net\AsyncWebClient\DownloadStringCompletedEventArgs.cs" />
+ <Compile Include="Migo2.Net\AsyncWebClient\RemoteFileModifiedException.cs" />
+ <Compile Include="Migo2.Net\AsyncWebClient\TransferStatusManager.cs" />
+ <Compile Include="Migo2.Net\AsyncWebClient\TransferStatusManager_Rate.cs" />
+ <Compile Include="Migo2.Utils\Rfc822DateTime.cs" />
+ <Compile Include="Migo2.Utils\UnitUtils.cs" />
+ <Compile Include="Migo2.Utils\XmlUtils.cs" />
+ <Compile Include="Migo2.Collections\OrderComparer.cs" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+ <ProjectExtensions>
+ <MonoDevelop>
+ <Properties>
+ <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="true" RelativeMakefileName="Makefile.am" IsAutotoolsProject="true" RelativeConfigureInPath="../../..">
+ <BuildFilesVar Sync="true" Name="SOURCES" />
+ <DeployFilesVar />
+ <ResourcesVar />
+ <OthersVar />
+ <GacRefVar />
+ <AsmRefVar />
+ <ProjectRefVar />
+ <MessageRegex Name="Vala" />
+ </MonoDevelop.Autotools.MakefileInfo>
+ </Properties>
+ </MonoDevelop>
+ </ProjectExtensions>
+</Project>
\ No newline at end of file
diff --git a/src/Libraries/Migo2/README.png b/src/Libraries/Migo2/README.png
new file mode 100644
index 0000000..5550fd9
Binary files /dev/null and b/src/Libraries/Migo2/README.png differ
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]