Revamped Duplicates Patch



Hi list,

Last weekend I took the Duplicates patch that was sent to this list a
few months ago, and updated it so works with current cvs head.  I added
some updater code so that it adds a new md5sum field to the photos
table, so make sure you take a backup of your database first.

The patch allows you to detect duplicates in an existing photo
collection.  Next to that, it is possible to omit duplicate pictures
from import, or you can tag them as "Duplicate" during the same
process.  

Give it a try, if you're interested.

Regards,
Thomas
--- /dev/null	2006-03-02 16:31:47.000000000 +0100
+++ src/DuplicatesFinder.cs	2006-03-13 21:14:40.000000000 +0100
@@ -0,0 +1,155 @@
+using System;
+using System.Collections;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace FSpot {
+	public delegate void DuplicatesFinderEnd (bool success);
+
+	public class DuplicatesFinder {
+		private System.Threading.Thread duplicates_thread;
+		private FSpot.ThreadProgressDialog progress_dialog_duplicates;
+		private ArrayList duplicates;
+		private bool end_duplicates;
+		private uint duplicates_timer = 0;
+		private System.Threading.Thread md5_thread;
+		private FSpot.ThreadProgressDialog progress_dialog_md5;
+		private bool end_computeMD5 = false;
+		private uint computeMD5_timer = 0;
+
+		private Db db;
+		private int[] selected_ids;
+		private FSpot.PhotoQuery query;
+    
+		public event DuplicatesFinderEnd SearchFinished;
+
+		public DuplicatesFinder (Db db, FSpot.PhotoQuery query)
+		{
+			this.db = db;
+			this.query = query;
+		}
+
+		public void StartFind (int[] selected_ids)
+		{
+			this.selected_ids = selected_ids;
+			md5_thread = new System.Threading.Thread (new System.Threading.ThreadStart (this.LookupDuplicates));
+			md5_thread.Name = Mono.Posix.Catalog.GetString ("Creating image unique identifiers");
+			progress_dialog_md5 = new FSpot.ThreadProgressDialog (md5_thread, selected_ids.Length);
+			progress_dialog_md5.Start();
+			StartComputeMD5Timer ();
+		}
+
+		private bool HandleDuplicatesTimer ()
+		{
+			if (!end_duplicates && duplicates_thread.IsAlive)
+				return true;
+
+			Console.WriteLine ("Duplicates Timer ...");
+
+			end_duplicates = false;
+			duplicates_timer = 0;
+			
+			foreach (int num in duplicates) {
+				query.Commit (num);
+			}
+			
+			if (SearchFinished != null) {
+				SearchFinished (true);
+			}
+			return false;
+		}
+
+		private void StartDuplicatesTimer ()
+		{
+			if (duplicates_timer == 0)
+				duplicates_timer = GLib.Timeout.Add (100, new GLib.TimeoutHandler (HandleDuplicatesTimer));
+		}
+
+		private bool HandleComputeMD5Timer ()
+		{
+			Console.WriteLine ("MD5 Timer ...");
+			if (md5_thread.IsAlive) {
+				Console.WriteLine ("The MD5 thread is alive ...");
+			}
+			
+			if (end_computeMD5 || !md5_thread.IsAlive) {
+				if (progress_dialog_md5 != null) {
+					progress_dialog_md5.Destroy ();
+				}
+				
+				computeMD5_timer = 0;
+				
+				if (end_computeMD5) {
+					end_computeMD5 = false;
+					StartDuplicatesTimer (); 
+					
+					lock (this) {
+						end_duplicates = true;
+					}
+				} 
+				else if (SearchFinished != null) {
+					SearchFinished (false);
+				}
+				return false;
+			}			
+			return true;
+		}
+
+		private void StartComputeMD5Timer ()
+		{
+			if (computeMD5_timer == 0)
+				computeMD5_timer = GLib.Timeout.Add (1000, new GLib.TimeoutHandler (HandleComputeMD5Timer));
+		}
+
+		/* FIXME: We should use a cache of all photos MD5? */
+		public static bool IsDuplicate (Photo photo) 
+		{
+			Photo[] photos;
+			photos = MainWindow.Toplevel.Database.Photos.Query (null, null);
+			Tag duplicate_tag = MainWindow.Toplevel.Database.Tags.Duplicate;
+			
+			if (photos == null)
+				return false;
+			
+			foreach (Photo p in photos) {
+				if (p.MD5Sum.CompareTo(photo.MD5Sum) == 0 && p.Id != photo.Id
+				    && !p.HasTag (duplicate_tag) && !photo.HasTag (duplicate_tag)) {
+					return true;
+				}
+			}            
+			return false;
+		} 
+
+		private void LookupDuplicates () 
+		{
+			duplicates = new ArrayList ();
+			if (db.Tags.Duplicate == null) {
+				db.Tags.CreateDuplicateTag ();
+			}
+		
+			int counter = 1;
+			foreach (int num in selected_ids) {
+				Photo photo = query.Photos [num];
+				progress_dialog_md5.Message = System.String.Format (Mono.Posix.Catalog.GetString ("Looking For Duplicates of {0}"), photo.Name);
+				progress_dialog_md5.Fraction = counter / (double) selected_ids.Length;
+				progress_dialog_md5.ProgressText = System.String.Format (Mono.Posix.Catalog.GetString ("{0} of {1}"), counter, selected_ids.Length);
+
+				if (IsDuplicate(photo)) {
+					duplicates.Add (num);
+					photo.AddTag(db.Tags.Duplicate);
+				}
+				counter++;
+			}
+		
+			progress_dialog_md5.Message = Mono.Posix.Catalog.GetString ("Done Looking For Duplicates");
+			progress_dialog_md5.Fraction = 1.0;
+			progress_dialog_md5.ButtonLabel = Gtk.Stock.Ok;
+
+			lock (this) {
+				end_computeMD5 = true;
+			}
+		}
+
+	}
+}
Index: src/ImportCommand.cs
===================================================================
RCS file: /cvs/gnome/f-spot/src/ImportCommand.cs,v
retrieving revision 1.59
diff -u -b -r1.59 ImportCommand.cs
--- src/ImportCommand.cs	13 Mar 2006 05:51:18 -0000	1.59
+++ src/ImportCommand.cs	13 Mar 2006 21:45:57 -0000
@@ -298,6 +298,7 @@
 	[Glade.Widget] Gtk.CheckButton attach_check;
 	[Glade.Widget] Gtk.CheckButton recurse_check;
 	[Glade.Widget] Gtk.CheckButton copy_check;
+	[Glade.Widget] Gtk.CheckButton duplicate_check;
 	[Glade.Widget] Gtk.Button ok_button;
 	[Glade.Widget] Gtk.Image tag_image;
 	[Glade.Widget] Gtk.Label tag_label;
@@ -312,6 +313,9 @@
 	SourceMenu menu;
 
 	int total;
+    
+	private int duplicates;
+
 	PhotoStore store;
 	FSpot.Delay step;
 	
@@ -319,7 +323,8 @@
 	ImportBackend importer;
 	IconView tray;
 
-	string loading_string;
+	string default_loading = Mono.Posix.Catalog.GetString ("Loading {0} of {1}");
+	string duplicate_loading = Mono.Posix.Catalog.GetString ("Importing {0} of {1}, {2} duplicate(s)");
 
 	string import_path;
 	public string ImportPath {
@@ -371,7 +376,6 @@
 	{
 		main_window = mw;
 		step = new FSpot.Delay (10, new GLib.IdleHandler (Step));
-		loading_string = Catalog.GetString ("Loading {0} of {1}");
 	}
 
 	private void HandleDialogResponse (object obj, ResponseArgs args)
@@ -383,12 +387,16 @@
 		}
 	}
 
-	private void UpdateProgressBar (int count, int total)
+	private void UpdateProgressBar (int count, int total, int duplicates)
 	{
 		if (progress_bar == null)
 			return;
 
-		progress_bar.Text = String.Format (loading_string, count, total);
+		if (duplicates > 0)
+			progress_bar.Text = String.Format (duplicate_loading, count, total, duplicates);
+		else 
+			progress_bar.Text = String.Format (default_loading,   count, total);
+
 		progress_bar.Fraction = (double) count / System.Math.Max (total, 1);
 	}
 
@@ -406,9 +414,13 @@
 		int count;
 		bool ongoing = true;
 
+	        //Console.WriteLine ("Start importing the photo ...");
 		if (importer == null)
 			return false;
 		
+		if (MainWindow.Toplevel.Database.Tags.Duplicate == null)
+			MainWindow.Toplevel.Database.Tags.CreateDuplicateTag ();
+
 		try {
 			// FIXME this is really just an incredibly ugly way of dealing
 			// with the recursive DoImport loops we sometimes get into
@@ -419,14 +431,24 @@
 
 		if (photo == null || thumbnail == null) {
 			Console.WriteLine ("Could not import file");
-		} else {
-			//icon_scrolled.Visible = true;
-			collection.Add (photo);
+		} 
+	        else {
+			if (FSpot.DuplicatesFinder.IsDuplicate (photo)) {
+	                	//Console.WriteLine ("Photo: {0} duplicated\n", photo.Name);
+        			photo.AddTag (MainWindow.Toplevel.Database.Tags.Duplicate);
+				store.Commit (photo);
 		
-			//grid.AddThumbnail (thumbnail);
-			if (count < total)
-				UpdateProgressBar (count + 1, total);
+				duplicates++;
+				if (duplicate_check.Active)
+					collection.Add (photo);
+				else
+					store.Remove (photo);
+			}
+			else {
+				collection.Add (photo);
+			}
 
+			UpdateProgressBar (count, total, duplicate_check.Active ? duplicates : 0);
 			thumbnail.Dispose ();
 		}
 
@@ -454,10 +476,11 @@
 		this.importer = imp;
 		AllowFinish = false;
 		
+		duplicates = 0;
 		total = importer.Prepare ();
 
 		if (total > 0)
-			UpdateProgressBar (1, total);
+			UpdateProgressBar (1, total, 0);
 		
 		collection.Clear ();
 		collection.Capacity = total;
@@ -490,7 +513,6 @@
 			importer.Finish ();
 		
 		importer = null;
-
 	}
 	
 	public void HandleTagToggled (object o, EventArgs args) 
@@ -539,15 +561,20 @@
 	private void HandleTagMenuSelected (Tag t) 
 	{
 		tag_selected = t;
-		//tag_image.Pixbuf = t.Icon;
-		//tag_label.Text = t.Name;
 	}
 
-
 	private void HandleRecurseToggled (object sender, System.EventArgs args)
 	{
 		this.Cancel ();
 		this.Dialog.Sensitive = false;
+		Idle.Add (new IdleHandler (Start));
+	}
+
+
+	private void HandleDuplicateToggled (object sender, System.EventArgs args)
+	{
+		this.Cancel ();
+		this.Dialog.Sensitive = false;
 	       
 		Idle.Add (new IdleHandler (Start));
 	}
@@ -570,6 +597,7 @@
 		//import_folder_entry.Activated += HandleEntryActivate;
 		recurse_check.Toggled += HandleRecurseToggled;
 		copy_check.Toggled += HandleRecurseToggled;
+		duplicate_check.Toggled += HandleDuplicateToggled;
 
 		menu = new SourceMenu (this);
 		source_option_menu.Menu = menu;
@@ -759,7 +787,7 @@
 	{
 		Db db = new Db (db_path, true);
 
-		ImportCommand command = new ImportCommand ();
+		ImportCommand command = new ImportCommand (main_window);
 
 		command.ImportFromPath (db.Photos, directory_path, true);
 
Index: src/MainWindow.cs
===================================================================
RCS file: /cvs/gnome/f-spot/src/MainWindow.cs,v
retrieving revision 1.286
diff -u -b -r1.286 MainWindow.cs
--- src/MainWindow.cs	6 Mar 2006 22:13:57 -0000	1.286
+++ src/MainWindow.cs	13 Mar 2006 21:46:00 -0000
@@ -84,6 +84,7 @@
 	// Find
 	[Glade.Widget] MenuItem find_tag;
 	[Glade.Widget] CheckMenuItem find_untagged;
+	[Glade.Widget] MenuItem find_duplicates;
 	
 	// Tags
 	[Glade.Widget] MenuItem edit_selected_tag;
@@ -1667,6 +1668,34 @@
 		if (view_mode == ModeType.IconView)
 			icon_view.QueueDraw ();
 	}
+
+	void HandleFindDuplicates (object sender, EventArgs args) {
+        find_duplicates.Sensitive = false;
+        
+        FSpot.DuplicatesFinder finder = new FSpot.DuplicatesFinder (db, query);        
+
+        if (db.Tags.Duplicate == null) {
+            db.Tags.CreateDuplicateTag ();
+            tag_selection_widget.Update ();
+        }
+
+        if (!(SelectedIds().Length > 0))
+            icon_view.SelectAllCells ();
+
+        System.Console.WriteLine ("Looking for duplicates ...");
+
+        finder.SearchFinished += new FSpot.DuplicatesFinderEnd (HandleEndDuplicates);
+        finder.StartFind (SelectedIds());
+    }
+
+    void HandleEndDuplicates (Boolean success) {
+        if (success) {
+            tag_selection_widget.Select (db.Tags.Duplicate);
+            UpdateQuery ();
+            SetViewMode (ModeType.IconView);
+        }
+        find_duplicates.Sensitive = true;
+    }
 
 	public void HandleMergeTagsCommand (object obj, EventArgs args)
 	{
Index: src/Makefile.am
===================================================================
RCS file: /cvs/gnome/f-spot/src/Makefile.am,v
retrieving revision 1.62
diff -u -b -r1.62 Makefile.am
--- src/Makefile.am	26 Feb 2006 22:37:08 -0000	1.62
+++ src/Makefile.am	13 Mar 2006 21:46:01 -0000
@@ -23,6 +23,7 @@
 	$(srcdir)/Delay.cs			\
 	$(srcdir)/DirectoryAdaptor.cs		\
 	$(srcdir)/DirectoryCollection.cs	\
+	$(srcdir)/DuplicatesFinder.cs	\
 	$(srcdir)/ExceptionDialog.cs		\
 	$(srcdir)/Exif.cs			\
 	$(srcdir)/FlickrExport.cs		\
Index: src/MetaStore.cs
===================================================================
RCS file: /cvs/gnome/f-spot/src/MetaStore.cs,v
retrieving revision 1.2
diff -u -b -r1.2 MetaStore.cs
--- src/MetaStore.cs	2 Mar 2006 03:17:23 -0000	1.2
+++ src/MetaStore.cs	13 Mar 2006 21:46:01 -0000
@@ -35,6 +35,7 @@
 	private const string version = "F-Spot Version";
 	private const string db_version = "F-Spot Database Version";
 	private const string hidden = "Hidden Tag Id";
+	private const string duplicate = "Duplicate Tag Id";
 
 	public MetaItem FSpotVersion {
 		get { return GetByName (version); }
@@ -48,6 +49,10 @@
 		get { return GetByName (hidden); }
 	}
 
+	public MetaItem DuplicateTagId {
+		get { return GetByName (duplicate); }
+	}
+
 	private MetaItem GetByName (string name)
 	{
 		foreach (MetaItem i in this.item_cache.Values)
@@ -87,8 +92,26 @@
 		try {
 			SqliteDataReader reader = command.ExecuteReader ();
 		
-			if (reader.Read ())
+			if (reader.Read ()) {
 				Create (hidden, reader [0].ToString ());
+			}
+
+			reader.Close ();
+		} catch (Exception e) {}
+	
+		command.Dispose ();
+
+		// Get the duplicate tag id, if it exists
+		command = new SqliteCommand ();
+		command.Connection = Connection;
+		command.CommandText = "SELECT id FROM tags WHERE name = 'Duplicate'";
+		
+		try {
+			SqliteDataReader reader = command.ExecuteReader ();
+		
+			if (reader.Read ()) {
+				Create (duplicate, reader [0].ToString ());
+			}
 
 			reader.Close ();
 		} catch (Exception e) {}
Index: src/PhotoStore.cs
===================================================================
RCS file: /cvs/gnome/f-spot/src/PhotoStore.cs,v
retrieving revision 1.91
diff -u -b -r1.91 PhotoStore.cs
--- src/PhotoStore.cs	7 Mar 2006 17:54:54 -0000	1.91
+++ src/PhotoStore.cs	13 Mar 2006 21:46:03 -0000
@@ -67,6 +67,7 @@
 		return Compare (this, photo);
 	}
 	
+	// FIXME: With md5sum field this could be easy
 	public static int Compare (Photo photo1, Photo photo2)
 	{
 		int result = photo1.Id.CompareTo (photo2.Id);
@@ -154,7 +155,7 @@
 	private string directory_path;
 	private string name;
 
-	private string Path {
+	public string Path {
 		get {
 			return directory_path + "/" + name;
 		}
@@ -202,6 +203,16 @@
 		}
 	}
 
+	private string md5sum;
+	public string MD5Sum {
+		get {
+			return md5sum;
+		}
+		set {
+			md5sum = value;
+		}
+	}
+
 	// Version management
 	public const int OriginalVersionId = 1;
 	private uint highest_version_id;
@@ -547,7 +558,33 @@
 		return version;
 	}
 
+	public void SetMD5Sum ()
+	{
+		md5sum = GetMD5Sum (this.Path);
+	}
+
+	public static string GetMD5Sum (string filename)
+	{
+		FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read);
+		System.Security.Cryptography.MD5 md5ServiceProvider = new System.Security.Cryptography.MD5CryptoServiceProvider();
+		byte[] md5 = md5ServiceProvider.ComputeHash(fs);
+
+		StringBuilder hash = new StringBuilder();
+		for (int pos = 0; pos < md5.Length; pos++) {
+			hash.Append(md5[pos].ToString("X2").ToLower());
+		}
+		return hash.ToString ();
+	}
+
 	// Constructor
+	public Photo (uint id, long unix_time, string directory_path, string name, string description, uint default_version_id, string md5sum) 
+		: this (id, unix_time, directory_path, name)
+	{
+		this.description = description;
+		this.default_version_id = default_version_id;
+		this.md5sum = md5sum;  
+	}
+     
 	public Photo (uint id, long unix_time, string directory_path, string name)
 		: base (id)
 	{
@@ -557,6 +594,7 @@
 		this.name = name;
 
 		description = "";
+		md5sum = ""; 
 
 		// Note that the original version is never stored in the photo_versions table in the
 		// database.
@@ -564,10 +602,7 @@
 	}
 
 	public Photo (uint id, long unix_time, string path)
-		: this (id, unix_time,
-
-			System.IO.Path.GetDirectoryName (path),
-			System.IO.Path.GetFileName (path))
+		: this (id, unix_time, System.IO.Path.GetDirectoryName (path), System.IO.Path.GetFileName (path))
 	{
 	}
 
@@ -657,7 +692,8 @@
 			"       directory_path     STRING NOT NULL,		   " +
 			"       name               STRING NOT NULL,		   " +
 			"       description        TEXT NOT NULL,	           " +
-			"       default_version_id INTEGER NOT NULL		   " +
+			"	default_version_id INTEGER NOT NULL,	   " +
+			"	md5sum             STRING NOT NULL		   " +
 			")";
 
 		command.ExecuteNonQuery ();
@@ -704,23 +740,28 @@
 		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 directory = System.IO.Path.GetDirectoryName (newPath);
+		string name = System.IO.Path.GetFileName (newPath);
+		string md5sum = Photo.GetMD5Sum(newPath);
+        
 		SqliteCommand command = new SqliteCommand ();
 		command.Connection = Connection;
 
 		command.CommandText = String.Format ("INSERT INTO photos (time, " +
-						     "directory_path, name, description, default_version_id) " +
-						     "       VALUES ({0}, '{1}', '{2}', '{3}', {4})",
+						     "directory_path, name, description, default_version_id, md5sum) " +
+						     "       VALUES ({0}, '{1}', '{2}', '{3}', {4}, '{5}')",
 						     unix_time,
-						     SqlString (System.IO.Path.GetDirectoryName (newPath)),
-						     SqlString (System.IO.Path.GetFileName (newPath)),
+						     SqlString (directory),
+						     SqlString (name),
 						     SqlString (description),
-						     Photo.OriginalVersionId);
+						     Photo.OriginalVersionId,
+                             md5sum);
 
 		command.ExecuteScalar ();
 		command.Dispose ();
 
 		uint id = (uint) Connection.LastInsertRowId;
-		Photo photo = new Photo (id, unix_time, newPath);
+		Photo photo = new Photo (id, unix_time, directory, name, description, Photo.OriginalVersionId, md5sum);
 		AddToCache (photo);
 		photo.Loaded = true;
 
@@ -791,19 +832,11 @@
 				
 				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);
-			*/
-
-
 		}
 	}
 
-	private void GetAllTags () {
+	private void GetAllTags () 
+	{
 			SqliteCommand command = new SqliteCommand ();
 		command.Connection = Connection;
 		command.CommandText = String.Format ("SELECT photo_id, tag_id " +
@@ -908,7 +941,8 @@
 						     "       directory_path,                       " +
 						     "       name,                                 " +
 						     "       description,                          " +
-						     "       default_version_id                    " +
+						     "       default_version_id,                   " +
+						     "       md5sum                                " +
 						     "     FROM photos                             " +
 						     "     WHERE id = {0}                          ",
 						     id);
@@ -918,10 +952,11 @@
 			photo = new Photo (id,
 					   Convert.ToInt64 (reader [0]),
 					   reader [1].ToString (),
-					   reader [2].ToString ());
+					   reader [2].ToString (),
+					   reader[3].ToString (),
+					   Convert.ToUInt32 (reader[4]),
+					   reader[5].ToString ());
 
-			photo.Description = reader[3].ToString ();
-			photo.DefaultVersionId = Convert.ToUInt32 (reader[4]);
 			AddToCache (photo);
 		}
 
@@ -953,7 +988,8 @@
 		command.CommandText = String.Format ("SELECT id,                                   " +
 				                     "       time,                                 " +
 						     "       description,                          " +
-						     "       default_version_id                    " +
+						     "       default_version_id,                   " +
+						     "       md5sum                                " +
 						     "  FROM photos                                " +
 						     " WHERE directory_path = \"{0}\"              " +
 						     "   AND name = \"{1}\"                        ",
@@ -966,10 +1002,11 @@
 			photo = new Photo (Convert.ToUInt32 (reader [0]),
 					   Convert.ToInt64 (reader [1]),
 					   directory_path,
-					   filename);
+					   filename,
+					   reader[2].ToString(),
+					   Convert.ToUInt32 (reader[3]),
+					   reader[4].ToString ());
 
-			photo.Description = reader[2].ToString ();
-			photo.DefaultVersionId = Convert.ToUInt32 (reader[3]);
 			AddToCache (photo);
 		}
 
@@ -1070,13 +1107,17 @@
 
 		SqliteCommand command = new SqliteCommand ();
 		command.Connection = Connection;
-		command.CommandText = String.Format ("UPDATE photos SET description = '{0}',     " +
+		command.CommandText = String.Format (
+						"UPDATE photos " + 
+						" SET description = '{0}',     " +
 						     "                  default_version_id = {1}, " +
-						     "                  time = {2} " +
-						     "              WHERE id = {3}",
+						"     time = {2}, " +
+						"     md5sum = '{3}' " +
+						" WHERE id = {4}",
 						     SqlString (photo.Description),
 						     photo.DefaultVersionId,
 						     DbUtils.UnixTimeFromDateTime (photo.Time),
+						SqlString (photo.MD5Sum),
 						     photo.Id);
 		command.ExecuteNonQuery ();
 		command.Dispose ();
@@ -1090,6 +1131,7 @@
 		command.Dispose ();
 
 		foreach (Tag tag in photo.Tags) {
+            
 			command = new SqliteCommand ();
 			command.Connection = Connection;
 			command.CommandText = String.Format ("INSERT INTO photo_tags (photo_id, tag_id) " +
@@ -1173,10 +1215,10 @@
 				photo = new Photo (id,
 						   Convert.ToInt64 (reader [1]),
 						   reader [2].ToString (),
-						   reader [3].ToString ());
-				
-				photo.Description = reader[4].ToString ();
-				photo.DefaultVersionId = Convert.ToUInt32 (reader[5]);		 
+						   reader [3].ToString (),
+                           reader [4].ToString (),
+                           Convert.ToUInt32 (reader[5]),
+                           reader [6].ToString ());
 				
 				version_list.Add (photo);
 			}
@@ -1212,7 +1254,8 @@
 						     "       photos.directory_path,              " +
 						     "       photos.name,                        " +
 						     "       photos.description,                 " +
-						     "       photos.default_version_id           " +
+						     "       photos.default_version_id,          " +
+                             "       photos.md5sum                       " +
 						     "     FROM photos                           " +
 						     "     WHERE directory_path = \"{0}\"", dir.FullName);
 
@@ -1271,7 +1314,8 @@
 				      "       photos.directory_path,              " +
 				      "       photos.name,                        " +
 				      "       photos.description,                 " +
-				      "       photos.default_version_id           " +
+				      "       photos.default_version_id,          " +
+                      "       photos.md5sum                       " +
 				      "     FROM photos                      ");
 		
 		if (range != null) {
Index: src/TagStore.cs
===================================================================
RCS file: /cvs/gnome/f-spot/src/TagStore.cs,v
retrieving revision 1.28
diff -u -b -r1.28 TagStore.cs
--- src/TagStore.cs	17 Feb 2006 12:11:21 -0000	1.28
+++ src/TagStore.cs	13 Mar 2006 21:46:03 -0000
@@ -297,6 +297,13 @@
 		return (Tag []) (l.ToArray (typeof (Tag)));
 	}
 
+	private Tag duplicate;
+	public Tag Duplicate {
+		get {
+			return duplicate;
+		}
+	}
+
 	// In this store we keep all the items (i.e. the tags) in memory at all times.  This is
 	// mostly to simplify handling of the parent relationship between tags, but it also makes it
 	// a little bit faster.  We achieve this by passing "true" as the cache_is_immortal to our
@@ -356,8 +363,14 @@
 		reader.Close ();
 		command.Dispose ();
 
+	        //System.Console.WriteLine ( FSpot.Core.Database.Meta.HiddenTagId.Value ) ;
 		if (FSpot.Core.Database.Meta.HiddenTagId.Value != null)
 			hidden = LookupInCache ((uint) FSpot.Core.Database.Meta.HiddenTagId.ValueAsInt) as Tag;
+
+	        //FIXME: implement DuplicateTagId
+	        //System.Console.WriteLine ( FSpot.Core.Database.Meta.DuplicateTagId.Value ) ;
+		if (FSpot.Core.Database.Meta.DuplicateTagId.Value != null)
+			duplicate = LookupInCache ((uint) FSpot.Core.Database.Meta.DuplicateTagId.ValueAsInt) as Tag;
 	}
 
 
@@ -396,6 +409,8 @@
 		FSpot.Core.Database.Meta.HiddenTagId.ValueAsInt = (int) hidden_tag.Id;
 		FSpot.Core.Database.Meta.Commit (FSpot.Core.Database.Meta.HiddenTagId);
 
+		CreateDuplicateTag();
+        
 		Tag people_category = CreateCategory (RootCategory, Catalog.GetString ("People"));
 		people_category.StockIconName = "f-spot-people.png";
 		people_category.SortPriority = -8;
@@ -479,6 +494,20 @@
 		return new_category;
 	}
 
+	public Tag CreateDuplicateTag()
+	{
+		Tag duplicate_tag = CreateTag (RootCategory, "Duplicate");
+		duplicate_tag.StockIconName = "f-spot-hidden.png"; //FIXME: let someone create proper icon ;-)
+		duplicate_tag.SortPriority = -11;
+		this.duplicate = duplicate_tag;
+		Commit (duplicate_tag);
+
+		FSpot.Core.Database.Meta.DuplicateTagId.ValueAsInt = (int) duplicate_tag.Id;
+		FSpot.Core.Database.Meta.Commit (FSpot.Core.Database.Meta.DuplicateTagId);
+
+		return duplicate;
+	}
+
 	public override DbItem Get (uint id)
 	{
 		if (id == 0)
@@ -495,6 +524,11 @@
 		    category.Children.Length > 0)
 			throw new InvalidTagOperationException (category, "Cannot remove category that contains children");
 
+		// FIXME: Hack!
+		if (string.Compare (((Tag) item).Name, Catalog.GetString("Duplicate")) == 0) {
+			duplicate = null;
+		}
+        
 		RemoveFromCache (item);
 		
 		((Tag)item).Category = null;
Index: src/Updater.cs
===================================================================
RCS file: /cvs/gnome/f-spot/src/Updater.cs,v
retrieving revision 1.4
diff -u -b -r1.4 Updater.cs
--- src/Updater.cs	13 Feb 2006 07:27:36 -0000	1.4
+++ src/Updater.cs	13 Mar 2006 21:46:04 -0000
@@ -62,10 +62,60 @@
 				System.Console.WriteLine ("Other tag restored.  Sorry about that!");
 			});
 			
-			// Update from version 2 to 3
-			//AddUpdate (delegate (SqliteConnection connection) {
-			//	do update here
-			//});
+			// Update from version 2 to 3: add md5sum column to photos table
+			AddUpdate (delegate (SqliteConnection connection) {
+				bool has_md5 = true;
+
+				try {
+					ExecuteNonQuery("SELECT md5sum FROM photos WHERE 1 = 2");
+				} catch (Exception e) {
+					has_md5 = false;
+				}
+
+				if (has_md5)
+					return;
+				System.Console.WriteLine("Adding MD5 field to photos");
+
+				string temp_table = MoveTableToTemp ("photos");
+
+				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,	    " +
+						"  default_version_id INTEGER NOT NULL,	" +
+						"  md5sum             STRING NOT NULL		" +
+						")");
+
+				ExecuteScalar (String.Format (  "INSERT INTO photos (id, time, directory_path, " + 
+								"                    name, description, default_version_id, md5sum)" + 
+								"SELECT id, time, directory_path, " +
+								"       name, description, default_version_id, '' " +
+								"FROM   {0} ", 
+								temp_table));
+
+				SqliteCommand command = new SqliteCommand ();
+				command.Connection = db.Connection;
+				command.CommandText = "SELECT id, directory_path, name FROM photos";
+				SqliteDataReader reader = command.ExecuteReader ();
+    
+				while (reader.Read ()) {
+					uint photo_id = Convert.ToUInt32 (reader [0]);
+					string directory = reader[1].ToString();
+					string name = reader[2].ToString();
+
+					string path = System.IO.Path.Combine(directory, name);
+
+					ExecuteScalar (String.Format (  "UPDATE photos " + 
+									"SET md5sum = '{0}' " +
+									"WHERE id = {1}",
+									Photo.GetMD5Sum(path),
+									photo_id));
+					}
+
+					command.Dispose ();
+				});
 		}
 
 		public static void Run (Db database)
Index: src/f-spot.glade
===================================================================
RCS file: /cvs/gnome/f-spot/src/f-spot.glade,v
retrieving revision 1.159
diff -u -b -r1.159 f-spot.glade
--- src/f-spot.glade	25 Feb 2006 08:54:05 -0000	1.159
+++ src/f-spot.glade	13 Mar 2006 21:46:26 -0000
@@ -7658,6 +7658,15 @@
 		  </child>
 
 		  <child>
+		    <widget class="GtkMenuItem" id="find_duplicates">
+		      <property name="visible">True</property>
+		      <property name="label" translatable="yes">_Find Duplicates</property>
+		      <property name="use_underline">True</property>
+		      <signal name="activate" handler="HandleFindDuplicates" last_modification_time="Tue, 10 Aug 2004 07:08:24 GMT"/>
+		    </widget>
+		  </child>
+
+		  <child>
 		    <widget class="GtkSeparatorMenuItem" id="separator15">
 		      <property name="visible">True</property>
 		    </widget>
@@ -9325,6 +9334,12 @@
 	  <property name="spacing">6</property>
 
 	  <child>
+	    <widget class="GtkHBox" id="hbox70">
+	      <property name="visible">True</property>
+	      <property name="homogeneous">False</property>
+	      <property name="spacing">0</property>
+
+	      <child>
 	    <widget class="GtkCheckButton" id="recurse_check">
 	      <property name="visible">True</property>
 	      <property name="can_focus">True</property>
@@ -9335,6 +9350,32 @@
 	      <property name="active">True</property>
 	      <property name="inconsistent">False</property>
 	      <property name="draw_indicator">True</property>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">False</property>
+		  <property name="fill">False</property>
+		</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="relief">GTK_RELIEF_NORMAL</property>
+		  <property name="focus_on_click">True</property>
+		  <property name="active">False</property>
+		  <property name="inconsistent">False</property>
+		  <property name="draw_indicator">True</property>
+		</widget>
+		<packing>
+		  <property name="padding">10</property>
+		  <property name="expand">False</property>
+		  <property name="fill">False</property>
+		</packing>
+	      </child>
 	    </widget>
 	    <packing>
 	      <property name="padding">0</property>


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