banshee r2970 - in trunk/banshee: . build src/Core/Banshee.Core/Banshee.Collection src/Core/Banshee.Services src/Core/Banshee.Services/Banshee.Collection.Database src/Core/Banshee.Services/Banshee.Database src/Core/Hyena src/Core/Hyena/Hyena.Data



Author: scottp
Date: Tue Jan 15 18:38:49 2008
New Revision: 2970
URL: http://svn.gnome.org/viewvc/banshee?rev=2970&view=rev

Log:
* src/Core/Hyena/Hyena.Data/HyenaDbConnection.cs: An abstraction of
  BansheeDbConnection/COmmand
* src/Core/Hyena/Hyena.Data/DatabaseModelProvider.cs: Added.
* src/Core/Hyena/Hyena.Data/CacheableDatabaseModelProvider.cs: Added.
  This class functionally inherits from both CacheableModelAdapter
  and DatabaseModelProvider.
* src/Core/Hyena/Hyena.Data/CacheableModelAdapter.cs: Added Add()
  method.
* src/Core/Banshee.Services/Banshee.Collection.Database/BansheeModelProvider.cs:
  Added. This will replace BansheeCacheableModelAdapter.
* src/Core/Banshee.Services/Banshee.Collection.Database/LibraryTrackInfo.cs:
  Modified to use new model provider system.
* src/Core/Banshee.Services/Banshee.Collection.Database/TrackListDatabaseModel.cs:
  Added Provider inner class which inherits from
  BansheeModelProvider. Made to use that inner class.
* src/Core/Banshee.Services/Banshee.Database/BansheeDbConnection.cs:
  Abstracted into Hyena.
* src/Core/Banshee.Core/Banshee.Collection/TrackInfo.cs: Made MimeType
  and FileSize virtual.
* build/build.environment.mk: Added sql-related dependancies to Hyena's
  references.

Added:
   trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/BansheeModelProvider.cs
   trunk/banshee/src/Core/Hyena/Hyena.Data/CacheableDatabaseModelProvider.cs
   trunk/banshee/src/Core/Hyena/Hyena.Data/DatabaseModelProvider.cs
   trunk/banshee/src/Core/Hyena/Hyena.Data/HyenaDbConnection.cs
Modified:
   trunk/banshee/ChangeLog
   trunk/banshee/build/build.environment.mk
   trunk/banshee/src/Core/Banshee.Core/Banshee.Collection/TrackInfo.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/LibraryTrackInfo.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/TrackListDatabaseModel.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Database/BansheeDbConnection.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Services.mdp
   trunk/banshee/src/Core/Banshee.Services/Makefile.am
   trunk/banshee/src/Core/Hyena/Hyena.Data/CacheableModelAdapter.cs
   trunk/banshee/src/Core/Hyena/Hyena.mdp
   trunk/banshee/src/Core/Hyena/Makefile.am

Modified: trunk/banshee/build/build.environment.mk
==============================================================================
--- trunk/banshee/build/build.environment.mk	(original)
+++ trunk/banshee/build/build.environment.mk	Tue Jan 15 18:38:49 2008
@@ -78,7 +78,7 @@
 
 DIR_HYENA = $(DIR_CORE)/Hyena
 MONO_BASE_PATH += $(DIR_HYENA)
-REF_HYENA = $(LINK_SYSTEM)
+REF_HYENA = $(LINK_SYSTEM) $(LINK_SQLITE)
 LINK_HYENA = -r:$(DIR_HYENA)/Hyena.dll
 LINK_HYENA_DEPS = $(REF_HYENA) $(LINK_HYENA)
 

Modified: trunk/banshee/src/Core/Banshee.Core/Banshee.Collection/TrackInfo.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Core/Banshee.Collection/TrackInfo.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Core/Banshee.Collection/TrackInfo.cs	Tue Jan 15 18:38:49 2008
@@ -108,12 +108,12 @@
             set { more_info_uri = value; }
         }
 
-        public string MimeType {
+        public virtual string MimeType {
             get { return mimetype; }
             set { mimetype = value; }
         }
 
-        public long FileSize {
+        public virtual long FileSize {
             get { return filesize; }
             set { filesize = value; }
         }

Added: trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/BansheeModelProvider.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/BansheeModelProvider.cs	Tue Jan 15 18:38:49 2008
@@ -0,0 +1,113 @@
+//
+// BansheeModelProvider.cs
+//
+// Author:
+//   Scott Peterson <lunchtimemama gmail com>
+//
+// Copyright (C) 2007 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.Data;
+
+using Hyena.Data;
+
+using Banshee.Database;
+
+namespace Banshee.Collection.Database
+{
+    
+    public abstract class BansheeModelProvider<T> : CacheableDatabaseModelProvider<T>
+    {
+        private IDatabaseModel<T> model; // FIXME do away with this
+        
+        public BansheeModelProvider(BansheeDbConnection connection, IDatabaseModel<T> model)
+            : base(connection, model)
+        {
+            this.model = model;
+        }
+        
+        protected override sealed int DatabaseVersion {
+            get { return 1; }
+        }
+        
+        protected override sealed void CheckVersion()
+        {
+            using(IDataReader reader = Connection.ExecuteReader(String.Format(
+                "SELECT Value FROM CoreConfiguration WHERE Key = '{0}ModelVersion'", TableName))) {
+                if(reader.Read()) {
+                    int version = Int32.Parse(reader.GetString(0));
+                    if(version < ModelVersion) {
+                        MigrateTable(version);
+                        Connection.Execute(String.Format(
+                            "UPDATE CoreConfiguration SET Value = '{0}' WHERE Key = '{0}ModelVersion'",
+                            ModelVersion, TableName));
+                    }
+                } else {
+                    Connection.Execute(String.Format(
+                        "INSERT INTO CoreConfiguration (Key, Value) VALUES ('{0}ModelVersion', '{1}')",
+                        TableName, ModelVersion));
+                }
+            }
+            using(IDataReader reader = Connection.ExecuteReader("SELECT Value FROM CoreConfiguration WHERE Key = 'DatabaseVersion'")) {
+                if(reader.Read()) {
+                    int version = Int32.Parse(reader.GetString(0));
+                    if(version < DatabaseVersion) {
+                        MigrateDatabase(version);
+                        Connection.Execute(String.Format(
+                            "UPDATE CoreConfiguration SET Value = '{0}' WHERE Key = 'DatabaseVersion'",
+                            DatabaseVersion));
+                    }
+                } else {
+                    Connection.Execute(String.Format(
+                        "INSERT INTO CoreConfiguration (Key, Value) VALUES ('DatabaseVersion', '{0}')",
+                        DatabaseVersion));
+                }
+            }
+        }
+
+        
+        protected override string ReloadFragment {
+            get { return model.ReloadFragment; } // FIXME move this elsewhere
+        }
+        
+        protected override sealed string CacheTableName {
+            get { return "CoreCache"; }
+        }
+        
+        protected override sealed bool Persistent {
+            get { return true; }
+        }
+        
+        protected override sealed string CacheModelsTableName {
+            get { return "CoreCacheModels"; }
+        }
+        
+        protected override sealed void MigrateDatabase(int old_version)
+        {
+        }
+        
+        protected override void MigrateTable(int old_version)
+        {
+        }
+    }
+}

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/LibraryTrackInfo.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/LibraryTrackInfo.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/LibraryTrackInfo.cs	Tue Jan 15 18:38:49 2008
@@ -29,228 +29,195 @@
 using System;
 using System.Data;
 
-using Banshee.IO;
+using Hyena.Data;
+
 using Banshee.Base;
 using Banshee.Database;
 using Banshee.ServiceStack;
 
 namespace Banshee.Collection.Database
-{    
+{
     public class LibraryTrackInfo : TrackInfo
     {
-        public enum Column : int {
-            TrackID,
-            ArtistID,
-            AlbumID,
-            TagSetID,
-            MusicBrainzID,
-            Uri,
-            UriType,
-            MimeType,
-            FileSize,
-            Title,
-            TrackNumber,
-            TrackCount,
-            DiscNumber,
-            Duration,
-            Year,
-            Rating,
-            PlayCount,
-            SkipCount,
-            LastPlayedStamp,
-            DateAddedStamp,
-            
-            // These columns are virtual - they are not actually 
-            // in CoreTracks and are returned on join selects
-            Artist,
-            AlbumTitle
-        }
-        
         private enum UriType : int {
             AbsolutePath,
             RelativePath,
             AbsoluteUri
         }
         
-        private int dbid;
-        private int db_index;
-
-        private int artist_id;
-        private int album_id;
+        private TrackListDatabaseModel model;
         
         public LibraryTrackInfo () : base ()
         {
-            dbid = -1;
-        }
-
-        public LibraryTrackInfo (IDataReader reader, int index) : base ()
-        {
-            LoadFromReader (reader);
-            DbIndex = index;
         }
 
-        private void LoadFromReader (IDataReader reader)
+        public LibraryTrackInfo (TrackListDatabaseModel model, int index) : base () // Get rid of model
         {
-            dbid = ReaderGetInt32 (reader, Column.TrackID);
-            
-            string uri = ReaderGetString (reader, Column.Uri);
-            UriType uri_type = (UriType)ReaderGetInt32 (reader, Column.UriType);
-            
-            if (uri_type == UriType.RelativePath) {
-                uri = System.IO.Path.Combine (Paths.LibraryLocation, uri);
-            }
-            
-            Uri = new SafeUri (uri);
-            FileSize = ReaderGetInt64 (reader, Column.FileSize);
-            
-            ArtistName = ReaderGetString (reader, Column.Artist);
-            ArtistId = ReaderGetInt32 (reader, Column.ArtistID);
-
-            AlbumTitle = ReaderGetString (reader, Column.AlbumTitle);
-            AlbumId = ReaderGetInt32 (reader, Column.AlbumID);
-
-            TrackTitle = ReaderGetString (reader, Column.Title);
-
-            TrackNumber = ReaderGetInt32 (reader, Column.TrackNumber);
-            TrackCount = ReaderGetInt32 (reader, Column.TrackCount);
-            Year = ReaderGetInt32 (reader, Column.Year);
-            Rating = ReaderGetInt32 (reader, Column.Rating);
-
-            Duration = ReaderGetTimeSpan (reader, Column.Duration);
-
-            PlayCount = ReaderGetInt32 (reader, Column.PlayCount);
-            SkipCount = ReaderGetInt32 (reader, Column.SkipCount);
-
-            LastPlayed = ReaderGetDateTime (reader, Column.LastPlayedStamp);
-            DateAdded = ReaderGetDateTime (reader, Column.DateAddedStamp);
-            
+            this.model = model;
             Attributes |= TrackAttributes.CanPlay;
-        }
-
-        private string ReaderGetString (IDataReader reader, Column column)
-        {
-            int column_id = (int)column;
-            return !reader.IsDBNull (column_id) 
-                ? String.Intern (reader.GetString (column_id)) 
-                : null;
-        }
-        
-        private int ReaderGetInt32 (IDataReader reader, Column column)
-        {
-            return reader.GetInt32 ((int)column);
-        }
-
-        private long ReaderGetInt64 (IDataReader reader, Column column)
-        {
-            return reader.GetInt64 ((int)column);
-        }
-
-        private TimeSpan ReaderGetTimeSpan (IDataReader reader, Column column)
-        {
-            long raw = reader.GetInt64 ((int)column);
-            return new TimeSpan (raw * TimeSpan.TicksPerMillisecond);
-        }
-
-        private DateTime ReaderGetDateTime (IDataReader reader, Column column)
-        {
-            long raw = reader.GetInt64 ((int)column);
-            return DateTimeUtil.ToDateTime (raw);
+            DbIndex = index;
         }
 
         public override void Save ()
         {
             if (DbId < 0) {
-                InsertCommit ();
+                DbId = model.Insert(this);
             } else {
-                UpdateCommit ();
+                model.Update(this);
             }
         }
         
-        private void InsertCommit ()
-        {
-            TableSchema.CoreTracks.InsertCommand.ApplyValues (
-                null, // TrackID
-                ArtistId,
-                AlbumId,
-                -1, // TagSetID
-                null, // MusicBrainzID
-                Uri == null ? null : 
-                    (Paths.MakePathRelativeToLibrary (Uri.AbsolutePath) 
-                        ?? Uri.AbsoluteUri),
-                UriType.RelativePath,
-                MimeType,
-                FileSize,
-                TrackTitle,
-                TrackNumber,
-                TrackCount,
-                DiscNumber,
-                Duration.TotalMilliseconds,
-                Year,
-                Rating,
-                PlayCount,
-                SkipCount,
-                DateTimeUtil.FromDateTime (LastPlayed),
-                DateTimeUtil.FromDateTime (DateAdded)
-            );
-            
-            DbId = ServiceManager.DbConnection.Execute (TableSchema.CoreTracks.InsertCommand);
-        }
-        
-        private void UpdateCommit ()
-        {
-            TableSchema.CoreTracks.UpdateCommand.ApplyValues (
-                DbId, // TrackID
-                ArtistId,
-                AlbumId,
-                -1, // TagSetID
-                null, // MusicBrainzID
-                Uri == null ? null : 
-                    (Paths.MakePathRelativeToLibrary (Uri.AbsolutePath) 
-                        ?? Uri.AbsoluteUri),
-                UriType.RelativePath,
-                MimeType,
-                FileSize,
-                TrackTitle,
-                TrackNumber,
-                TrackCount,
-                DiscNumber,
-                Duration.TotalMilliseconds,
-                Year,
-                Rating,
-                PlayCount,
-                SkipCount,
-                DateTimeUtil.FromDateTime (LastPlayed),
-                DateTimeUtil.FromDateTime (DateAdded),
-                DbId // TrackID (again, for WHERE clause)
-            );
-
-            Console.WriteLine ("Updating: {0}", TableSchema.CoreTracks.UpdateCommand.CommandText);
-            ServiceManager.DbConnection.Execute (TableSchema.CoreTracks.UpdateCommand);
+        [DatabaseColumn("TrackID", BindingFlags = DatabaseBindingFlags.PrimaryKey)]
+        private int track_id;
+        public int TrackId {
+            get { return track_id; }
+            internal set { track_id = value; }
         }
 
         public int DbId {
-            get { return dbid; }
-            set { dbid = value; }
+            get { return TrackId; }
+            set { TrackId = value; }
         }
         
+        private int db_index;
         public int DbIndex {
             get { return db_index; }
             internal set { db_index = value; }
         }
 
+        [DatabaseColumn("ArtistID", Index = "CoreTracksArtistIndex")]
+        private int artist_id;
         public int ArtistId {
             get { return artist_id; }
             set { artist_id = value; }
         }
 
+        [DatabaseColumn("AlbumID", Index = "CoreTracksAlbumIndex")]
+        private int album_id;
         public int AlbumId {
             get { return album_id; }
             set { album_id = value; }
         }
 
+        [DatabaseVirtualColumn("Name", "CoreArtists", "ArtistID", "ArtistID")]
+        public override string ArtistName {
+            get { return base.ArtistName; }
+            set { base.ArtistName = value; }
+        }
+        
+        [DatabaseVirtualColumn("Title", "CoreAlbums", "AlbumID", "AlbumID")]
+        public override string AlbumTitle {
+            get { return base.AlbumTitle; }
+            set { base.AlbumTitle = value; }
+        }
+        
+        [DatabaseColumn]
+        private int TagSetID;
+        
+        [DatabaseColumn]
+        private string MusicBrainzID;
+        
+        [DatabaseColumn("Uri")]
+        private string uri;
+        
+        [DatabaseColumn("UriType")]
+        private int uri_type;
+        
+        [DatabaseColumn]
+        public override string MimeType {
+            get { return base.MimeType; }
+            set { base.MimeType = value; }
+        }
+        
+        [DatabaseColumn]
+        public override long FileSize {
+            get { return base.FileSize; }
+            set { base.FileSize = value; }
+        }
+        
+        [DatabaseColumn("Title")]
+        public override string TrackTitle {
+            get { return base.TrackTitle; }
+            set { base.TrackTitle = value; }
+        }
+        
+        [DatabaseColumn]
+        public override int TrackNumber {
+            get { return base.TrackNumber; }
+            set { base.TrackNumber = value; }
+        }
+        
+        [DatabaseColumn]
+        public override int TrackCount {
+            get { return base.TrackCount; }
+            set { base.TrackCount = value; }
+        }
+        
+        [DatabaseColumn]
+        public override int DiscNumber {
+            get { return base.DiscNumber; }
+            set { base.DiscNumber = value; }
+        }
+        
+        [DatabaseColumn("Duration")]
+        private long duration {
+            get { return (long)Duration.TotalMilliseconds; }
+            set { Duration = new TimeSpan (value * TimeSpan.TicksPerMillisecond); }
+        }
+        
+        [DatabaseColumn]
+        public override int Year {
+            get { return base.Year; }
+            set { base.Year = value; }
+        }
+        
+        [DatabaseColumn]
+        public override int Rating {
+            get { return base.Rating; }
+            set { base.Rating = value; }
+        }
+        
+        [DatabaseColumn]
+        public override int PlayCount {
+            get { return base.PlayCount; }
+            set { base.PlayCount = value; }
+        }
+        
+        [DatabaseColumn]
+        public override int SkipCount {
+            get { return base.SkipCount; }
+            set { base.SkipCount = value; }
+        }
+        
+        [DatabaseColumn]
+        private long LastPlayedStamp {
+            get { return DateTimeUtil.FromDateTime(LastPlayed); }
+            set { LastPlayed = DateTimeUtil.ToDateTime(value); }
+        }
+        
+        [DatabaseColumn]
+        private long DateAddedStamp {
+            get { return DateTimeUtil.FromDateTime(DateAdded); }
+            set { DateAdded = DateTimeUtil.ToDateTime(value); }
+        }
+        
+        private bool set_up;
+        public void SetUp()
+        {
+            if(set_up) {
+                return;
+            }
+            set_up = true;
+            
+            if (uri_type == (int)UriType.RelativePath) {
+                uri = System.IO.Path.Combine(Paths.LibraryLocation, uri);
+            }
+            Uri = new SafeUri(uri);
+        }
+
         private static BansheeDbCommand check_command = new BansheeDbCommand (
             "SELECT COUNT(*) FROM CoreTracks WHERE Uri = ? OR Uri = ?", 2);
-            
+        
         public static bool ContainsUri (SafeUri uri)
         {
             string relative_path = Paths.MakePathRelativeToLibrary (uri.AbsolutePath) ?? uri.AbsoluteUri;
@@ -258,4 +225,4 @@
                 check_command.ApplyValues (relative_path, uri.AbsoluteUri))) > 0;
         }
     }
-}
+}
\ No newline at end of file

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/TrackListDatabaseModel.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/TrackListDatabaseModel.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Collection.Database/TrackListDatabaseModel.cs	Tue Jan 15 18:38:49 2008
@@ -42,10 +42,54 @@
 namespace Banshee.Collection.Database
 {
     public class TrackListDatabaseModel : TrackListModel, IExportableModel, ICacheableModel, 
-        IDatabaseModel<TrackInfo>, IFilterable, ISortable, ICareAboutView
+        IDatabaseModel<LibraryTrackInfo>, IFilterable, ISortable, ICareAboutView
     {
+        private sealed class Provider : BansheeModelProvider<LibraryTrackInfo>
+        {
+            private BansheeDbConnection connection;
+            private TrackListDatabaseModel model;
+            
+            public Provider(BansheeDbConnection connection, TrackListDatabaseModel model) //FIXME get rid of model
+                : base(connection, model)
+            {
+                this.connection = connection;
+                this.model = model;
+            }
+            
+            protected override LibraryTrackInfo MakeNewObject(int offset)
+            {
+                return new LibraryTrackInfo(model, offset);
+            }
+
+            protected override void Add(int key, LibraryTrackInfo value)
+            {
+                base.Add(key, value);
+                value.SetUp();
+            }
+            
+            protected override string TableName {
+                get { return "CoreTracks"; }
+            }
+            
+            protected override int ModelVersion {
+                get { return 1; }
+            }
+            
+            public string FromFragment {
+                get { return From; }
+            }
+            
+            public string WhereFragment {
+                get { return Where; }
+            }
+            
+            public string SelectFragment {
+                get { return Select; }
+            }
+        }
+        
         private BansheeDbConnection connection;
-        private BansheeCacheableModelAdapter<TrackInfo> cache;
+        private Provider provider;
         private int count;
         private int unfiltered_count;
         
@@ -64,7 +108,7 @@
 
         public TrackListDatabaseModel (BansheeDbConnection connection, string uuid)
         {
-            cache = new BansheeCacheableModelAdapter<TrackInfo> (connection, uuid, this);
+            provider = new Provider(connection, this);
             this.connection = connection;
             Refilter ();
         }
@@ -171,7 +215,7 @@
         {
             lock(this) {
                 GenerateFilterQueryPart();
-                cache.Clear ();
+                provider.Clear ();
             }
         }
         
@@ -187,13 +231,13 @@
                 sort_column = column;
             
                 GenerateSortQueryPart();
-                cache.Clear ();
+                provider.Clear ();
             }
         }
         
         public override void Clear()
         {
-            cache.Clear ();
+            provider.Clear ();
             unfiltered_count = 0;
             count = 0;
             OnCleared();
@@ -203,8 +247,8 @@
         public override void Reload()
         {
             string unfiltered_query = String.Format (
-                "FROM CoreTracks, CoreAlbums, CoreArtists{0} WHERE {1} {2}",
-                JoinFragment, FetchCondition, ConditionFragment
+                "FROM {0}{1} WHERE {2} {3}",
+                FetchFrom, JoinFragment, FetchCondition, ConditionFragment
             );
 
             unfiltered_count = connection.QueryInt32 (String.Format (
@@ -237,11 +281,11 @@
                 
             reload_fragment = qb.ToString ();
 
-            if (!first_reload || !cache.Warm) {
-                cache.Reload ();
+            if (!first_reload || !provider.Warm) {
+                provider.Reload ();
             }
 
-            count = cache.Count;
+            count = provider.Count;
             first_reload = false;
 
             OnReloaded ();
@@ -257,7 +301,7 @@
         }
 
         public override TrackInfo this[int index] {
-            get { return cache.GetValue (index); }
+            get { return provider.GetValue (index); }
         }
         
         public override int Count {
@@ -347,7 +391,7 @@
         }
 
         public int CacheId {
-            get { return cache.CacheId; }
+            get { return provider.CacheId; }
         }
 
         public ISortableColumn SortColumn { 
@@ -375,9 +419,19 @@
         }
 
         // Implement IDatabaseModel
-        public TrackInfo GetItemFromReader (IDataReader reader, int index)
+        public LibraryTrackInfo GetItemFromReader (IDataReader reader, int index)
+        {
+            return new LibraryTrackInfo (this, index);
+        }
+        
+        public int Insert(LibraryTrackInfo track)
+        {
+            return provider.Insert(track);
+        }
+        
+        public void Update(LibraryTrackInfo track)
         {
-            return new LibraryTrackInfo (reader, index);
+            provider.Update(track);
         }
 
         private const string primary_key = "CoreTracks.TrackID";
@@ -390,15 +444,15 @@
         }
 
         public string FetchColumns {
-            get { return "CoreTracks.*, CoreArtists.Name, CoreAlbums.Title"; }
+            get { return provider.SelectFragment; }
         }
 
         public string FetchFrom {
-            get { return "CoreTracks, CoreArtists, CoreAlbums"; }
+            get { return provider.FromFragment; }
         }
 
         public string FetchCondition {
-            get { return "CoreArtists.ArtistID = CoreTracks.ArtistID AND CoreAlbums.AlbumID = CoreTracks.AlbumID"; }
+            get { return provider.WhereFragment; }
         }
 
         public override QueryField ArtistField {

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Database/BansheeDbConnection.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Database/BansheeDbConnection.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Database/BansheeDbConnection.cs	Tue Jan 15 18:38:49 2008
@@ -31,40 +31,30 @@
 using System.Data;
 using Mono.Data.Sqlite;
 
+using Hyena.Data;
+
 using Banshee.Base;
 using Banshee.ServiceStack;
 
 namespace Banshee.Database
 {
-    public class BansheeDbConnection : IService, IDisposable
+    public sealed class BansheeDbConnection : HyenaDbConnection, IService
     {
-        private SqliteConnection connection;
-
         public BansheeDbConnection () : this (true)
         {
         }
 
         public BansheeDbConnection (bool connect)
+            : base(connect)
         {
             if (connect) {
-                Open ();
-                Migrate ();
+                BansheeDbFormatMigrator migrator = new BansheeDbFormatMigrator (Connection);
+                migrator.SlowStarted += OnMigrationSlowStarted;
+                migrator.SlowPulse += OnMigrationSlowPulse;
+                migrator.SlowFinished += OnMigrationSlowFinished;
+                migrator.Migrate ();
             }
         }
-
-        private void Migrate ()
-        {
-            BansheeDbFormatMigrator migrator = new BansheeDbFormatMigrator (connection);
-            migrator.SlowStarted += OnMigrationSlowStarted;
-            migrator.SlowPulse += OnMigrationSlowPulse;
-            migrator.SlowFinished += OnMigrationSlowFinished;
-
-            migrator.Migrate ();
-
-            migrator.SlowStarted -= OnMigrationSlowStarted;
-            migrator.SlowPulse -= OnMigrationSlowPulse;
-            migrator.SlowFinished -= OnMigrationSlowFinished;
-        }
         
         //private Gtk.Window slow_window;
         //private Gtk.ProgressBar slow_progress;
@@ -134,102 +124,7 @@
             }*/
         }
 
-        public void Dispose ()
-        {
-            Close ();
-        }
-
-        public void Open ()
-        {
-            lock (this) {
-                if (connection != null) {
-                    return;
-                }
-
-                string dbfile = DatabaseFile;
-                Console.WriteLine ("Opening connection to Banshee Database: {0}", dbfile);
-                connection = new SqliteConnection (String.Format ("Version=3,URI=file:{0}", dbfile));
-                connection.Open ();
-
-                Execute (@"
-                    PRAGMA synchronous = OFF;
-                    PRAGMA cache_size = 32768;
-                ");
-            }
-        }
-
-        public void Close ()
-        {
-            lock (this) {
-                if (connection != null) {
-                    connection.Close ();
-                    connection = null;
-                }
-            }
-        }
-
-#region Convenience methods 
-
-        public IDataReader ExecuteReader (SqliteCommand command)
-        {
-            if (command.Connection == null)
-                command.Connection = connection;
-            return command.ExecuteReader ();
-        }
-
-        public IDataReader ExecuteReader (BansheeDbCommand command)
-        {
-            return ExecuteReader (command.Command);
-        }
-
-        public IDataReader ExecuteReader (object command)
-        {
-            return ExecuteReader (new SqliteCommand (command.ToString ()));
-        }
-
-        public object ExecuteScalar (SqliteCommand command)
-        {
-            if (command.Connection == null)
-                command.Connection = connection;
-            return command.ExecuteScalar ();
-        }
-
-        public object ExecuteScalar (BansheeDbCommand command)
-        {
-            return ExecuteScalar (command.Command);
-        }
-
-        public object ExecuteScalar (object command)
-        {
-            return ExecuteScalar (new SqliteCommand (command.ToString ()));
-        }
-
-        public Int32 QueryInt32 (object command)
-        {
-            return Convert.ToInt32 (ExecuteScalar (command));
-        }
-
-        public int Execute (SqliteCommand command)
-        {
-            if (command.Connection == null)
-                command.Connection = connection;
-            command.ExecuteNonQuery ();
-            return command.LastInsertRowID ();
-        }
-
-        public int Execute (BansheeDbCommand command)
-        {
-            return Execute (command.Command);
-        }
-
-        public int Execute (object command)
-        {
-            return Execute (new SqliteCommand (command.ToString ()));
-        }
-
-#endregion
-
-        public string DatabaseFile {
+        public override string DatabaseFile {
             get {
                 if (ApplicationContext.CommandLine.Contains ("db"))
                     return ApplicationContext.CommandLine["db"];
@@ -254,115 +149,27 @@
                 return dbfile;
             }
         }
-
-        public IDbConnection Connection {
-            get { return connection; }
-        }
         
         string IService.ServiceName {
             get { return "DbConnection"; }
         }
     }
     
-    public class BansheeDbCommand
+    public sealed class BansheeDbCommand : HyenaDbCommand
     {
-        private SqliteCommand command;
-
-#region Properties
-
-        public SqliteCommand Command {
-            get { return command; }
-        }
-
-        public SqliteParameterCollection Parameters {
-            get { return command.Parameters; }
-        }
-
-        public string CommandText {
-            get { return command.CommandText; }
-        }
-
-#endregion
-
         public BansheeDbCommand(string command)
+            : base(command)
         {
-            this.command = new SqliteCommand (command);
         }
 
-        public BansheeDbCommand (string command, int num_params) : this (command)
+        public BansheeDbCommand (string command, int num_params)
+            : base(command, num_params)
         {
-            for (int i = 0; i < num_params; i++) {
-                Parameters.Add (new SqliteParameter ());
-            }
         }
 
-        public BansheeDbCommand (string command, params object [] param_values) : this (command, param_values.Length)
-        {
-            ApplyValues (param_values);
-        }
-
-        public BansheeDbCommand ApplyValues (params object [] param_values)
-        {
-            if (param_values.Length != Parameters.Count) {
-                throw new ArgumentException (String.Format (
-                    "Command has {0} parameters, but {1} values given.", Parameters.Count, param_values.Length
-                ));
-            }
-
-            for (int i = 0; i < param_values.Length; i++) {
-                Parameters[i].Value = param_values[i];
-            }
-
-            return this;
-        }
-        
-        public void AddNamedParameter (string name, object value)
-        {
-            SqliteParameter param = new SqliteParameter (name, DbType.String);
-            param.Value = value;
-            Parameters.Add (param);
-        }
-                
-        /*public DbCommand(string command, params object [] parameters) : this(command)
-        {
-            for(int i = 0; i < parameters.Length;) {
-                SqliteParameter param;
-                
-                if(parameters[i] is SqliteParameter) {
-                    param = (SqliteParameter)parameters[i];
-                    if(i < parameters.Length - 1 && !(parameters[i + 1] is SqliteParameter)) {
-                        param.Value = parameters[i + 1];
-                        i += 2;
-                    } else {
-                        i++;
-                    }
-                } else {
-                    param = new SqliteParameter();
-                    param.ParameterName = (string)parameters[i];
-                    param.Value = parameters[i + 1];
-                    i += 2;
-                }
-                
-                Parameters.Add(param);
-            }
-        }
-        
-        public void AddParameter (object value)
+        public BansheeDbCommand (string command, params object [] param_values)
+            : base(command, param_values.Length)
         {
-            SqliteParameter param = new SqliteParameter ();
-            param.Value = value;
-            Parameters.Add (param);
         }
-        
-        public void AddParameter<T>(string name, T value)
-        {
-            AddParameter<T>(new DbParameter<T>(name), value);
-        }
-        
-        public void AddParameter<T>(DbParameter<T> param, T value)
-        {
-            param.Value = value;
-            Parameters.Add(param);
-        }*/
     }
 }

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Services.mdp
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Services.mdp	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Services.mdp	Tue Jan 15 18:38:49 2008
@@ -110,6 +110,7 @@
     <File name="Banshee.Metadata.Rhapsody/RhapsodyQueryJob.cs" subtype="Code" buildaction="Compile" />
     <File name="Banshee.Networking/NetworkDetect.cs" subtype="Code" buildaction="Compile" />
     <File name="Banshee.Networking/NetworkManager.cs" subtype="Code" buildaction="Compile" />
+    <File name="Banshee.Collection.Database/BansheeModelProvider.cs" subtype="Code" buildaction="Compile" />
   </Contents>
   <References>
     <ProjectReference type="Gac" localcopy="True" refto="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />

Modified: trunk/banshee/src/Core/Banshee.Services/Makefile.am
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Makefile.am	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Makefile.am	Tue Jan 15 18:38:49 2008
@@ -13,6 +13,7 @@
 	Banshee.Collection.Database/AlbumListDatabaseModel.cs \
 	Banshee.Collection.Database/ArtistListDatabaseModel.cs \
 	Banshee.Collection.Database/BansheeCacheableModelAdapter.cs \
+	Banshee.Collection.Database/BansheeModelProvider.cs \
 	Banshee.Collection.Database/IDatabaseModel.cs \
 	Banshee.Collection.Database/LibraryAlbumInfo.cs \
 	Banshee.Collection.Database/LibraryArtistInfo.cs \

Added: trunk/banshee/src/Core/Hyena/Hyena.Data/CacheableDatabaseModelProvider.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Core/Hyena/Hyena.Data/CacheableDatabaseModelProvider.cs	Tue Jan 15 18:38:49 2008
@@ -0,0 +1,298 @@
+//
+// CacheableDatabaseModelProvider.cs
+//
+// Author:
+//   Scott Peterson <lunchtimemama gmail com>
+//
+// Copyright (C) 2007 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.Collections.Generic;
+using System.Data;
+
+namespace Hyena.Data
+{
+    public sealed class DatabaseCacheTable
+    {
+        public static int ModelCount;
+    }
+    
+    public abstract class CacheableDatabaseModelProvider<T> : DatabaseModelProvider<T>
+    {
+        private sealed class DatabaseCacheableModelProvider : CacheableModelAdapter<T>
+        {
+            private CacheableDatabaseModelProvider<T> db;
+            
+            public DatabaseCacheableModelProvider(ICacheableModel model, CacheableDatabaseModelProvider<T> db)
+                : base(model)
+            {
+                this.db = db;
+            }
+            
+            protected override Dictionary<int, T> Cache {
+                get { return db.Cache; }
+            }
+            
+            public Dictionary<int, T> CacheImpl {
+                get { return base.Cache; }
+            }
+            
+            public override T GetValue(int index)
+            {
+                return db.GetValue(index);
+            }
+            
+            public T GetValueImpl(int index)
+            {
+                return base.GetValue(index);
+            }
+            
+            public override void Clear()
+            {
+                db.Clear();
+            }
+            
+            public void ClearImpl()
+            {
+                base.Clear();
+            }
+            
+            protected override void FetchSet(int offset, int limit)
+            {
+                db.FetchSet(offset, limit);
+            }
+            
+            protected override void Add(int key, T value)
+            {
+                db.Add(key, value);
+            }
+            
+            public void AddImpl(int key, T value)
+            {
+                base.Add(key, value);
+            }
+
+            public override int Reload ()
+            {
+                return db.Reload();
+            }
+            
+            public void InvalidateManagedCacheImpl()
+            {
+                InvalidateManagedCache();
+            }
+        }
+        
+        private DatabaseCacheableModelProvider cache;
+        private HyenaDbConnection connection;
+        private HyenaDbCommand select_range_command;
+        private HyenaDbCommand count_command;
+        private string reload_sql;
+        private int uid;
+        private int rows;
+        private bool warm;
+        
+        private static bool cache_initialized = false;
+        
+        protected abstract string ReloadFragment { get; }
+        protected abstract bool Persistent { get; }
+        
+        protected virtual string CacheModelsTableName {
+            get { return "HyenaCacheModels"; }
+        }
+        
+        protected virtual string CacheTableName {
+            get { return "HyenaCache"; }
+        }
+        
+        public CacheableDatabaseModelProvider(HyenaDbConnection connection, ICacheableModel model)
+            : base(connection)
+        {
+            this.connection = connection;
+            cache = new DatabaseCacheableModelProvider(model, this);
+            
+            CheckCacheTable();
+            
+            count_command = new HyenaDbCommand(String.Format(
+                "SELECT COUNT(*) FROM {0} WHERE ModelID = ?", CacheTableName), 1);
+            
+            if(Persistent) {
+                FindOrCreateCacheModelId(TableName);
+            } else {
+                uid = DatabaseCacheTable.ModelCount++;
+            }
+                
+            select_range_command = new HyenaDbCommand(Where.Length > 0
+                ? String.Format(@"
+                                SELECT {0} FROM {1}
+                                    INNER JOIN {2}
+                                        ON {3} = {2}.ItemID
+                                    WHERE
+                                        {2}.ModelID = ? AND
+                                        {4}
+                                    LIMIT ?, ?", Select, From, CacheTableName, PrimaryKey, Where)
+                : String.Format(@"
+                                SELECT {0} FROM {1}
+                                    INNER JOIN {2}
+                                        ON {3} = {2}.ItemID
+                                    WHERE
+                                        {2}.ModelID = ?
+                                    LIMIT ?, ?", Select, From, CacheTableName, PrimaryKey), 3);
+            
+            reload_sql = String.Format(@"
+                DELETE FROM {0} WHERE ModelID = {1};
+                    INSERT INTO {0} SELECT null, {1}, {2} ", CacheTableName, uid, PrimaryKey);
+            
+            if (!cache_initialized && !Persistent) {
+                // Invalidate any old cache
+                connection.Execute(String.Format("DELETE FROM {0}", CacheTableName));
+            }
+            cache_initialized = true;
+        }
+        
+        private void CheckCacheTable()
+        {
+            using(IDataReader reader = connection.ExecuteReader(GetSchemaSql(CacheTableName))) {
+                if(!reader.Read()) {
+                    connection.Execute(String.Format(@"
+                        CREATE TABLE {0} (
+                            OrderID INTEGER PRIMARY KEY,
+                            ModelID INTEGER,
+                            ItemID INTEGER)", CacheTableName));
+                }
+            }
+        }
+        
+        protected override void PrepareSelectRangeCommand(int offset, int limit)
+        {
+            SelectRangeCommand.ApplyValues(uid, offset, limit);
+        }
+        
+        protected override HyenaDbCommand SelectRangeCommand {
+            get { return select_range_command; }
+        }
+            
+        protected virtual Dictionary<int, T> Cache {
+            get { return cache.CacheImpl; }
+        }
+        
+        public virtual T GetValue(int index)
+        {
+            return cache.GetValueImpl(index);
+        }
+        
+        public virtual void Clear()
+        {
+            cache.ClearImpl();
+        }
+        
+        protected void InvalidateManagedCache()
+        {
+            cache.InvalidateManagedCacheImpl();
+        }
+        
+        protected virtual void Add(int key, T value)
+        {
+            cache.AddImpl(key, value);
+        }
+        
+        protected virtual void FetchSet(int offset, int limit)
+        {
+            int i = offset;
+            foreach(T item in FetchRange(offset, limit)) {
+                if(!Cache.ContainsKey(i)) {
+                    Add(i, item);
+                }
+                i++;
+            }
+        }
+        
+        public virtual int Reload()
+        {
+            InvalidateManagedCache ();
+            connection.Execute(reload_sql + ReloadFragment);
+            UpdateCount();
+            return rows;
+        }
+        
+        protected void UpdateCount()
+        {
+            //using (new Timer (String.Format ("Counting items for {0}", db_model))) {
+                rows = Convert.ToInt32(connection.ExecuteScalar(count_command.ApplyValues(uid)));
+            //}
+        }
+        
+        private void FindOrCreateCacheModelId(string id)
+        {
+            using(IDataReader reader = connection.ExecuteReader(GetSchemaSql(CacheModelsTableName))) {
+                if(reader.Read()) {
+                    uid = connection.QueryInt32(String.Format(
+                        "SELECT CacheID FROM {0} WHERE ModelID = '{1}'",
+                        CacheModelsTableName, id
+                    ));
+                    if(uid == 0) {
+                        //Console.WriteLine ("Didn't find existing cache for {0}, creating", id);
+                        uid = connection.Execute(new HyenaDbCommand(String.Format(
+                            "INSERT INTO {0} (ModelID) VALUES (?)",
+                            CacheModelsTableName),
+                            id
+                        ));
+                    } else {
+                        //Console.WriteLine ("Found existing cache for {0}: {1}", id, uid);
+                        warm = true;
+                        InvalidateManagedCache();
+                        UpdateCount();
+                    }
+                }
+                else {
+                    connection.Execute(String.Format(
+                        "CREATE TABLE {0} (CacheID INTEGER PRIMARY KEY, ModelID TEXT UNIQUE)",
+                        CacheModelsTableName));
+                    uid = connection.Execute(new HyenaDbCommand(String.Format(
+                        "INSERT INTO {0} (ModelID) VALUES (?)",
+                        CacheModelsTableName),
+                        id
+                    ));
+                }
+            }
+        }
+        
+        public bool Warm {
+            //get { return warm; }
+            get { return warm; }
+        }
+
+        public int Count {
+            get { return rows; }
+        }
+        
+        public int CacheId {
+            get { return uid; }
+        }
+        
+        public static implicit operator CacheableModelAdapter<T> (CacheableDatabaseModelProvider<T> cdmp)
+        {
+            return cdmp.cache;
+        }
+    }
+}
\ No newline at end of file

Modified: trunk/banshee/src/Core/Hyena/Hyena.Data/CacheableModelAdapter.cs
==============================================================================
--- trunk/banshee/src/Core/Hyena/Hyena.Data/CacheableModelAdapter.cs	(original)
+++ trunk/banshee/src/Core/Hyena/Hyena.Data/CacheableModelAdapter.cs	Tue Jan 15 18:38:49 2008
@@ -57,6 +57,11 @@
             
             return default (T);
         }
+        
+        protected virtual void Add(int key, T value)
+        {
+            Cache.Add(key, value);
+        }
 
         // Responsible for fetching a set of items and placing them in the cache
         protected abstract void FetchSet (int offset, int limit);

Added: trunk/banshee/src/Core/Hyena/Hyena.Data/DatabaseModelProvider.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Core/Hyena/Hyena.Data/DatabaseModelProvider.cs	Tue Jan 15 18:38:49 2008
@@ -0,0 +1,818 @@
+//
+// DatabaseCollection.cs
+//
+// Author:
+//   Scott Peterson  <lunchtimemama gmail com>
+//
+// Copyright (C) 2007 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.Collections.Generic;
+using System.Data;
+using System.Reflection;
+using System.Text;
+
+namespace Hyena.Data
+{
+    [Flags]
+    public enum DatabaseBindingFlags
+    {
+        NotNull = 1,
+        PrimaryKey = 2,
+        Unique = 4
+    }
+    
+    public abstract class DatabaseColumnBaseAttribute : Attribute
+    {
+        private string column_name;
+        
+        public DatabaseColumnBaseAttribute()
+        {
+        }
+        
+        public DatabaseColumnBaseAttribute(string column_name)
+        {
+            this.column_name = column_name;
+        }
+        
+        public string ColumnName {
+            get { return column_name; }
+        }
+    }
+    
+    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
+    public sealed class DatabaseColumnAttribute : DatabaseColumnBaseAttribute
+    {
+        private DatabaseBindingFlags binding_flags;
+        private string default_value;
+        private string index;
+        
+        public DatabaseColumnAttribute()
+        {
+        }
+        
+        public DatabaseColumnAttribute(string column_name)
+            : base(column_name)
+        {
+        }
+        
+        public DatabaseBindingFlags BindingFlags {
+            get { return binding_flags; }
+            set { binding_flags = value; }
+        }
+        
+        public string DefaultValue {
+            get { return default_value; }
+            set { default_value = value; }
+        }
+        
+        public string Index {
+            get { return index; }
+            set { index = value; }
+        }
+    }
+    
+    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
+    public sealed class DatabaseVirtualColumnAttribute : DatabaseColumnBaseAttribute
+    {
+        private string target_table;
+        private string target_column;
+        private string local_key;
+        private string foreign_key;
+        
+        public DatabaseVirtualColumnAttribute(string column_name, string target_table, string local_key, string foreign_key)
+            : base(column_name)
+        {
+            this.target_table = target_table;
+            this.local_key = local_key;
+            this.foreign_key = foreign_key;
+        }
+        
+        public string TargetTable {
+            get { return target_table; }
+        }
+        
+        public string LocalKey {
+            get { return local_key; }
+        }
+        
+        public string ForeignKey {
+            get { return foreign_key; }
+        }
+    }
+    
+    public abstract class DatabaseModelProvider<T>
+    {
+        private abstract class ColumnBase
+        {
+            private readonly DatabaseColumnBaseAttribute attribute;
+            private readonly FieldInfo field_info;
+            private readonly PropertyInfo property_info;
+            private readonly Type type;
+            private readonly string column_type;
+            private readonly string name;
+            
+            public ColumnBase(FieldInfo field_info, DatabaseColumnBaseAttribute attribute)
+                : this(attribute, field_info, field_info.FieldType)
+            {
+                this.field_info = field_info;
+            }
+            
+            public ColumnBase(PropertyInfo property_info, DatabaseColumnBaseAttribute attribute) :
+                this(attribute, property_info, property_info.PropertyType)
+            {
+                if(!property_info.CanRead || !property_info.CanWrite) {
+                    throw new Exception(String.Format("{0}: The property {1} must have both a get and a set " +
+                                                      "block in order to be bound to a database column.",
+                                                      property_info.DeclaringType,
+                                                      property_info.Name));
+                }
+                this.property_info = property_info;
+            }
+            
+            private ColumnBase(DatabaseColumnBaseAttribute attribute, MemberInfo member_info, Type type)
+            {
+                if(type.Equals(typeof(string))) {
+                    column_type = "TEXT";
+                } else if(type.Equals(typeof(int)) || type.Equals(typeof(long))) {
+                    column_type = "INTEGER";
+                } else {
+                    throw new Exception(String.Format("{0}.{1}: The type {2} cannot be bound to a database column.",
+                                                      member_info.DeclaringType,
+                                                      member_info.Name,
+                                                      type.FullName));
+                }
+                this.attribute = attribute;
+                this.name = attribute.ColumnName ?? member_info.Name;
+                this.type = type;
+            }
+            
+            public object GetValue(T target)
+            {
+                object result;
+                if(field_info != null) {
+                    result = field_info.GetValue(target);
+                } else {
+                    result = property_info.GetGetMethod(true).Invoke(target, null);
+                }
+                return GetValue(type, result);
+            }
+            
+            protected virtual object GetValue(Type type, object value)
+            {
+                return value;
+            }
+            
+            public void SetValue(T target, IDataReader reader, int column)
+            {
+                if(field_info != null) {
+                    field_info.SetValue(target, SetValue(type, reader, column));
+                } else {
+                    property_info.GetSetMethod(true).Invoke(target, new object[] { SetValue(type, reader, column) });
+                }
+            }
+            
+            protected virtual object SetValue(Type type, IDataReader reader, int column)
+            {
+                // FIXME should we insist on nullable types?
+                object result;
+                if(type.Equals(typeof(string))) {
+                    result = !reader.IsDBNull(column)
+                        ? String.Intern(reader.GetString(column))
+                        : null;
+                } else if(type.Equals(typeof(int))) {
+                    result = !reader.IsDBNull(column)
+                        ? reader.GetInt32(column)
+                        : 0;
+                } else {
+                    result = !reader.IsDBNull(column)
+                        ? reader.GetInt64(column)
+                        : 0;
+                }
+                return result;
+            }
+            
+            public string Name {
+                get { return name; }
+            }
+            
+            public string Type {
+                get { return column_type; }
+            }
+        }
+        
+        private sealed class Column : ColumnBase
+        {
+            private DatabaseColumnAttribute attribute;
+            
+            public Column(FieldInfo field_info, DatabaseColumnAttribute attribute)
+                : base(field_info, attribute)
+            {
+                this.attribute = attribute;
+            }
+            
+            public Column(PropertyInfo property_info, DatabaseColumnAttribute attribute)
+                : base(property_info, attribute)
+            {
+                this.attribute = attribute;
+            }
+            
+            public DatabaseBindingFlags BindingFlags {
+                get { return attribute.BindingFlags; }
+            }
+            
+            public string DefaultValue {
+                get { return attribute.DefaultValue; }
+            }
+            
+            public string Index {
+                get { return attribute.Index; }
+            }
+            
+            public string Schema {
+                get {
+                    StringBuilder builder = new StringBuilder();
+                    builder.Append(Name);
+                    builder.Append(' ');
+                    builder.Append(Type);
+                    if((attribute.BindingFlags & DatabaseBindingFlags.NotNull) > 0) {
+                        builder.Append(" NOT NULL");
+                    }
+                    if((attribute.BindingFlags & DatabaseBindingFlags.Unique) > 0) {
+                        builder.Append(" UNIQUE");
+                    }
+                    if((attribute.BindingFlags & DatabaseBindingFlags.PrimaryKey) > 0) {
+                        builder.Append(" PRIMARY KEY");
+                    }
+                    if(attribute.DefaultValue != null) {
+                        builder.Append(" DEFAULT ");
+                        builder.Append(attribute.DefaultValue);
+                    }
+                    return builder.ToString();
+                }
+            }
+            
+            public override bool Equals(object o)
+            {
+                Column column = o as Column;
+                return o != null && column.Name.Equals(Name);
+            }
+            
+            public override int GetHashCode()
+            {
+                return Name.GetHashCode();
+            }
+        }
+        
+        private sealed class VirtualColumn : ColumnBase
+        {
+            private DatabaseVirtualColumnAttribute attribute;
+            
+            public VirtualColumn(FieldInfo field_info, DatabaseVirtualColumnAttribute attribute)
+                : base(field_info, attribute)
+            {
+                this.attribute = attribute;
+            }
+            
+            public VirtualColumn(PropertyInfo property_info, DatabaseVirtualColumnAttribute attribute)
+                : base(property_info, attribute)
+            {
+                this.attribute = attribute;
+            }
+            
+            public string TargetTable {
+                get { return attribute.TargetTable; }
+            }
+            
+            public string LocalKey {
+                get { return attribute.LocalKey; }
+            }
+            
+            public string ForeignKey {
+                get { return attribute.ForeignKey; }
+            }
+        }
+        
+        private readonly List<Column> columns = new List<Column>();
+        private readonly List<VirtualColumn> virtual_columns = new List<VirtualColumn>();
+        
+        private Column key;
+        private HyenaDbConnection connection;
+        
+        private HyenaDbCommand create_command;
+        private HyenaDbCommand insert_command;
+        private HyenaDbCommand update_command;
+        private HyenaDbCommand select_command;
+        private HyenaDbCommand select_range_command;
+        private HyenaDbCommand select_single_command;
+        
+        private string primary_key;
+        private string select;
+        private string from;
+        private string where;
+        
+        private const string HYENA_DATABASE_NAME = "hyena_database_master";
+
+        protected abstract string TableName { get; }
+        protected abstract int ModelVersion { get; }
+        protected abstract int DatabaseVersion { get; }
+        protected abstract void MigrateTable(int old_version);
+        protected abstract void MigrateDatabase(int old_version);
+        protected abstract T MakeNewObject(int offset);
+        
+        protected virtual string HyenaTableName {
+            get { return "HyenaModelVersions"; }
+        }
+        
+        protected HyenaDbConnection Connection {
+            get { return connection; }
+        }
+        
+        protected DatabaseModelProvider(HyenaDbConnection connection)
+        {
+            foreach(FieldInfo field in typeof(T).GetFields(BindingFlags.Instance | BindingFlags.NonPublic)) {
+                foreach(Attribute attribute in field.GetCustomAttributes(true)) {
+                    AddColumn(field, attribute);
+                }
+            }
+            foreach(PropertyInfo property in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)) {
+                foreach(Attribute attribute in property.GetCustomAttributes(true)) {
+                    AddColumn(property, attribute);
+                }
+            }
+            foreach(PropertyInfo property in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.NonPublic)) {
+                foreach(Attribute attribute in property.GetCustomAttributes(true)) {
+                    AddColumn(property, attribute);
+                }
+            }
+            
+            if(key == null) {
+                throw new Exception(String.Format("The {0} table does not have a primary key", TableName));
+            }
+            
+            this.connection = connection;
+            
+            CheckVersion();
+            CheckTable();
+        }
+        
+        private void CheckTable()
+        {
+            using(IDataReader reader = connection.ExecuteReader(GetSchemaSql(TableName))) {
+                if(reader.Read()) {
+                    Dictionary<string, string> schema = GetSchema(reader);
+                    foreach(Column column in columns) {
+                        if(!schema.ContainsKey(column.Name)) {
+                            connection.Execute(String.Format(
+                                "ALTER TABLE {0} ADD {1}", TableName, column.Schema));
+                        }
+                        if(column.Index != null) {
+                            using(IDataReader index_reader = connection.ExecuteReader(GetSchemaSql(column.Index))) {
+                                if(!index_reader.Read()) {
+                                    connection.Execute(String.Format(
+                                        "CREATE INDEX {0} ON {1}({2})", column.Index, TableName, column.Name));
+                                }
+                            }
+                        }
+                    }
+                } else {
+                    CreateTable();
+                }
+            }
+        }
+        
+        private static Dictionary<string, string> GetSchema(IDataReader reader)
+        {
+            Dictionary<string, string> schema = new Dictionary<string, string>();
+            string sql = reader.GetString(0);
+            sql = sql.Substring(sql.IndexOf('(') + 1);
+            foreach (string column_def in sql.Split (',')) {
+                string column_def_t = column_def.Trim();
+                int ws_index = column_def_t.IndexOfAny(new char [] { ' ', '\t', '\n', '\r' });
+                schema.Add(column_def_t.Substring (0, ws_index), null);
+            }
+            return schema;
+        }
+        
+        protected virtual void CheckVersion()
+        {
+            using(IDataReader reader = connection.ExecuteReader(GetSchemaSql(HyenaTableName))) {
+                if(reader.Read()) {
+                    using(IDataReader table_reader = connection.ExecuteReader(String.Format(
+                        "SELECT version FROM {0} WHERE name = '{1}'", HyenaTableName, TableName))) {
+                        if(table_reader.Read()) {
+                            int version = table_reader.GetInt32(0);
+                            if(version < ModelVersion) {
+                                MigrateTable(version);
+                                connection.Execute(String.Format(
+                                    "UPDATE {0} SET version = {1} WHERE name = '{3}'",
+                                    HyenaTableName, ModelVersion, TableName));
+                            }
+                        } else {
+                            connection.Execute(String.Format(
+                                "INSERT INTO {0} (name, version) VALUES ('{1}', {2})",
+                                HyenaTableName, TableName, ModelVersion));
+                        }
+                    }
+                    using(IDataReader db_reader = connection.ExecuteReader(String.Format(
+                        "SELECT version FROM {0} WHERE name = '{1}'", HyenaTableName, HYENA_DATABASE_NAME))) {
+                        db_reader.Read();
+                        int version = db_reader.GetInt32(0);
+                        if(version < DatabaseVersion) {
+                            MigrateDatabase(version);
+                            connection.Execute(String.Format(
+                                "UPDATE {0} SET version = {1} WHERE name = '{2}'",
+                                HyenaTableName, ModelVersion, HYENA_DATABASE_NAME));
+                        }
+                    }
+                }
+                else {
+                    connection.Execute(String.Format(
+                        "CREATE TABLE {0} (id INTEGER PRIMARY KEY, name TEXT UNIQUE, version INTEGER)", HyenaTableName));
+                    connection.Execute(String.Format(
+                        "INSERT INTO {0} (name, version) VALUES ('{1}', {2})",
+                        HyenaTableName, HYENA_DATABASE_NAME, DatabaseVersion));
+                    connection.Execute(String.Format(
+                        "INSERT INTO {0} (name, version) VALUES ('{1}', {2})",
+                        HyenaTableName, TableName, ModelVersion));
+                }
+            }
+        }
+        
+        protected static string GetSchemaSql(string table_name)
+        {
+            return String.Format("SELECT sql FROM sqlite_master WHERE name = '{0}'", table_name);
+        }
+        
+        private void AddColumn(MemberInfo member, Attribute attribute)
+        {
+            DatabaseColumnAttribute column = attribute as DatabaseColumnAttribute;
+            if(column != null) {
+                Column c = member is FieldInfo
+                    ? new Column((FieldInfo)member, column)
+                    : new Column((PropertyInfo)member, column);
+                
+                foreach(Column col in columns) {
+                    if(col.Name == c.Name) {
+                        throw new Exception(String.Format("{0} has multiple columns named {1}", TableName, c.Name));
+                    }
+                    if(col.Index != null && col.Index == c.Index) {
+                        throw new Exception(String.Format("{0} has multiple indecies named {1}", TableName, c.Name));
+                    }
+                }
+                
+                columns.Add(c);
+                
+                if((c.BindingFlags & DatabaseBindingFlags.PrimaryKey) > 0) {
+                    if(key != null) {
+                        throw new Exception(String.Format("Multiple primary keys in the {0} table", TableName));
+                    }
+                    key = c;
+                }
+            }
+            DatabaseVirtualColumnAttribute virtual_column = attribute as DatabaseVirtualColumnAttribute;
+            if(virtual_column != null) {
+                if(member is FieldInfo) {
+                    virtual_columns.Add(new VirtualColumn((FieldInfo)member, virtual_column));
+                } else {
+                    virtual_columns.Add(new VirtualColumn((PropertyInfo)member, virtual_column));
+                }
+            }
+        }
+        
+        protected virtual void CreateTable()
+        {
+            connection.Execute(CreateCommand);
+            foreach(Column column in columns) {
+                if(column.Index != null) {
+                    connection.Execute(String.Format(
+                        "CREATE INDEX {0} ON {1}({2})", column.Index, TableName, column.Name));
+                }
+            }
+        }
+        
+        protected virtual void PrepareInsertCommand(T target)
+        {
+            for(int i = 0; i < columns.Count; i++) {
+                InsertCommand.Parameters[i].Value = columns[i].GetValue(target);
+            }
+        }
+        
+        public int Insert(T target)
+        {
+            PrepareInsertCommand(target);
+            return connection.Execute(InsertCommand);
+        }
+
+        protected virtual void PrepareUpdateCommand(T target)
+        {
+            for(int i = 0; i < columns.Count; i++) {
+                UpdateCommand.Parameters[i].Value = columns[i].GetValue(target);
+            }
+            UpdateCommand.Parameters[columns.Count].Value = key.GetValue(target);
+        }
+        
+        public void Update(T target)
+        {
+            PrepareUpdateCommand(target);
+            connection.Execute(UpdateCommand);
+        }
+        
+        public void Load(T target, IDataReader reader)
+        {
+            int i = 0;
+            
+            foreach(Column column in columns) {
+                column.SetValue(target, reader, i++);
+            }
+            
+            foreach(VirtualColumn column in virtual_columns) {
+                column.SetValue(target, reader, i++);
+            }
+        }
+        
+        protected virtual void PrepareSelectCommand()
+        {
+        }
+        
+        public IEnumerable<T> FetchAll()
+        {
+            PrepareSelectCommand();
+            int i = 1;
+            using(IDataReader reader = connection.ExecuteReader(SelectCommand)) {
+                while(reader.Read()) {
+                    T new_object = MakeNewObject(i);
+                    Load(new_object, reader);
+                    yield return new_object;
+                }
+            }
+        }
+        
+        protected virtual void PrepareSelectRangeCommand(int offset, int limit)
+        {
+            SelectRangeCommand.ApplyValues(offset, limit);
+        }
+        
+        public IEnumerable<T> FetchRange(int offset, int limit)
+        {
+            PrepareSelectRangeCommand(offset, limit);
+            using(IDataReader reader = connection.ExecuteReader(SelectRangeCommand)) {
+                while(reader.Read()) {
+                    T new_object = MakeNewObject(offset++);
+                    Load(new_object, reader);
+                    yield return new_object;
+                }
+            }
+        }
+        
+        protected virtual void PrepareSelectSingleCommand(object id)
+        {
+            SelectSingleCommand.ApplyValues(id);
+        }
+        
+        public T FetchSingle(int id)
+        {
+            PrepareSelectSingleCommand(id);
+            using(IDataReader reader = connection.ExecuteReader(SelectSingleCommand)) {
+                if(reader.Read()) {
+                    T new_object = MakeNewObject(id);
+                    Load(new_object, reader);
+                    return new_object;
+                }
+            }
+            return default(T);
+        }
+        
+        protected virtual HyenaDbCommand CreateCommand {
+            get {
+                if(create_command == null) {
+                    StringBuilder builder = new StringBuilder();
+                    builder.Append("CREATE TABLE ");
+                    builder.Append(TableName);
+                    builder.Append('(');
+                    bool first = true;
+                    foreach(Column column in columns) {
+                        if(first) {
+                            first = false;
+                        } else {
+                            builder.Append(',');
+                        }
+                        builder.Append(column.Schema);
+                    }
+                    builder.Append(')');
+                    create_command = new HyenaDbCommand(builder.ToString());
+                }
+                return create_command;
+            }
+        }
+        
+        protected virtual HyenaDbCommand InsertCommand {
+            get {
+                // FIXME can this string building be done more nicely?
+                if(insert_command == null) {
+                    StringBuilder cols = new StringBuilder ();
+                    StringBuilder vals = new StringBuilder ();
+                    int count = 0;
+                    bool first = true;
+                    foreach(Column column in columns) {
+                        if(first) {
+                            first = false;
+                        } else {
+                            cols.Append(',');
+                            vals.Append(',');
+                        }
+                        cols.Append(column.Name);
+                        vals.Append('?');
+                        count++;
+                    }
+
+                    insert_command = new HyenaDbCommand(String.Format(
+                            "INSERT INTO {0} ({1}) VALUES ({2})",
+                            TableName, cols.ToString(), vals.ToString()), count);
+                }
+                return insert_command;
+            }
+        }
+        
+        protected virtual HyenaDbCommand UpdateCommand {
+            get {
+                if(update_command == null) {
+                    StringBuilder builder = new StringBuilder();
+                    builder.Append("UPDATE ");
+                    builder.Append(TableName);
+                    builder.Append(" SET ");
+                    int count = 0;
+                    bool first = true;
+                    foreach(Column column in columns) {
+                        if(first) {
+                            first = false;
+                        } else {
+                            builder.Append(',');
+                        }
+                        builder.Append(column.Name);
+                        builder.Append(" = ?");
+                        count++;
+                    }
+                    builder.Append(" WHERE ");
+                    builder.Append(key.Name);
+                    builder.Append(" = ?");
+                    count++;
+                    update_command = new HyenaDbCommand(builder.ToString(), count);
+                }
+                return update_command;
+            }
+        }
+        
+        protected virtual HyenaDbCommand SelectCommand {
+            get {
+                if(select_command == null) {
+                    select_command = new HyenaDbCommand(Where.Length > 0
+                        ? String.Format("SELECT {0} FROM {1} WHERE {2}", Select, From, Where)
+                        : String.Format("SELECT {0} FROM {1}", Select, From));
+                }
+                return select_command;
+            }
+        }
+        
+        protected virtual HyenaDbCommand SelectRangeCommand {
+            get {
+                if(select_range_command == null) {
+                    select_range_command = new HyenaDbCommand(Where.Length > 0
+                        ? String.Format("SELECT {0} FROM {1} WHERE {2} LIMIT ?, ?", Select, From, Where)
+                        : String.Format("SELECT {0} FROM {1} LIMIT ?, ?", Select, From), 2);
+                }
+                return select_range_command;
+            }
+        }
+        
+        protected virtual HyenaDbCommand SelectSingleCommand {
+            get {
+                if(select_single_command == null) {
+                    select_single_command = new HyenaDbCommand(Where.Length > 0
+                        ? String.Format("SELECT {0} FROM {1} WHERE {2} AND {3} = ?", Select, From, Where, PrimaryKey)
+                        : String.Format("SELECT {0} FROM {1} WHERE {2} = ?", Select, From, PrimaryKey), 1);
+                }
+                return select_single_command;
+            }
+        }
+        
+        protected virtual string Select {
+            get {
+                if(select == null) {
+                    BuildQuerySql();
+                }
+                return select;
+            }
+        }
+        
+        protected virtual string From {
+            get {
+                if(from == null) {
+                    BuildQuerySql();
+                }
+                return from;
+            }
+        }
+        
+        protected virtual string Where {
+            get {
+                if(where == null) {
+                    BuildQuerySql();
+                }
+                return where;
+            }
+        }
+        
+        protected string PrimaryKey {
+            get {
+                if(primary_key == null) {
+                    primary_key = String.Format("{0}.{1}", TableName, key.Name);
+                }
+                return primary_key;
+            }
+        }
+        
+        private void BuildQuerySql()
+        {
+            StringBuilder select_builder = new StringBuilder();
+            bool first = true;
+            foreach(Column column in columns) {
+                if(first) {
+                    first = false;
+                } else {
+                    select_builder.Append(',');
+                }
+                select_builder.Append(TableName);
+                select_builder.Append('.');
+                select_builder.Append(column.Name);
+            }
+            
+            StringBuilder where_builder = new StringBuilder();
+            Dictionary<string, string> tables = new Dictionary<string,string>(virtual_columns.Count + 1);
+            tables.Add(TableName, null);
+            bool first_virtual = true;
+            foreach(VirtualColumn column in virtual_columns) {
+                if(first_virtual) {
+                    first_virtual = false;
+                } else {
+                    where_builder.Append(" AND ");
+                }
+                if(first) {
+                    first = false;
+                } else {
+                    select_builder.Append(',');
+                }
+                select_builder.Append(column.TargetTable);
+                select_builder.Append('.');
+                select_builder.Append(column.Name);
+                
+                where_builder.Append(column.TargetTable);
+                where_builder.Append('.');
+                where_builder.Append(column.ForeignKey);
+                where_builder.Append(" = ");
+                where_builder.Append(TableName);
+                where_builder.Append('.');
+                where_builder.Append(column.LocalKey);
+                
+                if(!tables.ContainsKey(column.TargetTable)) {
+                    tables.Add(column.TargetTable, null);
+                }
+            }
+            
+            StringBuilder from_builder = new StringBuilder();
+            bool first_tables = true;
+            foreach(KeyValuePair<string, string> pair in tables) {
+                if(first_tables) {
+                    first_tables = false;
+                } else {
+                    from_builder.Append(',');
+                }
+                from_builder.Append(pair.Key);
+            }
+
+            select = select_builder.ToString();
+            from = from_builder.ToString();
+            where = where_builder.ToString();
+        }
+	}
+}

Added: trunk/banshee/src/Core/Hyena/Hyena.Data/HyenaDbConnection.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Core/Hyena/Hyena.Data/HyenaDbConnection.cs	Tue Jan 15 18:38:49 2008
@@ -0,0 +1,253 @@
+//
+// BansheeDbConnection.cs
+//
+// Author:
+//   Aaron Bockover <abockover novell com>
+//
+// Copyright (C) 2007 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.IO;
+using System.Data;
+using Mono.Data.Sqlite;
+
+namespace Hyena.Data
+{
+    public abstract class HyenaDbConnection : IDisposable
+    {
+        private SqliteConnection connection;
+
+        public HyenaDbConnection() : this(true)
+        {
+        }
+
+        public HyenaDbConnection(bool connect)
+        {
+            if (connect) {
+                Open ();
+            }
+        }
+
+        public void Dispose ()
+        {
+            Close ();
+        }
+
+        public void Open ()
+        {
+            lock (this) {
+                if (connection != null) {
+                    return;
+                }
+
+                string dbfile = DatabaseFile;
+                connection = new SqliteConnection (String.Format ("Version=3,URI=file:{0}", dbfile));
+                connection.Open ();
+
+                Execute (@"
+                    PRAGMA synchronous = OFF;
+                    PRAGMA cache_size = 32768;
+                ");
+            }
+        }
+
+        public void Close ()
+        {
+            lock (this) {
+                if (connection != null) {
+                    connection.Close ();
+                    connection = null;
+                }
+            }
+        }
+
+#region Convenience methods 
+
+        public IDataReader ExecuteReader (SqliteCommand command)
+        {
+            if (command.Connection == null)
+                command.Connection = connection;
+            return command.ExecuteReader ();
+        }
+
+        public IDataReader ExecuteReader (HyenaDbCommand command)
+        {
+            return ExecuteReader (command.Command);
+        }
+
+        public IDataReader ExecuteReader (object command)
+        {
+            return ExecuteReader (new SqliteCommand (command.ToString ()));
+        }
+
+        public object ExecuteScalar (SqliteCommand command)
+        {
+            if (command.Connection == null)
+                command.Connection = connection;
+            return command.ExecuteScalar ();
+        }
+
+        public object ExecuteScalar (HyenaDbCommand command)
+        {
+            return ExecuteScalar (command.Command);
+        }
+
+        public object ExecuteScalar (object command)
+        {
+            return ExecuteScalar (new SqliteCommand (command.ToString ()));
+        }
+
+        public Int32 QueryInt32 (object command)
+        {
+            return Convert.ToInt32 (ExecuteScalar (command));
+        }
+
+        public int Execute (SqliteCommand command)
+        {
+            if (command.Connection == null)
+                command.Connection = connection;
+            command.ExecuteNonQuery ();
+            return command.LastInsertRowID ();
+        }
+
+        public int Execute (HyenaDbCommand command)
+        {
+            return Execute (command.Command);
+        }
+
+        public int Execute (object command)
+        {
+            return Execute (new SqliteCommand (command.ToString ()));
+        }
+
+#endregion
+
+        public abstract string DatabaseFile { get; }
+
+        public IDbConnection Connection {
+            get { return connection; }
+        }
+    }
+    
+    public class HyenaDbCommand
+    {
+        private SqliteCommand command;
+
+#region Properties
+
+        public SqliteCommand Command {
+            get { return command; }
+        }
+
+        public SqliteParameterCollection Parameters {
+            get { return command.Parameters; }
+        }
+
+        public string CommandText {
+            get { return command.CommandText; }
+        }
+
+#endregion
+
+        public HyenaDbCommand(string command)
+        {
+            this.command = new SqliteCommand (command);
+        }
+
+        public HyenaDbCommand (string command, int num_params) : this (command)
+        {
+            for (int i = 0; i < num_params; i++) {
+                Parameters.Add (new SqliteParameter ());
+            }
+        }
+
+        public HyenaDbCommand (string command, params object [] param_values) : this (command, param_values.Length)
+        {
+            ApplyValues (param_values);
+        }
+
+        public HyenaDbCommand ApplyValues (params object [] param_values)
+        {
+            if (param_values.Length != Parameters.Count) {
+                throw new ArgumentException (String.Format (
+                    "Command has {0} parameters, but {1} values given.", Parameters.Count, param_values.Length
+                ));
+            }
+
+            for (int i = 0; i < param_values.Length; i++) {
+                Parameters[i].Value = param_values[i];
+            }
+
+            return this;
+        }
+        
+        public void AddNamedParameter (string name, object value)
+        {
+            SqliteParameter param = new SqliteParameter (name, DbType.String);
+            param.Value = value;
+            Parameters.Add (param);
+        }
+                
+        /*public DbCommand(string command, params object [] parameters) : this(command)
+        {
+            for(int i = 0; i < parameters.Length;) {
+                SqliteParameter param;
+                
+                if(parameters[i] is SqliteParameter) {
+                    param = (SqliteParameter)parameters[i];
+                    if(i < parameters.Length - 1 && !(parameters[i + 1] is SqliteParameter)) {
+                        param.Value = parameters[i + 1];
+                        i += 2;
+                    } else {
+                        i++;
+                    }
+                } else {
+                    param = new SqliteParameter();
+                    param.ParameterName = (string)parameters[i];
+                    param.Value = parameters[i + 1];
+                    i += 2;
+                }
+                
+                Parameters.Add(param);
+            }
+        }
+        
+        public void AddParameter (object value)
+        {
+            SqliteParameter param = new SqliteParameter ();
+            param.Value = value;
+            Parameters.Add (param);
+        }
+        
+        public void AddParameter<T>(string name, T value)
+        {
+            AddParameter<T>(new DbParameter<T>(name), value);
+        }
+        
+        public void AddParameter<T>(DbParameter<T> param, T value)
+        {
+            param.Value = value;
+            Parameters.Add(param);
+        }*/
+    }
+}
\ No newline at end of file

Modified: trunk/banshee/src/Core/Hyena/Hyena.mdp
==============================================================================
--- trunk/banshee/src/Core/Hyena/Hyena.mdp	(original)
+++ trunk/banshee/src/Core/Hyena/Hyena.mdp	Tue Jan 15 18:38:49 2008
@@ -40,9 +40,14 @@
     <File name="Hyena.Data.Query/QueryField.cs" subtype="Code" buildaction="Compile" />
     <File name="Hyena.Data.Query/UserQueryParser.cs" subtype="Code" buildaction="Compile" />
     <File name="Hyena.Data.Query/XmlQueryParser.cs" subtype="Code" buildaction="Compile" />
+    <File name="Hyena.Data/HyenaDbConnection.cs" subtype="Code" buildaction="Compile" />
+    <File name="Hyena.Data/DatabaseModelProvider.cs" subtype="Code" buildaction="Compile" />
+    <File name="Hyena.Data/CacheableDatabaseModelProvider.cs" subtype="Code" buildaction="Compile" />
   </Contents>
   <References>
     <ProjectReference type="Gac" localcopy="True" refto="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+    <ProjectReference type="Gac" localcopy="True" refto="Mono.Data.SqliteClient, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756" />
+    <ProjectReference type="Gac" localcopy="True" refto="System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
   </References>
   <Deployment.LinuxDeployData generateScript="False" />
   <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="True" RelativeMakefileName="Makefile.am">

Modified: trunk/banshee/src/Core/Hyena/Makefile.am
==============================================================================
--- trunk/banshee/src/Core/Hyena/Makefile.am	(original)
+++ trunk/banshee/src/Core/Hyena/Makefile.am	Tue Jan 15 18:38:49 2008
@@ -20,8 +20,11 @@
 	Hyena.Data.Query/QueryToken.cs \
 	Hyena.Data.Query/UserQueryParser.cs \
 	Hyena.Data.Query/XmlQueryParser.cs \
+	Hyena.Data/CacheableDatabaseModelProvider.cs \
 	Hyena.Data/CacheableModelAdapter.cs \
 	Hyena.Data/ColumnDescription.cs \
+	Hyena.Data/DatabaseModelProvider.cs \
+	Hyena.Data/HyenaDbConnection.cs \
 	Hyena.Data/ICacheableModel.cs \
 	Hyena.Data/ICareAboutView.cs \
 	Hyena.Data/IFilterable.cs \



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