[f-spot] Plug massive memory leak by replacing thumbnailing.



commit 9573c72401152f08c7d2aedfd99912360c153612
Author: Ruben Vermeersch <ruben savanne be>
Date:   Thu Jun 3 19:20:15 2010 +0200

    Plug massive memory leak by replacing thumbnailing.
    
    This also drops the use of the deprecated GnomeThumbnailFactory by
    adding a new implementation of the XDG thumbnail spec: XdgThumbnailSpec.

 build/build.environment.mk                         |    5 +-
 .../FacebookExport/FacebookExportDialog.cs         |    3 +-
 .../Tools/LiveWebGallery/PhotoRequestHandler.cs    |    3 +-
 src/Core/App.cs                                    |    2 +-
 src/Core/Makefile.am                               |    2 -
 src/Core/Photo.cs                                  |    7 +-
 src/FileImportBackend.cs                           |   15 ++-
 src/ImageLoaderThread.cs                           |   89 +++++++----
 src/Loaders/GdkImageLoader.cs                      |    2 +-
 src/PhotoLoader.cs                                 |   18 ---
 src/PixbufCache.cs                                 |   10 +-
 src/Platform/Gnome/ThumbnailFactory.cs             |  153 -------------------
 src/Platform/Makefile.am                           |    2 -
 src/Platform/Null/ThumbnailFactory.cs              |   81 ----------
 src/RotateCommand.cs                               |    4 -
 src/SingleView.cs                                  |    2 +-
 src/ThumbnailCommand.cs                            |    7 +-
 src/ThumbnailGenerator.cs                          |   76 ++--------
 src/Utils/FSpot.Utils.dll.config                   |    2 +
 src/Utils/Makefile.am                              |    5 +-
 src/{Core => Utils}/SafeUri.cs                     |    0
 src/{Core => Utils}/SafeUriExtensions.cs           |    0
 src/Utils/XdgThumbnailSpec.cs                      |  159 ++++++++++++++++++++
 src/Widgets/Filmstrip.cs                           |   36 ++---
 src/Widgets/IconView.cs                            |    9 -
 src/main.cs                                        |    5 +
 26 files changed, 289 insertions(+), 408 deletions(-)
---
diff --git a/build/build.environment.mk b/build/build.environment.mk
index 404aaec..65d0df3 100644
--- a/build/build.environment.mk
+++ b/build/build.environment.mk
@@ -112,7 +112,7 @@ LINK_FSPOT_BLING = -r:$(DIR_BIN)/FSpot.Bling.dll
 LINK_FSPOT_BLING_DEPS = $(REF_FSPOT_BLING) $(LINK_FSPOT_BLING)
 
 # FSpot.Platform
-REF_FSPOT_PLATFORM = $(LINK_GCONF) $(LINK_GTK) $(LINK_FSPOT_CORE_DEPS) $(LINK_DBUS) $(LINK_GNOME)
+REF_FSPOT_PLATFORM = $(LINK_GCONF) $(LINK_GTK) $(LINK_FSPOT_CORE_DEPS) $(LINK_DBUS)
 LINK_FSPOT_PLATFORM = -r:$(DIR_BIN)/FSpot.Platform.dll
 LINK_FSPOT_PLATFORM_DEPS = $(REF_FSPOT_PLATFORM) $(LINK_FSPOT_PLATFORM)
 
@@ -125,7 +125,8 @@ LINK_FSPOT_WIDGETS_DEPS = $(REF_FSPOT_WIDGETS) $(LINK_FSPOT_WIDGETS)
 REF_FSPOT = $(LINK_FSPOT_WIDGETS_DEPS) $(LINK_FSPOT_PLATFORM_DEPS) $(LINK_FSPOT_QUERY_DEPS) \
             $(LINK_GLIB) $(LINK_MONO_ADDINS_DEPS) $(LINK_UNIQUE_DEPS) $(LINK_MONO_ADDINS_SETUP_DEPS) \
             $(LINK_SEMWEB_DEPS) $(LINK_GLADE) $(LINK_MONODATA) $(LINK_MONO_DATA_SQLITECLIENT) \
-            $(LINK_MONO_ADDINS_GUI_DEPS) $(LINK_FSPOT_JOB_SCHEDULER_DEPS) $(LINK_ICSHARP_ZIP_LIB)
+            $(LINK_MONO_ADDINS_GUI_DEPS) $(LINK_FSPOT_JOB_SCHEDULER_DEPS) $(LINK_ICSHARP_ZIP_LIB) \
+            $(LINK_GNOME)
 # FIXME: do not link executables
 LINK_FSPOT = -r:$(DIR_BIN)/f-spot.exe
 LINK_FSPOT_DEPS = $(REF_FSPOT) $(LINK_FSPOT)
diff --git a/extensions/Exporters/FacebookExport/FacebookExportDialog.cs b/extensions/Exporters/FacebookExport/FacebookExportDialog.cs
index 04b5c07..b4ea505 100644
--- a/extensions/Exporters/FacebookExport/FacebookExportDialog.cs
+++ b/extensions/Exporters/FacebookExport/FacebookExportDialog.cs
@@ -23,6 +23,7 @@ using Gtk;
 using GtkBeans;
 
 using Hyena;
+using FSpot.Utils;
 using FSpot.Platform;
 using FSpot.UI.Dialog;
 
@@ -192,7 +193,7 @@ namespace FSpot.Exporter.Facebook
 				tag_image.Destroy ();
 			}
 
-			using (Gdk.Pixbuf data = PixbufUtils.ScaleToMaxSize (ThumbnailFactory.LoadThumbnail (item.DefaultVersion.Uri), 400, 400)) {
+			using (Gdk.Pixbuf data = XdgThumbnailSpec.LoadThumbnail (item.DefaultVersion.Uri, ThumbnailSize.Large)) {
 				tag_image_height = data.Height;
 				tag_image_width = data.Width;
 				tag_image = new Gtk.Image (data);
diff --git a/extensions/Tools/LiveWebGallery/PhotoRequestHandler.cs b/extensions/Tools/LiveWebGallery/PhotoRequestHandler.cs
index 8fae02c..508ed81 100644
--- a/extensions/Tools/LiveWebGallery/PhotoRequestHandler.cs
+++ b/extensions/Tools/LiveWebGallery/PhotoRequestHandler.cs
@@ -13,6 +13,7 @@ using System.Text;
 
 using FSpot;
 using FSpot.Filters;
+using FSpot.Utils;
 using Hyena;
 
 namespace LiveWebGalleryExtension
@@ -82,7 +83,7 @@ namespace LiveWebGalleryExtension
 		
 		protected override void SendImage (Photo photo, Stream dest) 
 		{
-			Gdk.Pixbuf thumb = FSpot.Platform.ThumbnailFactory.LoadThumbnail (photo.DefaultVersion.Uri);
+			Gdk.Pixbuf thumb = XdgThumbnailSpec.LoadThumbnail (photo.DefaultVersion.Uri, ThumbnailSize.Large);
 			byte[] buf = thumb.SaveToBuffer ("png");
 			SendHeadersAndStartContent(dest, "Content-Type: " + MimeTypeForExt (".png"),
 											 "Content-Length: " + buf.Length,
diff --git a/src/Core/App.cs b/src/Core/App.cs
index 8666576..550aff4 100644
--- a/src/Core/App.cs
+++ b/src/Core/App.cs
@@ -335,7 +335,7 @@ namespace FSpot
 				Log.Information ("Exiting...");
 				Banshee.Kernel.Scheduler.Dispose ();
 				Database.Dispose ();
-				ImageLoaderThread.Cleanup ();
+				ImageLoaderThread.CleanAll ();
 				Gtk.Application.Quit ();
 				System.Environment.Exit (0);
 			}
diff --git a/src/Core/Makefile.am b/src/Core/Makefile.am
index 1dd2488..c5aafe2 100644
--- a/src/Core/Makefile.am
+++ b/src/Core/Makefile.am
@@ -21,8 +21,6 @@ SOURCES = \
 	PhotoChanges.cs \
 	PhotosChanges.cs \
 	Roll.cs \
-	SafeUri.cs \
-	SafeUriExtensions.cs \
 	Defines.cs
 
 RESOURCES =
diff --git a/src/Core/Photo.cs b/src/Core/Photo.cs
index c7c4613..8830bdb 100644
--- a/src/Core/Photo.cs
+++ b/src/Core/Photo.cs
@@ -246,7 +246,6 @@ namespace FSpot
 						img.Save (buffer, stream);
 					}
 					(GetVersion (version) as PhotoVersion).MD5Sum = GenerateMD5 (VersionUri (version));
-					FSpot.ThumbnailGenerator.Create (versionUri).Dispose ();
 					DefaultVersionId = version;
 				} catch (System.Exception e) {
 					Log.Exception (e);
@@ -287,7 +286,7 @@ namespace FSpot
 						file.Delete ();
 					}	
 				try {
-					ThumbnailFactory.DeleteThumbnail (uri);
+					XdgThumbnailSpec.RemoveThumbnail (uri);
 				} catch {
 					//ignore an error here we don't really care.
 				}
@@ -334,8 +333,6 @@ namespace FSpot
 		//FIXME. or better, fix the copy api !
 				GLib.File source = GLib.FileFactory.NewForUri (original_uri);
 				source.Copy (destination, GLib.FileCopyFlags.None, null, null);
-	
-				FSpot.ThumbnailGenerator.Create (new_uri).Dispose ();
 			}
 			highest_version_id ++;
 
@@ -536,7 +533,7 @@ namespace FSpot
 			 	if (md5_cache.ContainsKey (uri))
 				 	return md5_cache [uri];
 
-				using (Gdk.Pixbuf pixbuf = ThumbnailGenerator.Create (uri))
+				using (Gdk.Pixbuf pixbuf = XdgThumbnailSpec.LoadThumbnail (uri, ThumbnailSize.Large))
 				{
 					byte[] serialized = GdkUtils.Serialize (pixbuf);
 					byte[] md5 = MD5Generator.ComputeHash (serialized);
diff --git a/src/FileImportBackend.cs b/src/FileImportBackend.cs
index 3c1cf82..550edd8 100644
--- a/src/FileImportBackend.cs
+++ b/src/FileImportBackend.cs
@@ -317,10 +317,17 @@ public class FileImportBackend : ImportBackend {
 		if (import_info == null)
 			throw new ImportException ("Not doing anything");
 
-		foreach (ImportInfo info in import_info) {
-			if (info.PhotoId != 0) 
-				FSpot.ThumbnailGenerator.Default.Request (store.Get (info.PhotoId).DefaultVersion.Uri, 0, 256, 256);
-		}
+		var infos = import_info;
+		ThreadAssist.SpawnFromMain (() => {
+			// Generate all thumbnails on a different thread, disposing is automatic.
+			var loader = ThumbnailLoader.Default;
+			foreach (ImportInfo info in infos) {
+				if (info.PhotoId != 0) {
+					var uri = store.Get (info.PhotoId).DefaultVersion.Uri;
+					loader.Request (uri, ThumbnailSize.Large, 10);
+				}
+			}
+		});
 
 		import_info = null;
 		xmptags.Finish();
diff --git a/src/ImageLoaderThread.cs b/src/ImageLoaderThread.cs
index 407c8e0..b7514ba 100644
--- a/src/ImageLoaderThread.cs
+++ b/src/ImageLoaderThread.cs
@@ -21,36 +21,46 @@ public class ImageLoaderThread {
 
 	// Types.
 
-	protected class RequestItem {
-		/* The path to the image.  */
-		public SafeUri uri;
+	public class RequestItem {
+		/* The uri to the image.  */
+		public SafeUri Uri { get; set; }
 
 		/* Order value; requests with a lower value get performed first.  */
-		public int order;
+		public int Order { get; set; }
 
 		/* The pixbuf obtained from the operation.  */
-		public Pixbuf result;
+        private Pixbuf result;
+		public Pixbuf Result {
+            get { return PixbufUtils.ShallowCopy (result); }
+            set { result = value; }
+        }
 
 		/* the maximium size both must be greater than zero if either is */
-		public int width;
-		public int height;
+		public int Width { get; set; }
+		public int Height { get; set; }
 
 		public RequestItem (SafeUri uri, int order, int width, int height) {
-			this.uri = uri;
-			this.order = order;
-			this.width = width;
-			this.height = height;
+			this.Uri = uri;
+			this.Order = order;
+			this.Width = width;
+			this.Height = height;
 			if ((width <= 0 && height > 0) || (height <= 0 && width > 0))
 				throw new System.Exception ("Invalid arguments");
 		}
+
+        ~RequestItem () {
+            if (result != null)
+                result.Dispose ();
+            result = null;
+        }
 	}
 
 
 	// Private members.
+    static List<ImageLoaderThread> instances = new List<ImageLoaderThread> ();
 
 	/* The thread used to handle the requests.  */
 	private Thread worker_thread;
-	private static ArrayList all_worker_threads = new ArrayList ();
 
 	/* The request queue; it's shared between the threads so it
 	   needs to be locked prior to access.  */
@@ -59,7 +69,6 @@ public class ImageLoaderThread {
 	/* A dict of all the requests; note that the current request
 	   isn't in the dict.  */
 	Dictionary<SafeUri, RequestItem> requests_by_uri;
-//	private Hashtable requests_by_path;
 
 	/* Current request.  Request currently being handled by the
 	   auxiliary thread.  Should be modified only by the auxiliary
@@ -73,14 +82,14 @@ public class ImageLoaderThread {
 	   thread that there are pending items in the
 	   `processed_requests' queue.  */
 	ThreadNotify pending_notify;
+
 	/* Whether a notification is pending on `pending_notify'
 	   already or not.  */
 	private bool pending_notify_notified;
 
-
 	// Public API.
 
-	public delegate void PixbufLoadedHandler (ImageLoaderThread loader, SafeUri uri, int order, Pixbuf result);
+	public delegate void PixbufLoadedHandler (ImageLoaderThread loader, RequestItem result);
 	public event PixbufLoadedHandler OnPixbufLoaded;
 
 	public ImageLoaderThread ()
@@ -92,11 +101,17 @@ public class ImageLoaderThread {
 		
 		pending_notify = new ThreadNotify (new Gtk.ReadyEvent (HandleProcessedRequests));
 
+        instances.Add (this);
+	}
+
+    void StartWorker ()
+    {
+        if (worker_thread != null)
+            return;
+
 		worker_thread = new Thread (new ThreadStart (WorkerThread));
 		worker_thread.Start ();
-
-		all_worker_threads.Add (worker_thread);
-	}
+    }
 
 	int block_count;
 	public void PushBlock ()
@@ -113,13 +128,21 @@ public class ImageLoaderThread {
 		}
 	}
 
-	// FIXME?
-	static public void Cleanup ()
+	public void Cleanup ()
 	{
-		foreach (Thread t in all_worker_threads)
-			t.Abort ();
+        var thread = worker_thread;
+        worker_thread = null;
+        
+        if (thread != null)
+            thread.Abort ();
 	}
 
+    public static void CleanAll ()
+    {
+        foreach (var thread in instances)
+            thread.Cleanup ();
+    }
+
 	public void Request (SafeUri uri, int order)
 	{
 		Request (uri, order, 0, 0);
@@ -150,9 +173,9 @@ public class ImageLoaderThread {
 	{
 		Pixbuf orig_image;
 		try {
-			using (FSpot.ImageFile img = FSpot.ImageFile.Create (request.uri)) {
-				if (request.width > 0) {
-					orig_image = img.Load (request.width, request.height);
+			using (FSpot.ImageFile img = FSpot.ImageFile.Create (request.Uri)) {
+				if (request.Width > 0) {
+					orig_image = img.Load (request.Width, request.Height);
 				} else {
 					orig_image = img.Load ();
 				}
@@ -165,7 +188,7 @@ public class ImageLoaderThread {
 		if (orig_image == null)
 			return;
 		
-		request.result = orig_image;
+		request.Result = orig_image;
 	}
 
 	/* Insert the request in the queue, return TRUE if the queue actually grew.
@@ -173,18 +196,20 @@ public class ImageLoaderThread {
 
 	private bool InsertRequest (SafeUri uri, int order, int width, int height)
 	{
+        StartWorker ();
+
 		/* Check if this is the same as the request currently being processed.  */
 		lock(processed_requests) {
-			if (current_request != null && current_request.uri == uri)
+			if (current_request != null && current_request.Uri == uri)
 				return false;
 		}
 		/* Check if a request for this path has already been queued.  */
 		RequestItem existing_request;
 		if (requests_by_uri.TryGetValue (uri, out existing_request)) {
 			/* FIXME: At least for now, this shouldn't happen.  */
-			if (existing_request.order != order)
+			if (existing_request.Order != order)
 				Log.WarningFormat ("BUG: Filing another request of order {0} (previously {1}) for `{2}'",
-						   order, existing_request.order, uri);
+						   order, existing_request.Order, uri);
 
 			queue.Remove (existing_request);
 			queue.Add (existing_request);
@@ -230,7 +255,7 @@ public class ImageLoaderThread {
 	
 					current_request = queue [pos] as RequestItem;
 					queue.RemoveAt (pos);
-					requests_by_uri.Remove (current_request.uri);
+					requests_by_uri.Remove (current_request.Uri);
 				}
 				
 				ProcessRequest (current_request);
@@ -244,7 +269,7 @@ public class ImageLoaderThread {
 	{
 		if (OnPixbufLoaded != null) {
 			foreach (RequestItem r in results)
-				OnPixbufLoaded (this, r.uri, r.order, r.result);
+				OnPixbufLoaded (this, r);
 		}
 	}
 
@@ -261,7 +286,7 @@ public class ImageLoaderThread {
 
 			pending_notify_notified = false;
 		}
-		
+
 		EmitLoaded (results);
 	}
 }
diff --git a/src/Loaders/GdkImageLoader.cs b/src/Loaders/GdkImageLoader.cs
index daf72ac..35eab2e 100644
--- a/src/Loaders/GdkImageLoader.cs
+++ b/src/Loaders/GdkImageLoader.cs
@@ -30,7 +30,7 @@ namespace FSpot.Loaders {
 				return;
 
 			//First, send a thumbnail if we have one
-			if ((thumb = ThumbnailFactory.LoadThumbnail (uri)) != null) {
+			if ((thumb = XdgThumbnailSpec.LoadThumbnail (uri, ThumbnailSize.Large, null)) != null) {
 				pixbuf_orientation = PixbufOrientation.TopLeft;
 				EventHandler<AreaPreparedEventArgs> prep = AreaPrepared;
 				if (prep != null)
diff --git a/src/PhotoLoader.cs b/src/PhotoLoader.cs
index 84c0f4f..9a6eedb 100644
--- a/src/PhotoLoader.cs
+++ b/src/PhotoLoader.cs
@@ -22,7 +22,6 @@ namespace FSpot {
 		{
 			using (ImageFile img = ImageFile.Create (item.DefaultVersion.Uri)) {
 				Gdk.Pixbuf pixbuf = img.Load ();
-				ValidateThumbnail (item, pixbuf);
 				return pixbuf;
 			}
 		}
@@ -31,27 +30,10 @@ namespace FSpot {
 		{
 			using (ImageFile img = ImageFile.Create (item.DefaultVersion.Uri)) {
 				Gdk.Pixbuf pixbuf = img.Load (width, height);
-				ValidateThumbnail (item.DefaultVersion.Uri, pixbuf);
 				return pixbuf;
 			}
 		}
 
-		static public Gdk.Pixbuf ValidateThumbnail (IBrowsableItem item, Gdk.Pixbuf pixbuf)
-		{
-			return ValidateThumbnail (item.DefaultVersion.Uri, pixbuf);
-		}
-
-		static public Gdk.Pixbuf ValidateThumbnail (SafeUri uri, Gdk.Pixbuf pixbuf)
-		{			
-			using (Gdk.Pixbuf thumbnail = ThumbnailCache.Default.GetThumbnailForUri (uri)) {
-				if (pixbuf != null && thumbnail != null && !ThumbnailFactory.ThumbnailIsValid (thumbnail, uri)) {
-					Log.DebugFormat ("regenerating thumbnail for {0}", uri);
-					FSpot.ThumbnailGenerator.Default.Request (uri, 0, 256, 256);
-				}
-			}
-			return pixbuf;
-		}
-
 		public PhotoLoader (PhotoQuery query)
 		{
 			this.query = query;
diff --git a/src/PixbufCache.cs b/src/PixbufCache.cs
index df6b7ad..402feeb 100644
--- a/src/PixbufCache.cs
+++ b/src/PixbufCache.cs
@@ -12,6 +12,7 @@ using System.Collections;
 using System.Threading;
 using Hyena;
 
+using FSpot.Utils;
 using FSpot.Platform;
 
 namespace FSpot {
@@ -34,13 +35,12 @@ namespace FSpot {
 			worker = new Thread (new ThreadStart (WorkerTask));
 			worker.Start ();
 
-			ThumbnailGenerator.Default.OnPixbufLoaded += HandleThumbnailLoaded;
+			ThumbnailLoader.Default.OnPixbufLoaded += HandleThumbnailLoaded;
 		}
 		
-		public void HandleThumbnailLoaded (ImageLoaderThread loader, SafeUri uri, int order, Gdk.Pixbuf result)
+		public void HandleThumbnailLoaded (ImageLoaderThread loader, ImageLoaderThread.RequestItem result)
 		{
-			if (result != null)
-				Reload (uri);
+            Reload (result.Uri);
 		}
 
 		public void Request (SafeUri uri, object closure, int width, int height)
@@ -184,7 +184,7 @@ namespace FSpot {
 		{
 			Gdk.Pixbuf loaded = null;
 			try {
-				loaded = ThumbnailFactory.LoadThumbnail (entry.Uri);
+				loaded = XdgThumbnailSpec.LoadThumbnail (entry.Uri, ThumbnailSize.Large);
 				this.Update (entry, loaded);
 			} catch (GLib.GException){
 				if (loaded != null)
diff --git a/src/Platform/Makefile.am b/src/Platform/Makefile.am
index 8afaef1..8ddca2c 100644
--- a/src/Platform/Makefile.am
+++ b/src/Platform/Makefile.am
@@ -6,7 +6,6 @@ SOURCES = \
 	Gnome/Desktop.cs \
 	Gnome/PreferenceBackend.cs \
 	Gnome/ScreenSaver.cs \
-	Gnome/ThumbnailFactory.cs \
 	Gnome/WebProxy.cs
 
 # Currently unused
@@ -14,7 +13,6 @@ SOURCES = \
 #	Null/Desktop.cs \
 #	Null/PreferenceBackend.cs \
 #	Null/ScreenSaver.cs \
-#	Null/ThumbnailFactory.cs \
 #	Null/WebProxy.cs
 
 RESOURCES =
diff --git a/src/RotateCommand.cs b/src/RotateCommand.cs
index e4b80f1..1749dcd 100644
--- a/src/RotateCommand.cs
+++ b/src/RotateCommand.cs
@@ -134,10 +134,6 @@ namespace FSpot {
 
 			Rotate (original_path, direction);
 
-			Gdk.Pixbuf thumb = FSpot.ThumbnailGenerator.Create (new SafeUri (original_path));
-			if (thumb != null)
-				thumb.Dispose ();
-		
 			return !done;
 		}
 	}
diff --git a/src/SingleView.cs b/src/SingleView.cs
index 1e27ea6..d62cfe6 100644
--- a/src/SingleView.cs
+++ b/src/SingleView.cs
@@ -122,7 +122,7 @@ namespace FSpot {
 			sidebar.CloseRequested += HandleHideSidePane;
 			sidebar.Show ();
 
-			ThumbnailGenerator.Default.OnPixbufLoaded += delegate { directory_view.QueueDraw (); };
+			ThumbnailLoader.Default.OnPixbufLoaded += delegate { directory_view.QueueDraw (); };
 
 			image_view = new PhotoImageView (collection);
 			GtkUtil.ModifyColors (image_view);
diff --git a/src/ThumbnailCommand.cs b/src/ThumbnailCommand.cs
index d328fea..34a9756 100644
--- a/src/ThumbnailCommand.cs
+++ b/src/ThumbnailCommand.cs
@@ -1,6 +1,7 @@
 using System;
 using Gtk;
 using FSpot;
+using FSpot.Utils;
 using FSpot.UI.Dialog;
 
 public class ThumbnailCommand {
@@ -15,7 +16,7 @@ public class ThumbnailCommand {
 	public bool Execute (Photo [] photos)
 	{
 		ProgressDialog progress_dialog = null;
-
+        var loader = ThumbnailLoader.Default;
 		if (photos.Length > 1) {
 			progress_dialog = new ProgressDialog (Mono.Unix.Catalog.GetString ("Updating Thumbnails"),
 							      ProgressDialog.CancelButtonType.Stop,
@@ -29,9 +30,7 @@ public class ThumbnailCommand {
 				break;
 
 			foreach (uint version_id in p.VersionIds) {
-				Gdk.Pixbuf thumb = FSpot.ThumbnailGenerator.Create (p.VersionUri (version_id));
-				if (thumb !=  null)
-					thumb.Dispose ();
+				loader.Request (p.VersionUri (version_id), ThumbnailSize.Large, 10);
 			}
 			
 			count++;
diff --git a/src/ThumbnailGenerator.cs b/src/ThumbnailGenerator.cs
index b987dce..d9a61ac 100644
--- a/src/ThumbnailGenerator.cs
+++ b/src/ThumbnailGenerator.cs
@@ -16,64 +16,20 @@ using Mono.Unix.Native;
 using GFileInfo = GLib.FileInfo;
 
 namespace FSpot {
-	public class ThumbnailGenerator : ImageLoaderThread {
-
-		static public ThumbnailGenerator Default = new ThumbnailGenerator ();
-
-		public static Gdk.Pixbuf Create (SafeUri uri)
-		{
-			try {
-				Gdk.Pixbuf thumb;
-
-				using (ImageFile img = ImageFile.Create (uri)) {
-					thumb = img.Load (256, 256);
-				}
-
-				if (thumb == null)
-					return null;
-
-				Save (thumb, uri);
-				return thumb;
-			} catch (Exception e) {
-				Log.Exception (e);
-				return null;
-			}
-		}
-		
-		private static void Save (Gdk.Pixbuf image, SafeUri uri)
-		{
-			try {
-				ThumbnailCache.Default.RemoveThumbnailForUri (uri);
-			} finally {
-				ThumbnailFactory.SaveThumbnail (image, uri);
-			}
-		}
-
-		protected override void EmitLoaded (System.Collections.Queue results)
-		{
-			base.EmitLoaded (results);
-			
-			foreach (RequestItem r in results) {
-				if (r.result != null)
-					r.result.Dispose ();
-			}
-				
-		}
-
-		protected override void ProcessRequest (RequestItem request)
-		{
-			try {
-				base.ProcessRequest (request);
-
-				Gdk.Pixbuf image = request.result;
-				if (image != null)
-					Save (image, request.uri);
-
-				System.Threading.Thread.Sleep (75);
-			} catch (System.Exception e) {
-				Log.Exception (e);
-			}
-		}
-
-	}
+    public class ThumbnailLoader : ImageLoaderThread {
+
+        static public ThumbnailLoader Default = new ThumbnailLoader ();
+
+        public void Request (SafeUri uri, ThumbnailSize size, int order)
+        {
+            var pixels = size == ThumbnailSize.Normal ? 128 : 256;
+            Request (uri, order, pixels, pixels);
+        }
+
+        protected override void ProcessRequest (RequestItem request)
+        {
+            var size = request.Width == 128 ? ThumbnailSize.Normal : ThumbnailSize.Large;
+            request.Result = XdgThumbnailSpec.LoadThumbnail (request.Uri, size);
+        }
+    }
 }
diff --git a/src/Utils/FSpot.Utils.dll.config b/src/Utils/FSpot.Utils.dll.config
index 20b20ab..8b57d83 100644
--- a/src/Utils/FSpot.Utils.dll.config
+++ b/src/Utils/FSpot.Utils.dll.config
@@ -1,4 +1,6 @@
 <configuration>
+  <dllmap dll="libglib-2.0-0.dll" target="libglib-2.0.so.0" os="!windows,osx"/>
+  <dllmap dll="libglib-2.0-0.dll" target="libglib-2.0.dylib" os="osx"/>
   <dllmap dll="libgdk-2.0-0.dll" target="libgdk-x11-2.0.so.0"/>
   <dllmap dll="libgdk_pixbuf-2.0-0.dll" target="libgdk_pixbuf-2.0.so.0"/>
   <dllmap dll="X11" target="libX11.so.6"/>
diff --git a/src/Utils/Makefile.am b/src/Utils/Makefile.am
index 1c9fd5a..4d07e43 100644
--- a/src/Utils/Makefile.am
+++ b/src/Utils/Makefile.am
@@ -13,9 +13,12 @@ SOURCES = \
 	PixbufOrientation.cs \
 	PixbufUtils.cs \
 	RecursiveFileEnumerator.cs \
+	SafeUri.cs \
+	SafeUriExtensions.cs \
 	Unix.cs \
 	UriExtensions.cs \
-	UriUtils.cs
+	UriUtils.cs \
+	XdgThumbnailSpec.cs
 
 RESOURCES =
 
diff --git a/src/Core/SafeUri.cs b/src/Utils/SafeUri.cs
similarity index 100%
rename from src/Core/SafeUri.cs
rename to src/Utils/SafeUri.cs
diff --git a/src/Core/SafeUriExtensions.cs b/src/Utils/SafeUriExtensions.cs
similarity index 100%
rename from src/Core/SafeUriExtensions.cs
rename to src/Utils/SafeUriExtensions.cs
diff --git a/src/Utils/XdgThumbnailSpec.cs b/src/Utils/XdgThumbnailSpec.cs
new file mode 100644
index 0000000..c9eabf9
--- /dev/null
+++ b/src/Utils/XdgThumbnailSpec.cs
@@ -0,0 +1,159 @@
+using Gdk;
+using System;
+using System.Runtime.InteropServices;
+using Hyena;
+
+namespace FSpot.Utils
+{
+    public enum ThumbnailSize
+    {
+        Normal,
+        Large
+    }
+
+    public static class XdgThumbnailSpec
+    {
+#region Public API
+        public delegate Pixbuf PixbufLoader (SafeUri uri);
+        public static PixbufLoader DefaultLoader { get; set; }
+
+        public static Pixbuf LoadThumbnail (SafeUri uri, ThumbnailSize size)
+        {
+            return LoadThumbnail (uri, size, DefaultLoader);
+        }
+
+        public static Pixbuf LoadThumbnail (SafeUri uri, ThumbnailSize size, PixbufLoader loader)
+        {
+            var thumb_uri = ThumbUri (uri, size);
+            var pixbuf = LoadFromUri (thumb_uri);
+            if (!IsValid (uri, pixbuf)) {
+                Log.DebugFormat ("Invalid thumbnail, reloading: {0}", uri);
+                if (pixbuf != null)
+                    pixbuf.Dispose ();
+
+                if (loader == null)
+                    return null;
+
+                pixbuf = CreateFrom (uri, thumb_uri, size, loader);
+            }
+            return pixbuf;
+        }
+
+        public static void RemoveThumbnail (SafeUri uri)
+        {
+            var normal_uri = ThumbUri (uri, ThumbnailSize.Normal);
+            var large_uri = ThumbUri (uri, ThumbnailSize.Large);
+
+            var file = GLib.FileFactory.NewForUri (normal_uri);
+            if (file.Exists)
+                file.Delete (null);
+
+            file = GLib.FileFactory.NewForUri (large_uri);
+            if (file.Exists)
+                file.Delete (null);
+        }
+#endregion
+
+#region Private helpers
+        const string ThumbMTimeOpt = "tEXt::Thumb::MTime";
+        const string ThumbUriOpt = "tEXt::Thumb::URI";
+
+        static SafeUri home_dir = new SafeUri (Environment.GetFolderPath (Environment.SpecialFolder.Personal));
+
+        private static Pixbuf CreateFrom (SafeUri uri, SafeUri thumb_uri, ThumbnailSize size, PixbufLoader loader)
+        {
+            var pixels = size == ThumbnailSize.Normal ? 128 : 256;
+            var pixbuf = loader (uri); 
+            double scale_x = (double) pixbuf.Width / pixels;
+            double scale_y = (double) pixbuf.Height / pixels;
+            double scale = Math.Max (1.0, Math.Max (scale_x, scale_y));
+            int target_x = (int) (pixbuf.Width / scale);
+            int target_y = (int) (pixbuf.Height / scale);
+            var thumb_pixbuf = pixbuf.ScaleSimple (target_x, target_y, InterpType.Nearest);
+            pixbuf.Dispose ();
+
+            var file = GLib.FileFactory.NewForUri (uri);
+            var info = file.QueryInfo ("time::modified", GLib.FileQueryInfoFlags.None, null);
+            var mtime = info.GetAttributeULong ("time::modified").ToString ();
+
+            thumb_pixbuf.Save (thumb_uri.LocalPath, "png", uri, mtime);
+
+            return thumb_pixbuf;
+        }
+
+        private static SafeUri ThumbUri (SafeUri uri, ThumbnailSize size)
+        {
+            var hash = CryptoUtil.Md5Encode (uri);
+            return home_dir.Append (".thumbnails")
+                           .Append (size == ThumbnailSize.Normal ? "normal" : "large")
+                           .Append (hash + ".png");
+        }
+
+        private static Pixbuf LoadFromUri (SafeUri uri)
+        {
+            var file = GLib.FileFactory.NewForUri (uri);
+            if (!file.Exists)
+                return null;
+            var stream = new GLib.GioStream (file.Read (null)); 
+            var pixbuf = new Pixbuf (stream);
+            stream.Close ();
+            return pixbuf;
+        }
+
+        private static bool IsValid (SafeUri uri, Pixbuf pixbuf)
+        {
+            if (pixbuf == null) {
+                return false;
+            }
+
+            if (pixbuf.GetOption (ThumbUriOpt) != uri.ToString ()) {
+                return false;
+            }
+
+            var file = GLib.FileFactory.NewForUri (uri);
+            var info = file.QueryInfo ("time::modified", GLib.FileQueryInfoFlags.None, null);
+
+            if (pixbuf.GetOption (ThumbMTimeOpt) != info.GetAttributeULong ("time::modified").ToString ()) {
+                return false;
+            }
+
+            return true;
+        }
+#endregion
+
+
+#region Gdk hackery
+        
+        // This hack below is needed because there is no wrapped version of
+        // Save which allows specifying the variable arguments (it's not
+        // possible with p/invoke). It embeds the thumb uri and mtime in the
+        // saved file.
+
+        [DllImport("libgdk_pixbuf-2.0-0.dll")]
+        static extern bool gdk_pixbuf_save(IntPtr raw, IntPtr filename, IntPtr type, out IntPtr error,
+                IntPtr optlabel1, IntPtr optvalue1, IntPtr optlabel2, IntPtr optvalue2, IntPtr dummy);
+        
+        static bool Save (this Pixbuf pixbuf, string filename, string type, string uri_opt, string mtime_opt)
+        {
+            IntPtr error = IntPtr.Zero;
+            IntPtr nfilename = GLib.Marshaller.StringToPtrGStrdup (filename);
+            IntPtr ntype = GLib.Marshaller.StringToPtrGStrdup (type);
+            IntPtr optlabel1 = GLib.Marshaller.StringToPtrGStrdup (ThumbUriOpt);
+            IntPtr optvalue1 = GLib.Marshaller.StringToPtrGStrdup (uri_opt);
+            IntPtr optlabel2 = GLib.Marshaller.StringToPtrGStrdup (ThumbMTimeOpt);
+            IntPtr optvalue2 = GLib.Marshaller.StringToPtrGStrdup (mtime_opt);
+            bool ret = gdk_pixbuf_save(pixbuf.Handle, nfilename, ntype, out error, optlabel1, optvalue1, optlabel2, optvalue2, IntPtr.Zero);
+            GLib.Marshaller.Free (nfilename);
+            GLib.Marshaller.Free (ntype);
+            GLib.Marshaller.Free (optlabel1);
+            GLib.Marshaller.Free (optvalue1);
+            GLib.Marshaller.Free (optlabel2);
+            GLib.Marshaller.Free (optvalue2);
+            if (error != IntPtr.Zero) throw new GLib.GException (error);
+            return ret;
+        }
+
+#endregion
+
+    }
+}
diff --git a/src/Widgets/Filmstrip.cs b/src/Widgets/Filmstrip.cs
index 155cf85..feba909 100644
--- a/src/Widgets/Filmstrip.cs
+++ b/src/Widgets/Filmstrip.cs
@@ -307,7 +307,7 @@ namespace FSpot.Widgets
 			this.selection.Collection.ItemsChanged += HandleCollectionItemsChanged;
 			this.squared_thumbs = squared_thumbs;
 			thumb_cache = new DisposableCache<SafeUri, Pixbuf> (30);
-			ThumbnailGenerator.Default.OnPixbufLoaded += HandlePixbufLoaded;
+			ThumbnailLoader.Default.OnPixbufLoaded += HandlePixbufLoaded;
 
 			animation = new DoubleAnimation (0, 0, TimeSpan.FromSeconds (1.5), SetPositionCore, new CubicEase (EasingMode.EaseOut));
 		}
@@ -548,8 +548,8 @@ namespace FSpot.Widgets
 			QueueDraw ();
 		}
 
-		void HandlePixbufLoaded (ImageLoaderThread pl, SafeUri uri, int order, Pixbuf p) {
-			if (!thumb_cache.Contains (uri)) {
+		void HandlePixbufLoaded (ImageLoaderThread pl, ImageLoaderThread.RequestItem item) {
+			if (!thumb_cache.Contains (item.Uri)) {
 				return;
 			}
 			
@@ -621,25 +621,19 @@ namespace FSpot.Widgets
 			}
 
 			if (current == null) {
-				try {
-					ThumbnailGenerator.Default.Request ((selection.Collection [i]).DefaultVersion.Uri, 0, 256, 256);
-
+                var pixbuf = XdgThumbnailSpec.LoadThumbnail (uri, ThumbnailSize.Large, null);
+                if (pixbuf == null) {
+					ThumbnailLoader.Default.Request (uri, ThumbnailSize.Large, 0);
+                    current = FSpot.Global.IconTheme.LoadIcon ("gtk-missing-image", ThumbSize, (Gtk.IconLookupFlags)0);
+                } else {
 					if (SquaredThumbs) {
-						using (Pixbuf p = ThumbnailFactory.LoadThumbnail (uri)) {
-							current = PixbufUtils.IconFromPixbuf (p, ThumbSize);
-						}
-					} else 
-						current = ThumbnailFactory.LoadThumbnail (uri, -1, ThumbSize);
+                        current = PixbufUtils.IconFromPixbuf (pixbuf, ThumbSize);
+                    } else {
+                        current = pixbuf.ScaleSimple (ThumbSize, ThumbSize, InterpType.Nearest);
+                    }
+                    pixbuf.Dispose ();
 					thumb_cache.Add (uri, current);
-				} catch {
-					try {
-						current = FSpot.Global.IconTheme.LoadIcon ("gtk-missing-image", ThumbSize, (Gtk.IconLookupFlags)0);
-					} catch {
-						current = null;
-					}
-					thumb_cache.Add (uri, null);
-				}
-
+                }
 			}
 			
 			//FIXME: we might end up leaking a pixbuf here
@@ -692,7 +686,7 @@ namespace FSpot.Widgets
 				this.selection.Changed -= HandlePointerChanged;
 				this.selection.Collection.Changed -= HandleCollectionChanged;
 				this.selection.Collection.ItemsChanged -= HandleCollectionItemsChanged;
-				ThumbnailGenerator.Default.OnPixbufLoaded -= HandlePixbufLoaded;
+				ThumbnailLoader.Default.OnPixbufLoaded -= HandlePixbufLoaded;
 				if (background_pixbuf != null)
 					background_pixbuf.Dispose ();
 				if (background_tile != null)
diff --git a/src/Widgets/IconView.cs b/src/Widgets/IconView.cs
index 59be723..b5e987d 100644
--- a/src/Widgets/IconView.cs
+++ b/src/Widgets/IconView.cs
@@ -1380,15 +1380,6 @@ namespace FSpot.Widgets
 
 			if (order >= 0 && order < collection.Count) {
 				var uri = collection [order].DefaultVersion.Uri;
-
-				if (result == null && !ThumbnailFactory.ThumbnailExists (uri))
-					FSpot.ThumbnailGenerator.Default.Request (uri, 0, 256, 256);
-
-				if (result == null)
-					return;
-
-				if (!ThumbnailFactory.ThumbnailIsValid (result, uri))
-					FSpot.ThumbnailGenerator.Default.Request (uri, 0, 256, 256);
 			}
 
 			if (result == null)
diff --git a/src/main.cs b/src/main.cs
index 5640eaa..d8cfba0 100644
--- a/src/main.cs
+++ b/src/main.cs
@@ -61,6 +61,11 @@ namespace FSpot
 		{
 			List<string> uris = new List<string> ();
 			Unix.SetProcessName (Defines.PACKAGE);
+            ThreadAssist.InitializeMainThread ();
+            XdgThumbnailSpec.DefaultLoader = (uri) => {
+                using (var file = ImageFile.Create (uri))
+                    return file.Load ();
+            };
 
 			// Options and Option parsing
 			bool shutdown = false;



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