[banshee] Fix random by album/artist/rating for playlists



commit a29a011c639b52efae15a1b038eeaa6fb64a6162
Author: Gabriel Burt <gabriel burt gmail com>
Date:   Wed Jul 1 14:19:34 2009 -0500

    Fix random by album/artist/rating for playlists
    
    Also, clean up the random-by code, refactoring and splitting out into
    separate files.

 .../DatabaseTrackListModel.cs                      |  193 ++------------------
 .../Banshee.Collection.Database/RandomBy.cs        |   74 ++++++++
 .../Banshee.Collection.Database/RandomByAlbum.cs   |  115 ++++++++++++
 .../Banshee.Collection.Database/RandomByArtist.cs  |  115 ++++++++++++
 .../Banshee.Collection.Database/RandomByRating.cs  |  159 ++++++++++++++++
 .../Banshee.Collection.Database/RandomByTrack.cs   |   58 ++++++
 src/Core/Banshee.Services/Makefile.am              |    5 +
 7 files changed, 544 insertions(+), 175 deletions(-)
---
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackListModel.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackListModel.cs
index e5e2757..d94ed4e 100644
--- a/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackListModel.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackListModel.cs
@@ -340,20 +340,12 @@ namespace Banshee.Collection.Database
 
 #region Get random methods
 
-        private const string random_condition = "AND LastStreamError = 0 AND (LastPlayedStamp < ? OR LastPlayedStamp IS NULL) AND (LastSkippedStamp < ? OR LastSkippedStamp IS NULL)";
-        private static string random_fragment = String.Format ("{0} ORDER BY RANDOM()", random_condition);
-        private static string random_by_album_fragment = String.Format ("AND CoreTracks.AlbumID = ? {0} ORDER BY Disc ASC, TrackNumber ASC", random_condition);
-        private static string random_by_artist_fragment = String.Format ("AND CoreAlbums.ArtistID = ? {0} ORDER BY CoreTracks.Year, CoreTracks.AlbumID ASC, Disc ASC, TrackNumber ASC", random_condition);
-        private static string random_by_rating_fragment = @"
-            AND (LastPlayedStamp < ? OR LastPlayedStamp IS NULL)
-            AND (LastSkippedStamp < ? OR LastSkippedStamp IS NULL)
-            AND (CoreTracks.Rating = ? OR (? = 3 AND CoreTracks.Rating = 0))
-            ORDER BY RANDOM()";
+        private static RandomBy [] randoms = new RandomBy [] {
+            new RandomByTrack (), new RandomByArtist (), new RandomByAlbum (), new RandomByRating ()
+        };
 
         private DateTime random_began_at = DateTime.MinValue;
         private DateTime last_random = DateTime.MinValue;
-        private int? random_album_id;
-        private int? random_artist_id;
 
         public override TrackInfo GetRandom (DateTime notPlayedSince, PlaybackShuffleMode mode, bool repeat, bool lastWasSkipped)
         {
@@ -369,8 +361,7 @@ namespace Banshee.Collection.Database
                 TrackInfo track = GetRandomTrack (mode, repeat, lastWasSkipped);
                 if (track == null && (repeat || mode != PlaybackShuffleMode.Linear)) {
                     random_began_at = (random_began_at == last_random) ? DateTime.Now : last_random;
-                    random_album_id = random_artist_id = null;
-                    track = GetRandomTrack (mode, repeat, lastWasSkipped);
+                    track = GetRandomTrack (mode, repeat, true);
                 }
 
                 last_random = DateTime.Now;
@@ -380,176 +371,28 @@ namespace Banshee.Collection.Database
 
         private TrackInfo GetRandomTrack (PlaybackShuffleMode mode, bool repeat, bool lastWasSkipped)
         {
-            // Skip the entire album or artist
-            if (lastWasSkipped) {
-                random_album_id = random_artist_id = null;
-            }
-
-            if (mode == PlaybackShuffleMode.Album) {
-                random_artist_id = null;
-                if (random_album_id == null) {
-                    random_album_id = GetRandomAlbumId (random_began_at);
-                    if (random_album_id == null && repeat) {
-                        random_began_at = last_random;
-                        random_album_id = GetRandomAlbumId (random_began_at);
-                    }
-                }
-
-                if (random_album_id != null) {
-                    return cache.GetSingle (random_by_album_fragment, (int)random_album_id, random_began_at, random_began_at);
+            foreach (var r in randoms) {
+                r.SetModelAndCache (this, cache);
+                if (lastWasSkipped || r.Mode != mode) {
+                    r.Reset ();
                 }
-                return null;
-            } else if (mode == PlaybackShuffleMode.Artist) {
-                random_album_id = null;
-                if (random_artist_id == null) {
-                    random_artist_id = GetRandomArtistId (random_began_at);
-                    if (random_artist_id == null && repeat) {
+            }
+            
+            var random = randoms.First (r => r.Mode == mode);
+            if (random != null) {
+                if (!random.IsReady) {
+                    if (!random.Next (random_began_at) && repeat) {
                         random_began_at = last_random;
-                        random_artist_id = GetRandomArtistId (random_began_at);
+                        random.Next (random_began_at);
                     }
                 }
 
-                if (random_artist_id != null) {
-                    return cache.GetSingle (random_by_artist_fragment, (int)random_artist_id, random_began_at, random_began_at);
-                }
-                return null;
-            } else if (mode == PlaybackShuffleMode.Rating) {
-                int random_rating = GetRandomRating (random_began_at);
-                if (random_rating == 0 && repeat) {
-                    random_began_at = last_random;
-                    random_rating = GetRandomRating (random_began_at);
-                }
-
-                if (random_rating != 0) {
-                    return cache.GetSingle (random_by_rating_fragment, random_began_at, random_began_at, random_rating, random_rating);
-                }
-                return null;
-            } else {
-                random_album_id = random_artist_id = null;
-            }
-
-            return cache.GetSingle (random_fragment, random_began_at, random_began_at);
-        }
-
-        private int? GetRandomAlbumId (DateTime stamp)
-        {
-            // Get a new Album that hasn't been played since y
-            int? album_id = null;
-            var reader = connection.Query (@"
-                    SELECT a.AlbumID, a.Title, MAX(t.LastPlayedStamp) as LastPlayed, MAX(t.LastSkippedStamp) as LastSkipped
-                    FROM CoreTracks t, CoreAlbums a, CoreCache c
-                    WHERE
-                        c.ModelID = ? AND
-                        t.TrackID = c.ItemID AND
-                        t.AlbumID = a.AlbumID AND
-                        t.LastStreamError = 0
-                    GROUP BY t.AlbumID
-                    HAVING
-                        (LastPlayed < ? OR LastPlayed IS NULL) AND
-                        (LastSkipped < ? OR LastSkipped IS NULL)
-                    ORDER BY RANDOM()
-                    LIMIT 1",
-                CacheId, stamp, stamp
-            );
-
-            if (reader.Read ()) {
-                album_id = Convert.ToInt32 (reader[0]);
-            }
-
-            reader.Dispose ();
-            return album_id;
-        }
-
-        private int? GetRandomArtistId (DateTime stamp)
-        {
-            // Get a new Artist that hasn't been played since y
-            int? artist_id = null;
-            var reader = connection.Query (@"
-                    SELECT a.ArtistID, a.ArtistName, MAX(t.LastPlayedStamp) as LastPlayed, MAX(t.LastSkippedStamp) as LastSkipped
-                    FROM CoreTracks t, CoreAlbums a, CoreCache c
-                    WHERE
-                        c.ModelID = ? AND
-                        t.TrackID = c.ItemID AND
-                        t.AlbumID = a.AlbumID AND
-                        t.LastStreamError = 0
-                    GROUP BY a.ArtistID
-                    HAVING
-                        (LastPlayed < ? OR LastPlayed IS NULL) AND
-                        (LastSkipped < ? OR LastSkipped IS NULL)
-                    ORDER BY RANDOM()
-                    LIMIT 1",
-                CacheId, stamp, stamp
-            );
-
-            if (reader.Read ()) {
-                artist_id = Convert.ToInt32 (reader[0]);
-            }
-
-            reader.Dispose ();
-            return artist_id;
-        }
-
-        private readonly Random random = new Random ();
-        private int GetRandomRating (DateTime stamp)
-        {
-            // Default rating for unrated songs.
-            const int unrated_rating = 3;
-            // counts[x] = number of tracks rated x + 1.
-            int[] counts = new int[5];
-            // Get the distribution of ratings for tracks that haven't been played since stamp.
-            using (var reader = connection.Query (@"
-                SELECT t.Rating, COUNT(*)
-                FROM CoreTracks AS t
-                JOIN CoreCache AS c ON c.ItemID = t.TrackID
-                WHERE
-                    c.ModelID = ? AND
-                    t.LastStreamError = 0 AND
-                    (t.LastPlayedStamp < ? OR t.LastPlayedStamp IS NULL) AND
-                    (t.LastSkippedStamp < ? OR t.LastSkippedStamp IS NULL)
-                GROUP BY t.Rating",
-                CacheId, stamp, stamp)) {
-
-                while (reader.Read ()) {
-                    int rating = Convert.ToInt32 (reader[0]);
-                    int count = Convert.ToInt32 (reader[1]);
-
-                    if (rating < 1 || rating > 5) {
-                        rating = unrated_rating;
-                    }
-
-                    counts[rating - 1] += count;
+                if (random.IsReady) {
+                    return random.GetTrack (random_began_at);
                 }
             }
 
-            if (counts.Sum () == 0) {
-                return 0;
-            }
-
-            // We will use powers of phi as weights. Such weights result in songs rated R
-            // played as often as songs rated R-1 and R-2 combined.
-            const double phi = 1.618033989;
-
-            // If you change the weights make sure ALL of them are strictly positive.
-            var weights = Enumerable.Range (0, 5).Select (i => Math.Pow (phi, i)).ToArray ();
-
-            // Apply weights to the counts.
-            var weighted_counts = counts.Select ((c, i) => c * weights[i]);
-
-            // Normalise the counts.
-            var weighted_total = weighted_counts.Sum ();
-            weighted_counts = weighted_counts.Select (c => c / weighted_total);
-
-            // Now that we have our counts, get a weighted random rating.
-            double random_value = random.NextDouble ();
-            int current_rating = 0;
-            foreach (var weighted_count in weighted_counts) {
-                current_rating++;
-                random_value -= weighted_count;
-                if (random_value <= 0.0) {
-                    break;
-                }
-            }
-            return current_rating;
+            return null;
         }
 
 #endregion
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomBy.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomBy.cs
new file mode 100644
index 0000000..6add717
--- /dev/null
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomBy.cs
@@ -0,0 +1,74 @@
+//
+// RandomBy.cs
+//
+// Author:
+//   Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+using Hyena;
+using Hyena.Data;
+using Hyena.Data.Sqlite;
+
+using Banshee.ServiceStack;
+using Banshee.PlaybackController;
+
+namespace Banshee.Collection.Database
+{
+    public abstract class RandomBy
+    {
+        protected const string RANDOM_CONDITION = "AND LastStreamError = 0 AND (LastPlayedStamp < ? OR LastPlayedStamp IS NULL) AND (LastSkippedStamp < ? OR LastSkippedStamp IS NULL)";
+
+        protected DatabaseTrackListModel Model { get; private set; }
+        protected IDatabaseTrackModelCache Cache { get; private set; }
+
+        public virtual bool IsReady { get { return true; } }
+        public PlaybackShuffleMode Mode { get; private set; }
+
+        public RandomBy (PlaybackShuffleMode mode)
+        {
+            Mode = mode;
+        }
+
+        public void SetModelAndCache (DatabaseTrackListModel model, IDatabaseTrackModelCache cache)
+        {
+            if (Model != model) {
+                Model = model;
+                Cache = cache;
+                Reset ();
+
+                OnModelAndCacheUpdated ();
+            }
+        }
+
+        protected virtual void OnModelAndCacheUpdated ()
+        {
+        }
+
+        public virtual void Reset () {}
+        public abstract bool Next (DateTime after);
+        public abstract TrackInfo GetTrack (DateTime after);
+    }
+}
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByAlbum.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByAlbum.cs
new file mode 100644
index 0000000..1b3cd60
--- /dev/null
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByAlbum.cs
@@ -0,0 +1,115 @@
+//
+// RandomByAlbum.cs
+//
+// Author:
+//   Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+using Hyena;
+using Hyena.Data;
+using Hyena.Data.Sqlite;
+
+using Banshee.ServiceStack;
+using Banshee.PlaybackController;
+
+namespace Banshee.Collection.Database
+{
+    public class RandomByAlbum : RandomBy
+    {
+        private static string track_condition = String.Format ("AND CoreTracks.AlbumID = ? {0} ORDER BY Disc ASC, TrackNumber ASC", RANDOM_CONDITION);
+        private HyenaSqliteCommand album_query;
+        private int? album_id;
+
+        public RandomByAlbum () : base (PlaybackShuffleMode.Album)
+        {
+        }
+
+        protected override void OnModelAndCacheUpdated ()
+        {
+            album_query = null;
+        }
+
+        public override void Reset ()
+        {
+            album_id = null;
+        }
+
+        public override bool IsReady { get { return album_id != null; } }
+
+        public override bool Next (DateTime after)
+        {
+            Reset ();
+
+            using (var reader = ServiceManager.DbConnection.Query (AlbumQuery, after, after)) {
+                if (reader.Read ()) {
+                    album_id = Convert.ToInt32 (reader[0]);
+                }
+            }
+
+            return IsReady;
+        }
+
+        public override TrackInfo GetTrack (DateTime after)
+        {
+            return album_id == null ? null : Cache.GetSingle (track_condition, (int)album_id, after, after);
+        }
+
+        private HyenaSqliteCommand AlbumQuery {
+            get {
+                if (album_query == null) {
+                    album_query = new HyenaSqliteCommand (String.Format (@"
+                            SELECT
+                                CoreAlbums.AlbumID,
+                                CoreAlbums.Title,
+                                MAX(CoreTracks.LastPlayedStamp) as LastPlayed,
+                                MAX(CoreTracks.LastSkippedStamp) as LastSkipped
+                            FROM
+                                CoreTracks, CoreAlbums, CoreCache {0}
+                            WHERE
+                                {1}
+                                CoreCache.ModelID = {2} AND
+                                CoreTracks.AlbumID = CoreAlbums.AlbumID AND
+                                CoreTracks.LastStreamError = 0
+                                {3}
+                            GROUP BY CoreTracks.AlbumID
+                            HAVING
+                                (LastPlayed < ? OR LastPlayed IS NULL) AND
+                                (LastSkipped < ? OR LastSkipped IS NULL)
+                            ORDER BY RANDOM()
+                            LIMIT 1",
+                        Model.JoinFragment,
+                        Model.CachesJoinTableEntries
+                            ? String.Format ("CoreCache.ItemID = {0}.{1} AND", Model.JoinTable, Model.JoinPrimaryKey)
+                            : "CoreCache.ItemId = CoreTracks.TrackID AND",
+                        Model.CacheId,
+                        Model.ConditionFragment
+                    ));
+                }
+                return album_query;
+            }
+        }
+    }
+}
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByArtist.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByArtist.cs
new file mode 100644
index 0000000..3fcb11e
--- /dev/null
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByArtist.cs
@@ -0,0 +1,115 @@
+//
+// RandomByArtist.cs
+//
+// Author:
+//   Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+using Hyena;
+using Hyena.Data;
+using Hyena.Data.Sqlite;
+
+using Banshee.ServiceStack;
+using Banshee.PlaybackController;
+
+namespace Banshee.Collection.Database
+{
+    public class RandomByArtist : RandomBy
+    {
+        private static string track_condition = String.Format ("AND CoreAlbums.ArtistID = ? {0} ORDER BY CoreTracks.Year, CoreTracks.AlbumID ASC, Disc ASC, TrackNumber ASC", RANDOM_CONDITION);
+        private HyenaSqliteCommand query;
+        private int? id;
+
+        public RandomByArtist () : base (PlaybackShuffleMode.Artist)
+        {
+        }
+
+        protected override void OnModelAndCacheUpdated ()
+        {
+            query = null;
+        }
+
+        public override void Reset ()
+        {
+            id = null;
+        }
+
+        public override bool IsReady { get { return id != null; } }
+
+        public override bool Next (DateTime after)
+        {
+            Reset ();
+
+            using (var reader = ServiceManager.DbConnection.Query (Query, after, after)) {
+                if (reader.Read ()) {
+                    id = Convert.ToInt32 (reader[0]);
+                }
+            }
+
+            return IsReady;
+        }
+
+        public override TrackInfo GetTrack (DateTime after)
+        {
+            return id == null ? null : Cache.GetSingle (track_condition, (int)id, after, after);
+        }
+
+        private HyenaSqliteCommand Query {
+            get {
+                if (query == null) {
+                    query = new HyenaSqliteCommand (String.Format (@"
+                            SELECT
+                                CoreAlbums.ArtistID,
+                                CoreAlbums.ArtistName,
+                                MAX(CoreTracks.LastPlayedStamp) as LastPlayed,
+                                MAX(CoreTracks.LastSkippedStamp) as LastSkipped
+                            FROM
+                                CoreTracks, CoreAlbums, CoreCache {0}
+                            WHERE
+                                {1}
+                                CoreCache.ModelID = {2} AND
+                                CoreTracks.AlbumID = CoreAlbums.AlbumID AND
+                                CoreTracks.LastStreamError = 0
+                                {3}
+                            GROUP BY CoreTracks.AlbumID
+                            HAVING
+                                (LastPlayed < ? OR LastPlayed IS NULL) AND
+                                (LastSkipped < ? OR LastSkipped IS NULL)
+                            ORDER BY RANDOM()
+                            LIMIT 1",
+                        Model.JoinFragment,
+                        Model.CachesJoinTableEntries
+                            ? String.Format ("CoreCache.ItemID = {0}.{1} AND", Model.JoinTable, Model.JoinPrimaryKey)
+                            : "CoreCache.ItemId = CoreTracks.TrackID AND",
+                        Model.CacheId,
+                        Model.ConditionFragment
+                    ));
+                }
+                return query;
+            }
+        }
+    }
+}
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByRating.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByRating.cs
new file mode 100644
index 0000000..b76a4ed
--- /dev/null
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByRating.cs
@@ -0,0 +1,159 @@
+//
+// RandomByRating.cs
+//
+// Authors:
+//   Elena Grassi <grassi e gmail com>
+//   Alexander Kojevnikov <alexander kojevnikov com>
+//   Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2008 Elena Grassi
+// Copyright (C) 2009 Alexander Kojevnikov
+// Copyright (C) 2009 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Linq;
+
+using Hyena;
+using Hyena.Data;
+using Hyena.Data.Sqlite;
+
+using Banshee.ServiceStack;
+using Banshee.PlaybackController;
+
+namespace Banshee.Collection.Database
+{
+    public class RandomByRating : RandomBy
+    {
+        private static Random random = new Random ();
+
+        private static string track_condition = String.Format ("AND (CoreTracks.Rating = ? OR (? = 3 AND CoreTracks.Rating = 0)) {0} ORDER BY RANDOM()", RANDOM_CONDITION);
+        private HyenaSqliteCommand query;
+        private int rating;
+
+        public RandomByRating () : base (PlaybackShuffleMode.Rating)
+        {
+        }
+
+        protected override void OnModelAndCacheUpdated ()
+        {
+            query = null;
+        }
+
+        public override void Reset ()
+        {
+            rating = 0;
+        }
+
+        public override bool IsReady { get { return rating != 0; } }
+
+        public override bool Next (DateTime after)
+        {
+            Reset ();
+
+            // Default rating for unrated songs.
+            const int unrated_rating = 3;
+            // counts[x] = number of tracks rated x + 1.
+            int[] counts = new int[5];
+            // Get the distribution of ratings for tracks that haven't been played since stamp.
+            Console.WriteLine ("About to run rating query: {0}", Query.Text);
+            using (var reader = ServiceManager.DbConnection.Query (Query, after, after)) {
+                while (reader.Read ()) {
+                    int r = Convert.ToInt32 (reader[0]);
+                    int count = Convert.ToInt32 (reader[1]);
+
+                    if (r < 1 || r > 5) {
+                        r = unrated_rating;
+                    }
+
+                    counts[r - 1] += count;
+                }
+            }
+
+            if (counts.Sum () == 0) {
+                rating = 0;
+                return false;
+            }
+
+            // We will use powers of phi as weights. Such weights result in songs rated R
+            // played as often as songs rated R-1 and R-2 combined.
+            const double phi = 1.618033989;
+
+            // If you change the weights make sure ALL of them are strictly positive.
+            var weights = Enumerable.Range (0, 5).Select (i => Math.Pow (phi, i)).ToArray ();
+
+            // Apply weights to the counts.
+            var weighted_counts = counts.Select ((c, i) => c * weights[i]);
+
+            // Normalise the counts.
+            var weighted_total = weighted_counts.Sum ();
+            weighted_counts = weighted_counts.Select (c => c / weighted_total);
+
+            // Now that we have our counts, get a weighted random rating.
+            double random_value = random.NextDouble ();
+            int current_rating = 0;
+            foreach (var weighted_count in weighted_counts) {
+                current_rating++;
+                random_value -= weighted_count;
+                if (random_value <= 0.0) {
+                    break;
+                }
+            }
+
+            rating = current_rating;
+            return IsReady;
+        }
+
+        public override TrackInfo GetTrack (DateTime after)
+        {
+            return !IsReady ? null : Cache.GetSingle (track_condition, rating, rating, after, after);
+        }
+
+        private HyenaSqliteCommand Query {
+            get {
+                if (query == null) {
+                    query = new HyenaSqliteCommand (String.Format (@"
+                        SELECT
+                            CoreTracks.Rating, COUNT(*)
+                        FROM
+                            CoreTracks, CoreCache {0}
+                        WHERE
+                            {1}
+                            CoreCache.ModelID = {2} AND
+                            CoreTracks.LastStreamError = 0 AND
+                            (CoreTracks.LastPlayedStamp < ? OR CoreTracks.LastPlayedStamp IS NULL) AND
+                            (CoreTracks.LastSkippedStamp < ? OR CoreTracks.LastSkippedStamp IS NULL)
+                            {3}
+                        GROUP BY CoreTracks.Rating",
+                        Model.JoinFragment,
+                        Model.CachesJoinTableEntries
+                            ? String.Format ("CoreCache.ItemID = {0}.{1} AND", Model.JoinTable, Model.JoinPrimaryKey)
+                            : "CoreCache.ItemId = CoreTracks.TrackID AND",
+                        Model.CacheId,
+                        Model.ConditionFragment
+                    ));
+                }
+                return query;
+            }
+        }
+    }
+}
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByTrack.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByTrack.cs
new file mode 100644
index 0000000..1b91bf5
--- /dev/null
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByTrack.cs
@@ -0,0 +1,58 @@
+//
+// RandomByTrack.cs
+//
+// Author:
+//   Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+using Hyena;
+using Hyena.Data;
+using Hyena.Data.Sqlite;
+
+using Banshee.ServiceStack;
+using Banshee.PlaybackController;
+
+namespace Banshee.Collection.Database
+{
+    public class RandomByTrack : RandomBy
+    {
+        private static string track_condition = String.Format ("{0} ORDER BY RANDOM()", RANDOM_CONDITION);
+
+        public RandomByTrack () : base (PlaybackShuffleMode.Song)
+        {
+        }
+
+        public override bool Next (DateTime after)
+        {
+            return true;
+        }
+
+        public override TrackInfo GetTrack (DateTime after)
+        {
+            return Cache.GetSingle (track_condition, after, after);
+        }
+    }
+}
diff --git a/src/Core/Banshee.Services/Makefile.am b/src/Core/Banshee.Services/Makefile.am
index a903a91..3bff0de 100644
--- a/src/Core/Banshee.Services/Makefile.am
+++ b/src/Core/Banshee.Services/Makefile.am
@@ -22,6 +22,11 @@ SOURCES =  \
 	Banshee.Collection.Database/IDatabaseTrackModelCache.cs \
 	Banshee.Collection.Database/IDatabaseTrackModelProvider.cs \
 	Banshee.Collection.Database/QueryFilterInfo.cs \
+	Banshee.Collection.Database/RandomBy.cs \
+	Banshee.Collection.Database/RandomByTrack.cs \
+	Banshee.Collection.Database/RandomByArtist.cs \
+	Banshee.Collection.Database/RandomByAlbum.cs \
+	Banshee.Collection.Database/RandomByRating.cs \
 	Banshee.Collection.Database/Tests/DatabaseAlbumInfoTests.cs \
 	Banshee.Collection.Database/Tests/DatabaseArtistInfoTests.cs \
 	Banshee.Collection.Database/Tests/DatabaseTrackInfoTests.cs \



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