Re: Revamped Duplicates Patch



Hi all,

Thanks for the comments on the duplicates patch.  I think I managed to
get a lot of the raised issues out of the way.

> It would be worth while to look over the critique of the original patch
> http://mail.gnome.org/archives/f-spot-list/2005-October/msg00044.html
> as well (Gabriel already covered a lot of it though).
> 

* The md5 sum field of photo is no longer a string but remains a byte
array
* I have marked the updater code as "slow"
* all md5sums are kept into a hashtable making looking for duplicates
quite a lot faster, because it omits the "all photos" query.  There is a
temporary hack though: the md5sums are stored as strings into the
hashtable, because byte arrays didn't seem to be very good hashtable
keys.  This could easily be improved though.
* I dropped the use of the special "Duplicate" tag.  Finding duplicates
now works like querying untagged pictures.
* the "Include Duplicates" checkbox in the import window no longer
appears next to the other checkboxes but below them.

I hope this removes most of the issues raised, but feel free to send
additional comments if you have any.

Regards,
Thomas

* src/ImportCommand.cs: Added "Include Duplicates" checkbox, and update progressbar text accordingly

* src/MainWindow.cs: Added method to find duplicates from Main Window

* src/PhotoQuery.cs: Added Duplicate property to allow searching for duplicates

* src/PhotoStore.cs: Added MD5 Sum field to Photo and updated all methods accordingly, implemented some additional method to make querying duplicates easy

* src/QueryDisplay.cs: Reflect change in PhotoQuery.cs, so that QueryDisplay shows when you are looking for Duplicates

* src/Updater.cs: Implemented Updater code to add md5 field to photos

* src/f-spot.glade: Import dialog has duplicate checkbox, Main window has "Find Duplicates" menu item


Patch based on a patch by Alvaro Del Castillo 
Index: src/ImportCommand.cs
===================================================================
RCS file: /cvs/gnome/f-spot/src/ImportCommand.cs,v
retrieving revision 1.60
diff -u -r1.60 ImportCommand.cs
--- src/ImportCommand.cs	13 Mar 2006 21:30:49 -0000	1.60
+++ src/ImportCommand.cs	21 Mar 2006 06:56:33 -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,6 +414,7 @@
 		int count;
 		bool ongoing = true;
 
+	        //Console.WriteLine ("Start importing the photo ...");
 		if (importer == null)
 			return false;
 		
@@ -419,16 +428,22 @@
 
 		if (photo == null || thumbnail == null) {
 			Console.WriteLine ("Could not import file");
-		} else {
-			//icon_scrolled.Visible = true;
-			collection.Add (photo);
-		
-			//grid.AddThumbnail (thumbnail);
+		} 
+	        else {
+			if (store.IsDuplicate (photo)) {
+				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 ();
 		}
-		if (count < total)
-			UpdateProgressBar (count + 1, total);
 
 		if (ongoing && total > 0)
 			return true;
@@ -454,10 +469,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;
@@ -488,9 +504,8 @@
 	{
 		if (importer != null)
 			importer.Finish ();
-		
+					
 		importer = null;
-
 	}
 	
 	public void HandleTagToggled (object o, EventArgs args) 
@@ -539,12 +554,17 @@
 	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 HandleRecurseToggled (object sender, System.EventArgs args)
+	private void HandleDuplicateToggled (object sender, System.EventArgs args)
 	{
 		this.Cancel ();
 		this.Dialog.Sensitive = false;
@@ -570,6 +590,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;
@@ -762,7 +783,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 -r1.286 MainWindow.cs
--- src/MainWindow.cs	6 Mar 2006 22:13:57 -0000	1.286
+++ src/MainWindow.cs	21 Mar 2006 06:56:37 -0000
@@ -84,6 +84,7 @@
 	// Find
 	[Glade.Widget] MenuItem find_tag;
 	[Glade.Widget] CheckMenuItem find_untagged;
+	[Glade.Widget] CheckMenuItem find_duplicate;
 	
 	// Tags
 	[Glade.Widget] MenuItem edit_selected_tag;
@@ -1432,6 +1433,7 @@
 			"Grahm Orr",
 			"Ewen Cheslack-Postava",
 			"Gabriel Burt",
+			"Thomas Van Machelen",
 			"Patanjali Somayaji",
 			"Matt Jones",
 			"Martin Willemoes Hansen",
@@ -1959,6 +1961,9 @@
 	{
 		if (find_untagged.Active != query.Untagged)
 			find_untagged.Active = query.Untagged;
+
+		if (find_duplicate.Active != query.Duplicate)
+		 	find_duplicate.Active = query.Duplicate;
 	}
 	
 	void HandleZoomChanged (object sender, System.EventArgs args)
@@ -2256,6 +2261,22 @@
 			tag_selection_widget.SelectionChanged += OnTagSelectionChanged;
 
 			query.Untagged = true;
+		}
+	}
+
+	void HandleFindDuplicates (object sender, EventArgs args) {
+		if (query.Duplicate == find_duplicate.Active)
+			return;
+
+		if (query.Duplicate)
+			query.Duplicate = false;
+		else {
+			// Having tags selected wouldn't make sense
+			tag_selection_widget.SelectionChanged -= OnTagSelectionChanged;
+			tag_selection_widget.TagSelection = new Tag [] {};
+			tag_selection_widget.SelectionChanged += OnTagSelectionChanged;
+
+			query.Duplicate = true;
 		}
 	}
 	
Index: src/PhotoQuery.cs
===================================================================
RCS file: /cvs/gnome/f-spot/src/PhotoQuery.cs,v
retrieving revision 1.20
diff -u -r1.20 PhotoQuery.cs
--- src/PhotoQuery.cs	19 Dec 2005 04:35:17 -0000	1.20
+++ src/PhotoQuery.cs	21 Mar 2006 06:56:37 -0000
@@ -63,6 +63,7 @@
 				tags = value;
 				photos = store.Query (tags, range);
 				untagged = false;
+				duplicate = false;
 				RequestReload ();
 			}
 		}
@@ -73,9 +74,11 @@
 			}
 			set {
 				range = value;
-				
+			
 				if (untagged)
 					photos = store.QueryUntagged (range);
+				else if (duplicate)
+				 	photos = store.QueryDuplicate (range);
 				else
 					photos = store.Query (tags, range);
 
@@ -89,17 +92,41 @@
 				return untagged;
 			}
 			set {
-				if (untagged != value) {
-					untagged = value;
+				if (untagged == value)
+					return;
+				
+				untagged = value;
+
+				if (untagged) {
+				 	duplicate = false;
+					tags = null;
+					photos = store.QueryUntagged (range);
+				} else
+					photos = store.Query (tags, range);
+				
+				RequestReload ();
+			}
+		}
 
-					if (untagged) {
-						tags = null;
-						photos = store.QueryUntagged (range);
-					} else
-						photos = store.Query (tags, range);
-					
-					RequestReload ();
-				}
+		private bool duplicate = false;
+		public bool Duplicate {
+			get {
+				return duplicate;
+			}
+			set {
+				if (duplicate == value)
+					return;
+
+				duplicate = value;
+
+				if (duplicate) {
+				 	untagged = false;
+					tags = null;
+					photos = store.QueryDuplicate (range);
+				} else
+					photos = store.Query (tags, range);
+
+				RequestReload ();
 			}
 		}
 
Index: src/PhotoStore.cs
===================================================================
RCS file: /cvs/gnome/f-spot/src/PhotoStore.cs,v
retrieving revision 1.91
diff -u -r1.91 PhotoStore.cs
--- src/PhotoStore.cs	7 Mar 2006 17:54:54 -0000	1.91
+++ src/PhotoStore.cs	21 Mar 2006 06:56:39 -0000
@@ -154,7 +154,7 @@
 	private string directory_path;
 	private string name;
 
-	private string Path {
+	public string Path {
 		get {
 			return directory_path + "/" + name;
 		}
@@ -202,6 +202,16 @@
 		}
 	}
 
+	private byte[] md5sum;
+	public byte[] MD5Sum {
+		get {
+			return md5sum;
+		}
+		set {
+			md5sum = value;
+		}
+	}
+
 	// Version management
 	public const int OriginalVersionId = 1;
 	private uint highest_version_id;
@@ -547,7 +557,23 @@
 		return version;
 	}
 
+	public static byte[] GetMD5Sum (string filename)
+	{
+		FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read);
+		System.Security.Cryptography.MD5 md5ServiceProvider = new System.Security.Cryptography.MD5CryptoServiceProvider();
+
+		return md5ServiceProvider.ComputeHash(fs);
+	}
+
 	// Constructor
+	public Photo (uint id, long unix_time, string directory_path, string name, string description, uint default_version_id, byte[] 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 +583,7 @@
 		this.name = name;
 
 		description = "";
+		md5sum = new byte[0]; 
 
 		// Note that the original version is never stored in the photo_versions table in the
 		// database.
@@ -564,10 +591,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))
 	{
 	}
 
@@ -575,6 +599,12 @@
 
 public class PhotoStore : DbStore {
 	TagStore tag_store;
+
+	private Hashtable md5_cache;
+	public Hashtable MD5Cache {
+		get { return md5_cache;}
+	}
+	
 	
 	public static ThumbnailFactory ThumbnailFactory = new ThumbnailFactory (ThumbnailSize.Large);
 
@@ -642,6 +672,7 @@
 		: base (connection, false)
 	{
 		this.tag_store = tag_store;
+		this.md5_cache = new Hashtable ();
 		EnsureThumbnailDirectory ();
 
 		if (! is_new)
@@ -657,7 +688,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,24 +736,30 @@
 		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);
+		byte[] 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,
+						     SqlString (Convert.ToBase64String (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);
+		AddToMD5Cache (photo);
 		photo.Loaded = true;
 
 		thumbnail = GenerateThumbnail (newPath);		
@@ -791,20 +829,12 @@
 				
 				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 () {
-			SqliteCommand command = new SqliteCommand ();
+	private void GetAllTags () 
+	{
+		SqliteCommand command = new SqliteCommand ();
 		command.Connection = Connection;
 		command.CommandText = String.Format ("SELECT photo_id, tag_id " +
 						     "FROM photo_tags ");
@@ -908,7 +938,8 @@
 						     "       directory_path,                       " +
 						     "       name,                                 " +
 						     "       description,                          " +
-						     "       default_version_id                    " +
+						     "       default_version_id,                   " +
+						     "       md5sum                                " +
 						     "     FROM photos                             " +
 						     "     WHERE id = {0}                          ",
 						     id);
@@ -918,11 +949,13 @@
 			photo = new Photo (id,
 					   Convert.ToInt64 (reader [0]),
 					   reader [1].ToString (),
-					   reader [2].ToString ());
+					   reader [2].ToString (),
+					   reader[3].ToString (),
+					   Convert.ToUInt32 (reader[4]),
+					   Convert.FromBase64String (reader[5].ToString ()));
 
-			photo.Description = reader[3].ToString ();
-			photo.DefaultVersionId = Convert.ToUInt32 (reader[4]);
 			AddToCache (photo);
+			AddToMD5Cache (photo);
 		}
 
 		command.Dispose ();
@@ -953,7 +986,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,11 +1000,13 @@
 			photo = new Photo (Convert.ToUInt32 (reader [0]),
 					   Convert.ToInt64 (reader [1]),
 					   directory_path,
-					   filename);
+					   filename,
+					   reader[2].ToString(),
+					   Convert.ToUInt32 (reader[3]),
+					   Convert.FromBase64String (reader[5].ToString ()));
 
-			photo.Description = reader[2].ToString ();
-			photo.DefaultVersionId = Convert.ToUInt32 (reader[3]);
 			AddToCache (photo);
+			AddToMD5Cache (photo);
 		}
 
 		command.Dispose ();
@@ -1012,6 +1048,7 @@
 
 			query_builder.Append (String.Format ("id = {0}", items[i].Id));
 			tv_query_builder.Append (String.Format ("photo_id = {0}", items[i].Id));
+			RemoveFromMD5Cache (items[i]);
 			RemoveFromCache (items[i]);
 		}
 
@@ -1045,6 +1082,8 @@
 		Remove (new Photo [] { (Photo)item });
 	}
 
+	
+
 	public override void Commit (DbItem item)
 	{
 		DbItemEventArgs args = new DbItemEventArgs (item);
@@ -1070,14 +1109,18 @@
 
 		SqliteCommand command = new SqliteCommand ();
 		command.Connection = Connection;
-		command.CommandText = String.Format ("UPDATE photos SET description = '{0}',     " +
-						     "                  default_version_id = {1}, " +
-						     "                  time = {2} " +
-						     "              WHERE id = {3}",
-						     SqlString (photo.Description),
-						     photo.DefaultVersionId,
-						     DbUtils.UnixTimeFromDateTime (photo.Time),
-						     photo.Id);
+		command.CommandText = String.Format (
+						"UPDATE photos " + 
+						" SET description = '{0}',     " +
+						"     default_version_id = {1}, " +
+						"     time = {2}, " +
+						"     md5sum = '{3}' " +
+						" WHERE id = {4}",
+						SqlString (photo.Description),
+						photo.DefaultVersionId,
+						DbUtils.UnixTimeFromDateTime (photo.Time),
+						SqlString (Convert.ToBase64String (photo.MD5Sum)),
+						photo.Id);
 		command.ExecuteNonQuery ();
 		command.Dispose ();
 
@@ -1090,6 +1133,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) " +
@@ -1122,6 +1166,38 @@
 			command.Dispose ();
 		}
 	}
+
+	public bool IsDuplicate (Photo photo)
+	{
+		if (photo.Id != (uint)md5_cache[Convert.ToBase64String(photo.MD5Sum)])
+		 	return true;
+
+		return false;
+	}
+
+	protected void AddToMD5Cache (Photo photo)
+	{
+		if (md5_cache[Convert.ToBase64String(photo.MD5Sum)] == null)
+			md5_cache.Add(Convert.ToBase64String(photo.MD5Sum), photo.Id);
+	}
+
+	protected void RemoveFromMD5Cache (Photo photo)
+	{
+		object id = md5_cache[Convert.ToBase64String(photo.MD5Sum)];
+		
+		if (id == null)
+			return;
+	 
+		if ((uint)id != photo.Id)
+			return;
+
+		Photo[] photos = QueryMD5Sum (photo);		
+
+		if (photos.Length == 0)
+			md5_cache.Remove (Convert.ToBase64String(photo.MD5Sum));
+		else
+			md5_cache[Convert.ToBase64String(photo.MD5Sum)] = photos[0].Id;
+	}
 	
 	public class DateRange 
 	{
@@ -1173,10 +1249,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]),
+						   Convert.FromBase64String (reader [6].ToString ()));
 				
 				version_list.Add (photo);
 			}
@@ -1187,6 +1263,7 @@
 		bool need_load = false;
 		foreach (Photo photo in version_list) {
 			AddToCache (photo);
+			AddToMD5Cache (photo);
 			need_load |= !photo.Loaded;
 		}
 		
@@ -1212,7 +1289,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);
 
@@ -1237,6 +1315,29 @@
 		return Query (query_builder.ToString ());
 	}
 
+	public Photo[] QueryDuplicate (DateRange range)
+	{
+		Photo[] photos = Query (null, range);
+
+		ArrayList duplicates = new ArrayList ();
+
+		foreach (Photo photo in photos) {
+			if (IsDuplicate (photo))
+				duplicates.Add (photo);
+		}
+
+		return duplicates.ToArray (typeof (Photo)) as Photo[];
+	}
+
+	private Photo[] QueryMD5Sum (Photo original)
+	{
+		string query = String.Format("SELECT * FROM photos WHERE md5sum = '{0}' AND id <> {1}",
+					     Convert.ToBase64String(original.MD5Sum),
+					     original.Id);
+
+		return Query (query);
+	}
+
 	public Photo [] Query (Tag [] tags, DateRange range)
 	{
 		string query;
@@ -1271,7 +1372,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/QueryDisplay.cs
===================================================================
RCS file: /cvs/gnome/f-spot/src/QueryDisplay.cs,v
retrieving revision 1.7
diff -u -r1.7 QueryDisplay.cs
--- src/QueryDisplay.cs	19 Dec 2005 04:35:17 -0000	1.7
+++ src/QueryDisplay.cs	21 Mar 2006 06:56:39 -0000
@@ -9,6 +9,7 @@
 	Gtk.EventBox tag_view_box;
 	Gtk.Label label;
 	Gtk.Label untagged;
+	Gtk.Label duplicate;
 	Gtk.HBox warning_box;
 	Gtk.Button clear_button;
 	TagSelectionWidget selector;
@@ -40,6 +41,10 @@
 		untagged.Visible = false;
 		hbox.PackStart (untagged, false, false, 0);
 
+		duplicate = new Gtk.Label (Catalog.GetString ("Duplicate photos"));
+		duplicate.Visible = false;
+		hbox.PackStart (duplicate, false, false, 0);
+
 		tag_view_box = new EventBox ();
 		tag_view = new TagView (tag_view_box);
 		tag_view.Show ();
@@ -76,11 +81,12 @@
 		
 		Tag [] tags = query.Tags;
 		tag_view.Tags = tags;
-		if ((tags != null && tags.Length > 0) || query.Untagged)
+		if ((tags != null && tags.Length > 0) || query.Untagged || query.Duplicate)
 			active_search = true;
 		
 		this.Visible = active_search;
 		untagged.Visible = query.Untagged;
+		duplicate.Visible = query.Duplicate;
 		warning_box.Visible = (query.Count < 1);
 	}
 }
Index: src/Updater.cs
===================================================================
RCS file: /cvs/gnome/f-spot/src/Updater.cs,v
retrieving revision 1.4
diff -u -r1.4 Updater.cs
--- src/Updater.cs	13 Feb 2006 07:27:36 -0000	1.4
+++ src/Updater.cs	21 Mar 2006 06:56:39 -0000
@@ -62,11 +62,51 @@
 				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) {
+				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}",
+									Convert.ToBase64String(Photo.GetMD5Sum(path)),
+									photo_id));
+					}
+
+					command.Dispose ();
+				}, true);
+			}
 
 		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 -r1.159 f-spot.glade
--- src/f-spot.glade	25 Feb 2006 08:54:05 -0000	1.159
+++ src/f-spot.glade	21 Mar 2006 06:57:01 -0000
@@ -7243,7 +7243,7 @@
 		      <signal name="activate" handler="HandleSendMailCommand" last_modification_time="Mon, 07 Jun 2004 22:31:01 GMT"/>
 
 		      <child internal-child="image">
-			<widget class="GtkImage" id="image40">
+			<widget class="GtkImage" id="image43">
 			  <property name="visible">True</property>
 			  <property name="stock">gnome-stock-mail-fwd</property>
 			  <property name="icon_size">1</property>
@@ -7596,7 +7596,7 @@
 			      <property name="visible">True</property>
 			      <property name="label" translatable="yes">_Month</property>
 			      <property name="use_underline">True</property>
-			      <property name="active">True</property>
+			      <property name="active">False</property>
 			      <signal name="activate" handler="HandleArrangeByTime" last_modification_time="Fri, 20 Aug 2004 22:26:32 GMT"/>
 			    </widget>
 			  </child>
@@ -7606,7 +7606,7 @@
 			      <property name="visible">True</property>
 			      <property name="label" translatable="yes">_Folder</property>
 			      <property name="use_underline">True</property>
-			      <property name="active">False</property>
+			      <property name="active">True</property>
 			      <property name="group">month</property>
 			      <signal name="activate" handler="HandleArrangeByDirectory" last_modification_time="Fri, 20 Aug 2004 22:26:32 GMT"/>
 			    </widget>
@@ -7664,6 +7664,16 @@
 		  </child>
 
 		  <child>
+		    <widget class="GtkCheckMenuItem" id="find_duplicate">
+		      <property name="visible">True</property>
+		      <property name="label" translatable="yes">_Find Duplicates</property>
+		      <property name="use_underline">True</property>
+		      <property name="active">False</property>
+		      <signal name="activate" handler="HandleFindDuplicates" last_modification_time="Tue, 10 Aug 2004 07:08:24 GMT"/>
+		    </widget>
+		  </child>
+
+		  <child>
 		    <widget class="GtkCheckMenuItem" id="find_untagged">
 		      <property name="visible">True</property>
 		      <property name="label" translatable="yes">_Untagged Photos</property>
@@ -9325,26 +9335,6 @@
 	  <property name="spacing">6</property>
 
 	  <child>
-	    <widget class="GtkCheckButton" id="recurse_check">
-	      <property name="visible">True</property>
-	      <property name="can_focus">True</property>
-	      <property name="label" translatable="yes">Include subdirectories</property>
-	      <property name="use_underline">True</property>
-	      <property name="relief">GTK_RELIEF_NORMAL</property>
-	      <property name="focus_on_click">True</property>
-	      <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>
-	      <property name="pack_type">GTK_PACK_END</property>
-	    </packing>
-	  </child>
-
-	  <child>
 	    <widget class="GtkVBox" id="vbox65">
 	      <property name="visible">True</property>
 	      <property name="homogeneous">False</property>
@@ -9611,6 +9601,44 @@
 	      <property name="relief">GTK_RELIEF_NORMAL</property>
 	      <property name="focus_on_click">True</property>
 	      <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="recurse_check">
+	      <property name="visible">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label" translatable="yes">Include subdirectories</property>
+	      <property name="use_underline">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <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>


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