banshee r3025 - in trunk/banshee: . src/Core/Banshee.Services/Banshee.Collection.Database src/Core/Banshee.Services/Banshee.Playlist src/Core/Banshee.Services/Banshee.SmartPlaylist src/Core/Banshee.Services/Banshee.Sources src/Core/Hyena/Hyena.Data.Query



Author: gburt
Date: Fri Jan 25 23:37:29 2008
New Revision: 3025
URL: http://svn.gnome.org/viewvc/banshee?rev=3025&view=rev

Log:
2008-01-25  Gabriel Burt  <gabriel burt gmail com>

	Smart playlists are migrated and saved now.  They should be functional.

	* src/Core/Banshee.Services/Banshee.Collection.Database/TrackListDatabaseModel.cs:
	Add playlistid and smartplaylistid fields for smart playlist migration.

	* src/Core/Banshee.Services/Banshee.Playlist/AbstractPlaylistSource.cs:
	* src/Core/Banshee.Services/Banshee.Playlist/PlaylistSource.cs: Call DbId
	setter from subclasses, not in base ctor.

	* src/Core/Banshee.Services/Banshee.SmartPlaylist/Migrator.cs: Improve to
	actually be able to migrate the vast majority of smart playlists
	including relative date queries and between queries.

	* src/Core/Banshee.Services/Banshee.SmartPlaylist/SmartPlaylistCore.cs:
	Remove debug output.

	* src/Core/Banshee.Services/Banshee.SmartPlaylist/SmartPlaylistSource.cs:
	Don't allow removing tracks, and change to revolve about Hyena.Data.Query
	query tree instead of a SQL condition string.

	* src/Core/Banshee.Services/Banshee.Sources/DatabaseSource.cs: Don't allow
	overriding of Reload method, since it's really RateLimitedReload
	subclasses want to override.

	* src/Core/Hyena/Hyena.Data.Query/DateQueryValue.cs: Actually generate
	relative date queries, and expose relevant properties. 

	* src/Core/Hyena/Hyena.Data.Query/QueryField.cs: For StringQueryValue
	fields that don't have a custom format, automatically match both literally
	and against both the column and value lowercased.

	* src/Core/Hyena/Hyena.Data.Query/QueryNode.cs: Add option to print pretty
	XML (indented).

	* src/Core/Hyena/Hyena.Data.Query/QueryOperator.cs: Add a Dual property
	that flips < and > characters.

	* src/Core/Hyena/Hyena.Data.Query/XmlQueryParser.cs: Add static
	convenience method for parsing.


Modified:
   trunk/banshee/ChangeLog
   trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/TrackListDatabaseModel.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Playlist/AbstractPlaylistSource.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Playlist/PlaylistSource.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.SmartPlaylist/Migrator.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.SmartPlaylist/SmartPlaylistCore.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.SmartPlaylist/SmartPlaylistSource.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Sources/DatabaseSource.cs
   trunk/banshee/src/Core/Hyena/Hyena.Data.Query/DateQueryValue.cs
   trunk/banshee/src/Core/Hyena/Hyena.Data.Query/QueryField.cs
   trunk/banshee/src/Core/Hyena/Hyena.Data.Query/QueryNode.cs
   trunk/banshee/src/Core/Hyena/Hyena.Data.Query/QueryOperator.cs
   trunk/banshee/src/Core/Hyena/Hyena.Data.Query/QueryTermNode.cs
   trunk/banshee/src/Core/Hyena/Hyena.Data.Query/XmlQueryParser.cs

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/TrackListDatabaseModel.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/TrackListDatabaseModel.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/TrackListDatabaseModel.cs	Fri Jan 25 23:37:29 2008
@@ -384,11 +384,11 @@
         }
         
         public override QueryField ArtistField {
-            get { return field_set.Fields [0]; }
+            get { return field_set["artist"]; }
         }
 
         public override QueryField AlbumField {
-            get { return field_set.Fields [1]; }
+            get { return field_set["album"]; }
         }
 
         protected static QueryFieldSet field_set = new QueryFieldSet (
@@ -459,7 +459,6 @@
                 "type", "mimetype", "format", "ext", "mime"
             ),
             new QueryField (
-                    // (strftime("%s", current_timestamp) - DateAddedStamp + 3600)
                 "lastplayed", Catalog.GetString ("Last Played Date"), "CoreTracks.LastPlayedStamp", typeof(DateQueryValue),
                 // Translators: These are unique search fields.  Please, no spaces. Blank ok.
                 Catalog.GetString ("lastplayed"), Catalog.GetString ("played"), Catalog.GetString ("playedon"),
@@ -470,15 +469,17 @@
                 // Translators: These are unique search fields.  Please, no spaces. Blank ok.
                 Catalog.GetString ("added"), Catalog.GetString ("imported"), Catalog.GetString ("addedon"), Catalog.GetString ("dateadded"), Catalog.GetString ("importedon"),
                 "added", "imported", "addedon", "dateadded", "importedon"
-            )/*,
+            ),
             new QueryField (
-                "playlist", Catalog.GetString ("Playlist"),
-                "CoreTracks.TrackID IN (SELECT TrackID FROM CorePlaylistEntries WHERE PlaylistID {0})",
-                QueryFieldType.Numeric,
-                // Translators: These are unique search fields.  Please, no spaces. Blank ok.
-                Catalog.GetString ("duration"), Catalog.GetString ("length"), Catalog.GetString ("time"),
-                "duration", "length", "time"
-            )*/
+                "playlistid", Catalog.GetString ("Playlist"),
+                "CoreTracks.TrackID {2} IN (SELECT TrackID FROM CorePlaylistEntries WHERE PlaylistID = {1})", typeof(IntegerQueryValue),
+                "playlistid", "playlist"
+            ),
+            new QueryField (
+                "smartplaylistid", Catalog.GetString ("Smart Playlist"),
+                "CoreTracks.TrackID {2} IN (SELECT TrackID FROM CoreSmartPlaylistEntries WHERE SmartPlaylistID = {1})", typeof(IntegerQueryValue),
+                "smartplaylistid", "smartplaylist"
+            )
         );
     }
 }

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	Fri Jan 25 23:37:29 2008
@@ -74,10 +74,8 @@
             get { return dbid; }
             protected set {
                 if (value == null) {
-                    Console.WriteLine ("intializing abstract playlist, but dbid = null!");
                     return;
                 }
-                Console.WriteLine ("intializing abstract playlist");
                 dbid = value;
                 track_model.JoinFragment = TrackJoin;
                 track_model.Condition = String.Format (TrackCondition, dbid);
@@ -92,7 +90,6 @@
         public AbstractPlaylistSource (string generic_name, string name, int? dbid, int sortColumn, int sortType) : base (generic_name, name, Convert.ToString (dbid), 500)
         {
             Properties.SetString ("IconName", IconName);
-            DbId = dbid;
         }
 
         public override void Rename (string newName)
@@ -103,7 +100,7 @@
 
         public virtual void Save ()
         {
-            if (dbid == null || dbid < 0)
+            if (dbid == null || dbid <= 0)
                 Create ();
             else
                 Update ();

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Playlist/PlaylistSource.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Playlist/PlaylistSource.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Playlist/PlaylistSource.cs	Fri Jan 25 23:37:29 2008
@@ -99,6 +99,7 @@
         {
             Properties.SetString ("RemoveTracksActionLabel", Catalog.GetString ("Remove From Playlist"));
             Properties.SetString ("UnmapSourceActionLabel", Catalog.GetString ("Delete Playlist"));
+            DbId = dbid;
         }
 
 #endregion

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.SmartPlaylist/Migrator.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.SmartPlaylist/Migrator.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.SmartPlaylist/Migrator.cs	Fri Jan 25 23:37:29 2008
@@ -28,14 +28,17 @@
 //
 
 using System;
+using System.Data;
 using System.Text.RegularExpressions;
 using System.Collections;
 using System.Collections.Generic;
 
 using Hyena.Data;
 using Hyena.Data.Query;
+using Hyena.Data.Sqlite;
 
 using Banshee.Collection.Database;
+using Banshee.ServiceStack;
 
 namespace Banshee.SmartPlaylist
 {
@@ -46,10 +49,24 @@
 
         public static void MigrateAll ()
         {
-            Console.WriteLine ("Migrating All.");
+            int version = ServiceManager.DbConnection.QueryInt32 ("SELECT Value FROM CoreConfiguration WHERE Key = 'SmartPlaylistVersion'");
+            if (version == 1)
+                return;
+
             Migrator m = new Migrator ();
-            foreach (SmartPlaylistSource source in SmartPlaylistSource.LoadAll ())
-                m.Migrate (source);
+            using (IDataReader reader = ServiceManager.DbConnection.ExecuteReader (
+                "SELECT SmartPlaylistID, Name, Condition, OrderBy, OrderDir, LimitNumber, LimitCriterion FROM CoreSmartPlaylists")) {
+                while (reader.Read ()) {
+                    m.Migrate (
+                        Convert.ToInt32 (reader[0]), reader[1] as string,
+                        reader[2] as string, reader[3] as string,
+                        reader[4] as string, reader[5] as string,
+                        reader[6] as string
+                    );
+                }
+            }
+
+            ServiceManager.DbConnection.Execute ("INSERT INTO CoreConfiguration (Key, Value) Values ('SmartPlaylistVersion', 1)");
         }
 
         public Migrator ()
@@ -69,19 +86,28 @@
             order_hash.Add ("LastPlayedStamp ASC", FindOrder ("LastPlayedStamp", "ASC"));
         }
 
-        private void Migrate (SmartPlaylistSource source)
+        private void Migrate (int dbid, string Name, string Condition, string OrderBy, string OrderDir, string LimitNumber, string LimitCriterion)
         {
-            Console.WriteLine ("migrating {0}, order {1}", source.Name, source.OrderBy);
-            if (source.OrderBy != null && source.OrderBy != String.Empty) {
-                Order order = order_hash [source.OrderBy];
-                source.OrderBy = order.Key;
-                source.OrderDir = order.Dir;
-            }
-            source.LimitCriterion = criteria [Convert.ToInt32 (source.LimitCriterion)];
-            Console.WriteLine ("Befor: {0}", source.Condition);
-            source.Condition = ParseCondition (source.Condition);
-            Console.WriteLine ("After: {0}\n", source.Condition);
-            //source.Save ();
+            if (OrderBy != null && OrderBy != String.Empty) {
+                Order order = order_hash [OrderBy];
+                OrderBy = order.Key;
+                OrderDir = order.Dir;
+            }
+
+            LimitCriterion = criteria [Convert.ToInt32 (LimitCriterion)];
+            string ConditionXml = ParseCondition (Condition);
+
+            ServiceManager.DbConnection.Execute (new HyenaSqliteCommand (@"
+                UPDATE CoreSmartPlaylists
+                    SET Name = ?,
+                        Condition = ?,
+                        OrderBy = ?,
+                        OrderDir = ?,
+                        LimitNumber = ?,
+                        LimitCriterion = ?
+                    WHERE SmartPlaylistID = ?",
+                Name, ConditionXml, OrderBy, OrderDir, LimitNumber, LimitCriterion, dbid
+            ));
         }
 
         private string ParseCondition (string value)
@@ -117,7 +143,8 @@
                 foreach (QueryOperator op in QueryOperator.Operators) {
                     if (op.MatchesCondition (condition, out col, out v1, out v2)) {
                         QueryTermNode term = new QueryTermNode ();
-                        QueryField field = TrackListDatabaseModel.FieldSet.GetByAlias (col);
+                        QueryField field = TrackListDatabaseModel.FieldSet [col];
+                        bool is_relative_date = false;
                         if (field == null) {
                             if (col.IndexOf ("DateAddedStamp") != -1) {
                                 field = TrackListDatabaseModel.FieldSet.GetByAlias ("added");
@@ -125,100 +152,46 @@
                                 field = TrackListDatabaseModel.FieldSet.GetByAlias ("lastplayed");
                             }
 
+                            // Fix ugly implementation of playlist/smart playlist conditions
+                            if (op == QueryOperator.InPlaylist || op == QueryOperator.NotInPlaylist) {
+                                field = TrackListDatabaseModel.FieldSet ["playlist"];
+                            } else if (op == QueryOperator.InSmartPlaylist || op == QueryOperator.NotInSmartPlaylist) {
+                                field = TrackListDatabaseModel.FieldSet ["smartplaylist"];
+                            }
+
                             if (field == null) {
-                                Console.WriteLine ("got unknown field {0} so skipping", col);
                                 continue;
                             }
+                            is_relative_date = true;
                         }
 
-                        //if (term.Field.IndexOf ("LastPlayedStamp") != -1) {
-                        //} else if (term.Field.IndexOf ("DateAddedStamp") != -1) {
-                        //}
+                        term.Operator = Operator.GetByUserOperator (op.NewOp);
                         term.Field = field;
-                        term.Value = QueryValue.CreateFromUserQuery (v1, field);
-                        root.AddChild (term);
-
-                        switch (op.GetType ().ToString ()) {
-                            case "EQText":
-                            case "EQ":
-                                term.Operator = Operator.GetByUserOperator ("==");
-                                break;
-
-                            case "NotEQText":
-                            case "NotEQ":
-                                term.Operator = Operator.GetByUserOperator ("!=");
-                                break;
-
-                            case "Between":
-                            case "LT":
-                                term.Operator = Operator.GetByUserOperator ("<");
-                                break;
-
-                            case "GT":
-                                term.Operator = Operator.GetByUserOperator (">");
-                                break;
-
-                            case "GTE":
-                                term.Operator = Operator.GetByUserOperator (">=");
-                                break;
-
-                            case "Like":
-                                term.Operator = Operator.GetByUserOperator (":");
-                                break;
-
-                            case "NotLike":
-                                term.Operator = Operator.GetByUserOperator (":");
-                                break;
-
-                            case "StartsWith":
-                                term.Operator = Operator.GetByUserOperator ("=");
-                                break;
-
-                            case "InPlaylist":
-                                term.Operator = Operator.GetByUserOperator (">");
-                                break;
-
-                            case "NotInPlaylist":
-                                term.Operator = Operator.GetByUserOperator (">");
-                                break;
-
-                            case "InSmartPlaylist":
-                                term.Operator = Operator.GetByUserOperator (">");
-                                break;
-
-                            case "NotInSmartPlaylist":
-                                term.Operator = Operator.GetByUserOperator (">");
-                                break;
-                        }
-
-                        Console.WriteLine ("..{0}\n=>{1}", condition, term.ToString ());
 
-                        // Set the column
-                        /*QueryBuilderMatchRow row = matchesBox.Children[count] as QueryBuilderMatchRow;
-                        if (!ComboBoxUtil.SetActiveString (row.FieldBox, model.GetName(col))) {
-                            if (col.IndexOf ("current_timestamp") == -1) {
-                                Console.WriteLine ("Found col that can't place");
-                                break;
+                        if (op == QueryOperator.Between) {
+                            QueryListNode and = new QueryListNode (Keyword.And);
+                            QueryTermNode t2 = new QueryTermNode ();
+                            t2.Field = term.Field;
+
+                            if (is_relative_date) {
+                                ParseRelativeDateCondition (term, v1, field, ">=");
+                                ParseRelativeDateCondition (t2, v2, field, "<=");
                             } else {
-                                bool found = false;
-                                foreach (string field in model) {
-                                    if (col.IndexOf (model.GetColumn (field)) != -1) {
-                                        ComboBoxUtil.SetActiveString (row.FieldBox, field);
-                                        found = true;
-                                        break;
-                                    }
-                                }
-
-                                if (!found) {
-                                    Console.WriteLine ("Found col that can't place");
-                                    break;
-                                }
+                                term.Operator = Operator.GetByUserOperator ("<=");
+                                term.Value = QueryValue.CreateFromUserQuery (v1, field);
+                                t2.Operator = Operator.GetByUserOperator (">=");
+                                t2.Value = QueryValue.CreateFromUserQuery (v2, field);
                             }
-                        }*/
-
-                        // Set the values
-                        //v1;
-                        //v2;
+                            and.AddChild (term);
+                            and.AddChild (t2);
+                            root.AddChild (and);
+                        } else if (is_relative_date) {
+                            ParseRelativeDateCondition (term, v1, field, term.Operator.UserOperator);
+                            root.AddChild (term);
+                        } else {
+                            term.Value = QueryValue.CreateFromUserQuery (v1, field);
+                            root.AddChild (term);
+                        }
 
                         break;
                     }
@@ -227,7 +200,25 @@
                 count++;
             }
 
-            return root.Trim ().ToXml (TrackListDatabaseModel.FieldSet);
+            QueryNode node = root.Trim ();
+
+            if (node != null) {
+                //Console.WriteLine ("After XML: {0}", node.ToXml (TrackListDatabaseModel.FieldSet, true));
+                //Console.WriteLine ("After SQL: {0}", node.ToSql (TrackListDatabaseModel.FieldSet));
+            }
+
+            return node == null ? String.Empty : node.ToXml (TrackListDatabaseModel.FieldSet);
+        }
+
+        private void ParseRelativeDateCondition (QueryTermNode term, string val, QueryField field, string op)
+        {
+            // Have to flip the operator b/c of how we used to construct the SQL query
+            term.Operator = Operator.GetByUserOperator (op).Dual;
+            DateQueryValue date_value = new DateQueryValue ();
+            // Have to negate the value b/c of how we used to constuct the SQL query
+            date_value.RelativeOffset = -Convert.ToInt64 (val);
+            date_value.Relative = true;
+            term.Value = date_value;
         }
 
         private Order FindOrder (string key)
@@ -246,14 +237,16 @@
 
         public sealed class QueryOperator
         {
+            public string NewOp;
             private string format;
 
             public string Format {
                 get { return format; }
             }
 
-            private QueryOperator (string format)
+            private QueryOperator (string new_op, string format)
             {
+                NewOp = new_op;
                 this.format = format;
             }
 
@@ -294,29 +287,30 @@
             }
 
             // calling lower() to have case insensitive comparisons with strings
-            public static QueryOperator EQText     = new QueryOperator("lower({1}) = {0}{2}{0}");
-            public static QueryOperator NotEQText  = new QueryOperator("lower({1}) != {0}{2}{0}");
+            public static QueryOperator EQText     = new QueryOperator("==", "lower({1}) = {0}{2}{0}");
+            public static QueryOperator NotEQText  = new QueryOperator("!=", "lower({1}) != {0}{2}{0}");
 
-            public static QueryOperator EQ         = new QueryOperator("{1} = {0}{2}{0}");
-            public static QueryOperator NotEQ      = new QueryOperator("{1} != {0}{2}{0}");
-            public static QueryOperator Between    = new QueryOperator("{1} BETWEEN {0}{2}{0} AND {0}{3}{0}");
-            public static QueryOperator LT         = new QueryOperator("{1} < {0}{2}{0}");
-            public static QueryOperator GT         = new QueryOperator("{1} > {0}{2}{0}");
-            public static QueryOperator GTE        = new QueryOperator("{1} >= {0}{2}{0}");
+            public static QueryOperator EQ         = new QueryOperator("==", "{1} = {0}{2}{0}");
+            public static QueryOperator NotEQ      = new QueryOperator("!=", "{1} != {0}{2}{0}");
+            // TODO how to deal w/ between?
+            public static QueryOperator Between    = new QueryOperator("", "{1} BETWEEN {0}{2}{0} AND {0}{3}{0}");
+            public static QueryOperator LT         = new QueryOperator("<", "{1} < {0}{2}{0}");
+            public static QueryOperator GT         = new QueryOperator(">", "{1} > {0}{2}{0}");
+            public static QueryOperator GTE        = new QueryOperator(">=", "{1} >= {0}{2}{0}");
 
             // Note, the following lower() calls are necessary b/c of a sqlite bug which makes the LIKE
             // command case sensitive with certain characters.
-            public static QueryOperator Like       = new QueryOperator("lower({1}) LIKE '%{2}%'");
-            public static QueryOperator NotLike    = new QueryOperator("lower({1}) NOT LIKE '%{2}%'");
-            public static QueryOperator StartsWith = new QueryOperator("lower({1}) LIKE '{2}%'");
-            public static QueryOperator EndsWith   = new QueryOperator("lower({1}) LIKE '%{2}'");
+            public static QueryOperator Like       = new QueryOperator(":", "lower({1}) LIKE '%{2}%'");
+            public static QueryOperator NotLike    = new QueryOperator("!:", "lower({1}) NOT LIKE '%{2}%'");
+            public static QueryOperator StartsWith = new QueryOperator("=", "lower({1}) LIKE '{2}%'");
+            public static QueryOperator EndsWith   = new QueryOperator(":=", "lower({1}) LIKE '%{2}'");
 
             // TODO these should either be made generic or moved somewhere else since they are Banshee/Track/Playlist specific.
-            public static QueryOperator InPlaylist      = new QueryOperator("TrackID IN (SELECT TrackID FROM PlaylistEntries WHERE {1} = {0}{2}{0})");
-            public static QueryOperator NotInPlaylist   = new QueryOperator("TrackID NOT IN (SELECT TrackID FROM PlaylistEntries WHERE {1} = {0}{2}{0})");
+            public static QueryOperator InPlaylist      = new QueryOperator("==", "TrackID IN (SELECT TrackID FROM PlaylistEntries WHERE {1} = {0}{2}{0})");
+            public static QueryOperator NotInPlaylist   = new QueryOperator("!=", "TrackID NOT IN (SELECT TrackID FROM PlaylistEntries WHERE {1} = {0}{2}{0})");
 
-            public static QueryOperator InSmartPlaylist      = new QueryOperator("TrackID IN (SELECT TrackID FROM SmartPlaylistEntries WHERE {1} = {0}{2}{0})");
-            public static QueryOperator NotInSmartPlaylist   = new QueryOperator("TrackID NOT IN (SELECT TrackID FROM SmartPlaylistEntries WHERE {1} = {0}{2}{0})");
+            public static QueryOperator InSmartPlaylist      = new QueryOperator("==", "TrackID IN (SELECT TrackID FROM SmartPlaylistEntries WHERE {1} = {0}{2}{0})");
+            public static QueryOperator NotInSmartPlaylist   = new QueryOperator("!=", "TrackID NOT IN (SELECT TrackID FROM SmartPlaylistEntries WHERE {1} = {0}{2}{0})");
 
             public static QueryOperator [] Operators = new QueryOperator [] {
                 EQText, NotEQText, EQ, NotEQ, Between, LT, GT, GTE, Like, NotLike,

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.SmartPlaylist/SmartPlaylistCore.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.SmartPlaylist/SmartPlaylistCore.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.SmartPlaylist/SmartPlaylistCore.cs	Fri Jan 25 23:37:29 2008
@@ -46,15 +46,8 @@
         {
             Gnome.Vfs.Vfs.Initialize();
 
-            Console.WriteLine ("== Migrating Smart Playlists ==");
             Migrator.MigrateAll ();
-            Console.WriteLine ("== Done Migrating Smart Playlists ==");
 
-            /*new XmlQueryParser (@"
-                    <foo>
-                    <bar>
-            ");*/
-            
             // Listen for added/removed sources and added/changed songs
             ServiceManager.SourceManager.SourceAdded += HandleSourceAdded;
             ServiceManager.SourceManager.SourceRemoved += HandleSourceRemoved;

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.SmartPlaylist/SmartPlaylistSource.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.SmartPlaylist/SmartPlaylistSource.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.SmartPlaylist/SmartPlaylistSource.cs	Fri Jan 25 23:37:29 2008
@@ -33,6 +33,8 @@
 using System.Collections.Generic;
 
 using Mono.Unix;
+
+using Hyena.Data.Query;
  
 using Banshee.Base;
 using Banshee.Sources;
@@ -61,13 +63,11 @@
         private static string generic_name = Catalog.GetString ("Smart Playlist");
         private static string properties_label = Catalog.GetString ("Edit Smart Playlist");
     
-        private string condition;
         private string order_by;
         private string order_dir;
         private string limit_number;
         private string limit_criterion;
 
-
 #region Properties
 
         // Source override
@@ -75,6 +75,10 @@
             get { return true; }
         }
 
+        public override bool CanRemoveTracks {
+            get { return false; }
+        }
+
         // AbstractPlaylistSource overrides
         protected override string SourceTable {
             get { return "CoreSmartPlaylists"; }
@@ -93,9 +97,30 @@
         }
 
         // Custom properties
-        public string Condition {
+        private QueryNode condition;
+        public QueryNode ConditionTree {
             get { return condition; }
-            set { condition = value; }
+            set {
+                condition = value;
+                if (condition != null) {
+                    condition_sql = condition.ToSql (TrackListDatabaseModel.FieldSet);
+                    condition_xml = condition.ToXml (TrackListDatabaseModel.FieldSet);
+                }
+            }
+        }
+
+        private string condition_sql;
+        public string ConditionSql {
+            get { return condition_sql; }
+        }
+
+        private string condition_xml;
+        public string ConditionXml {
+            get { return condition_xml; }
+            set {
+                condition_xml = value;
+                ConditionTree = XmlQueryParser.Parse (condition_xml, TrackListDatabaseModel.FieldSet);
+            }
         }
 
         public string OrderBy {
@@ -150,15 +175,25 @@
         {
         }
 
+        /*public SmartPlaylistSource (SmartPlaylistSource original) : this (original.Name)
+        {
+            ConditionXml = original.ConditionXml;
+            OrderBy = original.OrderBy;
+            OrderDir = original.OrderDir;
+            LimitNumber = original.LimitNumber;
+            LimitCriterion = original.LimitCriterion;
+        }*/
+
         // For existing smart playlists that we're loading from the database
-        public SmartPlaylistSource (int? dbid, string name, string condition, string order_by, string order_dir, string limit_number, string limit_criterion) :
+        public SmartPlaylistSource (int? dbid, string name, string condition_xml, string order_by, string order_dir, string limit_number, string limit_criterion) :
             base (generic_name, name, dbid, -1, 0)
         {
-            Condition = condition;
+            ConditionXml = condition_xml;
             OrderBy = order_by;
             OrderDir = order_dir;
             LimitNumber = limit_number;
             LimitCriterion = limit_criterion;
+            DbId = dbid;
 
             Properties.SetString ("SourcePropertiesActionLabel", properties_label);
 
@@ -194,9 +229,9 @@
         {
             DbId = ServiceManager.DbConnection.Execute (new BansheeDbCommand (@"
                 INSERT INTO CoreSmartPlaylists
-                    (Name, Condition, OrderBy, LimitNumber, LimitCriterion)
-                    VALUES (?, ?, ?, ?, ?)",
-                Name, Condition, OrderBy, LimitNumber, LimitCriterion
+                    (Name, Condition, OrderBy, OrderDir, LimitNumber, LimitCriterion)
+                    VALUES (?, ?, ?, ?, ?, ?)",
+                Name, ConditionXml, OrderBy, OrderDir, LimitNumber, LimitCriterion
             ));
         }
 
@@ -207,10 +242,11 @@
                     SET Name = ?,
                         Condition = ?,
                         OrderBy = ?,
+                        OrderDir = ?,
                         LimitNumber = ?,
                         LimitCriterion = ?
                     WHERE SmartPlaylistID = ?",
-                Name, Condition, OrderBy, LimitNumber, LimitCriterion, DbId
+                Name, ConditionXml, OrderBy, OrderDir, LimitNumber, LimitCriterion, DbId
             ));
         }
 
@@ -218,24 +254,14 @@
 
 #region DatabaseSource overrides
 
-        public override void Reload ()
+        protected override void RateLimitedReload ()
         {
-            // TODO don't actually do this here, do it on a timeout
-
             // Wipe the member list clean
             ServiceManager.DbConnection.Execute (String.Format (
                 "DELETE FROM CoreSmartPlaylistEntries WHERE SmartPlaylistID = {0}", DbId
             ));
 
             // Repopulate it 
-            Console.WriteLine (String.Format (
-                @"INSERT INTO CoreSmartPlaylistEntries 
-                    SELECT {0} as SmartPlaylistID, TrackId
-                        FROM CoreTracks, CoreArtists, CoreAlbums
-                        WHERE CoreTracks.ArtistID = CoreArtists.ArtistID AND CoreTracks.AlbumID = CoreAlbums.AlbumID
-                        {1} {2}",
-                DbId, PrependCondition("AND"), OrderAndLimit
-            ));
             ServiceManager.DbConnection.Execute (String.Format (
                 @"INSERT INTO CoreSmartPlaylistEntries 
                     SELECT {0} as SmartPlaylistID, TrackId
@@ -245,14 +271,14 @@
                 DbId, PrependCondition("AND"), OrderAndLimit
             ));
 
-            base.Reload ();
+            base.RateLimitedReload ();
         }
 
 #endregion
 
         private string PrependCondition (string with)
         {
-            return (Condition == null || Condition == String.Empty) ? " " : with + " (" + Condition + ")";
+            return String.IsNullOrEmpty (ConditionSql) ? " " : String.Format ("{0} ({1})", with, ConditionSql);
         }
 
         private static Order [] orders = new Order [] {
@@ -286,18 +312,21 @@
             using (IDataReader reader = ServiceManager.DbConnection.ExecuteReader (
                 "SELECT SmartPlaylistID, Name, Condition, OrderBy, OrderDir, LimitNumber, LimitCriterion FROM CoreSmartPlaylists")) {
                 while (reader.Read ()) {
-                    SmartPlaylistSource playlist = new SmartPlaylistSource (
-                        Convert.ToInt32 (reader[0]), reader[1] as string,
-                        reader[2] as string, reader[3] as string,
-                        reader[4] as string, reader[5] as string,
-                        reader[6] as string
-                    );
-                    sources.Add (playlist);
+                    try {
+                        SmartPlaylistSource playlist = new SmartPlaylistSource (
+                            Convert.ToInt32 (reader[0]), reader[1] as string,
+                            reader[2] as string, reader[3] as string,
+                            reader[4] as string, reader[5] as string,
+                            reader[6] as string
+                        );
+                        sources.Add (playlist);
+                    } catch (Exception e) {
+                        Log.Warning ("Ignoring Smart Playlist", String.Format ("Caught error: {0}", e), false);
+                    }
                 }
             }
             
             return sources;
         }
-
     }
 }

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	Fri Jan 25 23:37:29 2008
@@ -104,7 +104,7 @@
 
 #region Public Methods
 
-        public virtual void Reload ()
+        public void Reload ()
         {
             reload_limiter.Execute ();
         }

Modified: trunk/banshee/src/Core/Hyena/Hyena.Data.Query/DateQueryValue.cs
==============================================================================
--- trunk/banshee/src/Core/Hyena/Hyena.Data.Query/DateQueryValue.cs	(original)
+++ trunk/banshee/src/Core/Hyena/Hyena.Data.Query/DateQueryValue.cs	Fri Jan 25 23:37:29 2008
@@ -34,6 +34,16 @@
 
 namespace Hyena.Data.Query
 {
+    public enum RelativeDateFactor {
+        Second = 1,
+        Minute = 60,
+        Hour   = 3600,
+        Day    = 3600*24,
+        Week   = 3600*24*7,
+        Month  = 3600*24*30,
+        Year   = 3600*24*365
+    }
+
     public class DateQueryValue : QueryValue
     {
         protected DateTime value;
@@ -48,6 +58,16 @@
             get { return value; }
         }
 
+        public bool Relative {
+            get { return relative; }
+            set { relative = value; }
+        }
+
+        public long RelativeOffset {
+            get { return offset; }
+            set { offset = value; IsEmpty = false; }
+        }
+
         public override void ParseUserQuery (string input)
         {
             // TODO: Add support for relative strings like "yesterday", "3 weeks ago", "5 days ago"
@@ -83,6 +103,16 @@
             }
         }
 
+        public override void AppendXml (XmlElement node)
+        {
+            if (relative) {
+                node.SetAttribute ("type", "rel");
+                node.InnerText = RelativeOffset.ToString ();
+            } else {
+                base.AppendXml (node);
+            }
+        }
+
         public override string ToSql ()
         {
             return DateTimeUtil.FromDateTime (

Modified: trunk/banshee/src/Core/Hyena/Hyena.Data.Query/QueryField.cs
==============================================================================
--- trunk/banshee/src/Core/Hyena/Hyena.Data.Query/QueryField.cs	(original)
+++ trunk/banshee/src/Core/Hyena/Hyena.Data.Query/QueryField.cs	Fri Jan 25 23:37:29 2008
@@ -101,12 +101,24 @@
             return ToTermString (PrimaryAlias, op, value);
         }
 
-        public string FormatSql (string format, params object [] args)
+        public string FormatSql (string format, Operator op, string value)
         {
-            if (Column.IndexOf ("{0}") == -1)
-                return String.Format ("{0} {1}", Column, String.Format (format, args));
-            else
-                return String.Format (Column, String.Format (format, args));
+            if (Column.IndexOf ("{0}") == -1 && Column.IndexOf ("{1}") == -1) {
+                if (ValueType == typeof(StringQueryValue)) {
+                    // Match string values literally and against a lower'd version 
+                    return String.Format ("({0} {1} OR LOWER({0}) {2})", Column,
+                        String.Format (format, value),
+                        String.Format (format, value.ToLower ())
+                    );
+                } else {
+                    return String.Format ("{0} {1}", Column, String.Format (format, value));
+                }
+            } else {
+                return String.Format (
+                    Column, String.Format (format, value),
+                    value, op.IsNot ? "NOT" : null
+                );
+            }
         }
 
         public static string ToTermString (string alias, string op, string value)

Modified: trunk/banshee/src/Core/Hyena/Hyena.Data.Query/QueryNode.cs
==============================================================================
--- trunk/banshee/src/Core/Hyena/Hyena.Data.Query/QueryNode.cs	(original)
+++ trunk/banshee/src/Core/Hyena/Hyena.Data.Query/QueryNode.cs	Fri Jan 25 23:37:29 2008
@@ -29,6 +29,7 @@
 
 using System;
 using System.Xml;
+using System.IO;
 using System.Text;
 
 namespace Hyena.Data.Query
@@ -76,7 +77,12 @@
 
         public abstract void AppendUserQuery (StringBuilder sb);
 
-        public virtual string ToXml (QueryFieldSet fieldSet)
+        public string ToXml (QueryFieldSet fieldSet)
+        {
+            return ToXml (fieldSet, false);
+        }
+
+        public virtual string ToXml (QueryFieldSet fieldSet, bool pretty)
         {
             XmlDocument doc = new XmlDocument ();
 
@@ -88,7 +94,19 @@
             request.AppendChild (query);
 
             AppendXml (doc, query, fieldSet);
-            return doc.OuterXml;
+
+            if (!pretty) {
+                return doc.OuterXml;
+            }
+
+            using (StringWriter sw = new StringWriter ()) {
+                using (XmlTextWriter xtw = new XmlTextWriter (sw)) {
+                    xtw.Formatting = System.Xml.Formatting.Indented;
+                    xtw.Indentation = 2;
+                    doc.WriteTo (xtw);
+                    return sw.ToString ();
+                }
+            }
         }
 
         public override string ToString ()

Modified: trunk/banshee/src/Core/Hyena/Hyena.Data.Query/QueryOperator.cs
==============================================================================
--- trunk/banshee/src/Core/Hyena/Hyena.Data.Query/QueryOperator.cs	(original)
+++ trunk/banshee/src/Core/Hyena/Hyena.Data.Query/QueryOperator.cs	Fri Jan 25 23:37:29 2008
@@ -38,15 +38,35 @@
     {
         public string Name;
         public string UserOperator;
+
+        public Operator Dual {
+            get {
+                string op = UserOperator.Replace ('>', '^');
+                op = op.Replace ('<', '>');
+                op = op.Replace ('^', '<');
+                return GetByUserOperator (op);
+            }
+        }
+
+        private bool is_not;
+        public bool IsNot {
+            get { return is_not; }
+        }
         
         private static List<Operator> operators = new List<Operator> ();
         private static Dictionary<string, Operator> by_op = new Dictionary<string, Operator> ();
         private static Dictionary<string, Operator> by_name = new Dictionary<string, Operator> ();
 
-        protected Operator (string name, string userOp)
+
+        protected Operator (string name, string userOp) : this (name, userOp, false)
+        {
+        }
+
+        protected Operator (string name, string userOp, bool is_not)
         {
             Name = name;
             UserOperator = userOp;
+            this.is_not = is_not;
         }
 
         static Operator () {
@@ -55,9 +75,10 @@
             Add (new Operator ("equals", "=="));
             Add (new Operator ("lessThanEquals", "<="));
             Add (new Operator ("greaterThanEquals", ">="));
-            //Add (new Operator ("notEqual", "!="));
+            Add (new Operator ("notEqual", "!=", true));
+            Add (new Operator ("endsWith", ":="));
             Add (new Operator ("startsWith", "="));
-            //Add (new Operator ("doesNotContain", "!:"));
+            Add (new Operator ("doesNotContain", "!:", true));
             Add (new Operator ("contains", ":"));
             Add (new Operator ("lessThan", "<"));
             Add (new Operator ("greaterThan", ">"));

Modified: trunk/banshee/src/Core/Hyena/Hyena.Data.Query/QueryTermNode.cs
==============================================================================
--- trunk/banshee/src/Core/Hyena/Hyena.Data.Query/QueryTermNode.cs	(original)
+++ trunk/banshee/src/Core/Hyena/Hyena.Data.Query/QueryTermNode.cs	Fri Jan 25 23:37:29 2008
@@ -134,50 +134,66 @@
             if (emit_or)
                 sb.Append (" OR ");
 
+            string format = null;
             switch (Operator.UserOperator) {
                 case "=": // Starts with
-                    sb.Append (field.FormatSql ("LIKE '{0}%'", safe_value));
+                    format = "LIKE '{0}%'";
+                    break;
+
+                case ":=": // Ends with
+                    format = "LIKE '%{0}'";
                     break;
                     
                 case "==": // Equal to
-                    sb.Append (field.FormatSql ("= '{0}'", safe_value));
+                    format = "= '{0}'";
                     break;
 
                 case "!=": // Not equal to
-                    sb.Append (field.FormatSql ("!= '{0}'", safe_value));
+                    format = "!= '{0}'";
                     break;
 
                 case "!:": // Doesn't contain
-                    sb.Append (field.FormatSql ("NOT LIKE '%{0}%'", safe_value));
+                    format = "NOT LIKE '%{0}%'";
                     break;
 
                 case ":": // Contains
                 default:
-                    sb.Append (field.FormatSql ("LIKE '%{0}%'", safe_value));
+                    format = "LIKE '%{0}%'";
                     break;
             }
 
+            sb.Append (field.FormatSql (format, Operator, safe_value));
             return true;
         }
 
         private bool EmitNumericMatch (StringBuilder sb, QueryField field, bool emit_or)
         {
-            if (emit_or) {
-                sb.Append (" OR ");
-            }
-            
+            string format;
             switch (Operator.UserOperator) {
+                // Operators that don't make sense for numeric types
+                case "!:":
+                case ":=":
+                    return false;
+
                 case ":":
                 case "=":
                 case "==":
-                    sb.Append (field.FormatSql ("= {0}", Value.ToSql ()));
+                    format = "= {0}";
                     break;
 
                 default:
-                    sb.Append (field.FormatSql ("{1} {0}", Value.ToSql (), Operator.UserOperator));
+                    format = Operator.UserOperator + " {0}";
                     break;
             }
-            
+
+            if (emit_or) {
+                sb.Append (" OR ");
+            }
+
+            sb.Append (field.FormatSql (format,
+                Operator, Value.ToSql ()
+            ));
+
             return true;
         }
         

Modified: trunk/banshee/src/Core/Hyena/Hyena.Data.Query/XmlQueryParser.cs
==============================================================================
--- trunk/banshee/src/Core/Hyena/Hyena.Data.Query/XmlQueryParser.cs	(original)
+++ trunk/banshee/src/Core/Hyena/Hyena.Data.Query/XmlQueryParser.cs	Fri Jan 25 23:37:29 2008
@@ -38,6 +38,11 @@
         private string str;
         private QueryFieldSet field_set;
 
+        public static QueryNode Parse (string input, QueryFieldSet fieldSet)
+        {
+            return new XmlQueryParser (input).BuildTree (fieldSet);
+        }
+
         public XmlQueryParser () : base () {}
 
         public XmlQueryParser (string str)
@@ -62,7 +67,6 @@
                 QueryNode node = Parse (query.FirstChild as XmlElement, null);
                 return (node != null) ? node.Trim () : null;
             } catch (Exception e) {
-                Console.WriteLine ("Caught exception trying to parse query tree from XML: {0}", e.ToString ());
             }
             return null;
         }



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