using Gnome; using Gdk; using Gtk; using Mono.Data.SqliteClient; using Mono.Unix; using System.Collections; using System.IO; using System.Text; using System; using FSpot; using FSpot.Query; using Banshee.Database; public class PhotoVersion : FSpot.IBrowsableItem { Photo photo; uint version_id; public System.DateTime Time { get { return photo.Time; } } public Tag [] Tags { get { return photo.Tags; } } public Uri DefaultVersionUri { get { return UriList.PathToFileUri (photo.GetVersionPath (version_id));} } public string Description { get { return photo.Description; } } public string Name { get { return photo.GetVersionName (version_id); } } public Photo Photo { get { return photo; } } public uint VersionId { get { return version_id; } } public PhotoVersion (Photo photo, uint version_id) { this.photo = photo; this.version_id = version_id; } } public class Photo : DbItem, IComparable, FSpot.IBrowsableItem { // IComparable public int CompareTo (object obj) { if (this.GetType () == obj.GetType ()) { // FIXME this is way under powered for a real compare in the // equal case but for now it should do. return Compare (this, (Photo)obj); } else if (obj is DateTime) { return this.time.CompareTo ((DateTime)obj); } else { throw new Exception ("Object must be of type Photo"); } } public int CompareTo (Photo photo) { return Compare (this, photo); } public static int Compare (Photo photo1, Photo photo2) { int result = photo1.Id.CompareTo (photo2.Id); if (result == 0) return 0; else result = CompareDate (photo1, photo2); if (result == 0) result = CompareCurrentDir (photo1, photo2); if (result == 0) result = CompareName (photo1, photo2); if (result == 0) result = photo1.Id.CompareTo (photo2.Id); return result; } private static int CompareDate (Photo photo1, Photo photo2) { return DateTime.Compare (photo1.time, photo2.time); } private static int CompareCurrentDir (Photo photo1, Photo photo2) { return string.Compare (photo1.directory_path, photo2.directory_path); } private static int CompareName (Photo photo1, Photo photo2) { return string.Compare (photo1.name, photo2.name); } public class CompareDateName : IComparer { public int Compare (object obj1, object obj2) { Photo p1 = (Photo)obj1; Photo p2 = (Photo)obj2; int result = Photo.CompareDate (p1, p2); if (result == 0) result = CompareName (p1, p2); return result; } } public class CompareDirectory : IComparer { public int Compare (object obj1, object obj2) { Photo p1 = (Photo)obj1; Photo p2 = (Photo)obj2; int result = Photo.CompareCurrentDir (p1, p2); if (result == 0) result = CompareName (p1, p2); return result; } } public class RandomSort : IComparer { Random random = new Random (); public int Compare (object obj1, object obj2) { return random.Next (-5, 5); } } // The time is always in UTC. private DateTime time; public DateTime Time { get { return time; } set { time = value; } } private string directory_path; private string name; private string Path { get { return System.IO.Path.Combine (directory_path, name); } } public string Name { get { return name; } } public string DirectoryPath { get { return directory_path; } set { directory_path = value; } } private ArrayList tags; public Tag [] Tags { get { if (tags == null) return new Tag [0]; else return (Tag []) tags.ToArray (typeof (Tag)); } } private bool loaded = false; public bool Loaded { get { return loaded; } set { loaded = value; } } private string description; public string Description { get { return description; } set { description = value; } } private uint roll_id = 0; public uint RollId { get { return roll_id; } set { roll_id = value; } } // Version management public const int OriginalVersionId = 1; private uint highest_version_id; private Hashtable version_names; public uint [] VersionIds { get { if (version_names == null) return new uint [0]; uint [] ids = new uint [version_names.Count]; uint i = 0; foreach (uint id in version_names.Keys) ids [i ++] = id; Array.Sort (ids); return ids; } } public IBrowsableItem GetVersion (uint version_id) { return new PhotoVersion (this, version_id); } private uint default_version_id = OriginalVersionId; public uint DefaultVersionId { get { return default_version_id; } set { default_version_id = value; } } // This doesn't check if a version of that name already exists, // it's supposed to be used only within the Photo and PhotoStore classes. public void AddVersionUnsafely (uint version_id, string name) { if (version_names == null) version_names = new Hashtable (); version_names [version_id] = name; highest_version_id = Math.Max (version_id, highest_version_id); } private string GetPathForVersionName (string version_name) { string name_without_extension = System.IO.Path.GetFileNameWithoutExtension (name); string extension = System.IO.Path.GetExtension (name); return System.IO.Path.Combine (directory_path, name_without_extension + " (" + version_name + ")" + extension); } public bool VersionNameExists (string version_name) { if (version_names != null) { foreach (string n in version_names.Values) { if (n == version_name) return true; } } return false; } public string GetVersionName (uint version_id) { if (version_names != null) return version_names [version_id] as string; else return null; } public string GetVersionPath (uint version_id) { if (version_id == OriginalVersionId) return Path; else return GetPathForVersionName (GetVersionName (version_id)); } public System.Uri VersionUri (uint version_id) { return UriList.PathToFileUri (GetVersionPath (version_id)); } public System.Uri DefaultVersionUri { get { return VersionUri (DefaultVersionId); } } public void DeleteVersion (uint version_id) { DeleteVersion (version_id, false); } public void DeleteVersion (uint version_id, bool remove_original) { if (version_id == OriginalVersionId && !remove_original) throw new Exception ("Cannot delete original version"); string path = GetVersionPath (version_id); if (File.Exists (path)) File.Delete (path); try { string thumb_path = ThumbnailGenerator.ThumbnailPath (path); File.Delete (thumb_path); } catch (System.Exception) { //ignore an error here we don't really care. } PhotoStore.DeleteThumbnail (path); version_names.Remove (version_id); do { version_id --; if (version_names.Contains (version_id)) { DefaultVersionId = version_id; break; } } while (version_id > OriginalVersionId); } public uint CreateVersion (string name, uint base_version_id, bool create_file) { string new_path = GetPathForVersionName (name); string original_path = GetVersionPath (base_version_id); if (VersionNameExists (name)) throw new Exception ("This version name already exists"); if (File.Exists (new_path)) throw new Exception (String.Format ("A file named {0} already exists", System.IO.Path.GetFileName (new_path))); if (create_file) { Mono.Unix.Native.Stat stat; int stat_err = Mono.Unix.Native.Syscall.stat (original_path, out stat); File.Copy (original_path, new_path); FSpot.ThumbnailGenerator.Create (new_path).Dispose (); if (stat_err == 0) try { Mono.Unix.Native.Syscall.chown(new_path, Mono.Unix.Native.Syscall.getuid (), stat.st_gid); } catch (Exception) {} } if (version_names == null) version_names = new Hashtable (); highest_version_id ++; version_names [highest_version_id] = name; return highest_version_id; } public uint CreateDefaultModifiedVersion (uint base_version_id, bool create_file) { int num = 1; while (true) { string name = Catalog.GetPluralString ("Modified", "Modified ({0})", num); name = String.Format (name, num); if (! VersionNameExists (name)) return CreateVersion (name, base_version_id, create_file); num ++; } } public uint CreateNamedVersion (string name, uint base_version_id, bool create_file) { int num = 1; string final_name; while (true) { final_name = String.Format ( Catalog.GetPluralString ("Modified in {1}", "Modified in {1} ({0})", num), num, name); if (num > 1) final_name = name + String.Format(" ({0})", num); if (! VersionNameExists (final_name)) return CreateVersion (final_name, base_version_id, create_file); num ++; } } public void RenameVersion (uint version_id, string new_name) { if (version_id == OriginalVersionId) throw new Exception ("Cannot rename original version"); if (VersionNameExists (new_name)) throw new Exception ("This name already exists"); string original_name = version_names [version_id] as string; string old_path = GetPathForVersionName (original_name); string new_path = GetPathForVersionName (new_name); if (File.Exists (new_path)) throw new Exception ("File with this name already exists"); File.Move (old_path, new_path); PhotoStore.MoveThumbnail (old_path, new_path); version_names [version_id] = new_name; } // Tag management. // This doesn't check if the tag is already there, use with caution. public void AddTagUnsafely (Tag tag) { if (tags == null) tags = new ArrayList (); tags.Add (tag); } // This on the other hand does, but is O(n) with n being the number of existing tags. public void AddTag (Tag tag) { if (!HasTag (tag)) AddTagUnsafely (tag); } public void AddTag (Tag []taglist) { /* * FIXME need a better naming convention here, perhaps just * plain Add. */ foreach (Tag tag in taglist) AddTag (tag); } public void RemoveTag (Tag tag) { if (HasTag (tag)) tags.Remove (tag); } public void RemoveTag (Tag []taglist) { foreach (Tag tag in taglist) RemoveTag (tag); } public void RemoveCategory (Tag []taglist) { foreach (Tag tag in taglist) { Category cat = tag as Category; if (cat != null) RemoveCategory (cat.Children); RemoveTag (tag); } } public bool HasTag (Tag tag) { if (tags == null) return false; return tags.Contains (tag); } private static FSpot.Xmp.XmpFile UpdateXmp (FSpot.IBrowsableItem item, FSpot.Xmp.XmpFile xmp) { if (xmp == null) xmp = new FSpot.Xmp.XmpFile (); Tag [] tags = item.Tags; string [] names = new string [tags.Length]; for (int i = 0; i < tags.Length; i++) names [i] = tags [i].Name; xmp.Store.Update ("dc:subject", "rdf:Bag", names); xmp.Dump (); return xmp; } public void WriteMetadataToImage () { string path = this.DefaultVersionUri.LocalPath; using (FSpot.ImageFile img = FSpot.ImageFile.Create (DefaultVersionUri)) { if (img is FSpot.JpegFile) { FSpot.JpegFile jimg = img as FSpot.JpegFile; jimg.SetDescription (this.Description); jimg.SetDateTimeOriginal (this.Time.ToLocalTime ()); jimg.SetXmp (UpdateXmp (this, jimg.Header.GetXmp ())); jimg.SaveMetaData (path); } else if (img is FSpot.Png.PngFile) { FSpot.Png.PngFile png = img as FSpot.Png.PngFile; if (img.Description != this.Description) png.SetDescription (this.Description); png.SetXmp (UpdateXmp (this, png.GetXmp ())); png.Save (path); } } } public uint SaveVersion (Gdk.Pixbuf buffer, bool create_version) { uint version = DefaultVersionId; using (ImageFile img = ImageFile.Create (DefaultVersionUri)) { // Always create a version if the source is not a jpeg for now. create_version = create_version || !(img is FSpot.JpegFile); if (buffer == null) throw new ApplicationException ("invalid (null) image"); if (create_version) version = CreateDefaultModifiedVersion (DefaultVersionId, false); try { string version_path = GetVersionPath (version); using (Stream stream = File.OpenWrite (version_path)) { img.Save (buffer, stream); } FSpot.ThumbnailGenerator.Create (version_path).Dispose (); DefaultVersionId = version; } catch (System.Exception e) { System.Console.WriteLine (e); if (create_version) DeleteVersion (version); throw e; } } return version; } // Constructor public Photo (uint id, long unix_time, string directory_path, string name) : base (id) { time = DbUtils.DateTimeFromUnixTime (unix_time); this.directory_path = directory_path; this.name = name; description = String.Empty; // Note that the original version is never stored in the photo_versions table in the // database. AddVersionUnsafely (OriginalVersionId, Catalog.GetString ("Original")); } public Photo (uint id, long unix_time, string path) : this (id, unix_time, System.IO.Path.GetDirectoryName (path), System.IO.Path.GetFileName (path)) { } } public class PhotoStore : DbStore { public int TotalPhotos { get { SqliteDataReader reader = Database.Query("SELECT COUNT(*) FROM photos"); reader.Read (); int total = Convert.ToInt32 (reader [0]); reader.Close (); return total; } } public static ThumbnailFactory ThumbnailFactory = new ThumbnailFactory (ThumbnailSize.Large); // FIXME this is a hack. Since we don't have Gnome.ThumbnailFactory.SaveThumbnail() in // GTK#, and generate them by ourselves directly with Gdk.Pixbuf, we have to make sure here // that the "large" thumbnail directory exists. private static void EnsureThumbnailDirectory () { string large_thumbnail_file_name_template = Thumbnail.PathForUri ("file:///boo", ThumbnailSize.Large); string large_thumbnail_directory_path = System.IO.Path.GetDirectoryName (large_thumbnail_file_name_template); if (! File.Exists (large_thumbnail_directory_path)) Directory.CreateDirectory (large_thumbnail_directory_path); } // // Generates the thumbnail, returns the Pixbuf, and also stores it as a side effect // public static Pixbuf GenerateThumbnail (Uri uri) { using (FSpot.ImageFile img = FSpot.ImageFile.Create (uri)) { return GenerateThumbnail (uri, img); } } public static Pixbuf GenerateThumbnail (Uri uri, ImageFile img) { Pixbuf thumbnail = null; if (img is FSpot.IThumbnailContainer) { try { thumbnail = ((FSpot.IThumbnailContainer)img).GetEmbeddedThumbnail (); } catch (Exception e) { System.Console.WriteLine (e.ToString ()); } } // Save embedded thumbnails in a silightly invalid way so that we know to regnerate them later. if (thumbnail != null) PixbufUtils.SaveAtomic (thumbnail, FSpot.ThumbnailGenerator.ThumbnailPath (uri), "png", new string [] { null} , new string [] { null}); else thumbnail = FSpot.ThumbnailGenerator.Create (uri); return thumbnail; } public static void DeleteThumbnail (string path) { string uri = UriList.PathToFileUri (path).ToString (); path = Thumbnail.PathForUri (uri, ThumbnailSize.Large); if (File.Exists (path)) File.Delete (path); } public static void MoveThumbnail (string old_path, string new_path) { File.Move (ThumbnailGenerator.ThumbnailPath (UriList.PathToFileUri (old_path)), ThumbnailGenerator.ThumbnailPath(UriList.PathToFileUri (new_path))); } // Constructor public PhotoStore (QueuedSqliteDatabase database, bool is_new) : base (database, false) { EnsureThumbnailDirectory (); if (! is_new) return; Database.ExecuteNonQuery ( "CREATE TABLE photos ( " + " id INTEGER PRIMARY KEY NOT NULL, " + " time INTEGER NOT NULL, " + " directory_path STRING NOT NULL, " + " name STRING NOT NULL, " + " description TEXT NOT NULL, " + " roll_id INTEGER NOT NULL, " + " default_version_id INTEGER NOT NULL " + ")"); Database.ExecuteNonQuery ( "CREATE TABLE photo_tags ( " + " photo_id INTEGER, " + " tag_id INTEGER " + ")"); Database.ExecuteNonQuery ( "CREATE TABLE photo_versions ( " + " photo_id INTEGER, " + " version_id INTEGER, " + " name STRING " + ")"); } public Photo Create (string path, uint roll_id, out Pixbuf thumbnail) { return Create (path, path, roll_id, out thumbnail); } public Photo Create (string newPath, string origPath, uint roll_id, out Pixbuf thumbnail) { Photo photo; using (FSpot.ImageFile img = FSpot.ImageFile.Create (origPath)) { long unix_time = DbUtils.UnixTimeFromDateTime (img.Date); string description = img.Description != null ? img.Description.Split ('\0') [0] : String.Empty; uint id = (uint) Database.Execute (new DbCommand ("INSERT INTO photos (time, " + "directory_path, name, description, roll_id, default_version_id) " + "VALUES (:time, :directory_path, :name, :description, " + ":roll_id, :default_version_id)", "time", unix_time, "directory_path", System.IO.Path.GetDirectoryName (newPath), "name", System.IO.Path.GetFileName (newPath), "description", description, "roll_id", roll_id, "default_version_id", Photo.OriginalVersionId)); photo = new Photo (id, unix_time, newPath); AddToCache (photo); photo.Loaded = true; thumbnail = GenerateThumbnail (UriList.PathToFileUri (newPath), img); EmitAdded (photo); } return photo; } private void GetVersions (Photo photo) { SqliteDataReader reader = Database.Query(new DbCommand("SELECT version_id, name FROM photo_versions WHERE photo_id = :id", photo.Id)); while (reader.Read ()) { uint version_id = Convert.ToUInt32 (reader [0]); string name = reader[1].ToString (); photo.AddVersionUnsafely (version_id, name); } reader.Close(); } private void GetTags (Photo photo) { SqliteDataReader reader = Database.Query(new DbCommand("SELECT tag_id FROM photo_tags WHERE photo_id = :id", photo.Id)); while (reader.Read ()) { uint tag_id = Convert.ToUInt32 (reader [0]); Tag tag = Core.Database.Tags.Get (tag_id) as Tag; photo.AddTagUnsafely (tag); } reader.Close(); } private void GetAllVersions () { SqliteDataReader reader = Database.Query("SELECT photo_id, version_id, name FROM photo_versions"); while (reader.Read ()) { uint id = Convert.ToUInt32 (reader [0]); Photo photo = LookupInCache (id) as Photo; if (photo == null) { //Console.WriteLine ("Photo {0} not found", id); continue; } if (photo.Loaded) { //Console.WriteLine ("Photo {0} already Loaded", photo); continue; } if (reader [1] != null) { uint version_id = Convert.ToUInt32 (reader [1]); string name = reader[2].ToString (); photo.AddVersionUnsafely (version_id, name); } /* string directory_path = null; if (reader [3] != null) directory_path = reader [3].ToString (); System.Console.WriteLine ("directory_path = {0}", directory_path); */ } reader.Close(); } private void GetAllTags () { SqliteDataReader reader = Database.Query("SELECT photo_id, tag_id FROM photo_tags"); while (reader.Read ()) { uint id = Convert.ToUInt32 (reader [0]); Photo photo = LookupInCache (id) as Photo; if (photo == null) { //Console.WriteLine ("Photo {0} not found", id); continue; } if (photo.Loaded) { //Console.WriteLine ("Photo {0} already Loaded", photo.Id); continue; } if (reader [1] != null) { uint tag_id = Convert.ToUInt32 (reader [1]); Tag tag = Core.Database.Tags.Get (tag_id) as Tag; photo.AddTagUnsafely (tag); } } reader.Close(); } private void GetAllData () { SqliteDataReader reader = Database.Query("SELECT photo_tags.photo_id, tag_id, version_id, name " + "FROM photo_tags, photo_versions " + "WHERE photo_tags.photo_id = photo_versions.photo_id"); while (reader.Read ()) { uint id = Convert.ToUInt32 (reader [0]); Photo photo = LookupInCache (id) as Photo; if (photo == null) { //Console.WriteLine ("Photo {0} not found", id); continue; } if (photo.Loaded) { //Console.WriteLine ("Photo {0} already Loaded", photo); continue; } if (reader [1] != null) { uint tag_id = Convert.ToUInt32 (reader [1]); Tag tag = Core.Database.Tags.Get (tag_id) as Tag; photo.AddTagUnsafely (tag); } if (reader [2] != null) { uint version_id = Convert.ToUInt32 (reader [2]); string name = reader[3].ToString (); photo.AddVersionUnsafely (version_id, name); } } reader.Close(); } private void GetData (Photo photo) { SqliteDataReader reader = Database.Query(new DbCommand("SELECT tag_id, version_id, name " + "FROM photo_tags, photo_versions " + "WHERE photo_tags.photo_id = photo_versions.photo_id " + "AND photo_tags.photo_id = :id", "id", photo.Id)); while (reader.Read ()) { if (reader [0] != null) { uint tag_id = Convert.ToUInt32 (reader [0]); Tag tag = Core.Database.Tags.Get (tag_id) as Tag; photo.AddTagUnsafely (tag); } if (reader [1] != null) { uint version_id = Convert.ToUInt32 (reader [1]); string name = reader[2].ToString (); photo.AddVersionUnsafely (version_id, name); } } reader.Close(); } public override DbItem Get (uint id) { Photo photo = LookupInCache (id) as Photo; if (photo != null) return photo; SqliteDataReader reader = Database.Query(new DbCommand("SELECT time, directory_path, name, description, " + "roll_id, default_version_id FROM photos WHERE id = :id", "id", id)); if (reader.Read ()) { photo = new Photo (id, Convert.ToInt64 (reader [0]), reader [1].ToString (), reader [2].ToString ()); photo.Description = reader[3].ToString (); photo.RollId = Convert.ToUInt32 (reader[4]); photo.DefaultVersionId = Convert.ToUInt32 (reader[5]); AddToCache (photo); } reader.Close(); if (photo == null) return null; GetTags (photo); GetVersions (photo); return photo; } public Photo GetByPath (string path) { //FIXME - No cacheing here - probably not a problem since // this is only used for DND Photo photo = null; string directory_path = System.IO.Path.GetDirectoryName (path); string filename = System.IO.Path.GetFileName (path); SqliteDataReader reader = Database.Query(new DbCommand("SELECT id, time, description, roll_id, default_version_id FROM photos " + "WHERE directory_path = :directory_path AND name = :name", "directory_path", directory_path, "name", filename)); if (reader.Read ()) { photo = new Photo (Convert.ToUInt32 (reader [0]), Convert.ToInt64 (reader [1]), directory_path, filename); photo.Description = reader[2].ToString (); photo.RollId = Convert.ToUInt32 (reader[3]); photo.DefaultVersionId = Convert.ToUInt32 (reader[4]); AddToCache (photo); } reader.Close(); if (photo == null) return null; GetTags (photo); GetVersions (photo); return photo; } public void Remove (Tag []tags) { Photo [] photos = Query (tags); foreach (Photo photo in photos) { photo.RemoveCategory (tags); Commit (photo); } foreach (Tag tag in tags) Core.Database.Tags.Remove (tag); } public void Remove (Photo []items) { EmitRemoved (items); StringBuilder query_builder = new StringBuilder (); StringBuilder tv_query_builder = new StringBuilder (); for (int i = 0; i < items.Length; i++) { if (i > 0) { query_builder.Append (" OR "); tv_query_builder.Append (" OR "); } query_builder.Append (String.Format ("id = {0}", items[i].Id)); tv_query_builder.Append (String.Format ("photo_id = {0}", items[i].Id)); RemoveFromCache (items[i]); } Database.ExecuteNonQuery (String.Format ("DELETE FROM photos WHERE {0}", query_builder.ToString ())); Database.ExecuteNonQuery (String.Format ("DELETE FROM photo_tags WHERE {0}", tv_query_builder.ToString ())); Database.ExecuteNonQuery (String.Format ("DELETE FROM photo_versions WHERE {0}", tv_query_builder.ToString ())); } public override void Remove (DbItem item) { Remove (new Photo [] { (Photo)item }); } public override void Commit (DbItem item) { DbItemEventArgs args = new DbItemEventArgs (item); Commit (args.Items, args); } public void Commit (DbItem [] items, DbItemEventArgs args) { if (items.Length > 1) Database.BeginTransaction (); foreach (DbItem item in items) { Update ((Photo)item); } EmitChanged (items, args); if (items.Length > 1) Database.CommitTransaction (); } private void Update (Photo photo) { // Update photo. Database.ExecuteNonQuery (new DbCommand ("UPDATE photos SET description = :description, " + "directory_path = :directory_path, " + "default_version_id = :default_version_id, time = :time WHERE id = :id ", "description", photo.Description, "directory_path", photo.DirectoryPath, "default_version_id", photo.DefaultVersionId, "time", DbUtils.UnixTimeFromDateTime (photo.Time), "id", photo.Id)); // Update tags. Database.ExecuteNonQuery (new DbCommand ("DELETE FROM photo_tags WHERE photo_id = :id", "id", photo.Id)); foreach (Tag tag in photo.Tags) { Database.ExecuteNonQuery (new DbCommand ("INSERT INTO photo_tags (photo_id, tag_id) " + " VALUES (:photo_id, :tag_id)", "photo_id", photo.Id, "tag_id", tag.Id)); } // Update versions. Database.ExecuteNonQuery (new DbCommand ("DELETE FROM photo_versions WHERE photo_id = :id", "id", photo.Id)); foreach (uint version_id in photo.VersionIds) { if (version_id == Photo.OriginalVersionId) continue; string version_name = photo.GetVersionName (version_id); Database.ExecuteNonQuery(new DbCommand ("INSERT INTO photo_versions (photo_id, version_id, name) " + " VALUES (:photo_id, :version_id, :name)", "photo_id", photo.Id, "version_id", version_id, "name", version_name)); } } public class DateRange { private DateTime start; public DateTime Start { get { return start; } } private DateTime end; public DateTime End { get { return end; } } public DateRange (DateTime start, DateTime end) { this.start = start; this.end = end; } } // Queries. [Obsolete ("drop this, use IQueryCondition correctly instead")] private string AddLastImportFilter (RollSet roll_set, bool added_where) { if (roll_set == null) return null; return String.Format (" {0}{1}", added_where ? " AND " : " WHERE ", roll_set.SqlClause () ); } public Photo [] Query (Tag [] tags, DateTime start, DateTime end, Roll [] rolls) { return Query (tags, null, new DateRange (start, end), new RollSet (rolls)); } public Photo [] Query (Tag [] tags, Roll [] rolls) { return Query (tags, null, null, rolls == null ? null : new RollSet (rolls)); } public Photo [] Query (Tag [] tags, DateTime start, DateTime end) { return Query (tags, null, new DateRange (start, end), null); } public Photo [] Query (Tag [] tags) { return Query (tags, null, null, null); } public Photo [] Query (string query) { SqliteDataReader reader = Database.Query(query); ArrayList version_list = new ArrayList (); ArrayList id_list = new ArrayList (); while (reader.Read ()) { uint id = Convert.ToUInt32 (reader [0]); Photo photo = LookupInCache (id) as Photo; if (photo == null) { photo = new Photo (id, Convert.ToInt64 (reader [1]), reader [2].ToString (), reader [3].ToString ()); photo.Description = reader[4].ToString (); photo.RollId = Convert.ToUInt32 (reader[5]); photo.DefaultVersionId = Convert.ToUInt32 (reader[6]); version_list.Add (photo); } id_list.Add (photo); } reader.Close(); bool need_load = false; foreach (Photo photo in version_list) { AddToCache (photo); need_load |= !photo.Loaded; } if (need_load) { GetAllTags (); GetAllVersions (); foreach (Photo photo in version_list) { photo.Loaded = true; } } else { //Console.WriteLine ("Skipped Loading Data"); } return id_list.ToArray (typeof (Photo)) as Photo []; } public Photo [] Query (System.IO.DirectoryInfo dir) { string query_string = String.Format ("SELECT photos.id, " + " photos.time, " + " photos.directory_path, " + " photos.name, " + " photos.description, " + " photos.roll_id, " + " photos.default_version_id " + " FROM photos " + " WHERE directory_path = \"{0}\"", dir.FullName); return Query (query_string); } public Photo [] QueryUntagged (DateRange range, RollSet importidrange) { StringBuilder query_builder = new StringBuilder (); query_builder.Append ("SELECT * FROM photos WHERE id NOT IN " + "(SELECT DISTINCT photo_id FROM photo_tags) "); bool added_where = true; if (range != null) { query_builder.Append (String.Format ("AND photos.time >= {0} AND photos.time <= {1} ", DbUtils.UnixTimeFromDateTime (range.Start), DbUtils.UnixTimeFromDateTime (range.End))); added_where = true; } if (importidrange != null) { query_builder.Append (AddLastImportFilter (importidrange, added_where)); added_where = true; } query_builder.Append("ORDER BY time"); return Query (query_builder.ToString ()); } public Photo [] Query (Tag [] tags, string extra_condition, DateRange range, RollSet importidrange) { return Query (OrTerm.FromTags(tags), extra_condition, range, importidrange); } public Photo [] Query (Term searchexpression, string extra_condition, DateRange range, RollSet importidrange) { bool hide = (extra_condition == null); // The SQL query that we want to construct is: // // SELECT photos.id // photos.time // photos.directory_path, // photos.name, // photos.description, // photos.roll_id, // photos.default_version_id // FROM photos, photo_tags // WHERE photos.id = photo_tags.photo_id // AND (photo_tags.tag_id = cat1tag1 // OR photo_tags.tag_id = cat1tag2 ) // AND (photo_tags.tag_id = cat2tag1 // OR photo_tags.tag_id = cat2tag2 ) // AND (photos.roll_id = roll_id1 // OR photos.roll_id = roll_id2 ...) // GROUP BY photos.id StringBuilder query_builder = new StringBuilder (); query_builder.Append ("SELECT photos.id, " + "photos.time, " + "photos.directory_path, " + "photos.name, " + "photos.description, " + "photos.roll_id, " + "photos.default_version_id " + "FROM photos "); bool where_statement_added = false; if (range != null) { query_builder.Append (String.Format ("WHERE photos.time >= {0} AND photos.time <= {1} ", DbUtils.UnixTimeFromDateTime (range.Start), DbUtils.UnixTimeFromDateTime (range.End))); where_statement_added = true; } if (importidrange != null) { query_builder.Append (AddLastImportFilter (importidrange, where_statement_added)); where_statement_added = true; } if (hide && Core.Database.Tags.Hidden != null) { query_builder.Append (String.Format ("{0} photos.id NOT IN (SELECT photo_id FROM photo_tags WHERE tag_id = {1}) ", where_statement_added ? " AND " : " WHERE ", Core.Database.Tags.Hidden.Id)); where_statement_added = true; } if (searchexpression != null) { query_builder.Append (String.Format ("{0} {1}", where_statement_added ? " AND " : " WHERE ", searchexpression.SqlCondition())); where_statement_added = true; } if (extra_condition != null && extra_condition.Length != 0) { query_builder.Append (String.Format ("{0} {1} ", where_statement_added ? " AND " : " WHERE ", extra_condition)); where_statement_added = true; } query_builder.Append ("ORDER BY photos.time"); Console.WriteLine("Query: {0}", query_builder.ToString()); return Query (query_builder.ToString ()); } #if TEST_PHOTO_STORE static void Dump (Photo photo) { Console.WriteLine ("\t[{0}] {1}", photo.Id, photo.Path); Console.WriteLine ("\t{0}", photo.Time.ToLocalTime ()); if (photo.Description != String.Empty) Console.WriteLine ("\t{0}", photo.Description); else Console.WriteLine ("\t(no description)"); Console.WriteLine ("\tTags:"); if (photo.Tags.Count == 0) { Console.WriteLine ("\t\t(no tags)"); } else { foreach (Tag t in photo.Tags) Console.WriteLine ("\t\t{0}", t.Name); } Console.WriteLine ("\tVersions:"); foreach (uint id in photo.VersionIds) Console.WriteLine ("\t\t[{0}] {1}", id, photo.GetVersionName (id)); } static void Dump (ArrayList photos) { foreach (Photo p in photos) Dump (p); } static void DumpAll (Db db) { Console.WriteLine ("\n*** All pictures"); Dump (db.Photos.Query (null)); } static void DumpForTags (Db db, ArrayList tags) { Console.Write ("\n*** Pictures for tags: "); foreach (Tag t in tags) Console.Write ("{0} ", t.Name); Console.WriteLine (); Dump (db.Photos.Query (tags)); } static void Main (string [] args) { Program program = new Program ("PhotoStoreTest", "0.0", Modules.UI, args); const string path = "/tmp/PhotoStoreTest.db"; try { File.Delete (path); } catch {} Db db = new Db (path, true); Tag portraits_tag = db.Tags.CreateTag (null, "Portraits"); Tag landscapes_tag = db.Tags.CreateTag (null, "Landscapes"); Tag favorites_tag = db.Tags.CreateTag (null, "Street"); uint portraits_tag_id = portraits_tag.Id; uint landscapes_tag_id = landscapes_tag.Id; uint favorites_tag_id = favorites_tag.Id; Pixbuf unused_thumbnail; Photo ny_landscape = db.Photos.Create (DateTime.Now.ToUniversalTime (), 1, "/home/ettore/Photos/ny_landscape.jpg", out unused_thumbnail); ny_landscape.Description = "Pretty NY skyline"; ny_landscape.AddTag (landscapes_tag); ny_landscape.AddTag (favorites_tag); db.Photos.Commit (ny_landscape); Photo me_in_sf = db.Photos.Create (DateTime.Now.ToUniversalTime (), 2, "/home/ettore/Photos/me_in_sf.jpg", out unused_thumbnail); me_in_sf.AddTag (landscapes_tag); me_in_sf.AddTag (portraits_tag); me_in_sf.AddTag (favorites_tag); db.Photos.Commit (me_in_sf); me_in_sf.RemoveTag (favorites_tag); me_in_sf.Description = "Myself and the SF skyline"; me_in_sf.CreateVersion ("cropped", Photo.OriginalVersionId); me_in_sf.CreateVersion ("UM-ed", Photo.OriginalVersionId); db.Photos.Commit (me_in_sf); Photo macro_shot = db.Photos.Create (DateTime.Now.ToUniversalTime (), 2, "/home/ettore/Photos/macro_shot.jpg", out unused_thumbnail); db.Dispose (); db = new Db (path, false); DumpAll (db); portraits_tag = db.Tags.Get (portraits_tag_id) as Tag; landscapes_tag = db.Tags.Get (landscapes_tag_id) as Tag; favorites_tag = db.Tags.Get (favorites_tag_id) as Tag; ArrayList query_tags = new ArrayList (); query_tags.Add (portraits_tag); query_tags.Add (landscapes_tag); DumpForTags (db, query_tags); query_tags.Clear (); query_tags.Add (favorites_tag); DumpForTags (db, query_tags); } #endif }