=== modified file 'src/Core/Banshee.Core/Banshee.Collection/AlbumInfo.cs'
--- src/Core/Banshee.Core/Banshee.Collection/AlbumInfo.cs	2009-02-11 01:24:04 +0000
+++ src/Core/Banshee.Core/Banshee.Collection/AlbumInfo.cs	2009-02-12 03:45:42 +0000
@@ -63,6 +63,14 @@
             set { artist_name_sort = value; }
         }
         
+        public virtual byte[] ArtistNameSortKey {
+            get { return Hyena.StringUtil.SortKey (ArtistNameSort ?? ArtistName); }
+        }
+        
+        public virtual string ArtistNameLowered {
+            get { return Hyena.StringUtil.SearchKey (ArtistName); }
+        }
+        
         public virtual string Title {
             get { return title; }
             set { title = value; }
@@ -73,6 +81,14 @@
             set { title_sort = value; }
         }
         
+        public virtual byte[] TitleSortKey {
+            get { return Hyena.StringUtil.SortKey (TitleSort ?? Title); }
+        }
+        
+        public virtual string TitleLowered {
+            get { return Hyena.StringUtil.SearchKey (Title); }
+        }
+        
         public virtual bool IsCompilation {
             get { return is_compilation; }
             set { is_compilation = value; }

=== modified file 'src/Core/Banshee.Core/Banshee.Collection/ArtistInfo.cs'
--- src/Core/Banshee.Core/Banshee.Collection/ArtistInfo.cs	2009-02-11 01:24:04 +0000
+++ src/Core/Banshee.Core/Banshee.Collection/ArtistInfo.cs	2009-02-12 03:58:04 +0000
@@ -58,5 +58,13 @@
             get { return name_sort; }
             set { name_sort = value; }
         }
+        
+        public virtual byte[] NameSortKey {
+            get { return Hyena.StringUtil.SortKey (NameSort ?? Name); }
+        }
+
+        public virtual string NameLowered {
+            get { return Hyena.StringUtil.SearchKey (Name); }
+        }
     }
 }

=== added directory 'src/Core/Banshee.Core/Banshee.Collection/Tests'
=== added file 'src/Core/Banshee.Core/Banshee.Collection/Tests/AlbumInfoTests.cs'
--- src/Core/Banshee.Core/Banshee.Collection/Tests/AlbumInfoTests.cs	1970-01-01 00:00:00 +0000
+++ src/Core/Banshee.Core/Banshee.Collection/Tests/AlbumInfoTests.cs	2009-02-12 04:07:42 +0000
@@ -0,0 +1,117 @@
+//
+// AlbumInfoTests.cs
+//
+// Author:
+//   John Millikin <jmillikin@gmail.com>
+//
+// 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.
+//
+
+#if ENABLE_TESTS
+
+using NUnit.Framework;
+using Banshee.Collection;
+
+namespace Banshee.Collection.Tests
+{
+    [TestFixture]
+    public class AlbumInfoTests
+    {
+        protected void AssertTitleSort (string title, string title_sort, byte[] expected)
+        {
+            AlbumInfo info = new AlbumInfo ();
+            info.Title = title;
+            info.TitleSort = title_sort;
+            Assert.AreEqual (expected, info.TitleSortKey);
+        }
+        
+        protected void AssertTitleLowered (string title, string expected)
+        {
+            AlbumInfo info = new AlbumInfo ();
+            info.Title = title;
+            Assert.AreEqual (expected, info.TitleLowered);
+        }
+        
+        protected void AssertArtistNameSort (string name, string name_sort, byte[] expected)
+        {
+            AlbumInfo info = new AlbumInfo ();
+            info.ArtistName = name;
+            info.ArtistNameSort = name_sort;
+            Assert.AreEqual (expected, info.ArtistNameSortKey);
+        }
+        
+        protected void AssertArtistNameLowered (string name, string expected)
+        {
+            AlbumInfo info = new AlbumInfo ();
+            info.ArtistName = name;
+            Assert.AreEqual (expected, info.ArtistNameLowered);
+        }
+        
+        [Test]
+        public void TestWithoutTitleSortKey ()
+        {
+            AssertTitleSort ("", null, new byte[] {1, 1, 1, 1, 0});
+            AssertTitleSort ("a", null, new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertTitleSort ("A", null, new byte[] {14, 2, 1, 1, 1, 1, 0});
+        }
+        
+        [Test]
+        public void TestTitleSortKey ()
+        {
+            AssertTitleSort ("Title", "", new byte[] {1, 1, 1, 1, 0});
+            AssertTitleSort ("Title", "a", new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertTitleSort ("Title", "A", new byte[] {14, 2, 1, 1, 1, 1, 0});
+        }
+        
+        [Test]
+        public void TestTitleLowered ()
+        {
+            AssertTitleLowered ("", "");
+            AssertTitleLowered ("A", "a");
+            AssertTitleLowered ("\u0104", "a");
+        }
+        
+        [Test]
+        public void TestWithoutArtistNameSortKey ()
+        {
+            AssertArtistNameSort ("", null, new byte[] {1, 1, 1, 1, 0});
+            AssertArtistNameSort ("a", null, new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertArtistNameSort ("A", null, new byte[] {14, 2, 1, 1, 1, 1, 0});
+        }
+        
+        [Test]
+        public void TestArtistNameSortKey ()
+        {
+            AssertArtistNameSort ("Title", "", new byte[] {1, 1, 1, 1, 0});
+            AssertArtistNameSort ("Title", "a", new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertArtistNameSort ("Title", "A", new byte[] {14, 2, 1, 1, 1, 1, 0});
+        }
+        
+        [Test]
+        public void TestArtistNameLowered ()
+        {
+            AssertArtistNameLowered ("", "");
+            AssertArtistNameLowered ("A", "a");
+            AssertArtistNameLowered ("\u0104", "a");
+        }
+    }
+}
+
+#endif

=== added file 'src/Core/Banshee.Core/Banshee.Collection/Tests/ArtistInfoTests.cs'
--- src/Core/Banshee.Core/Banshee.Collection/Tests/ArtistInfoTests.cs	1970-01-01 00:00:00 +0000
+++ src/Core/Banshee.Core/Banshee.Collection/Tests/ArtistInfoTests.cs	2009-02-12 04:07:50 +0000
@@ -0,0 +1,79 @@
+//
+// ArtistInfoTests.cs
+//
+// Author:
+//   John Millikin <jmillikin@gmail.com>
+//
+// 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.
+//
+
+#if ENABLE_TESTS
+
+using NUnit.Framework;
+using Banshee.Collection;
+
+namespace Banshee.Collection.Tests
+{
+    [TestFixture]
+    public class ArtistInfoTests
+    {
+        protected void AssertNameSort (string name, string name_sort, byte[] expected)
+        {
+            ArtistInfo info = new ArtistInfo ();
+            info.Name = name;
+            info.NameSort = name_sort;
+            Assert.AreEqual (expected, info.NameSortKey);
+        }
+        
+        protected void AssertNameLowered (string name, string expected)
+        {
+            ArtistInfo info = new ArtistInfo ();
+            info.Name = name;
+            Assert.AreEqual (expected, info.NameLowered);
+        }
+        
+        [Test]
+        public void TestWithoutNameSortKey ()
+        {
+            AssertNameSort ("", null, new byte[] {1, 1, 1, 1, 0});
+            AssertNameSort ("a", null, new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertNameSort ("A", null, new byte[] {14, 2, 1, 1, 1, 1, 0});
+        }
+        
+        [Test]
+        public void TestNameSortKey ()
+        {
+            AssertNameSort ("Title", "", new byte[] {1, 1, 1, 1, 0});
+            AssertNameSort ("Title", "a", new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertNameSort ("Title", "A", new byte[] {14, 2, 1, 1, 1, 1, 0});
+        }
+        
+        [Test]
+        public void TestNameLowered ()
+        {
+            AssertNameLowered ("", "");
+            AssertNameLowered ("A", "a");
+            AssertNameLowered ("\u0104", "a");
+        }
+    }
+}
+
+#endif
+

=== added file 'src/Core/Banshee.Core/Banshee.Collection/Tests/TrackInfoTests.cs'
--- src/Core/Banshee.Core/Banshee.Collection/Tests/TrackInfoTests.cs	1970-01-01 00:00:00 +0000
+++ src/Core/Banshee.Core/Banshee.Collection/Tests/TrackInfoTests.cs	2009-02-12 04:08:43 +0000
@@ -0,0 +1,78 @@
+//
+// TrackInfoTests.cs
+//
+// Author:
+//   John Millikin <jmillikin@gmail.com>
+//
+// 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.
+//
+
+#if ENABLE_TESTS
+
+using NUnit.Framework;
+using Banshee.Collection;
+
+namespace Banshee.Collection.Tests
+{
+    [TestFixture]
+    public class TrackInfoTests
+    {
+        protected void AssertTitleSort (string title, string title_sort, byte[] expected)
+        {
+            TrackInfo info = new TrackInfo ();
+            info.TrackTitle = title;
+            info.TrackTitleSort = title_sort;
+            Assert.AreEqual (expected, info.TrackTitleSortKey);
+        }
+        
+        protected void AssertTitleLowered (string title, string expected)
+        {
+            TrackInfo info = new TrackInfo ();
+            info.TrackTitle = title;
+            Assert.AreEqual (expected, info.TitleLowered);
+        }
+        
+        [Test]
+        public void TestWithoutTitleSortKey ()
+        {
+            AssertTitleSort ("", null, new byte[] {1, 1, 1, 1, 0});
+            AssertTitleSort ("a", null, new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertTitleSort ("A", null, new byte[] {14, 2, 1, 1, 1, 1, 0});
+        }
+        
+        [Test]
+        public void TestTitleSortKey ()
+        {
+            AssertTitleSort ("Title", "", new byte[] {1, 1, 1, 1, 0});
+            AssertTitleSort ("Title", "a", new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertTitleSort ("Title", "A", new byte[] {14, 2, 1, 1, 1, 1, 0});
+        }
+        
+        [Test]
+        public void TestTitleLowered ()
+        {
+            AssertTitleLowered ("", "");
+            AssertTitleLowered ("A", "a");
+            AssertTitleLowered ("\u0104", "a");
+        }
+    }
+}
+
+#endif

=== modified file 'src/Core/Banshee.Core/Banshee.Collection/TrackInfo.cs'
--- src/Core/Banshee.Core/Banshee.Collection/TrackInfo.cs	2009-02-11 01:24:04 +0000
+++ src/Core/Banshee.Core/Banshee.Collection/TrackInfo.cs	2009-02-12 04:05:44 +0000
@@ -256,6 +256,14 @@
             set { track_title_sort = value; }
         }
         
+        public virtual byte[] TrackTitleSortKey {
+            get { return Hyena.StringUtil.SortKey (TrackTitleSort ?? TrackTitle); }
+        }
+        
+        public virtual string TitleLowered {
+            get { return Hyena.StringUtil.SearchKey (TrackTitle); }
+        }
+
         [Exportable]
         public virtual string MusicBrainzId {
             get { return musicbrainz_id; }

=== modified file 'src/Core/Banshee.Core/Makefile.am'
--- src/Core/Banshee.Core/Makefile.am	2008-10-21 20:03:00 +0000
+++ src/Core/Banshee.Core/Makefile.am	2009-02-12 04:08:19 +0000
@@ -28,6 +28,9 @@
 	Banshee.Collection/TrackInfo.cs \
 	Banshee.Collection/TrackMediaAttributes.cs \
 	Banshee.Collection/UnknownTrackInfo.cs \
+	Banshee.Collection/Tests/AlbumInfoTests.cs \
+	Banshee.Collection/Tests/ArtistInfoTests.cs \
+	Banshee.Collection/Tests/TrackInfoTests.cs \
 	Banshee.Configuration.Schema/ImportSchema.cs \
 	Banshee.Configuration.Schema/LibrarySchema.cs \
 	Banshee.Configuration/ConfigurationClient.cs \
@@ -74,4 +77,4 @@
 include $(top_srcdir)/build/build.mk
 
 EXTRA_DIST += Banshee.Core.dll.config
-module_SCRIPTS += Banshee.Core.dll.config
\ No newline at end of file
+module_SCRIPTS += Banshee.Core.dll.config

=== modified file 'src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumInfo.cs'
--- src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumInfo.cs	2009-02-11 02:34:53 +0000
+++ src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumInfo.cs	2009-02-12 03:45:25 +0000
@@ -200,6 +200,16 @@
             set { base.TitleSort = value; }
         }
         
+        [DatabaseColumn(Select = false)]
+        public override byte[] TitleSortKey {
+            get { return base.TitleSortKey; }
+        }
+
+        [DatabaseColumn(Select = false)]
+        public override string TitleLowered {
+            get { return base.TitleLowered; }
+        }
+
         [DatabaseColumn]
         public override string ArtistName {
             get { return base.ArtistName; }
@@ -213,13 +223,13 @@
         }
 
         [DatabaseColumn(Select = false)]
-        protected string TitleLowered {
-            get { return Hyena.StringUtil.SearchKey (Title); }
+        public override byte[] ArtistNameSortKey {
+            get { return base.ArtistNameSortKey; }
         }
-
+        
         [DatabaseColumn(Select = false)]
-        protected string ArtistNameLowered {
-            get { return Hyena.StringUtil.SearchKey (ArtistName); }
+        public override string ArtistNameLowered {
+            get { return base.ArtistNameLowered; }
         }
 
         public override string ToString ()

=== modified file 'src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumListModel.cs'
--- src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumListModel.cs	2008-09-03 14:28:58 +0000
+++ src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumListModel.cs	2009-02-11 21:42:51 +0000
@@ -51,7 +51,7 @@
                         (SELECT CoreTracks.AlbumID FROM CoreTracks, CoreCache{0}
                             WHERE CoreCache.ModelID = {1} AND
                                   CoreCache.ItemId = {2} {3})
-                    ORDER BY CoreAlbums.TitleLowered, CoreAlbums.ArtistNameLowered";
+                    ORDER BY CoreAlbums.TitleSortKey, CoreAlbums.ArtistNameSortKey";
         }
         
         public override string FilterColumn {

=== modified file 'src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistInfo.cs'
--- src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistInfo.cs	2009-02-11 02:34:53 +0000
+++ src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistInfo.cs	2009-02-12 03:58:16 +0000
@@ -134,8 +134,8 @@
         }
 
         [DatabaseColumn(Select = false)]
-        protected string NameLowered {
-            get { return Hyena.StringUtil.SearchKey (Name); }
+        public override string NameLowered {
+            get { return base.NameLowered; }
         }
 
         [DatabaseColumn]
@@ -144,6 +144,11 @@
             set { base.NameSort = value; }
         }
 
+        [DatabaseColumn(Select = false)]
+        public override byte[] NameSortKey {
+            get { return base.NameSortKey; }
+        }
+        
         [DatabaseColumn("MusicBrainzID")]
         public override string MusicBrainzId {
             get { return base.MusicBrainzId; }

=== modified file 'src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistListModel.cs'
--- src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistListModel.cs	2009-02-11 01:24:04 +0000
+++ src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistListModel.cs	2009-02-11 21:42:51 +0000
@@ -50,7 +50,7 @@
                     (SELECT CoreTracks.ArtistID FROM CoreTracks, CoreCache{0}
                         WHERE CoreCache.ModelID = {1} AND
                               CoreCache.ItemID = {2} {3})
-                    ORDER BY NameLowered";
+                    ORDER BY NameSortKey";
         }
         
         public override string FilterColumn {

=== modified file 'src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs'
--- src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs	2009-02-11 02:34:53 +0000
+++ src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs	2009-02-12 04:09:18 +0000
@@ -450,15 +450,20 @@
             set { base.TrackTitle = value; }
         }
         
-        [DatabaseColumn]
+        [DatabaseColumn ("TitleSort")]
         public override string TrackTitleSort {
             get { return base.TrackTitleSort; }
             set { base.TrackTitleSort = value; }
         }
         
+        [DatabaseColumn("TitleSortKey", Select = false)]
+        public override byte[] TrackTitleSortKey {
+            get { return base.TrackTitleSortKey; }
+        }
+        
         [DatabaseColumn(Select = false)]
-        protected string TitleLowered {
-            get { return Hyena.StringUtil.SearchKey (TrackTitle); }
+        public override string TitleLowered {
+            get { return base.TitleLowered; }
         }
 
         [DatabaseColumn(Select = false)]

=== modified file 'src/Core/Banshee.Services/Banshee.Database/BansheeDbConnection.cs'
--- src/Core/Banshee.Services/Banshee.Database/BansheeDbConnection.cs	2009-01-26 04:24:37 +0000
+++ src/Core/Banshee.Services/Banshee.Database/BansheeDbConnection.cs	2009-02-11 21:43:00 +0000
@@ -71,6 +71,9 @@
             lock (this) {
                 migrator.Migrate ();
                 migrator = null;
+
+                // Update cached sorting keys
+                SortKeyUpdater.Update ();
             }
         }
 

=== modified file 'src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs'
--- src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs	2009-02-11 02:34:53 +0000
+++ src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs	2009-02-11 21:42:57 +0000
@@ -52,7 +52,7 @@
         // NOTE: Whenever there is a change in ANY of the database schema,
         //       this version MUST be incremented and a migration method
         //       MUST be supplied to match the new version number
-        protected const int CURRENT_VERSION = 24;
+        protected const int CURRENT_VERSION = 25;
         protected const int CURRENT_METADATA_VERSION = 5;
         
 #region Migration Driver
@@ -578,6 +578,20 @@
         }
 #endregion
 
+#region Version 25
+
+        [DatabaseVersion (25)]
+        private bool Migrate_25 ()
+        {
+            Execute ("ALTER TABLE CoreArtists ADD COLUMN NameSortKey BLOB");
+            Execute ("ALTER TABLE CoreAlbums ADD COLUMN ArtistNameSortKey BLOB");
+            Execute ("ALTER TABLE CoreAlbums ADD COLUMN TitleSortKey BLOB");
+            Execute ("ALTER TABLE CoreTracks ADD COLUMN TitleSortKey BLOB");
+            return true;
+        }
+        
+#endregion
+
 #pragma warning restore 0169
         
 #region Fresh database setup
@@ -643,6 +657,7 @@
                     Title               TEXT,
                     TitleLowered        TEXT,
                     TitleSort           TEXT,
+                    TitleSortKey        BLOB,
                     TrackNumber         INTEGER,
                     TrackCount          INTEGER,
                     Disc                INTEGER,
@@ -687,6 +702,7 @@
                     Title               TEXT,
                     TitleLowered        TEXT,
                     TitleSort           TEXT,
+                    TitleSortKey        BLOB,
 
                     ReleaseDate         INTEGER,
                     Duration            INTEGER,
@@ -696,6 +712,7 @@
                     ArtistName          TEXT,
                     ArtistNameLowered   TEXT,
                     ArtistNameSort      TEXT,
+                    ArtistNameSortKey   BLOB,
                     
                     Rating              INTEGER
                 )
@@ -711,6 +728,7 @@
                     Name                TEXT,
                     NameLowered         TEXT,
                     NameSort            TEXT,
+                    NameSortKey         BLOB,
                     Rating              INTEGER
                 )
             ");

=== added file 'src/Core/Banshee.Services/Banshee.Database/SortKeyUpdater.cs'
--- src/Core/Banshee.Services/Banshee.Database/SortKeyUpdater.cs	1970-01-01 00:00:00 +0000
+++ src/Core/Banshee.Services/Banshee.Database/SortKeyUpdater.cs	2009-02-11 21:43:00 +0000
@@ -0,0 +1,69 @@
+//
+// SortKeyUpdater.cs
+//
+// Author:
+//   John Millikin <jmillikin@gmail.com>
+//
+// 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.Globalization;
+using Banshee.Configuration;
+using Banshee.ServiceStack;
+
+namespace Banshee.Database
+{
+    internal class SortKeyUpdater
+    {
+        public static void Update ()
+        {
+            string locale = CultureInfo.CurrentCulture.Name;
+            Hyena.Log.DebugFormat ("locale = {0}, previous = {1}", locale, PreviousLocale);
+            if (locale != PreviousLocale) {
+                ForceUpdate (locale);
+            }
+        }
+        
+        public static void ForceUpdate ()
+        {
+            ForceUpdate (CultureInfo.CurrentCulture.Name);
+        }
+        
+        protected static void ForceUpdate (string new_locale)
+        {
+            BansheeDbConnection db = ServiceManager.DbConnection;
+            db.Execute ("BEGIN");
+            db.Execute (@"UPDATE CoreArtists SET NameSortKey = HYENA_COLLATION_KEY(IFNULL(NameSort,Name))");
+            db.Execute (@"UPDATE CoreAlbums SET TitleSortKey = HYENA_COLLATION_KEY(IFNULL(TitleSort,Title)),
+                                                ArtistNameSortKey = HYENA_COLLATION_KEY(IFNULL(ArtistNameSort, ArtistName))");
+            db.Execute (@"UPDATE CoreTracks SET TitleSortKey = HYENA_COLLATION_KEY(IFNULL(TitleSort,Title))");
+            
+            DatabaseConfigurationClient.Client.Set<string> ("SortKeyLocale", new_locale);
+            db.Execute ("COMMIT");
+        }
+        
+        protected static string PreviousLocale {
+            get {
+                return DatabaseConfigurationClient.Client.Get<string> ("SortKeyLocale", "");
+            }
+        }
+    }
+}

=== modified file 'src/Core/Banshee.Services/Banshee.Query/BansheeQuery.cs'
--- src/Core/Banshee.Services/Banshee.Query/BansheeQuery.cs	2009-02-11 02:34:53 +0000
+++ src/Core/Banshee.Services/Banshee.Query/BansheeQuery.cs	2009-02-11 21:43:00 +0000
@@ -337,7 +337,7 @@
             PlayCountField.ShortLabel   = Catalog.GetString ("Plays");
         }
 
-        private const string default_sort = @"CoreAlbums.ArtistNameLowered ASC, CoreAlbums.TitleLowered ASC, CoreTracks.Disc ASC, CoreTracks.TrackNumber ASC";
+        private const string default_sort = @"CoreAlbums.ArtistNameSortKey ASC, CoreAlbums.TitleSortKey ASC, CoreTracks.Disc ASC, CoreTracks.TrackNumber ASC";
         public static string GetSort (string key)
         {
             return GetSort (key, false);
@@ -353,40 +353,40 @@
                 case "track":
                 case "grouping":
                     sort_query = String.Format (@"
-                        CoreAlbums.ArtistNameLowered ASC, 
-                        CoreAlbums.TitleLowered ASC, 
+                        CoreAlbums.ArtistNameSortKey ASC, 
+                        CoreAlbums.TitleSortKey ASC, 
                         CoreTracks.Disc ASC,
                         CoreTracks.TrackNumber {0}", ascDesc); 
                     break;
 
                 case "albumartist":
                     sort_query = String.Format (@"
-                        CoreAlbums.ArtistNameLowered {0}, 
-                        CoreAlbums.TitleLowered ASC, 
+                        CoreAlbums.ArtistNameSortKey {0}, 
+                        CoreAlbums.TitleSortKey ASC, 
                         CoreTracks.Disc ASC,
                         CoreTracks.TrackNumber ASC", ascDesc); 
                     break;
 
                 case "artist":
                     sort_query = String.Format (@"
-                        CoreArtists.NameLowered {0}, 
-                        CoreAlbums.TitleLowered ASC,
+                        CoreArtists.NameSortKey {0}, 
+                        CoreAlbums.TitleSortKey ASC,
                         CoreTracks.Disc ASC,
                         CoreTracks.TrackNumber ASC", ascDesc); 
                     break;
 
                 case "album":
                     sort_query = String.Format (@"
-                        CoreAlbums.TitleLowered {0},
+                        CoreAlbums.TitleSortKey {0},
                         CoreTracks.Disc ASC,
                         CoreTracks.TrackNumber ASC", ascDesc); 
                     break;
 
                 case "title":
                     sort_query = String.Format (@"
-                        CoreTracks.TitleLowered {0},
-                        CoreAlbums.ArtistNameLowered ASC, 
-                        CoreAlbums.TitleLowered ASC", ascDesc); 
+                        CoreTracks.TitleSortKey {0},
+                        CoreAlbums.ArtistNameSortKey ASC, 
+                        CoreAlbums.TitleSortKey ASC", ascDesc); 
                     break;
 
                 case "random":
@@ -395,8 +395,8 @@
 
                 case "disc":
                     sort_query = String.Format (@"
-                        CoreAlbums.ArtistNameLowered ASC, 
-                        CoreAlbums.TitleLowered ASC, 
+                        CoreAlbums.ArtistNameSortKey ASC, 
+                        CoreAlbums.TitleSortKey ASC, 
                         CoreTracks.Disc {0},
                         CoreTracks.TrackNumber ASC", ascDesc);
                     break;
@@ -406,18 +406,26 @@
                 case "lastplayed":
                 case "lastskipped":
                     column = String.Format ("{0}stamp", key);
-                    goto case "comment";
+                    goto case "year";
                 case "added":
                     column = "dateaddedstamp";
-                    goto case "comment";
+                    goto case "year";
+
+                case "conductor":
+                case "genre":
+                case "composer":
+                case "comment":
+                    sort_query = String.Format (
+                        "HYENA_COLLATION_KEY(CoreTracks.{0}) {1}, {2}",
+                        column ?? key, ascDesc, default_sort
+                    );
+                    break;
 
                 case "year":
                 case "bitrate":
                 case "bpm":
-                case "conductor":
                 case "trackcount":
                 case "disccount":
-                case "genre":
                 case "duration":
                 case "rating":
                 case "playcount":
@@ -428,9 +436,7 @@
                 case "dateaddedstamp":
                 case "uri":
                 case "mimetype":
-                case "composer":
                 case "licenseuri":
-                case "comment":
                     sort_query = String.Format (
                         "CoreTracks.{0} {1}, {2}",
                         column ?? key, ascDesc, default_sort

=== modified file 'src/Core/Banshee.Services/Makefile.am'
--- src/Core/Banshee.Services/Makefile.am	2009-02-02 18:28:57 +0000
+++ src/Core/Banshee.Services/Makefile.am	2009-02-12 03:27:00 +0000
@@ -48,6 +48,7 @@
 	Banshee.Database/BansheeDbFormatMigrator.cs \
 	Banshee.Database/BansheeModelCache.cs \
 	Banshee.Database/BansheeModelProvider.cs \
+	Banshee.Database/SortKeyUpdater.cs \
 	Banshee.Equalizer/EqualizerManager.cs \
 	Banshee.Equalizer/EqualizerSetting.cs \
 	Banshee.Equalizer/EqualizerSettingEvent.cs \

=== modified file 'src/Libraries/Hyena/Hyena.Data.Sqlite/HyenaSqliteCommand.cs'
--- src/Libraries/Hyena/Hyena.Data.Sqlite/HyenaSqliteCommand.cs	2009-01-28 01:29:07 +0000
+++ src/Libraries/Hyena/Hyena.Data.Sqlite/HyenaSqliteCommand.cs	2009-02-12 01:44:51 +0000
@@ -207,7 +207,7 @@
             return this;
         }
 
-        private static object SqlifyObject (object o)
+        public static object SqlifyObject (object o)
         {
             if (o is string) {
                 return String.Format ("'{0}'", (o as string).Replace ("'", "''"));
@@ -217,6 +217,9 @@
                 return ((bool)o) ? "1" : "0";
             } else if (o == null) {
                 return "NULL";
+            } else if (o is byte[]) {
+                string hex = BitConverter.ToString (o as byte[]).Replace ("-", "");
+                return String.Format ("X'{0}'", hex);
             } else if (o is Array) {
                 StringBuilder sb = new StringBuilder ();
                 bool first = true;

=== modified file 'src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteUtils.cs'
--- src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteUtils.cs	2009-02-11 02:34:53 +0000
+++ src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteUtils.cs	2009-02-12 02:32:01 +0000
@@ -42,6 +42,8 @@
             } else if (type == typeof (int) || type == typeof (long) || type == typeof (bool)
                 || type == typeof (DateTime) || type == typeof (TimeSpan) || type.IsEnum) {
                 return "INTEGER";
+            } else if (type == typeof (byte[])) {
+                return "BLOB";
             } else {
                 throw new Exception (String.Format (
                     "The type {0} cannot be bound to a database column.", type.Name));
@@ -123,11 +125,21 @@
             return builder.ToString ();
         }
     }
+        
+    [SqliteFunction (Name = "HYENA_COLLATION_KEY", FuncType = FunctionType.Scalar, Arguments = 1)]
+    internal class CollationKeyFunction : SqliteFunction
+    {
+        public override object Invoke (object[] args)
+        {
+            return Hyena.StringUtil.SortKey (args[0] as string);
+        }
+    }
     
     [SqliteFunction (Name = "HYENA_SEARCH_KEY", FuncType = FunctionType.Scalar, Arguments = 1)]
     internal class SearchKeyFunction : SqliteFunction
     {
-        public override object Invoke (object[] args) {
+        public override object Invoke (object[] args)
+        {
             return Hyena.StringUtil.SearchKey (args[0] as string);
         }
     }

=== modified file 'src/Libraries/Hyena/Hyena.Data.Sqlite/Tests/SqliteCommandTests.cs'
--- src/Libraries/Hyena/Hyena.Data.Sqlite/Tests/SqliteCommandTests.cs	2008-05-19 20:34:45 +0000
+++ src/Libraries/Hyena/Hyena.Data.Sqlite/Tests/SqliteCommandTests.cs	2009-02-12 01:52:08 +0000
@@ -106,6 +106,62 @@
             return tf.GetValue (cmd, null) as string;
         }
     }
+    
+    [TestFixture]
+    public class ObjectToSqlTests
+    {
+        protected void AssertToSql (object o, object expected)
+        {
+            Assert.AreEqual (expected, HyenaSqliteCommand.SqlifyObject (o));
+        }
+        
+        [Test]
+        public void TestNull ()
+        {
+            AssertToSql (null, "NULL");
+        }
+        
+        [Test]
+        public void TestBool ()
+        {
+            AssertToSql (false, "0");
+            AssertToSql (true, "1");
+        }
+        
+        [Test]
+        public void TestString ()
+        {
+            AssertToSql ("", "''");
+            AssertToSql ("test", "'test'");
+            AssertToSql ("te'st", "'te''st'");
+        }
+        
+        [Test]
+        public void TestByteArray ()
+        {
+            // BLOB notation
+            AssertToSql (new byte[] {}, "X''");
+            AssertToSql (new byte[] {0x10, 0x20, 0x30}, "X'102030'");
+        }
+        
+        [Test]
+        public void TestOtherArray ()
+        {
+            AssertToSql (new object[] {}, "");
+            AssertToSql (new object[] {"a"}, "'a'");
+            AssertToSql (new object[] {"a", "b"}, "'a','b'");
+        }
+        
+        [Test]
+        public void TestDateTime ()
+        {
+            // Returned using local time, not UTC
+            AssertToSql (new DateTime (2000, 1, 2), 946792800);
+            
+            // Disregards milliseconds
+            AssertToSql (new DateTime (2000, 1, 2, 10, 9, 8, 7), 946829348);
+        }
+    }
 }
 
 #endif

=== added file 'src/Libraries/Hyena/Hyena.Data.Sqlite/Tests/SqliteUtilTests.cs'
--- src/Libraries/Hyena/Hyena.Data.Sqlite/Tests/SqliteUtilTests.cs	1970-01-01 00:00:00 +0000
+++ src/Libraries/Hyena/Hyena.Data.Sqlite/Tests/SqliteUtilTests.cs	2009-02-12 04:25:18 +0000
@@ -0,0 +1,85 @@
+//
+// SqliteUtilTests.cs
+//
+// Author:
+//   John Millikin <jmillikin@gmail.com>
+//
+// 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.
+//
+
+#if ENABLE_TESTS
+
+using NUnit.Framework;
+using Hyena.Data.Sqlite;
+
+namespace Hyena.Data.Sqlite.Tests
+{
+    [TestFixture]
+    public class CollationKeyTests
+    {
+        protected void TestCollationKey (object before, object after)
+        {
+            Assert.AreEqual (after, (new CollationKeyFunction ()).Invoke (new object[] {before}));
+        }
+        
+        [Test]
+        public void TestNull ()
+        {
+            TestCollationKey (null, null);
+            TestCollationKey (System.DBNull.Value, null);
+        }
+        
+        [Test]
+        public void TestKey ()
+        {
+            // See Hyena.StringUtil.Tests for full tests. This just checks that
+            // the collation function is actually being used.
+            TestCollationKey ("", new byte[] {1, 1, 1, 1, 0});
+            TestCollationKey ("\u0104", new byte[] {14, 2, 1, 27, 1, 1, 1, 0});
+        }
+    }
+    
+    [TestFixture]
+    public class SearchKeyTests
+    {
+        protected void TestSearchKey (object before, object after)
+        {
+            Assert.AreEqual (after, (new SearchKeyFunction ()).Invoke (new object[] {before}));
+        }
+        
+        [Test]
+        public void TestNull ()
+        {
+            TestSearchKey (null, null);
+            TestSearchKey (System.DBNull.Value, null);
+        }
+        
+        [Test]
+        public void TestKey ()
+        {
+            // See Hyena.StringUtil.Tests for full tests. This just checks that
+            // the search key function is actually being used.
+            TestSearchKey ("", "");
+            TestSearchKey ("\u0104", "a");
+        }
+    }
+}
+
+#endif

=== modified file 'src/Libraries/Hyena/Hyena/StringUtil.cs'
--- src/Libraries/Hyena/Hyena/StringUtil.cs	2009-02-11 02:34:53 +0000
+++ src/Libraries/Hyena/Hyena/StringUtil.cs	2009-02-12 02:27:23 +0000
@@ -222,5 +222,12 @@
             
             return invalid_path_regex.Replace (input, String.Empty);
         }
+        
+        private static CompareInfo culture_compare_info = CultureInfo.CurrentCulture.CompareInfo;
+        public static byte[] SortKey (string orig)
+        {
+            if (orig == null) { return null; }
+            return culture_compare_info.GetSortKey (orig, CompareOptions.IgnoreCase).KeyData;
+        }
     }
 }

=== modified file 'src/Libraries/Hyena/Hyena/Tests/StringUtilTests.cs'
--- src/Libraries/Hyena/Hyena/Tests/StringUtilTests.cs	2009-02-11 02:34:53 +0000
+++ src/Libraries/Hyena/Hyena/Tests/StringUtilTests.cs	2009-02-12 03:01:13 +0000
@@ -236,6 +236,27 @@
             AssertSearchKey ("/", "");
         }
     }
+    
+    [TestFixture]
+    public class SortKeyTests
+    {
+        private void AssertSortKey (string before, object after)
+        {
+            Assert.AreEqual (after, StringUtil.SortKey (before));
+        }
+        
+        [Test]
+        public void TestNull ()
+        {
+            AssertSortKey (null, null);
+        }
+        
+        [Test]
+        public void TestEmpty ()
+        {
+            AssertSortKey ("", new byte[] {1, 1, 1, 1, 0});
+        }
+    }
 }
 
 #endif

=== modified file 'src/Libraries/Hyena/Makefile.am'
--- src/Libraries/Hyena/Makefile.am	2009-02-11 02:34:53 +0000
+++ src/Libraries/Hyena/Makefile.am	2009-02-12 01:58:06 +0000
@@ -31,6 +31,7 @@
 	Hyena.Data.Sqlite/Tests/DbBoundType.cs \
 	Hyena.Data.Sqlite/Tests/SqliteCommandTests.cs \
 	Hyena.Data.Sqlite/Tests/SqliteModelProviderTests.cs \
+	Hyena.Data.Sqlite/Tests/SqliteUtilTests.cs \
 	Hyena.Data/ArrayModelCache.cs \
 	Hyena.Data/ColumnDescription.cs \
 	Hyena.Data/DictionaryModelCache.cs \

