=== 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-11 02:42:28 +0000
@@ -200,6 +200,11 @@
             set { base.TitleSort = value; }
         }
         
+        [DatabaseColumn(Select = false)]
+        public byte[] TitleSortKey {
+            get { return Hyena.StringUtil.GetSortKey (TitleSort ?? Title); }
+        }
+        
         [DatabaseColumn]
         public override string ArtistName {
             get { return base.ArtistName; }
@@ -213,6 +218,11 @@
         }
 
         [DatabaseColumn(Select = false)]
+        public byte[] ArtistNameSortKey {
+            get { return Hyena.StringUtil.GetSortKey (ArtistNameSort ?? ArtistName); }
+        }
+        
+        [DatabaseColumn(Select = false)]
         protected string TitleLowered {
             get { return Hyena.StringUtil.SearchKey (Title); }
         }

=== 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 02:41:03 +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-11 02:42:28 +0000
@@ -144,6 +144,11 @@
             set { base.NameSort = value; }
         }
 
+        [DatabaseColumn(Select = false)]
+        public byte[] NameSortKey {
+            get { return Hyena.StringUtil.GetSortKey (NameSort ?? Name); }
+        }
+        
         [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 02:41:03 +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-11 02:42:28 +0000
@@ -457,6 +457,11 @@
         }
         
         [DatabaseColumn(Select = false)]
+        public byte[] TrackTitleSortKey {
+            get { return Hyena.StringUtil.GetSortKey (TrackTitleSort ?? TrackTitle); }
+        }
+        
+        [DatabaseColumn(Select = false)]
         protected string TitleLowered {
             get { return Hyena.StringUtil.SearchKey (TrackTitle); }
         }

=== 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 02:41:03 +0000
@@ -71,6 +71,9 @@
             lock (this) {
                 migrator.Migrate ();
                 migrator = null;
+
+                // Update cached sorting keys
+                (new SortKeyUpdater (this)).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 02:46:20 +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 02:42:28 +0000
@@ -0,0 +1,82 @@
+//
+// 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;
+
+namespace Banshee.Database
+{
+    internal class SortKeyUpdater
+    {
+        protected BansheeDbConnection db;
+        
+        public SortKeyUpdater (BansheeDbConnection db)
+        {
+            this.db = db;
+        }
+        
+        public void Update ()
+        {
+            string locale = CultureInfo.CurrentCulture.Name;
+            Hyena.Log.DebugFormat ("locale = {0}, previous = {1}", locale, PreviousLocale);
+            if (locale != PreviousLocale) {
+                ForceUpdate (locale);
+            }
+        }
+        
+        public void ForceUpdate ()
+        {
+            ForceUpdate (CultureInfo.CurrentCulture.Name);
+        }
+        
+        protected void ForceUpdate (string new_locale)
+        {
+            db.Execute ("BEGIN");
+            db.Execute (@"UPDATE CoreArtists SET NameSortKey = HYENA_COLLATE(IFNULL(NameSort,Name))");
+            db.Execute (@"UPDATE CoreAlbums SET TitleSortKey = HYENA_COLLATE(IFNULL(TitleSort,Title)),
+                                                ArtistNameSortKey = HYENA_COLLATE(IFNULL(ArtistNameSort, ArtistName))");
+            db.Execute (@"UPDATE CoreTracks SET TitleSortKey = HYENA_COLLATE(IFNULL(TitleSort,Title))");
+            
+            if (db.Query<int> ("SELECT COUNT(*) FROM CoreConfiguration WHERE Key = 'SortKeyLocale'") == 0) {
+                db.Execute ("INSERT INTO CoreConfiguration VALUES (null, 'SortKeyLocale', ?)", new_locale);
+            } else {
+                db.Execute ("UPDATE CoreConfiguration SET Value = ? WHERE Key = 'SortKeyLocale'", new_locale);
+            }
+            
+            db.Execute ("COMMIT");
+        }
+        
+        protected string PreviousLocale {
+            get {
+                if (!db.TableExists("CoreConfiguration")) {
+                    return "";
+                }
+                
+                return db.Query<string> ("SELECT Value FROM CoreConfiguration WHERE Key = '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 03:01:26 +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_COLLATE(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-11 02:42:28 +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-11 03:04:06 +0000
@@ -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-11 02:59:08 +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,6 +125,20 @@
             return builder.ToString ();
         }
     }
+        
+    [SqliteFunction (Name = "HYENA_COLLATE", FuncType = FunctionType.Scalar, Arguments = 1)]
+    internal class CollateFunction : SqliteFunction
+    {
+        public override object Invoke (object[] args)
+        {
+            string sortname = args[0] as string;
+            if (System.DBNull.Value.Equals (sortname) || sortname == null) {
+                return args[0]; // noop
+            }
+            
+            return Hyena.StringUtil.GetSortKey (sortname);
+        }
+    }
     
     [SqliteFunction (Name = "HYENA_SEARCH_KEY", FuncType = FunctionType.Scalar, Arguments = 1)]
     internal class SearchKeyFunction : SqliteFunction

=== 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-11 02:42:28 +0000
@@ -222,5 +222,10 @@
             
             return invalid_path_regex.Replace (input, String.Empty);
         }
+        
+        public static byte[] GetSortKey (string orig)
+        {
+            return CultureInfo.CurrentCulture.CompareInfo.GetSortKey (orig, CompareOptions.IgnoreCase).KeyData;
+        }
     }
 }

