? data/images/source-playlist-uncommited.png
? po/stamp-it
? src/banshee-import
? src/banshee-import.exe.mdb
? src/Banshee.Base/Sources/DapPlaylistSource.cs
Index: data/images/Makefile.am
===================================================================
RCS file: /cvs/gnome/banshee/data/images/Makefile.am,v
retrieving revision 1.36
diff -u -r1.36 Makefile.am
--- data/images/Makefile.am	15 Mar 2006 17:43:35 -0000	1.36
+++ data/images/Makefile.am	26 May 2006 11:21:25 -0000
@@ -51,6 +51,7 @@
 	source-cd-audio.png \
 	source-library.png \
 	source-playlist.png \
+	source-playlist-uncommited.png \
 	source-localqueue.png \
 	star.png \
 	sync-needed.png \
Index: src/PlayerInterface.cs
===================================================================
RCS file: /cvs/gnome/banshee/src/PlayerInterface.cs,v
retrieving revision 1.181
diff -u -r1.181 PlayerInterface.cs
--- src/PlayerInterface.cs	24 May 2006 18:37:40 -0000	1.181
+++ src/PlayerInterface.cs	26 May 2006 11:21:32 -0000
@@ -115,10 +115,10 @@
                 Dnd.TargetUriList
             };
 
-        /*private static TargetEntry [] sourceViewSourceEntries = 
+        private static TargetEntry [] sourceViewSourceEntries = 
             new TargetEntry [] {
                 Dnd.TargetSource
-            };*/
+	    };
             
         private static TargetEntry [] sourceViewDestEntries = 
             new TargetEntry [] {
@@ -383,10 +383,10 @@
             SourceManager.SourceTrackAdded += OnSourceTrackAdded;
             SourceManager.SourceTrackRemoved += OnSourceTrackRemoved;
             
-            /*sourceView.EnableModelDragSource(
+            sourceView.EnableModelDragSource(
                 Gdk.ModifierType.Button1Mask | Gdk.ModifierType.Button3Mask,
                 sourceViewSourceEntries, 
-                DragAction.Copy | DragAction.Move);*/
+                DragAction.Copy);
         
             sourceView.EnableModelDragDest(
                 sourceViewDestEntries, 
@@ -1974,7 +1974,7 @@
             
             switch(md.Run()) {
                 case (int)ResponseType.Ok:
-                    dapSource.Device.Save(Globals.Library.Tracks.Values);
+                    dapSource.Device.Save(LibrarySource.Instance);
                     break;
                 case (int)ResponseType.Apply:
                     dapSource.Device.Save();
Index: src/SourceView.cs
===================================================================
RCS file: /cvs/gnome/banshee/src/SourceView.cs,v
retrieving revision 1.46
diff -u -r1.46 SourceView.cs
--- src/SourceView.cs	22 May 2006 19:27:58 -0000	1.46
+++ src/SourceView.cs	26 May 2006 11:21:33 -0000
@@ -266,6 +266,10 @@
 
             return false;
         }
+
+	//protected override void OnDragBegin (Gdk.DragContext context)
+	//{
+	//}
         
         protected override bool OnDragMotion(Gdk.DragContext context, int x, int y, uint time)
         {
@@ -273,11 +277,9 @@
             SetDragDestRow(null, TreeViewDropPosition.IntoOrAfter);
             Gdk.Drag.Status(context, Gdk.DragAction.Copy, time);
 
-            // FIXME: We need to handle this nicer
-            if(!(SourceManager.ActiveSource is LibrarySource ||
-                SourceManager.ActiveSource is PlaylistSource)) {
-                return false;
-            }
+            //if(!(SourceManager.ActiveSource is LibrarySource || SourceManager.ActiveSource is PlaylistSource)) {
+            //    return false;
+            //}
         
             if(!newPlaylistVisible) {
                 TreeIter library = FindSource(LibrarySource.Instance);
@@ -296,11 +298,11 @@
                 SetDragDestRow(path, TreeViewDropPosition.IntoOrAfter);
                 
                 // TODO: Support other drag destinations
-                if((source is PlaylistSource) || (source is DapSource)) {
-                    return true;
-                }
+                //if((source is PlaylistSource) || (source is DapSource)) {
+                //    return true;
+                //}
 
-                Gdk.Drag.Status(context, 0, time);
+                //Gdk.Drag.Status(context, 0, time);
                 return true;
             }
             
Index: src/Banshee.Base/Makefile.am
===================================================================
RCS file: /cvs/gnome/banshee/src/Banshee.Base/Makefile.am,v
retrieving revision 1.31
diff -u -r1.31 Makefile.am
--- src/Banshee.Base/Makefile.am	22 May 2006 22:51:19 -0000	1.31
+++ src/Banshee.Base/Makefile.am	26 May 2006 11:21:34 -0000
@@ -30,7 +30,7 @@
 	$(srcdir)/AudioCd/AudioCdCore.cs \
 	$(srcdir)/AudioCd/AudioCdTrackInfo.cs \
 	$(srcdir)/AudioCd/AudioCdDisk.cs
-	
+
 DAP_SOURCES = \
 	$(srcdir)/Dap/Dap.cs \
 	$(srcdir)/Dap/DapCore.cs \
@@ -46,6 +46,7 @@
 	$(srcdir)/Sources/PlaylistSource.cs \
 	$(srcdir)/Sources/AudioCdSource.cs \
 	$(srcdir)/Sources/DapSource.cs \
+	$(srcdir)/Sources/DapPlaylistSource.cs \
 	$(srcdir)/Sources/LocalQueueSource.cs
 
 NETWORKMANAGER_SOURCES = \
Index: src/Banshee.Base/Dap/Dap.cs
===================================================================
RCS file: /cvs/gnome/banshee/src/Banshee.Base/Dap/Dap.cs,v
retrieving revision 1.16
diff -u -r1.16 Dap.cs
--- src/Banshee.Base/Dap/Dap.cs	17 Apr 2006 17:44:10 -0000	1.16
+++ src/Banshee.Base/Dap/Dap.cs	26 May 2006 11:21:35 -0000
@@ -124,6 +124,7 @@
         private uint uid;
         private PropertyTable properties = new PropertyTable();
         private ArrayList tracks = new ArrayList(); 
+        private ArrayList playlists = new ArrayList(); 
         private ActiveUserEvent save_report_event;
         private bool is_syncing = false;
         private bool can_cancel_save = true;
@@ -132,11 +133,15 @@
         public event DapTrackListUpdatedHandler TrackAdded;
         public event DapTrackListUpdatedHandler TrackRemoved;
         public event EventHandler TracksCleared;
+	public event EventHandler PlaylistAdded;
+	public event EventHandler PlaylistRemoved;
+	public event EventHandler PlaylistsCleared;
         public event EventHandler PropertiesChanged;
         public event EventHandler Ejected;
         public event EventHandler SaveStarted;
         public event EventHandler SaveFinished;
         public event EventHandler Reactivate;
+	public event EventHandler Loaded;
         
         public Hal.Device HalDevice;
         private Source source;
@@ -181,6 +186,14 @@
                 handler(this, new EventArgs());
             }
         }
+
+	protected void OnLoaded()
+	{
+	    EventHandler handler = Loaded;
+	    if(handler != null) {
+		handler(this, new EventArgs());
+	    }
+	}
         
         protected void InstallProperty(string name, string value)
         {
@@ -214,12 +227,7 @@
                 handler(this, new DapTrackListUpdatedArgs(track));
             }
         }
-        
-        public virtual void Dispose()
-        {
-            SourceManager.RemoveSource(source);
-        }
-        
+
         public void ClearTracks()
         {
             ClearTracks(true);
@@ -236,6 +244,57 @@
                 }
             }
         }
+
+	public void AddPlaylist(DapPlaylistSource dps)
+	{
+	    if(dps == null)
+		return;
+
+	    playlists.Add(dps);
+	    OnPlaylistAdded (dps);
+	}
+
+	public void AddPlaylist(PlaylistSource ps)
+	{
+	    if(ps == null)
+		return;
+
+	    DapPlaylistSource dps = new DapPlaylistSource(ps.Name);
+
+	    // FIXME: Also add tracks we need if there is enough disk space
+	    foreach(TrackInfo ti in ps.Tracks) {
+		if (TrackExistsInList(ti, tracks))
+		    dps.AddTrack(ti);
+	    }
+
+	    AddPlaylist(dps);
+	}
+        
+	public void RemovePlaylist(DapPlaylistSource dps)
+	{
+	    dps.Delete();
+	    playlists.Remove(dps);
+	    OnPlaylistRemoved(dps);
+	}
+
+	public void ClearPlaylists()
+	{
+	    foreach(DapPlaylistSource dps in playlists)
+		dps.Delete();
+
+	    playlists.Clear();
+	    OnPlaylistsCleared();
+
+    	    EventHandler handler = PlaylistsCleared;
+	    if(handler != null) {
+		handler(null, new EventArgs());
+	    }
+	}
+
+        public virtual void Dispose()
+        {
+            SourceManager.RemoveSource(source);
+        }
         
         protected virtual TrackInfo OnTrackAdded(TrackInfo track)
         {
@@ -249,6 +308,26 @@
         protected virtual void OnTracksCleared()
         {
         }
+
+        protected virtual void OnPlaylistAdded(DapPlaylistSource dps)
+        {
+            EventHandler handler = PlaylistAdded;
+	    if(handler != null) {
+		handler(dps, new EventArgs());
+	    }
+        }
+        
+        protected virtual void OnPlaylistRemoved(DapPlaylistSource dps)
+        {
+            EventHandler handler = PlaylistRemoved;
+	    if(handler != null) {
+		handler(dps, new EventArgs());
+	    }
+        }
+
+	protected virtual void OnPlaylistsCleared()
+	{
+	}
         
         public virtual void Activate()
         {
@@ -276,7 +355,7 @@
                 a.TrackNumber == b.TrackNumber;
         }
         
-        protected bool TrackExistsInList(TrackInfo track, ICollection list)
+        protected bool TrackExistsInList(TrackInfo track, IEnumerable list)
         {
             try {
                 foreach(TrackInfo track_b in list) {
@@ -290,15 +369,16 @@
             return false;
         }
 
-        public void Save(ICollection library)
+        public void Save(LibrarySource library)
         {
             Queue remove_queue = new Queue();
             
-            foreach(TrackInfo ti in Tracks) {
-                if(TrackExistsInList(ti, library)) {
+            foreach(TrackInfo ti in library.Tracks) {
+                if(TrackExistsInList(ti, library.Tracks)) {
                     continue;
                 }
                 
+		//RemoveTrack(ti);
                 remove_queue.Enqueue(ti);
             }
             
@@ -306,13 +386,21 @@
                 RemoveTrack(remove_queue.Dequeue() as TrackInfo);
             }
             
-            foreach(TrackInfo ti in library) {
-                if(TrackExistsInList(ti, Tracks) || ti.Uri == null) {
+            foreach(TrackInfo ti in library.Tracks) {
+                if(TrackExistsInList(ti, library.Tracks) || ti.Uri == null) {
                     continue;
                 }
                 
                 AddTrack(ti);
             }
+
+	    foreach(ChildSource cs in library.Children) {
+		if(!(cs is PlaylistSource))
+		    continue;
+
+		PlaylistSource ps = cs as PlaylistSource;
+		AddPlaylist(ps);
+	    }
             
             Save();
         }
@@ -574,12 +662,19 @@
                 return tracks.Count;
             }
         }
-        
+
+       
         public IList Tracks {
             get {
                 return tracks;
             }
         }
+
+	public IList Playlists {
+	    get {
+		return playlists;
+	    }
+	}
         
         public bool IsSyncing {
             get {
Index: src/Banshee.Base/Sources/DapSource.cs
===================================================================
RCS file: /cvs/gnome/banshee/src/Banshee.Base/Sources/DapSource.cs,v
retrieving revision 1.7
diff -u -r1.7 DapSource.cs
--- src/Banshee.Base/Sources/DapSource.cs	17 Apr 2006 17:44:10 -0000	1.7
+++ src/Banshee.Base/Sources/DapSource.cs	26 May 2006 11:21:36 -0000
@@ -48,9 +48,12 @@
         public DapSource(Banshee.Dap.DapDevice device) : base(device.Name, 400)
         {
             this.device = device;
+
             device.SaveStarted += OnDapSaveStarted;
             device.SaveFinished += OnDapSaveFinished;
             device.Reactivate += OnDapReactivate;
+	    device.Loaded += OnDapLoaded;
+
             CanRename = device.CanSetName;
         }
         
@@ -128,6 +131,13 @@
             OnViewChanged();
             OnUpdated();
         }
+
+	private void OnDapLoaded(object o, EventArgs args)
+	{
+	    foreach(DapPlaylistSource dps in device.Playlists) {
+		AddChildSource(dps);
+	    }
+	}
         
         public override void Activate()
         {
Index: src/Banshee.Base/Sources/LibrarySource.cs
===================================================================
RCS file: /cvs/gnome/banshee/src/Banshee.Base/Sources/LibrarySource.cs,v
retrieving revision 1.9
diff -u -r1.9 LibrarySource.cs
--- src/Banshee.Base/Sources/LibrarySource.cs	23 May 2006 18:03:02 -0000	1.9
+++ src/Banshee.Base/Sources/LibrarySource.cs	26 May 2006 11:21:36 -0000
@@ -150,7 +150,7 @@
                 return "/LibraryMenu";
             }
         }
-        
+
         public override IEnumerable Tracks {
             get { return Globals.Library.Tracks.Values; }
         }
Index: src/Banshee.Dap/Ipod/IpodDap.cs
===================================================================
RCS file: /cvs/gnome/banshee/src/Banshee.Dap/Ipod/IpodDap.cs,v
retrieving revision 1.13
diff -u -r1.13 IpodDap.cs
--- src/Banshee.Dap/Ipod/IpodDap.cs	9 Apr 2006 21:30:26 -0000	1.13
+++ src/Banshee.Dap/Ipod/IpodDap.cs	26 May 2006 11:21:37 -0000
@@ -37,7 +37,8 @@
 
 using Banshee.Base;
 using Banshee.Dap;
- 
+using Banshee.Sources;
+
 namespace Banshee.Dap.Ipod
 {
     [DapProperties(DapType = DapType.NonGeneric, PipelineName="Ipod")]
@@ -138,6 +139,17 @@
                     IpodDapTrackInfo track = new IpodDapTrackInfo(song);
                     AddTrack(track);            
                 }
+
+		foreach(Playlist playlist in device.SongDatabase.Playlists) {
+		    DapPlaylistSource dps = new DapPlaylistSource (playlist.Name);
+
+		    foreach(Song song in playlist.Songs) {
+			IpodDapTrackInfo track = new IpodDapTrackInfo(song);
+			dps.AddTrack(track);
+		    }
+
+		    AddPlaylist(dps);
+		}
             } else {
                 BuildDatabaseUnsupportedWidget();
             }
@@ -145,9 +157,32 @@
             if(previous_database_supported != database_supported) {
                 OnPropertiesChanged();
             }
+
+	    OnLoaded();
         }
-        
-        public override void Eject()
+
+	// FIXME: Lame comparsion
+        private string ToLower(string str)
+	{
+            return str == null ? null : str.ToLower();
+	}
+
+        private bool TrackSongCompare(TrackInfo a, Song b)
+        {
+            return ToLower(a.Title) == ToLower(b.Title) &&
+                ToLower(a.Album) == ToLower(b.Album) &&
+                ToLower(a.Artist) == ToLower(b.Artist) &&
+                a.Year == b.Year &&
+                a.TrackNumber == b.TrackNumber;
+        }
+
+	private void DeleteAllPlaylists (SongDatabase db)
+	{
+	    while (db.Playlists.Length > 0)
+		db.RemovePlaylist (db.Playlists [db.Playlists.Length-1]);
+	}
+	
+	public override void Eject()
         {
             device.Eject();
             base.Eject();
@@ -168,6 +203,12 @@
                 }
             }
             
+	    DeleteAllPlaylists (device.SongDatabase);
+	    
+	    foreach(DapPlaylistSource ps in Playlists) {
+		CommitPlaylistToDevice (ps);
+	    }
+	    
             device.SongDatabase.SaveProgressChanged += delegate(object o, SaveProgressArgs args)
             {
                 double progress = args.CurrentSong == null ? 0.0 : args.TotalProgress;
@@ -242,6 +283,30 @@
                 song.Genre = String.Empty;
             }
         }
+	
+	private void CommitPlaylistToDevice (DapPlaylistSource source)
+	{
+	    Playlist playlist = device.SongDatabase.LookupPlaylist (source.Name);
+	    
+	    // FIXME: Incrementally update. Maybe later :-)
+	    
+	    if (playlist != null)
+		device.SongDatabase.RemovePlaylist (playlist);
+	    
+	    playlist = device.SongDatabase.CreatePlaylist (source.Name);
+	    
+	    foreach (TrackInfo track in source.Tracks) {
+		
+		foreach(Song song in device.SongDatabase.Songs) {
+		    
+		    if (!TrackSongCompare (track, song))
+			continue;
+		    
+		    playlist.AddSong (song);
+		}
+	    }
+       }
+
         
         public override Gdk.Pixbuf GetIcon(int size)
         {
--- /dev/null	2006-05-22 16:25:23.000000000 +0200
+++ src/Banshee.Base/Sources/DapPlaylistSource.cs	2006-05-25 18:05:48.000000000 +0200
@@ -0,0 +1,200 @@
+/***************************************************************************
+ *  DapPlaylistSource.cs
+ *
+ *  Copyright (C) 2006 Novell, Inc.
+ *  Written by Lukas Lipka <lukaslipka@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.Data;
+using System.Collections;
+using Mono.Unix;
+
+using Banshee.Base;
+
+namespace Banshee.Sources
+{
+    public class DapPlaylistSource : ChildSource
+    {
+        private ArrayList tracks = new ArrayList();
+        private Queue remove_queue = new Queue();
+        private Queue append_queue = new Queue();
+
+        public DapPlaylistSource() : base(Catalog.GetString("New Playlist"), 500)
+        {
+        }
+
+        public DapPlaylistSource(string name) : base(name, 500)
+        {
+        }
+      
+        protected override bool UpdateName(string oldName, string newName)
+        {
+            if(oldName.Equals(newName)) {
+                return false;
+            }
+            
+	    // FIXME: Check for same name
+            /*if(PlaylistUtil.PlaylistExists(newName)) {
+                LogCore.Instance.PushWarning(
+                    Catalog.GetString("Cannot Rename Playlist"),
+                    Catalog.GetString("A playlist with this name already exists. Please choose another name."));
+                return false;
+		}*/
+            
+            Name = newName;
+            return true;
+        }
+ 
+        public override void AddTrack(TrackInfo track)
+        {
+            if(track is LibraryTrackInfo) {
+                lock(TracksMutex) {
+                    tracks.Add(track);
+                    append_queue.Enqueue(track);
+                }
+                OnUpdated();
+            }
+        }
+
+        public override void RemoveTrack(TrackInfo track)
+        {
+            lock(TracksMutex) {
+                tracks.Remove(track);
+                remove_queue.Enqueue(track);
+            }
+        }
+
+        public override void Commit()
+        {
+        }
+
+        public void Delete()
+        {
+            tracks.Clear();
+            append_queue.Clear();
+            remove_queue.Clear();
+            SourceManager.RemoveSource(this);
+        }
+        
+        /*public override void Reorder(TrackInfo track, int position)
+        {
+            lock(TracksMutex) {
+                int sql_position = 1;
+            
+                if(position > 0) {
+                    TrackInfo sibling = tracks[position] as TrackInfo;
+                    if(sibling == track || sibling == null) {
+                        return;
+                    }
+                    
+                    sql_position = Convert.ToInt32(Globals.Library.Db.QuerySingle(String.Format(
+                        @"SELECT ViewOrder
+                            FROM PlaylistEntries
+                            WHERE PlaylistID = '{0}'
+                                AND TrackID = '{1}'
+                            LIMIT 1", id, sibling.TrackId)
+                    ));
+                } else if(tracks[position] == track) {
+                    return;
+                } 
+                
+                Globals.Library.Db.Execute(String.Format(
+                    @"UPDATE PlaylistEntries
+                        SET ViewOrder = ViewOrder + 1
+                        WHERE PlaylistID = '{0}'
+                            AND ViewOrder >= '{1}'",
+                    id, sql_position
+                ));
+                
+                Globals.Library.Db.Execute(String.Format(
+                    @"UPDATE PlaylistEntries
+                        SET ViewOrder = '{1}'
+                        WHERE PlaylistID = '{0}'
+                            AND TrackID = '{2}'",
+                    id, sql_position, track.TrackId
+                ));
+                
+                tracks.Remove(track);
+                tracks.Insert(position, track);
+            }
+        }*/
+        
+        private void OnLibraryTrackRemoved(object o, LibraryTrackRemovedArgs args)
+        {
+            if(args.Track != null) {
+                if(tracks.Contains(args.Track)) {
+                    RemoveTrack(args.Track);
+                    
+                    if(Count == 0) {
+                        Delete();
+                    } else {
+                        Commit();
+                    }
+                }          
+            } else if (args.Tracks == null) {
+		return;
+	    }
+            
+            int removed_count = 0;
+            
+            lock(TracksMutex) {
+                foreach(TrackInfo track in args.Tracks) {
+                    if(tracks.Contains(track)) {
+                        tracks.Remove(track);
+                        remove_queue.Enqueue(track);
+                        removed_count++;
+                    }
+                }
+            }
+            
+            if(removed_count > 0) {
+                if(Count == 0) {
+                    Delete();
+                } else {
+                    Commit();
+                }
+            }
+        }
+        
+        public override IEnumerable Tracks {
+            get { return tracks; }
+        }
+        
+        public override object TracksMutex {
+            get { return tracks.SyncRoot; }
+        }
+        
+        public override int Count {
+            get { return tracks.Count; }
+        }  
+        
+        private static Gdk.Pixbuf commited_icon = IconThemeUtils.LoadIcon(22, "source-playlist");
+        private static Gdk.Pixbuf uncommited_icon = IconThemeUtils.LoadIcon(22, "source-playlist-uncommited");
+
+        public override Gdk.Pixbuf Icon {
+            get { return commited_icon; }
+        }
+    }
+}
