First patch for "Finding Duplicates" implementation



Hi guys!

Here goes the first patch that implements "Finding Duplicates" as I have
written in:

http://mail.gnome.org/archives/f-spot-list/2005-June/msg00031.html

To play with it:

1. Download F-Spot from CVS
2. cd "f-spot"
3. Save the patch attached to this email, "f-spot-duplicates.diff", in
the "f-spot" directory
4. patch -p0 < f-spot-duplicates.diff
5. Compile and install
6. Launch F-Spot and import some photos with duplicates
7. Go to Find->Find Duplicates
8. PLAY :)
9. Report experience to the list

I have to play more with my big collection of photos full of duplicates
in order to play with the UI and find the best way to help the user to
clean the duplicates, but I feel that the basics of the feature could be
the one in the patch.

The patch change the database schema so you will need to recreate your
database. I haven't done any effort to upgrade nicely the database from
earlier versions because in "README" we can read:

<<If things stop working after a CVS update, chances are that
    the database format has changed.  In that case you will have
    to nuke your database (rm ~/.gnome2/f-spot/photos.db) and
    start over, importing all your pictures again.>>

Cheers

-- Alvaro
Index: src/MainWindow.cs
===================================================================
RCS file: /cvs/gnome/f-spot/src/MainWindow.cs,v
retrieving revision 1.202
diff -u -b -B -p -u -r1.202 MainWindow.cs
--- src/MainWindow.cs	18 Jun 2005 22:07:10 -0000	1.202
+++ src/MainWindow.cs	22 Jun 2005 17:12:43 -0000
@@ -10,6 +10,9 @@ using System.Collections;
 using System.Runtime.InteropServices;
 using System.Text.RegularExpressions;
 
+using System.IO;
+using System.Security.Cryptography;
+
 using LibGPhoto2;
 
 public class MainWindow {
@@ -17,6 +20,7 @@ public class MainWindow {
 
 	Db db;
 
+	
 	TagSelectionWidget tag_selection_widget;
 	[Glade.Widget] Gtk.Window main_window;
 	[Glade.Widget] Gtk.VBox left_vbox;
@@ -1633,6 +1637,77 @@ public class MainWindow {
 	void HandleClearDateRange (object sender, EventArgs args) {
 		query.Range = null;
 	}
+
+	void HandleFindDuplicates (object sender, EventArgs args) {
+        Tag tag_duplicate = null;
+        foreach (Tag tag in db.Tags.RootCategory.Children) {
+            if (string.Compare (tag.Name, "Duplicates") == 0) {
+                tag_duplicate = tag;
+                break;
+            }
+        }
+        if (tag_duplicate == null) {
+            tag_duplicate = db.Tags.CreateTag (null, "Duplicates");
+            tag_duplicate.StockIconName = "f-spot-hidden.png";
+            tag_duplicate.SortPriority = -11;
+            db.Tags.Commit (tag_duplicate);
+            tag_selection_widget.Update ();
+        }
+		System.Console.WriteLine ("Looking for duplicates ...");
+        if (!PhotoSelectionActive()) {
+            icon_view.SelectAllCells ();
+        }
+
+        main_window.GdkWindow.Cursor = new Gdk.Cursor (Gdk.CursorType.Watch);
+        main_window.GdkWindow.Display.Sync ();
+
+        // A really simple implementation: Build hash table with MD5 of images
+        // and we later use it to find duplicates. 
+        Hashtable photos_md5 = new Hashtable ();
+
+        foreach (int num in SelectedIds ()) {            
+            Photo photo = query.Photos [num];
+            if (string.Compare(photo.MD5Sum, "") != 0) {
+                photos_md5.Add (num, photo.MD5Sum);
+                continue;
+            }
+            // Computing time is measured
+            long startTime = DateTime.Now.Ticks;
+			FileStream fs = new FileStream(photo.Path, FileMode.Open, FileAccess.Read);
+			MD5 md5ServiceProvider = new 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());
+            }
+            long endTime = DateTime.Now.Ticks;
+            TimeSpan timeTaken = new TimeSpan(endTime - startTime);
+            Console.WriteLine("MD5 compute: {0}", timeTaken.ToString());
+            
+            photos_md5.Add (num, hash);
+            photo.MD5Sum = hash.ToString ();
+            db.Photos.Commit (photo);            
+        }
+
+        ICollection md5keys = photos_md5.Keys;
+		foreach (int num in SelectedIds ()) {
+            foreach (int key in md5keys) {
+                if (string.Compare(key.ToString(), num.ToString()) != 0 && 
+                    string.Compare(photos_md5[num].ToString(), photos_md5[key].ToString()) == 0) {
+                    Tag [] tags = new Tag [1];
+                    tags [0] = tag_duplicate;
+                    AddTagExtended (num, tags);
+                    icon_view.ScrollTo(num);
+                    break;
+                }
+            }
+        }
+        tag_selection_widget.Select (tag_duplicate);
+        UpdateQuery ();
+        SetViewMode (ModeType.IconView);
+        main_window.GdkWindow.Cursor = null;
+    }	
 
 	// Version Id updates.
 
Index: src/Makefile.am
===================================================================
RCS file: /cvs/gnome/f-spot/src/Makefile.am,v
retrieving revision 1.31
diff -u -b -B -p -u -r1.31 Makefile.am
Index: src/PhotoStore.cs
===================================================================
RCS file: /cvs/gnome/f-spot/src/PhotoStore.cs,v
retrieving revision 1.67
diff -u -b -B -p -u -r1.67 PhotoStore.cs
--- src/PhotoStore.cs	28 May 2005 10:35:45 -0000	1.67
+++ src/PhotoStore.cs	22 Jun 2005 17:12:46 -0000
@@ -28,6 +28,7 @@ public class Photo : DbItem, IComparable
 		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);
@@ -134,6 +135,16 @@ public class Photo : DbItem, IComparable
 		}
 	}
 
+    private string md5sum;
+    public string MD5Sum {
+		get {
+			return md5sum;
+		}
+		set {
+			md5sum = value;
+		}
+	}
+
 	// Version management
 	public const int OriginalVersionId = 1;
 	private uint highest_version_id;
@@ -378,6 +389,7 @@ public class Photo : DbItem, IComparable
 		this.name = name;
 
 		description = "";
+        md5sum      = "";
 
 		// Note that the original version is never stored in the photo_versions table in the
 		// database.
@@ -478,7 +490,8 @@ public class PhotoStore : DbStore {
 			"       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 ();
@@ -529,8 +542,8 @@ public class PhotoStore : DbStore {
 		command.Connection = Connection;
 
 		command.CommandText = String.Format ("INSERT INTO photos (time, " +
-						     "directory_path, name, description, default_version_id) " +
-						     "       VALUES ({0}, '{1}', '{2}', '', {3})                                       ",
+						     "directory_path, name, description, default_version_id, md5sum) " +
+						     "       VALUES ({0}, '{1}', '{2}', '', {3},'')",
 						     unix_time,
 						     SqlString (System.IO.Path.GetDirectoryName (path)),
 						     SqlString (System.IO.Path.GetFileName (path)),
@@ -718,7 +731,8 @@ public class PhotoStore : DbStore {
 						     "       directory_path,                       " +
 						     "       name,                                 " +
 						     "       description,                          " +
-						     "       default_version_id                    " +
+						     "       default_version_id,                   " +
+                             "       md5sum                                " +
 						     "     FROM photos                             " +
 						     "     WHERE id = {0}                          ",
 						     id);
@@ -732,6 +746,7 @@ public class PhotoStore : DbStore {
 
 			photo.Description = reader[3].ToString ();
 			photo.DefaultVersionId = Convert.ToUInt32 (reader[4]);
+            photo.MD5Sum = reader[5].ToString ();
 			AddToCache (photo);
 		}
 
@@ -885,11 +900,13 @@ public class PhotoStore : DbStore {
 
 		SqliteCommand command = new SqliteCommand ();
 		command.Connection = Connection;
-		command.CommandText = String.Format ("UPDATE photos SET description = '{0}',     " +
-						     "                  default_version_id = {1} " +
-						     "              WHERE id = {2}",
+		command.CommandText = String.Format ("UPDATE photos SET description = '{0}'," +
+						     "                default_version_id = {1}," +
+                             "                md5sum = '{2}'"+                
+						     "                WHERE id = {3}",
 						     SqlString (photo.Description),
 						     photo.DefaultVersionId,
+                             SqlString (photo.MD5Sum),  
 						     photo.Id);
 		command.ExecuteNonQuery ();
 		command.Dispose ();
@@ -994,6 +1011,7 @@ public class PhotoStore : DbStore {
 				
 				photo.Description = reader[4].ToString ();
 				photo.DefaultVersionId = Convert.ToUInt32 (reader[5]);		 
+                photo.MD5Sum = reader[6].ToString ();
 				
 				version_list.Add (photo);
 			}
@@ -1033,7 +1051,8 @@ public class PhotoStore : DbStore {
 						     "       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);
 
@@ -1073,7 +1092,8 @@ public class PhotoStore : DbStore {
 				      "       photos.directory_path,              " +
 				      "       photos.name,                        " +
 				      "       photos.description,                 " +
-				      "       photos.default_version_id           " +
+				      "       photos.default_version_id,          " +
+                      "       photos.md5sum                       " +
 				      "     FROM photos                      ");
 		
 		if (range != null) {
Index: src/f-spot.glade
===================================================================
RCS file: /cvs/gnome/f-spot/src/f-spot.glade,v
retrieving revision 1.101
diff -u -b -B -p -u -r1.101 f-spot.glade
--- src/f-spot.glade	17 Jun 2005 16:11:25 -0000	1.101
+++ src/f-spot.glade	22 Jun 2005 17:13:17 -0000
@@ -7128,6 +7128,15 @@ Photo Details</property>
 		  </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>


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