[f-spot] Plug massive memory leak by replacing thumbnailing.
- From: Ruben Vermeersch <rubenv src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [f-spot] Plug massive memory leak by replacing thumbnailing.
- Date: Thu, 3 Jun 2010 17:45:19 +0000 (UTC)
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]