[banshee] New media art storage spec (bgo#520516)
- From: Alexander Kojevnikov <alexk src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [banshee] New media art storage spec (bgo#520516)
- Date: Thu, 18 Feb 2010 01:50:14 +0000 (UTC)
commit b40532989c9ece3a678d29c953b51f7d0af3991a
Author: Alexander Kojevnikov <alexander kojevnikov com>
Date: Thu Feb 18 12:41:44 2010 +1100
New media art storage spec (bgo#520516)
This commit implements a simplified version of the proposed spec [1]
* Image files consist of a prefix ('album-', 'podcast-') and a single
MD5 hash.
* The hash is calculated on *unmodified* artist and album name. That
is, no lower-casing, no stripping, no removal of 'special' characters
(@#!...) and no removal of what's inside brackets.
* For album covers, the hashed string is in this format:
'<artist>\t<album>'. If we used a space, for the string "foo bar baz"
we wouldn't be able to tell whether it's album "bar baz" by "foo" or
album "baz" by "foo bar".
* Strings are normalised to NFKD before hashing.
Thanks go to John Millikin and Bertrand Lorenz for initial work on the
spec implementation and to Gabriel Burt for overall guidance.
[1] http://live.gnome.org/MediaArtStorageSpec
src/Core/Banshee.Core/Banshee.Base/CoverArtSpec.cs | 66 +++++++++++----
.../Banshee.Base/Tests/CoverArtSpecTests.cs | 50 ++++--------
src/Core/Banshee.Core/Banshee.Core.csproj | 1 +
.../Banshee.Metadata.Rhapsody/RhapsodyQueryJob.cs | 15 +++-
.../Banshee.Collection.Gui/ArtworkManager.cs | 87 ++++++++++++++++++--
.../Banshee.Dap.MassStorage/MassStorageSource.cs | 7 +--
.../Banshee.Podcasting/PodcastService.cs | 3 +-
7 files changed, 160 insertions(+), 69 deletions(-)
---
diff --git a/src/Core/Banshee.Core/Banshee.Base/CoverArtSpec.cs b/src/Core/Banshee.Core/Banshee.Base/CoverArtSpec.cs
index 2b676f2..4340e3b 100644
--- a/src/Core/Banshee.Core/Banshee.Base/CoverArtSpec.cs
+++ b/src/Core/Banshee.Core/Banshee.Base/CoverArtSpec.cs
@@ -28,6 +28,8 @@
using System;
using System.IO;
+using System.Collections.Generic;
+using System.Text;
using System.Text.RegularExpressions;
using Mono.Unix;
@@ -85,25 +87,61 @@ namespace Banshee.Base
public static string CreateArtistAlbumId (string artist, string album)
{
- return CreateArtistAlbumId (artist, album, false);
+ if (artist == unknown_artist || artist == unknown_artist_tr || album == unknown_album || album == unknown_album_tr) {
+ return null;
+ }
+
+ string digestible = String.Format ("{0}\t{1}", artist ?? "", album ?? "");
+
+ return String.Format ("album-{0}", Digest (digestible));
}
- public static string CreateArtistAlbumId (string artist, string album, bool asUriPart)
+ public static string Digest (string str)
+ {
+ if (String.IsNullOrEmpty (str)) {
+ return null;
+ }
+
+ str = str.Normalize (NormalizationForm.FormKD);
+ return Hyena.CryptoUtil.Md5Encode (str, Encoding.UTF8);
+ }
+
+ static CoverArtSpec () {
+ Hyena.Log.DebugFormat ("Album artwork path set to {0}", root_path);
+ }
+
+ private static string root_path = Path.Combine (XdgBaseDirectorySpec.GetUserDirectory (
+ "XDG_CACHE_HOME", ".cache"), "media-art");
+
+ public static string RootPath {
+ get { return root_path; }
+ }
+
+ #region Old spec
+
+ private static string legacy_root_path = Path.Combine (XdgBaseDirectorySpec.GetUserDirectory (
+ "XDG_CACHE_HOME", ".cache"), "album-art");
+
+ public static string LegacyRootPath {
+ get { return legacy_root_path; }
+ }
+
+ public static string CreateLegacyArtistAlbumId (string artist, string album)
{
if (artist == unknown_artist || artist == unknown_artist_tr || album == unknown_album || album == unknown_album_tr) {
return null;
}
- string sm_artist = EscapePart (artist);
- string sm_album = EscapePart (album);
+ string sm_artist = LegacyEscapePart (artist);
+ string sm_album = LegacyEscapePart (album);
return String.IsNullOrEmpty (sm_artist) || String.IsNullOrEmpty (sm_album)
? null
- : String.Format ("{0}{1}{2}", sm_artist, asUriPart ? "/" : "-", sm_album);
+ : String.Format ("{0}{1}{2}", sm_artist, "-", sm_album);
}
- private static Regex filter_regex = new Regex (@"[^A-Za-z0-9]*", RegexOptions.Compiled);
- public static string EscapePart (string part)
+ private static Regex legacy_filter_regex = new Regex (@"[^A-Za-z0-9]*", RegexOptions.Compiled);
+ public static string LegacyEscapePart (string part)
{
if (String.IsNullOrEmpty (part)) {
return null;
@@ -113,19 +151,9 @@ namespace Banshee.Base
if (lp_index > 0) {
part = part.Substring (0, lp_index);
}
-
- return filter_regex.Replace (part, "").ToLower ();
- }
-
- private static string root_path = Path.Combine (XdgBaseDirectorySpec.GetUserDirectory (
- "XDG_CACHE_HOME", ".cache"), "album-art");
-
- static CoverArtSpec () {
- Hyena.Log.DebugFormat ("Album artwork path set to {0}", root_path);
+ return legacy_filter_regex.Replace (part, "").ToLower ();
}
- public static string RootPath {
- get { return root_path; }
- }
+ #endregion Old spec
}
}
diff --git a/src/Core/Banshee.Core/Banshee.Base/Tests/CoverArtSpecTests.cs b/src/Core/Banshee.Core/Banshee.Base/Tests/CoverArtSpecTests.cs
index 5335c3d..25f2dbb 100644
--- a/src/Core/Banshee.Core/Banshee.Base/Tests/CoverArtSpecTests.cs
+++ b/src/Core/Banshee.Core/Banshee.Base/Tests/CoverArtSpecTests.cs
@@ -34,59 +34,41 @@ using Banshee.Base;
namespace Banshee.Base.Tests
{
[TestFixture]
- public class EscapePartTests
+ public class DigestPartTests
{
- private void AssertEscaped (string original, string expected)
+ private void AssertDigested (string original, string expected)
{
- Assert.AreEqual (expected, CoverArtSpec.EscapePart (original));
+ Assert.AreEqual (expected, CoverArtSpec.Digest (original));
}
[Test]
public void TestEmpty ()
{
- AssertEscaped (null, null);
- AssertEscaped ("", null);
+ AssertDigested (null, null);
+ AssertDigested ("", null);
+ AssertDigested (" ", "7215ee9c7d9dc229d2921a40e899ec5f");
}
[Test]
- public void TestLowercased ()
+ public void TestUnicode ()
{
- AssertEscaped ("A", "a");
+ AssertDigested ("\u00e9", "5526861fbb1e71a1bda6ac364310a807");
+ AssertDigested ("e\u0301", "5526861fbb1e71a1bda6ac364310a807");
}
[Test]
- public void TestUnwanted ()
+ public void TestEscaped ()
{
- // Part of the in-progress media art storage spec
- AssertEscaped ("!", "");
- AssertEscaped ("@", "");
- AssertEscaped ("#", "");
- AssertEscaped ("$", "");
- AssertEscaped ("^", "");
- AssertEscaped ("&", "");
- AssertEscaped ("*", "");
- AssertEscaped ("_", "");
- AssertEscaped ("+", "");
- AssertEscaped ("=", "");
- AssertEscaped ("|", "");
- AssertEscaped ("\\", "");
- AssertEscaped ("/", "");
- AssertEscaped ("?", "");
- AssertEscaped ("~", "");
- AssertEscaped ("`", "");
- AssertEscaped ("'", "");
- AssertEscaped ("\"", "");
-
- // Banshee-specific: strip *everything* non-ASCII
- AssertEscaped ("\u00e9toile", "toile");
- AssertEscaped ("e\u0301", "e");
+ AssertDigested ("(a)", "69dfdf4e6a7c8489262f9d8b9958c9b3");
}
[Test]
- public void TestStripNotes ()
+ public void TestExamples ()
{
- AssertEscaped ("a(b)cd", "a");
- AssertEscaped ("a(b)c(d)e", "abc");
+ AssertDigested ("Metallica", "8b0ee5a501cef4a5699fd3b2d4549e8f");
+ AssertDigested ("And Justice For All", "17e81b0a8cc3038f346e809fccda207d");
+ AssertDigested ("Radio ga ga", "baad45f3f55461478cf25e3221f4f40d");
+ AssertDigested ("World Soccer", "cd678d70c5c329759a7a7b476f7c71b1");
}
}
}
diff --git a/src/Core/Banshee.Core/Banshee.Core.csproj b/src/Core/Banshee.Core/Banshee.Core.csproj
index 6292653..d9e3088 100644
--- a/src/Core/Banshee.Core/Banshee.Core.csproj
+++ b/src/Core/Banshee.Core/Banshee.Core.csproj
@@ -24,6 +24,7 @@
<WarningLevel>4</WarningLevel>
<Optimize>false</Optimize>
<OutputPath>..\..\..\bin</OutputPath>
+ <DefineConstants>ENABLE_TESTS</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Windows|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
diff --git a/src/Core/Banshee.Services/Banshee.Metadata.Rhapsody/RhapsodyQueryJob.cs b/src/Core/Banshee.Services/Banshee.Metadata.Rhapsody/RhapsodyQueryJob.cs
index 02d42e2..4dfbdab 100644
--- a/src/Core/Banshee.Services/Banshee.Metadata.Rhapsody/RhapsodyQueryJob.cs
+++ b/src/Core/Banshee.Services/Banshee.Metadata.Rhapsody/RhapsodyQueryJob.cs
@@ -48,6 +48,19 @@ namespace Banshee.Metadata.Rhapsody
{
private static Uri base_uri = new Uri("http://www.rhapsody.com/");
+ private static Regex filter_regex = new Regex (@"[^A-Za-z0-9]*", RegexOptions.Compiled);
+ private static string EscapeUrlPart (string part)
+ {
+ return filter_regex.Replace (part, "").Replace (" ", "-").ToLower ();
+ }
+
+ private static string GetAlbumUrl (IBasicTrackInfo track)
+ {
+ string artist = EscapeUrlPart (track.AlbumArtist);
+ string album = EscapeUrlPart (track.AlbumTitle);
+ return String.Format ("{0}/{1}", artist, album);
+ }
+
public RhapsodyQueryJob(IBasicTrackInfo track)
{
Track = track;
@@ -65,7 +78,7 @@ namespace Banshee.Metadata.Rhapsody
return;
}
- Uri data_uri = new Uri(base_uri, String.Format("/{0}/data.xml", artwork_id.Replace('-', '/')));
+ Uri data_uri = new Uri(base_uri, String.Format("/{0}/data.xml", GetAlbumUrl (Track)));
XmlDocument doc = new XmlDocument();
HttpWebResponse response = GetHttpStream (data_uri);
diff --git a/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtworkManager.cs b/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtworkManager.cs
index 90b5577..618aebc 100644
--- a/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtworkManager.cs
+++ b/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtworkManager.cs
@@ -37,6 +37,7 @@ using Gdk;
using Hyena;
using Hyena.Gui;
using Hyena.Collections;
+using Hyena.Data.Sqlite;
using Banshee.Base;
using Banshee.IO;
@@ -254,7 +255,7 @@ namespace Banshee.Collection.Gui
#region Cache Directory Versioning/Migration
- private const int CUR_VERSION = 2;
+ private const int CUR_VERSION = 3;
private void MigrateCacheDir ()
{
int version = CacheVersion;
@@ -262,16 +263,16 @@ namespace Banshee.Collection.Gui
return;
}
- var root_path = CoverArtSpec.RootPath;
+ var legacy_root_path = CoverArtSpec.LegacyRootPath;
if (version < 1) {
string legacy_artwork_path = Paths.Combine (Paths.LegacyApplicationData, "covers");
- if (!Directory.Exists (root_path)) {
- Directory.Create (CoverArtSpec.RootPath);
+ if (!Directory.Exists (legacy_root_path)) {
+ Directory.Create (legacy_root_path);
if (Directory.Exists (legacy_artwork_path)) {
- Directory.Move (new SafeUri (legacy_artwork_path), new SafeUri (root_path));
+ Directory.Move (new SafeUri (legacy_artwork_path), new SafeUri (legacy_root_path));
}
}
@@ -283,7 +284,7 @@ namespace Banshee.Collection.Gui
if (version < 2) {
int deleted = 0;
- foreach (string dir in Directory.GetDirectories (root_path)) {
+ foreach (string dir in Directory.GetDirectories (legacy_root_path)) {
int size;
string dirname = System.IO.Path.GetFileName (dir);
if (Int32.TryParse (dirname, out size) && !IsCachedSize (size)) {
@@ -297,14 +298,84 @@ namespace Banshee.Collection.Gui
}
}
+ if (version < 3) {
+ Log.Information ("Migrating album-art cache directory");
+ var started = DateTime.Now;
+ int count = 0;
+
+ var root_path = CoverArtSpec.RootPath;
+ if (!Directory.Exists (root_path)) {
+ Directory.Create (root_path);
+ }
+
+ string sql = "SELECT Title, ArtistName FROM CoreAlbums";
+ using (var reader = new HyenaDataReader (ServiceManager.DbConnection.Query (sql))) {
+ while (reader.Read ()) {
+ var album = reader.Get<string>(0);
+ var artist = reader.Get<string>(1);
+ var old_file = CoverArtSpec.CreateLegacyArtistAlbumId (artist, album);
+ var new_file = CoverArtSpec.CreateArtistAlbumId (artist, album);
+
+ if (String.IsNullOrEmpty (old_file) || String.IsNullOrEmpty (new_file)) {
+ continue;
+ }
+
+ old_file = String.Format ("{0}.jpg", old_file);
+ new_file = String.Format ("{0}.jpg", new_file);
+
+ var old_path = new SafeUri (Paths.Combine (legacy_root_path, old_file));
+ var new_path = new SafeUri (Paths.Combine (root_path, new_file));
+
+ if (Banshee.IO.File.Exists (old_path) && !Banshee.IO.File.Exists (new_path)) {
+ Banshee.IO.File.Move (old_path, new_path);
+ count++;
+ }
+ }
+ }
+
+ if (ServiceManager.DbConnection.TableExists ("PodcastSyndications")) {
+ sql = "SELECT Title FROM PodcastSyndications";
+ foreach (var title in ServiceManager.DbConnection.QueryEnumerable<string> (sql)) {
+ var old_digest = CoverArtSpec.LegacyEscapePart (title);
+ var new_digest = CoverArtSpec.Digest (title);
+
+ if (String.IsNullOrEmpty (old_digest) || String.IsNullOrEmpty (new_digest)) {
+ continue;
+ }
+
+ var old_file = String.Format ("podcast-{0}.jpg", old_digest);
+ var new_file = String.Format ("podcast-{0}.jpg", new_digest);
+
+ var old_path = new SafeUri (Paths.Combine (legacy_root_path, old_file));
+ var new_path = new SafeUri (Paths.Combine (root_path, new_file));
+
+ if (Banshee.IO.File.Exists (old_path) && !Banshee.IO.File.Exists (new_path)) {
+ Banshee.IO.File.Move (old_path, new_path);
+ count++;
+ }
+ }
+ }
+
+ Directory.Delete (legacy_root_path, true);
+ Log.InformationFormat ("Migrated {0} files in {1}s", count, DateTime.Now.Subtract(started).TotalSeconds);
+ }
+
CacheVersion = CUR_VERSION;
}
private static SafeUri cache_version_file = new SafeUri (Paths.Combine (CoverArtSpec.RootPath, ".cache_version"));
private static int CacheVersion {
get {
- if (Banshee.IO.File.Exists (cache_version_file)) {
- using (var reader = new System.IO.StreamReader (Banshee.IO.File.OpenRead (cache_version_file))) {
+ var file = cache_version_file;
+ if (!Banshee.IO.File.Exists (file)) {
+ file = new SafeUri (Paths.Combine (CoverArtSpec.LegacyRootPath, ".cache_version"));
+ if (!Banshee.IO.File.Exists (file)) {
+ file = null;
+ }
+ }
+
+ if (file != null) {
+ using (var reader = new System.IO.StreamReader (Banshee.IO.File.OpenRead (file))) {
int version;
if (Int32.TryParse (reader.ReadLine (), out version)) {
return version;
diff --git a/src/Dap/Banshee.Dap.MassStorage/Banshee.Dap.MassStorage/MassStorageSource.cs b/src/Dap/Banshee.Dap.MassStorage/Banshee.Dap.MassStorage/MassStorageSource.cs
index 3e687c6..aa48186 100644
--- a/src/Dap/Banshee.Dap.MassStorage/Banshee.Dap.MassStorage/MassStorageSource.cs
+++ b/src/Dap/Banshee.Dap.MassStorage/Banshee.Dap.MassStorage/MassStorageSource.cs
@@ -509,12 +509,7 @@ namespace Banshee.Dap.MassStorage
!String.IsNullOrEmpty (CoverArtFileName) && (FolderDepth == -1 || FolderDepth > 0)) {
SafeUri cover_uri = new SafeUri (System.IO.Path.Combine (System.IO.Path.GetDirectoryName (new_uri.LocalPath),
CoverArtFileName));
- string coverart_id;
- if (track.HasAttribute (TrackMediaAttributes.Podcast)) {
- coverart_id = String.Format ("podcast-{0}", Banshee.Base.CoverArtSpec.EscapePart (track.AlbumTitle));
- } else {
- coverart_id = track.ArtworkId;
- }
+ string coverart_id = track.ArtworkId;
if (!File.Exists (cover_uri) && CoverArtSpec.CoverExists (coverart_id)) {
Gdk.Pixbuf pic = null;
diff --git a/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastService.cs b/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastService.cs
index 01b3cbc..9fc9987 100644
--- a/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastService.cs
+++ b/src/Extensions/Banshee.Podcasting/Banshee.Podcasting/PodcastService.cs
@@ -608,7 +608,8 @@ namespace Banshee.Podcasting
public static string ArtworkIdFor (Feed feed)
{
- return String.Format ("podcast-{0}", Banshee.Base.CoverArtSpec.EscapePart (feed.Title));
+ string digest = Banshee.Base.CoverArtSpec.Digest (feed.Title);
+ return digest == null ? null : String.Format ("podcast-{0}", digest);
}
// Via Monopod
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]