[banshee] Fix random by album/artist/rating for playlists
- From: Gabriel Burt <gburt src gnome org>
- To: svn-commits-list gnome org
- Subject: [banshee] Fix random by album/artist/rating for playlists
- Date: Wed, 1 Jul 2009 19:22:20 +0000 (UTC)
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]