[banshee/1.5.1-fixes: 16/56] Fix bug with AutoDJ updating LastPlayed erroneously



commit 8b2d3b8198cbb0be0c4768cdc7da82da22950a30
Author: Gabriel Burt <gabriel burt gmail com>
Date:   Wed Oct 14 20:08:07 2009 -0700

    Fix bug with AutoDJ updating LastPlayed erroneously
    
    The old random-bys used the LastPlayed/LastSkipped values in CoreTracks
    to not repeat a track that had already been shuffled.  This works well
    for playback shuffling, but the AutoDJ was using it for its
    fill-shuffling as well.  Fix that by adding a CoreShuffles table that
    the PlayQueue and others can use to get a random track from a source,
    without repeats, and without messing with the LastPlayedStamp.  This
    fixes BGO #594701

 .../DatabaseTrackListModel.cs                      |   58 +-------
 .../Banshee.Collection.Database/RandomBy.cs        |   67 +++++++++-
 .../Banshee.Collection.Database/RandomByAlbum.cs   |   19 ++-
 .../Banshee.Collection.Database/RandomByArtist.cs  |   14 ++-
 .../Banshee.Collection.Database/RandomByRating.cs  |   35 +++++-
 .../Banshee.Collection.Database/RandomByScore.cs   |   39 +++++-
 .../Banshee.Collection.Database/RandomBySlot.cs    |   22 +++-
 .../Banshee.Collection.Database/RandomByTrack.cs   |   11 ++-
 .../Banshee.Collection.Database/Shuffler.cs        |  140 ++++++++++++++++++++
 .../Banshee.Collection/MemoryTrackListModel.cs     |    2 +-
 .../Banshee.Collection/TrackListModel.cs           |    2 +-
 .../Banshee.Database/BansheeDbFormatMigrator.cs    |   46 +++++++-
 .../PlaybackControllerService.cs                   |    5 +-
 src/Core/Banshee.Services/Makefile.am              |    1 +
 .../Banshee.PlayQueue/HeaderWidget.cs              |    6 +-
 .../Banshee.PlayQueue/PlayQueueSource.cs           |   13 ++-
 .../Banshee.PlayQueue/QueueableSourceComboBox.cs   |    4 +-
 17 files changed, 398 insertions(+), 86 deletions(-)
---
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackListModel.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackListModel.cs
index 384edaf..afcf511 100644
--- a/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackListModel.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackListModel.cs
@@ -338,65 +338,19 @@ namespace Banshee.Collection.Database
             }
         }
 
-#region Get random methods
-
-        private static RandomBy [] randoms = new RandomBy [] {
-            new RandomByTrack (), new RandomByArtist (), new RandomByAlbum (), new RandomByRating (), new RandomByScore()
-        };
-
-        private DateTime random_began_at = DateTime.MinValue;
-        private DateTime last_random = DateTime.MinValue;
-
-        public override TrackInfo GetRandom (DateTime notPlayedSince, PlaybackShuffleMode mode, bool repeat, bool lastWasSkipped)
+        public override TrackInfo GetRandom (DateTime notPlayedSince)
         {
-            lock (this) {
-                if (Count == 0) {
-                    return null;
-                }
-
-                if (random_began_at < notPlayedSince) {
-                    random_began_at = last_random = notPlayedSince;
-                }
-
-                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;
-                    track = GetRandomTrack (mode, repeat, true);
-                }
-
-                last_random = DateTime.Now;
-                return track;
-            }
+            return GetRandom (notPlayedSince, PlaybackShuffleMode.Song, true, false, Shuffler.Playback);
         }
 
-        private TrackInfo GetRandomTrack (PlaybackShuffleMode mode, bool repeat, bool lastWasSkipped)
+        public TrackInfo GetRandom (DateTime notPlayedSince, PlaybackShuffleMode mode, bool repeat, bool resetSinceTime, Shuffler shuffler)
         {
-            foreach (var r in randoms) {
-                r.SetModelAndCache (this, cache);
-                if (lastWasSkipped || r.Mode != mode) {
-                    r.Reset ();
-                }
-            }
-            
-            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.Next (random_began_at);
-                    }
-                }
-
-                if (random.IsReady) {
-                    return random.GetTrack (random_began_at);
-                }
+            lock (this) {
+                shuffler.SetModelAndCache (this, cache);
+                return shuffler.GetRandom (notPlayedSince, mode, repeat, resetSinceTime);
             }
-
-            return null;
         }
 
-#endregion
-
         public override TrackInfo this[int index] {
             get {
                 lock (this) {
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomBy.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomBy.cs
index 6add717..ae01174 100644
--- a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomBy.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomBy.cs
@@ -44,12 +44,43 @@ namespace Banshee.Collection.Database
         protected DatabaseTrackListModel Model { get; private set; }
         protected IDatabaseTrackModelCache Cache { get; private set; }
 
+        private HyenaSqliteCommand insert_shuffle;
+
+        protected Shuffler Shuffler { get; private set; }
+
         public virtual bool IsReady { get { return true; } }
         public PlaybackShuffleMode Mode { get; private set; }
 
-        public RandomBy (PlaybackShuffleMode mode)
+        protected string Condition { get; set; }
+        protected string OrderBy { get; set; }
+
+        public RandomBy (PlaybackShuffleMode mode, Shuffler shuffler)
         {
+            Shuffler = shuffler;
             Mode = mode;
+            insert_shuffle = new HyenaSqliteCommand ("INSERT OR REPLACE INTO CoreShuffles (ShufflerID, TrackID, LastShuffledAt) VALUES (?, ?, ?)");
+        }
+
+        private HyenaSqliteCommand shuffler_query;
+        protected HyenaSqliteCommand ShufflerQuery {
+            get {
+                if (shuffler_query == null) {
+                    var provider = DatabaseTrackInfo.Provider;
+                    shuffler_query = new HyenaSqliteCommand (String.Format (@"
+                        SELECT {0}
+                            FROM {1} LEFT OUTER JOIN CoreShuffles ON (CoreShuffles.ShufflerId = {2} AND CoreShuffles.TrackID = CoreTracks.TrackID)
+                            WHERE {3} {4} AND {5} AND
+                                LastStreamError = 0 AND (CoreShuffles.LastShuffledAt < ? OR CoreShuffles.LastShuffledAt IS NULL)
+                            ORDER BY {6}",
+                        provider.Select,
+                        Model.FromFragment, Shuffler.DbId,
+                        String.IsNullOrEmpty (provider.Where) ? "1=1" : provider.Where, Model.ConditionFragment ?? "1=1", Condition,
+                        OrderBy
+                    ));
+                }
+
+                return shuffler_query;
+            }
         }
 
         public void SetModelAndCache (DatabaseTrackListModel model, IDatabaseTrackModelCache cache)
@@ -61,6 +92,8 @@ namespace Banshee.Collection.Database
 
                 OnModelAndCacheUpdated ();
             }
+
+            shuffler_query = null;
         }
 
         protected virtual void OnModelAndCacheUpdated ()
@@ -68,7 +101,37 @@ namespace Banshee.Collection.Database
         }
 
         public virtual void Reset () {}
+
         public abstract bool Next (DateTime after);
-        public abstract TrackInfo GetTrack (DateTime after);
+
+        public TrackInfo GetTrack (DateTime after)
+        {
+            if (Shuffler == Shuffler.Playback) {
+                return GetPlaybackTrack (after);
+            } else {
+                var track = GetShufflerTrack (after);
+
+                // Record this shuffle
+                if (track != null) {
+                    ServiceManager.DbConnection.Execute (insert_shuffle, Shuffler.DbId, track.TrackId, DateTime.Now);
+                }
+
+                return track;
+            }
+        }
+
+        public abstract TrackInfo GetPlaybackTrack (DateTime after);
+        public abstract DatabaseTrackInfo GetShufflerTrack (DateTime after);
+
+        protected DatabaseTrackInfo GetTrack (HyenaSqliteCommand cmd, params object [] args)
+        {
+            using (var reader = ServiceManager.DbConnection.Query (cmd, args)) {
+                if (reader.Read ()) {
+                    return DatabaseTrackInfo.Provider.Load (reader);
+                }
+            }
+
+            return null;
+        }
     }
 }
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByAlbum.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByAlbum.cs
index 1b3cd60..185fde8 100644
--- a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByAlbum.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByAlbum.cs
@@ -39,12 +39,15 @@ 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 static string last_played_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)
+        public RandomByAlbum (Shuffler shuffler) : base (PlaybackShuffleMode.Album, shuffler)
         {
+            Condition = "CoreTracks.AlbumID = ?";
+            OrderBy = "Disc ASC, TrackNumber ASC";
         }
 
         protected override void OnModelAndCacheUpdated ()
@@ -72,9 +75,17 @@ namespace Banshee.Collection.Database
             return IsReady;
         }
 
-        public override TrackInfo GetTrack (DateTime after)
+        public override TrackInfo GetPlaybackTrack (DateTime after)
         {
-            return album_id == null ? null : Cache.GetSingle (track_condition, (int)album_id, after, after);
+            return album_id == null ? null : Cache.GetSingle (last_played_condition, (int)album_id, after, after);
+        }
+
+        public override DatabaseTrackInfo GetShufflerTrack (DateTime after)
+        {
+            if (album_id == null)
+                return null;
+
+            return GetTrack (ShufflerQuery, (int)album_id, after);
         }
 
         private HyenaSqliteCommand AlbumQuery {
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByArtist.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByArtist.cs
index 3fcb11e..ed20f36 100644
--- a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByArtist.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByArtist.cs
@@ -43,8 +43,10 @@ namespace Banshee.Collection.Database
         private HyenaSqliteCommand query;
         private int? id;
 
-        public RandomByArtist () : base (PlaybackShuffleMode.Artist)
+        public RandomByArtist (Shuffler shuffler) : base (PlaybackShuffleMode.Artist, shuffler)
         {
+            Condition = "CoreAlbums.ArtistID = ?";
+            OrderBy = "CoreTracks.Year, CoreTracks.AlbumID ASC, Disc ASC, TrackNumber ASC";
         }
 
         protected override void OnModelAndCacheUpdated ()
@@ -72,11 +74,19 @@ namespace Banshee.Collection.Database
             return IsReady;
         }
 
-        public override TrackInfo GetTrack (DateTime after)
+        public override TrackInfo GetPlaybackTrack (DateTime after)
         {
             return id == null ? null : Cache.GetSingle (track_condition, (int)id, after, after);
         }
 
+        public override DatabaseTrackInfo GetShufflerTrack (DateTime after)
+        {
+            if (id == null)
+                return null;
+
+            return GetTrack (ShufflerQuery, (int)id, after);
+        }
+
         private HyenaSqliteCommand Query {
             get {
                 if (query == null) {
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByRating.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByRating.cs
index 35180c2..2f71260 100644
--- a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByRating.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByRating.cs
@@ -40,22 +40,34 @@ namespace Banshee.Collection.Database
     {
         private static string track_condition = String.Format ("AND (CoreTracks.Rating = ? OR (? = 3 AND CoreTracks.Rating = 0)) {0} ORDER BY RANDOM()", RANDOM_CONDITION);
 
-        public RandomByRating () : base (PlaybackShuffleMode.Rating)
+        public RandomByRating (Shuffler shuffler) : base (PlaybackShuffleMode.Rating, shuffler)
         {
+            Condition = "(CoreTracks.Rating = ? OR (? = 3 AND CoreTracks.Rating = 0))";
+            OrderBy = "RANDOM()";
         }
 
-        public override TrackInfo GetTrack (DateTime after)
+        public override TrackInfo GetPlaybackTrack (DateTime after)
         {
             var track = !IsReady ? null : Cache.GetSingle (track_condition, slot + 1, slot + 1, after, after);
             Reset ();
             return track;
         }
 
+        public override DatabaseTrackInfo GetShufflerTrack (DateTime after)
+        {
+            if (!IsReady)
+                return null;
+
+            var track = GetTrack (ShufflerQuery, slot + 1, slot + 1, after);
+            Reset ();
+            return track;
+        }
+
         protected override int Slots {
             get { return 5; }
         }
 
-        protected override string QuerySql {
+        protected override string PlaybackSlotQuerySql {
             get {
                 return @"
                     SELECT
@@ -72,5 +84,22 @@ namespace Banshee.Collection.Database
                     GROUP BY Slot";
             }
         }
+
+        protected override string ShufflerSlotQuerySql {
+            get {
+                return @"
+                    SELECT
+                        (CoreTracks.Rating - 1) AS Slot, COUNT(*)
+                    FROM
+                        CoreTracks LEFT OUTER JOIN CoreShuffles ON (CoreShuffles.ShufflerId = " + Shuffler.DbId.ToString () +
+                    @" AND CoreShuffles.TrackID = CoreTracks.TrackID)
+                        {0}
+                    WHERE
+                        CoreTracks.LastStreamError = 0 AND
+                        (CoreShuffles.LastShuffledAt < ? OR CoreShuffles.LastShuffledAt IS NULL)
+                        {3}
+                    GROUP BY Slot";
+            }
+        }
     }
 }
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByScore.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByScore.cs
index 81f34de..c997773 100644
--- a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByScore.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByScore.cs
@@ -36,11 +36,13 @@ namespace Banshee.Collection.Database
     {
         private static string track_condition = String.Format ("AND (CoreTracks.Score BETWEEN ? AND ? OR (? = 50 AND CoreTracks.Score = 0)) {0} ORDER BY RANDOM()", RANDOM_CONDITION);
 
-        public RandomByScore () : base (PlaybackShuffleMode.Score)
+        public RandomByScore (Shuffler shuffler) : base (PlaybackShuffleMode.Score, shuffler)
         {
+            Condition = "(CoreTracks.Score BETWEEN ? AND ? OR (? = 50 AND CoreTracks.Score = 0))";
+            OrderBy = "RANDOM()";
         }
 
-        public override TrackInfo GetTrack (DateTime after)
+        public override TrackInfo GetPlaybackTrack (DateTime after)
         {
             int min = slot * 100 / Slots + 1;
             int max = (slot + 1) * 100 / Slots;
@@ -50,11 +52,24 @@ namespace Banshee.Collection.Database
             return track;
         }
 
+        public override DatabaseTrackInfo GetShufflerTrack (DateTime after)
+        {
+            if (!IsReady)
+                return null;
+
+            int min = slot * 100 / Slots + 1;
+            int max = (slot + 1) * 100 / Slots;
+
+            var track = GetTrack (ShufflerQuery, min, max, max, after);
+            Reset ();
+            return track;
+        }
+
         protected override int Slots {
             get { return 20; }
         }
 
-        protected override string QuerySql {
+        protected override string PlaybackSlotQuerySql {
             get {
                 // NOTE: SQLite wrongly assumes that (-1)/5 == 0, the CASE WHEN works around this.
                 return @"
@@ -72,5 +87,23 @@ namespace Banshee.Collection.Database
                     GROUP BY Slot";
             }
         }
+
+        protected override string ShufflerSlotQuerySql {
+            get {
+                // NOTE: SQLite wrongly assumes that (-1)/5 == 0, the CASE WHEN works around this.
+                return @"
+                    SELECT
+                        CASE WHEN IFNULL(CoreTracks.Score, 0) = 0 THEN -1 ELSE (CoreTracks.Score - 1) * 20 / 100 END AS Slot, COUNT(*)
+                    FROM
+                        CoreTracks LEFT OUTER JOIN CoreShuffles ON (CoreShuffles.ShufflerId = " + Shuffler.DbId.ToString () +
+                    @" AND CoreShuffles.TrackID = CoreTracks.TrackID)
+                        {0}
+                    WHERE
+                        CoreTracks.LastStreamError = 0 AND
+                        (CoreShuffles.LastShuffledAt < ? OR CoreShuffles.LastShuffledAt IS NULL)
+                        {3}
+                    GROUP BY Slot";
+            }
+        }
     }
 }
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomBySlot.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomBySlot.cs
index 348a726..2a988de 100644
--- a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomBySlot.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomBySlot.cs
@@ -49,7 +49,7 @@ namespace Banshee.Collection.Database
         private HyenaSqliteCommand query;
         protected int slot;
 
-        public RandomBySlot (PlaybackShuffleMode mode) : base (mode)
+        public RandomBySlot (PlaybackShuffleMode mode, Shuffler shuffler) : base (mode, shuffler)
         {
         }
 
@@ -74,7 +74,11 @@ namespace Banshee.Collection.Database
             int default_slot = (Slots - 1) / 2;
 
             // Get the distribution for tracks that haven't been played since stamp.
-            using (var reader = ServiceManager.DbConnection.Query (Query, after, after)) {
+            var reader = Shuffler == Shuffler.Playback
+                ? ServiceManager.DbConnection.Query (SlotQuery, after, after)
+                : ServiceManager.DbConnection.Query (SlotQuery, after);
+
+            using (reader) {
                 while (reader.Read ()) {
                     int s = Convert.ToInt32 (reader[0]);
                     int count = Convert.ToInt32 (reader[1]);
@@ -121,10 +125,10 @@ namespace Banshee.Collection.Database
             return IsReady;
         }
 
-        private HyenaSqliteCommand Query {
+        private HyenaSqliteCommand SlotQuery {
             get {
                 if (query == null) {
-                    query = new HyenaSqliteCommand (String.Format (QuerySql,
+                    query = new HyenaSqliteCommand (String.Format (SlotQuerySql,
                         Model.JoinFragment,
                         Model.CachesJoinTableEntries
                             ? String.Format ("CoreCache.ItemID = {0}.{1} AND", Model.JoinTable, Model.JoinPrimaryKey)
@@ -137,7 +141,15 @@ namespace Banshee.Collection.Database
             }
         }
 
+        private string SlotQuerySql {
+            get {
+                return Shuffler == Shuffler.Playback ? PlaybackSlotQuerySql : ShufflerSlotQuerySql;
+            }
+        }
+
         protected abstract int Slots { get; }
-        protected abstract string QuerySql { get; }
+
+        protected abstract string PlaybackSlotQuerySql { get; }
+        protected abstract string ShufflerSlotQuerySql { get; }
     }
 }
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByTrack.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByTrack.cs
index 1b91bf5..18c57dc 100644
--- a/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByTrack.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/RandomByTrack.cs
@@ -41,8 +41,10 @@ namespace Banshee.Collection.Database
     {
         private static string track_condition = String.Format ("{0} ORDER BY RANDOM()", RANDOM_CONDITION);
 
-        public RandomByTrack () : base (PlaybackShuffleMode.Song)
+        public RandomByTrack (Shuffler shuffler) : base (PlaybackShuffleMode.Song, shuffler)
         {
+            Condition = "1=1";
+            OrderBy = "RANDOM()";
         }
 
         public override bool Next (DateTime after)
@@ -50,9 +52,14 @@ namespace Banshee.Collection.Database
             return true;
         }
 
-        public override TrackInfo GetTrack (DateTime after)
+        public override TrackInfo GetPlaybackTrack (DateTime after)
         {
             return Cache.GetSingle (track_condition, after, after);
         }
+
+        public override DatabaseTrackInfo GetShufflerTrack (DateTime after)
+        {
+            return GetTrack (ShufflerQuery, after);
+        }
     }
 }
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/Shuffler.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/Shuffler.cs
new file mode 100644
index 0000000..6636462
--- /dev/null
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/Shuffler.cs
@@ -0,0 +1,140 @@
+//
+// Shuffler.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 System.Linq;
+
+using Hyena;
+using Hyena.Data;
+using Hyena.Data.Sqlite;
+
+using Banshee.ServiceStack;
+using Banshee.PlaybackController;
+
+namespace Banshee.Collection.Database
+{
+    public class Shuffler
+    {
+        public static readonly Shuffler Playback = new Shuffler () { Id = "playback", DbId = 0 };
+
+        private DateTime random_began_at = DateTime.MinValue;
+        private DateTime last_random = DateTime.MinValue;
+        private RandomBy [] randoms;
+        private DatabaseTrackListModel model;
+
+        public string Id { get; private set; }
+        public int DbId { get; private set; }
+
+        private Shuffler ()
+        {
+            randoms = new RandomBy [] {
+                new RandomByTrack (this), new RandomByArtist (this), new RandomByAlbum (this), new RandomByRating (this), new RandomByScore (this)
+            };
+        }
+
+        public Shuffler (string id) : this ()
+        {
+            Id = id;
+            LoadOrCreate ();
+        }
+
+        public void SetModelAndCache (DatabaseTrackListModel model, IDatabaseTrackModelCache cache)
+        {
+            this.model = model;
+
+            foreach (var random in randoms) {
+                random.SetModelAndCache (model, cache);
+            }
+        }
+
+        public TrackInfo GetRandom (DateTime notPlayedSince, PlaybackShuffleMode mode, bool repeat, bool resetSinceTime)
+        {
+            lock (this) {
+                if (this == Playback) {
+                    if (model.Count == 0) {
+                        return null;
+                    }
+                } else {
+                    if (model.UnfilteredCount == 0) {
+                        return null;
+                    }
+                }
+
+                if (random_began_at < notPlayedSince) {
+                    random_began_at = last_random = notPlayedSince;
+                }
+
+                TrackInfo track = GetRandomTrack (mode, repeat, resetSinceTime);
+                if (track == null && (repeat || mode != PlaybackShuffleMode.Linear)) {
+                    random_began_at = (random_began_at == last_random) ? DateTime.Now : last_random;
+                    track = GetRandomTrack (mode, repeat, true);
+                }
+
+                last_random = DateTime.Now;
+                return track;
+            }
+        }
+
+        private TrackInfo GetRandomTrack (PlaybackShuffleMode mode, bool repeat, bool resetSinceTime)
+        {
+            foreach (var r in randoms) {
+                if (resetSinceTime || r.Mode != mode) {
+                    r.Reset ();
+                }
+            }
+            
+            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.Next (random_began_at);
+                    }
+                }
+
+                if (random.IsReady) {
+                    return random.GetTrack (random_began_at);
+                }
+            }
+
+            return null;
+        }
+
+        private void LoadOrCreate ()
+        {
+            var db = ServiceManager.DbConnection;
+
+            int res = db.Query<int> ("SELECT ShufflerID FROM CoreShufflers WHERE ID = ?", Id);
+            if (res > 0) {
+                DbId = res;
+            } else {
+                DbId = db.Execute ("INSERT INTO CoreShufflers (ID) VALUES (?)", Id);
+            }
+        }
+    }
+}
diff --git a/src/Core/Banshee.Services/Banshee.Collection/MemoryTrackListModel.cs b/src/Core/Banshee.Services/Banshee.Collection/MemoryTrackListModel.cs
index dbe12c6..b39a777 100644
--- a/src/Core/Banshee.Services/Banshee.Collection/MemoryTrackListModel.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection/MemoryTrackListModel.cs
@@ -81,7 +81,7 @@ namespace Banshee.Collection
             get { lock (this) { return (index >= 0 && index < tracks.Count) ? tracks[index] : null; } }
         }
 
-        public override TrackInfo GetRandom (DateTime since, Banshee.PlaybackController.PlaybackShuffleMode mode, bool repeat, bool lastWasSkipped)
+        public override TrackInfo GetRandom (DateTime since)
         {
             if (Count == 0)
                 return null;
diff --git a/src/Core/Banshee.Services/Banshee.Collection/TrackListModel.cs b/src/Core/Banshee.Services/Banshee.Collection/TrackListModel.cs
index e3c6f02..043ae3d 100644
--- a/src/Core/Banshee.Services/Banshee.Collection/TrackListModel.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection/TrackListModel.cs
@@ -51,6 +51,6 @@ namespace Banshee.Collection
         
         public abstract int IndexOf (TrackInfo track);
 
-        public abstract TrackInfo GetRandom (DateTime notPlayedSince, Banshee.PlaybackController.PlaybackShuffleMode mode, bool repeat, bool lastWasSkipped);
+        public abstract TrackInfo GetRandom (DateTime since);
     }
 }
diff --git a/src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs b/src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs
index 36dd90e..04e4c01 100644
--- a/src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs
+++ b/src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs
@@ -56,7 +56,7 @@ namespace Banshee.Database
         // NOTE: Whenever there is a change in ANY of the database schema,
         //       this version MUST be incremented and a migration method
         //       MUST be supplied to match the new version number
-        protected const int CURRENT_VERSION = 35;
+        protected const int CURRENT_VERSION = 36;
         protected const int CURRENT_METADATA_VERSION = 6;
         
 #region Migration Driver
@@ -802,6 +802,33 @@ namespace Banshee.Database
 
 #endregion
 
+#region Version 36
+
+        [DatabaseVersion (36)]
+        private bool Migrate_36 ()
+        {
+            Execute(@"
+                CREATE TABLE CoreShuffles (
+                    ShufflerId           INTEGER,
+                    TrackID             INTEGER,
+                    LastShuffledAt      INTEGER,
+                    CONSTRAINT one_entry_per_track UNIQUE (ShufflerID, TrackID)
+                )
+            ");
+            Execute("CREATE INDEX CoreShufflesIndex ON CoreShuffles (ShufflerId, TrackID, LastShuffledAt)");
+
+            Execute(@"
+                CREATE TABLE CoreShufflers (
+                    ShufflerId      INTEGER PRIMARY KEY,
+                    Id              TEXT UNIQUE
+                )
+            ");
+
+            return true;
+        }
+
+#endregion
+
 #pragma warning restore 0169
         
 #region Fresh database setup
@@ -1010,6 +1037,23 @@ namespace Banshee.Database
             // This index slows down queries were we shove data into the CoreCache.
             // Since we do that frequently, not using it.
             //Execute("CREATE INDEX CoreCacheModelId      ON CoreCache(ModelID)");
+
+            Execute(@"
+                CREATE TABLE CoreShuffles (
+                    ShufflerId           INTEGER,
+                    TrackID             INTEGER,
+                    LastShuffledAt      INTEGER,
+                    CONSTRAINT one_entry_per_track UNIQUE (ShufflerID, TrackID)
+                )
+            ");
+            Execute("CREATE INDEX CoreShufflesIndex ON CoreShuffles (ShufflerId, TrackID, LastShuffledAt)");
+
+            Execute(@"
+                CREATE TABLE CoreShufflers (
+                    ShufflerId      INTEGER PRIMARY KEY,
+                    Id              TEXT UNIQUE
+                )
+            ");
         }
         
 #endregion
diff --git a/src/Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs b/src/Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs
index 8b58e70..c7c5959 100644
--- a/src/Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs
+++ b/src/Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs
@@ -344,7 +344,10 @@ namespace Banshee.PlaybackController
         
         private TrackInfo QueryTrackRandom (PlaybackShuffleMode mode, bool restart)
         {
-            var track = Source.TrackModel.GetRandom (source_set_at, mode, restart, last_was_skipped);
+            var track_shuffler = Source.TrackModel as Banshee.Collection.Database.DatabaseTrackListModel;
+            TrackInfo track = track_shuffler == null
+                ? Source.TrackModel.GetRandom (source_set_at)
+                : track_shuffler.GetRandom (source_set_at, mode, restart, last_was_skipped, Banshee.Collection.Database.Shuffler.Playback);
             // Reset to default of true, only ever set to false by EosTransition
             last_was_skipped = true;
             return track;
diff --git a/src/Core/Banshee.Services/Makefile.am b/src/Core/Banshee.Services/Makefile.am
index c36d426..c48cdcf 100644
--- a/src/Core/Banshee.Services/Makefile.am
+++ b/src/Core/Banshee.Services/Makefile.am
@@ -29,6 +29,7 @@ SOURCES =  \
 	Banshee.Collection.Database/RandomByScore.cs \
 	Banshee.Collection.Database/RandomBySlot.cs \
 	Banshee.Collection.Database/RandomByTrack.cs \
+	Banshee.Collection.Database/Shuffler.cs \
 	Banshee.Collection.Database/Tests/DatabaseAlbumInfoTests.cs \
 	Banshee.Collection.Database/Tests/DatabaseArtistInfoTests.cs \
 	Banshee.Collection.Database/Tests/DatabaseTrackInfoTests.cs \
diff --git a/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/HeaderWidget.cs b/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/HeaderWidget.cs
index 96e6e63..1f0982c 100644
--- a/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/HeaderWidget.cs
+++ b/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/HeaderWidget.cs
@@ -114,14 +114,14 @@ namespace Banshee.PlayQueue
 
     public sealed class SourceChangedEventArgs : EventArgs
     {
-        private ITrackModelSource value;
+        private DatabaseSource value;
 
-        public SourceChangedEventArgs (ITrackModelSource value)
+        public SourceChangedEventArgs (DatabaseSource value)
         {
             this.value = value;
         }
 
-        public ITrackModelSource Value {
+        public DatabaseSource Value {
             get { return this.value; }
         }
     }
diff --git a/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/PlayQueueSource.cs b/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/PlayQueueSource.cs
index aa48a36..3ef237b 100644
--- a/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/PlayQueueSource.cs
+++ b/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/PlayQueueSource.cs
@@ -58,6 +58,7 @@ namespace Banshee.PlayQueue
 
         private ITrackModelSource prior_playback_source;
         private DatabaseTrackInfo current_track;
+        private Shuffler shuffler;
         private long offset;
         private TrackInfo prior_playback_track;
         private PlayQueueActions actions;
@@ -70,7 +71,7 @@ namespace Banshee.PlayQueue
 
         private PlaybackShuffleMode populate_mode = (PlaybackShuffleMode) PopulateModeSchema.Get ();
         private string populate_from_name = PopulateFromSchema.Get ();
-        private ITrackModelSource populate_from = null;
+        private DatabaseSource populate_from = null;
         private int played_songs_number = PlayedSongsNumberSchema.Get ();
         private int upcoming_songs_number = UpcomingSongsNumberSchema.Get ();
         
@@ -107,7 +108,7 @@ namespace Banshee.PlayQueue
             ServiceManager.SourceManager.VideoLibrary.TracksDeleted += HandleTracksDeleted;
             
             populate_from = ServiceManager.SourceManager.Sources.FirstOrDefault (
-                source => source.Name == populate_from_name) as ITrackModelSource;
+                source => source.Name == populate_from_name) as DatabaseSource;
             if (populate_from != null) {
                 populate_from.Reload ();
             }
@@ -125,6 +126,8 @@ namespace Banshee.PlayQueue
             header_widget = CreateHeaderWidget ();
             header_widget.ShowAll ();
 
+            shuffler = new Shuffler (UniqueId);
+
             Properties.Set<Gtk.Widget> ("Nereid.SourceContents.HeaderWidget", header_widget);
         }
         
@@ -568,8 +571,10 @@ namespace Banshee.PlayQueue
                 // Add songs from the selected source, skip if all tracks need to be populated.
                 bool skip = tracks_to_add == upcoming_songs_number;
                 for (int i = 0; i < tracks_to_add; i++) {
-                    var track = populate_from.TrackModel.GetRandom (
-                        source_set_at, populate_mode, false, skip && i == 0) as DatabaseTrackInfo;
+
+                    var track = populate_from.DatabaseTrackModel.GetRandom (
+                        source_set_at, populate_mode, false, skip && i == 0, shuffler) as DatabaseTrackInfo;
+
                     if (track != null) {
                         track.LastPlayed = DateTime.Now;
                         // track.Save() is quite slow, update LastPlayedStamp directly in the database.
diff --git a/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/QueueableSourceComboBox.cs b/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/QueueableSourceComboBox.cs
index 1ae0725..db7f6bc 100644
--- a/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/QueueableSourceComboBox.cs
+++ b/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/QueueableSourceComboBox.cs
@@ -103,11 +103,11 @@ namespace Banshee.PlayQueue
             return TreeIter.Zero;
         }
 
-        public ITrackModelSource Source {
+        public DatabaseSource Source {
             get {
                 TreeIter iter;
                 if (GetActiveIter (out iter)) {
-                    return filter.GetValue(iter, 0) as ITrackModelSource;
+                    return filter.GetValue(iter, 0) as DatabaseSource;
                 }
                 return null;
             }



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