[banshee/filenamepatterns] Let PrimarySources have their own FileNamePattern



commit d8775a042c8042afa6d6c3f983d3d2b34a2dc9d6
Author: Gabriel Burt <gabriel burt gmail com>
Date:   Tue Dec 29 17:25:06 2009 -0600

    Let PrimarySources have their own FileNamePattern
    
    Use it for Audiobooks to let users organize audiobook files differently
    than their music.  See the Audiobook source-specific preferences.

 .../Banshee.Core/Banshee.Base/FileNamePattern.cs   |  256 ++++++--------------
 .../Banshee.Collection/SampleTrackInfo.cs          |    2 +
 src/Core/Banshee.Core/Makefile.am                  |    3 -
 .../DatabaseTrackInfo.cs                           |    9 +
 .../Banshee.Library}/LibrarySchema.cs              |    5 +-
 .../Banshee.Library/LibrarySource.cs               |   10 +-
 .../Banshee.Library/MusicFileNamePattern.cs        |  166 +++++++++++++
 .../Banshee.Library/MusicLibrarySource.cs          |   21 +-
 .../Banshee.Library}/Tests/FileNamePatternTests.cs |    4 +-
 .../Banshee.Metadata/SaveTrackMetadataJob.cs       |    2 +-
 .../Tests/TaglibReadWriteTests.cs                  |    2 +-
 .../Banshee.Sources/PrimarySource.cs               |   12 +
 src/Core/Banshee.Services/Makefile.am              |    4 +
 .../DefaultPreferenceWidgets.cs                    |   51 +++--
 .../Banshee.Dap.MassStorage/AndroidDevice.cs       |    2 +-
 .../Banshee.AudioCd/AudioCdRipper.cs               |    4 +-
 .../Banshee.Audiobook/AudiobookFileNamePattern.cs  |  148 +++++++++++
 .../Banshee.Audiobook/AudiobookLibrarySource.cs    |    6 +
 src/Extensions/Banshee.Audiobook/Makefile.am       |    3 +-
 19 files changed, 481 insertions(+), 229 deletions(-)
---
diff --git a/src/Core/Banshee.Core/Banshee.Base/FileNamePattern.cs b/src/Core/Banshee.Core/Banshee.Base/FileNamePattern.cs
index b0f2bf7..ab96cc3 100644
--- a/src/Core/Banshee.Core/Banshee.Base/FileNamePattern.cs
+++ b/src/Core/Banshee.Core/Banshee.Base/FileNamePattern.cs
@@ -3,8 +3,11 @@
 //
 // Author:
 //   Aaron Bockover <abockover novell com>
+//   Alexander Kojevnikov <alexander kojevnikov com>
+//   Gabriel Burt <gburt novell com>
 //
-// Copyright (C) 2005-2008 Novell, Inc.
+// Copyright (C) 2005-2009 Novell, Inc.
+// Copyright (C) 2009 Alexander Kojevnikov
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
@@ -32,205 +35,52 @@ using System.Collections.Generic;
 using System.IO;
 using Mono.Unix;
 
-using Banshee.Collection;
+using Banshee.Configuration;
 using Banshee.Configuration.Schema;
+using Banshee.Collection;
 
 namespace Banshee.Base
 {
-    public static class FileNamePattern
+    public class FileNamePattern
     {
         public delegate string ExpandTokenHandler (TrackInfo track, object replace);
         public delegate string FilterHandler (string path);
 
-        public static FilterHandler Filter;
-
-        public struct Conversion
-        {
-            private readonly string token;
-            private readonly string name;
-            private readonly ExpandTokenHandler handler;
+        public FilterHandler Filter;
 
-            private readonly string token_string;
-
-            public Conversion (string token, string name, ExpandTokenHandler handler)
-            {
-                this.token = token;
-                this.name = name;
-                this.handler = handler;
+        private SortedList<string, Conversion> conversion_table = new SortedList<string, Conversion> ();
 
-                this.token_string = "%" + this.token + "%";
-            }
-
-            public string Token {
-                get { return token; }
-            }
-
-            public string Name {
-                get { return name; }
-            }
-
-            public ExpandTokenHandler Handler {
-                get { return handler; }
-            }
-
-            public string TokenString {
-                get { return token_string; }
-            }
-        }
-
-        private static SortedList<string, Conversion> conversion_table;
-
-        public static void AddConversion (string token, string name, ExpandTokenHandler handler)
+        public FileNamePattern ()
         {
-            conversion_table.Add (token, new Conversion (token, name, handler));
         }
 
-        static FileNamePattern ()
+        public void AddConversion (string token, string name, ExpandTokenHandler handler)
         {
-            conversion_table = new SortedList<string, Conversion> ();
-
-            AddConversion ("track_artist", Catalog.GetString ("Track Artist"),
-                delegate (TrackInfo t, object r) {
-                    return Escape (t == null ? (string)r : t.DisplayArtistName);
-            });
-
-            AddConversion ("album_artist", Catalog.GetString ("Album Artist"),
-                delegate (TrackInfo t, object r) {
-                    return Escape (t == null ? (string)r : t.DisplayAlbumArtistName);
-            });
-
-            // Alias for %album_artist%
-            AddConversion ("artist", Catalog.GetString ("Album Artist"),
-                delegate (TrackInfo t, object r) {
-                    return Escape (t == null ? (string)r : t.DisplayAlbumArtistName);
-            });
-
-            AddConversion ("album_artist_initial", Catalog.GetString("Album Artist Initial"),
-                delegate (TrackInfo t, object r) {
-                    return Escape (t == null ? (string)r : t.DisplayAlbumArtistName.Substring(0, 1));
-            });
-
-            AddConversion ("conductor", Catalog.GetString ("Conductor"),
-                delegate (TrackInfo t, object r) {
-                    return Escape (t == null ? (string)r : t.Conductor);
-            });
-
-            AddConversion ("composer", Catalog.GetString ("Composer"),
-                delegate (TrackInfo t, object r) {
-                    return Escape (t == null ? (string)r : t.Composer);
-            });
-
-            AddConversion ("genre", Catalog.GetString ("Genre"),
-                delegate (TrackInfo t, object r) {
-                    return Escape (t == null ? (string)r : t.DisplayGenre);
-            });
-
-            AddConversion ("album", Catalog.GetString ("Album"),
-                delegate (TrackInfo t, object r) {
-                    return Escape (t == null ? (string)r : t.DisplayAlbumTitle);
-            });
-
-            AddConversion ("title", Catalog.GetString ("Title"),
-                delegate (TrackInfo t, object r) {
-                    return Escape (t == null ? (string)r : t.DisplayTrackTitle);
-            });
-
-            AddConversion ("year", Catalog.GetString ("Year"),
-                delegate (TrackInfo t, object r) {
-                    int year = t == null ? (int)r : t.Year;
-                    return year > 0 ? String.Format ("{0}", year) : null;
-            });
-
-            AddConversion ("track_count", Catalog.GetString ("Count"),
-                delegate (TrackInfo t, object r) {
-                    int track_count = t == null ? (int)r : t.TrackCount;
-                    return track_count > 0 ? String.Format ("{0:00}", track_count) : null;
-            });
-
-            AddConversion ("track_number", Catalog.GetString ("Number"),
-                delegate (TrackInfo t, object r) {
-                    int track_number = t == null ? (int)r : t.TrackNumber;
-                    return track_number > 0 ? String.Format ("{0:00}", track_number) : null;
-            });
-
-            AddConversion ("track_count_nz", Catalog.GetString ("Count (unsorted)"),
-                delegate (TrackInfo t, object r) {
-                    int track_count = t == null ? (int)r : t.TrackCount;
-                    return track_count > 0 ? String.Format ("{0}", track_count) : null;
-            });
-
-            AddConversion ("track_number_nz", Catalog.GetString ("Number (unsorted)"),
-                delegate (TrackInfo t, object r) {
-                    int track_number = t == null ? (int)r : t.TrackNumber;
-                    return track_number > 0 ? String.Format ("{0}", track_number) : null;
-            });
-
-            AddConversion ("disc_count", Catalog.GetString ("Disc Count"),
-                delegate (TrackInfo t, object r) {
-                    int disc_count = t == null ? (int)r : t.DiscCount;
-                    return disc_count > 0 ? String.Format ("{0}", disc_count) : null;
-            });
-
-            AddConversion ("disc_number", Catalog.GetString ("Disc Number"),
-                delegate (TrackInfo t, object r) {
-                    int disc_number = t == null ? (int)r : t.DiscNumber;
-                    return disc_number > 0 ? String.Format ("{0}", disc_number) : null;
-            });
-
-            AddConversion ("grouping", Catalog.GetString ("Grouping"),
-                delegate (TrackInfo t, object r) {
-                    return Escape (t == null ? (string)r : t.Grouping);
-            });
-
-            AddConversion ("path_sep", Path.DirectorySeparatorChar.ToString (),
-                delegate (TrackInfo t, object r) {
-                    return Path.DirectorySeparatorChar.ToString ();
-            });
+            conversion_table.Add (token, new Conversion (token, name, handler));
         }
 
-        public static IEnumerable<Conversion> PatternConversions {
+        public IEnumerable<Conversion> PatternConversions {
             get { return conversion_table.Values; }
         }
 
-        public static string DefaultFolder {
-            get { return "%album_artist%%path_sep%%album%"; }
+        public virtual IEnumerable<TrackInfo> SampleTracks {
+            get { yield return new SampleTrackInfo (); }
         }
 
-        public static string DefaultFile {
-            get { return "%track_number%. %title%"; }
-        }
+        public string DefaultFolder { get; set; }
+        public string DefaultFile   { get; set; }
 
-        public static string DefaultPattern {
+        public string DefaultPattern {
             get { return CreateFolderFilePattern (DefaultFolder, DefaultFile); }
         }
 
-        private static string [] suggested_folders = new string [] {
-            DefaultFolder,
-            "%album_artist%%path_sep%%album_artist% - %album%",
-            "%album_artist%%path_sep%%album% (%year%)",
-            "%album_artist% - %album%",
-            "%album%",
-            "%album_artist%"
-        };
-
-        public static string [] SuggestedFolders {
-            get { return suggested_folders; }
-        }
+        public SchemaEntry<string> FileSchema { get; set; }
+        public SchemaEntry<string> FolderSchema { get; set; }
 
-        private static string [] suggested_files = new string [] {
-            DefaultFile,
-            "%track_number%. %track_artist% - %title%",
-            "%track_artist% - %title%",
-            "%track_artist% - %track_number% - %title%",
-            "%track_artist% (%album%) - %track_number% - %title%",
-            "%title%"
-        };
-
-        public static string [] SuggestedFiles {
-            get { return suggested_files; }
-        }
+        public string [] SuggestedFolders { get; set; }
+        public string [] SuggestedFiles   { get; set; }
 
-        private static string OnFilter (string input)
+        private string OnFilter (string input)
         {
             string repl_pattern = input;
 
@@ -242,33 +92,29 @@ namespace Banshee.Base
             return repl_pattern;
         }
 
-        public static string CreateFolderFilePattern (string folder, string file)
+        public string CreateFolderFilePattern (string folder, string file)
         {
             return String.Format ("{0}%path_sep%{1}", folder, file);
         }
 
-        public static string CreatePatternDescription (string pattern)
+        public string CreatePatternDescription (string pattern)
         {
             pattern = Convert (pattern, conversion => conversion.Name);
             return OnFilter (pattern);
         }
 
-        public static string CreateFromTrackInfo (TrackInfo track)
+        public string CreateFromTrackInfo (TrackInfo track)
         {
             string pattern = null;
 
             try {
-                pattern = CreateFolderFilePattern (
-                    LibrarySchema.FolderPattern.Get (),
-                    LibrarySchema.FilePattern.Get ()
-                );
-            } catch {
-            }
+                pattern = CreateFolderFilePattern (FolderSchema.Get (), FileSchema.Get ());
+            } catch {}
 
             return CreateFromTrackInfo (pattern, track);
         }
 
-        public static string CreateFromTrackInfo (string pattern, TrackInfo track)
+        public string CreateFromTrackInfo (string pattern, TrackInfo track)
         {
             if (pattern == null || pattern.Trim () == String.Empty) {
                 pattern = DefaultPattern;
@@ -281,8 +127,12 @@ namespace Banshee.Base
 
         private static Regex optional_tokens_regex = new Regex ("{([^}]*)}", RegexOptions.Compiled);
 
-        public static string Convert (string pattern, Func<Conversion, string> handler)
+        public string Convert (string pattern, Func<Conversion, string> handler)
         {
+            if (String.IsNullOrEmpty (pattern)) {
+                return null;
+            }
+
             pattern = optional_tokens_regex.Replace (pattern, delegate (Match match) {
                 var sub_pattern = match.Groups[1].Value;
                 foreach (var conversion in PatternConversions) {
@@ -307,12 +157,12 @@ namespace Banshee.Base
             return pattern;
         }
 
-        public static string BuildFull (string base_dir, TrackInfo track)
+        public string BuildFull (string base_dir, TrackInfo track)
         {
             return BuildFull (base_dir, track, Path.GetExtension (track.Uri.ToString ()));
         }
 
-        public static string BuildFull (string base_dir, TrackInfo track, string ext)
+        public string BuildFull (string base_dir, TrackInfo track, string ext)
         {
             if (ext == null || ext.Length < 1) {
                 ext = String.Empty;
@@ -337,5 +187,39 @@ namespace Banshee.Base
         {
             return Hyena.StringUtil.EscapeFilename (input);
         }
+
+        public struct Conversion
+        {
+            private readonly string token;
+            private readonly string name;
+            private readonly ExpandTokenHandler handler;
+
+            private readonly string token_string;
+
+            public Conversion (string token, string name, ExpandTokenHandler handler)
+            {
+                this.token = token;
+                this.name = name;
+                this.handler = handler;
+
+                this.token_string = "%" + this.token + "%";
+            }
+
+            public string Token {
+                get { return token; }
+            }
+
+            public string Name {
+                get { return name; }
+            }
+
+            public ExpandTokenHandler Handler {
+                get { return handler; }
+            }
+
+            public string TokenString {
+                get { return token_string; }
+            }
+        }
     }
 }
diff --git a/src/Core/Banshee.Core/Banshee.Collection/SampleTrackInfo.cs b/src/Core/Banshee.Core/Banshee.Collection/SampleTrackInfo.cs
index 4f50859..42e3ea7 100644
--- a/src/Core/Banshee.Core/Banshee.Collection/SampleTrackInfo.cs
+++ b/src/Core/Banshee.Core/Banshee.Collection/SampleTrackInfo.cs
@@ -39,6 +39,8 @@ namespace Banshee.Collection
             AlbumTitle = "Help!";
             TrackNumber = 7;
             TrackCount = 14;
+            DiscNumber = 1;
+            DiscCount = 2;
             Duration = TimeSpan.FromSeconds (182);
             Year = 1965;
 
diff --git a/src/Core/Banshee.Core/Makefile.am b/src/Core/Banshee.Core/Makefile.am
index ccf03e9..4cba7ba 100644
--- a/src/Core/Banshee.Core/Makefile.am
+++ b/src/Core/Banshee.Core/Makefile.am
@@ -13,8 +13,6 @@ SOURCES =  \
 	Banshee.Base/Resource.cs \
 	Banshee.Base/SafeUri.cs \
 	Banshee.Base/Tests/CoverArtSpecTests.cs \
-	Banshee.Base/Tests/FileNamePatternTests.cs \
-	Banshee.Base/Tests/TaglibReadWriteTests.cs \
 	Banshee.Base/UriList.cs \
 	Banshee.Base/XdgBaseDirectorySpec.cs \
 	Banshee.Collection/AlbumInfo.cs \
@@ -31,7 +29,6 @@ SOURCES =  \
 	Banshee.Collection/TrackMediaAttributes.cs \
 	Banshee.Collection/UnknownTrackInfo.cs \
 	Banshee.Configuration.Schema/ImportSchema.cs \
-	Banshee.Configuration.Schema/LibrarySchema.cs \
 	Banshee.Configuration/ConfigurationClient.cs \
 	Banshee.Configuration/IConfigurationClient.cs \
 	Banshee.Configuration/MemoryConfigurationClient.cs \
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs
index a96c644..edb3b48 100644
--- a/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs
@@ -43,6 +43,7 @@ using Banshee.Metadata;
 using Banshee.Preferences;
 using Banshee.Query;
 using Banshee.Sources;
+using Banshee.Library;
 using Banshee.ServiceStack;
 using Banshee.Streaming;
 
@@ -629,6 +630,14 @@ namespace Banshee.Collection.Database
             }
         }
 
+        public FileNamePattern FileNamePattern {
+            get {
+                var src = PrimarySource;
+                var pattern = src == null ? null : src.FileNamePattern;
+                return pattern ?? MusicLibrarySource.MusicFileNamePattern;
+            }
+        }
+
         public bool CopyToLibraryIfAppropriate (bool force_copy)
         {
             bool copy_success = true;
diff --git a/src/Core/Banshee.Core/Banshee.Configuration.Schema/LibrarySchema.cs b/src/Core/Banshee.Services/Banshee.Library/LibrarySchema.cs
similarity index 97%
rename from src/Core/Banshee.Core/Banshee.Configuration.Schema/LibrarySchema.cs
rename to src/Core/Banshee.Services/Banshee.Library/LibrarySchema.cs
index 6a17f7d..a95ee1a 100644
--- a/src/Core/Banshee.Core/Banshee.Configuration.Schema/LibrarySchema.cs
+++ b/src/Core/Banshee.Services/Banshee.Library/LibrarySchema.cs
@@ -28,6 +28,7 @@
 
 using System;
 using Banshee.Configuration;
+using Banshee.Library;
 
 namespace Banshee.Configuration.Schema
 {
@@ -38,7 +39,7 @@ namespace Banshee.Configuration.Schema
 
         public static readonly SchemaEntry<string> FolderPattern = new SchemaEntry<string>(
             "library", "folder_pattern",
-            Banshee.Base.FileNamePattern.DefaultFolder,
+            "%album_artist%%path_sep%%album%",
             "Library Folder Pattern",
             "Format for creating a track folder inside the library. Do not create an absolute path. " +
                 "Location here is relative to the Banshee music directory. See LibraryLocation. Legal tokens: " +
@@ -49,7 +50,7 @@ namespace Banshee.Configuration.Schema
 
         public static readonly SchemaEntry<string> FilePattern = new SchemaEntry<string>(
             "library", "file_pattern",
-            Banshee.Base.FileNamePattern.DefaultFile,
+            "%track_number%. %title%",
             "Library File Pattern",
             "Format for creating a track filename inside the library. Do not use path tokens/characters here. " +
                 "See LibraryFolderPattern. Legal tokens: %album_artist%, %track_artist%, %album%, %genre%, %title%, %track_number%, " +
diff --git a/src/Core/Banshee.Services/Banshee.Library/LibrarySource.cs b/src/Core/Banshee.Services/Banshee.Library/LibrarySource.cs
index ad713e1..c55ea11 100644
--- a/src/Core/Banshee.Services/Banshee.Library/LibrarySource.cs
+++ b/src/Core/Banshee.Services/Banshee.Library/LibrarySource.cs
@@ -137,7 +137,7 @@ namespace Banshee.Library
 
             PrimarySource source = track.PrimarySource;
 
-            // If it's from a local primary source, change it's PrimarySource
+            // If it's from a local primary source, change its PrimarySource
             if (source.IsLocal || source is LibrarySource) {
                 track.PrimarySource = this;
 
@@ -146,14 +146,18 @@ namespace Banshee.Library
                 }
 
                 track.Save (false);
+
+                // TODO optimize, remove this?  I think it makes moving items
+                // between local libraries very slow.
                 source.NotifyTracksChanged ();
             } else {
                 // Figure out where we should put it if were to copy it
-                string path = FileNamePattern.BuildFull (BaseDirectory, track);
+                var pattern = this.FileNamePattern ?? MusicLibrarySource.MusicFileNamePattern;
+                string path = pattern.BuildFull (BaseDirectory, track);
                 SafeUri uri = new SafeUri (path);
 
                 // Make sure it's not already in the library
-                // TODO optimize - no need to recrate this int [] every time
+                // TODO optimize - no need to recreate this int [] every time
                 if (DatabaseTrackInfo.ContainsUri (uri, new int [] {DbId})) {
                     return;
                 }
diff --git a/src/Core/Banshee.Services/Banshee.Library/MusicFileNamePattern.cs b/src/Core/Banshee.Services/Banshee.Library/MusicFileNamePattern.cs
new file mode 100644
index 0000000..13528f1
--- /dev/null
+++ b/src/Core/Banshee.Services/Banshee.Library/MusicFileNamePattern.cs
@@ -0,0 +1,166 @@
+//
+// MusicFileNamePattern.cs
+//
+// Author:
+//   Aaron Bockover <abockover novell com>
+//   Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2005-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.Text.RegularExpressions;
+using System.Collections.Generic;
+using System.IO;
+using Mono.Unix;
+
+using Banshee.Base;
+using Banshee.Collection;
+using Banshee.Configuration.Schema;
+
+namespace Banshee.Library
+{
+    public class MusicFileNamePattern : FileNamePattern
+    {
+        public MusicFileNamePattern ()
+        {
+            DefaultFolder    = LibrarySchema.FolderPattern.DefaultValue;
+            DefaultFile      = LibrarySchema.FilePattern.DefaultValue;
+
+            SuggestedFolders = new string [] {
+                DefaultFolder,
+                "%album_artist%%path_sep%%album_artist% - %album%",
+                "%album_artist%%path_sep%%album% (%year%)",
+                "%album_artist% - %album%",
+                "%album%",
+                "%album_artist%"
+            };
+
+            SuggestedFiles   = new string [] {
+                DefaultFile,
+                "%track_number%. %track_artist% - %title%",
+                "%track_artist% - %title%",
+                "%track_artist% - %track_number% - %title%",
+                "%track_artist% (%album%) - %track_number% - %title%",
+                "%title%"
+            };
+
+            AddConversion ("track_artist", Catalog.GetString ("Track Artist"),
+                delegate (TrackInfo t, object r) {
+                    return Escape (t == null ? (string)r : t.DisplayArtistName);
+            });
+
+            AddConversion ("album_artist", Catalog.GetString ("Album Artist"),
+                delegate (TrackInfo t, object r) {
+                    return Escape (t == null ? (string)r : t.DisplayAlbumArtistName);
+            });
+
+            // Alias for %album_artist%
+            AddConversion ("artist", Catalog.GetString ("Album Artist"),
+                delegate (TrackInfo t, object r) {
+                    return Escape (t == null ? (string)r : t.DisplayAlbumArtistName);
+            });
+
+            AddConversion ("album_artist_initial", Catalog.GetString("Album Artist Initial"),
+                delegate (TrackInfo t, object r) {
+                    return Escape (t == null ? (string)r : t.DisplayAlbumArtistName.Substring(0, 1));
+            });
+
+            AddConversion ("conductor", Catalog.GetString ("Conductor"),
+                delegate (TrackInfo t, object r) {
+                    return Escape (t == null ? (string)r : t.Conductor);
+            });
+
+            AddConversion ("composer", Catalog.GetString ("Composer"),
+                delegate (TrackInfo t, object r) {
+                    return Escape (t == null ? (string)r : t.Composer);
+            });
+
+            AddConversion ("genre", Catalog.GetString ("Genre"),
+                delegate (TrackInfo t, object r) {
+                    return Escape (t == null ? (string)r : t.DisplayGenre);
+            });
+
+            AddConversion ("album", Catalog.GetString ("Album"),
+                delegate (TrackInfo t, object r) {
+                    return Escape (t == null ? (string)r : t.DisplayAlbumTitle);
+            });
+
+            AddConversion ("title", Catalog.GetString ("Title"),
+                delegate (TrackInfo t, object r) {
+                    return Escape (t == null ? (string)r : t.DisplayTrackTitle);
+            });
+
+            AddConversion ("year", Catalog.GetString ("Year"),
+                delegate (TrackInfo t, object r) {
+                    int year = t == null ? (int)r : t.Year;
+                    return year > 0 ? String.Format ("{0}", year) : null;
+            });
+
+            AddConversion ("track_count", Catalog.GetString ("Count"),
+                delegate (TrackInfo t, object r) {
+                    int track_count = t == null ? (int)r : t.TrackCount;
+                    return track_count > 0 ? String.Format ("{0:00}", track_count) : null;
+            });
+
+            AddConversion ("track_number", Catalog.GetString ("Number"),
+                delegate (TrackInfo t, object r) {
+                    int track_number = t == null ? (int)r : t.TrackNumber;
+                    return track_number > 0 ? String.Format ("{0:00}", track_number) : null;
+            });
+
+            AddConversion ("track_count_nz", Catalog.GetString ("Count (unsorted)"),
+                delegate (TrackInfo t, object r) {
+                    int track_count = t == null ? (int)r : t.TrackCount;
+                    return track_count > 0 ? String.Format ("{0}", track_count) : null;
+            });
+
+            AddConversion ("track_number_nz", Catalog.GetString ("Number (unsorted)"),
+                delegate (TrackInfo t, object r) {
+                    int track_number = t == null ? (int)r : t.TrackNumber;
+                    return track_number > 0 ? String.Format ("{0}", track_number) : null;
+            });
+
+            AddConversion ("disc_count", Catalog.GetString ("Disc Count"),
+                delegate (TrackInfo t, object r) {
+                    int disc_count = t == null ? (int)r : t.DiscCount;
+                    return disc_count > 0 ? String.Format ("{0}", disc_count) : null;
+            });
+
+            AddConversion ("disc_number", Catalog.GetString ("Disc Number"),
+                delegate (TrackInfo t, object r) {
+                    int disc_number = t == null ? (int)r : t.DiscNumber;
+                    return disc_number > 0 ? String.Format ("{0}", disc_number) : null;
+            });
+
+            AddConversion ("grouping", Catalog.GetString ("Grouping"),
+                delegate (TrackInfo t, object r) {
+                    return Escape (t == null ? (string)r : t.Grouping);
+            });
+
+            AddConversion ("path_sep", Path.DirectorySeparatorChar.ToString (),
+                delegate (TrackInfo t, object r) {
+                    return Path.DirectorySeparatorChar.ToString ();
+            });
+        }
+    }
+}
diff --git a/src/Core/Banshee.Services/Banshee.Library/MusicLibrarySource.cs b/src/Core/Banshee.Services/Banshee.Library/MusicLibrarySource.cs
index 7eb8f3c..59f4f7f 100644
--- a/src/Core/Banshee.Services/Banshee.Library/MusicLibrarySource.cs
+++ b/src/Core/Banshee.Services/Banshee.Library/MusicLibrarySource.cs
@@ -32,6 +32,7 @@ using System.Collections.Generic;
 
 using Mono.Unix;
 
+using Banshee.Base;
 using Banshee.Collection;
 using Banshee.SmartPlaylist;
 using Banshee.Preferences;
@@ -42,6 +43,17 @@ namespace Banshee.Library
 {
     public class MusicLibrarySource : LibrarySource
     {
+        private static FileNamePattern music_filename_pattern = new MusicFileNamePattern ();
+        public static FileNamePattern MusicFileNamePattern {
+            get { return music_filename_pattern; }
+        }
+
+        static MusicLibrarySource ()
+        {
+            music_filename_pattern.FolderSchema = LibrarySchema.FolderPattern;
+            music_filename_pattern.FileSchema   = LibrarySchema.FilePattern;
+        }
+
         // Catalog.GetString ("Music Library")
         public MusicLibrarySource () : base (Catalog.GetString ("Music"), "Library", 40)
         {
@@ -58,14 +70,7 @@ namespace Banshee.Library
                 DatabaseConfigurationClient.Client.Set<int> ("MusicLibraryLocationMigrated", 1);
             }
 
-            Section file_system = PreferencesPage.Add (new Section ("file-system",
-                Catalog.GetString ("File System Organization"), 5));
-
-            file_system.Add (new SchemaPreference<string> (LibrarySchema.FolderPattern,
-                Catalog.GetString ("Folder hie_rarchy")));
-
-            file_system.Add (new SchemaPreference<string> (LibrarySchema.FilePattern,
-                Catalog.GetString ("File _name")));
+            SetFileNamePattern (MusicFileNamePattern);
 
             PreferencesPage.Add (new Section ("misc", Catalog.GetString ("Miscellaneous"), 10));
         }
diff --git a/src/Core/Banshee.Core/Banshee.Base/Tests/FileNamePatternTests.cs b/src/Core/Banshee.Services/Banshee.Library/Tests/FileNamePatternTests.cs
similarity index 96%
rename from src/Core/Banshee.Core/Banshee.Base/Tests/FileNamePatternTests.cs
rename to src/Core/Banshee.Services/Banshee.Library/Tests/FileNamePatternTests.cs
index 4227d7d..75a1a56 100644
--- a/src/Core/Banshee.Core/Banshee.Base/Tests/FileNamePatternTests.cs
+++ b/src/Core/Banshee.Services/Banshee.Library/Tests/FileNamePatternTests.cs
@@ -34,7 +34,7 @@ using NUnit.Framework;
 using Banshee.Base;
 using Banshee.Collection;
 
-namespace Banshee.Base.Tests
+namespace Banshee.Library.Tests
 {
     [TestFixture]
     public class FileNamePatternTest
@@ -45,6 +45,8 @@ namespace Banshee.Base.Tests
             return num < 10 ? "0" + str : str;
         }
 
+        public static FileNamePattern FileNamePattern { get { return MusicLibrarySource.MusicFileNamePattern; } }
+
         [Test]
         public void MakePathsRelative ()
         {
diff --git a/src/Core/Banshee.Services/Banshee.Metadata/SaveTrackMetadataJob.cs b/src/Core/Banshee.Services/Banshee.Metadata/SaveTrackMetadataJob.cs
index c999d69..bc00bca 100644
--- a/src/Core/Banshee.Services/Banshee.Metadata/SaveTrackMetadataJob.cs
+++ b/src/Core/Banshee.Services/Banshee.Metadata/SaveTrackMetadataJob.cs
@@ -115,7 +115,7 @@ namespace Banshee.Metadata
                 return false;
             }
 
-            string new_filename = FileNamePattern.BuildFull (source.BaseDirectory, track, System.IO.Path.GetExtension (old_uri.ToString ()));
+            string new_filename = track.FileNamePattern.BuildFull (source.BaseDirectory, track, System.IO.Path.GetExtension (old_uri.ToString ()));
             SafeUri new_uri = new SafeUri (new_filename);
 
             if (!new_uri.Equals (old_uri) && !Banshee.IO.File.Exists (new_uri)) {
diff --git a/src/Core/Banshee.Core/Banshee.Base/Tests/TaglibReadWriteTests.cs b/src/Core/Banshee.Services/Banshee.Metadata/Tests/TaglibReadWriteTests.cs
similarity index 99%
rename from src/Core/Banshee.Core/Banshee.Base/Tests/TaglibReadWriteTests.cs
rename to src/Core/Banshee.Services/Banshee.Metadata/Tests/TaglibReadWriteTests.cs
index 8a94b8b..f10d812 100644
--- a/src/Core/Banshee.Core/Banshee.Base/Tests/TaglibReadWriteTests.cs
+++ b/src/Core/Banshee.Services/Banshee.Metadata/Tests/TaglibReadWriteTests.cs
@@ -40,7 +40,7 @@ using Banshee.Configuration.Schema;
 
 using Hyena.Tests;
 
-namespace Banshee.Base.Tests
+namespace Banshee.Metadata
 {
     // FIXME: These tests don't really belong here
 
diff --git a/src/Core/Banshee.Services/Banshee.Sources/PrimarySource.cs b/src/Core/Banshee.Services/Banshee.Sources/PrimarySource.cs
index bdab989..6082aef 100644
--- a/src/Core/Banshee.Services/Banshee.Sources/PrimarySource.cs
+++ b/src/Core/Banshee.Services/Banshee.Sources/PrimarySource.cs
@@ -38,6 +38,7 @@ using Hyena.Data.Sqlite;
 using Hyena.Collections;
 
 using Banshee.Base;
+using Banshee.Preferences;
 using Banshee.ServiceStack;
 using Banshee.Configuration;
 using Banshee.Sources;
@@ -238,6 +239,17 @@ namespace Banshee.Sources
             set { ExpandedSchema.Set (value); }
         }
 
+        public FileNamePattern FileNamePattern { get; private set; }
+
+        protected void SetFileNamePattern (FileNamePattern pattern)
+        {
+            FileNamePattern = pattern;
+
+            var file_system = PreferencesPage.Add (new Section ("file-system", Catalog.GetString ("File Organization"), 5));
+            file_system.Add (new SchemaPreference<string> (pattern.FolderSchema, Catalog.GetString ("Folder hie_rarchy")));
+            file_system.Add (new SchemaPreference<string> (pattern.FileSchema, Catalog.GetString ("File _name")));
+        }
+
         public virtual void Dispose ()
         {
             if (Application.ShuttingDown)
diff --git a/src/Core/Banshee.Services/Makefile.am b/src/Core/Banshee.Services/Makefile.am
index 57664de..d6635af 100644
--- a/src/Core/Banshee.Services/Makefile.am
+++ b/src/Core/Banshee.Services/Makefile.am
@@ -80,10 +80,13 @@ SOURCES =  \
 	Banshee.Library/IImportSource.cs \
 	Banshee.Library/ImportSourceManager.cs \
 	Banshee.Library/LibraryImportManager.cs \
+	Banshee.Library/LibrarySchema.cs \
 	Banshee.Library/LibrarySource.cs \
+	Banshee.Library/MusicFileNamePattern.cs \
 	Banshee.Library/MusicLibrarySource.cs \
 	Banshee.Library/ThreadPoolImportSource.cs \
 	Banshee.Library/VideoLibrarySource.cs \
+	Banshee.Library/Tests/FileNamePatternTests.cs \
 	Banshee.MediaEngine/IAudioCdRipper.cs \
 	Banshee.MediaEngine/IBpmDetector.cs \
 	Banshee.MediaEngine/IEqualizer.cs \
@@ -119,6 +122,7 @@ SOURCES =  \
 	Banshee.Metadata/MetadataServiceJob.cs \
 	Banshee.Metadata/SaveTrackMetadataJob.cs \
 	Banshee.Metadata/SaveTrackMetadataService.cs \
+	Banshee.Metadata/Tests/TaglibReadWriteTests.cs \
 	Banshee.Networking/INetworkAvailabilityService.cs \
 	Banshee.Networking/Network.cs \
 	Banshee.Networking/NetworkManager.cs \
diff --git a/src/Core/Banshee.ThickClient/Banshee.Preferences.Gui/DefaultPreferenceWidgets.cs b/src/Core/Banshee.ThickClient/Banshee.Preferences.Gui/DefaultPreferenceWidgets.cs
index 08e4d7f..399b7c7 100644
--- a/src/Core/Banshee.ThickClient/Banshee.Preferences.Gui/DefaultPreferenceWidgets.cs
+++ b/src/Core/Banshee.ThickClient/Banshee.Preferences.Gui/DefaultPreferenceWidgets.cs
@@ -30,11 +30,11 @@ using System;
 using Mono.Unix;
 using Gtk;
 
-
 using Hyena;
 using Hyena.Widgets;
 
 using Banshee.Base;
+using Banshee.Sources;
 using Banshee.Library;
 using Banshee.Preferences;
 using Banshee.Collection;
@@ -49,20 +49,23 @@ namespace Banshee.Preferences.Gui
     {
         public static void Load (PreferenceService service)
         {
-            Page music = ServiceManager.SourceManager.MusicLibrary.PreferencesPage;
+            foreach (var library in ServiceManager.SourceManager.FindSources<LibrarySource> ()) {
+                new LibraryLocationButton (library);
 
-            foreach (LibrarySource source in ServiceManager.SourceManager.FindSources<LibrarySource> ()) {
-                new LibraryLocationButton (source);
-            }
+                if (library.FileNamePattern != null) {
+                    var library_page = library.PreferencesPage;
+                    var pattern = library.FileNamePattern;
 
-            PreferenceBase folder_pattern = music["file-system"]["folder_pattern"];
-            folder_pattern.DisplayWidget = new PatternComboBox (folder_pattern, FileNamePattern.SuggestedFolders);
+                    var folder_pattern = library_page["file-system"][pattern.FolderSchema.Key];
+                    folder_pattern.DisplayWidget = new PatternComboBox (library, folder_pattern, pattern.SuggestedFolders);
 
-            PreferenceBase file_pattern = music["file-system"]["file_pattern"];
-            file_pattern.DisplayWidget = new PatternComboBox (file_pattern, FileNamePattern.SuggestedFiles);
+                    var file_pattern = library_page["file-system"][pattern.FileSchema.Key];
+                    file_pattern.DisplayWidget = new PatternComboBox (library, file_pattern, pattern.SuggestedFiles);
 
-            PreferenceBase pattern_display = music["file-system"].FindOrAdd (new VoidPreference ("file_folder_pattern"));
-            pattern_display.DisplayWidget = new PatternDisplay (folder_pattern.DisplayWidget, file_pattern.DisplayWidget);
+                    var pattern_display = library_page["file-system"].FindOrAdd (new VoidPreference ("file_folder_pattern"));
+                    pattern_display.DisplayWidget = new PatternDisplay (library, folder_pattern.DisplayWidget, file_pattern.DisplayWidget);
+                }
+            }
 
             service["extensions"].DisplayWidget = new Banshee.Addins.Gui.AddinView ();
         }
@@ -165,7 +168,7 @@ namespace Banshee.Preferences.Gui
         {
             private Preference<string> preference;
 
-            public PatternComboBox (PreferenceBase pref, string [] patterns)
+            public PatternComboBox (PrimarySource source, PreferenceBase pref, string [] patterns)
             {
                 preference = (Preference<string>)pref;
 
@@ -177,11 +180,11 @@ namespace Banshee.Preferences.Gui
                         already_added = true;
                     }
 
-                    Add (FileNamePattern.CreatePatternDescription (pattern), pattern);
+                    Add (source.FileNamePattern.CreatePatternDescription (pattern), pattern);
                 }
 
                 if (!already_added) {
-                    Add (FileNamePattern.CreatePatternDescription (conf_pattern), conf_pattern);
+                    Add (source.FileNamePattern.CreatePatternDescription (conf_pattern), conf_pattern);
                 }
 
                 ActiveValue = conf_pattern;
@@ -198,11 +201,11 @@ namespace Banshee.Preferences.Gui
         {
             private PatternComboBox folder;
             private PatternComboBox file;
+            private PrimarySource source;
 
-            private SampleTrackInfo track = new SampleTrackInfo ();
-
-            public PatternDisplay (object a, object b)
+            public PatternDisplay (PrimarySource source, object a, object b)
             {
+                this.source = source;
                 folder= (PatternComboBox)a;
                 file = (PatternComboBox)b;
 
@@ -214,11 +217,17 @@ namespace Banshee.Preferences.Gui
 
             private void OnChanged (object o, EventArgs args)
             {
-                string display = FileNamePattern.CreateFromTrackInfo (FileNamePattern.CreateFolderFilePattern (
-                    folder.ActiveValue, file.ActiveValue), track);
+                var pattern = source.FileNamePattern.CreateFolderFilePattern (folder.ActiveValue, file.ActiveValue);
+
+                var sb = new System.Text.StringBuilder ();
+                foreach (var track in source.FileNamePattern.SampleTracks) {
+                    string display = source.FileNamePattern.CreateFromTrackInfo (pattern, track);
+                    if (!String.IsNullOrEmpty (display)) {
+                        sb.AppendFormat ("<small>{0}.ogg</small>", GLib.Markup.EscapeText (display));
+                    }
+                }
 
-                Markup = String.IsNullOrEmpty (display) ? String.Empty : String.Format ("<small>{0}.ogg</small>",
-                    GLib.Markup.EscapeText (display));
+                Markup = sb.ToString ();
             }
         }
     }
diff --git a/src/Dap/Banshee.Dap.MassStorage/Banshee.Dap.MassStorage/AndroidDevice.cs b/src/Dap/Banshee.Dap.MassStorage/Banshee.Dap.MassStorage/AndroidDevice.cs
index 23bf5cf..3d92eb9 100644
--- a/src/Dap/Banshee.Dap.MassStorage/Banshee.Dap.MassStorage/AndroidDevice.cs
+++ b/src/Dap/Banshee.Dap.MassStorage/Banshee.Dap.MassStorage/AndroidDevice.cs
@@ -140,7 +140,7 @@ namespace Banshee.Dap.MassStorage
 
         public override bool GetTrackPath (TrackInfo track, out string path)
         {
-            path = FileNamePattern.CreateFromTrackInfo (
+            path = MusicLibrarySource.MusicFileNamePattern.CreateFromTrackInfo (
                 "%artist%%path_sep%%album%%path_sep%%track_number%. %title%",
                 track);
             return true;
diff --git a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdRipper.cs b/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdRipper.cs
index 9d822ab..6a88d97 100644
--- a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdRipper.cs
+++ b/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdRipper.cs
@@ -35,6 +35,7 @@ using Mono.Addins;
 using Hyena.Jobs;
 
 using Banshee.Base;
+using Banshee.Library;
 using Banshee.ServiceStack;
 using Banshee.Streaming;
 using Banshee.Collection;
@@ -186,7 +187,8 @@ namespace Banshee.AudioCd
             status = String.Format("{0} - {1}", track.ArtistName, track.TrackTitle);
             user_job.Status = status;
 
-            SafeUri uri = new SafeUri (FileNamePattern.BuildFull (ServiceManager.SourceManager.MusicLibrary.BaseDirectory, track, null));
+            SafeUri uri = new SafeUri (MusicLibrarySource.MusicFileNamePattern.BuildFull (
+                ServiceManager.SourceManager.MusicLibrary.BaseDirectory, track, null));
             bool tagging_supported;
             ripper.RipTrack (track.IndexOnDisc, track, uri, out tagging_supported);
         }
diff --git a/src/Extensions/Banshee.Audiobook/Banshee.Audiobook/AudiobookFileNamePattern.cs b/src/Extensions/Banshee.Audiobook/Banshee.Audiobook/AudiobookFileNamePattern.cs
new file mode 100644
index 0000000..6fc5dfd
--- /dev/null
+++ b/src/Extensions/Banshee.Audiobook/Banshee.Audiobook/AudiobookFileNamePattern.cs
@@ -0,0 +1,148 @@
+//
+// AudiobookFileNamePattern.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.Text.RegularExpressions;
+using System.Collections.Generic;
+using System.IO;
+using Mono.Unix;
+
+using Banshee.Base;
+using Banshee.Collection;
+using Banshee.Configuration.Schema;
+
+namespace Banshee.Audiobook
+{
+    public class AudiobookFileNamePattern : FileNamePattern
+    {
+        public AudiobookFileNamePattern ()
+        {
+            SuggestedFolders = new string [] {
+                "%author% - %book_title%",
+                "%author%%path_sep%%book_title%"
+            };
+            DefaultFolder = SuggestedFolders [0];
+
+            SuggestedFiles = new string [] {
+                "%title%",
+                "{%disc_number% - }{%track_number% - }%title%"
+            };
+            DefaultFile = SuggestedFiles [0];
+
+            AddConversion ("author", Catalog.GetString ("Author"),
+                delegate (TrackInfo t, object r) {
+                    return Escape (t == null ? (string)r : t.DisplayAlbumArtistName);
+            });
+
+            AddConversion ("author_initial", Catalog.GetString("Author Initial"),
+                delegate (TrackInfo t, object r) {
+                    return Escape (t == null ? (string)r : t.DisplayAlbumArtistName.Substring (0, 1));
+            });
+
+            AddConversion ("book_title", Catalog.GetString ("Book Title"),
+                delegate (TrackInfo t, object r) {
+                    return Escape (t == null ? (string)r : t.DisplayAlbumTitle);
+            });
+
+            AddConversion ("title", Catalog.GetString ("Title"),
+                delegate (TrackInfo t, object r) {
+                    return Escape (t == null ? (string)r : t.DisplayTrackTitle);
+            });
+
+            AddConversion ("year", Catalog.GetString ("Year"),
+                delegate (TrackInfo t, object r) {
+                    int year = t == null ? (int)r : t.Year;
+                    return year > 0 ? String.Format ("{0}", year) : null;
+            });
+
+            AddConversion ("track_count", Catalog.GetString ("Count"),
+                delegate (TrackInfo t, object r) {
+                    int track_count = t == null ? (int)r : t.TrackCount;
+                    return track_count > 0 ? String.Format ("{0:00}", track_count) : null;
+            });
+
+            AddConversion ("track_number", Catalog.GetString ("Number"),
+                delegate (TrackInfo t, object r) {
+                    int track_number = t == null ? (int)r : t.TrackNumber;
+                    return track_number > 0 ? String.Format ("{0:00}", track_number) : null;
+            });
+
+            AddConversion ("track_count_nz", Catalog.GetString ("Count (unsorted)"),
+                delegate (TrackInfo t, object r) {
+                    int track_count = t == null ? (int)r : t.TrackCount;
+                    return track_count > 0 ? String.Format ("{0}", track_count) : null;
+            });
+
+            AddConversion ("track_number_nz", Catalog.GetString ("Number (unsorted)"),
+                delegate (TrackInfo t, object r) {
+                    int track_number = t == null ? (int)r : t.TrackNumber;
+                    return track_number > 0 ? String.Format ("{0}", track_number) : null;
+            });
+
+            AddConversion ("disc_count", Catalog.GetString ("Disc Count"),
+                delegate (TrackInfo t, object r) {
+                    int disc_count = t == null ? (int)r : t.DiscCount;
+                    return disc_count > 0 ? String.Format ("{0}", disc_count) : null;
+            });
+
+            AddConversion ("disc_number", Catalog.GetString ("Disc Number"),
+                delegate (TrackInfo t, object r) {
+                    int disc_number = t == null ? (int)r : t.DiscNumber;
+                    return disc_number > 0 ? String.Format ("{0}", disc_number) : null;
+            });
+
+            AddConversion ("grouping", Catalog.GetString ("Grouping"),
+                delegate (TrackInfo t, object r) {
+                    return Escape (t == null ? (string)r : t.Grouping);
+            });
+
+            AddConversion ("path_sep", Path.DirectorySeparatorChar.ToString (),
+                delegate (TrackInfo t, object r) {
+                    return Path.DirectorySeparatorChar.ToString ();
+            });
+        }
+
+        public override IEnumerable<TrackInfo> SampleTracks {
+            get {
+                var track = new TrackInfo () {
+                    ArtistName = "J.K. Rowling",
+                    AlbumTitle = "Harry Potter and the Goblet of Fire",
+                    TrackTitle = "Chapter 20-09 - The First Task",
+                    TrackNumber = 4,
+                    TrackCount = 16,
+                    DiscNumber = 9,
+                    DiscCount = 17,
+                    Duration = TimeSpan.FromSeconds (312),
+                    Year = 2000
+                };
+
+                yield return track;
+            }
+        }
+    }
+}
diff --git a/src/Extensions/Banshee.Audiobook/Banshee.Audiobook/AudiobookLibrarySource.cs b/src/Extensions/Banshee.Audiobook/Banshee.Audiobook/AudiobookLibrarySource.cs
index 6719514..bb11b30 100644
--- a/src/Extensions/Banshee.Audiobook/Banshee.Audiobook/AudiobookLibrarySource.cs
+++ b/src/Extensions/Banshee.Audiobook/Banshee.Audiobook/AudiobookLibrarySource.cs
@@ -63,6 +63,12 @@ namespace Banshee.Audiobook
                   </column>
                 </column-controller>
             ", Catalog.GetString ("Author")));
+
+            var pattern = new AudiobookFileNamePattern ();
+            pattern.FolderSchema = CreateSchema<string> ("folder_pattern", pattern.DefaultFolder, "", "");
+            pattern.FileSchema   = CreateSchema<string> ("file_pattern",   pattern.DefaultFile, "", "");
+
+            SetFileNamePattern (pattern);
         }
 
         public override string DefaultBaseDirectory {
diff --git a/src/Extensions/Banshee.Audiobook/Makefile.am b/src/Extensions/Banshee.Audiobook/Makefile.am
index b6fa398..cb4e30a 100644
--- a/src/Extensions/Banshee.Audiobook/Makefile.am
+++ b/src/Extensions/Banshee.Audiobook/Makefile.am
@@ -4,7 +4,8 @@ LINK = $(REF_EXTENSION_AUDIOBOOK)
 INSTALL_DIR = $(EXTENSIONS_INSTALL_DIR)
 
 SOURCES =  \
-	Banshee.Audiobook/AudiobookLibrarySource.cs
+	Banshee.Audiobook/AudiobookLibrarySource.cs \
+	Banshee.Audiobook/AudiobookFileNamePattern.cs
 
 RESOURCES =  \
 	Banshee.Audiobook.addin.xml



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