f-spot r4313 - in trunk: . src src/Core src/Jobs



Author: thomasvm
Date: Sat Sep  6 18:52:15 2008
New Revision: 4313
URL: http://svn.gnome.org/viewvc/f-spot?rev=4313&view=rev

Log:
Add duplicate detection to the import window.  Fix bgo #169646

Added:
   trunk/src/Jobs/CalculateHashJob.cs
Modified:
   trunk/ChangeLog
   trunk/src/Core/Photo.cs
   trunk/src/Core/PhotoVersion.cs
   trunk/src/Core/PhotosChanges.cs
   trunk/src/FileImportBackend.cs
   trunk/src/ImportBackend.cs
   trunk/src/ImportCommand.cs
   trunk/src/Makefile.am
   trunk/src/PhotoStore.cs
   trunk/src/TagStore.cs
   trunk/src/Updater.cs
   trunk/src/f-spot.glade

Modified: trunk/src/Core/Photo.cs
==============================================================================
--- trunk/src/Core/Photo.cs	(original)
+++ trunk/src/Core/Photo.cs	Sat Sep  6 18:52:15 2008
@@ -207,6 +207,18 @@
 				changes.RatingChanged = true;
 			}
 		}
+
+		private string md5_sum;
+		public string MD5Sum {
+			get { return md5_sum; }
+			set { 
+				if (md5_sum == value)
+				 	return;
+
+				md5_sum = value; 
+				changes.MD5SumChanged = true;
+			} 
+		}
 	
 		// Version management
 		public const int OriginalVersionId = 1;
@@ -254,9 +266,9 @@
 	
 		// 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.
-		internal void AddVersionUnsafely (uint version_id, System.Uri uri, string name, bool is_protected)
+		internal void AddVersionUnsafely (uint version_id, System.Uri uri, string md5_sum, string name, bool is_protected)
 		{
-			Versions [version_id] = new PhotoVersion (this, version_id, uri, name, is_protected);
+			Versions [version_id] = new PhotoVersion (this, version_id, uri, md5_sum, name, is_protected);
 	
 			highest_version_id = Math.Max (version_id, highest_version_id);
 			changes.AddVersion (version_id);
@@ -272,7 +284,9 @@
 			if (VersionNameExists (name))
 				throw new ApplicationException ("A version with that name already exists");
 			highest_version_id ++;
-			Versions [highest_version_id] = new PhotoVersion (this, highest_version_id, uri, name, is_protected);
+			string md5_sum = GenerateMD5 (uri);
+
+			Versions [highest_version_id] = new PhotoVersion (this, highest_version_id, uri, name, md5_sum, is_protected);
 
 			changes.AddVersion (highest_version_id);
 			return highest_version_id;
@@ -416,6 +430,7 @@
 		{
 			System.Uri new_uri = GetUriForVersionName (name, System.IO.Path.GetExtension (VersionUri (base_version_id).AbsolutePath));
 			System.Uri original_uri = VersionUri (base_version_id);
+			string md5_sum = MD5Sum;
 	
 			if (VersionNameExists (name))
 				throw new Exception ("This version name already exists");
@@ -440,9 +455,13 @@
 	//				try {
 	//					Mono.Unix.Native.Syscall.chown(new_path, Mono.Unix.Native.Syscall.getuid (), stat.st_gid);
 	//				} catch (Exception) {}
+	//
+			} else {
+				md5_sum = Photo.GenerateMD5 (new_uri);
 			}
 			highest_version_id ++;
-			Versions [highest_version_id] = new PhotoVersion (this, highest_version_id, new_uri, name, is_protected);
+
+			Versions [highest_version_id] = new PhotoVersion (this, highest_version_id, new_uri, md5_sum, name, is_protected);
 
 			changes.AddVersion (highest_version_id);
 	
@@ -466,7 +485,7 @@
 					continue;
 	
 				highest_version_id ++;
-				Versions [highest_version_id] = new PhotoVersion (this, highest_version_id, version.Uri, name, is_protected);
+				Versions [highest_version_id] = new PhotoVersion (this, highest_version_id, version.Uri, version.MD5Sum, name, is_protected);
 
 				changes.AddVersion (highest_version_id);
 
@@ -597,9 +616,54 @@
 			return tags.Contains (tag);
 		}
 	
+		//
+		// MD5 Calculator
+		//
+		private static System.Security.Cryptography.MD5 md5_generator;
+
+		private static System.Security.Cryptography.MD5 MD5Generator {
+			get {
+				if (md5_generator == null)
+				 	md5_generator = new System.Security.Cryptography.MD5CryptoServiceProvider ();
+
+				return md5_generator;
+			} 
+		}
+
+		private static IDictionary<System.Uri, string> md5_cache = new Dictionary<System.Uri, string> ();
+
+		public static void ResetMD5Cache () {
+			if (md5_cache != null)	
+				md5_cache.Clear (); 
+		}
+
+		public static string GenerateMD5 (System.Uri uri)
+		{
+		 	try {
+			 	if (md5_cache.ContainsKey (uri)) {
+				 	Log.DebugFormat("Return cache hit for {0}", uri);
+				 	return md5_cache [uri];
+				}
+
+				using (Gdk.Pixbuf pixbuf = ThumbnailGenerator.Create (uri))
+				{
+					byte[] serialized = PixbufSerializer.Serialize (pixbuf);
+					byte[] md5 = MD5Generator.ComputeHash (serialized);
+					string md5_string = Convert.ToBase64String (md5);
+
+					md5_cache.Add (uri, md5_string);
+					return md5_string;
+				}
+			} catch (Exception e) {
+			 	Log.DebugFormat("Failed to create MD5Sum for Uri {0}; {1}", uri, e.Message);
+			}
+
+			return string.Empty; 
+		}
+
 
 		// Constructor
-		public Photo (uint id, long unix_time, System.Uri uri)
+		public Photo (uint id, long unix_time, System.Uri uri, string md5_sum)
 			: base (id)
 		{
 			if (uri == null)
@@ -609,10 +673,11 @@
 	
 			description = String.Empty;
 			rating = 0;
+			this.md5_sum = md5_sum;
 	
 			// Note that the original version is never stored in the photo_versions table in the
 			// database.
-			AddVersionUnsafely (OriginalVersionId, uri, Catalog.GetString ("Original"), true);
+			AddVersionUnsafely (OriginalVersionId, uri, md5_sum, Catalog.GetString ("Original"), true);
 		}
 	}
 }

Modified: trunk/src/Core/PhotoVersion.cs
==============================================================================
--- trunk/src/Core/PhotoVersion.cs	(original)
+++ trunk/src/Core/PhotoVersion.cs	Sat Sep  6 18:52:15 2008
@@ -5,6 +5,7 @@
  *	Ettore Perazzoli <ettore perazzoli org>
  *	Larry Ewing <lewing gnome org>
  *	Stephane Delcroix <stephane delcroix org>
+ *	Thomas Van Machelen <thomas vanmachelen gmail com>
  * 
  * This is free software. See COPYING for details.
  */
@@ -16,6 +17,7 @@
 		Photo photo;
 		uint version_id;
 		System.Uri uri;
+		string md5_sum;
 		string name;
 		bool is_protected;
 	
@@ -52,6 +54,11 @@
 				uri = value;
 			}
 		}
+
+		public string MD5Sum {
+			get { return md5_sum; } 
+			internal set { md5_sum = value; }
+		}
 	
 		public uint VersionId {
 			get { return version_id; }
@@ -65,11 +72,12 @@
 			get { return photo.Rating; }
 		}
 	
-		public PhotoVersion (Photo photo, uint version_id, System.Uri uri, string name, bool is_protected)
+		public PhotoVersion (Photo photo, uint version_id, System.Uri uri, string md5_sum, string name, bool is_protected)
 		{
 			this.photo = photo;
 			this.version_id = version_id;
 			this.uri = uri;
+			this.md5_sum = md5_sum;
 			this.name = name;
 			this.is_protected = is_protected;
 		}

Modified: trunk/src/Core/PhotosChanges.cs
==============================================================================
--- trunk/src/Core/PhotosChanges.cs	(original)
+++ trunk/src/Core/PhotosChanges.cs	Sat Sep  6 18:52:15 2008
@@ -25,6 +25,7 @@
 			Description		= 0x10,
 			RollId			= 0x20,
 			Data			= 0x40,
+			MD5Sum			= 0x80
 		}
 
 		Changes changes = Changes.None;
@@ -108,6 +109,17 @@
 			}
 		}
 
+		public bool MD5SumChanged {
+			get { return (changes & Changes.MD5Sum) == Changes.MD5Sum ; } 
+			set {
+				if (value)
+				 	changes |= Changes.MD5Sum;
+				else
+				 	changes &= ~Changes.MD5Sum; 
+			}
+		 
+		}
+
 		public static PhotosChanges operator | (PhotosChanges c1, PhotosChanges c2)
 		{
 			PhotosChanges changes = new PhotosChanges ();

Modified: trunk/src/FileImportBackend.cs
==============================================================================
--- trunk/src/FileImportBackend.cs	(original)
+++ trunk/src/FileImportBackend.cs	Sat Sep  6 18:52:15 2008
@@ -23,11 +23,13 @@
 	TagStore tag_store = FSpot.Core.Database.Tags;
 	bool recurse;
 	bool copy;
+	bool include_duplicates;
 	string [] base_paths;
 	Tag [] tags;
 	Gtk.Window parent;
 
 	int count;
+	int duplicate_count;
 	XmpTagsImporter xmptags;
 
 	ArrayList import_info;
@@ -117,6 +119,7 @@
 		xmptags = new XmpTagsImporter (store, tag_store);
 
 		roll = rolls.Create ();
+		Photo.ResetMD5Cache ();
 
 		return import_info.Count;
 	}
@@ -191,9 +194,11 @@
 		return dest;
 	}
 
-	public override bool Step (out Photo photo, out Pixbuf thumbnail, out int count)
+	public override bool Step (out StepStatusInfo status_info)
 	{
-		thumbnail = null;
+		Photo photo = null;
+		Pixbuf thumbnail = null;
+		bool is_duplicate = false;
 
 		if (import_info == null)
 			throw new ImportException ("Prepare() was not called");
@@ -209,41 +214,62 @@
 			string destination = info.OriginalPath;
 			if (copy)
 				destination = ChooseLocation (info.OriginalPath, directories);
-			
+
 			// Don't copy if we are already home
 			if (info.OriginalPath == destination) {
 				info.DestinationPath = destination;
-				photo = store.Create (info.DestinationPath, roll.Id, out thumbnail);
+
+				if (!include_duplicates)
+					photo = store.CheckForDuplicate (UriUtils.PathToFileUri (destination));
+
+				if (photo == null)
+					photo = store.Create (info.DestinationPath, roll.Id, out thumbnail);
+				else
+				 	is_duplicate = true;
 			} else {
 				System.IO.File.Copy (info.OriginalPath, destination);
 				info.DestinationPath = destination;
 
-				photo = store.Create (info.DestinationPath, info.OriginalPath, roll.Id, out thumbnail);
+				if (!include_duplicates)
+				 	photo = store.CheckForDuplicate (UriUtils.PathToFileUri (destination));
 
-				try {
-					File.SetAttributes (destination, File.GetAttributes (info.DestinationPath) & ~FileAttributes.ReadOnly);
-					DateTime create = File.GetCreationTime (info.OriginalPath);
-					File.SetCreationTime (info.DestinationPath, create);
-					DateTime mod = File.GetLastWriteTime (info.OriginalPath);
-					File.SetLastWriteTime (info.DestinationPath, mod);
-				} catch (IOException) {
-					// we don't want an exception here to be fatal.
+				if (photo == null)
+				{
+					photo = store.Create (info.DestinationPath, info.OriginalPath, roll.Id, out thumbnail);
+				 	
+
+					try {
+						File.SetAttributes (destination, File.GetAttributes (info.DestinationPath) & ~FileAttributes.ReadOnly);
+						DateTime create = File.GetCreationTime (info.OriginalPath);
+						File.SetCreationTime (info.DestinationPath, create);
+						DateTime mod = File.GetLastWriteTime (info.OriginalPath);
+						File.SetLastWriteTime (info.DestinationPath, mod);
+					} catch (IOException) {
+						// we don't want an exception here to be fatal.
+					}
+				}
+				else
+				{
+					is_duplicate = true; 
 				}
 			} 
 
-			if (tags != null) {
-				foreach (Tag t in tags) {
-					photo.AddTag (t);
+			if (!is_duplicate)
+			{
+				if (tags != null) {
+					foreach (Tag t in tags) {
+						photo.AddTag (t);
+					}
+					needs_commit = true;
 				}
-				needs_commit = true;
-			}
 
-			needs_commit |= xmptags.Import (photo, info.DestinationPath, info.OriginalPath);
+				needs_commit |= xmptags.Import (photo, info.DestinationPath, info.OriginalPath);
 
-			if (needs_commit)
-				store.Commit(photo);
-			
-			info.Photo = photo;
+				if (needs_commit)
+					store.Commit(photo);
+
+				info.Photo = photo;
+			}
 		} catch (System.Exception e) {
 			System.Console.WriteLine ("Error importing {0}{2}{1}", info.OriginalPath, e.ToString (), Environment.NewLine);
 			if (thumbnail != null)
@@ -266,7 +292,11 @@
 		}
 
 		this.count ++;
-		count = this.count;
+
+		if (is_duplicate)
+		 	this.duplicate_count ++;
+
+		status_info = new StepStatusInfo (photo, thumbnail, this.count, is_duplicate);
 
 		return (!abort && count != import_info.Count);
 	}
@@ -322,18 +352,26 @@
 
 		import_info = null;
 		xmptags.Finish();
-		count = 0;
-	//rolls.EndImport();    // Clean up the imported session.
+		Photo.ResetMD5Cache ();
+
+		if (count == duplicate_count)
+		 	rolls.Remove (roll);
+
+		count = duplicate_count = 0;
+		//rolls.EndImport();    // Clean up the imported session.
 	}
 
-	public FileImportBackend (PhotoStore store, string [] base_paths, bool recurse, Gtk.Window parent) : this (store, base_paths, false, recurse, null, parent) {}
+	public FileImportBackend (PhotoStore store, string [] base_paths, bool recurse, Gtk.Window parent) : this (store, base_paths, false, recurse, false, null, parent) {}
+
+	public FileImportBackend (PhotoStore store, string [] base_paths, bool copy, bool recurse, Tag [] tags, Gtk.Window parent) : this (store, base_paths, copy, recurse, false, null, parent) {}
 
-	public FileImportBackend (PhotoStore store, string [] base_paths, bool copy, bool recurse, Tag [] tags, Gtk.Window parent)
+	public FileImportBackend (PhotoStore store, string [] base_paths, bool copy, bool recurse, bool include_duplicates, Tag [] tags, Gtk.Window parent)
 	{
 		this.store = store;
 		this.copy = copy;
 		this.base_paths = base_paths;
 		this.recurse = recurse;
+		this.include_duplicates = include_duplicates;
 		this.tags = tags;
 		this.parent = parent;
 	}

Modified: trunk/src/ImportBackend.cs
==============================================================================
--- trunk/src/ImportBackend.cs	(original)
+++ trunk/src/ImportBackend.cs	Sat Sep  6 18:52:15 2008
@@ -7,7 +7,7 @@
 	public abstract int Prepare ();
 
 	// Import one picture.  Returns false when done; then you have to call Finish().
-	public abstract bool Step (out Photo photo, out Pixbuf thumbnail, out int count);
+	public abstract bool Step (out StepStatusInfo import_info);
 
 	// Cancel importing.
 	public abstract void Cancel ();

Modified: trunk/src/ImportCommand.cs
==============================================================================
--- trunk/src/ImportCommand.cs	(original)
+++ trunk/src/ImportCommand.cs	Sat Sep  6 18:52:15 2008
@@ -321,6 +321,7 @@
 	[Glade.Widget] Gtk.OptionMenu source_option_menu;
 	[Glade.Widget] Gtk.ScrolledWindow icon_scrolled;
 	[Glade.Widget] Gtk.ScrolledWindow photo_scrolled;
+	[Glade.Widget] Gtk.CheckButton duplicate_check;
 	[Glade.Widget] Gtk.CheckButton recurse_check;
 	[Glade.Widget] Gtk.CheckButton copy_check;
 	[Glade.Widget] Gtk.Button ok_button;
@@ -431,10 +432,8 @@
 	}
 
 	private bool Step ()
-	{			
-		Photo photo;
-		Pixbuf thumbnail;
-		int count;
+	{	
+	 	StepStatusInfo status_info;		
 		bool ongoing = true;
 
 		if (importer == null)
@@ -443,27 +442,27 @@
 		try {
 			// FIXME this is really just an incredibly ugly way of dealing
 			// with the recursive DoImport loops we sometimes get into
-			ongoing = importer.Step (out photo, out thumbnail, out count);
+			ongoing = importer.Step (out status_info);
 		} catch (ImportException e){
 			System.Console.WriteLine (e);
 			return false;
 		}
 
-		if (photo == null || thumbnail == null) {
+		if (!status_info.IsDuplicate && (status_info.Photo == null || status_info.Thumbnail == null)) {
 			Console.WriteLine ("Could not import file");
 		} else {
 			//icon_scrolled.Visible = true;
-			collection.Add (photo);
-		
+		 	if (!status_info.IsDuplicate)
+				collection.Add (status_info.Photo);
 			//grid.AddThumbnail (thumbnail);
 
 		}
 
-		if (thumbnail != null)
-			thumbnail.Dispose ();
+		if (status_info.Thumbnail != null)
+			status_info.Thumbnail.Dispose ();
 		
-		if (count < total)
-			UpdateProgressBar (count + 1, total);
+		if (status_info.Count < total)
+			UpdateProgressBar (status_info.Count + 1, total);
 
 		if (ongoing && total > 0)
 			return true;
@@ -600,6 +599,7 @@
 		this.Dialog.DefaultResponse = ResponseType.Ok;
 		
 		//import_folder_entry.Activated += HandleEntryActivate;
+		duplicate_check.Toggled += HandleRecurseToggled;
 		recurse_check.Toggled += HandleRecurseToggled;
 		copy_check.Toggled += HandleRecurseToggled;
 
@@ -776,9 +776,13 @@
 		bool recurse = true;
 		if (recurse_check != null)
 			recurse = recurse_check.Active;
+
+		bool include_duplicates = false;
+		if (include_duplicates != null)
+		 	include_duplicates = duplicate_check.Active;
 		
 //		importer = new FileImportBackend (store, pathimport, copy, recurse, null);
-		importer = new FileImportBackend (store, pathimport, copy, recurse, null, Dialog);
+		importer = new FileImportBackend (store, pathimport, copy, recurse, include_duplicates, null, Dialog);
 		AllowFinish = false;
 		
 		total = importer.Prepare ();
@@ -848,3 +852,48 @@
 
 #endif
 }
+
+public class StepStatusInfo {
+	private Photo photo;
+	private Pixbuf thumbnail;
+	private int count;
+	private bool is_duplicate;
+
+	public Photo Photo {
+		get  {
+			return photo; 
+		} 
+	}
+
+	public Pixbuf Thumbnail {
+		get {
+			return thumbnail; 
+		} 
+	}
+
+	public int Count {
+		get {
+			return count; 
+		} 
+	}
+
+	public bool IsDuplicate {
+		get {
+			return is_duplicate; 
+		} 
+	}
+
+	public StepStatusInfo (Photo photo, Pixbuf thumbnail, int count, bool is_duplicate)
+	{
+		this.photo = photo;
+		this.thumbnail = thumbnail;
+		this.count = count;
+		this.is_duplicate = is_duplicate;
+	}
+ 
+	public StepStatusInfo (Photo photo, Pixbuf thumbnail, int count)
+		: this (photo, thumbnail, count, false)
+	{ }
+}
+
+

Added: trunk/src/Jobs/CalculateHashJob.cs
==============================================================================
--- (empty file)
+++ trunk/src/Jobs/CalculateHashJob.cs	Sat Sep  6 18:52:15 2008
@@ -0,0 +1,50 @@
+/*
+ * Jobs/CalculateHashJob.cs
+ *
+ * Author(s)
+ *   Thomas Van Machelen <thomas vanmachelen gmail com>
+ *
+ * This is free software. See COPYING for details.
+ */
+
+using System;
+using Banshee.Kernel;
+using FSpot.Utils;
+
+namespace FSpot.Jobs {
+	public class CalculateHashJob : Job
+	{
+		public CalculateHashJob (uint id, string job_options, int run_at, JobPriority job_priority, bool persistent) 
+			: this (id, job_options, DbUtils.DateTimeFromUnixTime (run_at), job_priority, persistent)
+		{
+		}
+
+		public CalculateHashJob (uint id, string job_options, DateTime run_at, JobPriority job_priority, bool persistent) 
+			: base (id, job_options, job_priority, run_at, persistent)
+		{
+		}
+
+		public static CalculateHashJob Create (JobStore job_store, uint photo_id)
+		{
+			return (CalculateHashJob) job_store.CreatePersistent (typeof(FSpot.Jobs.CalculateHashJob), photo_id.ToString ()); 
+		}
+
+		protected override bool Execute ()
+		{
+			//this will add some more reactivity to the system
+			System.Threading.Thread.Sleep (200);
+
+			uint photo_id = Convert.ToUInt32 (JobOptions);
+			Log.DebugFormat ("Calculating Hash {0}...", photo_id);
+
+			try {
+			 	Photo photo = FSpot.Core.Database.Photos.Get (Convert.ToUInt32 (photo_id)) as Photo;
+				FSpot.Core.Database.Photos.UpdateMD5Sum (photo);
+			} catch (System.Exception e) {
+			 	Log.DebugFormat ("Error Calculating Hash for photo {0}: {1}", JobOptions, e.Message);
+			}
+			return false;
+		}
+	} 
+}
+

Modified: trunk/src/Makefile.am
==============================================================================
--- trunk/src/Makefile.am	(original)
+++ trunk/src/Makefile.am	Sat Sep  6 18:52:15 2008
@@ -174,6 +174,7 @@
 	$(srcdir)/Imaging/Tiff.cs		\
 	$(srcdir)/JobStore.cs			\
 	$(srcdir)/Jobs/SyncMetadataJob.cs	\
+	$(srcdir)/Jobs/CalculateHashJob.cs	\
 	$(srcdir)/Loupe.cs			\
 	$(srcdir)/MainWindow.cs			\
 	$(srcdir)/MemorySurface.cs		\

Modified: trunk/src/PhotoStore.cs
==============================================================================
--- trunk/src/PhotoStore.cs	(original)
+++ trunk/src/PhotoStore.cs	Sat Sep  6 18:52:15 2008
@@ -123,7 +123,8 @@
 			"	description        TEXT NOT NULL,	           " +
 			"	roll_id            INTEGER NOT NULL,		   " +
 			"	default_version_id INTEGER NOT NULL,		   " +
-			"	rating		   INTEGER NULL			   " +
+			"	rating		   INTEGER NULL,		   " +
+			"	md5_sum		   TEXT NULL  			   " +
 			")");
 
 
@@ -141,11 +142,42 @@
 			"	version_id	INTEGER,	" +
 			"	name		STRING,		" +
 			"	uri		STRING NOT NULL," +
-			"	protected	BOOLEAN, 	" +
+			"	md5_sum		STRING NOT NULL," +
+			"	protected	BOOLEAN,	" +
 			"	UNIQUE (photo_id, version_id)	" +
 			")");
 	}
 
+	public Photo CheckForDuplicate (System.Uri uri) {
+		// Here we can go wild in comparing photos,
+		// for now we check on uri and md5
+		Photo found = GetByUri (uri);
+		
+		if (found != null)
+		 	return found;
+
+		string md5 = Photo.GenerateMD5 (uri);			
+		Gnome.Vfs.FileInfo info = new Gnome.Vfs.FileInfo (uri.ToString (), FileInfoOptions.GetMimeType);
+
+		Photo[] md5_matches = GetByMD5 (md5);
+
+		foreach (Photo match in md5_matches)
+		{
+		 	Gnome.Vfs.FileInfo match_info = new Gnome.Vfs.FileInfo (match.DefaultVersionUri.ToString (), FileInfoOptions.GetMimeType);
+
+			// same mimetype?
+			if (info.MimeType != match_info.MimeType)
+			 	continue;
+
+			// other comparisons?
+
+			// TODO? load pixbuf and compare sizes?	
+
+			return match;
+		}
+
+		return null;
+	}
 
 	[Obsolete ("Use Create (Uri, uint, out Pixbuf) instead")]
 	public Photo Create (string path, uint roll_id, out Pixbuf thumbnail)
@@ -170,18 +202,23 @@
 		using (FSpot.ImageFile img = FSpot.ImageFile.Create (orig_uri)) {
 			long unix_time = DbUtils.UnixTimeFromDateTime (img.Date);
 			string description = img.Description != null  ? img.Description.Split ('\0') [0] : String.Empty;
+			string md5_sum = Photo.GenerateMD5 (new_uri);
+
+	 		uint id = (uint) Database.Execute (
+				new DbCommand (
+					"INSERT INTO photos (time, uri, description, roll_id, default_version_id, rating, md5_sum) "	+
+	 				"VALUES (:time, :uri, :description, :roll_id, :default_version_id, :rating, :md5_sum)",
+	 				"time", unix_time,
+					"uri", new_uri.OriginalString,
+	 				"description", description,
+					"roll_id", roll_id,
+	 				"default_version_id", Photo.OriginalVersionId,
+					"rating", "0",
+					"md5_sum", md5_sum
+				)
+			);
 	
-	 		uint id = (uint) Database.Execute (new DbCommand (
-				"INSERT INTO photos (time, uri, description, roll_id, default_version_id, rating) "	+
-	 			"VALUES (:time, :uri, :description, :roll_id, :default_version_id, :rating)",
-	 			"time", unix_time,
-				"uri", new_uri.OriginalString,
-	 			"description", description,
-				"roll_id", roll_id,
-	 			"default_version_id", Photo.OriginalVersionId,
-				"rating", "0"));
-	
-			photo = new Photo (id, unix_time, new_uri);
+			photo = new Photo (id, unix_time, new_uri, md5_sum);
 			AddToCache (photo);
 			photo.Loaded = true;
 	
@@ -194,7 +231,13 @@
 
 	private void GetVersions (Photo photo)
 	{
-		SqliteDataReader reader = Database.Query(new DbCommand("SELECT version_id, name, uri, protected FROM photo_versions WHERE photo_id = :id", "id", photo.Id));
+		SqliteDataReader reader = Database.Query(
+			new DbCommand("SELECT version_id, name, uri, md5_sum, protected " + 
+				      "FROM photo_versions " + 
+				      "WHERE photo_id = :id", 
+				      "id", photo.Id
+			)
+		);
 
 		while (reader.Read ()) {
 			uint version_id = Convert.ToUInt32 (reader [0]);
@@ -204,8 +247,9 @@
 #else
 			System.Uri uri = new System.Uri (reader[2].ToString (), true);
 #endif
-			bool is_protected = Convert.ToBoolean (reader[3]);
-			photo.AddVersionUnsafely (version_id, uri, name, is_protected);
+			string md5_sum = reader[3].ToString ();
+			bool is_protected = Convert.ToBoolean (reader[4]);
+			photo.AddVersionUnsafely (version_id, uri, md5_sum, name, is_protected);
 		}
 		reader.Close();
 	}
@@ -223,7 +267,7 @@
 	}		
 	
 	private void GetAllVersions  () {
-		SqliteDataReader reader = Database.Query("SELECT photo_id, version_id, name, uri, protected FROM photo_versions");
+		SqliteDataReader reader = Database.Query("SELECT photo_id, version_id, name, uri, md5_sum, protected FROM photo_versions");
 		
 		while (reader.Read ()) {
 			uint id = Convert.ToUInt32 (reader [0]);
@@ -247,8 +291,9 @@
 #else
 				System.Uri uri = new System.Uri (reader[3].ToString (), true);
 #endif
-				bool is_protected = Convert.ToBoolean (reader[4]);	
-				photo.AddVersionUnsafely (version_id, uri, name, is_protected);
+				string md5_sum = reader[4].ToString ();
+				bool is_protected = Convert.ToBoolean (reader[5]);	
+				photo.AddVersionUnsafely (version_id, uri, md5_sum, name, is_protected);
 			}
 
 			/*
@@ -293,17 +338,23 @@
 		if (photo != null)
 			return photo;
 
-		SqliteDataReader reader = Database.Query(new DbCommand("SELECT time, uri, description, roll_id, default_version_id, rating "
-			+ "FROM photos WHERE id = :id", "id", id));
+		SqliteDataReader reader = Database.Query(
+			new DbCommand("SELECT time, uri, description, roll_id, default_version_id, rating, md5_sum " + 
+				      "FROM photos " + 
+				      "WHERE id = :id", "id", id
+				     )
+		);
 
 		if (reader.Read ()) {
 			photo = new Photo (id,
 				Convert.ToInt64 (reader [0]),
 #if MONO_2_0
-				new System.Uri (reader [1].ToString ()));
+				new System.Uri (reader [1].ToString ()),
 #else
-				new System.Uri (reader [1].ToString (), true));
+				new System.Uri (reader [1].ToString (), true),
 #endif
+				reader[6].ToString ()
+			);
 
 			photo.Description = reader[2].ToString ();
 			photo.RollId = Convert.ToUInt32 (reader[3]);
@@ -333,6 +384,7 @@
 		Photo photo = null;
 
 		uint timer = Log.DebugTimerStart ();
+
 		SqliteDataReader reader = Database.Query (new DbCommand ("SELECT id, time, description, roll_id, default_version_id, rating " + 
 									 " FROM photos " +
 									 " LEFT JOIN photo_versions AS pv ON photos.id = pv.photo_id" +
@@ -341,7 +393,8 @@
 		if (reader.Read ()) {
 			photo = new Photo (Convert.ToUInt32 (reader [0]),
 					   Convert.ToInt64 (reader [1]),
-					   uri);
+					   uri,
+					   reader[6].ToString ());
 
 			photo.Description = reader[2].ToString ();
 			photo.RollId = Convert.ToUInt32 (reader[3]);
@@ -354,8 +407,10 @@
 		if (photo == null)
 			return null;
 
-		if (LookupInCache (photo.Id) as Photo != null)
-			return LookupInCache (photo.Id) as Photo;
+		Photo cached = LookupInCache (photo.Id) as Photo;
+
+		if (cached != null)
+			return cached;
 
 		AddToCache (photo);
 	
@@ -365,6 +420,63 @@
 		return photo;
 	}
 
+	public Photo[] GetByMD5 (string md5_sum)
+	{
+		List<Photo> photos = new List<Photo> ();
+		
+		SqliteDataReader reader = Database.Query (
+			new DbCommand ("SELECT DISTINCT " + 
+				       "id, time, photos.uri, description, roll_id, default_version_id, rating " + 
+				       "FROM photos " + 
+				       "LEFT JOIN photo_versions " + 
+				       "ON   photos.id = photo_versions.photo_id " +
+				       "WHERE photos.md5_sum = :md5_sum " +
+				       "OR photo_versions.md5_sum = :md5_sum", 
+				       "md5_sum", md5_sum
+				      )
+		);
+
+		while (reader.Read ()) {
+			Photo photo = new Photo (Convert.ToUInt32 (reader [0]),
+				Convert.ToInt64 (reader [1]),
+#if MONO_2_0
+				new System.Uri (reader [2].ToString ()),
+#else
+				new System.Uri (reader [2].ToString (), true),
+#endif
+				md5_sum
+			);
+
+			photo.Description = reader[3].ToString ();
+			photo.RollId = Convert.ToUInt32 (reader[4]);
+			photo.DefaultVersionId = Convert.ToUInt32 (reader[5]);
+			photo.Rating = Convert.ToUInt32 (reader [6]);
+			photo.MD5Sum = md5_sum;
+
+			// get cached if possible
+			Photo cached = LookupInCache (photo.Id) as Photo;
+
+			if (cached != null)
+			{
+				photos.Add (cached);
+				continue;
+			}
+
+			// Add to cache and fully load if not found in cache
+			AddToCache (photo);
+	
+			GetTags (photo);
+			GetVersions (photo);
+
+			// add to collection
+			photos.Add (photo);
+		}
+
+	        reader.Close();
+
+		return photos.ToArray ();
+	}
+
 	public void Remove (Tag []tags)
 	{
 		Photo [] photos = Query (tags, String.Empty, null, null);	
@@ -426,20 +538,26 @@
 	private PhotoChanges Update (Photo photo) {
 		PhotoChanges changes = photo.Changes;
 		// Update photo.
-		if (changes.DescriptionChanged || changes.DefaultVersionIdChanged || changes.TimeChanged || changes.UriChanged || changes.RatingChanged )
-			Database.ExecuteNonQuery (new DbCommand (
-				"UPDATE photos SET description = :description, " +
-				"default_version_id = :default_version_id, " +
-				"time = :time, " +
-				"uri = :uri, " +
-				"rating = :rating " +
-				"WHERE id = :id ",
-				"description", photo.Description,
-				"default_version_id", photo.DefaultVersionId,
-				"time", DbUtils.UnixTimeFromDateTime (photo.Time),
-				"uri", photo.VersionUri (Photo.OriginalVersionId).OriginalString,
-				"rating", String.Format ("{0}", photo.Rating),
-				"id", photo.Id));
+		if (changes.DescriptionChanged || changes.DefaultVersionIdChanged || changes.TimeChanged || changes.UriChanged || changes.RatingChanged || changes.MD5SumChanged )
+			Database.ExecuteNonQuery (
+				new DbCommand (
+					"UPDATE photos " + 
+					"SET description = :description, " + 
+					"    default_version_id = :default_version_id, " + 
+					"    time = :time, " + 
+					"    uri = :uri, " +
+					"    rating = :rating, " +
+					"    md5_sum = :md5_sum	" +
+					"WHERE id = :id ",
+					"description", photo.Description,
+					"default_version_id", photo.DefaultVersionId,
+					"time", DbUtils.UnixTimeFromDateTime (photo.Time),
+					"uri", photo.VersionUri (Photo.OriginalVersionId).OriginalString,
+					"rating", String.Format ("{0}", photo.Rating),
+					"md5_sum", photo.MD5Sum,
+					"id", photo.Id
+				)
+			);
 
 		// Update tags.
 		if (changes.TagsRemoved != null)
@@ -493,6 +611,38 @@
 		photo.Changes = null;
 		return changes;
 	}
+
+	public void UpdateMD5Sum (Photo photo) {
+		string md5_sum = Photo.GenerateMD5 (photo.VersionUri (Photo.OriginalVersionId)); 
+		photo.MD5Sum = md5_sum;
+
+		Database.ExecuteNonQuery (
+			new DbCommand (
+				"UPDATE photos " +
+				"SET    md5_sum = :md5_sum " +
+				"WHERE  ID = :id",
+				"md5_sum", md5_sum,
+				"id", photo.Id
+			)
+		);
+
+		bool needs_commit = false;
+
+		foreach (uint version_id in photo.VersionIds) {
+			if (version_id == Photo.OriginalVersionId)
+			 	continue;
+
+			PhotoVersion version = photo.GetVersion (version_id) as PhotoVersion;
+
+			string version_md5_sum = Photo.GenerateMD5 (version.Uri);
+			version.MD5Sum = version_md5_sum; 
+
+			needs_commit = true;
+		}
+
+		if (needs_commit)
+			Commit (photo);
+	}
 	
 	// Dbus
 	public event ItemsAddedHandler ItemsAddedOverDBus;
@@ -750,11 +900,12 @@
 				photo = new Photo (id,
 						   Convert.ToInt64 (reader [1]),
 #if MONO_2_0
-						   new System.Uri (reader [2].ToString ()));
+						   new System.Uri (reader [2].ToString ()),
 #else
-						   new System.Uri (reader [2].ToString (), true));
+						   new System.Uri (reader [2].ToString (), true),
 #endif
-				
+						   reader [6].ToString ()
+				);
 				photo.Description = reader[3].ToString ();
 				photo.RollId = Convert.ToUInt32 (reader[4]);
 				photo.DefaultVersionId = Convert.ToUInt32 (reader[5]);
@@ -814,7 +965,8 @@
 				"photos.description, "		+
 				"photos.roll_id, "		+
 				"photos.default_version_id, "	+
-				"photos.rating "		+
+				"photos.rating, "		+
+				"photos.md5_sum "		+
 			"FROM photos " 				+
 			"WHERE uri LIKE :uri "		+
 			"AND uri NOT LIKE :uri_",
@@ -940,7 +1092,8 @@
 					     "photos.description, "		+
 				      	     "photos.roll_id, "   		+
 					     "photos.default_version_id, "	+
-					     "photos.rating "			+
+					     "photos.rating, "			+
+					     "photos.md5_sum "			+
 				      "FROM photos ");
 		
 		if (range != null) {

Modified: trunk/src/TagStore.cs
==============================================================================
--- trunk/src/TagStore.cs	(original)
+++ trunk/src/TagStore.cs	Sat Sep  6 18:52:15 2008
@@ -35,7 +35,8 @@
 	public static byte [] Serialize (Pixbuf pixbuf)
 	{
 		Pixdata pixdata = new Pixdata ();
-		pixdata.FromPixbuf (pixbuf, true); // FIXME GTK# shouldn't this be a constructor or something?
+		IntPtr raw_pixdata = pixdata.FromPixbuf (pixbuf, true); // FIXME GTK# shouldn't this be a constructor or something?
+									//       It's probably because we need the IntPtr to free it afterwards
 
 		uint data_length;
 		IntPtr raw_data = gdk_pixdata_serialize (ref pixdata, out data_length);
@@ -43,8 +44,8 @@
 		byte [] data = new byte [data_length];
 		Marshal.Copy (raw_data, data, 0, (int) data_length);
 		
-		GLib.Marshaller.Free (raw_data);
-
+		GLib.Marshaller.Free (new IntPtr[] { raw_data, raw_pixdata });
+		
 		return data;
 	}
 }

Modified: trunk/src/Updater.cs
==============================================================================
--- trunk/src/Updater.cs	(original)
+++ trunk/src/Updater.cs	Sat Sep  6 18:52:15 2008
@@ -274,7 +274,65 @@
 					"SELECT photo_id, version_id, name, uri, protected FROM {0}", tmp_photo_versions));
 			});
 
-			// Update to version 14.0
+			// Update to version 16.0
+			 AddUpdate (new Version (16,0), delegate () {
+				 string temp_table = MoveTableToTemp ("photos");
+  
+				 Execute ("CREATE TABLE photos ( " +
+					  "	id                 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,   " +
+					  "	time               INTEGER NOT NULL,	   	   " +
+					  "	uri		   STRING NOT NULL,		   " +
+					  "	description        TEXT NOT NULL,	           " +
+					  "	roll_id            INTEGER NOT NULL,		   " +
+					  "	default_version_id INTEGER NOT NULL,		   " +
+					  "	rating		   INTEGER NULL,		   " +
+					  "	md5_sum		   TEXT NULL  			   " +
+					  ")"
+				 );
+  
+				 Execute (string.Format ("INSERT INTO photos (id, time, uri, description, roll_id, " + 
+							 "default_version_id, rating, md5_sum) " + 
+							 "SELECT id, time, uri, description, roll_id, " +
+							 "       default_version_id, rating, '' " +
+							 "FROM   {0} ", 
+							 temp_table
+							)
+				 );
+
+
+				 string temp_versions_table = MoveTableToTemp ("photo_versions");
+
+				 Console.WriteLine("{0} - {1}", temp_table, temp_versions_table);
+
+				 Execute ("CREATE TABLE photo_versions (    	" +
+					  "      photo_id        INTEGER,  	" +
+					  "      version_id      INTEGER,  	" +
+					  "      name            STRING,    	" +
+					  "	uri		STRING NOT NULL," +
+					  "	md5_sum		STRING NOT NULL," +
+					  "	protected	BOOLEAN		" +
+					  ")");
+
+				 Execute (string.Format ("INSERT INTO photo_versions (photo_id, version_id, name, uri, md5_sum, protected) " + 
+							 "SELECT photo_id, version_id, name, uri, '', protected " +
+							 "FROM   {0} ", 
+							 temp_versions_table
+							)
+				 );
+
+				 // This is kind of hacky but should be a lot faster on
+				 // large photo databases
+				 Execute (string.Format ("INSERT INTO jobs (job_type, job_options, run_at, job_priority) " +
+							 "SELECT '{0}', id, {1}, {2} " +
+							 "FROM   photos ",
+							 typeof(Jobs.CalculateHashJob).ToString (),
+							 FSpot.Utils.DbUtils.UnixTimeFromDateTime (DateTime.Now),
+							 0
+							)
+				 );
+			 }, true);
+
+			 // Update to version 17.0
 			//AddUpdate (new Version (14,0),delegate () {
 			//	do update here
 			//});

Modified: trunk/src/f-spot.glade
==============================================================================
--- trunk/src/f-spot.glade	(original)
+++ trunk/src/f-spot.glade	Sat Sep  6 18:52:15 2008
@@ -4408,6 +4408,22 @@
               </packing>
             </child>
             <child>
+              <widget class="GtkCheckButton" id="duplicate_check">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="label" translatable="yes">Include Duplicates</property>
+                <property name="use_underline">True</property>
+                <property name="response_id">0</property>
+                <property name="active">False</property>
+                <property name="draw_indicator">True</property>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">3</property>
+              </packing>
+            </child>
+            <child>
               <widget class="GtkCheckButton" id="copy_check">
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
@@ -4420,7 +4436,7 @@
               <packing>
                 <property name="expand">False</property>
                 <property name="fill">False</property>
-                <property name="position">3</property>
+                <property name="position">4</property>
               </packing>
             </child>
             <child>
@@ -4437,7 +4453,7 @@
                 <property name="expand">False</property>
                 <property name="fill">False</property>
                 <property name="pack_type">GTK_PACK_END</property>
-                <property name="position">3</property>
+                <property name="position">5</property>
               </packing>
             </child>
           </widget>



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