[f-spot/rubenv-gsoc-2009: 23/86] Transform the simple ImageLoaders to item loaders.



commit b7e907e9507ed51ef11112b07bdfbed0d78d1937
Author: Ruben Vermeersch <ruben savanne be>
Date:   Mon Jul 27 20:10:57 2009 +0200

    Transform the simple ImageLoaders to item loaders.
    
    This brings back the ideas from the unified-loading branch.
    
    Changes:
     * Rename load_thumbnail to the more suitable load_embedded in
       libfspotraw.
    
     * Changed IImageLoader to expose multiple loadable items. Related
       changes to event args, extension methods etc.
    
     * Ported both loaders to this new API. Lots of code duplication because
       they have no shared base class. Not very fond of this fact.
    
     * Changed PhotoImageView such that it uses the new ImageLoader API.

 lib/libfspotraw/fspot-librawloader.cpp   |    2 +-
 lib/libfspotraw/fspot-librawloader.h     |    2 +-
 src/Loaders/AreaPreparedEventArgs.cs     |   17 +--
 src/Loaders/AreaUpdatedEventArgs.cs      |   19 +-
 src/Loaders/GdkImageLoader.cs            |  290 +++++++++++++++++++-----------
 src/Loaders/IImageLoader.cs              |   12 +-
 src/Loaders/IImageLoaderExtensions.cs    |   54 ++++++
 src/Loaders/ImageLoader.cs               |    2 +-
 src/Loaders/ImageLoaderItem.cs           |   21 +++
 src/Loaders/ImageLoaderItemExtensions.cs |   30 +++
 src/Loaders/ItemCompletedEventArgs.cs    |   22 +++
 src/Loaders/LibrawImageLoader.cs         |  274 ++++++++++++++++++++++------
 src/Makefile.am                          |    4 +
 src/PhotoImageView.cs                    |   30 +++-
 14 files changed, 579 insertions(+), 200 deletions(-)
---
diff --git a/lib/libfspotraw/fspot-librawloader.cpp b/lib/libfspotraw/fspot-librawloader.cpp
index 0df9dff..0a96205 100644
--- a/lib/libfspotraw/fspot-librawloader.cpp
+++ b/lib/libfspotraw/fspot-librawloader.cpp
@@ -180,7 +180,7 @@ fspot_librawloader_finalize (GObject *object)
 }
 
 GdkPixbuf *
-fspot_librawloader_load_thumbnail (FSpotLibrawLoader *self, int *orientation)
+fspot_librawloader_load_embedded (FSpotLibrawLoader *self, int *orientation)
 {
 	int result;
 	libraw_processed_image_t *image = NULL;
diff --git a/lib/libfspotraw/fspot-librawloader.h b/lib/libfspotraw/fspot-librawloader.h
index 5e25484..4b155d5 100644
--- a/lib/libfspotraw/fspot-librawloader.h
+++ b/lib/libfspotraw/fspot-librawloader.h
@@ -42,7 +42,7 @@ struct _FSpotLibrawLoaderClass
 };
 
 GType fspot_librawloader_get_type (void);
-GdkPixbuf * fspot_librawloader_load_thumbnail (FSpotLibrawLoader *self, int *orientation);
+GdkPixbuf * fspot_librawloader_load_embedded (FSpotLibrawLoader *self, int *orientation);
 GdkPixbuf * fspot_librawloader_load_full (FSpotLibrawLoader *self);
 FSpotLibrawLoader * fspot_librawloader_new (const gchar *filename);
 gboolean fspot_librawloader_get_aborted (FSpotLibrawLoader *self);
diff --git a/src/Loaders/AreaPreparedEventArgs.cs b/src/Loaders/AreaPreparedEventArgs.cs
index 82387d7..8055a15 100644
--- a/src/Loaders/AreaPreparedEventArgs.cs
+++ b/src/Loaders/AreaPreparedEventArgs.cs
@@ -1,29 +1,22 @@
 //
-// Fspot.Loaders.AreaPreparedEventArgs.cs
-//
-// Copyright (c) 2009 Novell, Inc.
+// Fspot/Loaders/AreaPreparedEventArgs.cs
 //
 // Author(s)
-//	Stephane Delcroix  <sdelcroix novell com>
+//	Ruben Vermeersch  <ruben savanne be>
 //
 // This is free software. See COPYING for details
 //
 
 using System;
-using Gdk;
 
 namespace FSpot.Loaders {
 	public class AreaPreparedEventArgs : EventArgs
 	{
-		bool reduced_resolution;
+		public ImageLoaderItem Item { get; private set; }
 
-		public bool ReducedResolution {
-			get { return reduced_resolution; }
-		}
-	
-		public AreaPreparedEventArgs (bool reduced_resolution) : base ()
+		public AreaPreparedEventArgs (ImageLoaderItem item) : base ()
 		{
-			this.reduced_resolution = reduced_resolution;
+			this.Item = item;
 		}
 	}
 }
diff --git a/src/Loaders/AreaUpdatedEventArgs.cs b/src/Loaders/AreaUpdatedEventArgs.cs
index 7308edf..17dd5cb 100644
--- a/src/Loaders/AreaUpdatedEventArgs.cs
+++ b/src/Loaders/AreaUpdatedEventArgs.cs
@@ -1,28 +1,27 @@
 //
-// Fspot.Loaders.AreaUpdatedEventArgs.cs
-//
-// Copyright (c) 2009 Novell, Inc.
+// Fspot/Loaders/AreaUpdatedEventArgs.cs
 //
 // Author(s)
+//	Ruben Vermeersch  <ruben savanne be>
 //	Stephane Delcroix  <sdelcroix novell com>
 //
+//
 // This is free software. See COPYING for details
 //
 
-using System;
 using Gdk;
+using System;
 
 namespace FSpot.Loaders {
 	public class AreaUpdatedEventArgs : EventArgs
 	{
-		Gdk.Rectangle area;
-		public Gdk.Rectangle Area { 
-			get { return area; }
-		}
+		public ImageLoaderItem Item { get; private set; }
+		public Rectangle Area { get; private set; }
 
-		public AreaUpdatedEventArgs (Gdk.Rectangle area) : base ()
+		public AreaUpdatedEventArgs (ImageLoaderItem item, Rectangle area) : base ()
 		{
-			this.area = area;
+			this.Item = item;
+			this.Area = area;
 		}
 	}
 }
diff --git a/src/Loaders/GdkImageLoader.cs b/src/Loaders/GdkImageLoader.cs
index 4d9a24c..8812279 100644
--- a/src/Loaders/GdkImageLoader.cs
+++ b/src/Loaders/GdkImageLoader.cs
@@ -9,86 +9,69 @@
 // This is free software. See COPYING for details
 //
 
+using FSpot.Platform;
+using FSpot.Utils;
+using Gdk;
 using System;
 using System.Threading;
-using Gdk;
-using FSpot.Utils;
-using FSpot.Platform;
 
 namespace FSpot.Loaders {
 	public class GdkImageLoader : Gdk.PixbufLoader, IImageLoader
 	{
-		Uri uri = null;
-
-#region public api
-		public GdkImageLoader () : base ()
-		{	
-		}
-
-		public void Load (Uri uri)
-		{
-			if (this.uri != null)
-				throw new Exception ("You should only request one image per loader!");
-			this.uri = uri;
-
-			if (is_disposed)
-				return;
+		Uri uri;
+		object sync_handle = new object ();
+		bool is_disposed = false;
+		Rectangle damage;
 
-			//First, send a thumbnail if we have one
-			if ((thumb = ThumbnailFactory.LoadThumbnail (uri)) != null) {
-				pixbuf_orientation = PixbufOrientation.TopLeft;
-				EventHandler<AreaPreparedEventArgs> prep = AreaPrepared;
-				if (prep != null)
-					prep (this, new AreaPreparedEventArgs (true));
-				EventHandler<AreaUpdatedEventArgs> upd = AreaUpdated;
-				if (upd != null)
-					upd (this, new AreaUpdatedEventArgs (new Rectangle (0, 0, thumb.Width, thumb.Height)));
-			}
+		public ImageLoaderItem ItemsRequested { get; private set; }
+		public ImageLoaderItem ItemsCompleted { get; private set; }
 
-			using (ImageFile image_file = ImageFile.Create (uri)) {
-				image_stream = image_file.PixbufStream ();
-				pixbuf_orientation = image_file.Orientation;
-			}
+		Pixbuf thumbnail;
+		public Pixbuf Thumbnail {
+			get { return PixbufUtils.ShallowCopy (thumbnail); }
+			private set { thumbnail = value; }
+		}
+		public PixbufOrientation ThumbnailOrientation { get; private set; }
 
-			loading = true;
-			// The ThreadPool.QueueUserWorkItem hack is there cause, as the bytes to read are present in the stream,
-			// the Read is CompletedAsynchronously, blocking the mainloop
-			image_stream.BeginRead (buffer, 0, count, delegate (IAsyncResult r) {
-				ThreadPool.QueueUserWorkItem (delegate {HandleReadDone (r);});
-			}, null);
+		public Pixbuf Large {
+			get { return PixbufUtils.ShallowCopy (Pixbuf); }
 		}
+		public PixbufOrientation LargeOrientation { get; private set; }
+
+		public Pixbuf Full { get { return Large; } }
+		public PixbufOrientation FullOrientation { get { return LargeOrientation; } }
 
 		new public event EventHandler<AreaPreparedEventArgs> AreaPrepared;
 		new public event EventHandler<AreaUpdatedEventArgs> AreaUpdated;
-		public event EventHandler Completed;
+		public event EventHandler<ItemsCompletedEventArgs> Completed;
 
+		public bool Loading { get; private set; }
 
-		Pixbuf thumb;
-		new public Pixbuf Pixbuf {
-			get {
-				if (thumb != null)
-					return thumb;
-				return base.Pixbuf;
-			}
-		}
+#region public api
+		public GdkImageLoader (Uri uri) : base ()
+		{
+			this.uri = uri;
+			Loading = false;
 
-		bool loading = false;
-		public bool Loading {
-			get { return loading; }
+			ItemsRequested = ImageLoaderItem.None;
+			ItemsCompleted = ImageLoaderItem.None;
 		}
 
-		bool notify_prepared = false;
-		bool prepared = false;
-		public bool Prepared {
-			get { return prepared; }
-		}
+		public ImageLoaderItem Load (ImageLoaderItem items, bool async)
+		{
+			if (is_disposed)
+				return ImageLoaderItem.None;
+
+			ItemsRequested |= items;
 
-		PixbufOrientation pixbuf_orientation = PixbufOrientation.TopLeft;
-		public PixbufOrientation PixbufOrientation {
-			get { return pixbuf_orientation; }
+			StartLoading ();
+
+			if (!async)
+				WaitForCompletion (items);
+
+			return ItemsCompleted & items;
 		}
 
-		bool is_disposed = false;
 		public override void Dispose ()
 		{
 			is_disposed = true;
@@ -99,9 +82,9 @@ namespace FSpot.Loaders {
 				{
 				}
 			Close ();
-			if (thumb != null) {
-				thumb.Dispose ();
-				thumb = null;
+			if (thumbnail != null) {
+				thumbnail.Dispose ();
+				thumbnail = null;
 			}
 			base.Dispose ();
 		}
@@ -120,9 +103,8 @@ namespace FSpot.Loaders {
 			if (is_disposed)
 				return;
 
-			prepared = notify_prepared = true;
-			damage = Rectangle.Zero;
 			base.OnAreaPrepared ();
+			SignalAreaPrepared (ImageLoaderItem.Large | ImageLoaderItem.Full);
 		}
 
 		protected override void OnAreaUpdated (int x, int y, int width, int height)
@@ -131,8 +113,8 @@ namespace FSpot.Loaders {
 				return;
 
 			Rectangle area = new Rectangle (x, y, width, height);
-			damage = damage == Rectangle.Zero ? area : damage.Union (area);
 			base.OnAreaUpdated (x, y, width, height);
+			SignalAreaUpdated (ImageLoaderItem.Large | ImageLoaderItem.Full, area);
 		}
 
 		protected virtual void OnCompleted ()
@@ -140,10 +122,7 @@ namespace FSpot.Loaders {
 			if (is_disposed)
 				return;
 
-			EventHandler eh = Completed;
-			if (eh != null)
-				eh (this, EventArgs.Empty);
-			Close ();
+			SignalItemCompleted (ImageLoaderItem.Large | ImageLoaderItem.Full);
 		}
 #endregion
 
@@ -151,62 +130,163 @@ namespace FSpot.Loaders {
 		System.IO.Stream image_stream;
 		const int count = 1 << 16;
 		byte [] buffer = new byte [count];
-		bool notify_completed = false;
-		Rectangle damage;
-		object sync_handle = new object ();
 
-		void HandleReadDone (IAsyncResult ar)
+		void StartLoading ()
+		{
+			lock (sync_handle) {
+				if (Loading)
+					return;
+				Loading = true;
+			}
+
+			// Load thumbnail immediately, if required
+			if (!ItemsCompleted.Contains (ImageLoaderItem.Thumbnail) &&
+				 ItemsRequested.Contains (ImageLoaderItem.Thumbnail)) {
+				LoadThumbnail ();
+			}
+
+			ThreadPool.QueueUserWorkItem (delegate {
+					try {
+						DoLoad ();
+					} catch (Exception e) {
+						Log.Debug (e.ToString ());
+						Log.Debug ("Requested: {0}, Done: {1}", ItemsRequested, ItemsCompleted);
+						Gtk.Application.Invoke (delegate { throw e; });
+					}
+				});
+		}
+
+		void DoLoad ()
+		{
+			while (!is_disposed && !ItemsCompleted.Contains (ItemsRequested)) {
+				if (ItemsRequested.Contains (ImageLoaderItem.Thumbnail))
+					LoadThumbnail ();
+
+				if (ItemsRequested.Contains (ImageLoaderItem.Large))
+					LoadLarge ();
+			}
+
+			lock (sync_handle) {
+				Loading = false;
+			}
+		}
+
+		void LoadThumbnail ()
 		{
 			if (is_disposed)
 				return;
 
-			int byte_read = image_stream.EndRead (ar);
-			lock (sync_handle) {
+			// Check if the thumbnail exists, if not: try to create it from the
+			// Large image. Will request Large if it is not present and wait
+			// for the next call to generate it (see the loop in DoLoad).
+			if (!ThumbnailFactory.ThumbnailExists (uri)) {
+				if (ItemsCompleted.Contains (ImageLoaderItem.Large)) {
+					ThumbnailFactory.SaveThumbnail (Pixbuf, uri);
+				} else {
+					ItemsRequested |= ImageLoaderItem.Large;
+					return;
+				}
+			}
+
+			Thumbnail = ThumbnailFactory.LoadThumbnail (uri);
+			ThumbnailOrientation = PixbufOrientation.TopLeft;
+			if (Thumbnail == null)
+				throw new Exception ("Null thumbnail returned");
+
+			SignalAreaPrepared (ImageLoaderItem.Thumbnail);
+			SignalAreaUpdated (ImageLoaderItem.Thumbnail, new Rectangle (0, 0, thumbnail.Width, thumbnail.Height));
+			SignalItemCompleted (ImageLoaderItem.Thumbnail);
+		}
+
+		void LoadLarge ()
+		{
+			if (is_disposed)
+				return;
+
+			using (ImageFile image_file = ImageFile.Create (uri)) {
+				image_stream = image_file.PixbufStream ();
+				LargeOrientation = image_file.Orientation;
+			}
+
+			while (Loading && !is_disposed) {
+				int byte_read = image_stream.Read (buffer, 0, count);
+
 				if (byte_read == 0) {
 					image_stream.Close ();
-					Close ();
-					loading = false;
-					notify_completed = true;
+                    Close ();
+					Loading = false;
+					SignalItemCompleted (ImageLoaderItem.Large | ImageLoaderItem.Full);
 				} else {
 					try {
-						if (!is_disposed && Write (buffer, (ulong)byte_read))
-							image_stream.BeginRead (buffer, 0, count, HandleReadDone, null);
+						Write (buffer, (ulong)byte_read);
 					} catch (System.ObjectDisposedException) {
 					} catch (GLib.GException) {
 					}
 				}
 			}
+		}
 
-			GLib.Idle.Add (delegate {
-				//Send the AreaPrepared event
-				if (notify_prepared) {
-					notify_prepared = false;
-					if (thumb != null) {
-						thumb.Dispose ();
-						thumb = null;
-					}
+		void WaitForCompletion (ImageLoaderItem items)
+		{
+			while (!ItemsCompleted.Contains(items)) {
+				Log.Debug ("Waiting for completion of {0} (done: {1})", ItemsRequested, ItemsCompleted);
+				Monitor.Enter (sync_handle);
+				Monitor.Wait (sync_handle);
+				Monitor.Exit (sync_handle);
+				Log.Debug ("Woke up after waiting for {0} (done: {1})", ItemsRequested, ItemsCompleted);
+			}
+		}
 
-					EventHandler<AreaPreparedEventArgs> eh = AreaPrepared;
-					if (eh != null)
-						eh (this, new AreaPreparedEventArgs (false));
-				}
+		void SignalAreaPrepared (ImageLoaderItem item) {
+			damage = Rectangle.Zero;
+			EventHandler<AreaPreparedEventArgs> eh = AreaPrepared;
+			if (eh != null)
+				GLib.Idle.Add (delegate {
+					eh (this, new AreaPreparedEventArgs (item));
+					return false;
+				});
+		}
 
-				//Send the AreaUpdated events
-				if (damage != Rectangle.Zero) {
-					EventHandler<AreaUpdatedEventArgs> eh = AreaUpdated;
-					if (eh != null)
-						eh (this, new AreaUpdatedEventArgs (damage));
-					damage = Rectangle.Zero;
-				}
+		void SignalAreaUpdated (ImageLoaderItem item, Rectangle area) {
+			EventHandler<AreaUpdatedEventArgs> eh = AreaUpdated;
+			if (eh == null)
+				return;
 
-				//Send the Completed event
-				if (notify_completed) {
-					notify_completed = false;
-					OnCompleted ();
+			lock (sync_handle) {
+				if (damage == Rectangle.Zero) {
+					damage = area;
+					GLib.Idle.Add (delegate {
+						Rectangle to_signal;
+						lock (sync_handle) {
+							to_signal = damage;
+							damage = Rectangle.Zero;
+						}
+						eh (this, new AreaUpdatedEventArgs (item, to_signal));
+						return false;
+					});
+				} else {
+					damage = damage.Union (area);
 				}
+			}
+		}
+
+		void SignalItemCompleted (ImageLoaderItem item)
+		{
+			ItemsCompleted |= item;
+			Log.Debug ("Notifying completion of {0} (done: {1}, requested: {2})", item, ItemsCompleted, ItemsRequested);
 
-				return false;
-			});
+			Monitor.Enter (sync_handle);
+			Monitor.PulseAll (sync_handle);
+			Monitor.Exit (sync_handle);
+
+			Log.Debug ("Signalled!");
+
+			EventHandler<ItemsCompletedEventArgs> eh = Completed;
+			if (eh != null)
+				GLib.Idle.Add (delegate {
+					eh (this, new ItemsCompletedEventArgs (item));
+					return false;
+				});
 		}
 #endregion
 	}
diff --git a/src/Loaders/IImageLoader.cs b/src/Loaders/IImageLoader.cs
index 84c7408..1894a5a 100644
--- a/src/Loaders/IImageLoader.cs
+++ b/src/Loaders/IImageLoader.cs
@@ -19,11 +19,15 @@ namespace FSpot.Loaders {
 
 		event EventHandler<AreaPreparedEventArgs> AreaPrepared;
 		event EventHandler<AreaUpdatedEventArgs> AreaUpdated;
-		event EventHandler Completed;
+		event EventHandler<ItemsCompletedEventArgs> Completed;
 
-		void Load (Uri uri);
+		ImageLoaderItem Load (ImageLoaderItem items, bool async);
 
-		Pixbuf Pixbuf { get; }
-		PixbufOrientation PixbufOrientation { get; }
+		Pixbuf Thumbnail { get; }
+		PixbufOrientation ThumbnailOrientation { get; }
+		Pixbuf Large { get; }
+		PixbufOrientation LargeOrientation { get; }
+		Pixbuf Full { get; }
+		PixbufOrientation FullOrientation { get; }
 	}
 }
diff --git a/src/Loaders/IImageLoaderExtensions.cs b/src/Loaders/IImageLoaderExtensions.cs
new file mode 100644
index 0000000..d7b39b8
--- /dev/null
+++ b/src/Loaders/IImageLoaderExtensions.cs
@@ -0,0 +1,54 @@
+//
+// Fspot/Loaders/IImageLoaderExtensions.cs
+//
+// Author(s)
+//	Ruben Vermeersch  <ruben savanne be>
+//
+// This is free software. See COPYING for details
+//
+
+using Gdk;
+using System;
+using FSpot.Utils;
+
+namespace FSpot.Loaders {
+	public static class IImageLoaderExtensions {
+		// Async loading
+		public static void Load (this IImageLoader loader, ImageLoaderItem items, EventHandler<ItemsCompletedEventArgs> handler)
+		{
+			loader.Completed += handler;
+			ImageLoaderItem loaded = loader.Load (items, true);
+			if (loaded != ImageLoaderItem.None)
+				handler (loader, new ItemsCompletedEventArgs (loaded));
+		}
+
+		// Sync loading
+		public static void Load (this IImageLoader loader, ImageLoaderItem items)
+		{
+			loader.Load (items, false);
+		}
+
+		// Accessors
+		public static Pixbuf Pixbuf (this IImageLoader loader, ImageLoaderItem item)
+		{
+			if (item == ImageLoaderItem.Thumbnail)
+				return loader.Thumbnail;
+			else if (item == ImageLoaderItem.Large)
+				return loader.Large;
+			else if (item == ImageLoaderItem.Full)
+				return loader.Full;
+			throw new Exception ("Unknown item requested: "+item);
+		}
+
+		public static PixbufOrientation PixbufOrientation (this IImageLoader loader, ImageLoaderItem item)
+		{
+			if (item == ImageLoaderItem.Thumbnail)
+				return loader.ThumbnailOrientation;
+			else if (item == ImageLoaderItem.Large)
+				return loader.LargeOrientation;
+			else if (item == ImageLoaderItem.Full)
+				return loader.FullOrientation;
+			throw new Exception ("Unknown item requested: "+item);
+		}
+	}
+}
diff --git a/src/Loaders/ImageLoader.cs b/src/Loaders/ImageLoader.cs
index 7f81115..0e1f135 100644
--- a/src/Loaders/ImageLoader.cs
+++ b/src/Loaders/ImageLoader.cs
@@ -84,7 +84,7 @@ namespace FSpot.Loaders {
 					throw new Exception ("Loader requested for unknown file type: "+extension);
 			}
 
-			loader = (IImageLoader) System.Activator.CreateInstance (t);
+			loader = (IImageLoader) System.Activator.CreateInstance (t, new object[] { uri });
 
 			return loader;
 		}
diff --git a/src/Loaders/ImageLoaderItem.cs b/src/Loaders/ImageLoaderItem.cs
new file mode 100644
index 0000000..268c0f2
--- /dev/null
+++ b/src/Loaders/ImageLoaderItem.cs
@@ -0,0 +1,21 @@
+//
+// Fspot/Loaders/ImageLoaderItem.cs
+//
+// Author(s)
+//	Ruben Vermeersch  <ruben savanne be>
+//
+// This is free software. See COPYING for details
+//
+
+using System;
+
+namespace FSpot.Loaders {
+	// Different bits of data which can be extracted
+	[Flags]
+	public enum ImageLoaderItem {
+		None        = 0,
+		Thumbnail   = 1 << 0,   // A small thumbnail
+		Large       = 1 << 1,   // A large image for displaying (should load reasonably fast)
+		Full        = 1 << 2    // The full image, for processing purposes (potentially very slow)
+	}
+}
diff --git a/src/Loaders/ImageLoaderItemExtensions.cs b/src/Loaders/ImageLoaderItemExtensions.cs
new file mode 100644
index 0000000..860684c
--- /dev/null
+++ b/src/Loaders/ImageLoaderItemExtensions.cs
@@ -0,0 +1,30 @@
+//
+// Fspot/Loaders/ImageLoaderItemExtensions.cs
+//
+// Author(s)
+//	Ruben Vermeersch  <ruben savanne be>
+//
+// This is free software. See COPYING for details
+//
+
+using System;
+
+namespace FSpot.Loaders {
+	public static class ImageLoaderItemExtensions {
+		public static bool Contains (this ImageLoaderItem item, ImageLoaderItem target)
+		{
+			return (item & target) == target;
+		}
+
+		public static ImageLoaderItem Largest (this ImageLoaderItem items)
+		{
+			if (items.Contains (ImageLoaderItem.Full))
+				return ImageLoaderItem.Full;
+			if (items.Contains (ImageLoaderItem.Large))
+				return ImageLoaderItem.Large;
+			if (items.Contains (ImageLoaderItem.Thumbnail))
+				return ImageLoaderItem.Thumbnail;
+			return ImageLoaderItem.None;
+		}
+	}
+}
diff --git a/src/Loaders/ItemCompletedEventArgs.cs b/src/Loaders/ItemCompletedEventArgs.cs
new file mode 100644
index 0000000..86be849
--- /dev/null
+++ b/src/Loaders/ItemCompletedEventArgs.cs
@@ -0,0 +1,22 @@
+//
+// Fspot/Loaders/ItemsCompletedEventArgs.cs
+//
+// Author(s)
+//	Ruben Vermeersch  <ruben savanne be>
+//
+// This is free software. See COPYING for details
+//
+
+using System;
+
+namespace FSpot.Loaders {
+	public class ItemsCompletedEventArgs : EventArgs
+	{
+		public ImageLoaderItem Items { get; private set; }
+
+		public ItemsCompletedEventArgs (ImageLoaderItem items) : base ()
+		{
+			this.Items = items;
+		}
+	}
+}
diff --git a/src/Loaders/LibrawImageLoader.cs b/src/Loaders/LibrawImageLoader.cs
index 06d6d25..5426864 100644
--- a/src/Loaders/LibrawImageLoader.cs
+++ b/src/Loaders/LibrawImageLoader.cs
@@ -10,122 +10,280 @@
 //
 
 using FSpot.Loaders.Native;
+using FSpot.Platform;
 using FSpot.Utils;
 using Gdk;
 using System;
 using System.Threading;
 
 namespace FSpot.Loaders {
-	public class LibrawImageLoader : IImageLoader {
-		NativeLibrawLoader loader;
+	public class LibrawImageLoader : IImageLoader
+	{
 		Uri uri;
+		object sync_handle = new object ();
 		bool is_disposed = false;
-		bool is_loading = false;
+		Rectangle damage;
+
+		public ImageLoaderItem ItemsRequested { get; private set; }
+		public ImageLoaderItem ItemsCompleted { get; private set; }
+
+		Pixbuf thumbnail;
+		public Pixbuf Thumbnail {
+			get { return PixbufUtils.ShallowCopy (thumbnail); }
+			private set { thumbnail = value; }
+		}
+		public PixbufOrientation ThumbnailOrientation { get; private set; }
+
+		Pixbuf large;
+		public Pixbuf Large {
+			get { return PixbufUtils.ShallowCopy (large); }
+		}
+		public PixbufOrientation LargeOrientation { get; private set; }
+
+		Pixbuf full;
+		public Pixbuf Full {
+			get { return PixbufUtils.ShallowCopy (full); }
+		}
+		public PixbufOrientation FullOrientation { get; private set; }
+
+		public event EventHandler<AreaPreparedEventArgs> AreaPrepared;
+		public event EventHandler<AreaUpdatedEventArgs> AreaUpdated;
+		public event EventHandler<ItemsCompletedEventArgs> Completed;
 
-		Pixbuf thumb, full;
+		public bool Loading { get; private set; }
 
-		public void Load (Uri uri)
+		NativeLibrawLoader loader;
+
+#region public api
+		public LibrawImageLoader (Uri uri) : base ()
 		{
-			if (this.uri != null)
-				throw new Exception ("You should only request one image per loader!");
 			this.uri = uri;
+			Loading = false;
 
-			if (is_disposed)
-				return;
+			ItemsRequested = ImageLoaderItem.None;
+			ItemsCompleted = ImageLoaderItem.None;
 
 			loader = new NativeLibrawLoader (uri.AbsolutePath);
-			LoadThumbnail ();
-			ThreadPool.QueueUserWorkItem (delegate { LoadFull (); });
+		}
+
+		public ImageLoaderItem Load (ImageLoaderItem items, bool async)
+		{
+			if (is_disposed)
+				return ImageLoaderItem.None;
+
+			ItemsRequested |= items;
+
+			StartLoading ();
+
+			if (!async)
+				WaitForCompletion (items);
+
+			return ItemsCompleted & items;
+		}
+
+		public void Dispose ()
+		{
+			is_disposed = true;
+			if (loader != null) {
+				loader.Aborted = true;
+				loader = null;
+			}
+			if (thumbnail != null)
+				thumbnail.Dispose ();
+			if (large != null)
+				large.Dispose ();
+			if (full != null)
+				full.Dispose ();
+		}
+#endregion
+
+#region private stuffs
+		void StartLoading ()
+		{
+			lock (sync_handle) {
+				if (Loading)
+					return;
+				Loading = true;
+			}
+
+			// Load thumbnail immediately, if required
+			if (!ItemsCompleted.Contains (ImageLoaderItem.Thumbnail) &&
+				 ItemsRequested.Contains (ImageLoaderItem.Thumbnail)) {
+				LoadThumbnail ();
+			}
+
+			ThreadPool.QueueUserWorkItem (delegate {
+					try {
+						DoLoad ();
+					} catch (Exception e) {
+						Log.Debug (e.ToString ());
+						Log.Debug ("Requested: {0}, Done: {1}", ItemsRequested, ItemsCompleted);
+						Gtk.Application.Invoke (delegate { throw e; });
+					}
+				});
+		}
+
+		void DoLoad ()
+		{
+			while (!is_disposed && !ItemsCompleted.Contains (ItemsRequested)) {
+				if (ItemsRequested.Contains (ImageLoaderItem.Thumbnail))
+					LoadThumbnail ();
+
+				if (ItemsRequested.Contains (ImageLoaderItem.Large))
+					LoadLarge ();
+
+				if (ItemsRequested.Contains (ImageLoaderItem.Full))
+					LoadFull ();
+			}
+
+			lock (sync_handle) {
+				Loading = false;
+			}
 		}
 
 		void LoadThumbnail ()
 		{
+			if (is_disposed)
+				return;
+
+			// Check if the thumbnail exists, if not: try to create it from the
+			// Large image. Will request Large if it is not present and wait
+			// for the next call to generate it (see the loop in DoLoad).
+			if (!ThumbnailFactory.ThumbnailExists (uri)) {
+				if (ItemsCompleted.Contains (ImageLoaderItem.Large)) {
+					ThumbnailFactory.SaveThumbnail (Large, uri);
+				} else {
+					ItemsRequested |= ImageLoaderItem.Large;
+					return;
+				}
+			}
+
+			Thumbnail = ThumbnailFactory.LoadThumbnail (uri);
+			ThumbnailOrientation = PixbufOrientation.TopLeft;
+			if (Thumbnail == null)
+				throw new Exception ("Null thumbnail returned");
+
+			SignalAreaPrepared (ImageLoaderItem.Thumbnail);
+			SignalAreaUpdated (ImageLoaderItem.Thumbnail, new Rectangle (0, 0, Thumbnail.Width, Thumbnail.Height));
+			SignalItemCompleted (ImageLoaderItem.Thumbnail);
+		}
+
+
+		void LoadLarge ()
+		{
+			if (is_disposed)
+				return;
+
 			int orientation;
-			thumb = loader.LoadThumbnail (out orientation);
+			large = loader.LoadEmbedded (out orientation);
 
 			switch (orientation) {
 				case 0:
-					PixbufOrientation = PixbufOrientation.TopLeft;
+					LargeOrientation = PixbufOrientation.TopLeft;
 					break;
 
 				case 3:
-					PixbufOrientation = PixbufOrientation.BottomRight;
+					LargeOrientation = PixbufOrientation.BottomRight;
 					break;
 
 				case 5:
-					PixbufOrientation = PixbufOrientation.LeftBottom;
+					LargeOrientation = PixbufOrientation.LeftBottom;
 					break;
 
 				case 6:
-					PixbufOrientation = PixbufOrientation.RightBottom;
+					LargeOrientation = PixbufOrientation.RightBottom;
 					break;
 
 				default:
 					throw new Exception ("Unexpected orientation returned!");
 			}
 
-			GLib.Idle.Add (delegate {
-				EventHandler<AreaPreparedEventArgs> prep = AreaPrepared;
-				if (prep != null)
-					prep (this, new AreaPreparedEventArgs (true));
-				EventHandler<AreaUpdatedEventArgs> upd = AreaUpdated;
-				if (upd != null)
-					upd (this, new AreaUpdatedEventArgs (new Rectangle (0, 0, thumb.Width, thumb.Height)));
-				return false;
-			});
+			SignalAreaPrepared (ImageLoaderItem.Large);
+			SignalAreaUpdated (ImageLoaderItem.Large, new Rectangle (0, 0, large.Width, large.Height));
+			SignalItemCompleted (ImageLoaderItem.Large);
 		}
 
 		void LoadFull ()
 		{
+			if (is_disposed)
+				return;
+
 			loader.ProgressUpdated += delegate (object o, ProgressUpdatedArgs args) {
 				Log.Debug ("Loading RAW: {0}/{1}", args.Done, args.Total);
 			};
 			full = loader.LoadFull ();
+			FullOrientation = PixbufOrientation.TopLeft;
 			if (full == null) {
 				return;
 			}
 
-			PixbufOrientation = PixbufOrientation.TopLeft;
-			GLib.Idle.Add (delegate {
-				EventHandler<AreaPreparedEventArgs> prep = AreaPrepared;
-				if (prep != null)
-					prep (this, new AreaPreparedEventArgs (false));
-				EventHandler<AreaUpdatedEventArgs> upd = AreaUpdated;
-				if (upd != null)
-					upd (this, new AreaUpdatedEventArgs (new Rectangle (0, 0, full.Width, full.Height)));
-				EventHandler eh = Completed;
-				if (eh != null)
-					eh (this, EventArgs.Empty);
-				return false;
-			});
-		}
-
-		public event EventHandler<AreaPreparedEventArgs> AreaPrepared;
-		public event EventHandler<AreaUpdatedEventArgs> AreaUpdated;
-		public event EventHandler Completed;
-
-		public bool Loading {
-			get { return is_loading; }
+			SignalAreaPrepared (ImageLoaderItem.Full);
+			SignalAreaUpdated (ImageLoaderItem.Full, new Rectangle (0, 0, full.Width, full.Height));
+			SignalItemCompleted (ImageLoaderItem.Full);
 		}
 
-		public void Dispose ()
+		void WaitForCompletion (ImageLoaderItem items)
 		{
-			if (loader != null) {
-				loader.Aborted = true;
-				loader = null;
+			while (!ItemsCompleted.Contains(items)) {
+				Log.Debug ("Waiting for completion of {0} (done: {1})", ItemsRequested, ItemsCompleted);
+				Monitor.Enter (sync_handle);
+				Monitor.Wait (sync_handle);
+				Monitor.Exit (sync_handle);
+				Log.Debug ("Woke up after waiting for {0} (done: {1})", ItemsRequested, ItemsCompleted);
 			}
-			if (thumb != null)
-				thumb.Dispose ();
-			if (full != null)
-				full.Dispose ();
 		}
 
-		public Pixbuf Pixbuf {
-			get {
-				return full == null ? PixbufUtils.ShallowCopy (thumb) : PixbufUtils.ShallowCopy (full);
+		void SignalAreaPrepared (ImageLoaderItem item) {
+			damage = Rectangle.Zero;
+			EventHandler<AreaPreparedEventArgs> eh = AreaPrepared;
+			if (eh != null)
+				GLib.Idle.Add (delegate {
+					eh (this, new AreaPreparedEventArgs (item));
+					return false;
+				});
+		}
+
+		void SignalAreaUpdated (ImageLoaderItem item, Rectangle area) {
+			EventHandler<AreaUpdatedEventArgs> eh = AreaUpdated;
+			if (eh == null)
+				return;
+
+			lock (sync_handle) {
+				if (damage == Rectangle.Zero) {
+					damage = area;
+					GLib.Idle.Add (delegate {
+						Rectangle to_signal;
+						lock (sync_handle) {
+							to_signal = damage;
+							damage = Rectangle.Zero;
+						}
+						eh (this, new AreaUpdatedEventArgs (item, to_signal));
+						return false;
+					});
+				} else {
+					damage = damage.Union (area);
+				}
 			}
 		}
 
-		public PixbufOrientation PixbufOrientation { get; private set; }
+		void SignalItemCompleted (ImageLoaderItem item)
+		{
+			ItemsCompleted |= item;
+			Log.Debug ("Notifying completion of {0} (done: {1}, requested: {2})", item, ItemsCompleted, ItemsRequested);
+
+			Monitor.Enter (sync_handle);
+			Monitor.PulseAll (sync_handle);
+			Monitor.Exit (sync_handle);
+
+			Log.Debug ("Signalled!");
+
+			EventHandler<ItemsCompletedEventArgs> eh = Completed;
+			if (eh != null)
+				GLib.Idle.Add (delegate {
+					eh (this, new ItemsCompletedEventArgs (item));
+					return false;
+				});
+		}
+#endregion
 	}
 }
diff --git a/src/Makefile.am b/src/Makefile.am
index f0f26fc..42f3c29 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -189,7 +189,11 @@ F_SPOT_CSDISTFILES =				\
 	$(srcdir)/Loaders/AreaPreparedEventArgs.cs		\
 	$(srcdir)/Loaders/AreaUpdatedEventArgs.cs		\
 	$(srcdir)/Loaders/ImageLoader.cs		\
+	$(srcdir)/Loaders/ImageLoaderItem.cs		\
+	$(srcdir)/Loaders/ImageLoaderItemExtensions.cs		\
 	$(srcdir)/Loaders/IImageLoader.cs		\
+	$(srcdir)/Loaders/IImageLoaderExtensions.cs		\
+	$(srcdir)/Loaders/ItemCompletedEventArgs.cs		\
 	$(srcdir)/Loaders/GdkImageLoader.cs		\
 	$(srcdir)/Loaders/LibrawImageLoader.cs		\
 	$(srcdir)/ImageLoaderThread.cs		\
diff --git a/src/PhotoImageView.cs b/src/PhotoImageView.cs
index d916ec1..450e252 100644
--- a/src/PhotoImageView.cs
+++ b/src/PhotoImageView.cs
@@ -166,17 +166,22 @@ namespace FSpot.Widgets {
 #region loader		
 		uint timer;
 		IImageLoader loader;
+		bool prepared_is_new;
+		ImageLoaderItem current_item;
+
 		void Load (Uri uri)
 		{
 			timer = Log.DebugTimerStart ();
 			if (loader != null)
 				loader.Dispose ();
 
+			current_item = ImageLoaderItem.None;
+
+			prepared_is_new = true;
 			loader = ImageLoader.Create (uri);
 			loader.AreaPrepared += HandlePixbufPrepared;
 			loader.AreaUpdated += HandlePixbufAreaUpdated;
-			loader.Completed += HandleDone;
-			loader.Load (uri);
+			loader.Load (ImageLoaderItem.Thumbnail | ImageLoaderItem.Large | ImageLoaderItem.Full, HandleCompleted);
 		}
 
 		void HandlePixbufPrepared (object sender, AreaPreparedEventArgs args)
@@ -188,9 +193,16 @@ namespace FSpot.Widgets {
 			if (!ShowProgress)
 				return;
 
-			Gdk.Pixbuf prev = this.Pixbuf;
-			this.Pixbuf = loader.Pixbuf;
-			PixbufOrientation = Accelerometer.GetViewOrientation (loader.PixbufOrientation);
+			if (args.Item < current_item)
+				return;
+
+			current_item = args.Item.Largest ();
+
+			Gdk.Pixbuf prev = Pixbuf;
+			PixbufOrientation orientation = Accelerometer.GetViewOrientation (loader.PixbufOrientation (current_item));
+			ChangeImage (loader.Pixbuf (current_item), orientation, prepared_is_new, current_item != ImageLoaderItem.Full);
+			prepared_is_new = false;
+
 			if (prev != null)
 				prev.Dispose ();
 
@@ -210,7 +222,7 @@ namespace FSpot.Widgets {
 			this.QueueDrawArea (area.X, area.Y, area.Width, area.Height);
 		}
 
-		void HandleDone (object sender, System.EventArgs args)
+		void HandleCompleted (object sender, ItemsCompletedEventArgs args)
 		{
 			Log.DebugTimerPrint (timer, "Loading image took {0}");
 			IImageLoader loader = sender as IImageLoader;
@@ -218,8 +230,10 @@ namespace FSpot.Widgets {
 				return;
 
 			Pixbuf prev = this.Pixbuf;
-			if (Pixbuf != loader.Pixbuf)
-				Pixbuf = loader.Pixbuf;
+			if (current_item != args.Items.Largest ()) {
+				current_item = args.Items.Largest ();
+				ChangeImage (loader.Pixbuf (current_item), Accelerometer.GetViewOrientation (loader.PixbufOrientation (current_item)), false, false);
+			}
 
 			if (Pixbuf == null) {
 				// FIXME: Do we have test cases for this ???



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