[Banshee-List] Basic tagging functionality



Hi,


I am new to Banshee but noticed the clean architecture and the huge potential.
Due to HAL and a small patch there, I got my digital audio player working and
can mount the music on my external hard drive to my laptop as digital audio
player by using the .audio_player file.


Many people are using the following directory hierarchy to organize their MP3
files and find them back:

Artist/Album/TrackNo - Track.mp3

Unfortunately my music library has grown over time and does not match this
hierarchy since it looks more like the following:

genre/Artist1 - Track1.mp3
genre/subgenre/Artist2 - Track2.mp3
genre/subgenre/Artist3/Artist3 - Track3.mp3
genre/subgenre/Artist3/Artist3 - Track4.mp3
mood/Artist4/Album1/Artist4 - Track5.mp3
mood/subgenre/Artist5 - Album2/TrackNo - Artist5 - Track6.mp3

When searching for music in Banshee I noticed that a lot of the information like
genre, subgenre and mood is lost in the UI. That's why I have written a patch for
Banshee.

The patch provides basic tagging functionality for Banshee 0.13.1. The tags are
read from a file .tag which can be placed in each directory (like genre, subgenre,
mood or also Artist2 and so) and applies it to the tracks in those directory. The
file can contain multiple tags each on a new line and comments with a sharp sign.
These tags are then stored comma-separated in the column Tags of the Tracks table of
banshee.db. The search widget at the upper right side provides the Tag column.
Currently, you can only search for a single tag or multiple tags in the right order,
e.g.
.tag contains
>>>
tag1
tag2
<<<

So you can search in the UI for tag1,tag2 but do not find anything when looking
for tag2,tag1.

It is necessary to trash ~/.config/banshee directory (after making a backup) and to
rescan your sources.

Please give me advice since this is my first C# code and I am sure I have missed
something in the architecture of Banshee or the .NET/Mono class library.


Michael.
diff -Naur ../banshee-0.13.1.orig/ChangeLog ./ChangeLog
--- ../banshee-0.13.1.orig/ChangeLog	2007-08-20 05:15:59.000000000 +0200
+++ ./ChangeLog	2007-09-23 19:08:47.000000000 +0200
@@ -1,3 +1,45 @@
+2007-09-23  Michael Große <mgrosze web de>
+	* /src/Core/Banshee.Base/Database.cs: Initial implementation of a
+	tagging feature. Tags are attached directly to the track as one
+	column separated by commas.
+	
+	* src/Core/Banshee/PlayerInterface.cs: Added column Tag to the
+	search widget
+
+	* data/UIManagerLayout.xml: Added context menu entry to search for
+	same Tags
+	
+	* src/Core/Banshee.Base/Banshee.Library/Import.cs: Added handler
+	to read file .tag which contains tags for this directory
+	
+	* src/Core/Banshee.Base/Banshee.PlayerMigration/AmarokPlayerImport.cs:
+	Add parameter tag of LibraryTrackInfo
+	
+	* src/Core/Banshee.Base/Banshee.PlayerMigration/RhythmboxPlayerImport.cs:
+	Add parameter tag of LibraryTrackInfo
+	
+	* src/Core/Banshee.Base/Banshee.SmartPlaylist/Editor.cs: Add Tags
+	condition
+	
+	* src/Core/Banshee.Base/Banshee.PlayerMigration/AmarokPlayerImport.cs:
+	Add parameter tag of LibraryTrackInfo
+	
+	* src/Core/Banshee.Base/LibraryTrackInfo.cs: Add parameter tag of
+	LibraryTrackInfo
+		
+	* src/Core/Banshee.Base/Database.cs: Add column Tags in Tracks
+	table and migration steps
+	
+	* src/Core/Banshee.Base/ImportManager.cs: Change Queue to
+	PriorityQueue to add tracks before tags to Library. New delegate
+	for tag handling
+
+	* src/Core/Banshee.Base/Library.cs: Add method AddTag to tag all
+	tracks in a certain directory
+
+	* src/Core/Banshee.Base/Library.cs: Add method AddTag to tag all
+	tracks in a certain directory
+
 2007-08-19  Aaron Bockover  <abock gnome org>
 
 	0.13.1 Released
diff -Naur ../banshee-0.13.1.orig/data/UIManagerLayout.xml ./data/UIManagerLayout.xml
--- ../banshee-0.13.1.orig/data/UIManagerLayout.xml	2007-08-20 04:59:40.000000000 +0200
+++ ./data/UIManagerLayout.xml	2007-09-23 19:02:15.000000000 +0200
@@ -104,6 +104,7 @@
             <menuitem name="SearchForSameArtist" action="SearchForSameArtistAction" />
             <menuitem name="SearchForSameAlbum" action="SearchForSameAlbumAction" />
             <menuitem name="SearchForSameGenre" action="SearchForSameGenreAction" />
+            <menuitem name="SearchForSameTag" action="SearchForSameTagAction" />
         </menu>
         <separator />
         <menuitem name="JumpToPlaying" action="JumpToPlayingAction" />
diff -Naur ../banshee-0.13.1.orig/src/Core/Banshee/PlayerInterface.cs ./src/Core/Banshee/PlayerInterface.cs
--- ../banshee-0.13.1.orig/src/Core/Banshee/PlayerInterface.cs	2007-08-20 04:59:40.000000000 +0200
+++ ./src/Core/Banshee/PlayerInterface.cs	2007-09-23 18:49:39.000000000 +0200
@@ -457,6 +457,7 @@
             searchEntry.AddFilterOption((int)TrackFilterType.AlbumTitle, Catalog.GetString("Album Title"));
             searchEntry.AddFilterOption((int)TrackFilterType.Genre, Catalog.GetString("Genre"));
             searchEntry.AddFilterOption((int)TrackFilterType.Year, Catalog.GetString("Year"));
+            searchEntry.AddFilterOption((int)TrackFilterType.Tag, Catalog.GetString("Tag"));
             
             searchEntry.Activated += delegate {
                 if(!SourceManager.ActiveSource.HandlesSearch) {
@@ -1349,13 +1350,16 @@
                 matches = new string [] { ti.Genre };
             } else if((filter_type & TrackFilterType.Year) == TrackFilterType.Year) {
                 matches = new string [] { ti.Year.ToString() };
+            } else if((filter_type & TrackFilterType.Tag) == TrackFilterType.Tag) {
+                matches = new string [] { TagUtil.ToString(ti.Tags) };
             } else {
                 matches = new string [] {
                     ti.Artist,
                     ti.Album,
                     ti.Title,
                     ti.Genre,
-                    ti.Year.ToString()
+                    ti.Year.ToString(),
+                    TagUtil.ToString(ti.Tags)
                 };
             }
             
@@ -2459,7 +2463,8 @@
         {
             Artist,
             Album,
-            Genre
+            Genre,
+            Tag
         }
 
         private void OnSearchForSameAlbumAction(object o, EventArgs args)
@@ -2477,6 +2482,11 @@
             SearchBySelectedTrack(SearchTrackCriteria.Genre);
         }
 
+        private void OnSearchForSameTagAction(object o, EventArgs args)
+        {
+            SearchBySelectedTrack(SearchTrackCriteria.Tag);
+        }
+
         private void SearchBySelectedTrack(SearchTrackCriteria criteria)
         {
             if(playlistView.Selection.CountSelectedRows() <= 0) {
@@ -2505,6 +2515,10 @@
                     searchEntry.ActivateFilter((int)TrackFilterType.Genre);
                     searchEntry.Query = track.Genre;
                     break;
+                case SearchTrackCriteria.Tag:
+                    searchEntry.ActivateFilter((int)TrackFilterType.Tag);
+                    searchEntry.Query = TagUtil.ToString(track.Tags);
+                    break;
             }
 
             suspendSearch = false;
diff -Naur ../banshee-0.13.1.orig/src/Core/Banshee.Base/ActionManager.cs ./src/Core/Banshee.Base/ActionManager.cs
--- ../banshee-0.13.1.orig/src/Core/Banshee.Base/ActionManager.cs	2007-08-20 04:59:40.000000000 +0200
+++ ./src/Core/Banshee.Base/ActionManager.cs	2007-09-23 19:01:09.000000000 +0200
@@ -258,6 +258,10 @@
                     Catalog.GetString("By Matching _Genre"), null,
                     Catalog.GetString("Search all songs of this genre"), null),
                     
+                new ActionEntry("SearchForSameTagAction", null,
+                    Catalog.GetString("By Matching _Tag"), null,
+                    Catalog.GetString("Search all songs of this tag"), null),
+                    
                 new ActionEntry("AddToPlaylistAction", Stock.Add,
                     Catalog.GetString("Add _to Playlist"), null,
                     Catalog.GetString("Append selected songs to playlist or create new playlist from selection"), null),
diff -Naur ../banshee-0.13.1.orig/src/Core/Banshee.Base/Banshee.Library/Import.cs ./src/Core/Banshee.Base/Banshee.Library/Import.cs
--- ../banshee-0.13.1.orig/src/Core/Banshee.Base/Banshee.Library/Import.cs	2007-07-31 23:04:44.000000000 +0200
+++ ./src/Core/Banshee.Base/Banshee.Library/Import.cs	2007-09-23 18:31:11.000000000 +0200
@@ -28,6 +28,8 @@
  
 using System;
 using System.IO;
+using System.Collections;
+using System.Collections.Generic;
 using Mono.Unix;
 using Banshee.Base;
  
@@ -38,6 +40,7 @@
         static Import()
         {
             ImportManager.Instance.ImportRequested += OnImportManagerImportRequested;
+            ImportManager.Instance.DirectoryTagRequested += OnImportManagerDirectoryTagRequested;
         }
     
         public static void QueueSource(UriList uris)
@@ -65,6 +68,48 @@
             return ImportManager.Instance.IsImportingInProgress();
         }
 
+        private static void OnImportManagerDirectoryTagRequested(object o, ImportEventArgs args)
+        {
+            ArrayList tags = new ArrayList();
+
+            StreamReader stream = null;
+            try { 
+                stream = new StreamReader(args.FileName);
+                string line;
+     
+                // Read the file line-by-line
+                while((line = stream.ReadLine()) != null) {
+                    line = line.Trim();
+
+                    // Ignore empty tags or comments
+                    if (line.Length == 0 || line[0] == '#')
+                    {
+                        continue;
+                    }
+
+                    tags.Add(line);
+                } 
+            } catch(IOException exc) { 
+                // Ignore tag file in case of I/O problems
+                return;
+
+            } finally {
+                if (stream != null)
+                {
+                    stream.Close(); 
+                }
+            }  
+
+            // FIXME: Add a GetDirectory function in IFile in IO abstraction
+            string directory = args.FileName.Substring(0, args.FileName.LastIndexOf('/'));
+
+            // Add the tags found in the file individually
+            foreach (string tag in tags)
+            {
+                Globals.Library.AddTag(directory, tag);
+            }
+        }
+
         private static void OnImportManagerImportRequested(object o, ImportEventArgs args)
         {
             try {
diff -Naur ../banshee-0.13.1.orig/src/Core/Banshee.Base/Banshee.PlayerMigration/AmarokPlayerImport.cs ./src/Core/Banshee.Base/Banshee.PlayerMigration/AmarokPlayerImport.cs
--- ../banshee-0.13.1.orig/src/Core/Banshee.Base/Banshee.PlayerMigration/AmarokPlayerImport.cs	2007-07-31 23:04:44.000000000 +0200
+++ ./src/Core/Banshee.Base/Banshee.PlayerMigration/AmarokPlayerImport.cs	2007-09-23 11:47:42.000000000 +0200
@@ -178,7 +178,7 @@
                      
                          try {
                              LibraryTrackInfo ti = new LibraryTrackInfo (uri, artist, album, title, genre, track_number, 0, year, duration,
-                                 String.Empty, RemoteLookupStatus.NoAttempt);
+                                 String.Empty, RemoteLookupStatus.NoAttempt, null);
                              ti.Rating = rating;
                              ti.PlayCount = playcount;
                          } catch (Exception e) {
diff -Naur ../banshee-0.13.1.orig/src/Core/Banshee.Base/Banshee.PlayerMigration/RhythmboxPlayerImport.cs ./src/Core/Banshee.Base/Banshee.PlayerMigration/RhythmboxPlayerImport.cs
--- ../banshee-0.13.1.orig/src/Core/Banshee.Base/Banshee.PlayerMigration/RhythmboxPlayerImport.cs	2007-07-31 23:04:44.000000000 +0200
+++ ./src/Core/Banshee.Base/Banshee.PlayerMigration/RhythmboxPlayerImport.cs	2007-09-23 11:47:15.000000000 +0200
@@ -131,7 +131,7 @@
                 
                 try {
                     LibraryTrackInfo ti = new LibraryTrackInfo (uri, artist, album, title, genre, track_number, 0, year, duration,
-                        String.Empty, RemoteLookupStatus.NoAttempt);
+                        String.Empty, RemoteLookupStatus.NoAttempt, null);
                     ti.Rating = rating;
                     ti.DateAdded = date_added;
                     ti.PlayCount = play_count;
diff -Naur ../banshee-0.13.1.orig/src/Core/Banshee.Base/Database.cs ./src/Core/Banshee.Base/Database.cs
--- ../banshee-0.13.1.orig/src/Core/Banshee.Base/Database.cs	2007-07-31 23:04:45.000000000 +0200
+++ ./src/Core/Banshee.Base/Database.cs	2007-09-23 18:35:00.000000000 +0200
@@ -74,7 +74,8 @@
                    	LastPlayedStamp INTEGER,
                    	DateAddedStamp INTEGER,
                    	
-                   	RemoteLookupStatus INTEGER
+                   	RemoteLookupStatus INTEGER,
+                   	Tags TEXT
                 )");
             }
             
@@ -144,6 +145,13 @@
                 LogCore.Instance.PushDebug("Adding new database column", "Playlists.SortType INTEGER");
                 Execute("ALTER TABLE Playlists ADD SortType INTEGER NOT NULL DEFAULT 0");
             }
+
+            try {
+                QuerySingle("SELECT Tags FROM Tracks LIMIT 1");
+            } catch(ApplicationException) {
+                LogCore.Instance.PushDebug("Adding new database column", "Tags TEXT");
+                Execute("ALTER TABLE Tracks ADD Tags TEXT");
+            }            
         }
     }
 }
diff -Naur ../banshee-0.13.1.orig/src/Core/Banshee.Base/ImportManager.cs ./src/Core/Banshee.Base/ImportManager.cs
--- ../banshee-0.13.1.orig/src/Core/Banshee.Base/ImportManager.cs	2007-07-31 23:04:44.000000000 +0200
+++ ./src/Core/Banshee.Base/ImportManager.cs	2007-09-23 18:39:22.000000000 +0200
@@ -67,19 +67,28 @@
         private static Gdk.Pixbuf user_event_icon = IconThemeUtils.LoadIcon(22, "system-search", Stock.Find);
         private static readonly object user_event_mutex = new object();
         
-        private Queue path_queue;
+        private PriorityQueue<string> path_queue;
         private ActiveUserEvent user_event;
         private int total_count;
         private int processed_count;
         private int scan_ref_count = 0;
         private bool processing_queue = false;
-        
+
+        // Directory tags need to be processed after all files from the directory are loaded
+        // to apply the tags to all files
+        private int PriorityDirectoryTag = 1;
+        private int PriorityFile = 2;
+
+        // Name of the hidden file which contains the tags for all files in this directory
+        private String TagFileName = ".tag";
+
         public event ImportEventHandler ImportRequested;
+        public event ImportEventHandler DirectoryTagRequested;
         public event EventHandler ImportFinished;
         
         public ImportManager()
         {
-            path_queue = new Queue();
+            path_queue = new PriorityQueue<string>();
         }
         
         public bool IsImportingInProgress() 
@@ -148,15 +157,26 @@
             DestroyUserEvent();
         }
         
-        private void Enqueue(string path)
+        private void EnqueueFile(string path)
         {
             if(path_queue.Contains(path)) {
                 return;
             }
-            
+
             total_count++;
             lock(path_queue.SyncRoot) {
-                path_queue.Enqueue(path);
+                path_queue.Enqueue(path, PriorityFile);
+            }
+        }
+        
+        private void EnqueueDirectoryTag(string path)
+        {
+            if(path_queue.Contains(path)) {
+                return;
+            }
+                        
+            lock(path_queue.SyncRoot) {
+                path_queue.Enqueue(path, PriorityDirectoryTag);
             }
         }
         
@@ -216,7 +236,9 @@
             if(is_regular_file) {
                 try {
                     if(!Path.GetFileName(source).StartsWith(".")) {
-                        Enqueue(source);
+                        EnqueueFile(source);
+                    } else if (Path.GetFileName(source) == TagFileName) {
+                        EnqueueDirectoryTag(source);
                     }
                 } catch(System.ArgumentException) {
                     // If there are illegal characters in path
@@ -256,19 +278,35 @@
             while(path_queue.Count > 0) {
                 CheckForCanceled();
                 
-                string filename = path_queue.Dequeue() as string;
-                    
-                ImportEventHandler handler = ImportRequested;
-                if(handler != null && filename != null) {
-                    ImportEventArgs args = new ImportEventArgs();
-                    args.FileName = filename;
-                    handler(this, args);
-                    UpdateCount(args.ReturnMessage);
-                } else {
-                    UpdateCount(null);
+                PriorityQueueElement<string> elem;
+                lock(path_queue.SyncRoot) {
+                    elem = path_queue.Dequeue();
+                }
+                int priority = elem.Priority;
+                string filename = elem.Object;
+                
+                if (priority == PriorityFile) {
+                    ImportEventHandler handler = ImportRequested;
+                    if(handler != null && filename != null) {
+                        ImportEventArgs args = new ImportEventArgs();
+                        args.FileName = filename;
+                        handler(this, args);
+                        UpdateCount(args.ReturnMessage);
+                    } else {
+                        UpdateCount(null);
+                    }
+
+                } else if (priority == PriorityDirectoryTag) {
+                    ImportEventHandler handler = DirectoryTagRequested;
+                    if(handler != null && filename != null) {
+                        ImportEventArgs args = new ImportEventArgs();
+                        args.FileName = filename;
+                        handler(this, args);
+                        UpdateCount(args.ReturnMessage);
+                    }
                 }
             }
-            
+                
             }
             
             path_queue.Clear();
diff -Naur ../banshee-0.13.1.orig/src/Core/Banshee.Base/Library.cs ./src/Core/Banshee.Base/Library.cs
--- ../banshee-0.13.1.orig/src/Core/Banshee.Base/Library.cs	2007-07-31 23:04:45.000000000 +0200
+++ ./src/Core/Banshee.Base/Library.cs	2007-09-23 18:22:19.000000000 +0200
@@ -297,6 +297,37 @@
             remove_queue.Clear();
         }
         
+        
+        public void AddTag (string directory, string tag)
+        {
+            // FIXME: Tag shall not contain a ","
+            lock (((IDictionary) TracksFnKeyed).SyncRoot) {
+                foreach (string f in TracksFnKeyed.Keys) {
+                    // add tag to files within directory
+                    if (f.StartsWith (directory)) {
+                        if (TracksFnKeyed[f].Tags.Length != 0) {
+                            int i = 0;
+                            for (; i < TracksFnKeyed[f].Tags.Length; i++) {
+                                if (TracksFnKeyed[f].Tags[i] == tag) {
+                                    break;
+                                }
+                            }
+                            // tag not present yet
+                            if (i == TracksFnKeyed[f].Tags.Length) {
+                                ArrayList list = new ArrayList (TracksFnKeyed[f].Tags);
+                                list.Add (tag);
+                                TracksFnKeyed[f].Tags = (string[]) list.ToArray (typeof (string));
+                            }
+                        } else {
+                            // no tag yet for this track
+                            TracksFnKeyed[f].Tags = new String[] { tag };
+                        }
+                    }
+                }
+            }
+        }
+
+        
         public LibraryTrackInfo GetTrack(int id)
         {
             return Tracks[id] as LibraryTrackInfo;
diff -Naur ../banshee-0.13.1.orig/src/Core/Banshee.Base/LibraryTrackInfo.cs ./src/Core/Banshee.Base/LibraryTrackInfo.cs
--- ../banshee-0.13.1.orig/src/Core/Banshee.Base/LibraryTrackInfo.cs	2007-07-31 23:04:45.000000000 +0200
+++ ./src/Core/Banshee.Base/LibraryTrackInfo.cs	2007-09-23 18:42:49.000000000 +0200
@@ -149,7 +149,8 @@
 
         public LibraryTrackInfo(SafeUri uri, string artist, string album, 
            string title, string genre, uint track_number, uint track_count,
-           int year, TimeSpan duration, string asin, RemoteLookupStatus remote_lookup_status)
+           int year, TimeSpan duration, string asin, RemoteLookupStatus remote_lookup_status,
+           string[] tags)
         {
             this.uri = uri;
             track_id = 0;
@@ -168,6 +169,12 @@
             this.remote_lookup_status = remote_lookup_status;
             
             this.date_added = DateTime.Now;
+        
+            if (tags != null) {
+                this.tags = tags;
+            } else {
+                this.tags = new string[0];
+            }
             
             CheckIfExists(uri);
             
@@ -180,7 +187,7 @@
         public LibraryTrackInfo(SafeUri uri, TrackInfo track) : this(
             uri, track.Artist, track.Album, track.Title, track.Genre,
             track.TrackNumber, track.TrackCount, track.Year, track.Duration, 
-            track.Asin, track.RemoteLookupStatus)
+            track.Asin, track.RemoteLookupStatus, track.Tags)
         {
         }
     
@@ -353,6 +360,7 @@
         
         private void SaveToDatabase(bool retryIfFail)
         {
+		
             DbCommand command = BuildCommand(track_id, "Tracks",
                 "TrackID", track_id <= 0 ? null : (object)track_id,
                 "Uri", uri.AbsoluteUri,
@@ -376,7 +384,8 @@
                 "Rating", rating, 
                 "NumberOfPlays", play_count, 
                 "LastPlayedStamp", DateTimeUtil.FromDateTime(last_played),
-                "RemoteLookupStatus", (int)remote_lookup_status);
+                "RemoteLookupStatus", (int)remote_lookup_status,
+                "Tags", TagUtil.ToString(tags));
 
             try {
                 Globals.Library.Db.Execute(command);
@@ -451,6 +460,9 @@
             if(temp_stamp > 0) {
                 date_added = DateTimeUtil.ToDateTime(temp_stamp);
             }
+    
+            string tagsString = reader["Tags"] as string;
+            tags = TagUtil.FromString(tagsString);
         }
 		
         private void LoadFromFile(string filename)
@@ -459,6 +471,7 @@
             track_id = 0;            
             StreamTagger.TrackInfoMerge(this, StreamTagger.ProcessUri(Uri));
             this.date_added = DateTime.Now;
+            this.tags = new string[0];
         }
 
         public override void Save()
diff -Naur ../banshee-0.13.1.orig/src/Core/Banshee.Base/TrackFilterType.cs ./src/Core/Banshee.Base/TrackFilterType.cs
--- ../banshee-0.13.1.orig/src/Core/Banshee.Base/TrackFilterType.cs	2007-07-31 23:04:44.000000000 +0200
+++ ./src/Core/Banshee.Base/TrackFilterType.cs	2007-09-22 22:08:16.000000000 +0200
@@ -38,6 +38,7 @@
         ArtistName = 1 << 1,
         AlbumTitle = 1 << 2,
         Genre      = 1 << 3,
-        Year       = 1 << 4      
+        Year       = 1 << 4,
+        Tag        = 1 << 5      
     }
 }
diff -Naur ../banshee-0.13.1.orig/src/Core/Banshee.Base/TrackInfo.cs ./src/Core/Banshee.Base/TrackInfo.cs
--- ../banshee-0.13.1.orig/src/Core/Banshee.Base/TrackInfo.cs	2007-07-31 23:04:45.000000000 +0200
+++ ./src/Core/Banshee.Base/TrackInfo.cs	2007-09-23 18:43:57.000000000 +0200
@@ -76,6 +76,7 @@
         protected uint rating;
         protected uint play_count;
         protected DateTime last_played;
+        protected string[] tags;
 
         protected TimeSpan duration;
         protected uint track_number;
@@ -285,6 +286,11 @@
             set { rating = value; WriteUpdate(); SaveRating(); }
         }
         
+        public string[] Tags {
+            get { return tags; }
+            set { tags = value; WriteUpdate(); } 
+        }
+
         public string DisplayArtist { 
             get { 
                 return artist == null || artist == String.Empty
diff -Naur ../banshee-0.13.1.orig/src/Core/Banshee.Base/Utilities.cs ./src/Core/Banshee.Base/Utilities.cs
--- ../banshee-0.13.1.orig/src/Core/Banshee.Base/Utilities.cs	2007-07-31 23:04:45.000000000 +0200
+++ ./src/Core/Banshee.Base/Utilities.cs	2007-09-23 18:45:06.000000000 +0200
@@ -216,6 +216,100 @@
                     String.Format("{0}:{1:00}", time / 60, time % 60));
         }
     }
+    
+    // Utility class for mapping Database Tags column to tags field in TrackInfo
+    public class TagUtil
+    {
+        public static string ToString(string[] tags) {
+            return String.Join(",", tags, 0, tags.Length);
+        }
+
+        public static string[] FromString(string tagsString) {
+            return tagsString.Split(new  char[] {','});
+        }
+    }
+    
+    public class PriorityQueueElement<T> where T : IComparable
+    {
+        protected T obj;
+        protected int priority;
+
+        public PriorityQueueElement(T obj, int priority)
+        {
+            this.obj = obj;
+            this.priority = priority;
+        }
+        
+        public T Object {
+            get { return obj; }
+        }
+
+        public int Priority {
+            get { return priority; }
+        }
+    }
+    
+    // Generic priority queue implementation on top of ArrayList.
+    // High priorities move to the head. Performance could be improved.
+    public class PriorityQueue<T> where T : IComparable
+    {
+        protected List<PriorityQueueElement<T>> queue;
+        protected object sync = new object();
+        
+        public object SyncRoot {
+            get { return sync; }
+        }
+
+        public PriorityQueue() {
+            queue = new List<PriorityQueueElement<T>>();
+        }
+        
+        public void Enqueue(T elem, int prio) {
+            int pos = queue.Count - 1;
+            for(; pos >= 0; pos--)
+            {
+                if (queue[pos].Priority > prio) {
+                    pos += 1;
+                    break;
+                }
+            }
+            
+            if (pos < 0) {
+                pos = 0;
+            }
+            
+            queue.Insert(pos, new PriorityQueueElement<T>(elem, prio));
+        }
+        
+        public PriorityQueueElement<T> Dequeue()
+        {
+            PriorityQueueElement<T> elem = queue[0];
+            queue.RemoveAt(0);
+            return elem;
+        }
+        
+        public void Clear()
+        {
+            queue.Clear();
+        }
+        
+        public bool Contains(T elem)
+        {
+            foreach (PriorityQueueElement<T> el in queue)
+            {
+                // TODO: Is this comparison okay?
+                if (el.Object.Equals(elem))
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+        
+        public int Count {
+            get { return queue.Count; }
+        }
+    }
 
     public class Timer : IDisposable
     {


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