banshee r3927 - in trunk/banshee: . build src src/Clients/Nereid/Nereid src/Core/Banshee.Core/Banshee.Base src/Core/Banshee.Core/Banshee.Collection src/Core/Banshee.Core/Resources src/Core/Banshee.Services src/Core/Banshee.Services/Banshee.Collection src/Core/Banshee.Services/Banshee.Collection.Database src/Core/Banshee.Services/Banshee.Database src/Core/Banshee.Services/Banshee.Metadata src/Core/Banshee.Services/Banshee.Metadata.Embedded src/Core/Banshee.Services/Banshee.Metadata.MusicBrainz src/Core/Banshee.Services/Banshee.Playlist src/Core/Banshee.Services/Banshee.Sources src/Core/Banshee.ThickClient src/Core/Banshee.ThickClient/Banshee.Collection.Gui src/Core/Banshee.ThickClient/Banshee.Gui src/Core/Banshee.ThickClient/Banshee.Gui.Widgets src/Core/Banshee.ThickClient/Banshee.Sources.Gui src/Dap/Banshee.Dap.Ipod/Banshee.Dap.Ipod src/Dap/Banshee.Dap/Banshee.Dap src/Extensions/Banshee.AudioCd/Banshee.AudioCd src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio src/Ex tensions/Banshee.NotificationArea/Banshee.NotificationArea src/Extensions/Banshee.Podcasting src/Extensions/Banshee.Podcasting/Banshee.Podcasting src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Data src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/ColumnCells src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/Models src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/Views src/Extensions/Banshee.Podcasting/Resources src/Libraries/Hyena.Gui/Hyena.Data.Gui src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView src/Libraries/Hyena/Hyena src/Libraries/Hyena/Hyena.Collections src/Libraries/Hyena/Hyena.Data src/Libraries/Hyena/Hyena.Data.Sqlite src/Libraries/Hyena/Hyena.Query src/Librarie s/Migo src/Libraries/Migo/Migo/Migo.DownloadCore src/Libraries/Migo/Migo/Migo.Net src/Libraries/Migo/Migo/Migo.Syndication src/Libraries/Migo/Migo/Migo.Syndication/EventArgs src/Libraries/Migo/Migo/Migo.TaskCore src/Libraries/Migo/Migo/Migo.TaskCore/AsyncCommandQueue tests tests/Migo



Author: gburt
Date: Sun May 18 00:19:17 2008
New Revision: 3927
URL: http://svn.gnome.org/viewvc/banshee?rev=3927&view=rev

Log:
2008-05-17  Gabriel Burt  <gabriel burt gmail com>

	Massive commit that brings Podcasting back to the land of the living.  Our
	internal classes for doing track filtering and showing the browsers have
	been made generic, so that Podcasting can use all of them to show its
	Browser (with a single Podcast filter).  I still do NOT recommend users use
	this; it needs more work to migrate 0.13.2 podcasting data, and probably
	has bugs that will eat your children.

	* src/Core/Banshee.ThickClient/Banshee.Gui/ViewActions.cs:
	* src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackActions.cs:
	* src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackRepeatActions.cs:
	* src/Core/Banshee.ThickClient/Banshee.Gui/GlobalActions.cs:
	* src/Core/Banshee.ThickClient/Banshee.Gui/SourceActions.cs:
	* src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackShuffleActions.cs:
	* src/Extensions/Banshee.NotificationArea/Banshee.NotificationArea/NotificationAreaService.cs:
	* src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmActions.cs:
	Update to use new BansheeActionGroup ctor and properties.

	* src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdSource.cs:
	* src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationSource.cs:
	Implement empty FilterModels property.

	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastService.cs:
	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastService_Interface.cs:
	Listen to the events offered up in Migo, grab podcast artwork, and move
	most of the action-related code into PodcastActions.cs.

	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastImageFetchJob.cs:
	New MetadataServiceJob subclass for fetching podcast artwork.

	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastCore_Interface.cs:
	Renamed to PodcastService.

	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/ColumnCellPodcast.cs:
	Hot new renderer for podcasts, showing artwork, name, and when last
	updated.  Duplicates a ton of code with ColumnCellAlbum, should be
	refactored.

	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/ColumnCellPublished.cs:
	Override for ColumnCellDateTime to just print the short date.

	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastActions.cs:
	New class, a BansheeActionGroup subclass, for registering and handling
	podcast actions.  Quite a few actions aren't implemented yet (like mark
	new/old and delete podcast).

	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastFeedPropertiesDialog.cs:
	Get rid of bold titles, make casing consistent, don't put ""s around the
	description.

	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastSubscribeDialog.cs:
	Casing, grammer, get rid of expander.

	* src/Extensions/Banshee.Podcasting/Makefile.am:
	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Data/PodcastItem.cs:
	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Data/PodcastTrackInfo.cs:
	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastPropertiesDialog.cs:
	PodcastItem renamed to PodcastTrackInfo, pulls more data from FeedItem and
	the enclosure/file.

	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/ColumnCells/PixbufColumnCell.cs:
	Add new ctor required by new ColumnController business.

	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/ColumnCells/ItemActivityColumnCell.cs:
	Add new ctor, get rid of Video and New Item icons; just show download status

	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/PodcastSourceContents.cs:
	Change to be a ListBrowserSourceContents subclass, which is what
	CompositeTrackListView inherit from too.

	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/PodcastSource.cs:
	Change to inherit from LibrarySource, add a XML column controller property
	to add Published and Download Status columns.

	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/Models/PodcastFeedModel.cs:
	Change to inherit from DatabaseBrowsableListModel, which goes against the
	db and knows how to filter the track model.

	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/Views/PodcastFeedView.cs:
	Change to inherit from TrackFilterListView and to use the new Podcast
	renderer.

	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/Views/PodcastItemView.cs:
	Fix up columns, names.

	* src/Extensions/Banshee.Podcasting/Resources/ActiveSourceUI.xml: Use the
	normal TrackContextMenu for podcast items, but add some things to it when
	podcast source is active.

	* src/Extensions/Banshee.Podcasting/Resources/GlobalUI.xml: Get rid of
	PodcastItemViewPopup, add several normal Library-source context menu
	items, remove some items from PodcastFeedPopup that didn't make sense.

	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.addin.xml: Update
	copyright info.

	* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.mdp: Lots of new
	files, ref TagLib, System.Xml, gdk.

	* src/Clients/Nereid/Nereid/PlayerInterface.cs: Fix assumptions regarding
	composite_view, and get rid of IHasTrackSelection crap.

	* src/Core/Banshee.Services/Banshee.Metadata.Embedded/EmbeddedQueryJob.cs:
	* src/Core/Banshee.Core/Banshee.Collection/TrackInfo.cs:
	* src/Core/Banshee.Services/Banshee.Metadata.MusicBrainz/MusicBrainzQueryJob.cs:
	* src/Dap/Banshee.Dap.Ipod/Banshee.Dap.Ipod/IpodTrackInfo.cs:
	* src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/TrackInfoDisplay.cs:
	Rename TrackInfo.ArtistAlbumId to ArtworkId.

	* src/Dap/Banshee.Dap/Banshee.Dap/MediaGroupSource.cs: Fix delete from
	drive action label, reload after intialized.

	* src/Dap/Banshee.Dap/Banshee.Dap/DapSource.cs: Add the two media sources
	before loading, avoiding ThreadAssist nastiness.

	* src/nuke-gconf-keys: New scriptlet for clearing your banshee-1 gconf
	keys, for testing from a clean slate.

	* src/Core/Banshee.ThickClient/Banshee.Collection.Gui/XmlColumnController.cs:
	Add ability to set a default <sort-column>column-id</sort-column>.

	* src/Core/Banshee.ThickClient/Banshee.Collection.Gui/AlbumListView.cs:
	* src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtistListView.cs:
	Inherit from TrackFilterListView, getting rid of a lot of dupe code.

	* src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellFileSize.cs:
	New column cell renderer for file sizes.  Should be changed to always spit
	out one decimal point and to right align, for scanability.

	* src/Core/Banshee.ThickClient/Banshee.Collection.Gui/TrackListView.cs:
	Move RowActivated handler here from Nereid.PlayerInterface, so that any
	TrackListView instance/subclass will start playing when you double click
	on a row.

	* src/Core/Banshee.ThickClient/Banshee.Collection.Gui/TrackFilterListView.cs:
	New class, refactored Album and Artist code here.

	* src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellDuration.cs:
	Don't display anything if duration is zero.

	* src/Core/Banshee.ThickClient/Banshee.Collection.Gui/DefaultColumnController.cs:
	Add FileSize column, make duration column a bit smaller by default.

	* src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellDateTime.cs:
	Add DateTimeFormat to control what format of date/time string is used.

	* src/Core/Banshee.ThickClient/Banshee.ThickClient.mdp: Add new files.

	* src/Core/Banshee.ThickClient/Banshee.Gui/BansheeActionGroup.cs: Add
	ShowContextMenu method, take the interface_action_service in the ctor and
	expose through Actions property.

	* src/Core/Banshee.ThickClient/Banshee.Gui/TrackActions.cs: Get rid of
	awful TrackSelector crap.

	* src/Core/Banshee.ThickClient/Makefile.am: Updates.

	* src/Core/Banshee.ThickClient/Banshee.Sources.Gui/ListBrowserSourceContents.cs:
	Refactored out of CompositeTrackSourceContents, made generic so can be
	used with any ITrackModelSource with its arbitrary FilterModels.  Means we
	can have different types of browsers for different sources (Music Library
	vs Podcasts, for instance) and they share a lot of code, and the top/left
	preference.

	* src/Core/Banshee.ThickClient/Banshee.Sources.Gui/CompositeTrackSourceContents.cs:
	Inherit from ListBrowserSourceContents.

	* src/Core/Banshee.Services/Banshee.Metadata/MetadataServiceJob.cs: Fix
	bug where files were used before they were fully downloaded by saving to a
	temp file and moving once finished writing.

	* src/Core/Banshee.Services/Banshee.Sources/Source.cs:
	* src/Core/Banshee.Services/Banshee.Sources/DatabaseSource.cs:
	* src/Core/Banshee.Services/Banshee.Sources/ITrackModelSource.cs: Get rid
	of AlbumModel and ArtistModel, replaced by generic FilterModels.

	* src/Core/Banshee.Services/Banshee.Playlist/AbstractPlaylistSource.cs:
	* src/Core/Banshee.Services/Banshee.Sources/PrimarySource.cs: Add the
	conditions intead of setting them, meaning we can play nicely with
	other conditions instead of racing.

	* src/Core/Banshee.Services/Banshee.Collection/BrowsableListModel.cs: New
	abstract subclass of BansheeListModel, that defines the Selection as a
	SelectAllSelection.

	* src/Core/Banshee.Services/Banshee.Collection/BansheeListModel.cs: Add
	FocusedItem getter, and make RaiseReloaded public.

	* src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumListModel.cs:
	* src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistListModel.cs:
	Inherit from DatabaseBrowsableListModel

	* src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseBrowsableListModel.cs:
	New abstract class for filter models.

	* src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs:
	Sort of lame, but put the actual Provider.Save/Refresh calls inside
	virtual methods so that PodcastTrackInfo can use its own provider.

	* src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackListModel.cs:
	Get rid of ReloadTrigger enum, replaced by just passing in the actual
	IFilterModel that triggered the reload (if any).  Reload algorithm made
	generic, uses the ITrackModelSource's list of FilterModels, so will work
	with 0 - N filter models (used by PodcastSource to filter just on Podcast,
	atm).  Get rid of built-in album/artist assumptions.

	* src/Core/Banshee.Services/Banshee.Services.mdp: New files.

	* src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs:
	New migration, drop any podcast tables created using the 1.0 series
	- 0.13.2 data shouldn't be affected - so we can start over.  Add an
	ExternalId column to CoreTracks.

	* src/Core/Banshee.Services/Banshee.Database/BansheeModelCache.cs: Don't
	require BansheeModelProvider since SqliteModelProvider suffices fine.

	* src/Core/Banshee.Services/Makefile.am: New files.

	* src/Core/Banshee.Core/Resources/translators.xml: Updated.

	* src/Core/Banshee.Core/Banshee.Base/CoverArtSpec.cs: Make the actual
	escaping code public (so we can use it to make podcast artwork ids).  Log
	a message stating where the album art directory is.

	* src/Libraries/Hyena/Hyena.Collections/Selection.cs: Add a FocusedIndex
	property, factored out of ListView, so that we can get to it so long as we
	can get to the Selection, instead of having to get to the ListView.

	* src/Libraries/Hyena/Hyena/Log.cs: Add WarningFormat.

	* src/Libraries/Hyena/Hyena.Query/RelativeTimeSpanQueryValue.cs: Added
	convenience factory method RelativeToNow.

	* src/Libraries/Hyena/Hyena.Query/TimeSpanQueryValue.cs: Determine the
	best factor after having our value set.

	* src/Libraries/Hyena/Hyena.Data/IFilterable.cs: Rename Filter to
	UserQuery.

	* src/Libraries/Hyena/Hyena.Data/IListModel.cs: Add non-generic IListModel
	interface.

	* src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteModelProvider.cs: Make Save,
	Load, and Delete methods virtual, change FetchFirstMatching to not
	duplicate FetchAllMatching code, change both to accept params, fix bug
	with Refresh, and add protected PrimaryKeyFor method.

	* src/Libraries/Hyena.Gui/Hyena.Data.Gui/IListView.cs: Add non-generic
	interface.

	* src/Libraries/Hyena.Gui/Hyena.Data.Gui/ColumnCell.cs: Add useful
	debugging logging if a specified property doesn't exist.

	* src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Rendering.cs:
	* src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Interaction.cs:
	Change to use Selection.FocusedIndex.

	* src/Libraries/Migo/Migo.mdp:
	* src/Libraries/Migo/Makefile.am: New files, only build if
	--enable-podcast set.

	* src/Libraries/Migo/Migo/Migo.Net/AsyncWebClient.cs: Add a public static
	UsreAgent property so we can set it to Banshee.Web.Browser.UserAgent.  Get
	rid of many try {} catch {} statements to avoid swallowing exceptions.

	* src/Libraries/Migo/Migo/Migo.TaskCore/AsyncCommandQueue/AsyncCommandQueue.cs:
	* src/Libraries/Migo/Migo/Migo.DownloadCore/DownloadManager.cs:
	* src/Libraries/Migo/Migo/Migo.TaskCore/TaskGroup.cs: Fix formatting,
	logging.

	* src/Libraries/Migo/Migo/Migo.DownloadCore/HttpFileDownloadTask.cs: Don't
	set the UserAgent here.

	* src/Libraries/Migo/Migo/Migo.Syndication/FeedsManager.cs: Separated most
	of this code out into FeedManager and EnclosureManager.  This class was
	trying to be too many things to too many users.

	* src/Libraries/Migo/Migo/Migo.Syndication/XmlUtils.cs: Add
	GetNamespaceManager method, and GetITunesDuration method.

	* src/Libraries/Migo/Migo/Migo.Syndication/FeedItem.cs: Default active to
	true, change provider to be a MigoModelProvider, add static Exists (guid)
	method, many of fixes.

	* src/Libraries/Migo/Migo/Migo.Syndication/FeedEnclosure.cs: Change
	provider and inherit from MigoItem, fix up SetFileImpl method, database
	properties, etc.

	* src/Libraries/Migo/Migo/Migo.Syndication/Feed.cs: Inherit from MigoItem,
	uset MigoModelProvider, fix up db properties, add method that starts
	automatically downloading any new items when appropriate.

	* src/Libraries/Migo/Migo/Migo.Syndication/Rfc822DateTime.cs: Fix so
	parses some dates it wasn't parsing before.

	* src/Libraries/Migo/Migo/Migo.Syndication/OpmlParser.cs: New class, not
	at all finished, for parsing OPML files.

	* src/Libraries/Migo/Migo/Migo.Syndication/RssParser.cs: Get the image url
	from itunes:image first, since it's probably higher quality.  Get the
	keywords and category properties too.  Get the duration for enclosures, if
	set.

	* src/Libraries/Migo/Migo/Migo.Syndication/MigoModelProvider.cs: A
	subclass of SqliteModelProvider with an interesting twist: every item that
	is retrieved is cached in a Dictionary indexed by DbId, which allows us to
	combine the power of Migo's downloading ability (which wouldn't work if
	there were multiple instances of a FeedItem, for example, that all
	represented the same actual item/db row, with the power of
	SqliteModelProvider for finding items.  So you can do
	Feed.Provider.FetchAllMatch ("LastDownloadedAt < ?', some datetime) and
	be returned the already-instantiated objects.

	* src/Libraries/Migo/Migo/Migo.Syndication/FeedUpdateTask.cs: Fixed up.

	* src/Libraries/Migo/Migo/Migo.Syndication/EventArgs/FeedItemEventArgs.cs:
	Chagned, but I don't think this is used anymore.  Should be culled.

	* src/Libraries/Migo/Migo/Migo.Syndication/FeedManager.cs:
	* src/Libraries/Migo/Migo/Migo.Syndication/EnclosureManager.cs: New
	classes taking over for things FeedsManager did before (namely, managing
	downloads and firing off events).

	* src/Libraries/Migo/Migo/Migo.Syndication/MigoItem.cs: Simple abstract
	class that implements ICacheableItem and has an abstract DbId property.

	* build/build.environment.mk: Tidy up MIGO deps.

	* tests/Makefile.am: Add Migo/XmlTests.

	* tests/Migo/XmlTests.cs: Add tests for itunes:duration parsing and
	Rfc822DateTime parsing.

	* tests/BansheeTests.cs: add TransformPair struct.


Added:
   trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseBrowsableListModel.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Collection/BrowsableListModel.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellFileSize.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/TrackFilterListView.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Sources.Gui/ListBrowserSourceContents.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Data/PodcastTrackInfo.cs   (contents, props changed)
      - copied, changed from r3916, /trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Data/PodcastItem.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/ColumnCellPodcast.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/ColumnCellPublished.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastActions.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastImageFetchJob.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastService_Interface.cs   (contents, props changed)
      - copied, changed from r3911, /trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastCore_Interface.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/EnclosureManager.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedManager.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/MigoItem.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/MigoModelProvider.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/OpmlParser.cs
   trunk/banshee/src/nuke-gconf-keys   (contents, props changed)
   trunk/banshee/tests/Migo/
   trunk/banshee/tests/Migo/XmlTests.cs
Removed:
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Data/PodcastItem.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastCore_Interface.cs
Modified:
   trunk/banshee/ChangeLog
   trunk/banshee/build/build.environment.mk
   trunk/banshee/src/Clients/Nereid/Nereid/PlayerInterface.cs
   trunk/banshee/src/Core/Banshee.Core/Banshee.Base/CoverArtSpec.cs
   trunk/banshee/src/Core/Banshee.Core/Banshee.Collection/TrackInfo.cs
   trunk/banshee/src/Core/Banshee.Core/Resources/translators.xml
   trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumListModel.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistListModel.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackListModel.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Collection/BansheeListModel.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Database/BansheeModelCache.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Metadata.Embedded/EmbeddedQueryJob.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Metadata.MusicBrainz/MusicBrainzQueryJob.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Metadata/MetadataServiceJob.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Playlist/AbstractPlaylistSource.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Services.mdp
   trunk/banshee/src/Core/Banshee.Services/Banshee.Sources/DatabaseSource.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Sources/ITrackModelSource.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Sources/PrimarySource.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Sources/Source.cs
   trunk/banshee/src/Core/Banshee.Services/Makefile.am
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/AlbumListView.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtistListView.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellDateTime.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellDuration.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/DefaultColumnController.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/TrackListView.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/XmlColumnController.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/TrackInfoDisplay.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/BansheeActionGroup.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/GlobalActions.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackActions.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackRepeatActions.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackShuffleActions.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/SourceActions.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/TrackActions.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/ViewActions.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Sources.Gui/CompositeTrackSourceContents.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.ThickClient.mdp
   trunk/banshee/src/Core/Banshee.ThickClient/Makefile.am
   trunk/banshee/src/Dap/Banshee.Dap.Ipod/Banshee.Dap.Ipod/IpodTrackInfo.cs
   trunk/banshee/src/Dap/Banshee.Dap/Banshee.Dap/DapSource.cs
   trunk/banshee/src/Dap/Banshee.Dap/Banshee.Dap/MediaGroupSource.cs
   trunk/banshee/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdSource.cs
   trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmActions.cs
   trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationSource.cs
   trunk/banshee/src/Extensions/Banshee.NotificationArea/Banshee.NotificationArea/NotificationAreaService.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastFeedPropertiesDialog.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastPropertiesDialog.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastSubscribeDialog.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/ColumnCells/ItemActivityColumnCell.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/ColumnCells/PixbufColumnCell.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/Models/PodcastFeedModel.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/PodcastSource.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/PodcastSourceContents.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/Views/PodcastFeedView.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/Views/PodcastItemView.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.addin.xml
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.mdp
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastService.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Makefile.am
   trunk/banshee/src/Extensions/Banshee.Podcasting/Resources/ActiveSourceUI.xml
   trunk/banshee/src/Extensions/Banshee.Podcasting/Resources/GlobalUI.xml
   trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ColumnCell.cs
   trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Data.Gui/IListView.cs
   trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Interaction.cs
   trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Rendering.cs
   trunk/banshee/src/Libraries/Hyena/Hyena.Collections/Selection.cs
   trunk/banshee/src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteModelProvider.cs
   trunk/banshee/src/Libraries/Hyena/Hyena.Data/IFilterable.cs
   trunk/banshee/src/Libraries/Hyena/Hyena.Data/IListModel.cs
   trunk/banshee/src/Libraries/Hyena/Hyena.Query/RelativeTimeSpanQueryValue.cs
   trunk/banshee/src/Libraries/Hyena/Hyena.Query/TimeSpanQueryValue.cs
   trunk/banshee/src/Libraries/Hyena/Hyena/Log.cs
   trunk/banshee/src/Libraries/Migo/Makefile.am
   trunk/banshee/src/Libraries/Migo/Migo.mdp
   trunk/banshee/src/Libraries/Migo/Migo/Migo.DownloadCore/DownloadManager.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.DownloadCore/HttpFileDownloadTask.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.Net/AsyncWebClient.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/EventArgs/FeedItemEventArgs.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/Feed.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedEnclosure.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedItem.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedUpdateTask.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedsManager.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/Rfc822DateTime.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/RssParser.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/XmlUtils.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.TaskCore/AsyncCommandQueue/AsyncCommandQueue.cs
   trunk/banshee/src/Libraries/Migo/Migo/Migo.TaskCore/TaskGroup.cs
   trunk/banshee/tests/BansheeTests.cs
   trunk/banshee/tests/Makefile.am

Modified: trunk/banshee/build/build.environment.mk
==============================================================================
--- trunk/banshee/build/build.environment.mk	(original)
+++ trunk/banshee/build/build.environment.mk	Sun May 18 00:19:17 2008
@@ -50,7 +50,7 @@
 LINK_LASTFM_GUI = -r:$(DIR_BIN)/Lastfm.Gui.dll
 LINK_LASTFM_GUI_DEPS = $(REF_LASTFM_GUI) $(LINK_LASTFM_GUI)
 
-REF_MIGO = $(LINK_SYSTEM) $(LINK_SYSTEM_WEB) $(LINK_SQLITE) $(LINK_HYENA)
+REF_MIGO = $(LINK_HYENA_DEPS) $(LINK_SYSTEM_WEB)
 LINK_MIGO = -r:$(DIR_BIN)/Migo.dll
 LINK_MIGO_DEPS = $(REF_MIGO) $(LINK_MIGO)
 

Modified: trunk/banshee/src/Clients/Nereid/Nereid/PlayerInterface.cs
==============================================================================
--- trunk/banshee/src/Clients/Nereid/Nereid/PlayerInterface.cs	(original)
+++ trunk/banshee/src/Clients/Nereid/Nereid/PlayerInterface.cs	Sun May 18 00:19:17 2008
@@ -54,7 +54,7 @@
 
 namespace Nereid
 {
-    public class PlayerInterface : BaseClientWindow, IService, IDisposable, IHasTrackSelection, IHasSourceView
+    public class PlayerInterface : BaseClientWindow, IService, IDisposable, IHasSourceView
     {
         // Major Layout Components
         private VBox primary_vbox;
@@ -77,8 +77,7 @@
         {
             BuildPrimaryLayout ();
             ConnectEvents ();
-            
-            ActionService.TrackActions.TrackSelector = this;
+
             ActionService.SourceActions.SourceView = this;
             
             composite_view.TrackView.HasFocus = true;
@@ -252,14 +251,6 @@
                 SourceViewWidth.Set (views_pane.Position);
             };
             
-            composite_view.TrackView.RowActivated += delegate (object o, RowActivatedArgs<TrackInfo> args) {
-                Source source = ServiceManager.SourceManager.ActiveSource;
-                if (source is ITrackModelSource) {
-                    ServiceManager.PlaybackController.Source = (ITrackModelSource)source;
-                    ServiceManager.PlayerEngine.OpenPlay (args.RowValue);
-                }
-            };
-
             source_view.RowActivated += delegate {
                 Source source = ServiceManager.SourceManager.ActiveSource;
                 if (source is ITrackModelSource) {
@@ -289,6 +280,7 @@
             view_container.SearchEntry.Ready = true;
         }
         
+        private TrackListModel previous_track_model = null;
         private void OnActiveSourceChanged (SourceEventArgs args)
         {
             Source source = ServiceManager.SourceManager.ActiveSource;
@@ -312,19 +304,24 @@
                 view_container.Content.ResetSource ();
             }
 
+            if (previous_track_model != null) {
+                previous_track_model.Reloaded -= HandleTrackModelReloaded;
+                previous_track_model = null;
+            }
+
+            if (source is ITrackModelSource) {
+                previous_track_model = (source as ITrackModelSource).TrackModel;
+                previous_track_model.Reloaded += HandleTrackModelReloaded;
+            }
+
             // Connect the source models to the views if possible
             if (source.Properties.Contains ("Nereid.SourceContents")) {
                 view_container.Content = source.Properties.Get<ISourceContents> ("Nereid.SourceContents");
                 view_container.Content.SetSource (source);
                 view_container.Show ();
             } else if (source is ITrackModelSource) {
-                if (composite_view.TrackModel != null) {
-                    composite_view.TrackModel.Reloaded -= HandleTrackModelReloaded;
-                }
                 composite_view.SetSource (source);
-                composite_view.TrackModel.Reloaded += HandleTrackModelReloaded;
-                PersistentColumnController column_controller = 
-                    composite_view.TrackView.ColumnController as PersistentColumnController;
+                PersistentColumnController column_controller = composite_view.TrackView.ColumnController as PersistentColumnController;
                 if (column_controller != null) {
                     column_controller.Source = source;
                 }
@@ -407,7 +404,7 @@
 #region Implement Interfaces
 
         // IHasTrackSelection
-        public IEnumerable<TrackInfo> GetSelectedTracks ()
+        /*public IEnumerable<TrackInfo> GetSelectedTracks ()
         {
             return new ModelSelection<TrackInfo> (composite_view.TrackModel, composite_view.TrackView.Selection);
         }
@@ -418,7 +415,7 @@
 
         public DatabaseTrackListModel TrackModel {
             get { return composite_view.TrackModel as DatabaseTrackListModel; }
-        }
+        }*/
 
         // IHasSourceView
         public Source HighlightedSource {

Modified: trunk/banshee/src/Core/Banshee.Core/Banshee.Base/CoverArtSpec.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Core/Banshee.Base/CoverArtSpec.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Core/Banshee.Base/CoverArtSpec.cs	Sun May 18 00:19:17 2008
@@ -68,15 +68,15 @@
         
         public static string CreateArtistAlbumId (string artist, string album, bool asUriPart)
         {
-            string sm_artist = CreateArtistAlbumIdPart (artist);
-            string sm_album = CreateArtistAlbumIdPart (album);
+            string sm_artist = EscapePart (artist);
+            string sm_album = EscapePart (album);
             
             return sm_artist == null || sm_album == null 
                 ? null 
                 : String.Format ("{0}{1}{2}", sm_artist, asUriPart ? "/" : "-", sm_album); 
         }
         
-        private static string CreateArtistAlbumIdPart (string part)
+        public static string EscapePart (string part)
         {
             if (String.IsNullOrEmpty (part)) {
                 return null;
@@ -93,6 +93,10 @@
         private static string root_path = Path.Combine (XdgBaseDirectorySpec.GetUserDirectory (
             "XDG_CACHE_HOME", ".cache"),  "album-art");
             
+        static CoverArtSpec () {
+            Hyena.Log.DebugFormat ("Album artwork path set to {0}", root_path);
+        }
+            
         public static string RootPath {
             get { return root_path; }
         }

Modified: trunk/banshee/src/Core/Banshee.Core/Banshee.Collection/TrackInfo.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Core/Banshee.Collection/TrackInfo.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Core/Banshee.Collection/TrackInfo.cs	Sun May 18 00:19:17 2008
@@ -109,7 +109,7 @@
                 return false;
             }
             
-            return ArtistAlbumId == track.ArtistAlbumId;
+            return ArtworkId == track.ArtworkId;
         }
 
         public virtual void Save ()
@@ -196,7 +196,7 @@
             } 
         }     
         
-        public string ArtistAlbumId { 
+        public virtual string ArtworkId { 
             get { return CoverArtSpec.CreateArtistAlbumId (ArtistName, AlbumTitle); }
         }
 

Modified: trunk/banshee/src/Core/Banshee.Core/Resources/translators.xml
==============================================================================
--- trunk/banshee/src/Core/Banshee.Core/Resources/translators.xml	(original)
+++ trunk/banshee/src/Core/Banshee.Core/Resources/translators.xml	Sun May 18 00:19:17 2008
@@ -99,6 +99,7 @@
     <person>Filipe Gomes</person>
   </language>
   <language code="pt_BR" name="Brazilian Portuguese">
+    <person>Rodrigo Flores</person>
     <person>Marco Carvalho</person>
     <person>Og Maciel</person>
     <person>Evandro Fernandes Giovanini</person>

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumListModel.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumListModel.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumListModel.cs	Sun May 18 00:19:17 2008
@@ -39,142 +39,32 @@
 
 namespace Banshee.Collection.Database
 {
-    public class DatabaseAlbumListModel : AlbumListModel, ICacheableDatabaseModel
+    public class DatabaseAlbumListModel : DatabaseBrowsableListModel<DatabaseAlbumInfo, AlbumInfo>
     {
-        private readonly BansheeModelProvider<DatabaseAlbumInfo> provider;
-        private readonly BansheeModelCache<DatabaseAlbumInfo> cache;
-        private readonly DatabaseTrackListModel track_model;
-        private readonly DatabaseArtistListModel artist_model;
-        private long count;
-        private string artist_id_filter_query;
-        private string reload_fragment;
-        
-        private readonly AlbumInfo select_all_album = new AlbumInfo (null);
-        
-        public DatabaseAlbumListModel (BansheeDbConnection connection, string uuid)
-        {
-            provider = DatabaseAlbumInfo.Provider;
-            cache = new BansheeModelCache <DatabaseAlbumInfo> (connection, uuid, this, provider);
-            cache.HasSelectAllItem = true;
-
-            Selection.Changed += HandleSelectionChanged;
-        }
-
-        public DatabaseAlbumListModel (DatabaseTrackListModel trackModel, DatabaseArtistListModel artistModel,
-                BansheeDbConnection connection, string uuid) : this (connection, uuid)
-        {
-            this.track_model = trackModel;
-            this.artist_model = artistModel;
-        }
-
-        private void HandleSelectionChanged (object sender, EventArgs args)
+        public DatabaseAlbumListModel (DatabaseTrackListModel trackModel, BansheeDbConnection connection, string uuid) 
+            : base (trackModel, connection, DatabaseAlbumInfo.Provider, new AlbumInfo (null), uuid)
         {
-            track_model.Reload (ReloadTrigger.AlbumFilter);
-        }
-
-        public override void Reload ()
-        {
-            Reload (false);
-        }
-
-        internal void Reload (bool notify)
-        {
-            ArtistInfoFilter = artist_model == null ? null : artist_model.SelectedItems;
-
-            bool either = (artist_id_filter_query != null) || (track_model != null);
-            bool both = (artist_id_filter_query != null) && (track_model != null);
-
-            reload_fragment = String.Format (@"
+            ReloadFragmentFormat = @"
                 FROM CoreAlbums INNER JOIN CoreArtists ON CoreAlbums.ArtistID = CoreArtists.ArtistID
-                    {0} {1} {2} {3} ORDER BY CoreAlbums.TitleLowered, CoreArtists.NameLowered",
-                either ? "WHERE" : null,
-                track_model == null ? null :
-                    String.Format (@"
-                        CoreAlbums.AlbumID IN
-                            (SELECT CoreTracks.AlbumID FROM CoreTracks, CoreCache{1}
-                                WHERE CoreCache.ModelID = {0} AND
-                                      CoreCache.ItemId = {2})",
-                        track_model.CacheId,
-                        track_model.CachesJoinTableEntries ? track_model.JoinFragment : null,
-                        (!track_model.CachesJoinTableEntries)
-                            ? "CoreTracks.TrackID"
-                            : String.Format ("{0}.{1} AND CoreTracks.TrackID = {0}.{2}", track_model.JoinTable, track_model.JoinPrimaryKey, track_model.JoinColumn)
-                        ),
-                both ? "AND" : null,
-                artist_id_filter_query
-            );
-            //Console.WriteLine ("reload fragment for albums is {0}", reload_fragment);
-
-            cache.SaveSelection ();
-            cache.Reload ();
-            cache.UpdateAggregates ();
-            cache.RestoreSelection ();
-
-            count = cache.Count + 1;
-            select_all_album.Title = String.Format ("All Albums ({0})", count - 1);
-
-            if (notify)
-                OnReloaded ();
+                    WHERE CoreAlbums.AlbumID IN
+                        (SELECT CoreTracks.AlbumID FROM CoreTracks, CoreCache{0}
+                            WHERE CoreCache.ModelID = {1} AND
+                                  CoreCache.ItemId = {2})
+                    ORDER BY CoreAlbums.TitleLowered, CoreArtists.NameLowered";
         }
         
-        public override AlbumInfo this[int index] {
-            get {
-                if (index == 0)
-                    return select_all_album;
-
-                return cache.GetValue (index - 1);
-            }
+        public override string FilterColumn {
+            get { return "CoreTracks.AlbumID"; }
         }
         
-        public override IEnumerable<ArtistInfo> ArtistInfoFilter {
-            set {
-                ModelHelper.BuildIdFilter<ArtistInfo> (value, "CoreAlbums.ArtistID", artist_id_filter_query,
-                    delegate (ArtistInfo artist) {
-                        if (!(artist is DatabaseArtistInfo)) {
-                            return null;
-                        }
-                        
-                        return ((DatabaseArtistInfo)artist).DbId.ToString ();
-                    },
-                
-                    delegate (string new_filter) {
-                        artist_id_filter_query = new_filter;
-                    }
-                );
-            }
-        }
-
-        public override int Count { 
-            get { return (int) count; }
-        }
-
-        // Implement ICacheableModel
-        public int FetchCount {
-            get { return 20; }
-        }
-
-        public string SelectAggregates { get { return null; } }
-
-        //private const string primary_key = "CoreAlbums.AlbumID";
-
-        public string ReloadFragment {
-            get { return reload_fragment; }
-        }
-
-        public int CacheId {
-            get { return (int) cache.CacheId; }
+        public override string ItemToFilterValue (object item)
+        {
+            return (item is DatabaseAlbumInfo) ? (item as DatabaseAlbumInfo).DbId.ToString () : null;
         }
-
-        public void InvalidateCache ()
+        
+        public override void UpdateSelectAllItem (long count)
         {
-            cache.ClearManagedCache ();
-            OnReloaded ();
+            select_all_item.Title = String.Format ("All Albums ({0})", count);
         }
-
-        public string JoinTable { get { return null; } }
-        public string JoinFragment { get { return null; } }
-        public string JoinPrimaryKey { get { return null; } }
-        public string JoinColumn { get { return null; } }
-        public bool CachesJoinTableEntries { get { return false; } }
     }
 }

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistListModel.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistListModel.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistListModel.cs	Sun May 18 00:19:17 2008
@@ -38,107 +38,32 @@
 
 namespace Banshee.Collection.Database
 {
-    public class DatabaseArtistListModel : ArtistListModel, ICacheableDatabaseModel
+    public class DatabaseArtistListModel : DatabaseBrowsableListModel<DatabaseArtistInfo, ArtistInfo>
     {
-        private readonly BansheeModelProvider<DatabaseArtistInfo> provider;
-        private readonly BansheeModelCache<DatabaseArtistInfo> cache;
-        private readonly DatabaseTrackListModel track_model;
-        private string reload_fragment;
-        private long count;
-        
-        private readonly ArtistInfo select_all_artist = new ArtistInfo(null);
-        
-        public DatabaseArtistListModel (BansheeDbConnection connection, string uuid)
-        {
-            provider = DatabaseArtistInfo.Provider;
-            cache = new BansheeModelCache <DatabaseArtistInfo> (connection, uuid, this, provider);
-            cache.HasSelectAllItem = true;
-
-            Selection.Changed += HandleSelectionChanged;
-        }
-
-        public DatabaseArtistListModel(DatabaseTrackListModel trackModel, BansheeDbConnection connection, string uuid) : this (connection, uuid)
-        {
-            this.track_model = trackModel;
-        }
-
-        private void HandleSelectionChanged (object sender, EventArgs args)
-        {
-            track_model.Reload (ReloadTrigger.ArtistFilter);
-        }
-
-        public override void Reload ()
+        public DatabaseArtistListModel (DatabaseTrackListModel trackModel, BansheeDbConnection connection, string uuid) 
+            : base (trackModel, connection, DatabaseArtistInfo.Provider, new ArtistInfo (null), uuid)
         {
-            Reload (true);
-        }
-    
-        internal void Reload (bool notify)
-        {
-            reload_fragment = String.Format (
-                "FROM CoreArtists {0} ORDER BY NameLowered",
-                track_model == null ? null : String.Format (@"
+            ReloadFragmentFormat = @"
+                FROM CoreArtists 
                     WHERE CoreArtists.ArtistID IN
-                        (SELECT CoreTracks.ArtistID FROM CoreTracks, CoreCache{1}
-                            WHERE CoreCache.ModelID = {0} AND
-                                  CoreCache.ItemID = {2})",
-                    track_model.CacheId,
-                    track_model.CachesJoinTableEntries ? track_model.JoinFragment : null,
-                    (!track_model.CachesJoinTableEntries)
-                        ? "CoreTracks.TrackID"
-                        : String.Format ("{0}.{1} AND CoreTracks.TrackID = {0}.{2}", track_model.JoinTable, track_model.JoinPrimaryKey, track_model.JoinColumn)
-                )
-            );
-
-            cache.SaveSelection ();
-            cache.Reload ();
-            cache.UpdateAggregates ();
-            cache.RestoreSelection ();
-
-            count = cache.Count + 1;
-            select_all_artist.Name = String.Format("All Artists ({0})", count - 1);
-
-            if (notify)
-                OnReloaded();
-        }
-
-        public override ArtistInfo this[int index] {
-            get {
-                if (index == 0)
-                    return select_all_artist;
-
-                return cache.GetValue (index - 1);
-            }
+                        (SELECT CoreTracks.ArtistID FROM CoreTracks, CoreCache{0}
+                            WHERE CoreCache.ModelID = {1} AND
+                                  CoreCache.ItemID = {2})
+                    ORDER BY NameLowered";
         }
-
-        public override int Count { 
-            get { return (int) count; }
-        }
-
-        public int CacheId {
-            get { return (int) cache.CacheId; }
+        
+        public override string FilterColumn {
+            get { return "CoreTracks.ArtistID"; }
         }
-
-        public void InvalidateCache ()
+        
+        public override string ItemToFilterValue (object item)
         {
-            cache.ClearManagedCache ();
-            OnReloaded ();
-        }
-
-        // Implement ICacheableModel
-        public int FetchCount {
-            get { return 20; }
+            return (item is DatabaseArtistInfo) ? (item as DatabaseArtistInfo).DbId.ToString () : null;
         }
-
-        public string SelectAggregates { get { return null; } }
         
-        public string ReloadFragment {
-            get { return reload_fragment; }
+        public override void UpdateSelectAllItem (long count)
+        {
+            select_all_item.Name = String.Format ("All Artists ({0})", count);
         }
-
-        public string JoinTable { get { return null; } }
-        public string JoinFragment { get { return null; } }
-        public string JoinPrimaryKey { get { return null; } }
-        public string JoinColumn { get { return null; } }
-        public bool CachesJoinTableEntries { get { return false; } }
     }
-}
+}
\ No newline at end of file

Added: trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseBrowsableListModel.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseBrowsableListModel.cs	Sun May 18 00:19:17 2008
@@ -0,0 +1,178 @@
+//
+// DatabaseBrowsableListModel.cs
+//
+// Author:
+//   Aaron Bockover <abockover novell com>
+//   Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2007 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.Data;
+using System.Text;
+using System.Collections.Generic;
+
+using Hyena;
+using Hyena.Data.Sqlite;
+
+using Banshee.Collection;
+using Banshee.Database;
+
+namespace Banshee.Collection.Database
+{
+    public interface IFilterListModel : Hyena.Data.IListModel
+    {
+        string FilterColumn { get; }
+        string ItemToFilterValue (object item);
+        void RaiseReloaded ();
+        void Reload (bool notify);
+
+        IEnumerable<object> GetSelectedObjects ();
+    }
+    
+    public abstract class DatabaseBrowsableListModel<T, U> : BrowsableListModel<U>, IFilterListModel, ICacheableDatabaseModel
+        where T : ICacheableItem, U, new()
+    {
+        private readonly BansheeModelCache<T> cache;
+        private readonly DatabaseTrackListModel browsing_model;
+        
+        private long count;
+        private string reload_fragment;
+        
+        private string reload_fragment_format;
+        protected string ReloadFragmentFormat {
+            get { return reload_fragment_format; }
+            set { reload_fragment_format = value; }
+        }
+        
+        protected readonly U select_all_item;
+
+        public DatabaseBrowsableListModel (DatabaseTrackListModel trackModel, BansheeDbConnection connection, SqliteModelProvider<T> provider, U selectAllItem, string uuid)
+            : base ()
+        {
+            browsing_model = trackModel;
+            select_all_item = selectAllItem;
+            
+            cache = new BansheeModelCache <T> (connection, uuid, this, provider);
+            cache.HasSelectAllItem = true;
+
+            Selection.Changed += HandleSelectionChanged;
+        }
+        
+#region IFilterModel<T> Implementation
+
+        public abstract string FilterColumn { get; }
+        public abstract string ItemToFilterValue (object item);
+
+#endregion
+
+        public IEnumerable<object> GetSelectedObjects ()
+        {
+            foreach (object o in SelectedItems) {
+                yield return o;
+            }
+        }
+
+        private void HandleSelectionChanged (object sender, EventArgs args)
+        {
+            browsing_model.Reload (this);
+        }
+
+        public override void Reload ()
+        {
+            Reload (false);
+        }
+        
+        protected virtual void GenerateReloadFragment ()
+        {
+            ReloadFragment = String.Format (
+                ReloadFragmentFormat,
+                browsing_model.CachesJoinTableEntries ? browsing_model.JoinFragment : null,
+                browsing_model.CacheId,
+                browsing_model.CachesJoinTableEntries
+                    ? String.Format ("{0}.{1} AND CoreTracks.TrackID = {0}.{2}", browsing_model.JoinTable, browsing_model.JoinPrimaryKey, browsing_model.JoinColumn)
+                    : "CoreTracks.TrackID"
+            );
+        }
+        
+        public abstract void UpdateSelectAllItem (long count);
+
+        public void Reload (bool notify)
+        {
+            GenerateReloadFragment ();
+
+            cache.SaveSelection ();
+            cache.Reload ();
+            cache.UpdateAggregates ();
+            cache.RestoreSelection ();
+
+            count = cache.Count + 1;
+            
+            UpdateSelectAllItem (count - 1);
+
+            if (notify)
+                OnReloaded ();
+        }
+        
+        public override U this[int index] {
+            get {
+                if (index == 0)
+                    return select_all_item;
+
+                return cache.GetValue (index - 1);
+            }
+        }
+        
+        public override int Count { 
+            get { return (int) count; }
+        }
+
+        // Implement ICacheableModel
+        public virtual int FetchCount {
+            get { return 20; }
+        }
+
+        public virtual string SelectAggregates { get { return null; } }
+
+        public string ReloadFragment {
+            get { return reload_fragment; }
+            protected set { reload_fragment = value; }
+        }
+
+        public int CacheId {
+            get { return (int) cache.CacheId; }
+        }
+
+        public void InvalidateCache ()
+        {
+            cache.ClearManagedCache ();
+            OnReloaded ();
+        }
+
+        public virtual string JoinTable { get { return null; } }
+        public virtual string JoinFragment { get { return null; } }
+        public virtual string JoinPrimaryKey { get { return null; } }
+        public virtual string JoinColumn { get { return null; } }
+        public virtual bool CachesJoinTableEntries { get { return false; } }
+    }
+}

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs	Sun May 18 00:19:17 2008
@@ -82,7 +82,7 @@
 
         public override void IncrementPlayCount ()
         {
-            if (Provider.Refresh (this)) {
+            if (ProviderRefresh ()) {
                 base.IncrementPlayCount ();
                 Save (true, BansheeQuery.PlayCountField, BansheeQuery.LastPlayedField);
             }
@@ -90,7 +90,7 @@
 
         public override void IncrementSkipCount ()
         {
-            if (Provider.Refresh (this)) {
+            if (ProviderRefresh ()) {
                 base.IncrementSkipCount ();
                 Save (true, BansheeQuery.SkipCountField, BansheeQuery.LastSkippedField);
             }
@@ -144,7 +144,7 @@
             bool is_new = (TrackId == 0);
             if (is_new) DateAdded = DateUpdated;
 
-            Provider.Save (this);
+            ProviderSave ();
 
             if (notify) {
                 if (is_new) {
@@ -155,6 +155,16 @@
             }
         }
         
+        protected virtual void ProviderSave ()
+        {
+            Provider.Save (this);
+        }
+        
+        protected virtual bool ProviderRefresh ()
+        {
+            return Provider.Refresh (this);
+        }
+        
         private int track_id;
         [DatabaseColumn ("TrackID", Constraints = DatabaseColumnConstraints.PrimaryKey)]
         public int TrackId {
@@ -177,15 +187,15 @@
             set { PrimarySourceId = value.DbId; }
         }
 
-        [DatabaseColumn ("ArtistID")]
         private int artist_id;
+        [DatabaseColumn ("ArtistID")]
         public int ArtistId {
             get { return artist_id; }
             set { artist_id = value; }
         }
-
-        [DatabaseColumn ("AlbumID")]
+        
         private int album_id;
+        [DatabaseColumn ("AlbumID")]
         public int AlbumId {
             get { return album_id; }
             set { album_id = value; }
@@ -422,6 +432,8 @@
             }
         }
 
+#region Implement ICacheableItem
+
         private long cache_entry_id;
         public long CacheEntryId {
             get { return cache_entry_id; }
@@ -434,6 +446,8 @@
             set { cache_model_id = value; }
         }
 
+#endregion
+
         private void UpdateUri ()
         {
             if (Uri == null && uri_type_set && UriField != null && PrimarySource != null) {

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackListModel.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackListModel.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackListModel.cs	Sun May 18 00:19:17 2008
@@ -44,19 +44,14 @@
 using Banshee.Database;
 
 namespace Banshee.Collection.Database
-{
-    public enum ReloadTrigger {
-        Query,
-        ArtistFilter,
-        AlbumFilter
-    };
-        
+{       
     public class DatabaseTrackListModel : TrackListModel, IExportableModel, 
         ICacheableDatabaseModel, IFilterable, ISortable, ICareAboutView
     {
         private readonly BansheeDbConnection connection;
         private IDatabaseTrackModelProvider provider;
         protected IDatabaseTrackModelCache cache;
+        private Banshee.Sources.DatabaseSource source;
         
         private long count;
 
@@ -72,53 +67,40 @@
         private string join_table, join_fragment, join_primary_key, join_column, condition;
 
         private string query_fragment;
-        private string filter;
-        private string artist_id_filter_query;
-        private string album_id_filter_query;
-
-        private DatabaseArtistListModel artist_model;
-        private DatabaseAlbumListModel album_model;
+        private string user_query;
 
         private int rows_in_view;
         
-        public DatabaseTrackListModel (BansheeDbConnection connection, IDatabaseTrackModelProvider provider)
+        public DatabaseTrackListModel (BansheeDbConnection connection, IDatabaseTrackModelProvider provider, Banshee.Sources.DatabaseSource source)
         {
             this.connection = connection;
             this.provider = provider;
+            this.source = source;
         }
 
         private bool initialized = false;
         public void Initialize (IDatabaseTrackModelCache cache)
         {
-            Initialize (cache, null, null);
-        }
-
-        public void Initialize (IDatabaseTrackModelCache cache, DatabaseArtistListModel artist_model, DatabaseAlbumListModel album_model)
-        {
             if (initialized)
                 return;
 
-            this.artist_model = artist_model;
-            this.album_model = album_model;
-
             initialized = true;
             this.cache = cache;
             cache.AggregatesUpdated += HandleCacheAggregatesUpdated;
-
             GenerateSortQueryPart ();
         }
         
-        private bool have_new_filter = true;
-        private void GenerateFilterQueryPart ()
+        private bool have_new_user_query = true;
+        private void GenerateUserQueryFragment ()
         {
-            if (!have_new_filter)
+            if (!have_new_user_query)
                 return;
 
-            if (String.IsNullOrEmpty (Filter)) {
+            if (String.IsNullOrEmpty (UserQuery)) {
                 query_fragment = null;
                 query_tree = null;
             } else {
-                query_tree = UserQueryParser.Parse (Filter, BansheeQuery.FieldSet);
+                query_tree = UserQueryParser.Parse (UserQuery, BansheeQuery.FieldSet);
                 query_fragment = (query_tree == null) ? null : query_tree.ToSql (BansheeQuery.FieldSet);
 
                 if (query_fragment != null && query_fragment.Length == 0) {
@@ -127,7 +109,7 @@
                 }
             }
 
-            have_new_filter = false;
+            have_new_user_query = false;
         }
 
         private QueryNode query_tree;
@@ -214,37 +196,45 @@
         
         public override void Reload ()
         {
-            Reload (ReloadTrigger.Query);
+            Reload (null);
         }
 
-        public void Reload (ReloadTrigger trigger)
+        public void Reload (IListModel reloadTrigger)
         {
             lock (this) {
-                bool artist_reloaded = false, album_reloaded = false;
-                GenerateFilterQueryPart ();
+                GenerateUserQueryFragment ();
 
                 UpdateUnfilteredAggregates ();
                 cache.SaveSelection ();
 
-                if (trigger == ReloadTrigger.AlbumFilter) {
-                    ReloadWithFilters ();
-                } else {
-                    ReloadWithoutArtistAlbumFilters ();
-
-                    if (artist_model != null && album_model != null) {
-                        if (trigger == ReloadTrigger.Query) {
-                            artist_reloaded = true;
-                            artist_model.Reload (false);
-                        }
+                List<IFilterListModel> reload_models = new List<IFilterListModel> ();
+                bool found = (reloadTrigger == null);
+                foreach (IFilterListModel model in source.FilterModels) {
+                    if (found) {
+                        reload_models.Add (model);
+                    } else if (model == reloadTrigger) {
+                        found = true;
+                    }
+                }
 
-                        album_reloaded = true;
-                        album_model.Reload (false);
+                if (reload_models.Count == 0) {
+                    ReloadWithFilters (true);
+                } else {
+                    ReloadWithoutFilters ();
 
-                        // Unless both artist/album selections are "all" (eg unfiltered), reload
-                        // the track model again with the artist/album filters now in place.
-                        if (!artist_model.Selection.AllSelected || !album_model.Selection.AllSelected) {
-                            ReloadWithFilters ();
-                        }
+                    foreach (IFilterListModel model in reload_models) {
+                        model.Reload (false);
+                    }
+                    
+                    bool have_filters = false;
+                    foreach (IFilterListModel model in source.FilterModels) {
+                        have_filters |= !model.Selection.AllSelected;
+                    }
+                    
+                    // Unless both artist/album selections are "all" (eg unfiltered), reload
+                    // the track model again with the artist/album filters now in place.
+                    if (have_filters) {
+                        ReloadWithFilters (true);
                     }
                 }
 
@@ -256,50 +246,30 @@
                 OnReloaded ();
 
                 // Trigger these after the track list, b/c visually it's more important for it to update first
-                if (artist_reloaded)
-                    artist_model.RaiseReloaded ();
-
-                if (album_reloaded)
-                    album_model.RaiseReloaded ();
+                foreach (IFilterListModel model in reload_models) {
+                    model.RaiseReloaded ();
+                }
             }
         }
 
-        private void ReloadWithoutArtistAlbumFilters ()
+        private void ReloadWithoutFilters ()
         {
-            StringBuilder qb = new StringBuilder ();
-            qb.Append (UnfilteredQuery);
-
-            if (query_fragment != null) {
-                qb.Append ("AND ");
-                qb.Append (query_fragment);
-            }
-            
-            if (sort_query != null) {
-                qb.Append (" ORDER BY ");
-                qb.Append (sort_query);
-            }
-                
-            reload_fragment = qb.ToString ();
-
-            cache.Reload ();
+            ReloadWithFilters (false);
         }
 
-        private void ReloadWithFilters ()
+        private void ReloadWithFilters (bool with_filters)
         {
             StringBuilder qb = new StringBuilder ();
             qb.Append (UnfilteredQuery);
-
-            ArtistInfoFilter = artist_model.SelectedItems;
-            AlbumInfoFilter = album_model.SelectedItems;
-
-            if (artist_id_filter_query != null) {
-                qb.Append ("AND ");
-                qb.Append (artist_id_filter_query);
-            }
-                    
-            if (album_id_filter_query != null) {
-                qb.Append ("AND ");
-                qb.Append (album_id_filter_query);
+            
+            if (with_filters) {
+                foreach (IFilterListModel model in source.FilterModels) {
+                    string filter = GetFilterFromModel (model);
+                    if (filter != null) {
+                        qb.Append ("AND");
+                        qb.Append (filter);
+                    }
+                }
             }
             
             if (query_fragment != null) {
@@ -311,9 +281,8 @@
                 qb.Append (" ORDER BY ");
                 qb.Append (sort_query);
             }
-                
+            
             reload_fragment = qb.ToString ();
-
             cache.Reload ();
         }
 
@@ -371,12 +340,12 @@
             get { return (int) count; }
         }
 
-        public string Filter {
-            get { return filter; }
+        public string UserQuery {
+            get { return user_query; }
             set { 
                 lock (this) {
-                    filter = value; 
-                    have_new_filter = true;
+                    user_query = value; 
+                    have_new_user_query = true;
                 }
             }
         }
@@ -411,10 +380,14 @@
             get { return join_column; }
             set { join_column = value; }
         }
+        
+        public void AddCondition (string part)
+        {
+            condition = condition == null ? part : String.Format ("{0} AND {1}", condition, part);
+        }
 
         public string Condition {
             get { return condition; }
-            set { condition = value; }
         }
 
         public string ConditionFragment {
@@ -429,8 +402,20 @@
             else
                 return String.Format (" {0} {1} ", prefix, condition);
         }
+        
+        private string GetFilterFromModel (IFilterListModel model)
+        {
+            string filter = null;
+            
+            ModelHelper.BuildIdFilter<object> (model.GetSelectedObjects (), model.FilterColumn, null,
+                delegate (object item) { return model.ItemToFilterValue (item); },
+                delegate (string new_filter) { filter = new_filter; }
+            );
+            
+            return filter;
+        }
 
-        public override IEnumerable<ArtistInfo> ArtistInfoFilter {
+        /*public override IEnumerable<ArtistInfo> ArtistInfoFilter {
             set {
                 ModelHelper.BuildIdFilter<ArtistInfo> (value, "CoreTracks.ArtistID", artist_id_filter_query,
                     delegate (ArtistInfo artist) {
@@ -443,6 +428,7 @@
                 
                     delegate (string new_filter) {
                         artist_id_filter_query = new_filter;
+                        Console.WriteLine ("artist filter now set to {0}", artist_id_filter_query);
                     }
                 );
             }
@@ -461,15 +447,14 @@
                 
                     delegate (string new_filter) {
                         album_id_filter_query = new_filter;
+                        Console.WriteLine ("album filter now set to {0}", album_id_filter_query);
                     }
                 );
             }
-        }
+        }*/
 
         public override void ClearArtistAlbumFilters ()
         {
-            artist_id_filter_query = null;
-            album_id_filter_query = null;
             Reload ();
         }
 

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Collection/BansheeListModel.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Collection/BansheeListModel.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Collection/BansheeListModel.cs	Sun May 18 00:19:17 2008
@@ -70,7 +70,7 @@
             });
         }
 
-        internal void RaiseReloaded ()
+        public void RaiseReloaded ()
         {
             OnReloaded ();
         }
@@ -103,5 +103,9 @@
                 return model_selection ?? model_selection = new ModelSelection<T> (this, Selection);
             }
         }
+        
+        public T FocusedItem {
+            get { return Selection.FocusedIndex == -1 ? default(T) : this[Selection.FocusedIndex]; }
+        }
     }
 }

Added: trunk/banshee/src/Core/Banshee.Services/Banshee.Collection/BrowsableListModel.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Collection/BrowsableListModel.cs	Sun May 18 00:19:17 2008
@@ -0,0 +1,53 @@
+//
+// BrowsableListModel.cs
+//
+// Author:
+//   Aaron Bockover <abockover novell com>
+//
+// Copyright (C) 2007 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;
+using Banshee.ServiceStack;
+
+namespace Banshee.Collection
+{
+    public abstract class BrowsableListModel<T> : BansheeListModel<T>
+    {
+        public BrowsableListModel () : base ()
+        {
+            selection = new SelectAllSelection ();
+            selection.SelectAll ();
+        }
+        
+        public BrowsableListModel (IDBusExportable parent) : base (parent)
+        {
+            selection = new SelectAllSelection ();
+            selection.SelectAll ();
+        }
+        
+        //public abstract void RaiseReloaded ();
+    }
+}

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs	Sun May 18 00:19:17 2008
@@ -52,7 +52,7 @@
         // NOTE: Whenever there is a change in ANY of the database schema,
         //       this version MUST be incremented and a migration method
         //       MUST be supplied to match the new version number
-        protected const int CURRENT_VERSION = 9;
+        protected const int CURRENT_VERSION = 10;
         protected const int CURRENT_METADATA_VERSION = 1;
         
 #region Migration Driver
@@ -381,6 +381,17 @@
 
 #endregion
 
+        [DatabaseVersion (10)]
+        private bool Migrate_10 ()
+        {
+            // Clear these out for people who ran the pre-alpha podcast plugin
+            Execute ("DROP TABLE IF EXISTS PodcastEnclosures");
+            Execute ("DROP TABLE IF EXISTS PodcastItems");
+            Execute ("DROP TABLE IF EXISTS PodcastSyndications");
+            Execute ("ALTER TABLE CoreTracks ADD COLUMN ExternalID INTEGER");
+            return true;
+        }
+
 #pragma warning restore 0169
         
 #region Fresh database setup
@@ -428,6 +439,7 @@
                     ArtistID            INTEGER,
                     AlbumID             INTEGER,
                     TagSetID            INTEGER,
+                    ExternalID          INTEGER,
                     
                     MusicBrainzID       TEXT,
 
@@ -607,6 +619,7 @@
                                 AND b.Name = Artist),
                         0,
                         0,
+                        0,
                         Uri,
                         0,
                         MimeType,

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Database/BansheeModelCache.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Database/BansheeModelCache.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Database/BansheeModelCache.cs	Sun May 18 00:19:17 2008
@@ -39,7 +39,7 @@
         public BansheeModelCache (HyenaSqliteConnection connection,
                                   string uuid,
                                   ICacheableDatabaseModel model,
-                                  BansheeModelProvider <T> provider)
+                                  SqliteModelProvider <T> provider)
             : base (connection, uuid, model, provider)
         {
         }

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Metadata.Embedded/EmbeddedQueryJob.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Metadata.Embedded/EmbeddedQueryJob.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Metadata.Embedded/EmbeddedQueryJob.cs	Sun May 18 00:19:17 2008
@@ -52,7 +52,7 @@
         
         public override void Run()
         {
-            if(track == null || CoverArtSpec.CoverExists (track.ArtistAlbumId)) {
+            if(track == null || CoverArtSpec.CoverExists (track.ArtworkId)) {
                 return;
             }
           
@@ -61,7 +61,7 @@
         
         protected void Fetch()
         {
-            string artist_album_id = track.ArtistAlbumId;
+            string artist_album_id = track.ArtworkId;
 
             if(artist_album_id == null) {
                 return;

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Metadata.MusicBrainz/MusicBrainzQueryJob.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Metadata.MusicBrainz/MusicBrainzQueryJob.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Metadata.MusicBrainz/MusicBrainzQueryJob.cs	Sun May 18 00:19:17 2008
@@ -72,7 +72,7 @@
                 return false;
             }
             
-            string album_artist_id = track.ArtistAlbumId;
+            string album_artist_id = track.ArtworkId;
             
             if(album_artist_id == null) {
                 return false;

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Metadata/MetadataServiceJob.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Metadata/MetadataServiceJob.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Metadata/MetadataServiceJob.cs	Sun May 18 00:19:17 2008
@@ -139,14 +139,19 @@
                 return false;
             }
             
-            Banshee.IO.StreamAssist.Save (from_stream, new FileStream (path, 
+            // Save the file to a temporary path while downloading, so that nobody sees it
+            // and thinks it's ready for use before it is
+            string tmp_path = String.Format ("{0}.part", path);
+            Banshee.IO.StreamAssist.Save (from_stream, new FileStream (tmp_path, 
                 FileMode.Create, FileAccess.ReadWrite));
                 
+            Banshee.IO.File.Move (new SafeUri (tmp_path), new SafeUri (path));
+                
             return true;
         }
         
         protected bool SaveHttpStreamCover (Uri uri, string albumArtistId, string [] ignoreMimeTypes)
-        { 
+        {
             return SaveHttpStream (uri, CoverArtSpec.GetPath (albumArtistId), ignoreMimeTypes);
         }
     }

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Playlist/AbstractPlaylistSource.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Playlist/AbstractPlaylistSource.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Playlist/AbstractPlaylistSource.cs	Sun May 18 00:19:17 2008
@@ -139,7 +139,7 @@
             DatabaseTrackModel.JoinPrimaryKey = JoinPrimaryKey;
             DatabaseTrackModel.JoinColumn = "TrackID";
             DatabaseTrackModel.CachesJoinTableEntries = CachesJoinTableEntries;
-            DatabaseTrackModel.Condition = String.Format (TrackCondition, dbid);
+            DatabaseTrackModel.AddCondition (String.Format (TrackCondition, dbid));
 
             base.AfterInitialized ();
         }

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Services.mdp
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Services.mdp	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Services.mdp	Sun May 18 00:19:17 2008
@@ -167,6 +167,8 @@
     <File name="Banshee.ServiceStack/IDelayedInitializeService.cs" subtype="Code" buildaction="Compile" />
     <File name="Banshee.Sources/DurationStatusFormatters.cs" subtype="Code" buildaction="Compile" />
     <File name="Banshee.SmartPlaylist/SmartPlaylistDefinition.cs" subtype="Code" buildaction="Compile" />
+    <File name="Banshee.Collection.Database/DatabaseBrowsableListModel.cs" subtype="Code" buildaction="Compile" />
+    <File name="Banshee.Collection/BrowsableListModel.cs" subtype="Code" buildaction="Compile" />
   </Contents>
   <References>
     <ProjectReference type="Gac" localcopy="True" refto="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Sources/DatabaseSource.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Sources/DatabaseSource.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Sources/DatabaseSource.cs	Sun May 18 00:19:17 2008
@@ -86,7 +86,7 @@
 
         protected DatabaseTrackListModel DatabaseTrackModel {
             get {
-                return track_model ?? track_model = new DatabaseTrackListModel (ServiceManager.DbConnection, TrackProvider);
+                return track_model ?? track_model = new DatabaseTrackListModel (ServiceManager.DbConnection, TrackProvider, this);
             }
             set { track_model = value; }
         }
@@ -110,7 +110,7 @@
 
             if (HasArtistAlbum) {
                 artist_model = new DatabaseArtistListModel (DatabaseTrackModel, ServiceManager.DbConnection, UniqueId);
-                album_model = new DatabaseAlbumListModel (DatabaseTrackModel, artist_model, ServiceManager.DbConnection, UniqueId);
+                album_model = new DatabaseAlbumListModel (DatabaseTrackModel, ServiceManager.DbConnection, UniqueId);
             }
 
             reload_limiter = new RateLimiter (RateLimitedReload);
@@ -141,7 +141,7 @@
         public override string FilterQuery {
             set {
                 base.FilterQuery = value;
-                DatabaseTrackModel.Filter = FilterQuery;
+                DatabaseTrackModel.UserQuery = FilterQuery;
                 ThreadAssist.SpawnFromMain (delegate {
                     Reload ();
                 });
@@ -172,12 +172,14 @@
             get { return DatabaseTrackModel; }
         }
         
-        public AlbumListModel AlbumModel {
-            get { return album_model; }
-        }
-        
-        public ArtistListModel ArtistModel {
-            get { return artist_model; }
+        public virtual IEnumerable<IFilterListModel> FilterModels {
+            get {
+                if (artist_model != null)
+                    yield return artist_model;
+                    
+                if (album_model != null)
+                    yield return album_model;
+            }
         }
         
         public virtual bool ShowBrowser { 
@@ -397,7 +399,7 @@
 
         protected virtual void AfterInitialized ()
         {
-            DatabaseTrackModel.Initialize (TrackCache, artist_model, album_model);
+            DatabaseTrackModel.Initialize (TrackCache);
             OnSetupComplete ();
         }
 
@@ -465,7 +467,8 @@
         protected void InvalidateCaches ()
         {
             track_model.InvalidateCache ();
-
+            
+            // TODO invalidate cache on all FilterModels
             if (artist_model != null)
                 artist_model.InvalidateCache ();
 

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Sources/ITrackModelSource.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Sources/ITrackModelSource.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Sources/ITrackModelSource.cs	Sun May 18 00:19:17 2008
@@ -38,8 +38,11 @@
     public interface ITrackModelSource : ISource
     {
         TrackListModel TrackModel { get; }
-        AlbumListModel AlbumModel { get; }
-        ArtistListModel ArtistModel { get; }
+        
+        IEnumerable<Banshee.Collection.Database.IFilterListModel> FilterModels { get; }
+        
+        //AlbumListModel AlbumModel { get; }
+        //ArtistListModel ArtistModel { get; }
 
         void Reload ();
         bool HasDependencies { get; }

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Sources/PrimarySource.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Sources/PrimarySource.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Sources/PrimarySource.cs	Sun May 18 00:19:17 2008
@@ -192,7 +192,7 @@
         private void PrimarySourceInitialize ()
         {
             // Scope the tracks to this primary source
-            track_model.Condition = String.Format ("CoreTracks.PrimarySourceID = {0}", DbId);
+            track_model.AddCondition (String.Format ("CoreTracks.PrimarySourceID = {0}", DbId));
 
             primary_sources[DbId] = this;
             

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Sources/Source.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Sources/Source.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Sources/Source.cs	Sun May 18 00:19:17 2008
@@ -107,14 +107,13 @@
                 tm_source.TrackModel.Parent = this;
                 ServiceManager.DBusServiceManager.RegisterObject (tm_source.TrackModel);
                 
-                if (tm_source.ArtistModel != null) {
-                    tm_source.ArtistModel.Parent = this;
-                    ServiceManager.DBusServiceManager.RegisterObject (tm_source.ArtistModel);
-                }
-                
-                if (tm_source.AlbumModel != null) {
-                    tm_source.AlbumModel.Parent = this;
-                    ServiceManager.DBusServiceManager.RegisterObject (tm_source.AlbumModel);
+                // TODO if/when browsable models can be added/removed on the fly, this would need to change to reflect that
+                foreach (IListModel model in tm_source.FilterModels) {
+                    Banshee.Collection.ExportableModel exportable = model as Banshee.Collection.ExportableModel;
+                    if (exportable != null) {
+                        exportable.Parent = this;
+                        ServiceManager.DBusServiceManager.RegisterObject (exportable);
+                    }
                 }
             }
         }

Modified: trunk/banshee/src/Core/Banshee.Services/Makefile.am
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Makefile.am	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Makefile.am	Sun May 18 00:19:17 2008
@@ -10,6 +10,7 @@
 	Banshee.Collection.Database/DatabaseAlbumListModel.cs \
 	Banshee.Collection.Database/DatabaseArtistInfo.cs \
 	Banshee.Collection.Database/DatabaseArtistListModel.cs \
+	Banshee.Collection.Database/DatabaseBrowsableListModel.cs \
 	Banshee.Collection.Database/DatabaseImportManager.cs \
 	Banshee.Collection.Database/DatabaseTrackInfo.cs \
 	Banshee.Collection.Database/DatabaseTrackListModel.cs \
@@ -20,6 +21,7 @@
 	Banshee.Collection/AlbumListModel.cs \
 	Banshee.Collection/ArtistListModel.cs \
 	Banshee.Collection/BansheeListModel.cs \
+	Banshee.Collection/BrowsableListModel.cs \
 	Banshee.Collection/ExportableModel.cs \
 	Banshee.Collection/IExportableModel.cs \
 	Banshee.Collection/IHasTrackSelection.cs \

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/AlbumListView.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/AlbumListView.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/AlbumListView.cs	Sun May 18 00:19:17 2008
@@ -38,39 +38,17 @@
 
 namespace Banshee.Collection.Gui
 {
-    public class AlbumListView : ListView<AlbumInfo>
+    public class AlbumListView : TrackFilterListView<AlbumInfo>
     {
-        private ColumnController column_controller;
-        
         public AlbumListView () : base ()
         {
             ColumnCellAlbum renderer = new ColumnCellAlbum ();
-            
-            column_controller = new ColumnController ();
             column_controller.Add (new Column ("Album", renderer, 1.0));
             ColumnController = column_controller;
             
             RowHeightProvider = renderer.ComputeRowHeight;
-            
-            RowActivated += delegate {
-                ServiceManager.PlaybackController.NextSource = (ServiceManager.SourceManager.ActiveSource 
-                    as Banshee.Sources.ITrackModelSource);
-                ServiceManager.PlaybackController.Next ();
-            };
-            
-            ServiceManager.PlayerEngine.ConnectEvent (OnPlayerEvent, PlayerEvent.TrackInfoUpdated);
-        }
 
-        protected override bool OnFocusInEvent(Gdk.EventFocus evnt)
-        {
-            ServiceManager.Get<InterfaceActionService> ().TrackActions.SuppressSelectActions ();
-            return base.OnFocusInEvent(evnt);
-        }
-        
-        protected override bool OnFocusOutEvent(Gdk.EventFocus evnt)
-        {
-            ServiceManager.Get<InterfaceActionService> ().TrackActions.UnsuppressSelectActions ();
-            return base.OnFocusOutEvent(evnt);
+            ServiceManager.PlayerEngine.ConnectEvent (OnPlayerEvent, PlayerEvent.TrackInfoUpdated);
         }
         
         private void OnPlayerEvent (PlayerEventArgs args)

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtistListView.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtistListView.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtistListView.cs	Sun May 18 00:19:17 2008
@@ -37,20 +37,12 @@
 
 namespace Banshee.Collection.Gui
 {
-    public class ArtistListView : ListView<ArtistInfo>
+    public class ArtistListView : TrackFilterListView<ArtistInfo>
     {
-        private ColumnController column_controller;
-        
-        public ArtistListView() : base()
+        public ArtistListView () : base ()
         {
-            column_controller = new ColumnController();
-            column_controller.Add(new Column("Artist", new ColumnCellText("Name", true), 1.0));
+            column_controller.Add (new Column ("Artist", new ColumnCellText ("Name", true), 1.0));
             ColumnController = column_controller;
-            
-            RowActivated += delegate {
-                ServiceManager.PlaybackController.NextSource = (ServiceManager.SourceManager.ActiveSource as Banshee.Sources.ITrackModelSource);
-                ServiceManager.PlaybackController.Next ();
-            };
         }
 
         // TODO add context menu for artists/albums...probably need a Banshee.Gui/ArtistActions.cs file.  Should
@@ -61,17 +53,5 @@
             ServiceManager.Get<InterfaceActionService> ().TrackActions["TrackContextMenuAction"].Activate ();
             return true;
         }*/
-
-        protected override bool OnFocusInEvent(Gdk.EventFocus evnt)
-        {
-            ServiceManager.Get<InterfaceActionService> ().TrackActions.SuppressSelectActions ();
-            return base.OnFocusInEvent(evnt);
-        }
-        
-        protected override bool OnFocusOutEvent(Gdk.EventFocus evnt)
-        {
-            ServiceManager.Get<InterfaceActionService> ().TrackActions.UnsuppressSelectActions ();
-            return base.OnFocusOutEvent(evnt);
-        }
     }
 }

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellDateTime.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellDateTime.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellDateTime.cs	Sun May 18 00:19:17 2008
@@ -32,12 +32,27 @@
 
 namespace Banshee.Collection.Gui
 {
+    public enum DateTimeFormat 
+    {
+        Long,
+        ShortDate,
+        LongDate,
+        ShortTime,
+        LongTime
+    }
+        
     public class ColumnCellDateTime : ColumnCellText
     {
         public ColumnCellDateTime (string property, bool expand) : base (property, expand)
         {
         }
         
+        private DateTimeFormat format = DateTimeFormat.Long;
+        public DateTimeFormat Format {
+            get { return format; }
+            set { format = value; }
+        }
+        
         protected override string Text {
             get {
                 if (BoundObject == null)
@@ -48,7 +63,16 @@
                 if (dt == DateTime.MinValue)
                     return String.Empty;
 
-                return dt.ToString ();
+                switch (Format)
+                {
+                case DateTimeFormat.Long:         return dt.ToString ();
+                case DateTimeFormat.ShortDate:    return dt.ToShortDateString ();
+                case DateTimeFormat.LongDate:     return dt.ToLongDateString ();
+                case DateTimeFormat.ShortTime:    return dt.ToShortTimeString ();
+                case DateTimeFormat.LongTime:     return dt.ToLongTimeString ();
+                }
+                
+                return String.Empty;
             }
         }
     }

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellDuration.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellDuration.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellDuration.cs	Sun May 18 00:19:17 2008
@@ -49,6 +49,9 @@
                 //int seconds = (int)Math.Round(((TimeSpan)BoundObject).TotalSeconds);
                 int seconds = (int) ((TimeSpan)BoundObject).TotalSeconds;
                 
+                if (seconds == 0)
+                    return String.Empty;
+                
                 return seconds >= 3600 ? 
                     String.Format ("{0}:{1:00}:{2:00}", seconds / 3600, (seconds / 60) % 60, seconds % 60) :
                     String.Format ("{0}:{1:00}", seconds / 60, seconds % 60);

Added: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellFileSize.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellFileSize.cs	Sun May 18 00:19:17 2008
@@ -0,0 +1,48 @@
+// 
+// ColumnCellFileSize.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 Hyena.Query;
+using Hyena.Data.Gui;
+
+namespace Banshee.Collection.Gui
+{
+    public class ColumnCellFileSize : ColumnCellText
+    {
+        public ColumnCellFileSize (string property, bool expand) : base (property, expand)
+        {
+        }
+        
+        protected override string Text {
+            get {
+                return new FileSizeQueryValue ((long) BoundObject).ToUserQuery ();
+            }
+        }
+    }
+}

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/DefaultColumnController.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/DefaultColumnController.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/DefaultColumnController.cs	Sun May 18 00:19:17 2008
@@ -69,6 +69,7 @@
                 DurationColumn,
                 GenreColumn,
                 YearColumn,
+                FileSizeColumn,
                 ComposerColumn,
                 PlayCountColumn,
                 SkipCountColumn,
@@ -112,7 +113,7 @@
         }
         
         private SortableColumn duration_column = new SortableColumn (Catalog.GetString ("Duration"),
-            new ColumnCellDuration ("Duration", true), 0.15, "Duration", true);
+            new ColumnCellDuration ("Duration", true), 0.10, "Duration", true);
         public SortableColumn DurationColumn {
             get { return duration_column; }
         }
@@ -128,6 +129,12 @@
         public SortableColumn YearColumn {
             get { return year_column; }
         }
+
+        private SortableColumn file_size_column = new SortableColumn (Catalog.GetString ("File Size"), 
+            new ColumnCellFileSize ("FileSize", true), 0.15, "FileSize", false);
+        public SortableColumn FileSizeColumn {
+            get { return file_size_column; }
+        }
         
         private SortableColumn composer_column = new SortableColumn (Catalog.GetString ("Composer"), 
             new ColumnCellText ("Composer", true), 0.25, "Composer", false);

Added: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/TrackFilterListView.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/TrackFilterListView.cs	Sun May 18 00:19:17 2008
@@ -0,0 +1,75 @@
+//
+// TrackFilterListView.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 Hyena.Data;
+using Hyena.Data.Gui;
+
+using Banshee.Collection;
+using Banshee.ServiceStack;
+using Banshee.Gui;
+
+namespace Banshee.Collection.Gui
+{
+    public class TrackFilterListView<T> : ListView<T>
+    {
+        protected ColumnController column_controller;
+        
+        public TrackFilterListView () : base ()
+        {
+            column_controller = new ColumnController ();
+            
+            RowActivated += delegate {
+                ServiceManager.PlaybackController.NextSource = (ServiceManager.SourceManager.ActiveSource as Banshee.Sources.ITrackModelSource);
+                ServiceManager.PlaybackController.Next ();
+            };
+        }
+
+        // TODO add context menu for artists/albums...probably need a Banshee.Gui/ArtistActions.cs file.  Should
+        // make TrackActions.cs more generic with regards to the TrackSelection stuff, using the new properties
+        // set on the sources themselves that give us access to the IListView<T>.
+        /*protected override bool OnPopupMenu ()
+        {
+            ServiceManager.Get<InterfaceActionService> ().TrackActions["TrackContextMenuAction"].Activate ();
+            return true;
+        }*/
+
+        protected override bool OnFocusInEvent(Gdk.EventFocus evnt)
+        {
+            ServiceManager.Get<InterfaceActionService> ().TrackActions.SuppressSelectActions ();
+            return base.OnFocusInEvent(evnt);
+        }
+        
+        protected override bool OnFocusOutEvent(Gdk.EventFocus evnt)
+        {
+            ServiceManager.Get<InterfaceActionService> ().TrackActions.UnsuppressSelectActions ();
+            return base.OnFocusOutEvent(evnt);
+        }
+    }
+}

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/TrackListView.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/TrackListView.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/TrackListView.cs	Sun May 18 00:19:17 2008
@@ -56,12 +56,18 @@
             
             ForceDragSourceSet = true;
             Reorderable = true;
+            
+            RowActivated += delegate (object o, RowActivatedArgs<TrackInfo> args) {
+                ITrackModelSource source = ServiceManager.SourceManager.ActiveSource as ITrackModelSource;
+                if (source != null && source.TrackModel == Model) {
+                    ServiceManager.PlaybackController.Source = source;
+                    ServiceManager.PlayerEngine.OpenPlay (args.RowValue);
+                }
+            };
         }
         
         public override void SetModel (IListModel<TrackInfo> value, double vpos)
         {
-            base.SetModel (value, vpos);
-            
             Source source = ServiceManager.SourceManager.ActiveSource;
             ColumnController controller = null;
             
@@ -80,6 +86,8 @@
             }
             
             ColumnController = controller ?? default_column_controller;
+            
+            base.SetModel (value, vpos);
         }
 
         protected override bool OnPopupMenu ()

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/XmlColumnController.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/XmlColumnController.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/XmlColumnController.cs	Sun May 18 00:19:17 2008
@@ -54,9 +54,12 @@
         
         private void ReadColumnController (XmlTextReader reader, int depth)
         {
+            string sort_column = null;
+            
             while (reader.Read ()) {
                 if (reader.NodeType == XmlNodeType.Element) {
                     switch (reader.Name) {
+                        case "sort-column": sort_column = reader.ReadString (); break;
                         case "column": ReadColumn (reader, reader.Depth); break;
                         case "add-all-defaults": AddDefaultColumns (); break;
                         case "add-default":
@@ -77,7 +80,20 @@
                             break;
                     }
                 } else if (reader.NodeType == XmlNodeType.EndElement && reader.Depth == depth) {
-                    return;
+                    break;
+                }
+            }
+            
+            if (sort_column != null) {
+                foreach (Column col in Columns) {
+                    if (col.Id == sort_column) {
+                        if (col is SortableColumn) {
+                            DefaultSortColumn = col as SortableColumn;
+                        } else {
+                            Hyena.Log.WarningFormat ("Defined default sort column {0} is not a SortableColumn", sort_column);
+                        }
+                        break;
+                    }
                 }
             }
         }

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/TrackInfoDisplay.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/TrackInfoDisplay.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/TrackInfoDisplay.cs	Sun May 18 00:19:17 2008
@@ -459,7 +459,7 @@
 
             incoming_track = track;
 
-            Gdk.Pixbuf pixbuf = artwork_manager.LookupScale (track.ArtistAlbumId, Allocation.Height);
+            Gdk.Pixbuf pixbuf = artwork_manager.LookupScale (track.ArtworkId, Allocation.Height);
             
             if (pixbuf == null) {
                 if ((track.MediaAttributes & TrackMediaAttributes.VideoStream) != 0) {
@@ -552,7 +552,7 @@
                 return false;
             }
             
-            Gdk.Pixbuf pixbuf = artwork_manager.Lookup (current_track.ArtistAlbumId);
+            Gdk.Pixbuf pixbuf = artwork_manager.Lookup (current_track.ArtworkId);
          
             if (pixbuf == null) {
                 HidePopup ();

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/BansheeActionGroup.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/BansheeActionGroup.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/BansheeActionGroup.cs	Sun May 18 00:19:17 2008
@@ -39,11 +39,13 @@
 {
     public class BansheeActionGroup : ActionGroup
     {
+        private InterfaceActionService action_service;
         protected Dictionary<string, string> labels = new Dictionary<string, string> ();
         protected Dictionary<string, string> icons = new Dictionary<string, string> ();
 
-        public BansheeActionGroup (string name) : base (name)
+        public BansheeActionGroup (InterfaceActionService action_service, string name) : base (name)
         {
+            this.action_service = action_service;
         }
         
         public void AddImportant (params ActionEntry [] action_entries)
@@ -117,6 +119,30 @@
                 }
             }
         }
+        
+        protected void ShowContextMenu (string menu_name)
+        {
+            Gtk.Menu menu = Actions.UIManager.GetWidget (menu_name) as Menu;
+            if (menu == null || menu.Children.Length == 0) {
+                return;
+            }
+
+            int visible_children = 0;
+            foreach (Widget child in menu)
+                if (child.Visible)
+                    visible_children++;
+
+            if (visible_children == 0) {
+                return;
+            }
+
+            menu.Show (); 
+            menu.Popup (null, null, null, 0, Gtk.Global.CurrentEventTime);
+        }
+        
+        public InterfaceActionService Actions {
+            get { return action_service; }
+        }
 
         public Source ActiveSource {
             get { return ServiceManager.SourceManager.ActiveSource; }

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/GlobalActions.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/GlobalActions.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/GlobalActions.cs	Sun May 18 00:19:17 2008
@@ -42,7 +42,7 @@
 {
     public class GlobalActions : BansheeActionGroup
     {
-        public GlobalActions (InterfaceActionService actionService) : base ("Global")
+        public GlobalActions (InterfaceActionService actionService) : base (actionService, "Global")
         {
             Add (new ActionEntry [] {
                 // Media Menu

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackActions.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackActions.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackActions.cs	Sun May 18 00:19:17 2008
@@ -45,7 +45,6 @@
 {
     public class PlaybackActions : BansheeActionGroup
     {
-        private InterfaceActionService action_service;
         private Gtk.Action play_pause_action;
         private PlaybackRepeatActions repeat_actions;
         private PlaybackShuffleActions shuffle_actions;
@@ -58,7 +57,7 @@
             get { return shuffle_actions; }
         }
         
-        public PlaybackActions (InterfaceActionService actionService) : base ("Playback")
+        public PlaybackActions (InterfaceActionService actionService) : base (actionService, "Playback")
         {
             Add (new ActionEntry [] {
                 new ActionEntry ("PlayPauseAction", null,
@@ -106,7 +105,6 @@
             this["NextAction"].IconName = "media-skip-forward";
             this["PreviousAction"].IconName = "media-skip-backward";
             
-            action_service = actionService;
             ServiceManager.PlayerEngine.ConnectEvent (OnPlayerEvent, 
                 PlayerEvent.Error | 
                 PlayerEvent.EndOfStream | 
@@ -138,7 +136,7 @@
         private void OnPlayerStateChange (PlayerEventStateChangeArgs args)
         {
             if (play_pause_action == null) {
-                play_pause_action = action_service["Playback.PlayPauseAction"];
+                play_pause_action = Actions["Playback.PlayPauseAction"];
             }
             
             switch (args.Current) {

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackRepeatActions.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackRepeatActions.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackRepeatActions.cs	Sun May 18 00:19:17 2008
@@ -42,7 +42,6 @@
 {
     public class PlaybackRepeatActions : BansheeActionGroup, IEnumerable<RadioAction>
     {
-        private InterfaceActionService action_service;
         private RadioAction active_action;
         
         public RadioAction Active {
@@ -65,9 +64,8 @@
 
         public event EventHandler Changed;
         
-        public PlaybackRepeatActions (InterfaceActionService actionService) : base ("PlaybackRepeat")
+        public PlaybackRepeatActions (InterfaceActionService actionService) : base (actionService, "PlaybackRepeat")
         {
-            action_service = actionService;
             actionService.AddActionGroup (this);
             
             Add (new ActionEntry [] {
@@ -122,7 +120,7 @@
         
         public void AttachSubmenu (string menuItemPath)
         {
-            MenuItem parent = action_service.UIManager.GetWidget (menuItemPath) as MenuItem;
+            MenuItem parent = Actions.UIManager.GetWidget (menuItemPath) as MenuItem;
             parent.Submenu = CreateMenu ();
         }
         

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackShuffleActions.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackShuffleActions.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackShuffleActions.cs	Sun May 18 00:19:17 2008
@@ -44,7 +44,6 @@
     {
         private RadioAction active_action;
         private PlaybackActions playback_actions;
-        private InterfaceActionService action_service;
 
         public RadioAction Active {
             get { return active_action; }
@@ -67,11 +66,10 @@
         public event EventHandler Changed;
         
         public PlaybackShuffleActions (InterfaceActionService actionService, PlaybackActions playbackActions)
-            : base ("PlaybackShuffle")
+            : base (actionService, "PlaybackShuffle")
         {
             playback_actions = playbackActions;
-            action_service = actionService;
-            actionService.AddActionGroup (this);
+            Actions.AddActionGroup (this);
             
             Add (new ActionEntry [] {
                 new ActionEntry ("ShuffleMenuAction", null,
@@ -134,7 +132,7 @@
         
         public void AttachSubmenu (string menuItemPath)
         {
-            MenuItem parent = action_service.UIManager.GetWidget (menuItemPath) as MenuItem;
+            MenuItem parent = Actions.UIManager.GetWidget (menuItemPath) as MenuItem;
             parent.Submenu = CreateMenu ();
         }
         

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/SourceActions.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/SourceActions.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/SourceActions.cs	Sun May 18 00:19:17 2008
@@ -50,8 +50,6 @@
 {
     public class SourceActions : BansheeActionGroup
     {
-        private InterfaceActionService action_service;
-
         private IHasSourceView source_view;
         public IHasSourceView SourceView {
             get { return source_view; }
@@ -66,10 +64,8 @@
             get { return (SourceView.HighlightedSource as PrimarySource) ?? base.ActivePrimarySource; }
         }
 
-        public SourceActions (InterfaceActionService actionService) : base ("Source")
+        public SourceActions (InterfaceActionService actionService) : base (actionService, "Source")
         {
-            action_service = actionService;
-
             Add (new ActionEntry [] {
                 new ActionEntry ("NewPlaylistAction", null,
                     Catalog.GetString ("_New Playlist"), "<control>N",
@@ -141,7 +137,7 @@
             //ServiceManager.SourceManager.SourceAdded += OnPlayerEngineStateChanged;
             //ServiceManager.SourceManager.SourceRemoved += OnPlayerEngineStateChanged;
             ServiceManager.SourceManager.ActiveSourceChanged += HandleActiveSourceChanged;
-            action_service.GlobalActions["EditMenuAction"].Activated += HandleEditMenuActivated;
+            Actions.GlobalActions["EditMenuAction"].Activated += HandleEditMenuActivated;
         }
             
 #region State Event Handlers
@@ -213,7 +209,7 @@
             UpdateActions ();
 
             string path = ActionSource.Properties.GetString ("GtkActionPath") ?? "/SourceContextMenu";
-            Gtk.Menu menu = action_service.UIManager.GetWidget (path) as Menu;
+            Gtk.Menu menu = Actions.UIManager.GetWidget (path) as Menu;
             if (menu == null || menu.Children.Length == 0) {
                 SourceView.ResetHighlight ();
                 UpdateActions ();

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/TrackActions.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/TrackActions.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/TrackActions.cs	Sun May 18 00:19:17 2008
@@ -47,7 +47,6 @@
 {
     public class TrackActions : BansheeActionGroup
     {
-        private InterfaceActionService action_service;
         private RatingActionProxy rating_proxy;
 
         private static readonly string [] require_selection_actions = new string [] {
@@ -55,30 +54,9 @@
             "RemoveTracksAction", "RemoveTracksFromLibraryAction", "DeleteTracksFromDriveAction",
             "RateTracksAction", "SelectNoneAction"
         };
-        
-        private IHasTrackSelection track_selector;
-        public IHasTrackSelection TrackSelector {
-            get { return track_selector; }
-            set {
-                if (track_selector != null && track_selector != value) {
-                    track_selector.TrackSelectionProxy.Changed -= HandleSelectionChanged;
-                    track_selector.TrackSelectionProxy.SelectionChanged -= HandleSelectionChanged;
-                }
-
-                track_selector = value;
 
-                if (track_selector != null) {
-                    track_selector.TrackSelectionProxy.Changed += HandleSelectionChanged;
-                    track_selector.TrackSelectionProxy.SelectionChanged += HandleSelectionChanged;
-                    UpdateActions ();
-                }
-            }
-        }
-
-        public TrackActions (InterfaceActionService actionService) : base ("Track")
+        public TrackActions (InterfaceActionService actionService) : base (actionService, "Track")
         {
-            action_service = actionService;
-
             Add (new ActionEntry [] {
                 new ActionEntry("TrackContextMenuAction", null, 
                     String.Empty, null, null, OnTrackContextMenu),
@@ -137,9 +115,9 @@
                 //    null, OnJumpToPlayingTrack),
             });
 
-            action_service.UIManager.ActionsChanged += HandleActionsChanged;
+            Actions.UIManager.ActionsChanged += HandleActionsChanged;
 
-            action_service.GlobalActions["EditMenuAction"].Activated += HandleEditMenuActivated;
+            Actions.GlobalActions["EditMenuAction"].Activated += HandleEditMenuActivated;
             ServiceManager.SourceManager.ActiveSourceChanged += HandleActiveSourceChanged;
 
             this["AddToPlaylistAction"].HideIfEmpty = false;
@@ -147,18 +125,30 @@
 
 #region State Event Handlers
 
+        private ITrackModelSource current_source;
         private void HandleActiveSourceChanged (SourceEventArgs args)
         {
+            if (current_source != null) {
+                current_source.TrackModel.Selection.Changed -= HandleSelectionChanged;
+                current_source = null;
+            }
+            
+            ITrackModelSource new_source = ActiveSource as ITrackModelSource;
+            if (new_source != null) {
+                new_source.TrackModel.Selection.Changed += HandleSelectionChanged;
+                current_source = new_source;
+            }
+            
             UpdateActions ();
         }
 
         private void HandleActionsChanged (object sender, EventArgs args)
         {
-            if (action_service.UIManager.GetAction ("/MainMenu/EditMenu") != null) {
-                rating_proxy = new RatingActionProxy (action_service.UIManager, this["RateTracksAction"]);
+            if (Actions.UIManager.GetAction ("/MainMenu/EditMenu") != null) {
+                rating_proxy = new RatingActionProxy (Actions.UIManager, this["RateTracksAction"]);
                 rating_proxy.AddPath ("/MainMenu/EditMenu", "AddToPlaylist");
                 rating_proxy.AddPath ("/TrackContextMenu", "AddToPlaylist");
-                action_service.UIManager.ActionsChanged -= HandleActionsChanged;
+                Actions.UIManager.ActionsChanged -= HandleActionsChanged;
             }
         }
 
@@ -197,13 +187,10 @@
 
         private void UpdateActions ()
         {
-            if (TrackSelector == null || TrackSelector.TrackSelectionProxy == null) {
-                return;
-            }
-            
-            Hyena.Collections.Selection selection = TrackSelector.TrackSelectionProxy.Selection;
             Source source = ServiceManager.SourceManager.ActiveSource;
             bool in_database = source is DatabaseSource;
+            
+            Hyena.Collections.Selection selection = (source is ITrackModelSource) ? (source as ITrackModelSource).TrackModel.Selection : null;
 
             if (selection != null) {
                 bool has_selection = selection.Count > 0;
@@ -235,15 +222,17 @@
 
         private void ResetRating ()
         {
-            int rating = 0;
-
-            // If there is only one track, get the preset rating
-            if (TrackSelector.TrackSelectionProxy.Selection.Count == 1) {
-                foreach (TrackInfo track in TrackSelector.GetSelectedTracks ()) {
-                    rating = track.Rating;
+            if (current_source != null) {
+                int rating = 0;
+    
+                // If there is only one track, get the preset rating
+                if (current_source.TrackModel.Selection.Count == 1) {
+                    foreach (TrackInfo track in current_source.TrackModel.SelectedItems) {
+                        rating = track.Rating;
+                    }
                 }
+                rating_proxy.Reset (rating);
             }
-            rating_proxy.Reset (rating);
         }
 
 #endregion
@@ -252,42 +241,30 @@
 
         private void OnSelectAll (object o, EventArgs args)
         {
-            TrackSelector.TrackSelectionProxy.Selection.SelectAll ();
+            if (current_source != null)
+                current_source.TrackModel.Selection.SelectAll ();
         }
 
         private void OnSelectNone (object o, EventArgs args)
         {
-            TrackSelector.TrackSelectionProxy.Selection.Clear ();
+            if (current_source != null)
+                current_source.TrackModel.Selection.Clear ();
         }
 
         private void OnTrackContextMenu (object o, EventArgs args)
         {
             ResetRating ();
-
-            Gtk.Menu menu = action_service.UIManager.GetWidget ("/TrackContextMenu") as Menu;
-            if (menu == null || menu.Children.Length == 0) {
-                return;
-            }
-
-            int visible_children = 0;
-            foreach (Widget child in menu)
-                if (child.Visible)
-                    visible_children++;
-
-            if (visible_children == 0) {
-                return;
-            }
-
-            menu.Show (); 
-            menu.Popup (null, null, null, 0, Gtk.Global.CurrentEventTime);
+            ShowContextMenu ("/TrackContextMenu");
         }
 
         private void OnTrackProperties (object o, EventArgs args)
         {
-            TrackEditor propEdit = new TrackEditor (TrackSelector.GetSelectedTracks ());
-            propEdit.Saved += delegate {
-                //ui.playlistView.QueueDraw();
-            };
+            if (current_source != null) {
+                TrackEditor propEdit = new TrackEditor (current_source.TrackModel.SelectedItems);
+                propEdit.Saved += delegate {
+                    //ui.playlistView.QueueDraw();
+                };
+            }
         }
 
         // Called when the Add to Playlist action is highlighted.
@@ -407,19 +384,21 @@
 
         private void OnSearchForSameArtist (object o, EventArgs args)
         {
-            Source source = ActiveSource;
-            foreach (TrackInfo track in TrackSelector.GetSelectedTracks ()) {
-                source.FilterQuery = BansheeQuery.ArtistField.ToTermString (":", track.ArtistName);
-                break;
+            if (current_source != null) {
+                foreach (TrackInfo track in current_source.TrackModel.SelectedItems) {
+                    ActiveSource.FilterQuery = BansheeQuery.ArtistField.ToTermString (":", track.ArtistName);
+                    break;
+                }
             }
         }
 
         private void OnSearchForSameAlbum (object o, EventArgs args)
         {
-            Source source = ActiveSource;
-            foreach (TrackInfo track in TrackSelector.GetSelectedTracks ()) {
-                source.FilterQuery = BansheeQuery.AlbumField.ToTermString (":", track.AlbumTitle);
-                break;
+            if (current_source != null) {
+                foreach (TrackInfo track in current_source.TrackModel.SelectedItems) {
+                    ActiveSource.FilterQuery = BansheeQuery.AlbumField.ToTermString (":", track.AlbumTitle);
+                    break;
+                }
             }
         }
 

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/ViewActions.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/ViewActions.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/ViewActions.cs	Sun May 18 00:19:17 2008
@@ -40,8 +40,6 @@
     public class ViewActions : BansheeActionGroup
     {
         public delegate void FullscreenHandler (bool fullscreen);
-        
-        private InterfaceActionService action_service;
         private FullscreenHandler fullscreen_handler;
         
         public FullscreenHandler Fullscreen {
@@ -57,7 +55,7 @@
             }
         }
     
-        public ViewActions (InterfaceActionService actionService) : base ("View")
+        public ViewActions (InterfaceActionService actionService) : base (actionService, "View")
         {
             Add (new ActionEntry [] {
                 new ActionEntry ("ViewMenuAction", null,
@@ -81,8 +79,6 @@
             });
             
             ServiceManager.PlayerEngine.ConnectEvent (OnPlayerEvent, PlayerEvent.StateChange);
-            action_service = actionService;
-
             OnFullScreen (null, EventArgs.Empty);
         }
         
@@ -90,7 +86,7 @@
         {
             if (((PlayerEventStateChangeArgs)args).Current == PlayerState.Ready && 
                 !ServiceManager.PlayerEngine.SupportsEqualizer) {
-                action_service["View.ShowEqualizerAction"].Sensitive = false;
+                Actions["View.ShowEqualizerAction"].Sensitive = false;
             }
         }
                 

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Sources.Gui/CompositeTrackSourceContents.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Sources.Gui/CompositeTrackSourceContents.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Sources.Gui/CompositeTrackSourceContents.cs	Sun May 18 00:19:17 2008
@@ -47,289 +47,34 @@
 
 namespace Banshee.Sources.Gui
 {
-    public class CompositeTrackSourceContents : VBox, ITrackModelSourceContents
+    public class CompositeTrackSourceContents : ListBrowserSourceContents, ITrackModelSourceContents
     {
         private ArtistListView artist_view;
         private AlbumListView album_view;
         private TrackListView track_view;
-        
-        private ArtistListModel artist_model;
-        private AlbumListModel album_model;
-        private TrackListModel track_model;
 
-        private Dictionary<object, double> model_positions = new Dictionary<object, double> ();
-        
-        private Gtk.ScrolledWindow artist_scrolled_window;
-        private Gtk.ScrolledWindow album_scrolled_window;
-        private Gtk.ScrolledWindow track_scrolled_window;
-        private bool view_is_built = false;
-        
-        private Paned container;
-        private Widget browser_container;
-        private InterfaceActionService action_service;
-        private ActionGroup browser_view_actions;
-        
-        private static string menu_xml = @"
-            <ui>
-              <menubar name=""MainMenu"">
-                <menu name=""ViewMenu"" action=""ViewMenuAction"">
-                  <placeholder name=""BrowserViews"">
-                    <menuitem name=""BrowserVisible"" action=""BrowserVisibleAction"" />
-                    <separator />
-                    <menuitem name=""BrowserTop"" action=""BrowserTopAction"" />
-                    <menuitem name=""BrowserLeft"" action=""BrowserLeftAction"" />
-                    <separator />
-                  </placeholder>
-                </menu>
-              </menubar>
-            </ui>
-        ";
-        
-        public CompositeTrackSourceContents ()
+        public CompositeTrackSourceContents () : base ()
         {
-            string position = BrowserPosition.Get ();
-            if (position == "top") {
-                LayoutTop ();
-            } else {
-                LayoutLeft ();
-            }
-            
-            if (ServiceManager.Contains ("InterfaceActionService")) {
-                action_service = ServiceManager.Get<InterfaceActionService> ();
-                
-                browser_view_actions = new ActionGroup ("BrowserView");
-                
-                browser_view_actions.Add (new RadioActionEntry [] {
-                    new RadioActionEntry ("BrowserLeftAction", null, 
-                        Catalog.GetString ("Browser on Left"), null,
-                        Catalog.GetString ("Show the artist/album browser to the left of the track list"), 0),
-                    
-                    new RadioActionEntry ("BrowserTopAction", null,
-                        Catalog.GetString ("Browser on Top"), null,
-                        Catalog.GetString ("Show the artist/album browser above the track list"), 1),
-                }, position == "top" ? 1 : 0, OnViewModeChanged);
-                
-                browser_view_actions.Add (new ToggleActionEntry [] {
-                    new ToggleActionEntry ("BrowserVisibleAction", null,
-                        Catalog.GetString ("Show Browser"), "<control>B",
-                        Catalog.GetString ("Show or hide the artist/album browser"), 
-                        OnToggleBrowser, BrowserVisible.Get ())
-                });
-                
-                action_service.AddActionGroup (browser_view_actions);
-                //action_merge_id = action_service.UIManager.NewMergeId ();
-                action_service.UIManager.AddUiFromString (menu_xml);
-            }
-            
-            ServiceManager.SourceManager.ActiveSourceChanged += delegate {
-                browser_container.Visible = ActiveSourceCanHasBrowser ? BrowserVisible.Get () : false; 
-            };
-            
-            NoShowAll = true;
         }
         
-        private void BuildCommon ()
+        protected override void InitializeViews ()
         {
-            if (view_is_built) {
-                return;
-            }
-            
-            artist_view = new ArtistListView ();
-            album_view = new AlbumListView ();
-            track_view = new TrackListView ();
-        
-            artist_view.HeaderVisible = false;
-            album_view.HeaderVisible = false;
-            
-            if (Banshee.Base.ApplicationContext.CommandLine.Contains ("smooth-scroll")) {
-                artist_scrolled_window = new SmoothScrolledWindow ();
-                album_scrolled_window = new SmoothScrolledWindow ();
-                track_scrolled_window = new SmoothScrolledWindow ();
-            } else {
-                artist_scrolled_window = new Gtk.ScrolledWindow ();
-                album_scrolled_window = new Gtk.ScrolledWindow ();
-                track_scrolled_window = new Gtk.ScrolledWindow ();
-            }
-            
-            artist_scrolled_window.Add (artist_view);
-            artist_scrolled_window.HscrollbarPolicy = PolicyType.Automatic;
-            artist_scrolled_window.VscrollbarPolicy = PolicyType.Automatic;
-            
-            album_scrolled_window.Add (album_view);
-            album_scrolled_window.HscrollbarPolicy = PolicyType.Automatic;
-            album_scrolled_window.VscrollbarPolicy = PolicyType.Automatic;
-            
-            track_scrolled_window.Add (track_view);
-            track_scrolled_window.HscrollbarPolicy = PolicyType.Automatic;
-            track_scrolled_window.VscrollbarPolicy = PolicyType.Automatic;
-            
-            track_view.SetModel (track_model);
-            artist_view.SetModel (artist_model);
-            album_view.SetModel (album_model);
-
-            artist_view.SelectionProxy.Changed += OnBrowserViewSelectionChanged;
-            album_view.SelectionProxy.Changed += OnBrowserViewSelectionChanged;
-            
-            view_is_built = true;
+            SetupMainView (track_view = new TrackListView ());
+            SetupFilterView (artist_view = new ArtistListView ());
+            SetupFilterView (album_view = new AlbumListView ());
         }
         
-        private void Reset ()
+        protected override void ClearFilterSelections ()
         {
-            // Unparent the views' scrolled window parents so they can be re-packed in 
-            // a new layout. The main container gets destroyed since it will be recreated.
-            
-            if (artist_scrolled_window != null) {
-                Container artist_album_container = artist_scrolled_window.Parent as Container;
-                if (artist_album_container != null) {
-                    artist_album_container.Remove (artist_scrolled_window);
-                    artist_album_container.Remove (album_scrolled_window);
-                }
-            }
-            
-            if (track_scrolled_window != null) {
-                container.Remove (track_scrolled_window);
-            }
-            
-            if (container != null) {
-                Remove (container);
-                container.Destroy ();
-            }
-            
-            BuildCommon ();
-        }
-
-        private void LayoutLeft ()
-        {
-            Reset ();
-            
-            container = new HPaned ();
-            VPaned artist_album_box = new VPaned ();
-            
-            artist_album_box.Add1 (artist_scrolled_window);
-            artist_album_box.Add2 (album_scrolled_window);
-
-            artist_album_box.Position = 350;
-            PersistentPaneController.Control (artist_album_box, "browser.left.artist_album_box");
-            
-            container.Add1 (artist_album_box);
-            container.Add2 (track_scrolled_window);
-            
-            browser_container = artist_album_box;
-            
-            container.Position = 275;
-            PersistentPaneController.Control (container, "browser.left");
-            ShowPack ();
-        }
-        
-        private void LayoutTop ()
-        {
-            Reset ();
-            
-            container = new VPaned ();
-            HBox artist_album_box = new HBox ();
-            artist_album_box.Spacing = 10;
-            
-            artist_album_box.PackStart (artist_scrolled_window, true, true, 0);
-            artist_album_box.PackStart (album_scrolled_window, true, true, 0);
-            
-            container.Add1 (artist_album_box);
-            container.Add2 (track_scrolled_window);
-            
-            browser_container = artist_album_box;
-            
-            container.Position = 175;
-            PersistentPaneController.Control (container, "browser.top");
-            ShowPack ();
-        }
-        
-        private void ShowPack ()
-        {
-            PackStart (container, true, true, 0);
-            NoShowAll = false;
-            ShowAll ();
-            NoShowAll = true;
-            browser_container.Visible = BrowserVisible.Get ();
-        }
-        
-        private void OnViewModeChanged (object o, ChangedArgs args)
-        {
-            if (args.Current.Value == 0) {
-                LayoutLeft ();
-                BrowserPosition.Set ("left");
-            } else {
-                LayoutTop ();
-                BrowserPosition.Set ("top");
-            }
-        }
-                
-        private void OnToggleBrowser (object o, EventArgs args)
-        {
-            ToggleAction action = (ToggleAction)o;
             artist_view.Selection.Clear ();
             album_view.Selection.Clear ();
-            browser_container.Visible = action.Active && ActiveSourceCanHasBrowser;
-            BrowserVisible.Set (action.Active);
-        }
-        
-        protected virtual void OnBrowserViewSelectionChanged (object o, EventArgs args)
-        {
-            Hyena.Collections.Selection selection = (Hyena.Collections.Selection)o;
-            TrackListModel model = track_view.Model as TrackListModel;
-
-            if (selection.AllSelected) {
-                if (model != null && o == artist_view.Selection ) {
-                    artist_view.ScrollTo (0);
-                } else if (model != null && o == album_view.Selection) {
-                    album_view.ScrollTo (0);
-                }
-                return;
-            }
         }
 
-        public void SetModels (TrackListModel track, ArtistListModel artist, AlbumListModel album)
+        public void SetModels (TrackListModel track, IListModel<ArtistInfo> artist, IListModel<AlbumInfo> album)
         {
-            // Save the old vertical positions
-            if (track_model != null) {
-                model_positions[track_model] = track_view.Vadjustment.Value;
-            }
-            
-            if (artist_model != null) {
-                model_positions[artist_model] = artist_view.Vadjustment.Value;
-            }
-            
-            if (album_model != null) {
-                model_positions[album_model] = album_view.Vadjustment.Value;
-            }
-            
-            // Set the new models and restore positions
-            track_model = track;
-            artist_model = artist;
-            album_model = album;
-
-            // Initialize the new positions if needed
-            if (track_model != null) {
-                if (!model_positions.ContainsKey (track_model)) {
-                    model_positions[track_model] = 0.0;
-                }
-                
-                track_view.SetModel (track_model, model_positions[track_model]);
-            }
-
-            if (artist_model != null) {
-                if (!model_positions.ContainsKey (artist_model)) {
-                    model_positions[artist_model] = 0.0;
-                }
-                
-                artist_view.SetModel (artist_model, model_positions[artist_model]);
-            }
-
-            if (album_model != null) {
-                if (!model_positions.ContainsKey (album_model)) {
-                    model_positions[album_model] = 0.0;
-                }
-                
-                album_view.SetModel (album_model, model_positions[album_model]);
-            }
+            SetModel (track);
+            SetModel (artist);
+            SetModel (album);
         }
         
         IListView<TrackInfo> ITrackModelSourceContents.TrackView {
@@ -368,7 +113,7 @@
             get { return (AlbumListModel)album_view.Model; }
         }
 
-        private bool ActiveSourceCanHasBrowser {
+        protected override bool ActiveSourceCanHasBrowser {
             get {
                 if (!(ServiceManager.SourceManager.ActiveSource is ITrackModelSource)) {
                     return false;
@@ -380,49 +125,40 @@
 
 #region Implement ISourceContents
 
-        private ITrackModelSource track_source;
-
-        public bool SetSource (ISource source)
+        public override bool SetSource (ISource source)
         {
-            track_source = source as ITrackModelSource;
+            ITrackModelSource track_source = source as ITrackModelSource;
             if (track_source == null) {
                 return false;
             }
-
-            SetModels (track_source.TrackModel, track_source.ArtistModel, track_source.AlbumModel);
+            
+            this.source = source;
+            
+            SetModel (track_view, track_source.TrackModel);
+            
+            foreach (IListModel model in track_source.FilterModels) {
+                if (model is IListModel<ArtistInfo>)
+                    SetModel (artist_view, (model as IListModel<ArtistInfo>));
+                else if (model is IListModel<AlbumInfo>)
+                    SetModel (album_view, (model as IListModel<AlbumInfo>));
+                else
+                    Hyena.Log.DebugFormat ("CompositeTrackSourceContents got non-album/artist filter model: {0}", model);
+            }
+            
             track_view.HeaderVisible = true;
             return true;
         }
 
-        public void ResetSource ()
+        public override void ResetSource ()
         {
-            track_source = null;
-            SetModels (null, null, null);
+            source = null;
+            track_view.SetModel (null);
+            artist_view.SetModel (null);
+            album_view.SetModel (null);
             track_view.HeaderVisible = false;
         }
 
-        public ISource Source {
-            get { return track_source; }
-        }
-
-        public Widget Widget {
-            get { return this; }
-        }
-
 #endregion
-        
-        public static readonly SchemaEntry<bool> BrowserVisible = new SchemaEntry<bool> (
-            "browser", "visible",
-            true,
-            "Artist/Album Browser Visibility",
-            "Whether or not to show the Artist/Album browser"
-        );
-        
-        public static readonly SchemaEntry<string> BrowserPosition = new SchemaEntry<string> (
-            "browser", "position",
-            "left",
-            "Artist/Album Browser Position",
-            "The position of the Artist/Album browser; either 'top' or 'left'"
-        );
+
     }
 }

Added: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Sources.Gui/ListBrowserSourceContents.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Sources.Gui/ListBrowserSourceContents.cs	Sun May 18 00:19:17 2008
@@ -0,0 +1,359 @@
+//
+// ListBrowserSourceContents.cs
+//
+// Authors:
+//   Aaron Bockover <abockover novell com>
+//   Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2007-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.Reflection;
+using System.Collections.Generic;
+
+using Gtk;
+using Mono.Unix;
+
+using Hyena.Data;
+using Hyena.Data.Gui;
+using Hyena.Widgets;
+
+using Banshee.Sources;
+using Banshee.ServiceStack;
+using Banshee.Collection;
+using Banshee.Configuration;
+using Banshee.Gui;
+using Banshee.Collection.Gui;
+
+using ScrolledWindow=Gtk.ScrolledWindow;
+
+namespace Banshee.Sources.Gui
+{
+    public abstract class ListBrowserSourceContents : VBox, ISourceContents
+    {
+        private object main_view;
+        private Gtk.ScrolledWindow main_scrolled_window;
+        
+        private List<object> filter_views = new List<object> ();
+        private List<ScrolledWindow> filter_scrolled_windows = new List<ScrolledWindow> ();
+        
+        private Dictionary<object, double> model_positions = new Dictionary<object, double> ();
+        
+        private Paned container;
+        private Widget browser_container;
+        private InterfaceActionService action_service;
+        private ActionGroup browser_view_actions;
+        
+        private static string menu_xml = @"
+            <ui>
+              <menubar name=""MainMenu"">
+                <menu name=""ViewMenu"" action=""ViewMenuAction"">
+                  <placeholder name=""BrowserViews"">
+                    <menuitem name=""BrowserVisible"" action=""BrowserVisibleAction"" />
+                    <separator />
+                    <menuitem name=""BrowserTop"" action=""BrowserTopAction"" />
+                    <menuitem name=""BrowserLeft"" action=""BrowserLeftAction"" />
+                    <separator />
+                  </placeholder>
+                </menu>
+              </menubar>
+            </ui>
+        ";
+
+        public ListBrowserSourceContents ()
+        {
+            InitializeViews ();
+        
+            string position = BrowserPosition.Get ();
+            if (position == "top") {
+                LayoutTop ();
+            } else {
+                LayoutLeft ();
+            }
+            
+            if (ServiceManager.Contains ("InterfaceActionService")) {
+                action_service = ServiceManager.Get<InterfaceActionService> ();
+                
+                if (action_service.FindActionGroup ("BrowserView") == null) {
+                    browser_view_actions = new ActionGroup ("BrowserView");
+                    
+                    browser_view_actions.Add (new RadioActionEntry [] {
+                        new RadioActionEntry ("BrowserLeftAction", null, 
+                            Catalog.GetString ("Browser on Left"), null,
+                            Catalog.GetString ("Show the artist/album browser to the left of the track list"), 0),
+                        
+                        new RadioActionEntry ("BrowserTopAction", null,
+                            Catalog.GetString ("Browser on Top"), null,
+                            Catalog.GetString ("Show the artist/album browser above the track list"), 1),
+                    }, position == "top" ? 1 : 0, null);
+                    
+                    browser_view_actions.Add (new ToggleActionEntry [] {
+                        new ToggleActionEntry ("BrowserVisibleAction", null,
+                            Catalog.GetString ("Show Browser"), "<control>B",
+                            Catalog.GetString ("Show or hide the artist/album browser"), 
+                            null, BrowserVisible.Get ())
+                    });
+                    
+                    action_service.AddActionGroup (browser_view_actions);
+                    //action_merge_id = action_service.UIManager.NewMergeId ();
+                    action_service.UIManager.AddUiFromString (menu_xml);
+                }
+                
+                (action_service.FindAction("BrowserView.BrowserLeftAction") as RadioAction).Changed += OnViewModeChanged;
+                (action_service.FindAction("BrowserView.BrowserTopAction") as RadioAction).Changed += OnViewModeChanged;
+                action_service.FindAction("BrowserView.BrowserVisibleAction").Activated += OnToggleBrowser;
+            }
+            
+            ServiceManager.SourceManager.ActiveSourceChanged += delegate {
+                browser_container.Visible = ActiveSourceCanHasBrowser ? BrowserVisible.Get () : false; 
+            };
+            
+            NoShowAll = true;
+        }
+        
+        protected abstract void InitializeViews ();
+        
+        protected void SetupMainView<T> (ListView<T> main_view)
+        {
+            main_scrolled_window = SetupView (main_view);
+        }
+        
+        protected void SetupFilterView<T> (ListView<T> filter_view)
+        {
+            filter_scrolled_windows.Add (SetupView (filter_view));
+            filter_view.HeaderVisible = false;
+            filter_view.SelectionProxy.Changed += OnBrowserViewSelectionChanged;
+        }
+        
+        private ScrolledWindow SetupView (Widget view)
+        {
+            ScrolledWindow window = null;
+            
+            if (Banshee.Base.ApplicationContext.CommandLine.Contains ("smooth-scroll")) {
+                window = new SmoothScrolledWindow ();
+            } else {
+                window = new ScrolledWindow ();
+            }
+            
+            window.Add (view);
+            window.HscrollbarPolicy = PolicyType.Automatic;
+            window.VscrollbarPolicy = PolicyType.Automatic;
+
+            return window;
+        }
+        
+        private void Reset ()
+        {
+            Hyena.Log.Information ("ListBrowser Reset");
+            // Unparent the views' scrolled window parents so they can be re-packed in 
+            // a new layout. The main container gets destroyed since it will be recreated.
+            
+            if (filter_scrolled_windows.Count > 0) {
+                Container filter_container = filter_scrolled_windows[0].Parent as Container;
+                if (filter_container != null) {
+                    foreach (ScrolledWindow window in filter_scrolled_windows) {
+                        filter_container.Remove (window);
+                    }
+                } else
+                    Hyena.Log.Information ("No filter container");
+            } else
+                Hyena.Log.Information ("No filter windows");
+            
+            if (container != null && main_scrolled_window != null) {
+                container.Remove (main_scrolled_window);
+            } else
+                Hyena.Log.Information ("No main container");
+            
+            if (container != null) {
+                Remove (container);
+                container.Destroy ();
+            }
+        }
+
+        private void LayoutLeft ()
+        {
+            Hyena.Log.Information ("ListBrowser LayoutLeft");
+            Reset ();
+            
+            container = new HPaned ();
+            VBox filter_box = new VBox ();
+            filter_box.Spacing = 10;
+            
+            foreach (ScrolledWindow window in filter_scrolled_windows) {
+                filter_box.PackStart (window, true, true, 0);
+            }
+            
+            //filter_box.Position = 350;
+            //PersistentPaneController.Control (filter_box, "browser.left.artist_album_box");
+            
+            container.Add1 (filter_box);
+            container.Add2 (main_scrolled_window);
+            
+            browser_container = filter_box;
+            
+            container.Position = 275;
+            PersistentPaneController.Control (container, "browser.left");
+            ShowPack ();
+        }
+        
+        private void LayoutTop ()
+        {
+            Hyena.Log.Information ("ListBrowser LayoutTop");
+            Reset ();
+            
+            container = new VPaned ();
+            HBox filter_box = new HBox ();
+            filter_box.Spacing = 10;
+            
+            foreach (ScrolledWindow window in filter_scrolled_windows) {
+                filter_box.PackStart (window, true, true, 0);
+            }
+            
+            container.Add1 (filter_box);
+            container.Add2 (main_scrolled_window);
+            
+            browser_container = filter_box;
+            
+            container.Position = 175;
+            PersistentPaneController.Control (container, "browser.top");
+            ShowPack ();
+        }
+        
+        private void ShowPack ()
+        {
+            PackStart (container, true, true, 0);
+            NoShowAll = false;
+            ShowAll ();
+            NoShowAll = true;
+            browser_container.Visible = BrowserVisible.Get ();
+        }
+        
+        private void OnViewModeChanged (object o, ChangedArgs args)
+        {
+            Hyena.Log.InformationFormat ("ListBrowser mode toggled, val = {0}", args.Current.Value);
+            if (args.Current.Value == 0) {
+                LayoutLeft ();
+                BrowserPosition.Set ("left");
+            } else {
+                LayoutTop ();
+                BrowserPosition.Set ("top");
+            }
+        }
+                
+        private void OnToggleBrowser (object o, EventArgs args)
+        {
+            ToggleAction action = (ToggleAction)o;
+            
+            browser_container.Visible = action.Active && ActiveSourceCanHasBrowser;
+            BrowserVisible.Set (action.Active);
+            
+            if (!browser_container.Visible) {
+                ClearFilterSelections ();
+            }
+        }
+        
+        protected abstract void ClearFilterSelections ();
+        
+        protected virtual void OnBrowserViewSelectionChanged (object o, EventArgs args)
+        {
+            Hyena.Collections.Selection selection = (Hyena.Collections.Selection) o;
+
+            if (selection.AllSelected) {
+                foreach (IListView view in filter_views) {
+                    if (view.Selection == selection) {
+                        view.ScrollTo (0);
+                    }
+                }
+            }
+        }
+
+        protected void SetModel<T> (IListModel<T> model)
+        {
+            ListView<T> view = FindListView <T> ();
+            if (view != null) {
+                SetModel (view, model);
+            } else {
+                Hyena.Log.DebugFormat ("Unable to find view for model {0}", model);
+            }
+        }
+        
+        protected void SetModel<T> (ListView<T> view, IListModel<T> model)
+        {
+            if (view.Model != null) {
+                model_positions[view.Model] = view.Vadjustment.Value;
+            }
+            
+            if (!model_positions.ContainsKey (model))
+                    model_positions[model] = 0.0;
+            
+            view.SetModel (model, model_positions[model]);
+        }
+        
+        private ListView<T> FindListView<T> ()
+        {
+            if (main_view is ListView<T>)
+                return (ListView<T>) main_view;
+        
+            foreach (object view in filter_views)
+                if (view is ListView<T>)
+                    return (ListView<T>) view;
+
+            return null;
+        }
+
+        protected abstract bool ActiveSourceCanHasBrowser { get; }
+
+#region Implement ISourceContents
+
+        protected ISource source;
+
+        public abstract bool SetSource (ISource source);
+
+        public abstract void ResetSource ();
+
+        public ISource Source {
+            get { return source; }
+        }
+
+        public Widget Widget {
+            get { return this; }
+        }
+
+#endregion
+        
+        public static readonly SchemaEntry<bool> BrowserVisible = new SchemaEntry<bool> (
+            "browser", "visible",
+            true,
+            "Artist/Album Browser Visibility",
+            "Whether or not to show the Artist/Album browser"
+        );
+        
+        public static readonly SchemaEntry<string> BrowserPosition = new SchemaEntry<string> (
+            "browser", "position",
+            "left",
+            "Artist/Album Browser Position",
+            "The position of the Artist/Album browser; either 'top' or 'left'"
+        );
+    }
+}

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.ThickClient.mdp
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.ThickClient.mdp	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.ThickClient.mdp	Sun May 18 00:19:17 2008
@@ -109,6 +109,9 @@
     <File name="Resources/jcastro.png" subtype="Code" buildaction="EmbedAsResource" />
     <File name="Banshee.Sources.Gui/SourceModel.cs" subtype="Code" buildaction="Compile" />
     <File name="Banshee.Sources.Gui/SourceComboBox.cs" subtype="Code" buildaction="Compile" />
+    <File name="Banshee.Collection.Gui/TrackFilterListView.cs" subtype="Code" buildaction="Compile" />
+    <File name="Banshee.Collection.Gui/ColumnCellFileSize.cs" subtype="Code" buildaction="Compile" />
+    <File name="Banshee.Sources.Gui/ListBrowserSourceContents.cs" subtype="Code" buildaction="Compile" />
     <File name="Banshee.Collection.Gui/DefaultColumnController.cs" subtype="Code" buildaction="Compile" />
     <File name="Banshee.Collection.Gui/XmlColumnController.cs" subtype="Code" buildaction="Compile" />
   </Contents>

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Makefile.am
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Makefile.am	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Makefile.am	Sun May 18 00:19:17 2008
@@ -14,6 +14,7 @@
 	Banshee.Collection.Gui/ColumnCellAlbum.cs \
 	Banshee.Collection.Gui/ColumnCellDateTime.cs \
 	Banshee.Collection.Gui/ColumnCellDuration.cs \
+	Banshee.Collection.Gui/ColumnCellFileSize.cs \
 	Banshee.Collection.Gui/ColumnCellPlaybackIndicator.cs \
 	Banshee.Collection.Gui/ColumnCellPositiveInt.cs \
 	Banshee.Collection.Gui/ColumnCellTrack.cs \
@@ -21,6 +22,7 @@
 	Banshee.Collection.Gui/DefaultColumnController.cs \
 	Banshee.Collection.Gui/PersistentColumnController.cs \
 	Banshee.Collection.Gui/TerseTrackListView.cs \
+	Banshee.Collection.Gui/TrackFilterListView.cs \
 	Banshee.Collection.Gui/TrackListView.cs \
 	Banshee.Collection.Gui/XmlColumnController.cs \
 	Banshee.Equalizer.Gui/EqualizerBandScale.cs \
@@ -94,6 +96,7 @@
 	Banshee.Sources.Gui/CompositeTrackSourceContents.cs \
 	Banshee.Sources.Gui/ISourceContents.cs \
 	Banshee.Sources.Gui/ITrackModelSourceContents.cs \
+	Banshee.Sources.Gui/ListBrowserSourceContents.cs \
 	Banshee.Sources.Gui/ObjectListSourceContents.cs \
 	Banshee.Sources.Gui/SourceComboBox.cs \
 	Banshee.Sources.Gui/SourceIconResolver.cs \

Modified: trunk/banshee/src/Dap/Banshee.Dap.Ipod/Banshee.Dap.Ipod/IpodTrackInfo.cs
==============================================================================
--- trunk/banshee/src/Dap/Banshee.Dap.Ipod/Banshee.Dap.Ipod/IpodTrackInfo.cs	(original)
+++ trunk/banshee/src/Dap/Banshee.Dap.Ipod/Banshee.Dap.Ipod/IpodTrackInfo.cs	Sun May 18 00:19:17 2008
@@ -218,8 +218,8 @@
                 }
             }
             
-            if (CoverArtSpec.CoverExists (ArtistAlbumId)) {
-                SetIpodCoverArt (device, track, CoverArtSpec.GetPath (ArtistAlbumId));
+            if (CoverArtSpec.CoverExists (ArtworkId)) {
+                SetIpodCoverArt (device, track, CoverArtSpec.GetPath (ArtworkId));
             }
         }
         

Modified: trunk/banshee/src/Dap/Banshee.Dap/Banshee.Dap/DapSource.cs
==============================================================================
--- trunk/banshee/src/Dap/Banshee.Dap/Banshee.Dap/DapSource.cs	(original)
+++ trunk/banshee/src/Dap/Banshee.Dap/Banshee.Dap/DapSource.cs	Sun May 18 00:19:17 2008
@@ -97,8 +97,8 @@
             Expanded = true;
             Properties.SetStringList ("Icon.Name", GetIconNames ());
             Properties.Set<string> ("SourcePropertiesActionLabel", Catalog.GetString ("Device Properties"));
-            Properties.Set<OpenPropertiesDelegate> ("SourceProperties.GuiHandler", delegate { 
-                new DapPropertiesDialog (this).RunDialog (); 
+            Properties.Set<OpenPropertiesDelegate> ("SourceProperties.GuiHandler", delegate {
+                new DapPropertiesDialog (this).RunDialog ();
             });
 
             if (String.IsNullOrEmpty (GenericName)) {
@@ -115,6 +115,9 @@
             acceptable_mimetypes = MediaCapabilities != null 
                 ? MediaCapabilities.PlaybackMimeTypes 
                 : new string [] { "taglib/mp3" };
+
+            AddChildSource (new MusicGroupSource (this));
+            AddChildSource (new VideoGroupSource (this));
         }
         
         public override void AddChildSource (Source child)
@@ -166,14 +169,6 @@
             LoadFromDevice ();
             OnTracksAdded ();
             HideStatus ();
-            
-            ThreadAssist.ProxyToMain (delegate {
-                DatabaseSource src;
-                AddChildSource (src = new MusicGroupSource (this));
-                src.Reload ();
-                AddChildSource (src = new VideoGroupSource (this));
-                src.Reload ();
-            });
         }
 
         protected virtual void LoadFromDevice ()

Modified: trunk/banshee/src/Dap/Banshee.Dap/Banshee.Dap/MediaGroupSource.cs
==============================================================================
--- trunk/banshee/src/Dap/Banshee.Dap/Banshee.Dap/MediaGroupSource.cs	(original)
+++ trunk/banshee/src/Dap/Banshee.Dap/Banshee.Dap/MediaGroupSource.cs	Sun May 18 00:19:17 2008
@@ -28,6 +28,8 @@
 
 using System;
 
+using Mono.Unix;
+
 using Hyena.Query;
 using Banshee.Query;
 using Banshee.Sources;
@@ -43,6 +45,13 @@
         public MediaGroupSource (DapSource parent, string name) : base (name, parent.DbId)
         {
             this.parent = parent;
+            Properties.Set<string> ("DeleteTracksFromDriveActionLabel", String.Format (Catalog.GetString ("Delete From {0}"), parent.Name));
+        }
+
+        protected override void AfterInitialized ()
+        {
+            base.AfterInitialized ();
+            Reload ();
         }
         
         public override string ConditionSql {

Modified: trunk/banshee/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdSource.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdSource.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdSource.cs	Sun May 18 00:19:17 2008
@@ -69,7 +69,11 @@
                 AudioCdTrackInfo playing_track = ServiceManager.PlayerEngine.CurrentTrack as AudioCdTrackInfo;
                 return playing_track != null && playing_track.Model == disc_model;
             }
-        }            
+        }
+        
+        public System.Collections.Generic.IEnumerable<Banshee.Collection.Database.IFilterListModel> FilterModels {
+            get { yield break; }
+        }
         
         public void StopPlayingDisc ()
         {

Modified: trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmActions.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmActions.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmActions.cs	Sun May 18 00:19:17 2008
@@ -54,13 +54,10 @@
     public class LastfmActions : BansheeActionGroup
     {
         private LastfmSource lastfm;
-
-        private InterfaceActionService action_service;
         private uint actions_id;
 
-        public LastfmActions (LastfmSource lastfm) : base ("Lastfm")
+        public LastfmActions (LastfmSource lastfm) : base (ServiceManager.Get<InterfaceActionService> (), "Lastfm")
         {
-            action_service = ServiceManager.Get<InterfaceActionService> ();
             this.lastfm = lastfm;
             
             AddImportant (
@@ -188,11 +185,11 @@
             this["LastfmLoveAction"].IsImportant = true;
             this["LastfmHateAction"].IsImportant = true;
 
-            actions_id = action_service.UIManager.AddUiFromResource ("GlobalUI.xml");
-            action_service.AddActionGroup (this);
+            actions_id = Actions.UIManager.AddUiFromResource ("GlobalUI.xml");
+            Actions.AddActionGroup (this);
 
             lastfm.Connection.StateChanged += HandleConnectionStateChanged;
-            action_service.SourceActions ["SourcePropertiesAction"].Activated += OnSourceProperties;
+            Actions.SourceActions ["SourcePropertiesAction"].Activated += OnSourceProperties;
             ServiceManager.PlaybackController.SourceChanged += OnPlaybackSourceChanged;
             ServiceManager.PlayerEngine.ConnectEvent (OnPlayerEvent, 
                 PlayerEvent.StartOfStream | 
@@ -202,8 +199,8 @@
 
         public override void Dispose ()
         {
-            action_service.UIManager.RemoveUi (actions_id);
-            action_service.RemoveActionGroup (this);
+            Actions.UIManager.RemoveUi (actions_id);
+            Actions.RemoveActionGroup (this);
             RestoreShuffleRepeat ();
             base.Dispose ();
         }
@@ -224,7 +221,7 @@
 
         private void OnSourceProperties (object o, EventArgs args)
         {
-            Source source = action_service.SourceActions.ActionSource;
+            Source source = Actions.SourceActions.ActionSource;
             if (source is LastfmSource) {
                 ShowLoginDialog ();
             } else if (source is StationSource) {
@@ -431,15 +428,15 @@
         private bool was_lastfm = false;
         private void OnPlaybackSourceChanged (object o, EventArgs args)
         {
-            if (action_service == null || action_service.PlaybackActions == null || ServiceManager.PlaybackController == null)
+            if (Actions == null || Actions.PlaybackActions == null || ServiceManager.PlaybackController == null)
                 return;
 
             UpdateActions ();
 
             bool is_lastfm = ServiceManager.PlaybackController.Source is StationSource;
-            action_service.PlaybackActions["PreviousAction"].Sensitive = !is_lastfm;
-            PlaybackRepeatActions repeat_actions = action_service.PlaybackActions.RepeatActions;
-            PlaybackShuffleActions shuffle_actions = action_service.PlaybackActions.ShuffleActions;
+            Actions.PlaybackActions["PreviousAction"].Sensitive = !is_lastfm;
+            PlaybackRepeatActions repeat_actions = Actions.PlaybackActions.RepeatActions;
+            PlaybackShuffleActions shuffle_actions = Actions.PlaybackActions.ShuffleActions;
 
             // Save/clear or restore shuffle/repeat values when we first switch to a Last.fm station
             if (is_lastfm && !was_lastfm) {
@@ -461,9 +458,9 @@
 
         private void RestoreShuffleRepeat ()
         {
-            if (action_service != null && action_service.PlaybackActions != null && old_repeat != null) {
-                action_service.PlaybackActions.RepeatActions.Active = old_repeat;
-                action_service.PlaybackActions.ShuffleActions.Active = old_shuffle;
+            if (Actions != null && Actions.PlaybackActions != null && old_repeat != null) {
+                Actions.PlaybackActions.RepeatActions.Active = old_repeat;
+                Actions.PlaybackActions.ShuffleActions.Active = old_shuffle;
             }
             old_repeat = old_shuffle = null;
         }

Modified: trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationSource.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationSource.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationSource.cs	Sun May 18 00:19:17 2008
@@ -96,6 +96,10 @@
             get { return Convert.ToString (dbid); }
         }
         
+        public System.Collections.Generic.IEnumerable<Banshee.Collection.Database.IFilterListModel> FilterModels {
+            get { yield break; }
+        }
+        
         // For StationSources that already exist in the db
         protected StationSource (LastfmSource lastfm, int dbId, string name, string type, string arg, int playCount) : base (generic_name, name, 150)
         {

Modified: trunk/banshee/src/Extensions/Banshee.NotificationArea/Banshee.NotificationArea/NotificationAreaService.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.NotificationArea/Banshee.NotificationArea/NotificationAreaService.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.NotificationArea/Banshee.NotificationArea/NotificationAreaService.cs	Sun May 18 00:19:17 2008
@@ -117,7 +117,7 @@
                     Catalog.GetString ("Close"), CloseWindow)
             });
             
-            actions = new BansheeActionGroup ("NotificationArea");
+            actions = new BansheeActionGroup (interface_action_service, "NotificationArea");
             actions.Add (new ToggleActionEntry [] {
                 new ToggleActionEntry ("ToggleNotificationsAction", null,
                     Catalog.GetString ("_Show Notifications"), null,
@@ -377,7 +377,7 @@
             Gdk.Pixbuf image = null;
             
             if (artwork_manager_service != null) {
-                image = artwork_manager_service.LookupScale (current_track.ArtistAlbumId, 42);
+                image = artwork_manager_service.LookupScale (current_track.ArtworkId, 42);
             }
             
             if (image == null) {

Copied: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Data/PodcastTrackInfo.cs (from r3916, /trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Data/PodcastItem.cs)
==============================================================================
--- /trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Data/PodcastItem.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Data/PodcastTrackInfo.cs	Sun May 18 00:19:17 2008
@@ -49,34 +49,32 @@
         DownloadPending = 1,        
         DownloadFailed = 2,
         DownloadPaused = 3,        
-        NewPodcastItem = 4,
-        Video = 5,
+        //NewPodcastItem = 4,
+        //Video = 5,
         Downloaded = 6,
-        None = 7,
-        Playing = 8,
-        Paused = 9
+        None = 7
     }
 
-    public class PodcastItem : DatabaseTrackInfo
+    public class PodcastTrackInfo : DatabaseTrackInfo
     {
-        private static BansheeModelProvider<PodcastItem> provider = new DatabaseTrackModelProvider<PodcastItem> (ServiceManager.DbConnection);
-        public static new BansheeModelProvider<PodcastItem> Provider {
+        private static BansheeModelProvider<PodcastTrackInfo> provider = new DatabaseTrackModelProvider<PodcastTrackInfo> (ServiceManager.DbConnection);
+        public static new BansheeModelProvider<PodcastTrackInfo> Provider {
             get { return provider; }
         }
         
+        public static PodcastTrackInfo GetByItemId (long item_id)
+        {
+            return Provider.FetchFirstMatching ("ExternalID = ?", item_id);
+        }
+        
         private bool @new;
         private int position;
         private long item_id;
 
-        /*private static BansheeModelProvider<PodcastItem> provider;
-        public new static BansheeModelProvider<PodcastItem> Provider {
-            get { return provider; }
-        }*/
-
 #region Properties
 
         public Feed Feed {
-            get { return item.Feed; }
+            get { return Item.Feed; }
         }
         
         private FeedItem item;
@@ -90,6 +88,10 @@
             set { item = value; item_id = value.DbId; }
         }
         
+        public DateTime PublishedDate {
+            get { return Item.PubDate; }
+        }
+        
         public bool New {
             get { return @new; }
             set { @new = value; }
@@ -98,21 +100,39 @@
         public int Position {
             get { return position; }
             set { position = value; }
-         }
+        }
         
         [DatabaseColumn ("ExternalID")]
-        public long ItemID {
+        public long ItemId {
             get { return item_id; }
-            set { item_id = value; }
+            private set { item_id = value; }
         }
         
         public FeedEnclosure Enclosure {
-            get { return (item == null) ? null : item.Enclosure; }
+            get { return (Item == null) ? null : Item.Enclosure; }
         }
 
         public PodcastItemActivity Activity {
             get {
-                PodcastItemActivity ret = PodcastItemActivity.None;
+                switch (Item.Enclosure.DownloadStatus) {
+                case FeedDownloadStatus.Downloaded:
+                    return PodcastItemActivity.Downloaded;
+               
+                case FeedDownloadStatus.DownloadFailed:
+                    return PodcastItemActivity.Downloaded;
+                    
+                case FeedDownloadStatus.Downloading:
+                    return PodcastItemActivity.Downloading;
+                    
+                case FeedDownloadStatus.Pending:
+                    return PodcastItemActivity.DownloadPending;
+                    
+                case FeedDownloadStatus.Paused:
+                    return PodcastItemActivity.DownloadPaused;
+
+                default:
+                    return PodcastItemActivity.None;   
+                }
             
                 /*if (Track != null) {
                     if (ServiceManager.PlayerEngine.CurrentTrack == Track) {
@@ -131,8 +151,8 @@
                             ret = PodcastItemActivity.Downloaded;
                          }
                     } 
-                } else {
-                    switch (item.Enclosure.DownloadStatus) {
+                } else {*/
+                    /*switch (Item.Enclosure.DownloadStatus) {
                     case FeedDownloadStatus.Pending: 
                         ret = PodcastItemActivity.DownloadPending;
                         break;
@@ -148,24 +168,27 @@
                     case FeedDownloadStatus.Paused: 
                         ret = PodcastItemActivity.DownloadPaused;
                         break;                        
-                    }
-                }*/
-                
-                return ret;
+                    }*/
+                //}
             }
         }
+        
+        public override string ArtworkId {
+            get { return PodcastService.ArtworkIdFor (Feed); }
+        }
 
 #endregion
 
 #region Constructors
     
-        public PodcastItem () : base ()
+        public PodcastTrackInfo () : base ()
         {
         }
         
-        public PodcastItem (FeedItem feed_item) : base ()
+        public PodcastTrackInfo (FeedItem feed_item) : base ()
         {
             Item = feed_item;
+            SyncWithFeedItem ();
         }
 
 #endregion
@@ -175,6 +198,36 @@
             Provider.Delete (this);
             //feed.Delete ();
         }
+        
+        public void SyncWithFeedItem ()
+        {
+            ArtistName = Item.Author;
+            AlbumTitle = Item.Feed.Title;
+            TrackTitle = Item.Title;
+            Year = Item.PubDate.Year;
+            CanPlay = true;
+            Genre = Genre ?? "Podcast";
+            ReleaseDate = Item.PubDate;
+            MimeType = Item.Enclosure.MimeType;
+            Duration = Item.Enclosure.Duration;
+            FileSize = item.Enclosure.FileSize;
+            Uri = new Banshee.Base.SafeUri (Item.Enclosure.LocalPath ?? item.Enclosure.Url);
+            
+            if (!String.IsNullOrEmpty (item.Enclosure.LocalPath)) {
+                TagLib.File file = Banshee.Streaming.StreamTagger.ProcessUri (Uri);
+                Banshee.Streaming.StreamTagger.TrackInfoMerge (this, file, true);
+            }
+        }
+        
+        protected override void ProviderSave ()
+        {
+            Provider.Save (this);
+        }
+        
+        protected override bool ProviderRefresh ()
+        {
+            return Provider.Refresh (this);
+        }
 
         public static void DeleteWithFeedId (long feed_id)
         {

Added: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/ColumnCellPodcast.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/ColumnCellPodcast.cs	Sun May 18 00:19:17 2008
@@ -0,0 +1,169 @@
+//
+// ColumnCellPodcast.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 Gtk;
+using Cairo;
+
+using Mono.Unix;
+
+using Hyena.Gui;
+using Hyena.Gui.Theming;
+using Hyena.Data.Gui;
+
+using Migo.Syndication;
+
+using Banshee.Gui;
+using Banshee.ServiceStack;
+using Banshee.Collection.Gui;
+
+namespace Banshee.Podcasting.Gui
+{
+    public class ColumnCellPodcast : ColumnCell
+    {
+        private static int pixbuf_spacing = 4;
+        private static int pixbuf_size = 48;
+        
+        private static Gdk.Pixbuf default_cover_pixbuf = IconThemeUtils.LoadIcon (pixbuf_size, "media-optical");
+        
+        private ArtworkManager artwork_manager;
+
+        public ColumnCellPodcast () : 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 Feed)) {
+                throw new InvalidCastException("ColumnCellPodcast can only bind to Feed objects");
+            }
+            
+            Feed feed = (Feed)BoundObject;
+            
+            bool is_default = false;          
+            Gdk.Pixbuf pixbuf = artwork_manager == null ? null : artwork_manager.LookupScale (PodcastService.ArtworkIdFor (feed), pixbuf_size);
+            
+            if (pixbuf == null) {
+                pixbuf = default_cover_pixbuf;
+                is_default = true;
+            }
+            
+            // int pixbuf_render_size = is_default ? pixbuf.Height : (int)cellHeight - 8;
+            int pixbuf_render_size = pixbuf_size;
+            int x = pixbuf_spacing;
+            int y = ((int)cellHeight - pixbuf_render_size) / 2;
+
+            ArtworkRenderer.RenderThumbnail (context.Context, pixbuf, true, x, y, 
+                pixbuf_render_size, pixbuf_render_size, !is_default, context.Theme.Context.Radius);
+                
+            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 (feed.Title ?? String.Empty);
+            layout.GetPixelSize (out fl_width, out fl_height);
+            
+            if (feed.DbId > 0) {
+                layout.FontDescription.Weight = Pango.Weight.Normal;
+                layout.FontDescription.Size = (int)(old_size * Pango.Scale.Small);
+                layout.FontDescription.Style = Pango.Style.Italic;
+                
+                if (feed.LastDownloadTime == DateTime.MinValue) {
+                    layout.SetText (Catalog.GetString ("Never updated"));
+                } else if (feed.LastDownloadTime.Date == DateTime.Now.Date) {
+                    layout.SetText (String.Format (Catalog.GetString ("Updated at {0}"), feed.LastDownloadTime.ToShortTimeString ()));
+                } else {
+                    layout.SetText (String.Format (Catalog.GetString ("Updated {0}"), Hyena.Query.RelativeTimeSpanQueryValue.RelativeToNow (feed.LastDownloadTime).ToUserQuery ()));
+                }    
+                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 (feed.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 (feed.Title ?? String.Empty);
+            layout.FontDescription.Weight = Pango.Weight.Bold;
+            layout.FontDescription.Size = old_size;
+            layout.FontDescription.Style = Pango.Style.Normal;
+            
+            layout.SetText (feed.Title ?? 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 < pixbuf_size ? pixbuf_size : height) + 6;
+        }
+    }
+}
\ No newline at end of file

Added: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/ColumnCellPublished.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/ColumnCellPublished.cs	Sun May 18 00:19:17 2008
@@ -0,0 +1,40 @@
+//
+// 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.Podcasting.Gui
+{
+    public class ColumnCellPublished : Banshee.Collection.Gui.ColumnCellDateTime
+    {
+        public ColumnCellPublished (string property, bool expand) : base (property, expand)
+        {
+            Format = Banshee.Collection.Gui.DateTimeFormat.ShortDate;
+        }
+    }
+}

Added: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastActions.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastActions.cs	Sun May 18 00:19:17 2008
@@ -0,0 +1,492 @@
+//
+// PodcastActions.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.Collections.Generic;
+
+using Mono.Unix;
+using Gtk;
+
+using Migo.Syndication;
+
+using Banshee.Base;
+using Banshee.Query;
+using Banshee.Sources;
+using Banshee.Library;
+using Banshee.Playlist;
+using Banshee.Collection;
+using Banshee.Collection.Database;
+using Banshee.ServiceStack;
+
+using Banshee.Widgets;
+using Banshee.Gui.Dialogs;
+using Banshee.Gui.Widgets;
+using Banshee.Gui;
+
+using Banshee.Podcasting;
+using Banshee.Podcasting.Data;
+
+namespace Banshee.Podcasting.Gui
+{
+    public class PodcastActions : BansheeActionGroup
+    {
+        private uint actions_id;
+        private PodcastSource source;
+        
+        public PodcastActions (PodcastSource source) : base (ServiceManager.Get<InterfaceActionService> (), "Podcast")
+        {
+            this.source = source;
+
+            AddImportant (new ActionEntry[] {
+                new ActionEntry (
+                    "PodcastUpdateAllAction", Stock.Refresh,
+                     Catalog.GetString ("Update Podcasts"), null,//"<control><shift>U",
+                     Catalog.GetString ("Refresh All Podcasts"), 
+                     OnPodcastUpdateAll
+                ),
+                new ActionEntry (
+                    "PodcastAddAction", Stock.New,
+                     Catalog.GetString ("Subscribe to Podcast"),"<control><shift>F", 
+                     Catalog.GetString ("Subscribe to a new podcast"),
+                     OnPodcastAdd
+                )         
+            });
+            
+            Add (new ActionEntry [] {
+                new ActionEntry("PodcastFeedPopupAction", null, 
+                    String.Empty, null, null, OnFeedPopup),
+                    
+                new ActionEntry (
+                    "PodcastDeleteAction", Stock.Delete,
+                     Catalog.GetString ("Unsubscribe and Delete"),
+                     null, String.Empty, 
+                     OnPodcastDelete
+                ),
+                new ActionEntry (
+                    "PodcastUpdateFeedAction", Stock.Refresh,
+                     /* Translators: this is a verb used as a button name, not a noun*/
+                     Catalog.GetString ("Check for New Episodes"),
+                     null, String.Empty, 
+                     OnPodcastUpdate
+                ),
+                new ActionEntry (
+                    "PodcastHomepageAction", Stock.JumpTo,
+                     Catalog.GetString ("Visit Podcast Homepage"),
+                     null, String.Empty, 
+                     OnPodcastHomepage
+                ),
+                new ActionEntry (
+                    "PodcastPropertiesAction", Stock.Properties,
+                     Catalog.GetString ("Properties"),
+                     null, String.Empty, 
+                     OnPodcastProperties
+                ),
+                new ActionEntry (
+                    "PodcastItemMarkNewAction", null,
+                     Catalog.GetString ("Mark as New"), 
+                     "<control><shift>N", String.Empty,
+                     OnPodcastItemMarkNew
+                ),
+                new ActionEntry (
+                    "PodcastItemMarkOldAction", null,
+                     Catalog.GetString ("Mark as Old"),
+                     "<control><shift>O", String.Empty,
+                     OnPodcastItemMarkOld
+                ),
+                new ActionEntry (
+                    "PodcastItemDownloadAction", Stock.Save,
+                     /* Translators: this is a verb used as a button name, not a noun*/
+                     Catalog.GetString ("Download Podcast(s)"),
+                     "<control><shift>D", String.Empty, 
+                     OnPodcastItemDownload
+                ),
+                new ActionEntry (
+                    "PodcastItemCancelAction", Stock.Cancel,
+                     Catalog.GetString ("Cancel Download"),
+                     "<control><shift>C", String.Empty, 
+                     OnPodcastItemCancel
+                ),
+                new ActionEntry (
+                    "PodcastItemDeleteFileAction", Stock.Remove,
+                     Catalog.GetString ("Remove Downloaded File(s)"),
+                     null, String.Empty, 
+                     OnPodcastItemDeleteFile
+                ),
+                new ActionEntry (
+                    "PodcastItemLinkAction", Stock.JumpTo,
+                     Catalog.GetString ("Visit Website"),
+                     null, String.Empty, 
+                     OnPodcastItemLink
+                ),
+                new ActionEntry (
+                    "PodcastItemPropertiesAction", Stock.Properties,
+                     Catalog.GetString ("Properties"),
+                     null, String.Empty, 
+                     OnPodcastItemProperties
+                )
+            });
+            
+            // TODO deleting podcasts is not implemented
+            this ["PodcastDeleteAction"].Sensitive = false;
+            
+            actions_id = Actions.UIManager.AddUiFromResource ("GlobalUI.xml");
+            Actions.AddActionGroup (this);
+
+            ServiceManager.SourceManager.ActiveSourceChanged += HandleActiveSourceChanged;
+            
+            source.TrackModel.Selection.Changed += delegate { UpdateActions (); };
+        }
+
+        public override void Dispose ()
+        {
+            Actions.UIManager.RemoveUi (actions_id);
+            Actions.RemoveActionGroup (this);
+            base.Dispose ();
+        }
+
+#region State Event Handlers
+
+        private void HandleActiveSourceChanged (SourceEventArgs args)
+        {
+            UpdateActions ();
+        }
+
+#endregion
+
+#region Utility Methods
+
+        private void UpdateActions ()
+        {
+            if (ServiceManager.SourceManager.ActiveSource == source) {
+                bool has_single_selection = source.TrackModel.Selection.Count == 1;
+                UpdateActions (true, has_single_selection,
+                   "PodcastItemLinkAction"
+                );
+            }
+        }
+        
+        private void SubscribeToPodcast (Uri uri, FeedAutoDownload syncPreference)
+        {
+            FeedsManager.Instance.FeedManager.CreateFeed (uri.ToString (), syncPreference);
+        }
+
+        private IEnumerable<TrackInfo> GetSelectedItems ()
+        {
+            return new List<TrackInfo> (source.TrackModel.SelectedItems);
+        }
+
+#endregion
+            
+#region Action Handlers
+
+        private void OnFeedPopup (object o, EventArgs args)
+        {
+            ShowContextMenu ("/PodcastFeedPopup");
+        }
+
+        private void RunSubscribeDialog ()
+        {        
+            Uri feedUri = null;
+            FeedAutoDownload syncPreference;
+            
+            PodcastSubscribeDialog subscribeDialog = new PodcastSubscribeDialog ();
+            
+            ResponseType response = (ResponseType) subscribeDialog.Run ();
+            syncPreference = subscribeDialog.SyncPreference;
+            
+            subscribeDialog.Destroy ();
+
+            if (response == ResponseType.Ok) {
+                string url = subscribeDialog.Url.Trim ().Trim ('/');
+                
+                if (String.IsNullOrEmpty (subscribeDialog.Url)) {
+                    return;
+                }
+                
+				if (!TryParseUrl (url, out feedUri)) {
+                    HigMessageDialog.RunHigMessageDialog (
+                        null,
+                        DialogFlags.Modal,
+                        MessageType.Warning,
+                        ButtonsType.Ok,
+                        Catalog.GetString ("Invalid URL"),
+                        Catalog.GetString ("Podcast URL is invalid.")
+                    );
+				} else {
+				    SubscribeToPodcast (feedUri, syncPreference); 
+				}
+            }        
+        }
+        
+        /*private void RunConfirmDeleteDialog (bool feed, 
+                                             int selCount, 
+                                             out bool delete, 
+                                             out bool deleteFiles)
+        {
+            
+            delete = false;
+            deleteFiles = false;        
+            string header = null;
+            int plural = (feed | (selCount > 1)) ? 2 : 1;
+
+            if (feed) {
+                header = Catalog.GetPluralString ("Delete Podcast?","Delete Podcasts?", selCount);
+            } else {
+                header = Catalog.GetPluralString ("Delete episode?", "Delete episodes?", selCount);
+            }
+                
+            HigMessageDialog 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);
+            
+            try {
+                switch ((ResponseType)md.Run ()) {
+                case ResponseType.Yes:
+                    deleteFiles = true;
+                    goto case ResponseType.No;
+                case ResponseType.No:
+                    delete = true;
+                    break;
+                }                
+            } finally {
+                md.Destroy ();
+            }       
+        }*/
+        
+		private bool TryParseUrl (string url, out Uri uri)
+		{
+			uri = null;
+			bool ret = false;
+			
+            try {
+                uri = new Uri (url);
+				
+				if (uri.Scheme == Uri.UriSchemeHttp || 
+				    uri.Scheme == Uri.UriSchemeHttps) {
+					ret = true;
+				}
+            } catch {}
+            
+            return ret;			
+		}
+
+        /*private void OnFeedSelectionChangedHandler (object sender, EventArgs e)
+        {
+            lock (sync) {
+                if (!disposed || disposing) {              
+                    if (source.FeedModel.SelectedItems.Count == 0) {
+                        source.FeedModel.Selection.Select (0);
+                    }
+                    
+                    if (source.FeedModel.Selection.Contains (0)) {
+                        itemModel.FilterOnFeed (Feed.All);
+                    } else {
+                        itemModel.FilterOnFeeds (source.FeedModel.CopySelectedItems ());
+                    }
+                    
+                    itemModel.Selection.Clear ();
+                }
+            }
+        }
+        
+        private void OnPodcastItemRowActivatedHandler (object sender, 
+                                                       RowActivatedArgs<PodcastItem> e)
+        {
+            lock (sync) {
+                if (!disposed || disposing) {
+                    if (e.RowValue.Enclosure != null) {
+                        e.RowValue.New = false;
+                        ServiceManager.PlayerEngine.OpenPlay (e.RowValue);
+                    }
+                }
+            }
+        }*/
+
+        private void OnPodcastAdd (object sender, EventArgs e)
+        {
+            RunSubscribeDialog ();
+        }
+        
+        private void OnPodcastUpdate (object sender, EventArgs e)
+        {
+            foreach (Feed feed in source.FeedModel.SelectedItems) {
+                feed.Update ();
+            }
+        }        
+        
+        private void OnPodcastUpdateAll (object sender, EventArgs e)
+        {
+            foreach (Feed feed in Feed.Provider.FetchAll ()) {
+                feed.Update ();
+            }
+        }      
+        
+        private void OnPodcastDelete (object sender, EventArgs e)
+        {
+            /*lock (sync) {
+                if (!disposed || disposing) {
+                    bool deleteFeed;
+                    bool deleteFiles;
+                    ReadOnlyCollection<Feed> feeds = source.FeedModel.CopySelectedItems ();
+                    
+                    if (feeds != null) {                    
+                        RunConfirmDeleteDialog (
+                            true, feeds.Count, out deleteFeed, out deleteFiles
+                        );
+                        
+                        
+                        if (deleteFeed) {
+                            source.FeedModel.Selection.Clear ();
+                            source.TrackModel.Selection.Clear ();
+                            
+                            foreach (Feed f in feeds) {
+                                f.Delete (deleteFiles);   
+                            }                                 
+                        }                   
+                    }                    
+                }
+            }*/
+        }        
+
+        private void OnPodcastItemDeleteFile (object sender, EventArgs e)
+        {
+            foreach (PodcastTrackInfo pi in GetSelectedItems ()) {
+                if (pi.Enclosure.LocalPath != null)
+                    pi.Enclosure.DeleteFile ();
+            }
+        }  
+
+        private void OnPodcastHomepage (object sender, EventArgs e)
+        {
+            Feed feed = source.FeedModel.FocusedItem;
+            if (feed != null && !String.IsNullOrEmpty (feed.Link)) {
+                Banshee.Web.Browser.Open (feed.Link);
+            }   
+        }   
+
+        private void OnPodcastProperties (object sender, EventArgs e)
+        {
+            Feed feed = source.FeedModel.FocusedItem;
+            if (feed != null) {
+                new PodcastFeedPropertiesDialog (feed).Run ();
+            }
+        }  
+
+        private void OnPodcastItemProperties (object sender, EventArgs e)
+        {
+                /*ReadOnlyCollection<PodcastItem> items = itemModel.CopySelectedItems ();
+                
+                if (items != null && items.Count == 1) {
+                    new PodcastPropertiesDialog (items[0]).Run ();
+                } */                
+        } 
+
+        private void OnPodcastItemMarkNew (object sender, EventArgs e)
+        {
+            MarkPodcastItemSelection (true);
+        }
+        
+        private void OnPodcastItemMarkOld (object sender, EventArgs e)
+        {
+            MarkPodcastItemSelection (false); 
+        }     
+        
+        private void MarkPodcastItemSelection (bool markNew) 
+        {
+                    /*ReadOnlyCollection<PodcastItem> items = itemModel.CopySelectedItems ();
+
+                    if (items != null) {
+                        ServiceManager.DbConnection.BeginTransaction ();
+                        
+                        try {                    
+                            foreach (PodcastItem pi in items) {
+                                if (pi.Track != null && pi.New != markNew) {
+                                    pi.New = markNew;
+                                    pi.Save ();
+                                }
+                            }
+                            
+                            ServiceManager.DbConnection.CommitTransaction ();
+                        } catch {
+                            ServiceManager.DbConnection.RollbackTransaction ();
+                        }                        
+                        
+                        itemModel.Reload ();                        
+                    }*/
+        }
+        
+        private void OnPodcastItemCancel (object sender, EventArgs e)
+        {
+            /*
+                if (!disposed || disposing) {                    
+                    ReadOnlyCollection<PodcastItem> items = itemModel.CopySelectedItems ();
+
+                    if (items != null) {
+                        foreach (PodcastItem pi in items) {
+                            pi.Enclosure.CancelAsyncDownload ();
+                        }
+                    }                
+                }*/
+        }        
+        
+        private void OnPodcastItemDownload (object sender, EventArgs e)
+        {
+            foreach (PodcastTrackInfo pi in GetSelectedItems ()) {
+                if (pi.Enclosure.DownloadStatus != FeedDownloadStatus.Downloaded)
+                    pi.Enclosure.AsyncDownload ();
+            }
+        }
+        
+        private void OnPodcastItemLink (object sender, EventArgs e)
+        {
+            PodcastTrackInfo track = source.TrackModel.FocusedItem as PodcastTrackInfo;
+            if (track != null && !String.IsNullOrEmpty (track.Item.Link)) {
+                Banshee.Web.Browser.Open (track.Item.Link);
+            }
+        }
+
+#endregion
+
+    }
+}

Modified: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastFeedPropertiesDialog.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastFeedPropertiesDialog.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastFeedPropertiesDialog.cs	Sun May 18 00:19:17 2008
@@ -71,24 +71,19 @@
             table.RowSpacing = 6;
             table.ColumnSpacing = 12;
 
-            Label description_label = new Label ();
-            description_label.Markup = String.Format("<b>{0}</b>", GLib.Markup.EscapeText(Catalog.GetString ("Description:")));
+            Label description_label = new Label (Catalog.GetString ("Description:"));
             description_label.SetAlignment (0f, 0f);
             description_label.Justify = Justification.Left;
 
-            Label last_updated_label = new Label ();
-            last_updated_label.Markup = String.Format("<b>{0}</b>", GLib.Markup.EscapeText(Catalog.GetString ("Last Updated:")));
+            Label last_updated_label = new Label (Catalog.GetString ("Last updated:"));
             last_updated_label.SetAlignment (0f, 0f);
             last_updated_label.Justify = Justification.Left;
 
-            Label feed_url_label = new Label ();
-            feed_url_label.Markup = String.Format("<b>{0}</b>", GLib.Markup.EscapeText(Catalog.GetString ("URL:")));
+            Label feed_url_label = new Label (Catalog.GetString ("URL:"));
             feed_url_label.SetAlignment (0f, 0f);
             feed_url_label.Justify = Justification.Left;
 
-            Label new_episode_option_label = new Label ();
-            new_episode_option_label.Markup = String.Format("<b>{0}</b>", GLib.Markup.EscapeText(Catalog.GetString (
-                                                  "When feed is updated:")));
+            Label new_episode_option_label = new Label (Catalog.GetString ("When feed is updated:"));
             new_episode_option_label.SetAlignment (0f, 0.5f);
             new_episode_option_label.Justify = Justification.Left;
 
@@ -103,20 +98,10 @@
             feed_url_text.Justify = Justification.Left;
             feed_url_text.Ellipsize = Pango.EllipsizeMode.End;
 
-            string description_string = (String.IsNullOrEmpty (feed.Description)) ?
+            string description_string = String.IsNullOrEmpty (feed.Description) ?
                                         Catalog.GetString ("No description available") :
                                         feed.Description;
 
-            if (!description_string.StartsWith ("\""))
-            {
-                description_string =  "\""+description_string;
-            }
-
-            if (!description_string.EndsWith ("\""))
-            {
-                description_string = description_string+"\"";
-            }
-
             Label descrition_text = new Label (description_string);
             descrition_text.Justify = Justification.Left;
             descrition_text.SetAlignment (0f, 0f);

Modified: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastPropertiesDialog.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastPropertiesDialog.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastPropertiesDialog.cs	Sun May 18 00:19:17 2008
@@ -39,9 +39,9 @@
 {
     internal class PodcastPropertiesDialog : Dialog
     {
-        private PodcastItem pi;
+        private PodcastTrackInfo pi;
 
-        public PodcastPropertiesDialog (PodcastItem pi)
+        public PodcastPropertiesDialog (PodcastTrackInfo pi)
         {
             if (pi == null)
             {

Modified: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastSubscribeDialog.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastSubscribeDialog.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastSubscribeDialog.cs	Sun May 18 00:19:17 2008
@@ -57,11 +57,7 @@
             get { return syncCombo.ActiveSyncPreference; }
         }
 
-        public PodcastSubscribeDialog () :  base (
-                Catalog.GetString("Subscribe"),
-                null,
-                DialogFlags.Modal | DialogFlags.NoSeparator
-            )
+        public PodcastSubscribeDialog () : base (Catalog.GetString("Subscribe"), null, DialogFlags.Modal | DialogFlags.NoSeparator)
         {
             accelGroup = new Gtk.AccelGroup();
             AddAccelGroup (accelGroup);
@@ -93,24 +89,24 @@
 
             Label header = new Label();
             header.Markup = "<big><b>" + GLib.Markup.EscapeText (
-                Catalog.GetString ("Subscribe to new Podcast")
+                Catalog.GetString ("Subscribe to New Podcast")
             ) + "</b></big>";
             
             header.Justify = Justification.Left;
             header.SetAlignment (0.0f, 0.0f);
 
             Label message = new Label (Catalog.GetString (
-                "Please enter the URL of the podcast you would like to subscribe to."
+                "Please enter the URL of the podcast to which you would like to subscribe."
             ));
             
             message.Wrap = true;
             message.Justify = Justification.Left;
             message.SetAlignment (0.0f, 0.0f);
 
-            Expander advancedExpander = new Expander ("Advanced");
+            VBox sync_vbox = new VBox ();
 
             VBox expander_children = new VBox();
-            expander_children.BorderWidth = 6;
+            //expander_children.BorderWidth = 6;
             expander_children.Spacing = 6;
 
             Label sync_text = new Label (
@@ -125,7 +121,7 @@
             expander_children.PackStart (sync_text);
             expander_children.PackStart (syncCombo);
 
-            advancedExpander.Add (expander_children);
+            sync_vbox.Add (expander_children);
 
             url_entry = new Entry ();
             url_entry.ActivatesDefault = true;
@@ -146,7 +142,7 @@
             );
 
             table.Attach (
-                advancedExpander, 0, 2, 1, 2,
+                sync_vbox, 0, 2, 1, 2,
                 AttachOptions.Expand | AttachOptions.Fill,
                 AttachOptions.Shrink, 0, 0
             );

Modified: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/ColumnCells/ItemActivityColumnCell.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/ColumnCells/ItemActivityColumnCell.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/ColumnCells/ItemActivityColumnCell.cs	Sun May 18 00:19:17 2008
@@ -42,21 +42,22 @@
 {
     public class PodcastItemActivityColumn : PixbufColumnCell
     {
-        Dictionary<PodcastItemActivity,Pixbuf> pixbufs = 
-            new Dictionary<PodcastItemActivity,Pixbuf> ();
+        Dictionary<PodcastItemActivity, Pixbuf> pixbufs = new Dictionary<PodcastItemActivity, Pixbuf> ();
     
-        public PodcastItemActivityColumn (string property) : base (property)
+        public PodcastItemActivityColumn (string property) : this (property, true)
+        {
+        }
+        
+        public PodcastItemActivityColumn (string property, bool expand) : base (property, expand)
         {
         }
         
         protected override void LoadPixbufs ()
         {
             if (pixbufs.Count == 0) {
-                pixbufs.Add (PodcastItemActivity.NewPodcastItem, null);
                 pixbufs.Add (PodcastItemActivity.DownloadPending, null);
                 pixbufs.Add (PodcastItemActivity.Downloading, null);
-                pixbufs.Add (PodcastItemActivity.DownloadFailed, null);
-                pixbufs.Add (PodcastItemActivity.Video, null);                                
+                pixbufs.Add (PodcastItemActivity.DownloadFailed, null);                            
             } else {
                 foreach (KeyValuePair<PodcastItemActivity,Pixbuf> kvp in pixbufs) {
                     if (kvp.Value != null) {
@@ -68,30 +69,18 @@
             
             Gtk.Image i = new Gtk.Image ();
             
-            pixbufs[PodcastItemActivity.NewPodcastItem] = 
-                IconThemeUtils.LoadIcon ("podcast-new", 16);
-            
             pixbufs[PodcastItemActivity.DownloadFailed] =
-                i.RenderIcon (Stock.DialogError, IconSize.Menu, "");
+                i.RenderIcon (Stock.DialogError, IconSize.Menu, String.Empty);
             
             pixbufs[PodcastItemActivity.Downloading] =
-                i.RenderIcon (Stock.GoForward, IconSize.Menu, "");
-            
-            pixbufs[PodcastItemActivity.Playing] =
-                i.RenderIcon (Stock.MediaPlay, IconSize.Menu, "");            
-            
-            pixbufs[PodcastItemActivity.Paused] =
-                i.RenderIcon (Stock.MediaPause, IconSize.Menu, "");             
-            
-            pixbufs[PodcastItemActivity.Video] = 
-                IconThemeUtils.LoadIcon ("video-x-generic", 16);
+                i.RenderIcon (Stock.GoForward, IconSize.Menu, String.Empty);
+                
+            pixbufs[PodcastItemActivity.Downloaded] =
+                i.RenderIcon (Stock.Harddisk, IconSize.Menu, String.Empty);
             
             i.Sensitive = false;
             pixbufs[PodcastItemActivity.DownloadPending] = 
-                i.RenderIcon (Stock.GoForward, IconSize.Menu, "");
-                
-            pixbufs[PodcastItemActivity.NewPodcastItem] = 
-                Gdk.Pixbuf.LoadFromResource ("podcast-new-16.png");
+                i.RenderIcon (Stock.GoForward, IconSize.Menu, String.Empty);
         }
         
         public override void Render (CellContext context, 

Modified: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/ColumnCells/PixbufColumnCell.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/ColumnCells/PixbufColumnCell.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/ColumnCells/PixbufColumnCell.cs	Sun May 18 00:19:17 2008
@@ -54,7 +54,11 @@
             set { spacing = value; }
         }
         
-        public PixbufColumnCell (string property) : base (property, true)
+        public PixbufColumnCell (string property) : this (property, true)
+        {
+        }
+        
+        public PixbufColumnCell (string property, bool expand) : base (property, expand)
         {
             LoadPixbufs ();
         }

Modified: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/Models/PodcastFeedModel.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/Models/PodcastFeedModel.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/Models/PodcastFeedModel.cs	Sun May 18 00:19:17 2008
@@ -31,91 +31,41 @@
 using System.Collections.ObjectModel;
 
 using Hyena.Data;
+
+using Banshee.Database;
+using Banshee.Collection.Database;
 using Banshee.Podcasting.Data;
 
 using Migo.Syndication;
 
 namespace Banshee.Podcasting.Gui
 {
-    public static class PodcastSortKeys
-    {
-        public const string Title = "Title";        
-    }
-
-    public class PodcastFeedModel : ListModel<Feed>
+    public class PodcastFeedModel : DatabaseBrowsableListModel<Feed, Feed>
     {
-        public override int Count { 
-            get { 
-                lock (SyncRoot) { 
-                    return List.Count + 1; 
-                }
-            }
-        }
-
-        public PodcastFeedModel () 
+        public PodcastFeedModel (DatabaseTrackListModel trackModel, BansheeDbConnection connection, string uuid) 
+            : base (trackModel, connection, Feed.Provider, new Feed (null, FeedAutoDownload.None), uuid)
         {
+            ReloadFragmentFormat = @"
+                FROM PodcastSyndications WHERE FeedID IN
+                    (SELECT FeedID FROM PodcastItems
+                        WHERE ItemID IN
+                            (SELECT CoreTracks.ExternalID FROM CoreTracks, CoreCache{0}
+                                WHERE CoreCache.ModelID = {1} AND CoreCache.ItemId = {2}))
+                    ORDER BY Title";
         }
-
-        public override Feed this[int index] {
-            get { 
-                lock (SyncRoot) {
-                    if (index == 0) {
-                        return Feed.All;
-                    }
-
-                    return (index <= List.Count) ? List[index-1] : null;                    
-                }
-            }
-        }    
-
-        public override ReadOnlyCollection<Feed> CopySelectedItems () 
-        {
-            List<Feed> feeds = null;
-            
-            lock (SyncRoot) {
-                ModelSelection<Feed> selected = SelectedItems;
-                
-                if (selected.Count > 0) {
-                    feeds = new List<Feed> (selected.Count);
-                    
-                    foreach (Feed feed in selected) {
-                        if (feed != Feed.All) {
-                            feeds.Add (feed);                            
-                        }
-                    }
-                }
-            }
-
-            return (feeds != null) ? 
-                new ReadOnlyCollection<Feed> (feeds) : null;
-        }    
-    
-        public override void Sort ()
+        
+        public override string FilterColumn {
+            get { return Feed.Provider.PrimaryKey; }
+        }
+        
+        public override string ItemToFilterValue (object item)
         {
-            lock (SyncRoot) {
-                if (SortColumn == null) {
-                    return;
-                }
-                
-                switch (SortColumn.SortKey) {
-                case PodcastItemSortKeys.Title:
-                    List.Sort (new TitleComparer (SortColumn.SortType));
-                    break;                    
-                }  
-            }
+            return (item != select_all_item && item is Feed) ? (item as Feed).DbId.ToString () : null;
         }
         
-        private class TitleComparer : SortTypeComparer<Feed>
+        public override void UpdateSelectAllItem (long count)
         {
-            public TitleComparer (SortType type) : base (type)
-            {
-            }
-            
-            public override int Compare (Feed lhs, Feed rhs)
-            {
-                int ret = String.Compare (lhs.Title, rhs.Title);   
-                return (SortType == SortType.Ascending) ? ret * -1 : ret;
-            }
-        } 
+            select_all_item.Title = String.Format ("All Podcasts ({0})", count);
+        }
     }
 }

Modified: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/PodcastSource.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/PodcastSource.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/PodcastSource.cs	Sun May 18 00:19:17 2008
@@ -54,35 +54,29 @@
 
 namespace Banshee.Podcasting.Gui
 {
-    public class PodcastListModel : DatabaseTrackListModel, IListModel<PodcastItem>
+    public class PodcastListModel : DatabaseTrackListModel, IListModel<PodcastTrackInfo>
     {
-        public PodcastListModel (BansheeDbConnection conn, IDatabaseTrackModelProvider provider) : base (conn, provider)
+        public PodcastListModel (BansheeDbConnection conn, IDatabaseTrackModelProvider provider, DatabaseSource source) : base (conn, provider, source)
         {
+            JoinTable = String.Format ("{0}, {1}", Feed.Provider.TableName, FeedItem.Provider.TableName);
+            JoinPrimaryKey = FeedItem.Provider.PrimaryKey;
+            JoinColumn = "ExternalID";
+            AddCondition (String.Format ("{0}.FeedID = {1}.FeedID AND CoreTracks.ExternalID = {1}.ItemID", Feed.Provider.TableName, FeedItem.Provider.TableName));
         }
         
-        public new PodcastItem this[int index] {
+        public new PodcastTrackInfo this[int index] {
             get {
                 lock (this) {
-                    return cache.GetValue (index) as PodcastItem;
+                    return cache.GetValue (index) as PodcastTrackInfo;
                 }
             }
         }
     }
     
-    public class PodcastSource : PrimarySource
+    public class PodcastSource : Banshee.Library.LibrarySource
     {
         private PodcastFeedModel feed_model;
-        
-        private PodcastFeedView feed_view;
-        public PodcastFeedView FeedView {
-            get { return feed_view; }
-        }
-        
-        private PodcastItemView item_view;
-        public PodcastItemView ItemView {
-            get { return item_view; }
-        }
-        
+
         private string baseDirectory;
         public override string BaseDirectory {
             get { return baseDirectory; }
@@ -91,60 +85,143 @@
         public override bool CanRename {
             get { return false; }
         }
-
+        
+        // FIXME all three of these should be true, eventually
         public override bool CanAddTracks {
             get { return false; }
         }
+
+        public override bool CanRemoveTracks {
+            get { return false; }
+        }
+
+        public override bool CanDeleteTracks {
+            get { return false; }
+        }
         
         public PodcastFeedModel FeedModel {
             get { return feed_model; }
         }
+        
+
+#region Constructors
 
         public PodcastSource () : this (null)
         {
         }
 
-        public PodcastSource (string baseDirectory) : base ("PodcastLibrary", Catalog.GetString ("Podcasts"), "PodcastLibrary", 100)
+        public PodcastSource (string baseDirectory) : base (Catalog.GetString ("Podcasts"), "PodcastLibrary", 100)
         {
             this.baseDirectory = baseDirectory;
 
             Properties.SetString ("Icon.Name", "podcast-icon-22");
             Properties.SetString ("ActiveSourceUIResource", "ActiveSourceUI.xml");
             Properties.SetString ("GtkActionPath", "/PodcastSourcePopup");
-            Properties.Set<bool> ("Nereid.SourceContents.HeaderVisible", false);
-
-            feed_view = new PodcastFeedView ();
-            item_view = new PodcastItemView ();
-            Properties.Set<ISourceContents> (
-                "Nereid.SourceContents", 
-                new PodcastSourceContents (feed_view, item_view)
-            );
+            Properties.Set<ISourceContents> ("Nereid.SourceContents", new PodcastSourceContents ());
+            
+            Properties.SetString ("TrackView.ColumnControllerXml", String.Format (@"
+                    <column-controller>
+                      <column>
+                          <title>Activity</title>
+                          <visible>true</visible>
+                          <renderer type=""Banshee.Podcasting.Gui.PodcastItemActivityColumn"" property=""Activity"" />
+                          <sort-key>DownloadStatus</sort-key>
+                          <width>.05</width>
+                          <max-width>30</max-width>
+                          <min-width>30</min-width>
+                      </column>
+                      <add-all-defaults />
+                      <remove-default column=""TrackColumn"" />
+                      <remove-default column=""DiscColumn"" />
+                      <remove-default column=""ComposerColumn"" />
+                      <remove-default column=""ArtistColumn"" />
+                      <column modify-default=""AlbumColumn"">
+                        <title>{0}</title>
+                      </column>
+                      <column modify-default=""FileSizeColumn"">
+                          <visible>true</visible>
+                      </column>
+                      <column>
+                          <visible>true</visible>
+                          <title>Published</title>
+                          <renderer type=""Banshee.Podcasting.Gui.ColumnCellPublished"" property=""PublishedDate"" />
+                          <sort-key>PublishedDate</sort-key>
+                      </column>
+                      <sort-column>published_date</sort-column>
+                    </column-controller>
+                ",
+                Catalog.GetString ("Podcast")
+            ));
         }
         
+#endregion
+        
         protected override bool HasArtistAlbum {
             get { return false; }
         }
         
         protected override void InitializeTrackModel ()
         {
-            DatabaseTrackModelProvider<PodcastItem> track_provider =
-                new DatabaseTrackModelProvider<PodcastItem> (ServiceManager.DbConnection);
+            DatabaseTrackModelProvider<PodcastTrackInfo> track_provider =
+                new DatabaseTrackModelProvider<PodcastTrackInfo> (ServiceManager.DbConnection);
 
-            DatabaseTrackModel = new PodcastListModel (ServiceManager.DbConnection, track_provider);
+            DatabaseTrackModel = new PodcastListModel (ServiceManager.DbConnection, track_provider, this);
 
-            TrackCache = new DatabaseTrackModelCache<PodcastItem> (ServiceManager.DbConnection,
+            TrackCache = new DatabaseTrackModelCache<PodcastTrackInfo> (ServiceManager.DbConnection,
                     UniqueId, track_model, track_provider);
                     
-            feed_model = new PodcastFeedModel ();
+            feed_model = new PodcastFeedModel (DatabaseTrackModel, ServiceManager.DbConnection, "PodcastFeeds");
             
             AfterInitialized ();
         }
-
-        public override void Reload ()
+        
+        // Probably don't want this -- do we want to allow actually removing the item?  It will be
+        // repopulated the next time we update the podcast feed...
+        /*protected override void DeleteTrack (DatabaseTrackInfo track)
+        {
+            PodcastTrackInfo episode = track as PodcastTrackInfo;
+            if (episode != null) {
+                if (episode.Uri.IsFile)
+                    base.DeleteTrack (track);
+                
+                episode.Delete ();
+                episode.Item.Delete (false);
+            }
+        }*/
+        
+        /*protected override void AddTrack (DatabaseTrackInfo track)
         {
-            feed_model.Reload ();
-            TrackModel.Reload ();
+            // TODO
+            // Need to create a Feed, FeedItem, and FeedEnclosure for this track for it to be
+            // considered a Podcast item
+            base.AddTrack (track);
+        }*/
+        
+        public override System.Collections.Generic.IEnumerable<Banshee.Collection.Database.IFilterListModel> FilterModels {
+            get {
+                yield return feed_model;
+            }
+        }
+        
+        public override bool ShowBrowser {
+            get { return true; }
         }
+        
+        /*public override IEnumerable<SmartPlaylistDefinition> DefaultSmartPlaylists {
+            get { return default_smart_playlists; }
+        }
+
+        private static SmartPlaylistDefinition [] default_smart_playlists = new SmartPlaylistDefinition [] {
+            new SmartPlaylistDefinition (
+                Catalog.GetString ("Favorites"),
+                Catalog.GetString ("Videos rated four and five stars"),
+                "rating>=4"),
+
+            new SmartPlaylistDefinition (
+                Catalog.GetString ("Unwatched"),
+                Catalog.GetString ("Videos that haven't been played yet"),
+                "plays=0"),
+        };/*
 
 /*
         public new TrackListModel TrackModel {

Modified: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/PodcastSourceContents.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/PodcastSourceContents.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/PodcastSourceContents.cs	Sun May 18 00:19:17 2008
@@ -33,6 +33,8 @@
 using Hyena.Data; 
 using Hyena.Data.Gui;
 
+using Migo.Syndication;
+
 using Banshee.Base;
 using Banshee.Configuration;
 
@@ -46,127 +48,73 @@
 
 using Banshee.Podcasting.Data;
 
+
 namespace Banshee.Podcasting.Gui
 {
-    public class PodcastSourceContents : VBox, ISourceContents
+    public class PodcastSourceContents : ListBrowserSourceContents
     {
- 	    private PodcastSource source;
- 	    
- 	    private VPaned vpaned;
-
- 	    private PodcastFeedView feedView;
- 	    private PodcastItemView itemView;
- 	    
- 	    PersistentColumnController itemViewColumnController;
+        private PodcastItemView track_view;
+        private PodcastFeedView feed_view;
         
-        public ISource Source {
-            get { return source; } 
+        public PodcastSourceContents () : base ()
+        {
         }
-        
-        public Widget Widget {
-            get { return this; } 
+
+        protected override void InitializeViews ()
+        {
+            SetupMainView (track_view = new PodcastItemView ());
+            SetupFilterView (feed_view = new PodcastFeedView ());
         }
- 	
- 	    public PodcastSourceContents (PodcastFeedView feedView, 
- 	                                  PodcastItemView itemView)
- 	    {
- 	        if (feedView == null) {
- 	            throw new ArgumentNullException ("feedView");   
- 	        } else if (itemView == null) {
- 	            throw new ArgumentNullException ("itemView");
- 	        }
- 	    
- 	        this.feedView = feedView;
- 	        this.itemView = itemView;
- 	    
- 	        InitializeWidget ();
- 	    }
- 	
-        public bool SetSource (ISource source)
+        
+        protected override void ClearFilterSelections ()
         {
-            PodcastSource ps = source as PodcastSource;
-            
-            if (ps != null) {
-                this.source = ps;
-                
-                itemViewColumnController.Source = ps;
-                itemViewColumnController.Load ();
+            feed_view.Selection.Clear ();
+        }
 
-                feedView.HeaderVisible = true;
-                itemView.HeaderVisible = true;
+        protected override bool ActiveSourceCanHasBrowser {
+            get {
+                if (!(ServiceManager.SourceManager.ActiveSource is PodcastSource)) {
+                    return false;
+                }
                 
-                feedView.SetModel (ps.FeedModel);
-                itemView.SetModel (ps.TrackModel as PodcastListModel);
-                
-                return true;
+                return ((PodcastSource)ServiceManager.SourceManager.ActiveSource).ShowBrowser;
             }
-            
-            return false;
         }
-        
-        public void ResetSource ()
-        {
-            SaveState ();           
 
-            feedView.SetModel (null);
-            itemView.SetModel (null);
-            
-            feedView.HeaderVisible = false;            
-            itemView.HeaderVisible = false;
-            
-            itemViewColumnController.Source = null;
-            source = null;
-        }
+#region Implement ISourceContents
 
-        private void InitializeWidget ()
+        public override bool SetSource (ISource source)
         {
-            itemViewColumnController = 
-                itemView.ColumnController as PersistentColumnController;
-            
-            ScrolledWindow podcastFeedScroller = new ScrolledWindow ();
-            podcastFeedScroller.ShadowType = ShadowType.None;            
-            podcastFeedScroller.HscrollbarPolicy = PolicyType.Automatic;
-            podcastFeedScroller.VscrollbarPolicy = PolicyType.Automatic;
-                        
-            ScrolledWindow podcastItemScroller = new ScrolledWindow ();
-            podcastItemScroller.ShadowType = ShadowType.None;            
-            podcastItemScroller.HscrollbarPolicy = PolicyType.Automatic;
-            podcastItemScroller.VscrollbarPolicy = PolicyType.Automatic;            
-            
-            podcastFeedScroller.Add (feedView);
-            podcastItemScroller.Add (itemView);
-
-            vpaned = new VPaned ();
+            PodcastSource track_source = source as PodcastSource;
+            if (track_source == null) {
+                return false;
+            }
             
-            vpaned.Add1 (podcastFeedScroller);
-            vpaned.Add2 (podcastItemScroller);            
+            this.source = source;
             
-            LoadState ();
+            SetModel (track_view, track_source.TrackModel);
             
-            feedView.Show ();
-            podcastFeedScroller.Show ();  
-
-            itemView.Show ();
-            podcastItemScroller.Show ();
-
-            vpaned.Show ();
+            foreach (IListModel model in track_source.FilterModels) {
+                if (model is IListModel<Feed>)
+                    SetModel (feed_view, (model as IListModel<Feed>));
+                else
+                    Hyena.Log.DebugFormat ("PodcastContents got non-feed filter model: {0}", model);
+            }
             
-            PackStart (vpaned, true, true, 0);
-        }
-        
-        private void LoadState ()
-        {
-            vpaned.Position = VPanedPositionSchema.Get ();        
+            track_view.HeaderVisible = true;
+            return true;
         }
 
-        private void SaveState ()
+        public override void ResetSource ()
         {
-            VPanedPositionSchema.Set (vpaned.Position);  
-            PersistentColumnController itemCC = 
-                itemView.ColumnController as PersistentColumnController;
-            itemCC.Save ();
+            source = null;
+            track_view.SetModel (null);
+            feed_view.SetModel (null);
+            track_view.HeaderVisible = false;
         }
 
+#endregion        
+
         public static readonly SchemaEntry<int> VPanedPositionSchema = new SchemaEntry<int> (
             "plugins.podcasting", "vpaned_position", 120, "VPaned Position", ""
         );     

Modified: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/Views/PodcastFeedView.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/Views/PodcastFeedView.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/Views/PodcastFeedView.cs	Sun May 18 00:19:17 2008
@@ -47,7 +47,26 @@
 
 namespace Banshee.Podcasting.Gui
 {
-    public class PodcastFeedView : ListView<Feed>
+    public class PodcastFeedView : TrackFilterListView<Feed>
+    {
+        public PodcastFeedView () : base ()
+        {
+            ColumnCellPodcast renderer = new ColumnCellPodcast ();
+            column_controller.Add (new Column ("Podcast", renderer, 1.0));
+            //column_controller.Add (new Column (null, "Activity", new FeedActivityColumnCell ("Activity"), 0.00, true, 26, 26));
+            
+            ColumnController = column_controller;
+            RowHeightProvider = renderer.ComputeRowHeight;
+        }
+        
+        protected override bool OnPopupMenu ()
+        {
+            ServiceManager.Get<InterfaceActionService> ().FindAction ("Podcast.PodcastFeedPopupAction").Activate ();
+            return true;
+        }
+    }
+
+    /*public class PodcastFeedView : ListView<Feed>
     {
         private ColumnController columnController;
         
@@ -115,5 +134,5 @@
             popup.Popup (null, null, null, 0, Gtk.Global.CurrentEventTime);
             return true;
         } 
-    }
+    }*/
 }

Modified: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/Views/PodcastItemView.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/Views/PodcastItemView.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Source/Views/PodcastItemView.cs	Sun May 18 00:19:17 2008
@@ -47,7 +47,13 @@
 
 namespace Banshee.Podcasting.Gui
 {
-    public class PodcastItemView : ListView<TrackInfo>
+    public class PodcastItemView : TrackListView
+    {
+        public PodcastItemView () : base ()
+        {
+        }
+    }
+    /*public class PodcastItemView : ListView<TrackInfo>
     {
         private PersistentColumnController columnController;
         
@@ -57,16 +63,15 @@
             
             SortableColumn podcastTitleSortColumn = new SortableColumn (
                 Catalog.GetString ("Podcast"), 
-                new ColumnCellText ("PodcastTitle", true), 0.35, 
+                new ColumnCellText ("AlbumTitle", true), 0.35, 
                 PodcastItemSortKeys.PodcastTitle, true
             );
             
             columnController.AddRange (
-                new Column ("test", new ColumnCellText ("TrackTitle", true), 0.45, true)
-                /*new Column (null, Catalog.GetString ("Activity"), new PodcastItemActivityColumn ("Activity"), 0.00, true, 26, 26),            
-                new SortableColumn (Catalog.GetString ("Title"), new ColumnCellText ("Title", true), 0.30, PodcastItemSortKeys.Title, true),
+                new Column (null, Catalog.GetString ("Activity"), new PodcastItemActivityColumn ("Activity"), 0.00, true, 26, 26),            
+                new SortableColumn (Catalog.GetString ("Title"), new ColumnCellText ("TrackTitle", true), 0.30, PodcastItemSortKeys.Title, true),
                 podcastTitleSortColumn,
-                new SortableColumn (Catalog.GetString ("Date"), new ColumnCellDateTime ("PubDate", false), 0.5, PodcastItemSortKeys.PubDate, true) */
+                new SortableColumn (Catalog.GetString ("Date"), new ColumnCellDateTime ("ReleaseDate", false), 0.5, PodcastItemSortKeys.PubDate, true)
             );
             
             podcastTitleSortColumn.SortType = Hyena.Data.SortType.Descending;
@@ -114,7 +119,7 @@
             
             ModelSelection<Banshee.Collection.TrackInfo> items = model.SelectedItems;
             
-            foreach (PodcastItem i in items) {
+            foreach (PodcastTrackInfo i in items) {
                 if (showCancel && showDownload && showMarkNew && showMarkOld) {
                     break;
                 } else if (!showDownload &&
@@ -135,7 +140,7 @@
                         showMarkNew = true;
                     }
                 }*/
-            }
+            /*}
             
             if (items.Count > 1) {
                 linkItem.Hide ();
@@ -173,5 +178,5 @@
             
             return true;
         }
-    }
+    }*/
 }
\ No newline at end of file

Modified: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.addin.xml
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.addin.xml	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.addin.xml	Sun May 18 00:19:17 2008
@@ -3,11 +3,11 @@
     id="Banshee.Podcasting"
     version="1.0"
     compatVersion="1.0"
-    copyright="Â 2008 Mike Urbanski.  Licensed under the MIT X11 license."
+    copyright="Â 2008 Mike Urbanski and Novell, Inc.  Licensed under the MIT X11 license."
     name="Podcasts"
     category="User Interface"
     description="Allows subscribing to and managing of Podcasts."
-    author="Michael C. Urbanski"
+    author="Michael C. Urbanski, Gabriel Burt"
     url="http://banshee-project.org/";
     defaultEnabled="true">
 

Modified: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.mdp
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.mdp	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.mdp	Sun May 18 00:19:17 2008
@@ -11,11 +11,10 @@
     <File name="Banshee.Podcasting.addin.xml" subtype="Code" buildaction="EmbedAsResource" />
     <File name="Resources/ActiveSourceUI.xml" subtype="Code" buildaction="EmbedAsResource" />
     <File name="Resources/GlobalUI.xml" subtype="Code" buildaction="EmbedAsResource" />
-    <File name="Banshee.Podcasting/PodcastCore.cs" subtype="Code" buildaction="Compile" />
-    <File name="Banshee.Podcasting/PodcastCore_Interface.cs" subtype="Code" buildaction="Compile" />
-    <File name="Banshee.Podcasting/PodcastImportManager.cs" subtype="Code" buildaction="Compile" />
     <File name="Banshee.Podcasting/PodcastService.cs" subtype="Code" buildaction="Compile" />
-    <File name="Banshee.Podcasting.Data/PodcastItem.cs" subtype="Code" buildaction="Compile" />
+    <File name="Banshee.Podcasting/PodcastService_Interface.cs" subtype="Code" buildaction="Compile" />
+    <File name="Banshee.Podcasting/PodcastImportManager.cs" subtype="Code" buildaction="Compile" />
+    <File name="Banshee.Podcasting.Data/PodcastTrackInfo.cs" subtype="Code" buildaction="Compile" />
     <File name="Banshee.Podcasting.Gui/DownloadManager/DownloadManagerInterface.cs" subtype="Code" buildaction="Compile" />
     <File name="Banshee.Podcasting.Gui/DownloadManager/DownloadUserJob.cs" subtype="Code" buildaction="Compile" />
     <File name="Banshee.Podcasting.Gui/Models/FilterableListModel.cs" subtype="Code" buildaction="Compile" />
@@ -37,6 +36,10 @@
     <File name="Resources/Images/podcast-icon-22.png" subtype="Code" buildaction="EmbedAsResource" />
     <File name="Resources/Images/podcast-icon-48.png" subtype="Code" buildaction="EmbedAsResource" />
     <File name="Resources/Images/podcast-new-16.png" subtype="Code" buildaction="EmbedAsResource" />
+    <File name="Banshee.Podcasting.Gui/ColumnCellPodcast.cs" subtype="Code" buildaction="Compile" />
+    <File name="Banshee.Podcasting/PodcastImageFetchJob.cs" subtype="Code" buildaction="Compile" />
+    <File name="Banshee.Podcasting.Gui/PodcastActions.cs" subtype="Code" buildaction="Compile" />
+    <File name="Banshee.Podcasting.Gui/ColumnCellPublished.cs" subtype="Code" buildaction="Compile" />
   </Contents>
   <References>
     <ProjectReference type="Project" localcopy="True" refto="Banshee.Core" />
@@ -46,6 +49,9 @@
     <ProjectReference type="Project" localcopy="True" refto="Migo" />
     <ProjectReference type="Project" localcopy="True" refto="Hyena" />
     <ProjectReference type="Project" localcopy="True" refto="Hyena.Gui" />
+    <ProjectReference type="Gac" localcopy="True" refto="taglib-sharp, Version=2.0.3.0, Culture=neutral, PublicKeyToken=db62eba44689b5b0" />
+    <ProjectReference type="Gac" localcopy="True" refto="System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+    <ProjectReference type="Gac" localcopy="True" refto="gdk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
   </References>
   <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="True" RelativeMakefileName="./Makefile.am">
     <BuildFilesVar Sync="True" Name="SOURCES" />

Added: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastImageFetchJob.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastImageFetchJob.cs	Sun May 18 00:19:17 2008
@@ -0,0 +1,91 @@
+//
+// PodcastImageFetchJob.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.IO;
+using System.Net;
+using System.Xml;
+using System.Text;
+using System.Collections.Generic;
+
+using Hyena;
+
+using Migo.Syndication;
+
+using Banshee.Base;
+using Banshee.Metadata;
+using Banshee.ServiceStack;
+using Banshee.Kernel;
+using Banshee.Collection;
+using Banshee.Streaming;
+
+using Banshee.Podcasting.Gui;
+
+namespace Banshee.Podcasting
+{
+    public class PodcastImageFetchJob : MetadataServiceJob
+    {
+        private static MetadataSettings settings = new MetadataSettings ();
+        private Feed feed;
+        
+        public PodcastImageFetchJob (Feed feed) : base ()
+        {
+            this.Settings = settings;
+            this.feed = feed;
+        }
+        
+        public override void Run()
+        {
+            Fetch ();
+        }
+        
+        public void Fetch ()
+        {
+            if (feed.ImageUrl == null) {
+                return;
+            }
+            
+            string cover_art_id = PodcastService.ArtworkIdFor (feed);
+            
+            if (cover_art_id == null) {
+                return;
+            } else if (CoverArtSpec.CoverExists (cover_art_id)) {
+                return;
+            } else if (!Settings.NetworkConnected) {
+                return;
+            }
+            
+            if (SaveHttpStreamCover (new Uri (feed.ImageUrl), cover_art_id, null)) {
+                if (ServiceManager.SourceManager.ActiveSource is PodcastSource) {
+                    (ServiceManager.SourceManager.ActiveSource as PodcastSource).FeedModel.Reload ();
+                }
+                return;
+            }
+        }
+    }
+}
\ No newline at end of file

Modified: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastService.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastService.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastService.cs	Sun May 18 00:19:17 2008
@@ -1,40 +1,368 @@
+/***************************************************************************
+ *  PodcastService.cs
+ *
+ *  Copyright (C) 2008 Michael C. Urbanski
+ *  Written by Mike Urbanski <michael c urbanski gmail com>
+ ****************************************************************************/
+
+/*  THIS FILE IS LICENSED UNDER THE MIT LICENSE AS OUTLINED IMMEDIATELY BELOW: 
+ *
+ *  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.Collections.Generic;
+using System.Collections.ObjectModel;
+
+using Mono.Unix;
 
 using Hyena;
 
 using Banshee.Base;
 using Banshee.ServiceStack;
 
+using Migo.TaskCore;
+using Migo.Syndication;
+using Migo.DownloadCore;
+
+using Banshee.MediaEngine;
+using Banshee.Podcasting.Gui;
+using Banshee.Podcasting.Data;
+using Banshee.Collection.Database;
+
 namespace Banshee.Podcasting
 {
-    public class PodcastService : IExtensionService, IDisposable
-    {
-    	private PodcastCore pc;
-		
-		public string ServiceName
-		{
-			get { return "PodcastService"; } 
-		}
-    	
-    	public void Initialize ()
-    	{
-           try {
-                pc = new PodcastCore ();
-            } catch (Exception e) {
-                Console.WriteLine (e.Message);
-                Console.WriteLine (e.StackTrace);                        
-                throw new ApplicationException ("Unable to initialize PodcastCore.");
-            }   
-    	}
-    	
-    	public void Dispose ()
-    	{
-    	    if (pc != null) {
-    	    	pc.Dispose ();
-    	    	pc = null;
-    	    }
-    	    
-    	    Log.Debug ("PodcastCore Disposed");
-    	}
+    public partial class PodcastService : IExtensionService, IDisposable
+    {  
+        private readonly string tmp_download_path;
+        private string tmp_enclosure_path; 
+            
+        private bool disposed;
+        
+        private DownloadManager download_manager;
+        private DownloadManagerInterface download_manager_iface;
+        
+        private FeedsManager feeds_manager;
+        
+        private PodcastSource source;
+        //private PodcastImportManager import_manager;
+        
+        private readonly object sync = new object ();
+
+        public PodcastService ()
+        {
+            // TODO translate Podcasts folder?
+            tmp_enclosure_path = Path.Combine (Paths.LibraryLocation, "Podcasts");
+            tmp_download_path = Path.Combine (Paths.ApplicationData, "downloads");
+            Migo.Net.AsyncWebClient.DefaultUserAgent = Banshee.Web.Browser.UserAgent;
+            
+            download_manager = new DownloadManager (2, tmp_download_path);
+            download_manager_iface = new DownloadManagerInterface (download_manager);
+            download_manager_iface.Initialize ();    
+    
+            feeds_manager = new FeedsManager (ServiceManager.DbConnection, download_manager, Path.Combine (Banshee.Base.Paths.CachedLibraryLocation, "Podcasts"));
+            
+            feeds_manager.FeedManager.ItemAdded += OnItemAdded;
+            feeds_manager.FeedManager.ItemChanged += OnItemChanged;
+            feeds_manager.FeedManager.ItemRemoved += OnItemRemoved;
+            
+            feeds_manager.FeedManager.FeedsChanged += OnFeedsChanged;
+              
+            ServiceManager.PlayerEngine.ConnectEvent (OnPlayerEvent, PlayerEvent.StateChange);
+
+            InitializeInterface ();
+            
+            foreach (Feed feed in Feed.Provider.FetchAll ()) {
+                RefreshArtworkFor (feed);
+            }
+
+            //import_manager = new PodcastImportManager (source);
+        }
+        
+        public void Initialize ()
+        {
+        }
+        
+        bool disposing;
+        public void Dispose ()
+        {                        
+            lock (sync) {
+                if (disposing | disposed) {
+                    return;
+                } else {
+                    disposing = true;               
+                }
+            }
+            
+            ServiceManager.PlayerEngine.DisconnectEvent (OnPlayerEvent);
+
+            if (download_manager_iface != null) {
+                download_manager_iface.Dispose ();                
+                download_manager_iface = null;
+            }                
+           
+            if (feeds_manager != null) {   
+                feeds_manager.Dispose ();
+                feeds_manager = null;
+            }
+
+            if (download_manager != null) {            
+                download_manager.Dispose ();
+                download_manager = null;
+            }
+            
+            DisposeInterface ();            
+            
+            lock (sync) {
+                disposing = false;            
+                disposed = true;
+            }
+        }
+        
+        private void RefreshArtworkFor (Feed feed)
+        {
+            Banshee.Kernel.Scheduler.Schedule (new PodcastImageFetchJob (feed), Banshee.Kernel.JobPriority.Highest);
+        }
+        
+        private void OnItemAdded (FeedItem item)
+        {
+            PodcastTrackInfo track = new PodcastTrackInfo (item);
+            track.PrimarySource = source;
+            track.Save (true);
+            
+            RefreshArtworkFor (item.Feed);
+        }
+        
+        private void OnItemRemoved (FeedItem item)
+        {
+            PodcastTrackInfo track = PodcastTrackInfo.GetByItemId (item.DbId);
+            if (track != null) {
+                track.Delete ();
+            }
+        }
+        
+        private void OnItemChanged (FeedItem item)
+        {
+            PodcastTrackInfo track = PodcastTrackInfo.GetByItemId (item.DbId);
+            if (track != null) {
+                track.SyncWithFeedItem ();
+                track.Save (true);
+            }
+        }
+        
+        private void OnFeedsChanged (object o, EventArgs args)
+        {
+            source.Reload ();
+        }
+
+        /*private void OnFeedAddedHandler (object sender, FeedEventArgs args)
+        {
+            lock (sync) {
+                source.FeedModel.Add (args.Feed);
+            }
+        }
+        
+        private void OnFeedRemovedHandler (object sender, FeedEventArgs args)
+        {
+            lock (sync) {
+                source.FeedModel.Remove (args.Feed);
+                args.Feed.Delete ();
+            }
+        }        
+
+        private void OnFeedRenamedHandler (object sender, FeedEventArgs args)
+        {
+            lock (sync) {
+                source.FeedModel.Sort ();
+            }
+        }
+
+        private void OnFeedUpdatingHandler (object sender, FeedEventArgs args)
+        {   
+            lock (sync) {
+                source.FeedModel.Reload ();
+            }
+        }
+
+        private void OnFeedDownloadCountChangedHandler (object sender, FeedDownloadCountChangedEventArgs args)
+        {
+            lock (sync) {
+                source.FeedModel.Reload ();                
+            }
+        }*/
+
+        /*private void OnFeedItemAddedHandler (object sender, FeedItemEventArgs args) 
+        {
+            lock (sync) {
+                if (args.Item != null) {
+                    AddFeedItem (args.Item);
+                } else if (args.Items != null) {
+                    foreach (FeedItem item in args.Items) {
+                        AddFeedItem (item);
+                    }
+                }
+            }
+        }*/
+        
+        public void AddFeedItem (FeedItem item)
+        {
+            if (item.Enclosure != null) {
+                PodcastTrackInfo pi = new PodcastTrackInfo (item);
+                pi.PrimarySource = source;
+                pi.Save (true);
+            } else {
+                item.Delete (false);                      
+            }
+        }
+
+        /*private void OnFeedItemRemovedHandler (object sender, FeedItemEventArgs e) 
+        {
+            lock (sync) {
+                if (e.Item != null) {
+                    PodcastItem.DeleteWithFeedId (e.Item.DbId);
+                } else if (e.Items != null) {
+                    foreach (FeedItem fi in e.Items) {
+                        PodcastItem.DeleteWithFeedId (fi.DbId);
+                    }
+                }
+                
+                source.Reload ();
+            }
+        } 
+
+        private void OnFeedItemCountChanged (object sender, 
+                                             FeedItemCountChangedEventArgs e)
+        {
+            //UpdateCount ();
+        }*/
+
+        /*private void OnFeedDownloadCompletedHandler (object sender, 
+                                                     FeedDownloadCompletedEventArgs e) 
+        {
+            lock (sync) {
+                Feed f = feedDict[e.Feed.DbId]; 
+                
+                if (e.Error == FeedDownloadError.None) {
+                    if (String.IsNullOrEmpty(e.Feed.LocalEnclosurePath)) {
+                        e.Feed.LocalEnclosurePath = Path.Combine (
+                            tmp_enclosure_path, SanitizeName (e.Feed.Name)
+                        );
+                    }                    
+                
+                    if (f.AutoDownload != FeedAutoDownload.None) {
+                        ReadOnlyCollection<FeedItem> items = e.Feed.Items;
+                        
+                        if (items != null) {
+                            if (f.AutoDownload == FeedAutoDownload.One && 
+                                items.Count > 0) {
+                                items[0].Enclosure.AsyncDownload ();
+                            } else {
+                                foreach (FeedItem fi in items) {
+                                    fi.Enclosure.AsyncDownload ();
+                                }
+                            }
+                        }
+                    }
+                }
+                
+                source.Reload ();                
+            }
+        }*/
+        
+        /*private void OnTaskAssociated (object sender, EventArgs e)
+        {
+            lock (sync) {
+                source.Reload ();
+            }
+        }        
+        
+        private void OnTaskStatusChanged (object sender, 
+        								  TaskStatusChangedEventArgs e)
+        {
+            lock (sync) {
+                source.Reload ();
+            }
+        }        
+        
+        private void TaskStartedHandler (object sender, 
+                                         TaskEventArgs<HttpFileDownloadTask> e)
+        {
+            lock (sync) {
+                source.Reload ();
+            }
+        }        
+        
+        private void OnTaskStoppedHandler (object sender, 
+                                           TaskEventArgs<HttpFileDownloadTask> e)
+        {
+            // TODO merge
+            lock (sync) {
+                if (e.Task != null && e.Task.Status == TaskStatus.Succeeded) {
+                    FeedEnclosure enc = e.Task.UserState as FeedEnclosure;
+                
+                    if (enc != null) {
+                        FeedItem item = enc.Item;
+                        DatabaseTrackInfo track = null;
+                        
+                        
+                        
+                        if (itemDict.ContainsKey (item.DbId)) {
+                            PodcastItem pi = itemDict[item.DbId];
+                            track = import_manager.ImportPodcast (enc.LocalPath);                            
+
+                            if (track != null) {
+                                pi.Track = track;
+                                pi.New = true;
+                                pi.Save ();
+                            }
+                            
+                            item.IsRead = true;                            
+                        }
+                    }                    
+                }
+                
+                source.Reload ();
+            }
+        }*/    
+
+        private void OnPlayerEvent (PlayerEventArgs args)
+        {
+            lock (sync) {
+                //source.Reload ();
+            }
+        }          
+
+        public static string ArtworkIdFor (Feed feed)
+        {
+            return String.Format ("podcast-{0}", Banshee.Base.CoverArtSpec.EscapePart (feed.Title));
+        }
+
+        // Via Monopod
+        /*private static string SanitizeName (string s)
+        {
+            // remove /, : and \ from names
+            return s.Replace ('/', '_').Replace ('\\', '_').Replace (':', '_').Replace (' ', '_');
+        }*/   
+        
+        public string ServiceName {
+            get { return "PodcastService"; } 
+        }
     }
-}
\ No newline at end of file
+}

Copied: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastService_Interface.cs (from r3911, /trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastCore_Interface.cs)
==============================================================================
--- /trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastCore_Interface.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastService_Interface.cs	Sun May 18 00:19:17 2008
@@ -53,19 +53,17 @@
 
 namespace Banshee.Podcasting
 {
-    public partial class PodcastCore
-    {                  
+    public partial class PodcastService
+    {                 
+        PodcastActions actions = null;
+         
         private void InitializeInterface ()
         {
-            BuildActions ();
-
             source = new PodcastSource (tmp_enclosure_path);
-            
-            //source.FeedView.SelectionProxy.Changed += OnFeedSelectionChangedHandler;
-            //source.ItemView.RowActivated += OnPodcastItemRowActivatedHandler;            
-            
             ServiceManager.SourceManager.AddSource (source);
-        }         
+
+            actions = new PodcastActions (source);
+        }
         
         private void DisposeInterface ()
         {
@@ -76,456 +74,10 @@
                 ServiceManager.SourceManager.RemoveSource (source);
                 source = null;
             }
-        }     
-        
-        private void BuildActions ()
-        {
-            InterfaceActionService interfaceActions = 
-                ServiceManager.Get<InterfaceActionService> ();
-            
-            BansheeActionGroup bag = new BansheeActionGroup ("Podcast");
-            
-            bag.AddImportant (new ActionEntry[] {
-                new ActionEntry (
-                    "PodcastUpdateAllAction", Stock.Refresh,
-                     Catalog.GetString ("Update Podcasts"), null,//"<control><shift>U",
-                     Catalog.GetString ("Update All Podcasts"), 
-                     OnPodcastUpdateAll
-                ),
-                new ActionEntry (
-                    "PodcastAddAction", Stock.New,
-                     Catalog.GetString ("Subscribe to Podcast"),"<control><shift>F", 
-                     Catalog.GetString ("Subscribe to a new podcast feed"),
-                     OnPodcastAdd
-                )         
-            });
-            
-            bag.Add (new ActionEntry [] {
-                new ActionEntry (
-                    "PodcastDeleteAction", Stock.Delete,
-                     Catalog.GetString ("Delete"),
-                     null, String.Empty, 
-                     OnPodcastDelete
-                ),
-                new ActionEntry (
-                    "PodcastUpdateFeedAction", Stock.Refresh,
-                     /* Translators: this is a verb used as a button name, not a noun*/
-                     Catalog.GetString ("Update"),
-                     null, String.Empty, 
-                     OnPodcastUpdate
-                ),
-                new ActionEntry (
-                    "PodcastHomepageAction", Stock.JumpTo,
-                     Catalog.GetString ("Homepage"),
-                     null, String.Empty, 
-                     OnPodcastHomepage
-                ),
-                new ActionEntry (
-                    "PodcastPropertiesAction", Stock.Properties,
-                     Catalog.GetString ("Properties"),
-                     null, String.Empty, 
-                     OnPodcastProperties
-                ),
-                new ActionEntry (
-                    "PodcastItemMarkNewAction", null,
-                     Catalog.GetString ("Mark as New"), 
-                     "<control><shift>N", String.Empty,
-                     OnPodcastItemMarkNew
-                ),
-                new ActionEntry (
-                    "PodcastItemMarkOldAction", null,
-                     Catalog.GetString ("Mark as Old"),
-                     "<control><shift>O", String.Empty,
-                     OnPodcastItemMarkOld
-                ),
-                new ActionEntry (
-                    "PodcastItemDownloadAction", Stock.GoDown,
-                     /* Translators: this is a verb used as a button name, not a noun*/
-                     Catalog.GetString ("Download"),
-                     "<control><shift>D", String.Empty, 
-                     OnPodcastItemDownload
-                ),
-                new ActionEntry (
-                    "PodcastItemCancelAction", Stock.Stop,
-                     Catalog.GetString ("Cancel"),
-                     "<control><shift>C", String.Empty, 
-                     OnPodcastItemCancel
-                ),
-                new ActionEntry (
-                    "PodcastItemDeleteAction", Stock.Remove,
-                     Catalog.GetString ("Remove from Library"),
-                     null, String.Empty, 
-                     OnPodcastItemRemoveAction
-                ),
-                new ActionEntry (
-                    "PodcastItemLinkAction", Stock.JumpTo,
-                     Catalog.GetString ("Link"),
-                     null, String.Empty, 
-                     OnPodcastItemLink
-                ),
-                new ActionEntry (
-                    "PodcastItemPropertiesAction", Stock.Properties,
-                     Catalog.GetString ("Properties"),
-                     null, String.Empty, 
-                     OnPodcastItemProperties
-                )
-            });            
-
-            interfaceActions.UIManager.AddUiFromResource ("GlobalUI.xml");
-            interfaceActions.AddActionGroup (bag);      
-        }
-        
-        private void RunSubscribeDialog ()
-        {        
-            Uri feedUri = null;
-            FeedAutoDownload syncPreference;
-            
-            PodcastSubscribeDialog subscribeDialog = new PodcastSubscribeDialog ();
-            
-            ResponseType response = (ResponseType) subscribeDialog.Run ();
-            syncPreference = subscribeDialog.SyncPreference;
-            
-            subscribeDialog.Destroy ();
-
-            if (response == ResponseType.Ok) {
-                string url = subscribeDialog.Url.Trim ().Trim ('/');
-                
-                if (String.IsNullOrEmpty (subscribeDialog.Url)) {
-                    return;
-                }
-                
-				if (!TryParseUrl (url, out feedUri)) {
-                    HigMessageDialog.RunHigMessageDialog (
-                        null,
-                        DialogFlags.Modal,
-                        MessageType.Warning,
-                        ButtonsType.Ok,
-                        Catalog.GetString ("Invalid URL"),
-                        Catalog.GetString ("Podcast feed URL is invalid.")
-                    );
-				} else {
-				    SubscribeToPodcast (feedUri, syncPreference); 
-				}
-            }        
-        }
-        
-        private void RunConfirmDeleteDialog (bool feed, 
-                                             int selCount, 
-                                             out bool delete, 
-                                             out bool deleteFiles)
-        {
-            
-            delete = false;
-            deleteFiles = false;        
-            string header = null;
-            int plural = (feed | (selCount > 1)) ? 2 : 1;
-
-            if (feed) {
-                header = Catalog.GetPluralString ("Delete Podcast?","Delete Podcasts?", selCount);
-            } else {
-                header = Catalog.GetPluralString ("Delete episode?", "Delete episodes?", selCount);
-            }
-                
-            HigMessageDialog 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);
-            
-            try {
-                switch ((ResponseType)md.Run ()) {
-                case ResponseType.Yes:
-                    deleteFiles = true;
-                    goto case ResponseType.No;
-                case ResponseType.No:
-                    delete = true;
-                    break;
-                }                
-            } finally {
-                md.Destroy ();
-            }       
-        }
-        
-		private bool TryParseUrl (string url, out Uri uri)
-		{
-			uri = null;
-			bool ret = false;
-			
-            try {
-                uri = new Uri (url);
-				
-				if (uri.Scheme == Uri.UriSchemeHttp || 
-				    uri.Scheme == Uri.UriSchemeHttps) {
-					ret = true;
-				}
-            } catch {}
             
-            return ret;			
-		}
-
-        /*private void OnFeedSelectionChangedHandler (object sender, EventArgs e)
-        {
-            lock (sync) {
-                if (!disposed || disposing) {              
-                    if (source.FeedModel.SelectedItems.Count == 0) {
-                        source.FeedModel.Selection.Select (0);
-                    }
-                    
-                    if (source.FeedModel.Selection.Contains (0)) {
-                        itemModel.FilterOnFeed (Feed.All);
-                    } else {
-                        itemModel.FilterOnFeeds (source.FeedModel.CopySelectedItems ());
-                    }
-                    
-                    itemModel.Selection.Clear ();
-                }
-            }
-        }
-        
-        private void OnPodcastItemRowActivatedHandler (object sender, 
-                                                       RowActivatedArgs<PodcastItem> e)
-        {
-            lock (sync) {
-                if (!disposed || disposing) {
-                    if (e.RowValue.Enclosure != null) {
-                        e.RowValue.New = false;
-                        ServiceManager.PlayerEngine.OpenPlay (e.RowValue);
-                    }
-                }
-            }
-        }*/
-
-        private void OnPodcastAdd (object sender, EventArgs e)
-        {
-            lock (sync) {
-                if (!disposed || disposing) {         
-                    RunSubscribeDialog ();
-                }
+            if (actions != null) {
+                //podcast_action.
             }
         }
-        
-        private void OnPodcastUpdate (object sender, EventArgs e)
-        {
-            lock (sync) {
-                if (!disposed || disposing) {          
-                    ReadOnlyCollection<Feed> feeds = source.FeedModel.CopySelectedItems ();
-                    
-                    if (feeds != null) {
-                        foreach (Feed f in feeds) {
-                            if (f != Feed.All) {
-                                f.AsyncDownload ();                                
-                            }
-                        }            	
-                    }
-                }
-            }
-        }        
-        
-        private void OnPodcastUpdateAll (object sender, EventArgs e)
-        {
-            lock (sync) {
-                if (!disposed || disposing) {
-                    foreach (Feed podcast in source.FeedModel.Copy ()) {
-                        podcast.AsyncDownload ();
-                    }
-                }
-            }
-        }      
-        
-        private void OnPodcastDelete (object sender, EventArgs e)
-        {
-            lock (sync) {
-                if (!disposed || disposing) {
-                    bool deleteFeed;
-                    bool deleteFiles;                    
-                    ReadOnlyCollection<Feed> feeds = source.FeedModel.CopySelectedItems ();
-                    
-                    if (feeds != null) {                    
-                        RunConfirmDeleteDialog (
-                            true, feeds.Count, out deleteFeed, out deleteFiles
-                        );
-                        
-                        
-                        if (deleteFeed) {
-                            source.FeedModel.Selection.Clear ();
-                            source.TrackModel.Selection.Clear ();
-                            
-                            foreach (Feed f in feeds) {
-                                f.Delete (deleteFiles);   
-                            }                                 
-                        }                   
-                    }                    
-                }
-            }
-        }        
-
-        private void OnPodcastItemRemoveAction (object sender, EventArgs e)
-        {
-            /*lock (sync) {
-                if (!disposed || disposing) {
-                    bool deleteItems;
-                    bool deleteFiles;                    
-                    ReadOnlyCollection<PodcastItem> items = itemModel.CopySelectedItems ();        
-                    
-                    if (items != null) {                             
-                        RunConfirmDeleteDialog (
-                            false, items.Count, 
-                            out deleteItems, out deleteFiles
-                        );
-                        
-                        if (deleteItems) {
-                            itemModel.Selection.Clear ();
-                            itemModel.Remove (items);
-                            
-                            foreach (PodcastItem i in items) {
-                                i.Item.Delete (deleteFiles);
-                            }
-                        }    
-                    }   
-                }
-            }*/
-        }  
-
-        private void OnPodcastHomepage (object sender, EventArgs e)
-        {
-            lock (sync) {
-                if (!disposed || disposing) {
-                    ReadOnlyCollection<Feed> feeds = source.FeedModel.CopySelectedItems ();
-                    
-                    if (feeds != null && feeds.Count == 1) {
-           	            string link = feeds[0].Link;
-           	            
-           	            if (!String.IsNullOrEmpty (link)) {
-                            Banshee.Web.Browser.Open (link);           	                
-           	            }
-                    }                 
-                }
-            }       
-        }   
-
-        private void OnPodcastProperties (object sender, EventArgs e)
-        {
-            lock (sync) {
-                if (!disposed || disposing) {
-                    ReadOnlyCollection<Feed> feeds = source.FeedModel.CopySelectedItems ();
-                    
-                    if (feeds != null && feeds.Count == 1) {
-                        new PodcastFeedPropertiesDialog (feeds[0]).Run ();
-                    }                 
-                }
-            }  
-        }  
-
-        private void OnPodcastItemProperties (object sender, EventArgs e)
-        {
-            lock (sync) {
-                if (!disposed || disposing) {
-                    /*ReadOnlyCollection<PodcastItem> items = itemModel.CopySelectedItems ();
-                    
-                    if (items != null && items.Count == 1) {
-                        new PodcastPropertiesDialog (items[0]).Run ();
-                    } */                
-                }
-            }  
-        } 
-
-        private void OnPodcastItemMarkNew (object sender, EventArgs e)
-        {
-            MarkPodcastItemSelection (true);
-        }
-        
-        private void OnPodcastItemMarkOld (object sender, EventArgs e)
-        {
-            MarkPodcastItemSelection (false); 
-        }     
-        
-        private void MarkPodcastItemSelection (bool markNew) 
-        {
-            lock (sync) {
-                if (!disposed || disposing) {                    
-                    /*ReadOnlyCollection<PodcastItem> items = itemModel.CopySelectedItems ();
-
-                    if (items != null) {
-                        ServiceManager.DbConnection.BeginTransaction ();
-                        
-                        try {                    
-                            foreach (PodcastItem pi in items) {
-                                if (pi.Track != null && pi.New != markNew) {
-                                    pi.New = markNew;
-                                    pi.Save ();
-                                }
-                            }
-                            
-                            ServiceManager.DbConnection.CommitTransaction ();
-                        } catch {
-                            ServiceManager.DbConnection.RollbackTransaction ();
-                        }                        
-                        
-                        itemModel.Reload ();                        
-                    }*/
-                }
-            }
-        }
-        
-        private void OnPodcastItemCancel (object sender, EventArgs e)
-        {
-            lock (sync) {/*
-                if (!disposed || disposing) {                    
-                    ReadOnlyCollection<PodcastItem> items = itemModel.CopySelectedItems ();
-
-                    if (items != null) {
-                        foreach (PodcastItem pi in items) {
-                            pi.Enclosure.CancelAsyncDownload ();
-                        }
-                    }                
-                }*/
-            }
-        }        
-        
-        private void OnPodcastItemDownload (object sender, EventArgs e)
-        {
-            lock (sync) {/*
-                if (!disposed || disposing) {
-                    ReadOnlyCollection<PodcastItem> items = itemModel.CopySelectedItems ();
-
-                    if (items != null) {
-                        foreach (PodcastItem pi in items) {
-                            pi.Enclosure.AsyncDownload ();
-                        }            	
-                    }                 
-                }*/
-            }       
-        }
-        
-        private void OnPodcastItemLink (object sender, EventArgs e)
-        {
-            lock (sync) {/*
-                if (!disposed || disposing) {
-                    ReadOnlyCollection<PodcastItem> items = itemModel.CopySelectedItems ();
-
-                    if (items != null && items.Count == 1) {
-           	            string link = items[0].Item.Link;
-           	            
-           	            if (!String.IsNullOrEmpty (link)) {
-                            Banshee.Web.Browser.Open (link);           	                
-           	            }
-                    }                 
-                }*/
-            }
-        }   
     }
 }

Modified: trunk/banshee/src/Extensions/Banshee.Podcasting/Makefile.am
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Podcasting/Makefile.am	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Makefile.am	Sun May 18 00:19:17 2008
@@ -4,11 +4,14 @@
 INSTALL_DIR = $(EXTENSIONS_INSTALL_DIR)
 
 SOURCES =  \
-	Banshee.Podcasting.Data/PodcastItem.cs \
+	Banshee.Podcasting.Data/PodcastTrackInfo.cs \
+	Banshee.Podcasting.Gui/ColumnCellPodcast.cs \
+	Banshee.Podcasting.Gui/ColumnCellPublished.cs \
 	Banshee.Podcasting.Gui/DownloadManager/DownloadManagerInterface.cs \
 	Banshee.Podcasting.Gui/DownloadManager/DownloadUserJob.cs \
 	Banshee.Podcasting.Gui/Models/FilterableListModel.cs \
 	Banshee.Podcasting.Gui/Models/ListModel.cs \
+	Banshee.Podcasting.Gui/PodcastActions.cs \
 	Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastFeedPropertiesDialog.cs \
 	Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastPropertiesDialog.cs \
 	Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastSubscribeDialog.cs \
@@ -22,10 +25,10 @@
 	Banshee.Podcasting.Gui/PodcastManager/Source/PodcastSourceContents.cs \
 	Banshee.Podcasting.Gui/PodcastManager/Source/Views/PodcastFeedView.cs \
 	Banshee.Podcasting.Gui/PodcastManager/Source/Views/PodcastItemView.cs \
-	Banshee.Podcasting/PodcastCore.cs \
-	Banshee.Podcasting/PodcastCore_Interface.cs \
+	Banshee.Podcasting/PodcastImageFetchJob.cs \
 	Banshee.Podcasting/PodcastImportManager.cs \
-	Banshee.Podcasting/PodcastService.cs
+	Banshee.Podcasting/PodcastService.cs \
+	Banshee.Podcasting/PodcastService_Interface.cs
 
 RESOURCES =  \
 	Banshee.Podcasting.addin.xml \

Modified: trunk/banshee/src/Extensions/Banshee.Podcasting/Resources/ActiveSourceUI.xml
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Podcasting/Resources/ActiveSourceUI.xml	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Resources/ActiveSourceUI.xml	Sun May 18 00:19:17 2008
@@ -2,7 +2,22 @@
   <toolbar name="HeaderToolbar">
     <placeholder name="SourceActions">
         <toolitem name="PodcastAdd" action="PodcastAddAction" />
-        <toolitem name="PodcastUpdateAll" action="PodcastUpdateAllAction" />        
+        <toolitem name="PodcastUpdateAll" action="PodcastUpdateAllAction" />
     </placeholder>
   </toolbar>
+
+  <popup name="TrackContextMenu" action="TrackContextMenuAction">
+    <placeholder name="BelowAddToPlaylist">
+        <separator />
+        <menuitem name="PodcastItemLink" action="PodcastItemLinkAction" />
+        <menuitem name="PodcastItemDownload" action="PodcastItemDownloadAction" />
+        <menuitem name="PodcastItemDeleteFile" action="PodcastItemDeleteFileAction" />
+        <!--
+        <menuitem name="PodcastItemCancel" action="PodcastItemCancelAction" />
+        <separator />
+        <menuitem name="PodcastItemMarkNew" action="PodcastItemMarkNewAction" />
+        <menuitem name="PodcastItemMarkOld" action="PodcastItemMarkOldAction" />
+        -->
+    </placeholder>
+  </popup>
 </ui>

Modified: trunk/banshee/src/Extensions/Banshee.Podcasting/Resources/GlobalUI.xml
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Podcasting/Resources/GlobalUI.xml	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Resources/GlobalUI.xml	Sun May 18 00:19:17 2008
@@ -1,30 +1,24 @@
 <ui>
     <popup name="PodcastSourcePopup" action="PodcastSourcePopupAction">
-        <menuitem name="PodcastUpdateAll" action="PodcastUpdateAllAction"/>                        
         <menuitem name="PodcastAdd" action="PodcastAddAction" />
-    </popup>  
+        <menuitem name="PodcastUpdateAll" action="PodcastUpdateAllAction"/>
+        <separator/>
+        <menuitem name="NewPlaylist" action="NewPlaylistAction"/>
+        <menuitem name="NewSmartPlaylist" action="NewSmartPlaylistAction"/>
+        <separator/>
+        <menu name="SortChildren" action="SortChildrenAction">
+          <menuitem name="SortChildrenNameAsc" action="SortChildrenNameAscAction"/>
+          <menuitem name="SortChildrenNameDesc" action="SortChildrenNameDescAction"/>
+          <menuitem name="SortChildrenSizeAsc" action="SortChildrenSizeAscAction"/>
+          <menuitem name="SortChildrenSizeDesc" action="SortChildrenSizeDescAction"/>
+        </menu>
+    </popup>
     
-    <popup name="PodcastFeedViewPopup" action="PodcastFeedViewPopupAction">
-        <menuitem name="PodcastUpdateAll" action="PodcastUpdateAllAction" />                   
+    <popup name="PodcastFeedPopup" action="PodcastFeedPopupAction">
         <menuitem name="PodcastUpdateFeed" action="PodcastUpdateFeedAction" />
-        <menuitem name="PodcastAdd" action="PodcastAddAction" />          
-        <separator />
+        <menuitem name="PodcastHomepage" action="PodcastHomepageAction" />
         <menuitem name="PodcastDelete" action="PodcastDeleteAction" />
-        <menuitem name="PodcastHomepage" action="PodcastHomepageAction" />        
         <separator />
-        <menuitem name="PodcastProperties" action="PodcastPropertiesAction" />        
-    </popup>    
-    
-    <popup name="PodcastItemViewPopup" action="PodcastItemViewPopupAction">
-        <menuitem name="PodcastItemLink" action="PodcastItemLinkAction" /> 
-        <menuitem name="PodcastItemDelete" action="PodcastItemDeleteAction" />         
-        <separator />                
-        <menuitem name="PodcastItemMarkNew" action="PodcastItemMarkNewAction" /> 
-        <menuitem name="PodcastItemMarkOld" action="PodcastItemMarkOldAction" /> 
-        <separator />                        
-        <menuitem name="PodcastItemDownload" action="PodcastItemDownloadAction" /> 
-        <menuitem name="PodcastItemCancel" action="PodcastItemCancelAction" />          
-        <separator />  
-        <menuitem name="PodcastItemProperties" action="PodcastItemPropertiesAction" />                
+        <menuitem name="PodcastProperties" action="PodcastPropertiesAction" />
     </popup>
 </ui>

Modified: trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ColumnCell.cs
==============================================================================
--- trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ColumnCell.cs	(original)
+++ trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ColumnCell.cs	Sun May 18 00:19:17 2008
@@ -67,6 +67,12 @@
         {
             if (property_info == null || property_info.ReflectedType != bound_object_parent.GetType ()) {
                 property_info = bound_object_parent.GetType ().GetProperty (property);
+                if (property_info == null) {
+                    throw new Exception (String.Format (
+                        "In {0}, type {1} does not have property {2}",
+                        this, bound_object_parent.GetType (), property
+                    ));
+                }
             }
         }
         

Modified: trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Data.Gui/IListView.cs
==============================================================================
--- trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Data.Gui/IListView.cs	(original)
+++ trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Data.Gui/IListView.cs	Sun May 18 00:19:17 2008
@@ -28,13 +28,18 @@
 
 namespace Hyena.Data.Gui
 {
-    public interface IListView<T>
+    public interface IListView
     {
-        void SetModel (IListModel<T> model);
-        IListModel<T> Model { get; }
-
+        Hyena.Collections.Selection Selection { get; }
+        
         void ScrollTo (int index);
         void CenterOn (int index);
         ColumnController ColumnController { get; set; }
     }
+    
+    public interface IListView<T> : IListView
+    {
+        void SetModel (IListModel<T> model);
+        IListModel<T> Model { get; }
+    }
 }

Modified: trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Interaction.cs
==============================================================================
--- trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Interaction.cs	(original)
+++ trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Interaction.cs	Sun May 18 00:19:17 2008
@@ -37,8 +37,6 @@
 {
     public partial class ListView<T> : Widget
     {
-        private int focused_row_index = -1;
-
         private Adjustment vadjustment;
         public Adjustment Vadjustment {
             get { return vadjustment; }
@@ -66,7 +64,7 @@
         {
             int row_limit;
             if (relative_row < 0) {
-                if (focused_row_index == -1) {
+                if (Selection.FocusedIndex == -1) {
                     return false;
                 }
                 
@@ -75,11 +73,11 @@
                 row_limit = Model.Count - 1;
             }
 
-            if (focused_row_index == row_limit) {
+            if (Selection.FocusedIndex == row_limit) {
                 return true;
             }
             
-            int row_index = Math.Min (Model.Count - 1, Math.Max (0, focused_row_index + relative_row));
+            int row_index = Math.Min (Model.Count - 1, Math.Max (0, Selection.FocusedIndex + relative_row));
 
             if (Selection != null) {
                 if ((modifier & Gdk.ModifierType.ControlMask) != 0) {
@@ -90,10 +88,10 @@
                     // Otherwise, select the new row and scroll etc as necessary.
                     if (relative_row * relative_row != 1) {
                         Selection.SelectFromFirst (row_index, true);
-                    } else if (Selection.Contains (focused_row_index)) {
+                    } else if (Selection.Contains (Selection.FocusedIndex)) {
                         Selection.SelectFromFirst (row_index, true);
                     } else {
-                        Selection.Select (focused_row_index);
+                        Selection.Select (Selection.FocusedIndex);
                         return true;
                     }
                 } else {
@@ -111,10 +109,10 @@
                     ScrollTo (y_at_row + RowHeight - (vadjustment.PageSize));
                 }
             } else {
-                ScrollTo (vadjustment.Value + ((row_index - focused_row_index) * RowHeight));
+                ScrollTo (vadjustment.Value + ((row_index - Selection.FocusedIndex) * RowHeight));
             }
 
-            focused_row_index = row_index;
+            Selection.FocusedIndex = row_index;
             InvalidateList ();
             return true;
         }
@@ -176,17 +174,17 @@
 
                 case Gdk.Key.Return:
                 case Gdk.Key.KP_Enter:
-                    if (Selection != null && focused_row_index != -1) {
+                    if (Selection != null && Selection.FocusedIndex != -1) {
                         Selection.Clear (false);
-                        Selection.Select (focused_row_index);
+                        Selection.Select (Selection.FocusedIndex);
                         OnRowActivated ();
                         handled = true;
                     }
                     break;
                 
                 case Gdk.Key.space:
-                    if (Selection != null && focused_row_index != 1) {
-                        Selection.ToggleSelect (focused_row_index);
+                    if (Selection != null && Selection.FocusedIndex != 1) {
+                        Selection.ToggleSelect (Selection.FocusedIndex);
                         handled = true;
                     }
                     break;
@@ -557,10 +555,10 @@
         
         protected virtual void OnRowActivated ()
         {
-            if (focused_row_index != -1) {
+            if (Selection.FocusedIndex != -1) {
                 RowActivatedHandler<T> handler = RowActivated;
                 if (handler != null) {
-                    handler (this, new RowActivatedArgs<T> (focused_row_index, model[focused_row_index]));
+                    handler (this, new RowActivatedArgs<T> (Selection.FocusedIndex, model[Selection.FocusedIndex]));
                 }
             }
         }
@@ -582,7 +580,7 @@
           
         private void FocusRow (int index)
         {
-            focused_row_index = index;
+            Selection.FocusedIndex = index;
         }
 
 #endregion

Modified: trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Rendering.cs
==============================================================================
--- trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Rendering.cs	(original)
+++ trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Rendering.cs	Sun May 18 00:19:17 2008
@@ -325,7 +325,7 @@
                     selection_height += single_list_alloc.Height;
                     selected_rows.Add (ri);
                     
-                    if (focused_row_index == ri) {
+                    if (Selection.FocusedIndex == ri) {
                         selected_focus_alloc = single_list_alloc;
                     }
                 } else {
@@ -345,7 +345,7 @@
                         cairo_context.Restore ();
                     }
                     
-                    if (Selection != null && focused_row_index == ri && !Selection.Contains (ri) && HasFocus) {
+                    if (Selection != null && Selection.FocusedIndex == ri && !Selection.Contains (ri) && HasFocus) {
                         CairoCorners corners = CairoCorners.All;
                         
                         if (Selection.Contains (ri - 1)) {

Modified: trunk/banshee/src/Libraries/Hyena/Hyena.Collections/Selection.cs
==============================================================================
--- trunk/banshee/src/Libraries/Hyena/Hyena.Collections/Selection.cs	(original)
+++ trunk/banshee/src/Libraries/Hyena/Hyena.Collections/Selection.cs	Sun May 18 00:19:17 2008
@@ -52,12 +52,18 @@
         RangeCollection ranges = new RangeCollection ();
         private int max_index;
         private int first_selected_index;
+        private int focused_index = -1;
         
         public event EventHandler Changed;
         
         public Selection ()
         {
         }
+        
+        public int FocusedIndex {
+            get { return focused_index; }
+            set { focused_index = value; }
+        }
 
         protected virtual void OnChanged ()
         {
@@ -148,6 +154,8 @@
         
         public void Clear (bool raise)
         {
+            focused_index = -1;
+            
             if (ranges.Count <= 0) {
                 return;
             }

Modified: trunk/banshee/src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteModelProvider.cs
==============================================================================
--- trunk/banshee/src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteModelProvider.cs	(original)
+++ trunk/banshee/src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteModelProvider.cs	Sun May 18 00:19:17 2008
@@ -41,6 +41,7 @@
         private readonly List<VirtualDatabaseColumn> virtual_columns = new List<VirtualDatabaseColumn> ();
         
         private DatabaseColumn key;
+        private int key_select_column_index;
         private HyenaSqliteConnection connection;
         
         private HyenaSqliteCommand create_command;
@@ -82,6 +83,10 @@
             get { return "HyenaModelVersions"; }
         }
         
+        public HyenaSqliteConnection Connection {
+            get { return connection; }
+        }
+        
         protected SqliteModelProvider (HyenaSqliteConnection connection)
         {
             this.connection = connection;
@@ -119,6 +124,8 @@
                 throw new Exception (String.Format ("The {0} table does not have a primary key", TableName));
             }
             
+            key_select_column_index = select_columns.IndexOf (key);
+            
             CheckVersion ();
             CheckTable ();
         }
@@ -260,12 +267,18 @@
             }
         }
         
-        public void Save (T target)
+        public virtual void Save (T target)
         {
-            if (((int)key.GetValue (target)) > 0) {
-                Update (target);
-            } else {
-                key.SetValue (target, Insert (target));
+            try {
+                if (Convert.ToInt32 (key.GetRawValue (target)) > 0) {
+                    Update (target);
+                } else {
+                    key.SetValue (target, Insert (target));
+                }
+            } catch (Exception e) {
+                Hyena.Log.Exception (e); 
+                Hyena.Log.DebugFormat ("type of key value: {0}", key.GetRawValue (target).GetType ());
+                throw;
             }
         }
         
@@ -308,7 +321,7 @@
             connection.Execute (UpdateCommand, GetUpdateParams (target));
         }
         
-        public T Load (IDataReader reader)
+        public virtual T Load (IDataReader reader)
         {
             T item = MakeNewObject ();
             Load (reader, item);
@@ -347,21 +360,19 @@
             }
         }
 
-        public T FetchFirstMatching (string condition)
+        public T FetchFirstMatching (string condition, params object [] vals)
         {
-            HyenaSqliteCommand fetch_matching_command = new HyenaSqliteCommand (String.Format ("{0} AND {1}", SelectCommand.Text, condition));
-            using (IDataReader reader = connection.Query (fetch_matching_command)) {
-                if (reader.Read ()) {
-                    return Load (reader);
-                }
+            foreach (T item in FetchAllMatching (condition, vals)) {
+                // Just return the first result, if there is one
+                return item;
             }
             return default(T);
         }
         
-        public IEnumerable<T> FetchAllMatching (string condition)
+        public IEnumerable<T> FetchAllMatching (string condition, params object [] vals)
         {
             HyenaSqliteCommand fetch_matching_command = new HyenaSqliteCommand (String.Format ("{0} AND {1}", SelectCommand.Text, condition));
-            using (IDataReader reader = connection.Query (fetch_matching_command)) {
+            using (IDataReader reader = connection.Query (fetch_matching_command, vals)) {
                 while (reader.Read ()) {
                     yield return Load (reader);
                 }
@@ -397,6 +408,11 @@
             return (long) key.GetValue (item);
         }
         
+        protected long PrimaryKeyFor (IDataReader reader)
+        {
+            return Convert.ToInt64 (reader[key_select_column_index]);
+        }
+        
         public void Delete (long id)
         {
             connection.Execute (delete_command, id);
@@ -432,7 +448,7 @@
 
             using (IDataReader reader = connection.Query (SelectSingleCommand, id)) {
                 if (reader.Read ()) {
-                    Load (reader);
+                    Load (reader, item);
                     return true;
                 }
             }

Modified: trunk/banshee/src/Libraries/Hyena/Hyena.Data/IFilterable.cs
==============================================================================
--- trunk/banshee/src/Libraries/Hyena/Hyena.Data/IFilterable.cs	(original)
+++ trunk/banshee/src/Libraries/Hyena/Hyena.Data/IFilterable.cs	Sun May 18 00:19:17 2008
@@ -30,7 +30,7 @@
 {
     public interface IFilterable
     {
-        string Filter { get; set; }
+        string UserQuery { get; set; }
         int UnfilteredCount { get; }
     }
 }

Modified: trunk/banshee/src/Libraries/Hyena/Hyena.Data/IListModel.cs
==============================================================================
--- trunk/banshee/src/Libraries/Hyena/Hyena.Data/IListModel.cs	(original)
+++ trunk/banshee/src/Libraries/Hyena/Hyena.Data/IListModel.cs	Sun May 18 00:19:17 2008
@@ -32,7 +32,7 @@
 
 namespace Hyena.Data
 {
-    public interface IListModel<T> : ISelectable
+    public interface IListModel : ISelectable
     {
         event EventHandler Cleared;
         event EventHandler Reloaded;
@@ -40,10 +40,14 @@
         void Clear ();
         void Reload ();
         
-        T this[int index] { get; }
         int Count { get; }
     }
     
+    public interface IListModel<T> : IListModel
+    {
+        T this[int index] { get; }
+    }
+    
     public interface IObjectListModel : IListModel<object>
     {
         ColumnDescription [] ColumnDescriptions { get; }

Modified: trunk/banshee/src/Libraries/Hyena/Hyena.Query/RelativeTimeSpanQueryValue.cs
==============================================================================
--- trunk/banshee/src/Libraries/Hyena/Hyena.Query/RelativeTimeSpanQueryValue.cs	(original)
+++ trunk/banshee/src/Libraries/Hyena/Hyena.Query/RelativeTimeSpanQueryValue.cs	Sun May 18 00:19:17 2008
@@ -50,6 +50,13 @@
         public override AliasedObjectSet<Operator> OperatorSet {
             get { return operators; }
         }
+        
+        public static RelativeTimeSpanQueryValue RelativeToNow (DateTime since)
+        {
+            RelativeTimeSpanQueryValue qv = new RelativeTimeSpanQueryValue ();
+            qv.SetRelativeValue ((since - DateTime.Now).TotalSeconds, TimeFactor.Second);
+            return qv;
+        }
 
         public override string XmlElementName {
             get { return "date"; }

Modified: trunk/banshee/src/Libraries/Hyena/Hyena.Query/TimeSpanQueryValue.cs
==============================================================================
--- trunk/banshee/src/Libraries/Hyena/Hyena.Query/TimeSpanQueryValue.cs	(original)
+++ trunk/banshee/src/Libraries/Hyena/Hyena.Query/TimeSpanQueryValue.cs	Sun May 18 00:19:17 2008
@@ -117,6 +117,7 @@
         {
             this.factor = factor;
             this.offset = (long) (offset * (double)factor);
+            DetermineFactor ();
             IsEmpty = false;
         }
 

Modified: trunk/banshee/src/Libraries/Hyena/Hyena/Log.cs
==============================================================================
--- trunk/banshee/src/Libraries/Hyena/Hyena/Log.cs	(original)
+++ trunk/banshee/src/Libraries/Hyena/Hyena/Log.cs	Sun May 18 00:19:17 2008
@@ -332,6 +332,11 @@
             Warning (message, null, showUser);
         }
         
+        public static void WarningFormat (string format, params object [] args)
+        {
+            Warning (String.Format (format, args));
+        }
+        
         #endregion
         
         #region Public Error Methods

Modified: trunk/banshee/src/Libraries/Migo/Makefile.am
==============================================================================
--- trunk/banshee/src/Libraries/Migo/Makefile.am	(original)
+++ trunk/banshee/src/Libraries/Migo/Makefile.am	Sun May 18 00:19:17 2008
@@ -16,6 +16,7 @@
 	Migo/Migo.Net/EventArgs/TransferRateUpdatedEventArgs.cs \
 	Migo/Migo.Net/Exceptions/RemoteFileModifiedException.cs \
 	Migo/Migo.Net/TransferStatusManager.cs \
+	Migo/Migo.Syndication/EnclosureManager.cs \
 	Migo/Migo.Syndication/Enumerations/FeedBackgroundSyncAction.cs \
 	Migo/Migo.Syndication/Enumerations/FeedBackgroundSyncStatus.cs \
 	Migo/Migo.Syndication/Enumerations/FeedDownloadError.cs \
@@ -31,8 +32,12 @@
 	Migo/Migo.Syndication/Feed.cs \
 	Migo/Migo.Syndication/FeedEnclosure.cs \
 	Migo/Migo.Syndication/FeedItem.cs \
+	Migo/Migo.Syndication/FeedManager.cs \
 	Migo/Migo.Syndication/FeedsManager.cs \
 	Migo/Migo.Syndication/FeedUpdateTask.cs \
+	Migo/Migo.Syndication/MigoItem.cs \
+	Migo/Migo.Syndication/MigoModelProvider.cs \
+	Migo/Migo.Syndication/OpmlParser.cs \
 	Migo/Migo.Syndication/Rfc822DateTime.cs \
 	Migo/Migo.Syndication/RssParser.cs \
 	Migo/Migo.Syndication/XmlUtils.cs \
@@ -64,5 +69,9 @@
 	Migo/Migo.TaskCore/Task.cs \
 	Migo/Migo.TaskCore/TaskGroup.cs
 
+if ENABLE_PODCAST
 include $(top_srcdir)/build/build.mk
+else
+EXTRA_DIST = $(SOURCES) $(RESOURCES)
+endif
 

Modified: trunk/banshee/src/Libraries/Migo/Migo.mdp
==============================================================================
--- trunk/banshee/src/Libraries/Migo/Migo.mdp	(original)
+++ trunk/banshee/src/Libraries/Migo/Migo.mdp	Sun May 18 00:19:17 2008
@@ -69,11 +69,17 @@
     <File name="Migo/Migo.Syndication/RssParser.cs" subtype="Code" buildaction="Compile" />
     <File name="Migo/Migo.Syndication/Rfc822DateTime.cs" subtype="Code" buildaction="Compile" />
     <File name="Migo/Migo.Syndication/XmlUtils.cs" subtype="Code" buildaction="Compile" />
+    <File name="Migo/Migo.Syndication/FeedManager.cs" subtype="Code" buildaction="Compile" />
+    <File name="Migo/Migo.Syndication/EnclosureManager.cs" subtype="Code" buildaction="Compile" />
+    <File name="Migo/Migo.Syndication/MigoItem.cs" subtype="Code" buildaction="Compile" />
+    <File name="Migo/Migo.Syndication/MigoModelProvider.cs" subtype="Code" buildaction="Compile" />
+    <File name="Migo/Migo.Syndication/OpmlParser.cs" subtype="Code" buildaction="Compile" />
   </Contents>
   <References>
     <ProjectReference type="Gac" localcopy="True" refto="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
     <ProjectReference type="Gac" localcopy="True" refto="System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
     <ProjectReference type="Project" localcopy="True" refto="Hyena" />
+    <ProjectReference type="Gac" localcopy="True" refto="System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
   </References>
   <Deployment.LinuxDeployData generateScript="False" />
   <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="True" RelativeMakefileName="Makefile.am">

Modified: trunk/banshee/src/Libraries/Migo/Migo/Migo.DownloadCore/DownloadManager.cs
==============================================================================
--- trunk/banshee/src/Libraries/Migo/Migo/Migo.DownloadCore/DownloadManager.cs	(original)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.DownloadCore/DownloadManager.cs	Sun May 18 00:19:17 2008
@@ -133,9 +133,7 @@
             if (SetDisposed ()) {
                 if (dg != null) {                
                     dg.StopAsync ();
-                    Console.WriteLine ("dg - Waiting");
                     dg.Handle.WaitOne ();
-                    Console.WriteLine ("dg - Canceled");
 
                     dg.TaskStopped -= TaskStoppedHandler;       
                     dg.Dispose ();

Modified: trunk/banshee/src/Libraries/Migo/Migo/Migo.DownloadCore/HttpFileDownloadTask.cs
==============================================================================
--- trunk/banshee/src/Libraries/Migo/Migo/Migo.DownloadCore/HttpFileDownloadTask.cs	(original)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.DownloadCore/HttpFileDownloadTask.cs	Sun May 18 00:19:17 2008
@@ -345,8 +345,6 @@
             }
             
 	        wc.Timeout = (60 * 1000);
-	        wc.UserAgent = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.2) Gecko/2007022617 Firefox/2.0.0.2 (Ubuntu-feisty)";
-
 	        wc.DownloadFileCompleted += OnDownloadFileCompletedHandler;
 	        wc.DownloadProgressChanged += OnDownloadProgressChangedHandler;
 	        wc.ResponseReceived += OnResponseReceivedHandler;	        

Modified: trunk/banshee/src/Libraries/Migo/Migo/Migo.Net/AsyncWebClient.cs
==============================================================================
--- trunk/banshee/src/Libraries/Migo/Migo/Migo.Net/AsyncWebClient.cs	(original)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.Net/AsyncWebClient.cs	Sun May 18 00:19:17 2008
@@ -58,7 +58,7 @@
         private DownloadType type;
         
         private IWebProxy proxy;
-        private string userAgent;
+        private string user_agent;
         private Encoding encoding;                        
         private ICredentials credentials;
 
@@ -186,12 +186,8 @@
             }
         }
         
-        public int Timeout
-		{
-            get {
-                return timeout;
-            }
-            
+        public int Timeout {
+            get { return timeout; }
             set {
                 if (value < -1) {
                     throw new ArgumentOutOfRangeException (
@@ -201,16 +197,20 @@
                 
                 timeout = value;
             }
-        }        
+        }
         
-        public string UserAgent 
-		{
-            get { return userAgent; }
-            set { userAgent = 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 
-		{
+        private bool Cancelled {
             get {
                 lock (cancelBusySync) {
                     return cancelled; 
@@ -473,10 +473,8 @@
                     request, timeout, true
                 );            
             } catch (Exception e) {
-                Console.WriteLine (e.Message);            
-                Console.WriteLine (e.StackTrace);
                 Completed (e);
-            }  
+            }
         }    
             
         private HttpWebRequest PrepRequest (Uri address)
@@ -554,8 +552,8 @@
                     }
                 }
 			} else {
-                if (!String.IsNullOrEmpty (this.userAgent)) {
-                    req.UserAgent = this.userAgent;
+                if (!String.IsNullOrEmpty (UserAgent)) {
+                    req.UserAgent = UserAgent;
                 }
 
                 if (this.range > 0) {
@@ -595,7 +593,6 @@
                     //Console.WriteLine ("am I here?");
                     //Console.WriteLine ("Why {1}:  {0}", we.Status, this.fileName);
                     //Console.WriteLine ("Cancelled:  {0}", this.Cancelled);
-                    Console.WriteLine (we.Message);
                     err = we;
                 }
             } catch (Exception e) {
@@ -641,7 +638,6 @@
             
             switch (type) {
                 case DownloadType.String:
-                    goto case DownloadType.Data;
                 case DownloadType.Data:
                     dataDownload = true;                    
                     
@@ -807,9 +803,7 @@
             EventHandler<EventArgs> handler = ResponseReceived;
             
             if (handler != null) {
-                try {
-                    handler (this, new EventArgs ());
-                } catch {}
+                handler (this, new EventArgs ());
             }
         }
        
@@ -831,12 +825,9 @@
             EventHandler <DownloadProgressChangedEventArgs> 
                 handler = DownloadProgressChanged;
         
-            try 
-            {
-                if (handler != null) {
-                    handler (this, args);
-                }
-            } catch {}
+            if (handler != null) {
+                handler (this, args);
+            }
         }
 
         private void OnDownloadProgressChangedHandler (object sender, 
@@ -866,13 +857,10 @@
         {
             EventHandler <DownloadDataCompletedEventArgs> 
                 handler = DownloadDataCompleted;
-                
-            try
-            {            
-                if (handler != null) {
-                    handler (this, args);
-                }
-            } catch {}
+
+            if (handler != null) {
+                handler (this, args);
+            }
         }        
         
         private void OnDownloadFileCompleted (Exception error,
@@ -889,13 +877,10 @@
         {
             EventHandler <AsyncCompletedEventArgs> 
                 handler = DownloadFileCompleted;
-
-            try
-            {            
-                if (handler != null) {
-                    handler (this, args);
-                }
-            } catch {}
+        
+            if (handler != null) {
+                handler (this, args);
+            }
         }
         
         private void OnDownloadStringCompleted (string resultStr,
@@ -914,13 +899,10 @@
         {
             EventHandler <DownloadStringCompletedEventArgs> 
                 handler = DownloadStringCompleted;
-                
-            try
-            {            
-                if (handler != null) {
-                    handler (this, args);
-                }
-            } catch {}
+                         
+            if (handler != null) {
+                handler (this, args);
+            }
         }
     }
 }

Added: trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/EnclosureManager.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/EnclosureManager.cs	Sun May 18 00:19:17 2008
@@ -0,0 +1,474 @@
+//
+// EnclosureManager.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.IO;
+using System.Data;
+using System.Threading;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+using Hyena;
+using Hyena.Data.Sqlite;
+
+using Migo.DownloadCore;
+using Migo.TaskCore;
+using Migo.TaskCore.Collections;
+
+namespace Migo.Syndication
+{
+    public class EnclosureManager
+    {
+        private Dictionary<FeedEnclosure, HttpFileDownloadTask> queued_downloads;
+        private DownloadManager download_manager;
+        private bool disposed;
+        private readonly object sync = new object ();
+        private ManualResetEvent download_handle;
+        
+        public event EventHandler<TaskEventArgs<HttpFileDownloadTask>> EnclosureDownloadCompleted;   
+    
+        public EnclosureManager (DownloadManager downloadManager)
+        {
+            download_manager = downloadManager;
+            download_manager.Tasks.TaskAdded += OnDownloadTaskAdded;
+            download_manager.Tasks.TaskRemoved += OnDownloadTaskRemoved;            
+            download_manager.Group.TaskStatusChanged += OnDownloadTaskStatusChangedHandler;
+
+            queued_downloads = new Dictionary<FeedEnclosure, HttpFileDownloadTask> ();
+            
+            download_handle = new ManualResetEvent (true);
+        }
+        
+        public void Dispose (AutoResetEvent disposeHandle)
+        {
+            
+            disposed = true;
+            
+            List<HttpFileDownloadTask> tasks = null;
+
+            lock (sync) {
+                if (queued_downloads.Count > 0) {
+                    tasks = new List<HttpFileDownloadTask> (queued_downloads.Values);
+                }
+            }
+
+            if (tasks != null) {
+                foreach (HttpFileDownloadTask t in tasks) {
+                    t.Stop ();
+                }
+
+                download_handle.WaitOne ();
+            }
+            
+            if (download_handle != null) {
+                download_handle.Close ();
+                download_handle = null;
+            }                   
+            
+            if (download_manager != null) {
+                download_manager.Tasks.TaskAdded -= OnDownloadTaskAdded;
+                download_manager.Tasks.TaskRemoved -= OnDownloadTaskRemoved; 
+                download_manager = null;
+            }  
+        }
+
+        public HttpFileDownloadTask QueueDownload (FeedEnclosure enclosure) 
+        {
+            return QueueDownload (enclosure, true);         
+        }
+           
+        public HttpFileDownloadTask QueueDownload (FeedEnclosure enclosure, bool queue)
+        {
+            if (enclosure == null) {
+                throw new ArgumentNullException ("enc");
+            }
+            
+            HttpFileDownloadTask task = null;       
+            
+            lock (sync) {
+                if (disposed) {
+                    return null;
+                }
+                
+                if (!queued_downloads.ContainsKey (enclosure)) {                    
+                    Feed parentFeed = enclosure.Item.Feed;                    
+                    
+                    if (parentFeed != null) {                        
+                        task = download_manager.CreateDownloadTask (enclosure.Url, enclosure);
+                        //Console.WriteLine ("Task DL path:  {0}", task.LocalPath);
+                        task.Name = String.Format ("{0} - {1}", parentFeed.Title, enclosure.Item.Title);
+                        
+                        //task.StatusChanged
+                        task.Completed += OnDownloadTaskCompletedHandler;                    
+                        
+                        // Race condition...
+                        // Should only be added when the task is associated or 
+                        // it can be canceled before it is added to the progress manager.
+                        
+                        // Add a pre-association dict and move tasks to the 
+                        // queued dict once they've been offically added.
+                        
+                        queued_downloads.Add (enclosure, task);
+                    }                    
+                }
+
+                if (task != null && queue) {
+                    download_manager.QueueDownload (task);
+                }
+            }
+                       
+            return task;        
+        }
+        
+        public IEnumerable<HttpFileDownloadTask> QueueDownloads (IEnumerable<FeedEnclosure> encs)
+        {
+            if (encs == null) {
+                throw new ArgumentNullException ("encs");
+            }
+            
+            ICollection<HttpFileDownloadTask> encsCol = encs as ICollection<HttpFileDownloadTask>;
+            
+            List<HttpFileDownloadTask> tasks = (encsCol == null) ?
+                new List<HttpFileDownloadTask> () : 
+                new List<HttpFileDownloadTask> (encsCol.Count);
+            
+            HttpFileDownloadTask tmpTask = null;
+            
+            lock (sync) {   
+                if (disposed) {
+                    return tasks;
+                }
+                
+                foreach (FeedEnclosure enc in encs) {
+                    tmpTask = QueueDownload (enc, false);
+                    if (tmpTask != null) {
+                        tasks.Add (tmpTask);
+                    }    
+                }
+                
+                if (tasks.Count > 0) {
+                    download_manager.QueueDownload (tasks);
+                }
+            }
+            
+            return tasks;
+        }
+       
+        public void CancelDownload (FeedEnclosure enc)
+        {
+            lock (sync) {
+                HttpFileDownloadTask task = FindDownloadTask (enc);            
+                        
+                if (task != null) {
+                    // Look into multi-cancel later      
+                    task.CancelAsync ();
+                }            
+            }
+        }
+        
+        public void StopDownload (FeedEnclosure enc)
+        {   
+            lock (sync) {
+                HttpFileDownloadTask task = FindDownloadTask (enc);            
+                        
+                if (task != null) {
+                    task.Stop ();
+                }   
+            }
+        }  
+        
+        /*private void OnFeedItemRemoved (Feed feed, FeedItem item)
+        {
+            if (feed == null) {
+                throw new ArgumentNullException ("feed");
+            } else if (item == null) {
+                throw new ArgumentNullException ("item");
+            }
+
+            EventHandler<FeedItemEventArgs> handler = FeedItemRemoved;            
+            
+            if (item.Enclosure != null) {
+                lock (sync) {
+                    HttpFileDownloadTask task;                
+                         
+                    if (queued_downloads.TryGetValue ((FeedEnclosure)item.Enclosure, out task)) {
+                        task.CancelAsync ();
+                    }
+                }
+            }            
+            
+            if (handler != null) {
+                OnFeedItemEvent (handler, new FeedItemEventArgs (feed, item));
+            }                 
+        }
+        
+        private void OnFeedItemsRemoved (Feed feed, IEnumerable<FeedItem> items)
+        {
+            if (feed == null) {
+                throw new ArgumentNullException ("feed");
+            } else if (items == null) {
+                throw new ArgumentNullException ("items");
+            }
+            
+            EventHandler<FeedItemEventArgs> handler = FeedItemRemoved;
+
+            lock (sync) {
+                HttpFileDownloadTask task;  
+                
+                foreach (FeedItem item in items) {                
+                    if (item.Enclosure != null) {                    
+                        if (queued_downloads.TryGetValue ((FeedEnclosure)item.Enclosure, out task)) {
+                            task.CancelAsync ();
+                        }
+                    }
+                }
+            }
+            
+            if (handler != null) {
+                OnFeedItemEvent (handler, new FeedItemEventArgs (feed, items));
+            }               
+        } */
+        
+        private HttpFileDownloadTask FindDownloadTask (FeedEnclosure enc)
+        {
+            if (enc == null) {
+                throw new ArgumentNullException ("enc");
+            }
+            
+            return FindDownloadTaskImpl ((FeedEnclosure)enc);
+        }
+        
+        private HttpFileDownloadTask FindDownloadTaskImpl (FeedEnclosure enc) 
+        {
+            HttpFileDownloadTask task = null;
+            Feed parentFeed = enc.Item.Feed as Feed;                               
+            
+            if (parentFeed != null && queued_downloads.ContainsKey (enc)) {
+                task = queued_downloads[enc];
+            }
+            
+            return task;
+        }     
+        
+        private void TaskAddedAction (HttpFileDownloadTask task)
+        {
+            Feed parentFeed = null;
+            FeedEnclosure enc = task.UserState as FeedEnclosure;
+                    
+            if (enc != null) {
+                lock (sync) { 
+                    parentFeed = enc.Item.Feed;                  
+                    
+                    if (parentFeed != null && queued_downloads.ContainsKey (enc)) {
+                        if (queued_downloads.Count == 0) {
+                            download_handle.Reset ();
+                        }                        
+
+                        enc.DownloadStatus = FeedDownloadStatus.Pending;                       
+                        //parentFeed.IncrementQueuedDownloadCount ();                    
+                    }
+                }
+            }        
+        }
+        
+        private void OnDownloadTaskAdded (object sender, TaskAddedEventArgs<HttpFileDownloadTask> e)
+        {
+            if (e.Task != null) {
+                TaskAddedAction (e.Task);
+            } else if (e.Tasks != null) {
+                foreach (HttpFileDownloadTask task in e.Tasks) {
+                    TaskAddedAction (task);                                    
+                }
+            }
+        }
+        
+        private void OnDownloadTaskCompletedHandler (object sender, TaskCompletedEventArgs args)
+        {
+            HttpFileDownloadTask task = sender as HttpFileDownloadTask;
+            FeedEnclosure enc = task.UserState as FeedEnclosure;
+
+            if (enc != null) {   
+                if (args.Error != null || task.Status == TaskStatus.Failed) {
+                    enc.DownloadStatus = FeedDownloadStatus.DownloadFailed;
+                } else if (!args.Cancelled) {
+                    if (task.Status == TaskStatus.Succeeded) {
+                        try {                        
+                            enc.SetFileImpl (
+                                task.RemoteUri.ToString (), 
+                                Path.GetDirectoryName (task.LocalPath), 
+                                task.MimeType, 
+                                Path.GetFileName (task.LocalPath)
+                            );
+                        } catch (Exception e) {
+                            Log.Exception (e);
+                            enc.LastDownloadError = FeedDownloadError.DownloadFailed;
+                            enc.DownloadStatus = FeedDownloadStatus.DownloadFailed;
+                            enc.Save ();
+                        }
+                    }
+                }
+            }
+            
+            OnEnclosureDownloadCompleted (task);            
+        }
+        
+        private void OnEnclosureDownloadCompleted (HttpFileDownloadTask task)
+        {
+            /*EventHandler<TaskEventArgs<HttpFileDownloadTask>> handler = EnclosureDownloadCompleted;
+        
+            if (handler != null) {
+                AsyncCommandQueue<ICommand> cmdQCpy = command_queue;
+                
+                if (cmdQCpy != null) {
+                    cmdQCpy.Register (new EventWrapper<TaskEventArgs<HttpFileDownloadTask>> (
+                	    handler, this, new TaskEventArgs<HttpFileDownloadTask> (task))
+                	);
+                }        
+            }   */                      
+        }  
+        
+        private void DownloadTaskRemoved (FeedEnclosure enc, HttpFileDownloadTask task, bool decQueuedCount)
+        {
+            if (queued_downloads.ContainsKey (enc)) {
+                queued_downloads.Remove (enc);    
+                task.Completed -= OnDownloadTaskCompletedHandler;                                            
+                
+                if (decQueuedCount) {
+                    //enc.Item.Feed.DecrementQueuedDownloadCount ();
+                }
+                
+                if (queued_downloads.Count == 0) {
+                	if (download_handle != null) {
+                	    download_handle.Set ();
+                	}
+                }
+            }
+        }
+        
+        private void OnDownloadTaskRemoved (object sender, TaskRemovedEventArgs<HttpFileDownloadTask> e)
+        {
+            if (e.Task != null) {
+                FeedEnclosure enc = e.Task.UserState as FeedEnclosure;
+                    
+                if (enc != null) {
+                    lock (sync) {
+                        DownloadTaskRemoved (enc, e.Task, true);
+                    }
+                }
+            } else if (e.Tasks != null) {
+                Feed tmpParent = null;
+                FeedEnclosure tmpEnclosure = null;
+                List<FeedEnclosure> tmpList = null;
+                
+                Dictionary<Feed, List<FeedEnclosure>> feedDict =
+                    new Dictionary<Feed,List<FeedEnclosure>> ();
+                
+                lock (sync) {
+                    foreach (HttpFileDownloadTask t in e.Tasks) {
+                        tmpEnclosure = t.UserState as FeedEnclosure;
+                        
+                        if (tmpEnclosure != null) {
+                            tmpParent = tmpEnclosure.Item.Feed;
+                            
+                            if (!feedDict.TryGetValue (tmpParent, out tmpList)) {
+                                tmpList = new List<FeedEnclosure> ();
+                                feedDict.Add (tmpParent, tmpList);  
+                            }
+                            
+                            tmpList.Add (tmpEnclosure);
+                            DownloadTaskRemoved (tmpEnclosure, t, false);
+                        }
+                    }
+                    
+                    //foreach (KeyValuePair<Feed,List<FeedEnclosure>> kvp in feedDict) {
+                        //kvp.Key.DecrementQueuedDownloadCount (kvp.Value.Count);
+                    //}
+                }
+            }                       
+        }        
+
+        private void TaskStatusChanged (TaskStatusChangedInfo statusInfo)
+        {
+            HttpFileDownloadTask task = statusInfo.Task as HttpFileDownloadTask;
+            
+            if (task == null) {
+                return;
+            }
+            
+            FeedEnclosure enc = task.UserState as FeedEnclosure;
+            
+            if (enc == null) {
+                return;
+            }
+               
+            switch (statusInfo.NewStatus) {
+            case TaskStatus.Stopped:
+            case TaskStatus.Cancelled:
+                enc.DownloadStatus = FeedDownloadStatus.None;
+                break;
+            case TaskStatus.Failed:
+                enc.DownloadStatus = FeedDownloadStatus.DownloadFailed;
+                break;
+            case TaskStatus.Paused: 
+                enc.DownloadStatus = FeedDownloadStatus.Paused;
+                break;
+            case TaskStatus.Ready:
+                enc.DownloadStatus = FeedDownloadStatus.Pending;
+                break;
+            case TaskStatus.Running:
+                enc.DownloadStatus = FeedDownloadStatus.Downloading;
+                //enc.Item.Feed.IncrementActiveDownloadCount ();                    
+                break;
+            case TaskStatus.Succeeded:
+                break;
+            }
+            
+            FeedsManager.Instance.FeedManager.OnItemChanged (enc.Item);
+
+            if (statusInfo.OldStatus == TaskStatus.Running) {
+                //enc.Item.Feed.DecrementActiveDownloadCount ();                    
+            }
+        }
+
+        private void OnDownloadTaskStatusChangedHandler (object sender, TaskStatusChangedEventArgs e)
+        {
+            if (e.StatusChanged != null) {
+                lock (sync) {
+                    TaskStatusChanged (e.StatusChanged);
+                }
+            } else {
+                lock (sync) {
+                    foreach (TaskStatusChangedInfo statusInfo in e.StatusesChanged) {
+                        TaskStatusChanged (statusInfo);                        
+                    }
+                }                
+            }
+        }
+    }
+}

Modified: trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/EventArgs/FeedItemEventArgs.cs
==============================================================================
--- trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/EventArgs/FeedItemEventArgs.cs	(original)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/EventArgs/FeedItemEventArgs.cs	Sun May 18 00:19:17 2008
@@ -31,38 +31,16 @@
 
 namespace Migo.Syndication
 {    
-    public class FeedItemEventArgs : FeedEventArgs
+    public class FeedItemEventArgs
     {
-        private readonly FeedItem item;
         private readonly IEnumerable<FeedItem> items;
-        
-        public FeedItem Item {
-            get { return item; }
-        }
-        
+
         public IEnumerable<FeedItem> Items {
             get { return items; }   
         }
         
-        public FeedItemEventArgs (Feed feed, FeedItem item) : this (feed, item, null) 
-        {
-            if (item == null) {
-            	throw new ArgumentNullException ("item");
-            }        
-        }
-        
-        public FeedItemEventArgs (Feed feed, IEnumerable<FeedItem> items) : this (feed, null, items) 
-        {
-            if (items == null) {
-            	throw new ArgumentNullException ("items");
-            }        
-        }
-        
-        private FeedItemEventArgs (Feed feed, 
-                                   FeedItem item, 
-                                   IEnumerable<FeedItem> items) : base (feed)
+        public FeedItemEventArgs (Feed feed, IEnumerable<FeedItem> items)
         {
-            this.item = item;
             this.items = items;
         }
     }

Modified: trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/Feed.cs
==============================================================================
--- trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/Feed.cs	(original)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/Feed.cs	Sun May 18 00:19:17 2008
@@ -35,6 +35,8 @@
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 
+using Mono.Unix;
+
 using Hyena;
 using Hyena.Data.Sqlite;
 
@@ -61,275 +63,216 @@
         None = 6        
     }
 
-    public class Feed
-    {        
+    public class Feed : MigoItem<Feed>
+    {
         private static SqliteModelProvider<Feed> provider;
         public static SqliteModelProvider<Feed> Provider {
-            get { return provider; }
-            set { provider = value; }
+            get { return provider ?? provider = new MigoModelProvider<Feed> (FeedsManager.Instance.Connection, "PodcastSyndications"); }
         }
+        
+        public static void Init () {}
 
-        private static Feed all = new Feed ();
-        public static Feed All {
-            get { return all; }
+        public static bool Exists (string url)
+        {
+            return Provider.Connection.Query<int> (String.Format ("select count(*) from {0} where url = ?", Provider.TableName), url) != 0;
         }
 
-        private bool canceled;
+        //private bool canceled;
         private bool deleted; 
         private bool updating;
         
-        private AsyncWebClient wc;
-        private ManualResetEvent updatingHandle = new ManualResetEvent (true);
+        //private ManualResetEvent updatingHandle = new ManualResetEvent (true);
         
-        private readonly object sync = new object ();
-       
-        private long queuedDownloadCount;
-        private long activeDownloadCount;        
+        private readonly object sync = new object ();     
         
         private List<FeedItem> items;
-        private List<FeedItem> inactive_items;
         
-        private FeedAutoDownload auto_download;
         private string copyright;
         private string description;
         private bool downloadEnclosuresAutomatically;
-        private FeedDownloadStatus downloadStatus;
-        private string downloadUrl;
         private string image;        
         private long interval;
-        private bool isList;
         private string language;
-        private DateTime lastBuildDate;
+        private DateTime last_build_date = DateTime.MinValue;
         private FeedDownloadError lastDownloadError;
-        private DateTime lastDownloadTime;      
-        private DateTime lastWriteTime;
+        private DateTime last_download_time = DateTime.MinValue;
         private string link;
-        private string localEnclosurePath;
-        private long localID = -1;
+        //private string local_enclosure_path;
+        private long dbid = -1;
         private long maxItemCount = 200;
-        private string name;
-        private FeedsManager parent;        
         private DateTime pubDate;
         private FeedSyncSetting syncSetting;
         private string title;
-        private long ttl;  
-        private long unreadItemCount;  
+        private long ttl;
         private string url;
-
-        public event EventHandler<FeedEventArgs> FeedDeleted;
-        public event EventHandler<FeedDownloadCompletedEventArgs> FeedDownloadCompleted;
-        public event EventHandler<FeedDownloadCountChangedEventArgs> FeedDownloadCountChanged;                
-        public event EventHandler<FeedEventArgs> FeedDownloading;
-        public event EventHandler<FeedItemCountChangedEventArgs> FeedItemCountChanged;
-        public event EventHandler<FeedEventArgs> FeedRenamed;
-        public event EventHandler<FeedEventArgs> FeedUrlChanged;
-        
-        public event EventHandler<FeedItemEventArgs> FeedItemAdded;
-        public event EventHandler<FeedItemEventArgs> FeedItemRemoved;
+        private string keywords, category;
         
 #region Database-bound Properties
+
+        [DatabaseColumn ("FeedID", Constraints = DatabaseColumnConstraints.PrimaryKey)]
+        public override long DbId { 
+            get { return dbid; }
+            protected set { dbid = value; }
+        }
         
         [DatabaseColumn]
-        public string Copyright { 
-            get { lock (sync) { return copyright; } } 
-            set { copyright = value; }
+        public string Title {
+            get { return title ?? Catalog.GetString ("Unknown Podcast"); }
+            set { title = value; }
         }
         
         [DatabaseColumn]
         public string Description { 
-            get { lock (sync) { return description; } } 
+            get { return description; }
             set { description = value; }
         }
+
+        [DatabaseColumn]
+        public string Url {
+            get { return url; }
+            set { url = value; }
+        }
         
         [DatabaseColumn]
-        public bool DownloadEnclosuresAutomatically { 
-            get { lock (sync) { return downloadEnclosuresAutomatically; } }  
-            set { lock (sync) { downloadEnclosuresAutomatically = value; } }
+        public string Keywords {
+            get { return keywords; }
+            set { keywords = value; }
+        }
+        
+        [DatabaseColumn]
+        public string Category {
+            get { return category; }
+            set { category = value; }
+        }
+        
+        [DatabaseColumn]
+        public string Copyright { 
+            get { return copyright; }
+            set { copyright = value; }
         }
         
         [DatabaseColumn]
-        public string DownloadUrl {
-            get { lock (sync) { return downloadUrl; } }
-            set { downloadUrl = value; }
-        }     
+        public bool DownloadEnclosuresAutomatically { 
+            get { return downloadEnclosuresAutomatically; }
+            set { downloadEnclosuresAutomatically = value; }
+        } 
         
         [DatabaseColumn]
-        public string Image {
-            get { lock (sync) { return image; } }
+        public string ImageUrl {
+            get { return image; }
             set { image = value; }
         }
 
         [DatabaseColumn]
         public long Interval { 
-            get { lock (sync) { return interval; } }
+            get { return interval; }
             set { 
-                lock (sync) { interval = (value < 15) ? 1440 : value; }
+                interval = (value < 15) ? 1440 : value;
             } 
         }
         
         [DatabaseColumn]
         public string Language { 
-            get { lock (sync) { return language; } }
+            get { return language; }
             set { language = value; }
         }
-        
-        [DatabaseColumn]
-        public DateTime LastBuildDate { 
-            get { lock (sync) { return lastBuildDate; } }
-            set { lastBuildDate = value; }
-        }
-        
+
         [DatabaseColumn]
         public FeedDownloadError LastDownloadError { 
-            get { lock (sync) { return lastDownloadError; } }
+            get { return lastDownloadError; }
             set { lastDownloadError = value; }
         }
         
         [DatabaseColumn]
         public DateTime LastDownloadTime { 
-            get { lock (sync) { return lastDownloadTime; } }
-            set { lastDownloadTime = value; }
-        }
-        
-        [DatabaseColumn]
-        public DateTime LastWriteTime { 
-            get { lock (sync) { return lastWriteTime; } }
-            set { lastWriteTime = value; }
+            get { return last_download_time; }
+            set { last_download_time = value; }
         }
         
         [DatabaseColumn]
         public string Link { 
-            get { lock (sync) { return link; } }
+            get { return link; }
             set { link = value; }
         }
         
-        [DatabaseColumn]
-        public string LocalEnclosurePath { 
-            get { lock (sync) { return localEnclosurePath; } }
-            set { 
-                lock (sync) {
-                    if (localEnclosurePath != value) {
-                        localEnclosurePath = value;
-                        Save ();                    	
-                    }
-                }
-            }
-        }
-        
-        [DatabaseColumn ("FeedID", Constraints = DatabaseColumnConstraints.PrimaryKey)]
-        public long DbId { 
-            get { lock (sync) { return localID; } }
-            set { lock (sync) { localID = value; } }
+        //[DatabaseColumn]
+        public string LocalEnclosurePath {
+            get { return Path.Combine (FeedsManager.Instance.PodcastStorageDirectory, Title); }
+            //set { local_enclosure_path = value; }
         }
 
         [DatabaseColumn]
         public long MaxItemCount {
-            get { lock (sync) { return maxItemCount; } }
-            set { lock (sync) { maxItemCount = value; } }
-        }
-            
-        [DatabaseColumn]
-        public string Name {
-            get { lock (sync) { return name; } }
-            
-            private set {
-                bool renamed = false;
-                
-                lock (sync) {
-                    if (value == null) {
-                       	throw new ArgumentNullException ("Name");
-                    }   
-                    
-                    if (value != name) {
-                    	name = value;
-                        renamed = true;
-                        Save ();
-                    }
-                }
-                
-                if (renamed) {
-                    OnFeedRenamed ();                	
-                }
-            }            
+            get { return maxItemCount; }
+            set { maxItemCount = value; }
         }
         
         [DatabaseColumn]
         public DateTime PubDate {
-            get { lock (sync) { return pubDate; } }
+            get { return pubDate; }
             set { pubDate = value; }
         }
         
         [DatabaseColumn]
+        public DateTime LastBuildDate {
+            get { return last_build_date; }
+            set { last_build_date = value; }
+        }
+        
+        /*private DateTime last_downloaded;
+        [DatabaseColumn]
+        public DateTime LastDownloaded {
+            get { return last_downloaded; }
+            set { last_downloaded = value; }
+        }*/
+        
+        [DatabaseColumn]
 		public FeedSyncSetting SyncSetting {
-            get { lock (sync) { return syncSetting; } } 
+            get { return syncSetting; }
             set { syncSetting = value; }
         }
-
+        
         [DatabaseColumn]
+        protected DateTime last_auto_download = DateTime.MinValue;
+        public DateTime LastAutoDownload {
+            get { return last_auto_download; }
+            set { last_auto_download = value; }
+        }
+
+        [DatabaseColumn("AutoDownload")]
+        protected FeedAutoDownload auto_download = FeedAutoDownload.None;
         public FeedAutoDownload AutoDownload {
             get { return auto_download; }
-            set { auto_download = value; }
-        }
-        
-        [DatabaseColumn]
-        public string Title {
-            get {
-                lock (sync) { 
-                    return String.IsNullOrEmpty (title) ? url : title; 
-                }
+            set {
+                if (value == auto_download)
+                    return;
+
+                auto_download = value;
+                CheckForItemsToDownload ();
             }
-            set { title = value; }
         }
               
         [DatabaseColumn]
         public long Ttl {
-            get { lock (sync) { return ttl; } }
+            get { return ttl; }
             set { ttl = value; }
         }
         
-        [DatabaseColumn]
-        public string Url {
-            get { lock (sync) { return url; } }
-            set {
-                if (String.IsNullOrEmpty (value)) {
-                   	throw new ArgumentNullException ("Url");
-                }   
-                
-                bool updated = false;
-                string oldUrl = null;
-                
-                lock (sync) {                
-                    if (value != url) {
-                        lastDownloadTime = DateTime.MinValue;
-                    	oldUrl = url;
-                    	url = value;
-                        updated = true;
-                        Save ();
-                    }
-                }
-                
-                if (updated) {
-                    parent.UpdateFeedUrl (oldUrl, this);                
-                    OnFeedUrlChanged ();
-                }
-            }
+        [DatabaseColumn("DownloadStatus")]
+        private FeedDownloadStatus download_status;
+        public FeedDownloadStatus DownloadStatus { 
+            get { return download_status; }
+            set { download_status = value; Manager.OnFeedsChanged (); }
         }
         
 #endregion
 
-#region Other Properties
-
-        public long ActiveDownloadCount { 
-            get { lock (sync) { return activeDownloadCount; } }
-        }
-        
-        public long QueuedDownloadCount { 
-            get { lock (sync) { return queuedDownloadCount; } }             
-        }         
+#region Other Properties  
 
         // TODO remove this, way too redundant with DownloadStatus
-        public PodcastFeedActivity Activity {
-            get {
+        /*public PodcastFeedActivity Activity {
+            get { return activity; }
+            
                 PodcastFeedActivity ret = PodcastFeedActivity.None;
                 
                 if (this == All) {
@@ -358,419 +301,93 @@
 
                 return ret;
             }
-        }
-
-        public FeedsManager Parent {
-            get { lock (sync) { return parent; } }
-        }
-        
-        public FeedDownloadStatus DownloadStatus { 
-            get { lock (sync) { return downloadStatus; } }
-        }
-        
-        public bool IsList {
-            get { lock (sync) { return isList; } }
-        }
-        
-        public long ItemCount {
-            get { lock (sync) { return Items.Count; } }         
-        }
-        
-        private bool items_loaded = false;
+        }*/
         
-        private ReadOnlyCollection<FeedItem> ro_items;
-        public ReadOnlyCollection<FeedItem> Items {
+        public IEnumerable<FeedItem> Items {
             get {
-                lock (sync) {
-                    if (!items_loaded) {
-                        LoadItems ();
+                if (DbId > 0) {
+                    foreach (FeedItem item in 
+                        FeedItem.Provider.FetchAllMatching (String.Format ("{0}.FeedID = {1} ORDER BY {0}.PubDate DESC", FeedItem.Provider.TableName, DbId)))
+                    {
+                        yield return item;
                     }
-                    return ro_items ?? ro_items = new System.Collections.ObjectModel.ReadOnlyCollection<FeedItem> (items);                                
-                }
-            }
-        }
-        
-        public long UnreadItemCount {
-            get { lock (sync) { return unreadItemCount; } } 
-            internal set {
-                if (value < 0 /*|| value > maxItemCount*/ || value > ItemCount) {  
-                    // max item count not yet implemented
-                   	throw new ArgumentOutOfRangeException (
-                        "UnreadItemCount:  Must be >= 0 and < MaxItemCount and <= ItemCount."
-                    );
-                }   
-                
-                if (value != unreadItemCount) {
-                	unreadItemCount = value;
                 }
             }
         }
         
 #endregion
 
+        private static FeedManager Manager {
+            get { return FeedsManager.Instance.FeedManager; }
+        }
+
 #region Constructors
 
-        public Feed (string url) : this ()
+        public Feed (string url, FeedAutoDownload auto_download) : this ()
         {
-            Uri uri;
-            if (String.IsNullOrEmpty (url)) {
-                throw new ArgumentException ("url:  Cannot be null or empty.");
-            } else if (/* !Uri.IsWellFormedUriString (url, UriKind.Absolute) -- this is not yet implemented in Mono */
-                !Uri.TryCreate (url, UriKind.Absolute, out uri)) {
-                throw new ArgumentException ("url:  Is not a well formed Url.");                
-            } else if (uri.Scheme != Uri.UriSchemeHttp && 
-                       uri.Scheme != Uri.UriSchemeHttps) {
-                throw new ArgumentException ("url:  Scheme must be either http or https.");                
-            }
-
-            this.url = url;
+            Url = url;
+            this.auto_download = auto_download;
         }
 
         public Feed ()
         {
-            parent = FeedsManager.Instance;
-            downloadStatus = FeedDownloadStatus.None;
-            inactive_items = new List<FeedItem> ();
-            interval = parent.DefaultInterval;
             items = new List<FeedItem> ();
         }
         
 #endregion
 
 #region Internal Methods
-
-        internal long DecrementActiveDownloadCount ()
-        {
-            return DecrementActiveDownloadCount (1);              
-        }
-        
-        internal long DecrementActiveDownloadCount (int cnt)
-        {
-            //Console.WriteLine ("pre-lock DecrementActiveDownloadCount");
-            
-            lock (sync) {     
-                //Console.WriteLine ("lock DecrementQueuedDownloadCount");                            
-                activeDownloadCount -= cnt;
-                
-                OnFeedDownloadCountChanged (
-                    FEEDS_EVENTS_DOWNLOAD_COUNT_FLAGS.FEDCF_ACTIVE_DOWNLOAD_COUNT_CHANGED
-                );
-                
-                return activeDownloadCount;                 
-            }                 
-        }        
-        
-        internal long DecrementQueuedDownloadCount ()
-        {
-            return DecrementQueuedDownloadCount (1);            
-        }
-
-        internal long DecrementQueuedDownloadCount (int cnt)
-        {
-            //Console.WriteLine ("pre-lock DecrementQueuedDownloadCount");
-            
-            lock (sync) {
-                //Console.WriteLine ("lock DecrementQueuedDownloadCount");                
-                queuedDownloadCount -= cnt;
-                
-                OnFeedDownloadCountChanged (
-                    FEEDS_EVENTS_DOWNLOAD_COUNT_FLAGS.FEDCF_QUEUED_DOWNLOAD_COUNT_CHANGED
-                );
-                
-                return queuedDownloadCount; 
-            }             
-        }
-
-        internal long IncrementActiveDownloadCount ()
-        {
-            return IncrementActiveDownloadCount (1);
-        }
-
-        internal long IncrementActiveDownloadCount (int cnt)
-        {
-            //Console.WriteLine ("pre-lock IncrementActiveDownloadCount");
-
-            lock (sync) {
-                //Console.WriteLine ("lock IncrementActiveDownloadCount");
-                activeDownloadCount += cnt;
-                
-                OnFeedDownloadCountChanged (
-                    FEEDS_EVENTS_DOWNLOAD_COUNT_FLAGS.FEDCF_ACTIVE_DOWNLOAD_COUNT_CHANGED
-                );
-                
-                return activeDownloadCount;                 
-            }
-        }
-
-        internal long IncrementQueuedDownloadCount ()
-        {
-            return IncrementQueuedDownloadCount (1);
-        }           
-
-        internal long IncrementQueuedDownloadCount (int cnt)
-        {
-            //Console.WriteLine ("pre-lock IncrementQueuedDownloadCount");
-                        
-            lock (sync) {
-                //Console.WriteLine ("lock IncrementQueuedDownloadCount");
-                queuedDownloadCount += cnt;        
-                
-                OnFeedDownloadCountChanged (
-                    FEEDS_EVENTS_DOWNLOAD_COUNT_FLAGS.FEDCF_QUEUED_DOWNLOAD_COUNT_CHANGED
-                );
-                
-                return queuedDownloadCount;                  
-            }            
-        }
-        
-        // Should ***ONLY*** be called by 'FeedUpdateTask'
-        internal bool AsyncDownloadImpl ()
-        {              
-            bool ret = false;            
-
-            lock (sync) {
-                if (!(updating || SetUpdating ())) {                
-                    return ret;                   
-                }             
-            }      
-            
-            OnFeedDownloading (); 
-            
-			try {                                                                       
-				wc = new AsyncWebClient ();                  
-				wc.Timeout = (30 * 1000); // 30 Seconds  
-				wc.IfModifiedSince = lastDownloadTime.ToUniversalTime ();
-				
-				downloadStatus = FeedDownloadStatus.Downloading;                    
-				wc.DownloadStringCompleted += OnDownloadStringCompleted;
-				wc.DownloadStringAsync (new Uri (url));
-                
-				ret = true;
-			} catch {
-                downloadStatus = FeedDownloadStatus.DownloadFailed;                                                            
-
-                if (wc != null) {
-                    wc.DownloadStringCompleted -= OnDownloadStringCompleted;
-                    wc = null;
-                }
-                
-                lock (sync) {
-                    ResetUpdating ();
-                    OnFeedDownloadCompleted (
-                        FeedDownloadError.DownloadFailed
-                    );                     
-                }  
-			}
-    
-            return ret;
-        }
-        
-        internal void SetItems (IEnumerable<FeedItem> itms)
-        {
-            if (itms == null) {
-            	throw new ArgumentNullException ("itms");
-            }
-
-            lock (sync) {
-                ClearItemsImpl ();
-                Add (itms);
-            }
-        }
-        
-        
-        internal void CancelDownload (FeedEnclosure enc) 
-        {
-            parent.CancelDownload (enc);
-        }        
-
-        internal HttpFileDownloadTask QueueDownload (FeedEnclosure enc) 
-        {
-            return parent.QueueDownload (enc);
-        }
-
-        internal void StopDownload (FeedEnclosure enc)
-        {
-            parent.StopDownload (enc);
-        }
-        
-        internal void Remove (FeedItem item)
+       
+        // Removing a FeedItem means removing the downloaded file.
+        /*public void Remove (FeedItem item)
         {
             if (item == null) {
                 throw new ArgumentNullException ("item");
             }
             
-            lock (sync) {
+           
                 if (items.Remove (item)) {
                     inactive_items.Add (item);
                     OnFeedItemRemoved (item);
-                    UpdateItemCountsImpl (-1, (!item.IsRead) ? -1 : 0);
                 }
             }
-        }
+        }*/
         
-        internal void Remove (IEnumerable<FeedItem> itms)
+        /*public void Remove (IEnumerable<FeedItem> itms)
         {
-            if (items == null) {
-                throw new ArgumentNullException ("items");
-            }
-            
-            long totalDelta = 0;            
-            long unreadDelta = 0;
-            
-            List<FeedItem> removedItems = new List<FeedItem> ();                    
-                    
-            lock (sync) {            
-                foreach (FeedItem i in itms) {
-                    if (i != null) {
-                        if (items.Remove (i)) {
-                            --totalDelta;
-                            
-                            if (!i.IsRead) {
-                                --unreadDelta;
-                            }                  
-                            
-                            removedItems.Add (i);
-                            inactive_items.Add (i);
-                        }   
-                    }
-                }
-                
                 if (removedItems.Count > 0) {
-                    OnFeedItemsRemoved (removedItems);
-                }                
-                
-                if (totalDelta != 0) {
-                    UpdateItemCountsImpl (totalDelta, unreadDelta);
-                }                
+                    OnItemsChanged ();
+                }     
             }
-        }
-        
-        internal void UpdateItemCounts (long totalDelta, long unreadDelta)
-        {
-            lock (sync) {
-                UpdateItemCountsImpl (totalDelta, unreadDelta);                    
-            }
-        }
+        }*/
         
 #endregion
-        
-        // TODO remove, unused?
-/*        
-        private void Add (FeedItem item)
-        {
-            Add (item, false);
-        }
-
-        private void Add (FeedItem item, bool commit)
-        {
-            if (item == null) {
-                throw new ArgumentNullException ("item");
-            }
-            
-            item.Parent = this;            
-            
-            if (commit) {
-                item.Save ();
-            }
-                        
-            if (item.LocalID == -1) {
-                return;
-            } else if (item.Active) {                                             
-                items.Add (item);
-                itemsByID.Add (item.LocalID, item);
-                
-                OnFeedItemAdded (item);
-                UpdateItemCountsImpl (1, (!item.IsRead) ? 1 : 0);
-            } else {
-                inactiveItems.Add (item);                   
-            }
-        }
-*/        
 
 #region Private Methods
-
-        private void LoadItems ()
-        {
-            if (DbId > 0 && !items_loaded) {
-                Console.WriteLine ("Loading items");
-                IEnumerable<FeedItem> items = FeedItem.Provider.FetchAllMatching (String.Format (
-                    "{0}.FeedID = {1}", FeedItem.Provider.TableName, DbId
-                ));
-                
-                foreach (FeedItem item in items) {
-                    item.Feed = this;
-                    item.LoadEnclosure ();
-                }
-                
-                this.items.AddRange (items);
-                Console.WriteLine ("Done loading items");
-                items_loaded = true;
-            }
-        }
-
-        private void Add (IEnumerable<FeedItem> itms)
-        {
-            Add (itms, false);
-        }
         
-        private void Add (IEnumerable<FeedItem> itms, bool commit)
+        public void SetItems (IEnumerable<FeedItem> items)
         {
-            if (items == null) {
-                throw new ArgumentNullException ("itms");
-            }      
-            
-            long totalCountDelta = 0;
-            long unreadCountDelta = 0;
-            
-            List<FeedItem> newItems = new List<FeedItem> ();
-            
-            if (commit) {
-                foreach (FeedItem item in itms)
-                    item.Save ();
+            bool added_any = false;
+            foreach (FeedItem item in items) {
+                added_any |= AddItem (item);
             }
             
-            foreach (FeedItem i in itms) {
-                i.Feed = this;             
-
-                if (i.DbId == -1) {
-                    continue;
-                } else if (i.Active) {
-                    ++totalCountDelta;
-                            
-                    if (!i.IsRead) {
-                        ++unreadCountDelta;
-                    }
-                    
-                    items.Add (i); 
-                    newItems.Add (i);
-                } else {
-                    inactive_items.Add (i);   
-                }
+            if (added_any) {
+               CheckForItemsToDownload ();
             }
-
-            if (newItems.Count > 0) {
-                OnFeedItemsAdded (newItems);
-            }
-            
-            UpdateItemCountsImpl (totalCountDelta, unreadCountDelta);
         }
         
-        private void ClearItemsImpl ()
+        private bool AddItem (FeedItem item)
         {
-            items.Clear ();
-            UnreadItemCount = 0;            
-        }
-        
-        private void Update (IEnumerable<FeedItem> new_items, bool init)
-        {
-            if (init) {
-                SetItems (new_items);
-            } else {
-                UpdateItems (new_items);         
+            if (!FeedItem.Exists (item.Guid)) {
+                item.Feed = this;
+                item.Save ();
+                return true;
             }
+            return false;
         }
         
-        private void UpdateItems (IEnumerable<FeedItem> new_items)
+        /*private void UpdateItems (IEnumerable<FeedItem> new_items)
         {
             ICollection<FeedItem> tmpNew = null;         
             List<FeedItem> zombies = new List<FeedItem> ();
@@ -808,12 +425,13 @@
             }
             
             if (tmpNew.Count > 0) {
-                Add (tmpNew, true);                
+                Add (tmpNew);                
             }
             
+            // TODO merge...should we really be deleting these items?
             if (zombies.Count > 0) {
-                foreach (FeedItem zombie in zombies) {
-                    if (zombie.Active) {
+                foreach (FeedItem item in zombies) {
+                    if (item.Active) {
                         zombie.Delete ();                        
                     } 
                 }
@@ -846,228 +464,45 @@
             }
 
             return diff;
-        }
-        
-        private void UpdateItemCountsImpl (long totalDelta, long unreadDelta)
-        {
-            //ItemCount += totalDelta;                
-            UnreadItemCount += unreadDelta;
-            FEEDS_EVENTS_ITEM_COUNT_FLAGS flags = 0;
-            
-            if (totalDelta != 0) {   
-                flags |= FEEDS_EVENTS_ITEM_COUNT_FLAGS.FEICF_TOTAL_ITEM_COUNT_CHANGED;                  
-            }             
-
-            if (unreadDelta != 0) {
-                flags |= FEEDS_EVENTS_ITEM_COUNT_FLAGS.FEICF_UNREAD_ITEM_COUNT_CHANGED;
-            }
-                    
-            if (flags != 0) {
-                OnFeedItemCountChanged (flags);  
-            }             
-        }
-        
-        private bool SetDeleted ()
-        {
-            bool ret = false;
-            
-            if (!deleted) {
-                ret = deleted = true;
-            }
-            
-            return ret;
-        }
-        
-        private bool SetUpdating ()
-        {
-            bool ret = false;
-            
-            if (!updating && !deleted) {
-                updatingHandle.Reset ();
-                ret = updating = true;
-            }
-            
-            return ret;
-        }        
-        
-        private bool ResetUpdating ()
-        {
-            bool ret = false;
-            
-            if (updating) {
-                updating = false;
-                ret = true;
-                updatingHandle.Set ();                
-            }
-            
-            return ret;    
-        }
+        }*/
         
 #endregion
 
 #region Public Methods
-        
-        public void AsyncDownload ()
+
+        public void Update ()
         {
-            bool update = false;
-            
-            lock (sync) {
-                if (SetUpdating ()) {
-                    update = true;
-                    downloadStatus = FeedDownloadStatus.Pending;
-                }
-            }            
-            
-            if (update) {
-                parent.QueueUpdate (this);                
-            }
+            Manager.QueueUpdate (this);
         }
-        
-        public bool CancelAsyncDownload ()
-        {
-            bool ret = false;            
-            
-            lock (sync) {
-                if (SetCanceled ()) {                    
-                    if (updating && wc != null) {
-                        ret = true;                         
-                        wc.CancelAsync ();
-                    } else {
-                        ret = true;
-                        ResetUpdating ();                    
-                        OnFeedDownloadCompleted (FeedDownloadError.Canceled);                                            
-                    }
-                }
-            }
-            
-            return ret;
-        }
-        
-        public int CompareTo (Feed right)
-        {
-            return title.CompareTo (right.Title);
-        }        
-        
+
         public void Delete ()
         {
-            Delete (true);                    
+            Delete (true);
+            Manager.OnFeedsChanged ();                    
         }
             
         public void Delete (bool deleteEnclosures)
         {
-            bool del = false;            
-            
             lock (sync) {
-                if (SetDeleted ()) {                
-                    if (updating) {
-                        CancelAsyncDownload ();                      
-                    }
-
-                    FeedItem[] itms = items.ToArray ();
-                    
-                    foreach (FeedItem i in itms) {
-                        i.DeleteImpl (false, deleteEnclosures);
-                    }
-
-                    Remove (itms);                    
-                  
-                    Provider.Delete (this);   
-                    del = true;
-                }
-            }
-            
-            if (del) {
-                updatingHandle.WaitOne ();
+                if (deleted)
+                    return;
                 
-                if (deleteEnclosures) {
-                	try {
-                        FileAttributes attributes;
-                        string[] files = Directory.GetFileSystemEntries (localEnclosurePath);
-                        
-                        foreach (string file in files) {
-                            try {                            
-                                attributes = File.GetAttributes (file) | FileAttributes.ReadOnly;
-                                
-                                if (attributes == FileAttributes.ReadOnly) {
-                                    File.Delete (file);                                
-                                }
-                            } catch { continue; }
-                        }
-
-                        Directory.Delete (localEnclosurePath, false);
-                    } catch {}
+                if (updating) {
+                    Manager.CancelUpdate (this);                 
                 }
-                
-                OnFeedDeleted ();            
-            }
-        }
-        
-        public void Delete (FeedItem item)
-        {
-            Delete (item, true);
-        }
-        
-        public void Delete (FeedItem item, bool deleteEncFile)
-        {
-            if (item == null) {
-                throw new ArgumentNullException ("item");     
-            }    
-            
-            FeedItem feedItem = item as FeedItem;
-            
-            if (feedItem != null && feedItem.Feed == this) {
-                feedItem.Delete (deleteEncFile);
-            }
-        }        
-        
-        public void Delete (IEnumerable<FeedItem> items)
-        {
-            Delete (items, true);
-        }
-        
-        public void Delete (IEnumerable<FeedItem> items, bool deleteEncFiles)
-        {
-            if (items == null) {
-                throw new ArgumentNullException ("items");     
-            }            
-            
-            FeedItem tmpItem;            
-            List<FeedItem> deletedItems = new List<FeedItem> ();
-            
-            lock (sync) {
-                foreach (FeedItem item in items) {
-                    tmpItem = item as FeedItem;
-
-                    if (tmpItem != null && tmpItem.Feed == this) {
-                        tmpItem.DeleteImpl (false, deleteEncFiles);
-                        deletedItems.Add (tmpItem);
 
-                        tmpItem.Active = false;
-                        tmpItem.Save ();
-                    }
+                foreach (FeedItem item in items) {
+                    item.Delete (deleteEnclosures);
                 }
                 
-                if (deletedItems.Count > 0) {
-                    Remove (deletedItems);
-                }
+                //items_dirty = true;
+                Provider.Delete (this);
             }
-        }        
-        
-        public void Download ()
-        {
-            throw new NotImplementedException ("Download");
+            
+            //updatingHandle.WaitOne ();
+            Manager.OnFeedsChanged ();
         }
 
-        // TODO remove, unused
-        /*public FeedItem GetItem (long itemID)
-        {
-            lock (sync) { 
-                FeedItem item;
-                item_id_map.TryGetValue (itemID, out item);
-                return item as FeedItem;
-            }
-        }*/
-
         public void MarkAllItemsRead ()
         {
             lock (sync) {
@@ -1079,269 +514,55 @@
 
         public override string ToString ()
         {
-            return String.Format (
-                "Title:  {0} - Url:  {1}",
-                Title, Url                                  
-            );   
+            return String.Format ("Title:  {0} - Url:  {1}", Title, Url);   
         }
 
         public void Save ()
         {
             Provider.Save (this);
-        }   
-
-        private bool SetCanceled ()
-        {
-            bool ret = false;
             
-            if (!canceled && updating) {
-                ret = canceled = true;
-            }
-            
-            return ret;
-        }
-        
-#endregion
-
-#region Private Event Handlers
-        
-        // Wow, this sucks, see the header FeedsManager Header. 
-        private void OnDownloadStringCompleted (object sender, 
-                                                Migo.Net.DownloadStringCompletedEventArgs args) 
-        {
-            FeedDownloadError error = FeedDownloadError.None;            
-
-            try {
-                lock (sync) {                              
-                    try { 
-                        if (args.Error != null) {
-                            error = FeedDownloadError.DownloadFailed;                                                        
-                            
-                            WebException we = args.Error as WebException;   
-
-                            if (we != null) {
-                                HttpWebResponse resp = we.Response as HttpWebResponse;
-                                
-                                if (resp != null) {
-                                    switch (resp.StatusCode) {
-                                        case HttpStatusCode.NotFound:
-                                        case HttpStatusCode.Gone:
-                                            error = FeedDownloadError.DoesNotExist;
-                                            break;                                
-                                        case HttpStatusCode.NotModified:
-                                            error = FeedDownloadError.None;                                                        
-                                            break;
-                                        case HttpStatusCode.Unauthorized:
-                                            error = FeedDownloadError.UnsupportedAuth;
-                                            break;                                
-                                        default:
-                                            error = FeedDownloadError.DownloadFailed;
-                                            break;
-                                    }
-                                }
-                            }
-                        } else if (canceled | deleted) {
-                            error = FeedDownloadError.Canceled;                        
-                        } else {
-                            try {
-                                RssParser parser = new RssParser (Url, args.Result);
-                                parser.UpdateFeed (this);
-                                Update (parser.GetFeedItems (), false);
-                            } catch (FormatException e) {
-                                Log.Exception (e);
-                                error = FeedDownloadError.InvalidFeedFormat;
-                            }                          
-                        }                        
-                    } catch (Exception e) {
-                        //Console.WriteLine ("Update error");
-                        Console.WriteLine (e.Message);
-                        Console.WriteLine (e.StackTrace);                        
-                        throw;
-                    } finally {  
-                        canceled = updating = false;
-                        wc.DownloadStringCompleted -= OnDownloadStringCompleted;
-                        wc = null; 
-                    }
-                } 
-            } finally {
-                try {
-                    lastDownloadError = error;
-                                    
-                    if (lastDownloadError == FeedDownloadError.None) {
-                        downloadStatus = FeedDownloadStatus.Downloaded;                         
-                    } else {
-                        downloadStatus = FeedDownloadStatus.DownloadFailed;                                                    
-                    }
-                        
-                    Save ();
-                    
-                    OnFeedDownloadCompleted (error);
-                } finally {                
-                    updatingHandle.Set ();
-                }
+            if (LastBuildDate > LastAutoDownload) {
+                CheckForItemsToDownload ();
             }
-        }
 
-/*      May add support for individual add in the future, but right now IEnumerables work           
-        private void OnFeedItemAdded (FeedItem item)
-        {
-            if (item == null) {
-                throw new ArgumentNullException ("item");
-            }
-            
-            parent.RegisterCommand (new CommandWrapper (delegate { 
-                EventHandler<FeedItemEventArgs> handler = FeedItemAdded;
-                
-                parent.OnFeedItemAdded (this, item);
-                
-                if (handler != null) {
-                    OnFeedItemEvent (handler, new FeedItemEventArgs (this, item));
-                }                          
-            }));  
+            Manager.OnFeedsChanged ();
         }
-*/        
-
-        private void OnFeedItemsAdded (IEnumerable<FeedItem> items)
-        {
-            if (items == null) {
-                throw new ArgumentNullException ("items");
-            }        
-
-            parent.RegisterCommand (new CommandWrapper (delegate { 
-                EventHandler<FeedItemEventArgs> handler = FeedItemAdded;
-                
-                parent.OnFeedItemsAdded (this, items);
-                
-                if (handler != null) {
-                    OnFeedItemEvent (handler, new FeedItemEventArgs (this, items));
-                }                          
-            }));               
-        }    
         
-        private void OnFeedItemRemoved (FeedItem item)
+        private void CheckForItemsToDownload ()
         {
-            if (item == null) {
-                throw new ArgumentNullException ("item");
-            }   
-
-            parent.RegisterCommand (new CommandWrapper (delegate { 
-                EventHandler<FeedItemEventArgs> handler = FeedItemRemoved;
-                
-                parent.OnFeedItemRemoved (this, item);
+            if (LastDownloadError != FeedDownloadError.None || AutoDownload == FeedAutoDownload.None)
+                return;
                 
-                if (handler != null) {
-                    OnFeedItemEvent (handler, new FeedItemEventArgs (this, item));
-                }                          
-            }));                        
-        }
-        
-        private void OnFeedItemsRemoved (IEnumerable<FeedItem> items)
-        {
-            if (items == null) {
-                throw new ArgumentNullException ("items");
-            }        
+            bool only_first = (AutoDownload == FeedAutoDownload.One);
             
-            parent.RegisterCommand (new CommandWrapper (delegate { 
-                EventHandler<FeedItemEventArgs> handler = FeedItemRemoved;
-                
-                parent.OnFeedItemsRemoved (this, items);
-                
-                if (handler != null) {
-                    OnFeedItemEvent (handler, new FeedItemEventArgs (this, items));
-                }                          
-            }));
-        }        
-        
-        private void OnFeedItemEvent (EventHandler<FeedItemEventArgs> handler,  
-                                                             FeedItemEventArgs e)
-        {
-            handler (this, e);            
-        }
-        
-        private void OnFeedDeleted ()
-        {
-            parent.RegisterCommand (new CommandWrapper (delegate { 
-                parent.OnFeedDeleted (this);            
-                OnFeedEventRaised (FeedDeleted);                
-            }));            
-        }
-
-        private void OnFeedDownloadCountChanged (FEEDS_EVENTS_DOWNLOAD_COUNT_FLAGS flags)
-        {             
-            parent.RegisterCommand (new CommandWrapper (delegate { 
-                EventHandler<FeedDownloadCountChangedEventArgs> handler = FeedDownloadCountChanged;            
-                
-                parent.OnFeedDownloadCountChanged (this, flags);              
-                
-                if (handler != null) {
-                    handler (
-                        this, new FeedDownloadCountChangedEventArgs (this, flags)
-                    );
+            bool any = false;
+            foreach (FeedItem item in Items) {
+                if (item.Active && item.Enclosure.DownloadStatus != FeedDownloadStatus.Downloaded && item.PubDate > LastAutoDownload) {
+                    item.Enclosure.AsyncDownload ();
+                    any = true;
+                    if (only_first)
+                        break;
                 }
-            }));
-        }        
-        
-        private void OnFeedDownloadCompleted (FeedDownloadError error)
-        {
-            parent.RegisterCommand (new CommandWrapper (delegate { 
-                EventHandler<FeedDownloadCompletedEventArgs> handler = FeedDownloadCompleted;                
-                
-                parent.OnFeedDownloadCompleted (this, error);
-                
-                if (handler != null) {
-                    handler (this, new FeedDownloadCompletedEventArgs (this, error));
-                }                
-            }));
-        }
-        
-        private void OnFeedDownloading ()
-        {
-            parent.RegisterCommand (new CommandWrapper (delegate { 
-                parent.OnFeedDownloading (this);            
-                OnFeedEventRaised (FeedDownloading);                
-            }));
-        }
-        
-        private void OnFeedItemCountChanged (FEEDS_EVENTS_ITEM_COUNT_FLAGS flags)
-        {
-            parent.RegisterCommand (new CommandWrapper (delegate {
-                EventHandler<FeedItemCountChangedEventArgs> handler = FeedItemCountChanged;
-                
-                parent.OnFeedItemCountChanged (this, flags);
-                            
-                if (handler != null) {
-                    handler (this, new FeedItemCountChangedEventArgs (this, flags));          
-                }                 
-            }));
-        }                
-                
-        private void OnFeedRenamed ()
-        {
-            parent.RegisterCommand (new CommandWrapper (delegate { 
-                parent.OnFeedRenamed (this);            
-                OnFeedEventRaised (FeedRenamed);
-            }));
-        }
-        
-        private void OnFeedUrlChanged ()
-        {            
-            parent.RegisterCommand (new CommandWrapper (delegate {
-                parent.OnFeedUrlChanged (this);                    
-                OnFeedEventRaised (FeedUrlChanged);
-            }));
+            }
+            
+            if (any) {
+                LastAutoDownload = DateTime.Now;
+                Save ();
+            }
         }
 
-        // Drr, isn't handler a copy already?  Do something?
-        private void OnFeedEventRaised (EventHandler<FeedEventArgs> handler)
+        /*private bool SetCanceled ()
         {
-            EventHandler<FeedEventArgs> handlerCpy = handler;
+            bool ret = false;
             
-            if (handlerCpy != null) {
-                handlerCpy (this, new FeedEventArgs (this));
+            if (!canceled && updating) {
+                ret = canceled = true;
             }
-        }
-
-#endregion        
+            
+            return ret;
+        }*/
+        
+#endregion  
 
     }
 }    

Modified: trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedEnclosure.cs
==============================================================================
--- trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedEnclosure.cs	(original)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedEnclosure.cs	Sun May 18 00:19:17 2008
@@ -36,27 +36,25 @@
 
 namespace Migo.Syndication
 {
-    public class FeedEnclosure
+    public class FeedEnclosure : MigoItem<FeedEnclosure>
     {
         private static SqliteModelProvider<FeedEnclosure> provider;
         public static SqliteModelProvider<FeedEnclosure> Provider {
-            get { return provider; }
-            set { provider = value; }
+            get { return provider ?? provider = new MigoModelProvider<FeedEnclosure> (FeedsManager.Instance.Connection, "PodcastEnclosures"); }
         }
         
-        private bool canceled;
-        private bool downloading;
-        private bool stopped;
+        public static void Init () {}
         
         private string mimetype;
         private FeedDownloadStatus download_status;        
-        private bool active;
         private FeedDownloadError last_download_error;
-        private long length;
+        private long file_size;
+        private TimeSpan duration;
+        private string keywords;
         
         private string local_path;
         private FeedItem item;
-        private string url;     
+        private string url;
         
         private readonly object sync = new object ();
         
@@ -70,30 +68,6 @@
 
 #region Public Properties
 
-        public FeedDownloadStatus DownloadStatus { 
-            get { 
-                lock (sync) {
-                    return download_status;
-                }
-            }
-            
-            internal set { 
-                lock (sync) {
-                    download_status = value;
-                    //Console.WriteLine ("Enclosure:  DownloadStatus:  {0}", downloadStatus);
-                    switch (value) {
-                    case FeedDownloadStatus.DownloadFailed: goto case FeedDownloadStatus.None;
-                    case FeedDownloadStatus.Downloaded: 
-                        Save ();
-                        goto case FeedDownloadStatus.None;
-                    case FeedDownloadStatus.None:
-                        ResetDownloading ();
-                        break;
-                    }
-                }
-            }             
-        }
-
         public FeedItem Item { 
             get { return item; } 
             internal set {          
@@ -101,111 +75,197 @@
                 	throw new ArgumentNullException ("Parent");
                 }
 
-                item = value as FeedItem;
-                
-                if (item == null) {
-                    throw new ArgumentException (
-                        "Parent must be of type FeedItem"
-                    );
-                }
+                item = value;
+                item_id = value.DbId;
             }
         }
 
 #endregion
 
+        public static EnclosureManager Manager {
+            get { return FeedsManager.Instance.EnclosureManager; }
+        }
+
+        public FeedDownloadError LastDownloadError {
+            get { return last_download_error; }
+            internal set { last_download_error = value; }
+        }
+
 #region Public Methods
         
-        public void Save ()
+        public void Save (bool save_item)
         {
             Provider.Save (this);
+            
+            if (save_item)
+                Item.Save ();
         }
         
-        public void AsyncDownload ()
-        {            
-            if (SetDownloading ()) {
-                if (!item.QueueDownload (this)) {
-                    ResetDownloading ();
-                }
+        public void Save ()
+        {
+            Save (true);
+        }
+        
+        public void Delete (bool and_delete_file)
+        {
+            if (and_delete_file) {
+                DeleteFile ();
             }
+            Provider.Delete (this);
+        }
+        
+        public void AsyncDownload ()
+        {
+            Manager.QueueDownload (this);
         }
         
         public void CancelAsyncDownload ()
         {
-            if (SetCanceled ()) {
-                CancelAsyncDownloadImpl ();
-            }
+            Manager.CancelDownload (this);
         }
 
         public void StopAsyncDownload ()
         {
-            if (SetStopped ()) {
-                item.StopDownload (this);
-            }
+            Manager.StopDownload (this);
         }
         
         // Deletes file associated with enclosure.
         // Does not cancel an active download like the WRP.
-        public void RemoveFile ()
+        public void DeleteFile ()
         {
-            lock (sync) {                
-                CheckActive ();            
-                
+            lock (sync) {
                 if (!String.IsNullOrEmpty (local_path) && File.Exists (local_path)) {
-                	try {
-                        FileAttributes attributes = 
-                                File.GetAttributes (local_path) | FileAttributes.ReadOnly;
-
-                        if (attributes == FileAttributes.ReadOnly) {
-                            File.Delete (local_path);	
-                        }
-                        
+                    try {
+                        File.Delete (local_path);
                         Directory.Delete (Path.GetDirectoryName (local_path));
-                	} catch {}
+                    } catch {}
                 }
                 
-                local_path = String.Empty;
-                download_status = FeedDownloadStatus.None;                                
+                LocalPath = null;
+                DownloadStatus = FeedDownloadStatus.None;
+                LastDownloadError = FeedDownloadError.None;
+                Save ();
+            }
+        }
+
+
+        public void SetFileImpl (string url, string path, string mimeType, string filename)
+        {
+            string tmpLocalPath;
+            string fullPath = path;
+            string localEnclosurePath = Item.Feed.LocalEnclosurePath;
+            
+            if (!localEnclosurePath.EndsWith (Path.DirectorySeparatorChar.ToString ())) {
+                localEnclosurePath += Path.DirectorySeparatorChar;
+            }
+            
+            if (!fullPath.EndsWith (Path.DirectorySeparatorChar.ToString ())) {
+                fullPath += Path.DirectorySeparatorChar;
+            }           
+            
+            fullPath += filename;
+            tmpLocalPath = localEnclosurePath+filename;            
+
+            try {
+                if (!Directory.Exists (path)) {
+                	throw new InvalidOperationException ("Directory specified by path does not exist");            	
+                } else if (!File.Exists (fullPath)) {
+                	throw new InvalidOperationException (
+                	    String.Format ("File:  {0}, does not exist", fullPath)
+                	);
+                }
+
+                if (!Directory.Exists (localEnclosurePath)) {
+                	Directory.CreateDirectory (localEnclosurePath);
+                }
+
+                if (File.Exists (tmpLocalPath)) {
+                    int lastDot = tmpLocalPath.LastIndexOf (".");
+                    
+                    if (lastDot == -1) {
+                        lastDot = tmpLocalPath.Length-1;
+                    }
+                    
+                    string rep = String.Format (
+                        "-{0}", 
+                        Guid.NewGuid ().ToString ()
+                                       .Replace ("-", String.Empty)
+                                       .ToLower ()
+                    );
+                    
+                    tmpLocalPath = tmpLocalPath.Insert (lastDot, rep);
+                }
+            
+                File.Move (fullPath, tmpLocalPath);
+                File.SetAttributes (tmpLocalPath, FileAttributes.ReadOnly);
                 
+                try {
+                    Directory.Delete (path);
+                } catch {}
+            } catch {
+                LastDownloadError = FeedDownloadError.DownloadFailed;
+                DownloadStatus = FeedDownloadStatus.DownloadFailed;
                 Save ();
+                throw;
             }
+
+            LocalPath = tmpLocalPath;
+            Url = url;
+            MimeType = mimeType;
+                            
+            DownloadStatus = FeedDownloadStatus.Downloaded;
+            LastDownloadError = FeedDownloadError.None;
+            Save ();
         }
         
 #endregion
 
 #region Database Columns
-
-        [DatabaseColumn]
-        public long Length { 
-            get { lock (sync) { return length; } } 
-            set { length = value; }
-        }
         
-        [DatabaseColumn ("EnclosureID", Constraints = DatabaseColumnConstraints.PrimaryKey)]
         private long dbid;
-        public long DbId { 
+        [DatabaseColumn ("EnclosureID", Constraints = DatabaseColumnConstraints.PrimaryKey)]
+        public override long DbId { 
             get { return dbid; }
-            internal set { dbid = value; }
+            protected set { dbid = value; }
         }
         
-        [DatabaseColumn ("ParentID")]
-        private long parent_id;
-        public long ParentId {
-            get { return parent_id; }
+        [DatabaseColumn ("ItemID")]
+        protected long item_id;
+        public long ItemId {
+            get { return item_id; }
         }
         
         [DatabaseColumn]
         public string LocalPath { 
-            get { lock (sync) { return local_path; } }
+            get { return local_path; }
             set { local_path = value; }
         }
         
         [DatabaseColumn]
         public string Url { 
-            get { lock (sync) { return url; } } 
+            get { return url; } 
             set { url = value; }
         }
         
         [DatabaseColumn]
+        public string Keywords { 
+            get { return keywords; } 
+            set { keywords = value; }
+        }
+        
+        [DatabaseColumn]
+        public TimeSpan Duration { 
+            get { return duration; } 
+            set { duration = value; }
+        }
+        
+        [DatabaseColumn]
+        public long FileSize { 
+            get { return file_size; } 
+            set { file_size = value; }
+        }
+        
+        [DatabaseColumn]
         public string MimeType {
             get { return mimetype; }
             set {
@@ -216,163 +276,29 @@
             }
         }
         
+        private DateTime downloaded_at;
         [DatabaseColumn]
-        public FeedDownloadError LastDownloadError {
-            get { lock (sync) { return last_download_error;  } }
-            internal set { lock (sync) { last_download_error = value; } }
+        public DateTime DownloadedAt {
+            get { return downloaded_at; }
+            internal set { downloaded_at = value; }
         }
         
         [DatabaseColumn]
-        internal bool Active {
-            get { lock (sync) { return active; } }
-            set { lock (sync) { active = value; } }
-        }
-
-#endregion
-
-        private void CheckActive ()
-        {
-            if (!active) {
-                throw new InvalidOperationException ("Enclosure previously deleted");                    
-            }
-        }
-
-        internal void ResetDownloading ()
-        {
-            lock (sync) {
-                if (downloading) {
-                    canceled = 
-                    downloading = 
-                    stopped = false;
+        public FeedDownloadStatus DownloadStatus { 
+            get {
+                lock (sync) {
+                    return download_status;
                 }
             }
-        }
-        
-        internal bool SetCanceled ()
-        {
-            bool ret = false;
-            
-            lock (sync) {
-                //Console.WriteLine ("Status - SetCanceled:  canceled:  {0} - downloading:  {1}", canceled, downloading);
-                if (!canceled && !stopped && downloading) {
-                    ret = canceled = true;
-                    last_download_error = FeedDownloadError.Canceled;
-                }   
-            }
-                //Console.WriteLine ("Status - SetCanceled:  ret:  {0}", ret);
-            return ret;
-        }
-        
-        internal bool SetDownloading ()
-        {
-            bool ret = false;
-            
-            lock (sync) {
-                if (!downloading && 
-                    download_status != FeedDownloadStatus.Downloaded) {
-                    canceled = false;
-                    stopped = false;
-                    ret = downloading = true;    
-                    
-                    download_status = FeedDownloadStatus.Pending;                    
-                    last_download_error = FeedDownloadError.None;
-                }            
-            }
-            
-            return ret;
-        }
-
-        internal bool SetStopped () 
-        {
-            bool ret = false;
             
-            lock (sync) {
-                if (!canceled && !stopped && downloading) {
-                    ret = stopped = true;
-                    last_download_error = FeedDownloadError.Canceled;
-                }            
+            internal set { 
+                lock (sync) {
+                    download_status = value;
+                }
             }
-            
-            return ret;            
-        }
-        
-        private void CancelAsyncDownloadImpl ()      
-        {
-            item.CancelDownload (this);            
         }
 
-        internal void SetFileImpl (string url, string path, string mimeType, string filename)
-        {      
-            string tmpLocalPath;
-            string fullPath = path;
-            string localEnclosurePath = item.Feed.LocalEnclosurePath;
-            
-            lock (sync) {   
-                CheckActive ();                
-                
-                if (!localEnclosurePath.EndsWith (Path.DirectorySeparatorChar.ToString ())) {
-                    localEnclosurePath += Path.DirectorySeparatorChar;
-                }
-                
-                if (!fullPath.EndsWith (Path.DirectorySeparatorChar.ToString ())) {
-                    fullPath += Path.DirectorySeparatorChar;
-                }           
-                
-                fullPath += filename;
-                tmpLocalPath = localEnclosurePath+filename;            
-
-                try {
-                    if (!Directory.Exists (path)) {
-                    	throw new InvalidOperationException ("Directory specified by path does not exist");            	
-                    } else if (!File.Exists (fullPath)) {
-                    	throw new InvalidOperationException (
-                    	    String.Format ("File:  {0}, does not exist", fullPath)
-                    	);
-                    }
-
-                    if (!Directory.Exists (localEnclosurePath)) {
-                    	Directory.CreateDirectory (localEnclosurePath);
-                    }
-
-                    if (File.Exists (tmpLocalPath)) {
-                        int lastDot = tmpLocalPath.LastIndexOf (".");
-                        
-                        if (lastDot == -1) {
-                            lastDot = tmpLocalPath.Length-1;
-                        }
-                        
-                        string rep = String.Format (
-                            "-{0}", 
-                            Guid.NewGuid ().ToString ()
-                                           .Replace ("-", String.Empty)
-                                           .ToLower ()
-                        );
-                        
-                        tmpLocalPath = tmpLocalPath.Insert (lastDot, rep);
-                    }
-                
-                    File.Move (fullPath, tmpLocalPath);
-                    File.SetAttributes (tmpLocalPath, FileAttributes.ReadOnly);
-                    
-                    try {
-                        Directory.Delete (path);
-                    } catch {}
-                } catch { 
-                    last_download_error = FeedDownloadError.DownloadFailed;
-                    download_status = FeedDownloadStatus.DownloadFailed;
-                    throw;
-                }
-                
-                local_path = tmpLocalPath;
-                
-                this.url = url;
-                this.mimetype = mimeType;
-                                
-                download_status = FeedDownloadStatus.Downloaded;
-                last_download_error = FeedDownloadError.None;                    
+#endregion
 
-                Save ();
-            }
-        }
     }
 }

Modified: trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedItem.cs
==============================================================================
--- trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedItem.cs	(original)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedItem.cs	Sun May 18 00:19:17 2008
@@ -36,22 +36,27 @@
 
 namespace Migo.Syndication
 {
-    public class FeedItem
+    public class FeedItem : MigoItem<FeedItem>
     {
         private static SqliteModelProvider<FeedItem> provider;
         public static SqliteModelProvider<FeedItem> Provider {
-            get { return provider; }
-            set { provider = value; }
+            get { return provider ?? provider = new MigoModelProvider<FeedItem> (FeedsManager.Instance.Connection, "PodcastItems"); }
         }
+        
+        public static bool Exists (string guid)
+        {
+            return Provider.Connection.Query<int> (String.Format ("select count(*) from {0} where Guid = ?", Provider.TableName), guid) != 0;
+        }
+        
+        public static void Init () {}
 
-        private bool active;
+        private bool active = true;
         private string author;
         private string comments;
         private string description;
         private FeedEnclosure enclosure;
         private string guid;
         private bool isRead;
-        private DateTime lastDownloadTime;  
         private string link;
         private long dbid;
         private DateTime modified;     
@@ -59,241 +64,174 @@
         private DateTime pubDate;       
         private string title;        
         
-        public readonly object sync = new object ();
+        public event Action<FeedItem> ItemAdded;
+        public event Action<FeedItem> ItemChanged;
+        public event Action<FeedItem> ItemRemoved;
 
 #region Database-backed Properties
-                
+
+        [DatabaseColumn ("ItemID", Constraints = DatabaseColumnConstraints.PrimaryKey)]
+        public override long DbId {
+            get { return dbid; }
+            protected set { dbid = value; }
+        }
+
+        [DatabaseColumn("FeedID")]
+        protected long feed_id;
+        public long FeedId {
+            get { return feed_id; }
+        }
+
         [DatabaseColumn]
         internal bool Active {
-            get { lock (sync) { return active; } }
-            set { 
-                lock (sync) {                
-                    if (value != active) {
-                        active = value;
-                        if (enclosure != null) {
-                            enclosure.Active = value;
-                        }
-                    }
+            get { return active;}
+            set {          
+                if (value != active) {
+                    active = value;
                 }
             }
         }
         
         [DatabaseColumn]
         public string Author {
-            get { lock (sync) { return author; } }
+            get { return author; }
             set { author = value; }
         }
         
         [DatabaseColumn]
         public string Comments {
-            get { lock (sync) { return comments; } } 
+            get { return comments; } 
             set { comments = value; }
         }
         
         [DatabaseColumn]
         public string Description {
-            get { lock (sync) { return description; }} 
+            get { return description; }
             set { description = value; }
         }
         
         [DatabaseColumn]
         public string Guid {
-            get { lock (sync) { return guid; } }
+            get { return guid; }
             set { guid = value; }
         }
 
         [DatabaseColumn]
         public bool IsRead {
-            get { lock (sync) { return isRead; } }
-            set { 
-                int delta = 0;                
-                
-                lock (sync) {                    
-                    if (isRead != value) {
-                        isRead = value;
-                        delta = value ? -1 : 1;    
-                        
-                        if (feed == null) {
-                            return;
-                        }
-                                
-                        Save ();
-                    }
-                }
-                
-                if (delta != 0) {
-                    feed.UpdateItemCounts (0, delta);                            
-                }                                            
+            get { return isRead; }
+            set {          
+                if (isRead != value) {
+                    isRead = value;
+                    Save ();
+                }                                         
             }
         }
         
         [DatabaseColumn]
-        public DateTime LastDownloadTime {
-            get { lock (sync) { return lastDownloadTime; } } 
-            set { lastDownloadTime = value; }
-        }  
-        
-        [DatabaseColumn]
         public string Link {
-            get { lock (sync) { return link; } }
+            get { return link; }
             set { link = value; }
         }
         
-        [DatabaseColumn ("ItemID", Constraints = DatabaseColumnConstraints.PrimaryKey)]
-        public long DbId {
-            get { lock (sync) { return dbid; } }
-            internal set { 
-                lock (sync) { 
-                    dbid = value; 
-                }
-            }
-        }
-        
         [DatabaseColumn]
         public DateTime Modified { 
-            get { lock (sync) { return modified; } } 
+            get { return modified; } 
             set { modified = value; }
         }      
         
         [DatabaseColumn]
         public DateTime PubDate {
-            get { lock (sync) { return pubDate; } } 
+            get { return pubDate; } 
             set { pubDate = value; }
         }       
         
         [DatabaseColumn]
         public string Title {
-            get { lock (sync) { return title; } } 
+            get { return title; } 
             set { title = value; }
         }
 
 #endregion
 
+#region Properties
+
         public Feed Feed {
-            get { lock (sync) { return feed; } }
-            
-            internal set {
-                if (value == null) {
-                    throw new ArgumentNullException ("Feed");
-                }
-                
-                lock (sync) {  
-                    feed = value;
+            get {
+                if (feed == null && feed_id > 0) {
+                    feed = Feed.Provider.FetchSingle (feed_id);
                 }
+                return feed;
             }
-        }
-        
-        private bool enclosure_loaded;
-        public void LoadEnclosure ()
-        {
-            if (!enclosure_loaded && DbId > 0) {
-                Console.WriteLine ("Loading item enclosures");
-                IEnumerable<FeedEnclosure> enclosures = FeedEnclosure.Provider.FetchAllMatching (String.Format (
-                    "{0}.ItemID = {1}", FeedEnclosure.Provider.TableName, DbId
-                ));
-                
-                foreach (FeedEnclosure enclosure in enclosures) {
-                    enclosure.Item = this;
-                    this.enclosure = enclosure;
-                    break; // should only have one
-                }
-                Console.WriteLine ("Done loading item enclosures");
-                enclosure_loaded = true;
-            }
+            internal set { feed = value; feed_id = value.DbId; }
         }
 
         public FeedEnclosure Enclosure {
-            get { lock (sync) { return enclosure; } }
+            get { LoadEnclosure (); return enclosure; }
             internal set {
-                lock (sync) {
-                    if (value == null) {
-                        throw new ArgumentNullException ("Enclosure");
-                    }
-                        
-                    FeedEnclosure tmp = value as FeedEnclosure;
-            
-                    if (tmp == null) {
-                        throw new ArgumentException (
-                            "Enclosure must be derived from 'FeedEnclosure'"
-                        );
-                    }
-                        
-                    enclosure = tmp;
-                    enclosure.Item = this;
-                }
-            }            
+                enclosure = value;
+                enclosure.Item = this;
+            }
         }
+        
+#endregion
+
+#region Constructor
  
         public FeedItem ()
         {
         }
+        
+#endregion
+
+        private static FeedManager Manager {
+            get { return FeedsManager.Instance.FeedManager; }
+        }
+
+#region Public Methods
 
         public void Save ()
         {
+            bool is_new = DbId < 1;
             Provider.Save (this);
-            if (enclosure != null)
-                enclosure.Save ();
-        }
-      
-        public void Delete ()
-        {
-            DeleteImpl (true, true);
-        }
-        
-        public void Delete (bool delEncFile)
-        {
-            DeleteImpl (true, delEncFile);
-        }        
-        
-        internal void DeleteImpl (bool removeFromParent, bool delEncFile)
-        {
-            lock (sync) {
-                if (!active) {
-                    return;
-                }
-                
-                if (delEncFile) {
-                    if (enclosure != null && delEncFile) {
-                        enclosure.RemoveFile ();
-                    }                
-                }                   
-                
-                Active = false;
-                
-                if (removeFromParent) {
-                    // TODO should this be Provider.Delete ?
-                    //CommitImpl ();    
-                }                
+            if (enclosure != null) {
+                enclosure.Item = this;
+                enclosure.Save (false);
             }
 
-            if (removeFromParent) {
-                feed.Remove (this);       
-            }            
+            if (is_new)
+                Manager.OnItemAdded (this);
+            else
+                Manager.OnItemChanged (this);
         }
         
-        internal void CancelDownload (FeedEnclosure enc)
+        public void Delete (bool delete_file)
         {
-            if (feed != null) {
-                feed.CancelDownload (enc);
+            if (enclosure != null) {
+                enclosure.Delete (delete_file);
             }
+            
+            Provider.Delete (this);
+            Manager.OnItemRemoved (this);
         }
         
-        internal bool QueueDownload (FeedEnclosure enc) 
-        {
-            bool queued = false;
-        
-            if (feed != null) {
-                queued = feed.QueueDownload (enc) != null;	
-            }
-        
-            return queued;
-        }
+#endregion
 
-        internal void StopDownload (FeedEnclosure enc)
+        private bool enclosure_loaded;
+        private void LoadEnclosure ()
         {
-            if (feed != null) {
-                feed.StopDownload (enc);
+            if (!enclosure_loaded && DbId > 0) {
+                IEnumerable<FeedEnclosure> enclosures = FeedEnclosure.Provider.FetchAllMatching (String.Format (
+                    "{0}.ItemID = {1}", FeedEnclosure.Provider.TableName, DbId
+                ));
+                
+                foreach (FeedEnclosure enclosure in enclosures) {
+                    enclosure.Item = this;
+                    this.enclosure = enclosure;
+                    break; // should only have one
+                }
+                enclosure_loaded = true;
             }
         }
+
     }
 }

Added: trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedManager.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedManager.cs	Sun May 18 00:19:17 2008
@@ -0,0 +1,201 @@
+//
+// FeedUpdater.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.Collections.Generic;
+
+using Migo.Net;
+using Migo.TaskCore;
+using Migo.TaskCore.Collections;
+
+namespace Migo.Syndication
+{
+    public class FeedManager
+    {
+        private bool disposed;
+        private Dictionary<Feed, FeedUpdateTask> update_feed_map;
+        private TaskList<FeedUpdateTask> update_task_list;
+        private TaskGroup<FeedUpdateTask> update_task_group;
+        
+#region Public Properties and Events
+
+        public event Action<FeedItem> ItemAdded;
+        public event Action<FeedItem> ItemChanged;
+        public event Action<FeedItem> ItemRemoved;
+        public event EventHandler FeedsChanged;
+
+#endregion
+        
+#region Constructor
+        
+        public FeedManager ()
+        {
+            update_feed_map = new Dictionary<Feed, FeedUpdateTask> ();
+            update_task_list = new TaskList<FeedUpdateTask> ();
+            
+            // Limit to 4 feeds downloading at a time
+            update_task_group = new TaskGroup<FeedUpdateTask> (4, update_task_list);
+            
+            update_task_group.TaskStopped += OnUpdateTaskStopped;
+            update_task_group.TaskAssociated += OnUpdateTaskAdded;
+            
+            // TODO
+            // Start timeout to refresh feeds every so often
+        }
+
+#endregion
+        
+#region Public Methods
+
+        public bool IsUpdating (Feed feed)
+        {
+            return update_feed_map.ContainsKey (feed);
+        }
+
+        public Feed CreateFeed (string url, FeedAutoDownload autoDownload)
+        {
+            Feed feed = null;
+            url = url.Trim ().TrimEnd ('/');
+
+            if (!Feed.Exists (url)) {
+                feed = new Feed (url, autoDownload);
+                feed.Save ();
+                feed.Update ();
+            }
+            
+            return feed;
+        }
+
+        public void QueueUpdate (Feed feed)
+        {
+            lock (update_task_group.SyncRoot) {
+                if (disposed) {
+                    return;
+                }
+                
+                if (!update_feed_map.ContainsKey (feed)) {
+                    FeedUpdateTask task = new FeedUpdateTask (feed);
+                    update_feed_map[feed] = task;
+                    lock (update_task_list.SyncRoot) { 
+                        update_task_list.Add (task);
+                    }
+                }
+            }        
+        }
+        
+        public void CancelUpdate (Feed feed)
+        {
+            lock (update_task_group.SyncRoot) {
+                if (update_feed_map.ContainsKey (feed)) {
+                    update_feed_map[feed].CancelAsync ();
+                }
+            }
+        }
+        
+        public void Dispose (System.Threading.AutoResetEvent disposeHandle)
+        {
+            lock (update_task_group.SyncRoot) {
+                if (update_task_group != null) {
+                    update_task_group.CancelAsync ();
+                    update_task_group.Handle.WaitOne ();
+                    update_task_group.Dispose (disposeHandle);
+                    disposeHandle.WaitOne ();
+                    
+                    update_task_group.TaskStopped -= OnUpdateTaskStopped;
+                    update_task_group.TaskAssociated -= OnUpdateTaskAdded;
+                    update_task_group = null;
+                }
+               
+                update_task_list = null;
+                disposed = true;
+            }
+        }        
+                
+#endregion
+
+#region Internal Methods
+
+        internal void OnFeedsChanged ()
+        {
+            EventHandler handler = FeedsChanged;
+            if (handler != null) {
+                handler (this, EventArgs.Empty);
+            }
+        }
+        
+        internal void OnItemAdded (FeedItem item)
+        {
+            Action<FeedItem> handler = ItemAdded;
+            if (handler != null) {
+                handler (item);
+            }
+        }
+        
+        internal void OnItemChanged (FeedItem item)
+        {
+            Action<FeedItem> handler = ItemChanged;
+            if (handler != null) {
+                handler (item);
+            }
+        }
+        
+        internal void OnItemRemoved (FeedItem item)
+        {
+            Action<FeedItem> handler = ItemRemoved;
+            if (handler != null) {
+                handler (item);
+            }
+        }
+
+#endregion
+
+#region Private Methods
+
+        private void OnUpdateTaskAdded (object sender, TaskEventArgs<FeedUpdateTask> e)
+        {   
+            lock (update_task_group.SyncRoot) {
+                update_task_group.Execute ();
+            }
+        }
+
+        private void OnUpdateTaskStopped (object sender, TaskEventArgs<FeedUpdateTask> e)
+        {
+            lock (update_task_group.SyncRoot) {
+                FeedUpdateTask fut = e.Task as FeedUpdateTask;
+                update_feed_map.Remove (fut.Feed);
+                
+                lock (update_task_list.SyncRoot) {
+                    update_task_list.Remove (e.Task);
+                }
+            }
+        }
+        
+#endregion
+
+    }
+}

Modified: trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedUpdateTask.cs
==============================================================================
--- trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedUpdateTask.cs	(original)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedUpdateTask.cs	Sun May 18 00:19:17 2008
@@ -29,56 +29,70 @@
 using System;
 using System.Threading;
 using System.ComponentModel;
+using System.Net;
+
+using Hyena;
 
 using Migo.Net;
 using Migo.TaskCore;
 
 namespace Migo.Syndication
 {
-	public class FeedUpdateTask : Task, IDisposable
-	{
+    public class FeedUpdateTask : Task, IDisposable
+    {
         private Feed feed;
-	    private bool disposed;	 
-	    private ManualResetEvent mre;
+        private bool disposed, cancelled, completed;
+        private ManualResetEvent mre;
+        private AsyncWebClient wc = null;
 
         public Feed Feed {
             get { return feed; }
         }
 
-	    public override WaitHandle WaitHandle {
-	        get {
-	            lock (SyncRoot) {
-	                if (mre == null) {
+        public override WaitHandle WaitHandle {
+            get {
+                lock (SyncRoot) {
+                    if (mre == null) {
                         mre = new ManualResetEvent (true);
-	                }
-	                
-	                return mre;
-	            }
-	        }
-	    }
-	    
-	    public FeedUpdateTask (Feed feed)
-	    {
+                    }
+                   
+                    return mre;
+            }
+            }
+        }
+
+#region Constructor
+
+        public FeedUpdateTask (Feed feed)
+        {
             if (feed == null) {
                 throw new ArgumentNullException ("feed");
             }
 
             this.feed = feed;
             this.Name = feed.Link;
-            feed.FeedDownloadCompleted += OnFeedDownloadCompletedHandler;
-	    }
+            
+            feed.DownloadStatus = FeedDownloadStatus.Pending;
+        }
+
+#endregion
+
+#region Public Methods
 
         public override void CancelAsync ()
-	    { 
-	        //Console.WriteLine ("CancelAsync - {0} - FeedUpdateTask - 000", IsCompleted);
-            lock (SyncRoot) {	
-                if (!feed.CancelAsyncDownload () && !IsCompleted) {
-                	//Console.WriteLine ("CancelAsync - FeedUpdateTask - 001");
-                    EmitCompletionEvents (FeedDownloadError.Canceled);                
+        { 
+            lock (SyncRoot) {
+                if (!completed) {
+                    cancelled = true;
+    
+                    if (wc != null) {
+                        wc.CancelAsync ();
+                    }
+
+                    EmitCompletionEvents (FeedDownloadError.Canceled);
                 }
             }
-	        //Console.WriteLine ("CancelAsync - FeedUpdateTask - 002");            
-	    }
+        }
     
         public void Dispose ()
         {
@@ -94,43 +108,109 @@
             }
         }
 
-	    public override void ExecuteAsync ()
-	    {
-	        lock (SyncRoot) {
-	            SetStatus (TaskStatus.Running);
+        public override void ExecuteAsync ()
+        {
+            lock (SyncRoot) {
+                SetStatus (TaskStatus.Running);
                 
                 if (mre != null) {
                     mre.Reset ();                    
-                }	            
+                }          
             }
-
-            feed.AsyncDownloadImpl ();
-	    }
+            
+            try {                                                                       
+                wc = new AsyncWebClient ();                  
+                wc.Timeout = (30 * 1000); // 30 Seconds  
+                wc.IfModifiedSince = feed.LastDownloadTime.ToUniversalTime ();
+                wc.DownloadStringCompleted += OnDownloadDataReceived;
+                
+                feed.DownloadStatus = FeedDownloadStatus.Downloading;
+                wc.DownloadStringAsync (new Uri (feed.Url));
+            } catch (Exception e) {
+                if (wc != null) {
+                    wc.DownloadStringCompleted -= OnDownloadDataReceived;
+                }
+                
+                EmitCompletionEvents (FeedDownloadError.DownloadFailed);
+                Log.Exception (e);
+            }
+        }
         
-        private void OnFeedDownloadCompletedHandler (object sender, 
-                                                     FeedDownloadCompletedEventArgs e)
+#endregion
+
+        private void OnDownloadDataReceived (object sender, Migo.Net.DownloadStringCompletedEventArgs args) 
         {
             lock (SyncRoot) {
-                EmitCompletionEvents (e.Error);
-	        }            
+                if (cancelled)
+                    return;
+
+                wc.DownloadStringCompleted -= OnDownloadDataReceived;
+                FeedDownloadError error;
+                
+                if (args.Error == null) {
+                     try {
+                        RssParser parser = new RssParser (feed.Url, args.Result);
+                        parser.UpdateFeed (feed);
+                        feed.SetItems (parser.GetFeedItems (feed));
+                        error = FeedDownloadError.None;
+                    } catch (FormatException e) {
+                        Log.Exception (e);
+                        error = FeedDownloadError.InvalidFeedFormat;
+                    }
+                } else {
+                    error = FeedDownloadError.DownloadFailed;
+                    WebException we = args.Error as WebException;
+                    if (we != null) {
+                        HttpWebResponse resp = we.Response as HttpWebResponse;
+                        if (resp != null) {
+                            switch (resp.StatusCode) {
+                            case HttpStatusCode.NotFound:
+                            case HttpStatusCode.Gone:
+                                error = FeedDownloadError.DoesNotExist;
+                                break;                                
+                            case HttpStatusCode.NotModified:
+                                error = FeedDownloadError.None;
+                                break;
+                            case HttpStatusCode.Unauthorized:
+                                error = FeedDownloadError.UnsupportedAuth;
+                                break;                                
+                            default:
+                                error = FeedDownloadError.DownloadFailed;
+                                break;
+                            }
+                        }
+                    }
+                }
+                
+                feed.LastDownloadError = error;
+                if (error == FeedDownloadError.None) {
+                    feed.LastDownloadTime = DateTime.Now;
+                }
+                    
+                feed.Save ();
+                
+                EmitCompletionEvents (error);
+                completed = true;
+            }
         }
         
         private void EmitCompletionEvents (FeedDownloadError err)
         {
-            feed.FeedDownloadCompleted -= OnFeedDownloadCompletedHandler;
-
-            switch (err) {                        
+            switch (err) {                
                 case FeedDownloadError.None:
                     SetStatus (TaskStatus.Succeeded);
+                    feed.DownloadStatus = FeedDownloadStatus.Downloaded;
                     break;
                 case FeedDownloadError.Canceled:
                     SetStatus (TaskStatus.Cancelled);
+                    feed.DownloadStatus = FeedDownloadStatus.None;
                     break;
                 default:
                     SetStatus (TaskStatus.Failed);
+                    feed.DownloadStatus = FeedDownloadStatus.DownloadFailed;
                     break;
             }
-            
+
             OnTaskCompleted (null, (Status == TaskStatus.Cancelled));
 
             if (mre != null) {

Modified: trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedsManager.cs
==============================================================================
--- trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedsManager.cs	(original)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/FeedsManager.cs	Sun May 18 00:19:17 2008
@@ -53,47 +53,25 @@
 {
     public class FeedsManager : IDisposable
     {        
-        private bool disposed;        
-        
-        private bool ticDirty = true;
-        private long totalItemCount;
-        
-        private bool tuicDirty = true;
-        private long totalUnreadItemCount;
-        
-        private List<Feed> feeds;
-        private List<Feed> queued_feeds;
+        private bool disposed;
 
         private AsyncCommandQueue<ICommand> command_queue;
         
-        private Dictionary<long, Feed> id_feed_map;
-        private Dictionary<string, Feed> url_feed_map;
-        
-        private Dictionary<FeedEnclosure, HttpFileDownloadTask> queued_downloads;
-        
-        private ManualResetEvent download_handle;
-        private DownloadManager download_manager;
-        
-        private TaskList<FeedUpdateTask> update_list;
-        private TaskGroup<FeedUpdateTask> update_group;
+        private FeedManager feed_manager;
+        private EnclosureManager enclosure_manager;
         
         private readonly object sync = new object (); 
         
-        public event EventHandler<TaskEventArgs<HttpFileDownloadTask>> EnclosureDownloadCompleted;        
-        
-        public event EventHandler<FeedEventArgs> FeedAdded;
+        /*public event EventHandler<FeedEventArgs> FeedAdded;
         public event EventHandler<FeedEventArgs> FeedDeleted;
-        public event EventHandler<FeedDownloadCountChangedEventArgs> FeedDownloadCountChanged;                
-        public event EventHandler<FeedDownloadCompletedEventArgs> FeedDownloadCompleted;
-        public event EventHandler<FeedEventArgs> FeedDownloading;
         public event EventHandler<FeedItemCountChangedEventArgs> FeedItemCountChanged;
         public event EventHandler<FeedEventArgs> FeedRenamed;
         public event EventHandler<FeedEventArgs> FeedUrlChanged;        
         
         public event EventHandler<FeedItemEventArgs> FeedItemAdded;
-        public event EventHandler<FeedItemEventArgs> FeedItemRemoved;
+        public event EventHandler<FeedItemEventArgs> FeedItemRemoved;*/
         
-        internal static FeedsManager Instance;
+        public static FeedsManager Instance;
         
         internal AsyncCommandQueue<ICommand> CommandQueue {
             get { return command_queue; }
@@ -112,105 +90,53 @@
         // TODO interval for what, and in what unit?
         public long DefaultInterval {
             get { lock (sync) { return 15; } }
-            set { throw new NotImplementedException ("DefaultInterval"); }
-        }
-
-        public DownloadManager DownloadManager {
-            get { return download_manager; }
         }
         
-        private ReadOnlyCollection<Feed> ro_feeds;
+        /*private ReadOnlyCollection<Feed> ro_feeds;
         public ReadOnlyCollection<Feed> Feeds {             
             get { 
                 lock (sync) {
                     return ro_feeds ?? ro_feeds = new ReadOnlyCollection<Feed> (feeds);
                 }
             }
+        }*/
+        
+        public FeedManager FeedManager {
+            get { return feed_manager; }
         }
-
-        public long ItemCountLimit {
-            get { lock (sync) { return -1; } }
-        }        
         
-        public long TotalItemCount { 
-            get {
-                lock (sync) {
-                    if (ticDirty) {
-                    	totalItemCount = 0;
-                        
-                        foreach (Feed f in feeds) {
-                            totalItemCount += f.ItemCount;
-                    	}
-                        
-                        ticDirty = false;                        
-                    }
-                    
-                    return totalItemCount;
-                }
-            }
+        public EnclosureManager EnclosureManager {
+            get { return enclosure_manager; }
         }
-                
-        public long TotalUnreadItemCount {
-            get {        
-                lock (sync) {
-                    if (tuicDirty) {
-                    	totalUnreadItemCount = 0;
-                        
-                        foreach (Feed f in feeds) {
-                            totalUnreadItemCount += f.UnreadItemCount;
-                    	}
-                        
-                        tuicDirty = false;
-                    }
-                    
-                    return totalUnreadItemCount;
-                }
-            }
+        
+        private HyenaSqliteConnection connection;
+        public HyenaSqliteConnection Connection {
+            get { return connection; }
         }
         
+        private string podcast_base_dir;
+        public string PodcastStorageDirectory {
+            get { return podcast_base_dir; }
+        }
+
 #endregion
 
 #region Constructor
         
-        public FeedsManager (HyenaSqliteConnection connection, DownloadManager manager)
-        {
-            if (connection == null) {
-                throw new ArgumentException ("connection is null");
-            } else if (manager == null) {
-                throw new ArgumentNullException ("manager");
-            }
-            
+        public FeedsManager (HyenaSqliteConnection connection, DownloadManager downloadManager, string podcast_base_dir)
+        {            
             // Hack to work around Feeds being needy and having to call all our internal methods, instead
             // of us just listening for their events.
             Instance = this;
-            
-            download_manager = manager;
+            this.connection = connection;
+            this.podcast_base_dir = podcast_base_dir;
 
-            FeedEnclosure.Provider = new SqliteModelProvider<FeedEnclosure> (connection, "PodcastEnclosures");
-            FeedItem.Provider = new SqliteModelProvider<FeedItem> (connection, "PodcastItems");
-            Migo.Syndication.Feed.Provider = new SqliteModelProvider<Migo.Syndication.Feed> (connection, "PodcastSyndications");
-            
-            feeds = new List<Feed> ();
-            queued_feeds = new List<Feed> ();
-            id_feed_map = new Dictionary<long, Feed> ();
-            url_feed_map = new Dictionary<string, Feed> ();
-
-            download_handle = new ManualResetEvent (true);
-
-            download_manager.Tasks.TaskAdded += OnDownloadTaskAdded;
-            download_manager.Tasks.TaskRemoved += OnDownloadTaskRemoved;            
-            download_manager.Group.TaskStatusChanged += OnDownloadTaskStatusChangedHandler;
-
-            queued_downloads = new Dictionary<FeedEnclosure, HttpFileDownloadTask> ();
-            update_list = new TaskList<FeedUpdateTask> ();
-            update_group = new TaskGroup<FeedUpdateTask> (4, update_list);
-            update_group.TaskStopped += TaskStoppedHandler;
-            update_group.TaskAssociated += TaskAssociatedHandler;
+            feed_manager = new FeedManager ();
+            enclosure_manager = new EnclosureManager (downloadManager);
             
-            /*foreach (Feed feed in Feed.Provider.FetchAll ()) {
-                Console.WriteLine ("Adding feed {0}", feed.Url);
-                AddFeed (feed);
-            }*/
+            Feed.Init ();
+            FeedItem.Init ();
+            FeedEnclosure.Init ();
             
             command_queue = new AsyncCommandQueue<ICommand> ();
         }
@@ -219,269 +145,13 @@
 
 #region Public Methods
 
-        public Feed CreateFeed (string url)
-        {
-            Feed feed = null;
-            url = url.Trim ().TrimEnd ('/');
-
-            lock (sync) {   
-                if (!url_feed_map.ContainsKey (url)) {
-                    feed = new Feed (url);
-                    feed.Save ();
-                    AddFeed (feed);
-                    OnFeedAdded (feed);
-                }
-            }
-            
-            return feed;
-        }
-        
-        public HttpFileDownloadTask QueueDownload (FeedEnclosure enclosure) 
-        {
-            return QueueDownload (enclosure, true);         
-        }
-           
-        public HttpFileDownloadTask QueueDownload (FeedEnclosure enclosure, bool queue)
-        {
-            if (enclosure == null) {
-                throw new ArgumentNullException ("enc");
-            }
-            
-            HttpFileDownloadTask task = null;       
-            
-            lock (sync) {
-                if (disposed) {
-                    return null;
-                }
-                
-                if (!queued_downloads.ContainsKey (enclosure)) {                    
-                    Feed parentFeed = enclosure.Item.Feed;                    
-                    
-                    if (parentFeed != null && !IsFeedOurs (parentFeed)) {                        
-                        task = download_manager.CreateDownloadTask (enclosure.Url, enclosure);
-                        //Console.WriteLine ("Task DL path:  {0}", task.LocalPath);
-                        task.Name = String.Format ("{0} - {1}", parentFeed.Title, enclosure.Item.Title);
-                        
-                        task.Completed += OnDownloadTaskCompletedHandler;                    
-                        
-                        // Race condition...
-                        // Should only be added when the task is associated or 
-                        // it can be canceled before it is added to the progress manager.
-                        
-                        // Add a pre-association dict and move tasks to the 
-                        // queued dict once they've been offically added.
-                        
-                        queued_downloads.Add (enclosure, task);
-                    }                    
-                }
-
-                if (task != null && queue) {
-                    download_manager.QueueDownload (task);                                                                    	
-                }      
-            }
-                       
-            return task;        
-        }
-        
-        public IEnumerable<HttpFileDownloadTask> QueueDownloads (IEnumerable<FeedEnclosure> encs)
-        {
-            if (encs == null) {
-                throw new ArgumentNullException ("encs");
-            }
-            
-            ICollection<HttpFileDownloadTask> encsCol = encs as ICollection<HttpFileDownloadTask>;
-            
-            List<HttpFileDownloadTask> tasks = (encsCol == null) ?
-                new List<HttpFileDownloadTask> () : 
-                new List<HttpFileDownloadTask> (encsCol.Count);
-            
-            HttpFileDownloadTask tmpTask = null;
-            
-            lock (sync) {   
-                if (disposed) {
-                    return tasks;
-                }
-                
-                foreach (FeedEnclosure enc in encs) {
-                    if (enc.SetDownloading ()) {
-                        tmpTask = QueueDownload (enc, false);
-                        
-                        if (tmpTask == null) {
-                            enc.ResetDownloading ();
-                            continue;
-                        }
-                        
-                        tasks.Add (tmpTask);
-                    }
-                }
-                
-                if (tasks.Count > 0) {
-                    download_manager.QueueDownload (tasks);
-                }
-            }
-            
-            return tasks;
-        }
-
-        // TODO remove these? not used
-        /*public void AsyncSyncAll ()
-        {
-            List<Feed> allFeeds = null;
-            
-            lock (sync) {
-                if (feeds.Count > 0) {
-                    allFeeds = new List<Feed> (feeds);
-                }
-            }
-
-            if (allFeeds != null) {
-                foreach (Feed f in allFeeds) {
-                    f.AsyncDownload ();
-                }
-            }
-        }
-        
-        public void DeleteFeed (long feedID)
-        {
-            Feed feed = null;
-            
-            lock (sync) {
-                idFeedDict.TryGetValue(feedID, out feed);
-            }
-            
-            if (feed != null) {
-                DeleteFeed (idFeedDict[feedID]);
-            }                
-        }   
-          
-        public void DeleteFeed (Feed feed)
-        {
-            if (feed == null) {
-                throw new ArgumentNullException ("feed");      	
-            } else if (!IsFeedOurs (feed)) {
-                feed.Delete ();
-            }
-        }
-      
-        public void DequeueDownloads (IEnumerable<FeedEnclosure> enclosures) 
-        {
-            if (enclosures == null) {
-                throw new ArgumentNullException ("enclosures");
-            }
-            
-            IEnumerable<HttpFileDownloadTask> tasks =   
-                FindDownloadTasks (enclosures);
-                
-            downloadManager.RemoveDownload (tasks);
-        }
-        
-        public bool ExistsFeed (string url)
-        {
-            lock (sync) {
-                return urlFeedDict.ContainsKey (url);
-            }
-        }
-        
-        public bool ExistsFeed (long feedID)
-        {
-            lock (sync) {
-                return idFeedDict.ContainsKey (feedID);
-            }
-        }
-        
-        public Feed GetFeed (long feedID)
-        {
-            Feed ret = null;
-            
-            lock (sync) {
-                idFeedDict.TryGetValue (feedID, out ret);
-            }
-            
-            return ret;
-        }
-            
-        public Feed GetFeedByUrl (string url)
-        {
-            Feed ret = null;
-            
-            lock (sync) {
-                urlFeedDict.TryGetValue (url, out ret);
-            }
-            
-            return ret;
-        }
-        
-        public bool IsSubscribed (string url)
-        {
-            lock (sync) {
-                return urlFeedDict.ContainsKey (url); 
-            }
-        }*/
-
         public void Dispose () 
         {
             if (SetDisposed ()) {               
                 AutoResetEvent disposeHandle = new AutoResetEvent (false);
-                Console.WriteLine ("FM - Dispose - 000");
-    
-                List<HttpFileDownloadTask> tasks = null;
-    
-                lock (sync) {
-                    if (queued_downloads.Count > 0) {
-                        tasks = new List<HttpFileDownloadTask> (queued_downloads.Values);
-                    }
-                }
-
-                if (tasks != null) {
-                    foreach (HttpFileDownloadTask t in tasks) {
-                        t.Stop ();
-                    }
-                    
-                    Console.WriteLine ("downloadHandle - WaitOne ()");
-                    download_handle.WaitOne ();
-                }
 
-                if (update_group != null) {
-                    update_group.CancelAsync ();                
-                    
-                    Console.WriteLine ("FM - Dispose - 000.5");
-                    
-                    update_group.Handle.WaitOne ();
-                    
-                    Console.WriteLine ("FM - Dispose - 001");
-                    
-                    update_group.Dispose (disposeHandle);
-                    
-                    Console.WriteLine ("FM - Dispose - 002");
-                    
-                    disposeHandle.WaitOne ();
-                    
-                    update_group.TaskStopped -= TaskStoppedHandler;
-                    update_group.TaskAssociated -= TaskAssociatedHandler;
-                    
-                    update_group = null;
-                    
-                    Console.WriteLine ("FM - Dispose - 003");
-                }
-               
-                update_list = null;
-                
-                Console.WriteLine ("FM - Dispose - 008");    
-                                 
-                if (download_handle != null) {
-                    download_handle.Close ();
-                    download_handle = null;
-                }                
-                
-                Console.WriteLine ("FM - Dispose - 007");                    
-                
-                if (download_manager != null) {
-                    download_manager.Tasks.TaskAdded -= OnDownloadTaskAdded;
-                    download_manager.Tasks.TaskRemoved -= OnDownloadTaskRemoved; 
-                    download_manager = null;
-                }                          
-
-                Console.WriteLine ("FM - Dispose - 004");                  
+                feed_manager.Dispose (disposeHandle);
+                enclosure_manager.Dispose (disposeHandle);
 
                 if (command_queue != null) {
                     command_queue.Dispose ();
@@ -495,278 +165,10 @@
 #endregion
 
 #region Private Methods
-
-        private void AddFeed (Feed feed)
-        {
-            if (feed != null && !id_feed_map.ContainsKey (feed.DbId)) {
-                id_feed_map[feed.DbId] = feed;
-                url_feed_map[feed.Url] = feed;
-                feeds.Add (feed);
-            }                 
-        }
-
-        private void RemoveFeed (Feed feed)
-        {
-            if (feed != null && feeds.Remove (feed)) {
-                url_feed_map.Remove (feed.Url);            
-                id_feed_map.Remove (feed.DbId);                
-            }
-        }           
-        
-        private bool IsFeedOurs (Feed feed)
-        {
-            bool ret = true;
-            Feed f = feed as Feed;
-            
-            if (f != null && f.Parent == this) {
-                ret = false;
-            }
-            
-            return ret;
-        }
-
-       
-        private HttpFileDownloadTask FindDownloadTask (FeedEnclosure enc)
-        {
-            if (enc == null) {
-                throw new ArgumentNullException ("enc");
-            }
-            
-            return FindDownloadTaskImpl ((FeedEnclosure)enc);
-        }
-        
-        private HttpFileDownloadTask FindDownloadTaskImpl (FeedEnclosure enc) 
-        {
-            HttpFileDownloadTask task = null;
-            Feed parentFeed = enc.Item.Feed as Feed;                               
-            
-            if (parentFeed != null && 
-                !IsFeedOurs (parentFeed) && 
-                queued_downloads.ContainsKey (enc)) {
-                task = queued_downloads[enc];
-            }
-            
-            return task;
-        }        
-
-
-        private bool SetDisposed ()
-        {
-            bool ret = false;
-                
-            lock (sync) {
-                if (!disposed) {
-                    ret = disposed = true;   
-                }
-            }
-                
-            return ret;
-        }        
-        
-        private void TaskAddedAction (HttpFileDownloadTask task)
-        {
-            Feed parentFeed = null;
-            FeedEnclosure enc = task.UserState as FeedEnclosure;
-                    
-            if (enc != null) {
-                lock (sync) { 
-                    parentFeed = enc.Item.Feed;                  
-                    
-                    if (parentFeed != null && !IsFeedOurs (parentFeed) &&
-                        queued_downloads.ContainsKey (enc)) {
-                        if (queued_downloads.Count == 0) {
-                            download_handle.Reset ();
-                        }                        
-                                                    
-                        enc.DownloadStatus = FeedDownloadStatus.Pending;                        
-                        parentFeed.IncrementQueuedDownloadCount ();                    
-                    }
-                }
-            }        
-        }
-        
-        private void OnDownloadTaskAdded (object sender, TaskAddedEventArgs<HttpFileDownloadTask> e)
-        {
-            if (e.Task != null) {
-                TaskAddedAction (e.Task);
-            } else if (e.Tasks != null) {
-                foreach (HttpFileDownloadTask task in e.Tasks) {
-                    TaskAddedAction (task);                                    
-                }
-            }
-        }
-        
-        private void  OnDownloadTaskCompletedHandler (object sender, 
-                                                      TaskCompletedEventArgs e)
-        {
-            HttpFileDownloadTask task = sender as HttpFileDownloadTask;             
-            FeedEnclosure enc = task.UserState as FeedEnclosure;
-
-            if (enc != null) {   
-                if (e.Error != null || task.Status == TaskStatus.Failed) {
-                    enc.DownloadStatus = FeedDownloadStatus.DownloadFailed;
-                } else if (!e.Cancelled) {
-                    if (task.Status == TaskStatus.Succeeded) {
-                        try {                        
-                            enc.SetFileImpl (
-                                task.RemoteUri.ToString (), 
-                                Path.GetDirectoryName (task.LocalPath), 
-                                task.MimeType, 
-                                Path.GetFileName (task.LocalPath)
-                            );                             
-                        } catch {
-                            enc.LastDownloadError = FeedDownloadError.DownloadFailed;
-                            enc.DownloadStatus = FeedDownloadStatus.DownloadFailed;  
-                        }
-                    }
-                }
-            }
-            
-            OnEnclosureDownloadCompleted (task);            
-        }
-        
-        private void DownloadTaskRemoved (FeedEnclosure enc, 
-                                          HttpFileDownloadTask task,
-                                          bool decQueuedCount)
-        {
-            if (queued_downloads.ContainsKey (enc)) {
-                queued_downloads.Remove (enc);    
-                task.Completed -= OnDownloadTaskCompletedHandler;                                            
-                
-                if (decQueuedCount) {
-                    enc.Item.Feed.DecrementQueuedDownloadCount ();
-                }
-                
-                if (queued_downloads.Count == 0) {
-                	if (download_handle != null) {
-                	    download_handle.Set ();
-                	}
-                }
-            }
-        }
         
-        private void OnDownloadTaskRemoved (object sender, 
-                                            TaskRemovedEventArgs<HttpFileDownloadTask> e)
-        {
-            if (e.Task != null) {
-                FeedEnclosure enc = e.Task.UserState as FeedEnclosure;
-                    
-                if (enc != null) {
-                    lock (sync) {
-                        DownloadTaskRemoved (enc, e.Task, true);
-                    }
-                }
-            } else if (e.Tasks != null) {
-                Feed tmpParent = null;
-                FeedEnclosure tmpEnclosure = null;
-                List<FeedEnclosure> tmpList = null;
-                
-                Dictionary<Feed, List<FeedEnclosure>> feedDict =
-                    new Dictionary<Feed,List<FeedEnclosure>> ();
-                
-                lock (sync) {
-                    foreach (HttpFileDownloadTask t in e.Tasks) {
-                        tmpEnclosure = t.UserState as FeedEnclosure;
-                        
-                        if (tmpEnclosure != null) {
-                            tmpParent = tmpEnclosure.Item.Feed;
-                            
-                            if (!feedDict.TryGetValue (tmpParent, out tmpList)) {
-                                tmpList = new List<FeedEnclosure> ();
-                                feedDict.Add (tmpParent, tmpList);  
-                            }
-                            
-                            tmpList.Add (tmpEnclosure);
-                            DownloadTaskRemoved (tmpEnclosure, t, false);
-                        }
-                    }
-                    
-                    foreach (KeyValuePair<Feed,List<FeedEnclosure>> kvp in feedDict) {
-                        kvp.Key.DecrementQueuedDownloadCount (kvp.Value.Count);
-                    }
-                }
-            }                       
-        }        
 
-        private void TaskStatusChanged (TaskStatusChangedInfo statusInfo)
-        {
-            HttpFileDownloadTask task = statusInfo.Task as HttpFileDownloadTask;
-            
-            if (task == null) {
-                return;
-            }
-            
-            FeedEnclosure enc = task.UserState as FeedEnclosure;
-            
-            if (enc == null) {
-                return;
-            }
-            
-            //Console.WriteLine ("OnDownloadTaskStatusChangedHandler - old state:  {0}", statusInfo.OldStatus);                    
-            //Console.WriteLine ("OnDownloadTaskStatusChangedHandler - new state:  {0}", statusInfo.NewStatus);                    
-            switch (statusInfo.NewStatus) {
-            case TaskStatus.Cancelled:
-                enc.DownloadStatus = FeedDownloadStatus.None;
-                break;
-            case TaskStatus.Failed:
-                enc.DownloadStatus = FeedDownloadStatus.DownloadFailed;
-                break;
-            case TaskStatus.Paused: 
-                enc.DownloadStatus = FeedDownloadStatus.Paused;
-                break;
-            case TaskStatus.Ready:
-                enc.DownloadStatus = FeedDownloadStatus.Pending;
-                break;
-            case TaskStatus.Running:
-                enc.DownloadStatus = FeedDownloadStatus.Downloading;
-                enc.Item.Feed.IncrementActiveDownloadCount ();                    
-                break;  
-            case TaskStatus.Stopped: goto case TaskStatus.Cancelled;
-            }
 
-            if (statusInfo.OldStatus == TaskStatus.Running) {
-                enc.Item.Feed.DecrementActiveDownloadCount ();                    
-            }
-        }
-
-        private void OnDownloadTaskStatusChangedHandler (object sender, 
-                                                         TaskStatusChangedEventArgs e)
-        {
-            if (e.StatusChanged != null) {
-                lock (sync) {
-                    TaskStatusChanged (e.StatusChanged);
-                }
-            } else {
-                lock (sync) {
-                    foreach (TaskStatusChangedInfo statusInfo in e.StatusesChanged) {
-                        TaskStatusChanged (statusInfo);                        
-                    }
-                }                
-            }
-        }
-      
-        private void TaskAssociatedHandler (object sender, 
-                                            TaskEventArgs<FeedUpdateTask> e)
-        {   
-            lock (update_group.SyncRoot) {
-                update_group.Execute ();
-            }
-        }        
-        
-        private void TaskStoppedHandler (object sender, 
-                                         TaskEventArgs<FeedUpdateTask> e)
-        {
-            lock (sync) {
-                FeedUpdateTask fut = e.Task as FeedUpdateTask;
-                queued_feeds.Remove (fut.Feed);
-                
-                lock (update_list.SyncRoot) {
-                    update_list.Remove (e.Task);
-                }
-            }
-        }
-        
-        private void OnFeedItemEvent (EventHandler<FeedItemEventArgs> handler, 
+        /*private void OnFeedItemEvent (EventHandler<FeedItemEventArgs> handler, 
                                       FeedItemEventArgs e)
         {
             if (handler == null) {
@@ -798,22 +200,20 @@
                 );              	
             	//handler (this, new FeedEventArgs (feed));
             }
-        }  
+        }  */
         
-        private void OnEnclosureDownloadCompleted (HttpFileDownloadTask task)
+        private bool SetDisposed ()
         {
-            EventHandler<TaskEventArgs<HttpFileDownloadTask>> handler = EnclosureDownloadCompleted;
-        
-            if (handler != null) {
-                AsyncCommandQueue<ICommand> cmdQCpy = command_queue;
+            bool ret = false;
                 
-                if (cmdQCpy != null) {
-                    cmdQCpy.Register (new EventWrapper<TaskEventArgs<HttpFileDownloadTask>> (
-                	    handler, this, new TaskEventArgs<HttpFileDownloadTask> (task))
-                	);
-                }        
-            }                         
-        }  
+            lock (sync) {
+                if (!disposed) {
+                    ret = disposed = true;   
+                }
+            }
+                
+            return ret;
+        }   
 
         /*private IEnumerable<HttpFileDownloadTask> FindDownloadTasks (IEnumerable<FeedEnclosure> enclosures)
         {            
@@ -842,84 +242,9 @@
 #endregion
 
 #region Internal Methods
-        
-        internal void CancelDownload (FeedEnclosure enc)
-        {
-            lock (sync) {
-                HttpFileDownloadTask task = FindDownloadTask (enc);            
-                        
-                if (task != null) {
-                    // Look into multi-cancel later      
-                    task.CancelAsync ();
-                }            
-            }
-        }
-        
-        internal void StopDownload (FeedEnclosure enc)
-        {   
-            lock (sync) {
-                HttpFileDownloadTask task = FindDownloadTask (enc);            
-                        
-                if (task != null) {
-                    task.Stop ();
-                }   
-            }
-        }     
-        
-        internal void QueueUpdate (Feed feed)
-        {
-            if (feed == null) {
-                throw new ArgumentNullException ("feed");
-            }
 
-            lock (sync) {
-                if (disposed) {
-                    return;
-                }
-                
-                if (!queued_feeds.Contains (feed)) {
-                    queued_feeds.Add (feed);
-                    lock (update_list.SyncRoot) { 
-                        update_list.Add (new FeedUpdateTask (feed));
-                    }
-                }            
-            }             
-        }
-        
-        internal void QueueUpdate (ICollection<Feed> feeds)
-        {
-            if (feeds == null) {
-                throw new ArgumentNullException ("feeds");
-            }
-            
-            lock (sync) {      
-                if (disposed) {
-                    return;
-                }
-                
-                List<FeedUpdateTask> tasks = null;
-                
-                if (feeds.Count > 0) {
-                    tasks = new List<FeedUpdateTask> (feeds.Count);
-                        
-                    foreach (Feed f in feeds) {
-                        if (!queued_feeds.Contains (f)) {
-                            queued_feeds.Add (f);
-                            tasks.Add (new FeedUpdateTask (f));
-                        }
-                    }
-                }
-                
-                if (tasks != null && tasks.Count > 0) {
-                    lock (update_list.SyncRoot) {
-                        update_list.AddRange (tasks);                  
-                    }   
-                }
-            }
-        }        
-        
         // Should only be called by 'Feed'
-        internal void RegisterCommand (ICommand command)
+/*        internal void RegisterCommand (ICommand command)
         {
              AsyncCommandQueue<ICommand> cmdQCpy = command_queue;
             
@@ -940,51 +265,8 @@
         }
         
         internal void OnFeedDeleted (Feed feed)
-        {      
-            try {
-                lock (sync) {                        
-                    RemoveFeed (feed);
-                }
-            } finally {
-                OnFeedEventRaised (feed, FeedDeleted);                
-            }
-        }
-        
-        internal void OnFeedDownloadCompleted (Feed feed, FeedDownloadError error)
-        {
-            if (feed == null) {
-                throw new ArgumentNullException ("feed");	
-            }
-            
-            EventHandler<FeedDownloadCompletedEventArgs> handler = FeedDownloadCompleted;
-                
-            if (handler != null) {
-                command_queue.Register (
-                    new EventWrapper<FeedDownloadCompletedEventArgs> (
-                        handler, this, 
-                        new FeedDownloadCompletedEventArgs (feed, error)
-                    )
-                );    
-            }          
-        }
-
-        internal void OnFeedDownloadCountChanged (Feed feed, FEEDS_EVENTS_DOWNLOAD_COUNT_FLAGS flags)
-        {
-            EventHandler<FeedDownloadCountChangedEventArgs> handler = FeedDownloadCountChanged;
-                     
-            if (handler != null) {             
-                command_queue.Register (
-                    new EventWrapper<FeedDownloadCountChangedEventArgs> (
-                        handler, this, 
-                        new FeedDownloadCountChangedEventArgs (feed, flags)
-                    )
-                );
-            }
-        }       
-        
-        internal void OnFeedDownloading (Feed feed)
         {
-            OnFeedEventRaised (feed, FeedDownloading);
+            OnFeedEventRaised (feed, FeedDeleted);
         }
         
         internal void OnFeedItemAdded (Feed feed, FeedItem item)
@@ -1015,60 +297,8 @@
             if (handler != null) {
                 OnFeedItemEvent (handler, new FeedItemEventArgs (feed, items));
             }               
-        }        
-
-        internal void OnFeedItemRemoved (Feed feed, FeedItem item)
-        {
-            if (feed == null) {
-                throw new ArgumentNullException ("feed");
-            } else if (item == null) {
-                throw new ArgumentNullException ("item");
-            }
-
-            EventHandler<FeedItemEventArgs> handler = FeedItemRemoved;            
-            
-            if (item.Enclosure != null) {
-                lock (sync) {
-                    HttpFileDownloadTask task;                
-                         
-                    if (queued_downloads.TryGetValue ((FeedEnclosure)item.Enclosure, out task)) {
-                        task.CancelAsync ();
-                    }
-                }
-            }            
-            
-            if (handler != null) {
-                OnFeedItemEvent (handler, new FeedItemEventArgs (feed, item));
-            }                 
         }
-        
-        internal void OnFeedItemsRemoved (Feed feed, IEnumerable<FeedItem> items)
-        {
-            if (feed == null) {
-                throw new ArgumentNullException ("feed");
-            } else if (items == null) {
-                throw new ArgumentNullException ("items");
-            }
-            
-            EventHandler<FeedItemEventArgs> handler = FeedItemRemoved;
 
-            lock (sync) {
-                HttpFileDownloadTask task;  
-                
-                foreach (FeedItem item in items) {                
-                    if (item.Enclosure != null) {                    
-                        if (queued_downloads.TryGetValue ((FeedEnclosure)item.Enclosure, out task)) {
-                            task.CancelAsync ();
-                        }
-                    }
-                }
-            }
-            
-            if (handler != null) {
-                OnFeedItemEvent (handler, new FeedItemEventArgs (feed, items));
-            }               
-        }              
-        
         internal void OnFeedItemCountChanged (Feed feed, FEEDS_EVENTS_ITEM_COUNT_FLAGS flags)
         {
             lock (sync) {
@@ -1076,14 +306,6 @@
                     throw new ArgumentNullException ("feed");	
                 }
 
-                if ((FEEDS_EVENTS_ITEM_COUNT_FLAGS.FEICF_TOTAL_ITEM_COUNT_CHANGED | flags) != 0) {
-                    ticDirty = true;
-                } 
-                    
-                if ((FEEDS_EVENTS_ITEM_COUNT_FLAGS.FEICF_UNREAD_ITEM_COUNT_CHANGED | flags) != 0) {
-                    tuicDirty = true;                       
-                }
-                
                 EventHandler<FeedItemCountChangedEventArgs> handler = FeedItemCountChanged;                
                 
                 if (handler != null) {
@@ -1102,18 +324,10 @@
             OnFeedEventRaised (feed, FeedRenamed);
         }
         
-        internal void UpdateFeedUrl (string oldUrl, Feed feed)
-        {
-            lock (sync) {
-                url_feed_map.Remove (oldUrl);
-                url_feed_map.Add (feed.Url, feed);
-            }        
-        }
-        
         internal void OnFeedUrlChanged (Feed feed)
         {
             OnFeedEventRaised (feed, FeedUrlChanged);
-        }
+        }*/
         
 #endregion 
     }   

Added: trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/MigoItem.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/MigoItem.cs	Sun May 18 00:19:17 2008
@@ -0,0 +1,53 @@
+//
+// MigoItem.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.Sqlite;
+
+namespace Migo.Syndication
+{
+    // This class is generic b/c of some ideas that I didn't implement yet...could be made non-generic
+    public abstract class MigoItem<T> : ICacheableItem
+    {
+        private long cache_entry_id;
+        public long 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

Added: trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/MigoModelProvider.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/MigoModelProvider.cs	Sun May 18 00:19:17 2008
@@ -0,0 +1,89 @@
+//
+// MigoModelProvider.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.Collections.Generic;
+
+using Hyena;
+using Hyena.Data.Sqlite;
+
+namespace Migo.Syndication
+{
+    // Caches all results retrieved from the database, such that any subsequent retrieval will
+    // return the same instance.
+    public class MigoModelProvider<T> : SqliteModelProvider<T> where T : ICacheableItem, new()
+    {
+        private Dictionary<long, T> full_cache = new Dictionary<long, T> ();
+        
+        public MigoModelProvider (HyenaSqliteConnection connection, string table_name) : base (connection, table_name)
+        {
+        }
+        
+        public override void Save (T target)
+        {
+            base.Save (target);
+            long dbid = PrimaryKeyFor (target);
+            if (!full_cache.ContainsKey (dbid)) {
+                full_cache[dbid] = target;
+            }
+        }
+
+        public override T Load (System.Data.IDataReader reader)
+        {
+            long dbid = PrimaryKeyFor (reader);
+            if (full_cache.ContainsKey (dbid)) {
+                return full_cache[dbid];
+            } else {
+                T item = base.Load (reader);
+                full_cache[dbid] = item;
+                return item;
+            }
+        }
+        
+        public new void Delete (long id)
+        {
+            full_cache.Remove (id);
+            base.Delete (id);
+        }
+        
+        public new void Delete (T item)
+        {
+            full_cache.Remove (PrimaryKeyFor (item));
+            base.Delete (item);
+        }
+        
+        public new void Delete (IEnumerable<T> items)
+        {
+            foreach (T item in items) {
+                full_cache.Remove (PrimaryKeyFor (item));
+            }
+                
+            base.Delete (items);
+        }
+    }
+}
\ No newline at end of file

Added: trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/OpmlParser.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/OpmlParser.cs	Sun May 18 00:19:17 2008
@@ -0,0 +1,175 @@
+//
+// OpmlParser.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.Xml;
+using System.Collections.Generic;
+
+namespace Migo.Syndication
+{
+    public class OpmlParser
+    {
+        private XmlDocument doc;
+        //private XmlNamespaceManager ns_mgr;
+        
+        public OpmlParser (string url, string xml)
+        {
+            doc = new XmlDocument ();
+            try {
+                doc.LoadXml (xml);
+                //ns_mgr = XmlUtils.GetNamespaceManager (doc);
+            } catch (XmlException) {
+                throw new FormatException ("Invalid xml document.");                                  
+            }
+            VerifyOpml ();
+        }
+        
+        public OpmlParser (string url, XmlDocument doc)
+        {
+            this.doc = doc;
+            //ns_mgr = XmlUtils.GetNamespaceManager (doc);
+            VerifyOpml ();
+        }
+    
+        /*public Feed CreateFeed ()
+        {
+            return UpdateFeed (new Feed ());
+        }
+        
+        public Feed UpdateFeed (Feed feed)
+        {
+            try {
+                feed.Title            = XmlUtils.GetXmlNodeText (doc, "/rss/channel/title");
+                feed.Description      = XmlUtils.GetXmlNodeText (doc, "/rss/channel/description");
+                feed.Copyright        = XmlUtils.GetXmlNodeText (doc, "/rss/channel/copyright");
+                feed.ImageUrl         = XmlUtils.GetXmlNodeText (doc, "/rss/channel/itunes:image/@href", ns_mgr);
+                if (String.IsNullOrEmpty (feed.ImageUrl)) {
+                    feed.ImageUrl = XmlUtils.GetXmlNodeText (doc, "/rss/channel/image/url");
+                }
+                feed.Interval         = XmlUtils.GetInt64 (doc, "/rss/channel/interval"); 
+                feed.Language         = XmlUtils.GetXmlNodeText (doc, "/rss/channel/language");
+                feed.LastBuildDate    = XmlUtils.GetRfc822DateTime (doc, "/rss/channel/lastBuildDate");
+                feed.Link             = XmlUtils.GetXmlNodeText (doc, "/rss/channel/link"); 
+                feed.PubDate          = XmlUtils.GetRfc822DateTime (doc, "/rss/channel/pubDate");
+                feed.Ttl              = XmlUtils.GetInt64 (doc, "/rss/channel/ttl");
+                feed.Keywords         = XmlUtils.GetXmlNodeText (doc, "/rss/channel/itunes:keywords", ns_mgr);
+                feed.Category         = XmlUtils.GetXmlNodeText (doc, "/rss/channel/itunes:category/@text", ns_mgr);
+                
+                return feed;
+            } catch (Exception e) {
+                 Hyena.Log.Exception (e);
+            }
+             
+            return null;
+        }
+        
+        public IEnumerable<FeedItem> GetFeedItems (Feed feed)
+        {
+            XmlNodeList nodes = null;
+            try {
+                nodes = doc.SelectNodes ("//item");
+            } catch (Exception e) {
+                Hyena.Log.Exception (e);
+            }
+            
+            if (nodes != null) {
+                foreach (XmlNode node in nodes) {
+                    FeedItem item = null;
+                    
+                    try {
+                        item = ParseItem (node);
+                    } catch (Exception e) {
+                        Hyena.Log.Exception (e);
+                    }
+                    
+                    if (item != null) {
+                        item.Feed = feed;
+                        yield return item;
+                    }
+                }
+            }
+        }
+        
+        public FeedItem ParseItem (XmlNode node)
+        {
+            try {
+                FeedItem item = new FeedItem ();
+                item.Description = XmlUtils.GetXmlNodeText (node, "description");                        
+                item.Title = XmlUtils.GetXmlNodeText (node, "title");                        
+            
+                if (String.IsNullOrEmpty (item.Description) && String.IsNullOrEmpty (item.Title)) {
+                    throw new FormatException ("node:  Either 'title' or 'description' node must exist.");
+                }
+                
+                item.Author            = XmlUtils.GetXmlNodeText (node, "author");
+                item.Comments          = XmlUtils.GetXmlNodeText (node, "comments");
+                item.Guid              = XmlUtils.GetXmlNodeText (node, "guid");
+                item.Link              = XmlUtils.GetXmlNodeText (node, "link");
+                item.Modified          = XmlUtils.GetRfc822DateTime (node, "dcterms:modified");
+                item.PubDate           = XmlUtils.GetRfc822DateTime (node, "pubDate");
+                
+                item.Enclosure = ParseEnclosure (node);
+                
+                return item;
+             } catch (Exception e) {
+                 Hyena.Log.Exception (e);
+             }
+             
+             return null;
+        }
+        
+        public FeedEnclosure ParseEnclosure (XmlNode node)
+        {
+            try {
+                FeedEnclosure enclosure = new FeedEnclosure ();
+                enclosure.Url = XmlUtils.GetXmlNodeText (node, "enclosure/@url");
+                enclosure.FileSize = Math.Max (0, XmlUtils.GetInt64 (node, "enclosure/@length"));
+                enclosure.MimeType = XmlUtils.GetXmlNodeText (node, "enclosure/@type");
+                enclosure.Duration = XmlUtils.GetItunesDuration (node, ns_mgr);
+                enclosure.Keywords = XmlUtils.GetXmlNodeText (node, "itunes:keywords", ns_mgr);
+                return enclosure;
+             } catch (Exception e) {
+                 Hyena.Log.Exception (e);
+             }
+             
+             return null;
+        }*/
+        
+        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.");
+        }
+    }
+}

Modified: trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/Rfc822DateTime.cs
==============================================================================
--- trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/Rfc822DateTime.cs	(original)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/Rfc822DateTime.cs	Sun May 18 00:19:17 2008
@@ -47,7 +47,7 @@
             @"(?<day>\d\d?) " +
             @"(?<month>" + monthsStr + ") " +
             @"(?<year>\d\d(\d\d)?) " +
-            @"(?<hours>[0-2]\d):(?<minutes>[0-5]\d)(:(?<seconds>[0-5]\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;

Modified: trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/RssParser.cs
==============================================================================
--- trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/RssParser.cs	(original)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/RssParser.cs	Sun May 18 00:19:17 2008
@@ -32,13 +32,12 @@
 using System.Xml;
 using System.Collections.Generic;
 
-using Migo.Syndication;
-
 namespace Migo.Syndication
 {
     public class RssParser
     {
         private XmlDocument doc;
+        private XmlNamespaceManager ns_mgr;
         
         public RssParser (string url, string xml)
         {
@@ -46,7 +45,7 @@
             try {
                 doc.LoadXml (xml);
             } catch (XmlException) {
-                throw new FormatException ("Invalid xml document.");                                  
+                throw new FormatException ("Invalid XML document.");                                  
             }
             CheckRss ();
         }
@@ -65,19 +64,22 @@
         public Feed UpdateFeed (Feed feed)
         {
             try {
-                feed.Copyright        = XmlUtils.GetXmlNodeText (doc, "/rss/channel/copyright");
-                feed.Image            = XmlUtils.GetXmlNodeText (doc, "/rss/channel/image/url");
+                feed.Title            = XmlUtils.GetXmlNodeText (doc, "/rss/channel/title");
                 feed.Description      = XmlUtils.GetXmlNodeText (doc, "/rss/channel/description");
+                feed.Copyright        = XmlUtils.GetXmlNodeText (doc, "/rss/channel/copyright");
+                feed.ImageUrl         = XmlUtils.GetXmlNodeText (doc, "/rss/channel/itunes:image/@href", ns_mgr);
+                if (String.IsNullOrEmpty (feed.ImageUrl)) {
+                    feed.ImageUrl = XmlUtils.GetXmlNodeText (doc, "/rss/channel/image/url");
+                }
                 feed.Interval         = XmlUtils.GetInt64 (doc, "/rss/channel/interval"); 
                 feed.Language         = XmlUtils.GetXmlNodeText (doc, "/rss/channel/language");
                 feed.LastBuildDate    = XmlUtils.GetRfc822DateTime (doc, "/rss/channel/lastBuildDate");
                 feed.Link             = XmlUtils.GetXmlNodeText (doc, "/rss/channel/link"); 
                 feed.PubDate          = XmlUtils.GetRfc822DateTime (doc, "/rss/channel/pubDate");
-                feed.Title            = XmlUtils.GetXmlNodeText (doc, "/rss/channel/title");
                 feed.Ttl              = XmlUtils.GetInt64 (doc, "/rss/channel/ttl");
-                feed.LastWriteTime    = DateTime.MinValue;
-                feed.LastDownloadTime = DateTime.Now;
-
+                feed.Keywords         = XmlUtils.GetXmlNodeText (doc, "/rss/channel/itunes:keywords", ns_mgr);
+                feed.Category         = XmlUtils.GetXmlNodeText (doc, "/rss/channel/itunes:category/@text", ns_mgr);
+                
                 return feed;
             } catch (Exception e) {
                  Hyena.Log.Exception (e);
@@ -86,7 +88,7 @@
             return null;
         }
         
-        public IEnumerable<FeedItem> GetFeedItems ()
+        public IEnumerable<FeedItem> GetFeedItems (Feed feed)
         {
             XmlNodeList nodes = null;
             try {
@@ -106,13 +108,14 @@
                     }
                     
                     if (item != null) {
+                        item.Feed = feed;
                         yield return item;
                     }
                 }
             }
         }
         
-        public static FeedItem ParseItem (XmlNode node)
+        public FeedItem ParseItem (XmlNode node)
         {
             try {
                 FeedItem item = new FeedItem ();
@@ -129,7 +132,6 @@
                 item.Link              = XmlUtils.GetXmlNodeText (node, "link");
                 item.Modified          = XmlUtils.GetRfc822DateTime (node, "dcterms:modified");
                 item.PubDate           = XmlUtils.GetRfc822DateTime (node, "pubDate");
-                item.LastDownloadTime  = DateTime.Now;
                 
                 item.Enclosure = ParseEnclosure (node);
                 
@@ -141,13 +143,15 @@
              return null;
         }
         
-        public static FeedEnclosure ParseEnclosure (XmlNode node)
+        public FeedEnclosure ParseEnclosure (XmlNode node)
         {
             try {
                 FeedEnclosure enclosure = new FeedEnclosure ();
                 enclosure.Url = XmlUtils.GetXmlNodeText (node, "enclosure/@url");
-                enclosure.Length = Math.Max (0, XmlUtils.GetInt64 (node, "enclosure/@length"));
+                enclosure.FileSize = Math.Max (0, XmlUtils.GetInt64 (node, "enclosure/@length"));
                 enclosure.MimeType = XmlUtils.GetXmlNodeText (node, "enclosure/@type");
+                enclosure.Duration = XmlUtils.GetITunesDuration (node, ns_mgr);
+                enclosure.Keywords = XmlUtils.GetXmlNodeText (node, "itunes:keywords", ns_mgr);
                 return enclosure;
              } catch (Exception e) {
                  Hyena.Log.Exception (e);
@@ -167,6 +171,9 @@
                     "node: 'title', 'description', and 'link' nodes must exist."
                 );                
             }
+            
+            ns_mgr = XmlUtils.GetNamespaceManager (doc);
+            ns_mgr.AddNamespace ("itunes", "http://www.itunes.com/dtds/podcast-1.0.dtd";);
         }
     }
 }

Modified: trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/XmlUtils.cs
==============================================================================
--- trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/XmlUtils.cs	(original)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.Syndication/XmlUtils.cs	Sun May 18 00:19:17 2008
@@ -31,14 +31,26 @@
 
 namespace Migo.Syndication
 {        
-    static class XmlUtils
+    public static class XmlUtils
     {
+        public static XmlNamespaceManager GetNamespaceManager (XmlDocument doc)
+        {
+            XmlNamespaceManager mgr = new XmlNamespaceManager (doc.NameTable);
+            return mgr;
+        }
+    
         public static string GetXmlNodeText (XmlNode node, string tag)
         {
             XmlNode n = node.SelectSingleNode (tag);
             return (n == null) ? String.Empty : n.InnerText.Trim ();
         }
         
+        public static string GetXmlNodeText (XmlNode node, string tag, XmlNamespaceManager mgr)
+        {
+            XmlNode n = node.SelectSingleNode (tag, mgr);
+            return (n == null) ? String.Empty : n.InnerText.Trim ();
+        }
+        
         public static DateTime GetRfc822DateTime (XmlNode node, string tag)
         {
             DateTime ret = DateTime.MinValue;
@@ -61,6 +73,33 @@
             }
                     
             return ret;              
-        }        
+        }
+        
+        
+        public static TimeSpan GetITunesDuration (XmlNode node, XmlNamespaceManager mgr)
+        {
+            return GetITunesDuration (GetXmlNodeText (node, "itunes:duration", mgr));
+        }
+        
+        public static 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);
+        }
     }
 }

Modified: trunk/banshee/src/Libraries/Migo/Migo/Migo.TaskCore/AsyncCommandQueue/AsyncCommandQueue.cs
==============================================================================
--- trunk/banshee/src/Libraries/Migo/Migo/Migo.TaskCore/AsyncCommandQueue/AsyncCommandQueue.cs	(original)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.TaskCore/AsyncCommandQueue/AsyncCommandQueue.cs	Sun May 18 00:19:17 2008
@@ -52,8 +52,7 @@
         public EventHandler<EventArgs> QueueProcessed;        
         public EventHandler<EventArgs> QueueProcessing; 
         
-        private bool IsProcessed
-        {
+        private bool IsProcessed {
             get {
                 bool ret = false;
 
@@ -67,12 +66,14 @@
             }
         }        
         
-        public virtual WaitHandle WaitHandle
-        {
+        public virtual WaitHandle WaitHandle {
             get { return executingHandle; }
         }
 
-        public AsyncCommandQueue () : this (null) {}
+        public AsyncCommandQueue () : this (null)
+        {
+        }
+        
         public AsyncCommandQueue (object sync)
         {
             userSync = sync;
@@ -119,8 +120,6 @@
                 
                 eventQueue = null;
             }
-            
-            Console.WriteLine ("async - Disposed");
         }
     
         public virtual bool Register (T command)
@@ -213,8 +212,7 @@
                         try {
                             execCommand (e);
                         } catch (Exception ex) {
-                            Console.WriteLine ("{0}:  {1}", GetType ().Name, ex.Message);
-                            Console.WriteLine ("{0}:  {1}", GetType ().Name, ex.StackTrace);                        
+                            Hyena.Log.Exception (ex);
                         }
                     } 
                                        
@@ -246,8 +244,7 @@
                 try {
                     handler (this, new EventArgs ());
                 } catch (Exception ex) {
-                    Console.WriteLine ("{0}: {1}", GetType ().Name, ex.Message);
-                    Console.WriteLine ("{0}: {1}", GetType ().Name, ex.StackTrace);    
+                    Hyena.Log.Exception (ex);
                 }                
             }            
         }

Modified: trunk/banshee/src/Libraries/Migo/Migo/Migo.TaskCore/TaskGroup.cs
==============================================================================
--- trunk/banshee/src/Libraries/Migo/Migo/Migo.TaskCore/TaskGroup.cs	(original)
+++ trunk/banshee/src/Libraries/Migo/Migo/Migo.TaskCore/TaskGroup.cs	Sun May 18 00:19:17 2008
@@ -210,19 +210,18 @@
             }
         }
         
-        public TaskGroup (int maxRunningTasks, 
-                          TaskCollection<T> tasks)
-            : this (maxRunningTasks, tasks, null, null) {}
-
-        public TaskGroup (int maxRunningTasks, 
-                          TaskCollection<T> tasks, 
-                          GroupStatusManager<T> statusManager) 
-            : this (maxRunningTasks, tasks, statusManager, null) {}
-        
-        protected TaskGroup (int maxRunningTasks, 
-                             TaskCollection<T> tasks, 
-                             GroupStatusManager<T> statusManager,
-                             GroupProgressManager<T> progressManager) 
+        public TaskGroup (int maxRunningTasks, TaskCollection<T> tasks)
+            : this (maxRunningTasks, tasks, null, null)
+        {
+        }
+
+        public TaskGroup (int maxRunningTasks, TaskCollection<T> tasks, GroupStatusManager<T> statusManager) 
+            : this (maxRunningTasks, tasks, statusManager, null)
+        {
+        }
+        
+        protected TaskGroup (int maxRunningTasks, TaskCollection<T> tasks,
+                                GroupStatusManager<T> statusManager, GroupProgressManager<T> progressManager) 
         {
             if (maxRunningTasks < 0) {
                 throw new ArgumentException ("maxRunningTasks must be >= 0");
@@ -345,7 +344,8 @@
                 try {
                     OnStarted ();
                     SpawnExecutionThread ();
-                } catch {
+                } catch (Exception e) {
+                    Hyena.Log.Exception (e);
                     OnStopped ();
                     Reset ();                                                            
                     SetExecuting (false);
@@ -355,42 +355,39 @@
 
         protected virtual bool SetCancelled ()
         {
-            bool ret = false;
-            
             lock (sync) {
                 CheckDisposed ();
                 
                 if (executing && !cancelRequested) {
-                    ret = cancelRequested = true;
+                    cancelRequested = true;
+                    return true;
                 }
             }
             
-            return ret;
+            return false;
         }
         
         private bool SetDisposed ()
         {
-            bool ret = false;
-                
             lock (sync) {
                 if (!disposed) {
-                    ret = disposed = true;   
+                    disposed = true;
+                    return true; 
                 }
             }
-                
-            return ret;
+
+            return false;
         }
         
         protected virtual bool SetExecuting (bool exec)
         {
-            bool ret = false;
-            
             lock (sync) {
                 CheckDisposed ();
                 
                 if (exec) {
                     if (!executing && !cancelRequested) {
-                        ret = executing = true;
+                        executing = true;
+                        return true;
                     }
                 } else {
                     executing = false;
@@ -398,7 +395,7 @@
                 }
             }
             
-            return ret;
+            return false;
         }
 
 /*  May implement at some point        
@@ -840,8 +837,7 @@
                                 task.ExecuteAsync ();
                             }
                         } catch (Exception e) {        
-                            Console.WriteLine ("PumpQueue Exception:  {0}", e.Message);
-                            Console.WriteLine (e.StackTrace);
+                            Hyena.Log.Exception (e);
                             
                             try {
                                 gsm.SuspendUpdate = true;

Added: trunk/banshee/src/nuke-gconf-keys
==============================================================================
--- (empty file)
+++ trunk/banshee/src/nuke-gconf-keys	Sun May 18 00:19:17 2008
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+gconftool-2 --recursive-unset /apps/banshee-1

Modified: trunk/banshee/tests/BansheeTests.cs
==============================================================================
--- trunk/banshee/tests/BansheeTests.cs	(original)
+++ trunk/banshee/tests/BansheeTests.cs	Sun May 18 00:19:17 2008
@@ -27,6 +27,7 @@
 //
 
 using System;
+using System.Collections.Generic;
 using System.Reflection;
 using System.Threading;
 
@@ -35,6 +36,34 @@
 using Hyena;
 using Mono.Addins;
 
+public struct TransformPair<F, T>
+{
+    public F From;
+    public T To;
+
+    public TransformPair (F from, T to)
+    {
+        From = from;
+        To = to;
+    }
+
+    public static TransformPair<F, T> [] GetFrom (params object [] objects)
+    {
+        TransformPair<F, T> [] pairs = new TransformPair<F, T> [objects.Length / 2];
+        for (int i = 0; i < objects.Length; i += 2) {
+            pairs[i/2] = new TransformPair<F, T> ((F)objects[i], (T)objects[i+1]);
+        }
+        return pairs;
+    }
+
+    public override string ToString ()
+    {
+        return From.ToString ();
+    }
+}
+
+public delegate To Transform<F, To> (F from);
+
 public abstract class BansheeTests
 {
     public static string Pwd;
@@ -44,20 +73,27 @@
         AddinManager.Initialize (Pwd + "/../bin/");
     }
 
-    public delegate void TestRunner<T> (T item);
-    public static void AssertForEach<T> (System.Collections.Generic.IEnumerable<T> objects, TestRunner<T> runner)
+    public static void AssertForEach<T> (IEnumerable<T> objects, Action<T> runner)
     {
         System.Text.StringBuilder sb = new System.Text.StringBuilder ();
         foreach (T o in objects) {
             try { runner (o); }
-            catch (AssertionException e) { sb.AppendFormat ("Failed processing {0}: {1}\n", o, e.Message); }
-            catch (Exception e) { sb.AppendFormat ("Failed processing {0}: {1}\n", o, e.ToString ()); }
+            catch (AssertionException e) { sb.AppendFormat ("Failed assertion on {0}: {1}\n", o, e.Message); }
+            catch (Exception e) { sb.AppendFormat ("\nCaught exception on {0}: {1}\n", o, e.ToString ()); }
         }
 
         if (sb.Length > 0)
             Assert.Fail ("\n" + sb.ToString ());
     }
 
+    // Fails to compile, causes SIGABRT in gmcs; boo
+    /*public static void AssertTransformsEach<A, B> (IEnumerable<TransformPair<A, B>> pairs, Transform<A, B> transform)
+    {
+        AssertForEach (pairs, delegate (TransformPair<A, B> pair) {
+            Assert.AreEqual (pair.To, transform (pair.From));
+        });
+    }*/
+
     private static Thread main_loop;
     public static void StartBanshee ()
     {

Modified: trunk/banshee/tests/Makefile.am
==============================================================================
--- trunk/banshee/tests/Makefile.am	(original)
+++ trunk/banshee/tests/Makefile.am	Sun May 18 00:19:17 2008
@@ -16,7 +16,8 @@
 	$(srcdir)/Hyena/QueryTests.cs \
 	$(srcdir)/Banshee.Services/SmartPlaylistTests.cs \
 	$(srcdir)/Banshee.ThickClient/GuiTests.cs \
-	$(srcdir)/Banshee.Core/TaglibReadWriteTests.cs
+	$(srcdir)/Banshee.Core/TaglibReadWriteTests.cs \
+	$(srcdir)/Migo/XmlTests.cs
 
 #	Banshee.Core/KernelTests.cs
 #	Banshee.Core/FileNamePatternTests.cs
@@ -29,7 +30,7 @@
 	AssemblyInfo.cs \
 	ConsoleUi.cs
 
-REF_BANSHEE_NUNIT = $(LINK_HYENA_DEPS) $(LINK_BANSHEE_CORE_DEPS) $(LINK_BANSHEE_THICKCLIENT_DEPS) $(LINK_NEREID_DEPS) $(ASSEMBLY_CSFILES) $(REF_BACKEND_UNIX)
+REF_BANSHEE_NUNIT = $(LINK_HYENA_DEPS) $(LINK_BANSHEE_CORE_DEPS) $(LINK_BANSHEE_THICKCLIENT_DEPS) $(LINK_NEREID_DEPS) $(ASSEMBLY_CSFILES) $(REF_BACKEND_UNIX) $(LINK_MIGO_DEPS)
 
 $(ASSEMBLY): $(ASSEMBLY_CSFILES)
 	$(MCS) $(MCS_FLAGS) $(NUNIT_FLAGS) -out:$@ -target:library -r:System.Xml $(REF_BANSHEE_NUNIT) -r:$(DIR_BIN)/Banshee.Unix.dll

Added: trunk/banshee/tests/Migo/XmlTests.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/tests/Migo/XmlTests.cs	Sun May 18 00:19:17 2008
@@ -0,0 +1,79 @@
+//
+// XmlTests.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 NUnit.Framework;
+
+using Migo.Syndication;
+
+[TestFixture]
+public class XmlTests : BansheeTests
+{
+    [Test]
+    public void TestParseDates ()
+    {
+        TransformPair<string, DateTime> [] pairs = TransformPair<string, DateTime>.GetFrom (
+            "Fri, 22 Feb 2008 16:00:00 EST",        DateTime.Parse ("22/02/2008 5.00.00"),
+            "Fri, 15 Feb 2008 4:10:00 EST",         DateTime.Parse ("14/02/2008 17.10.00"),
+            "Tue, 08 Apr 2008 03:37:04 -0400",      DateTime.Parse ("07/04/2008 18.37.04"),
+            "Tue, 26 Feb 2008 03:28:51 -0500",      DateTime.Parse ("25/02/2008 16.28.51"),
+            "Sun, 11 May 2008 01:33:26 -0400",      DateTime.Parse ("10/05/2008 16.33.26"),
+            "Fri, 16 May 2008 16:09:10 -0500",      DateTime.Parse ("16/05/2008 6.09.10"),
+            "Fri, 14 Mar 2008 13:44:53 -0500",      DateTime.Parse ("14/03/2008 3.44.53"),
+            "Fri, 07 December 2007 17:00:00 EST",   DateTime.Parse ("07/12/2007 6.00.00"),
+            "Sat, 08 Mar 2008 12:00:00 EST",        DateTime.Parse ("08/03/2008 1.00.00"),
+            "Sat, 17 May 2008 20:47:57 +0000",      DateTime.Parse ("17/05/2008 15.47.57"),
+            "Sat, 17 May 2008 19:33:42 +0000",      DateTime.Parse ("17/05/2008 14.33.42")
+        );
+
+        AssertForEach (pairs, delegate (TransformPair<string, DateTime> pair) {
+            Assert.AreEqual (pair.To, Rfc822DateTime.Parse (pair.From));
+        });
+    }
+
+    [Test]
+    public void TestParseITunesDuration ()
+    {
+        TransformPair<string, TimeSpan> [] pairs = TransformPair<string, TimeSpan>.GetFrom (
+            null,      TimeSpan.Zero,
+            "",        TimeSpan.Zero,
+            "0",       TimeSpan.Zero,
+            "0:0",     TimeSpan.Zero,
+            "0:0:0",   TimeSpan.Zero,
+            "1:0:0",   new TimeSpan (1, 0, 0),
+            "363",     new TimeSpan (0, 0, 363),
+            "2:45",    new TimeSpan (0, 2, 45),
+            "1:02:22", new TimeSpan (1, 2, 22),
+            "9:0:0",   new TimeSpan (9, 0, 0)
+        );
+
+        AssertForEach (pairs, delegate (TransformPair<string, TimeSpan> pair) {
+            Assert.AreEqual (pair.To, XmlUtils.GetITunesDuration (pair.From));
+        });
+    }
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]