[f-spot] Start replacing import by a more clean version.



commit e7862f55c4419938fb5eaa949028d63c3427a1b6
Author: Ruben Vermeersch <ruben savanne be>
Date:   Sat Jun 5 01:48:35 2010 +0200

    Start replacing import by a more clean version.

 src/Core/Global.cs                |    9 +-
 src/FileImportBackend.cs          |   22 ++--
 src/Import/FileImportSource.cs    |  123 ++++++++++++++
 src/Import/ImportController.cs    |  307 +++++++++++++++++++++++++++++++++++
 src/Import/ImportSource.cs        |   22 +++
 src/ImportCommand.cs              |   12 +-
 src/MainWindow.cs                 |   19 ++-
 src/Makefile.am                   |    5 +
 src/PhotoStore.cs                 |   13 +-
 src/UI.Dialog/ImportDialog.cs     |  319 ++++++++++++++++++++++++++++++++++++
 src/UI.Dialog/PreferenceDialog.cs |    9 +-
 src/f-spot.glade                  |  305 ----------------------------------
 src/main.cs                       |   12 +-
 src/ui/import.ui                  |  325 +++++++++++++++++++++++++++++++++++++
 14 files changed, 1154 insertions(+), 348 deletions(-)
---
diff --git a/src/Core/Global.cs b/src/Core/Global.cs
index d7aef6a..a92e014 100644
--- a/src/Core/Global.cs
+++ b/src/Core/Global.cs
@@ -5,6 +5,7 @@
  *
  */
 using System;
+using Hyena;
 
 namespace FSpot {
 	public static class Global {
@@ -20,10 +21,10 @@ namespace FSpot {
 			set { base_dir = value; }
 		}
 
-		private static string photo_directory;
-		public static string PhotoDirectory {
-			get { return photo_directory; }
-			set { photo_directory = value; }
+		private static SafeUri photo_uri;
+		public static SafeUri PhotoUri {
+			get { return photo_uri; }
+			set { photo_uri = value; }
 		}
 
 		public static string HelpDirectory {
diff --git a/src/FileImportBackend.cs b/src/FileImportBackend.cs
index 550edd8..d0ab7f4 100644
--- a/src/FileImportBackend.cs
+++ b/src/FileImportBackend.cs
@@ -100,7 +100,7 @@ public class FileImportBackend : ImportBackend {
 	}
 	
 
-	public static SafeUri UniqueName (string path, string filename)
+/*	public static SafeUri UniqueName (string path, string filename)
 	{
 		int i = 1;
 		string dest = System.IO.Path.Combine (path, filename);
@@ -115,7 +115,7 @@ public class FileImportBackend : ImportBackend {
 		}
 		
 		return new SafeUri ("file://"+dest, true);
-	}
+	}*/
 	
 	public static SafeUri ChooseLocation (SafeUri uri)
 	{
@@ -124,14 +124,14 @@ public class FileImportBackend : ImportBackend {
 
 	private static SafeUri ChooseLocation (SafeUri uri, Stack created_directories)
 	{
-		string name = uri.GetFilename ();
+/*		string name = uri.GetFilename ();
 		DateTime time;
 		using (FSpot.ImageFile img = FSpot.ImageFile.Create (uri)) {
 			time = img.Date;
 		}
 
 		string dest_dir = String.Format ("{0}{1}{2}{1}{3:D2}{1}{4:D2}",
-						 FSpot.Global.PhotoDirectory,
+						 FSpot.Global.PhotoUri, // FIXME broken
 						 System.IO.Path.DirectorySeparatorChar,
 						 time.Year,
 						 time.Month,
@@ -159,15 +159,15 @@ public class FileImportBackend : ImportBackend {
 			}
 			
 			info = System.IO.Directory.CreateDirectory (dest_dir);
-		}
+		}*/
 
 		// If the destination we'd like to use is the file itself return that
-		if ("file://" + Path.Combine (dest_dir, name) == uri.ToString ())
-			return uri;
+		//if ("file://" + Path.Combine (dest_dir, name) == uri.ToString ())
+		//	return uri;
 		 
-		var dest = UniqueName (dest_dir, name);
+		//var dest = UniqueName (dest_dir, name);
 		
-		return dest;
+		return uri;
 	}
 
 	public override bool Step (out StepStatusInfo status_info)
@@ -195,7 +195,7 @@ public class FileImportBackend : ImportBackend {
 				info.DestinationUri = destination;
 
 				if (detect_duplicates)
-					photo = store.CheckForDuplicate (destination);
+					photo = null;//store.CheckForDuplicate (destination);
 
 				if (photo == null)
 					photo = store.Create (info.DestinationUri, roll.Id);
@@ -208,7 +208,7 @@ public class FileImportBackend : ImportBackend {
 				info.DestinationUri = destination;
 
 				if (detect_duplicates)
-					photo = store.CheckForDuplicate (destination);
+					photo = null;// store.CheckForDuplicate (destination);
 
 				if (photo == null)
 				{
diff --git a/src/Import/FileImportSource.cs b/src/Import/FileImportSource.cs
new file mode 100644
index 0000000..8239bb7
--- /dev/null
+++ b/src/Import/FileImportSource.cs
@@ -0,0 +1,123 @@
+using Hyena;
+using System;
+using System.Threading;
+using System.Collections.Generic;
+using FSpot.Utils;
+
+namespace FSpot.Import
+{
+    internal class FileImportSource : ImportSource {
+        public string Name { get; set; }
+        public string IconName { get; set; }
+        public SafeUri Root { get; set; }
+
+        public Thread PhotoScanner;
+
+        public FileImportSource (SafeUri root, string name, string icon_name)
+        {
+            Log.Debug ("Added source "+Name);
+            Root = root;
+            Name = name;
+
+            if (IsIPodPhoto) {
+                IconName = "multimedia-player";
+            } else if (IsCamera) {
+                IconName = "media-flash";
+            } else {
+                IconName = icon_name;
+            }
+        }
+
+        public void StartPhotoScan (ImportController controller)
+        {
+            if (PhotoScanner != null)
+                PhotoScanner.Abort ();
+
+            PhotoScanner = ThreadAssist.Spawn (() => ScanPhotos (controller));
+        }
+
+        void ScanPhotos (ImportController controller)
+        {
+            var infos = new List<FileImportInfo> ();
+			var enumerator = new RecursiveFileEnumerator (Root, controller.RecurseSubdirectories, true);
+			foreach (var file in enumerator) {
+				if (ImageFile.HasLoader (new SafeUri (file.Uri, true))) {
+					infos.Add (new FileImportInfo (new SafeUri(file.Uri, true)));
+                }
+
+                if (infos.Count % 10 == 0) {
+                    var to_add = infos; // prevents race condition
+                    ThreadAssist.ProxyToMain (() => controller.Photos.Add (to_add.ToArray ()));
+                    infos = new List<FileImportInfo> ();
+                }
+			}
+
+            if (infos.Count > 0) {
+                ThreadAssist.ProxyToMain (() => controller.Photos.Add (infos.ToArray ()));
+            }
+
+            controller.PhotoScanFinished ();
+        }
+
+        public void Deactivate ()
+        {
+            if (PhotoScanner != null) {
+                PhotoScanner.Abort (); // FIXME: abort is not nice
+                PhotoScanner = null;
+            }
+        }
+
+		private bool IsCamera {
+			get {
+				try {
+                    var file = GLib.FileFactory.NewForUri (Root.Append ("DCIM"));
+                    return file.Exists;
+				} catch {
+					return false;
+				}
+			}
+		}
+
+		private bool IsIPodPhoto {
+			get {
+				try {
+                    var file = GLib.FileFactory.NewForUri (Root.Append ("Photos"));
+                    var file2 = GLib.FileFactory.NewForUri (Root.Append ("iPod_Control"));
+                    return file.Exists && file2.Exists;
+				} catch {
+					return false;
+				}
+			}
+		}
+    }
+
+	internal class FileImportInfo : IBrowsableItem {
+		public FileImportInfo (SafeUri original)
+		{
+			DefaultVersion = new ImportInfoVersion () {
+				BaseUri = original.GetBaseUri (),
+				Filename = original.GetFilename ()
+			};
+
+			try {
+				using (FSpot.ImageFile img = FSpot.ImageFile.Create (original)) {
+					Time = img.Date;
+				}
+			} catch (Exception) {
+				Time = DateTime.Now;
+			}
+		}
+
+		public IBrowsableItemVersion DefaultVersion { get; private set; }
+		public SafeUri DestinationUri { get; set; }
+
+		public System.DateTime Time { get; private set; }
+
+		public Tag [] Tags { get { throw new NotImplementedException (); } }
+		public string Description { get { throw new NotImplementedException (); } }
+		public string Name { get { throw new NotImplementedException (); } }
+		public uint Rating { get { return 0; } }
+
+		internal uint PhotoId { get; set; }
+	}
+}
diff --git a/src/Import/ImportController.cs b/src/Import/ImportController.cs
new file mode 100644
index 0000000..6e04597
--- /dev/null
+++ b/src/Import/ImportController.cs
@@ -0,0 +1,307 @@
+using Hyena;
+using FSpot.Utils;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace FSpot.Import
+{
+    public enum ImportEvent {
+        SourceChanged,
+        PhotoScanStarted,
+        PhotoScanFinished,
+        ImportStarted,
+        ImportFinished,
+        ImportError
+    }
+
+    public class ImportController
+    {
+        public PhotoList Photos { get; private set; }
+
+        public ImportController ()
+        {
+            Photos = new PhotoList ();
+            LoadPreferences ();
+        }
+
+        ~ImportController ()
+        {
+            DeactivateSource (ActiveSource);
+        }
+
+#region Import Preferences
+
+        private bool copy_files;
+        private bool recurse_subdirectories;
+        private bool duplicate_detect;
+
+        public bool CopyFiles {
+            get { return copy_files; }
+            set { copy_files = value; SavePreferences (); }
+        }
+
+        public bool RecurseSubdirectories {
+            get { return recurse_subdirectories; }
+            set { recurse_subdirectories = value; SavePreferences (); RescanPhotos (); }
+        }
+
+        public bool DuplicateDetect {
+            get { return duplicate_detect; }
+            set { duplicate_detect = value; SavePreferences (); }
+        }
+
+        void LoadPreferences ()
+        {
+            copy_files = Preferences.Get<bool> (Preferences.IMPORT_COPY_FILES);
+            recurse_subdirectories = Preferences.Get<bool> (Preferences.IMPORT_INCLUDE_SUBFOLDERS);
+            duplicate_detect = Preferences.Get<bool> (Preferences.IMPORT_CHECK_DUPLICATES);
+        }
+
+        void SavePreferences ()
+        {
+            Preferences.Set(Preferences.IMPORT_COPY_FILES, copy_files);
+            Preferences.Set(Preferences.IMPORT_INCLUDE_SUBFOLDERS, recurse_subdirectories);
+            Preferences.Set(Preferences.IMPORT_CHECK_DUPLICATES, duplicate_detect);
+        }
+
+#endregion
+
+#region Source Scanning
+
+        private List<ImportSource> sources;
+        public List<ImportSource> Sources {
+            get {
+                if (sources == null)
+                    sources = ScanSources ();
+                return sources;
+            }
+        }
+
+        List<ImportSource> ScanSources ()
+        {
+		    var monitor = GLib.VolumeMonitor.Default;
+            var sources = new List<ImportSource> ();
+            foreach (var mount in monitor.Mounts) {
+                var root = new SafeUri (mount.Root.Uri, true);
+                var icon = (mount.Icon as GLib.ThemedIcon).Names [0];
+                sources.Add (new FileImportSource (root, mount.Name, icon));
+            }
+            return sources;
+        }
+
+#endregion
+
+#region Status Reporting
+
+        public delegate void ImportProgressHandler (int current, int total);
+        public event ImportProgressHandler ProgressUpdated;
+
+        public delegate void ImportEventHandler (ImportEvent evnt);
+        public event ImportEventHandler StatusEvent;
+
+        void FireEvent (ImportEvent evnt)
+        {
+            var h = StatusEvent;
+            if (h != null)
+                h (evnt);
+        }
+
+        void ReportProgress (int current, int total)
+        {
+            var h = ProgressUpdated;
+            if (h != null)
+                h (current, total);
+        }
+
+#endregion
+
+#region Source Switching
+
+        private ImportSource active_source;
+        public ImportSource ActiveSource {
+            set {
+                if (value == active_source)
+                    return;
+                var old_source = active_source;
+                active_source = value;
+                FireEvent (ImportEvent.SourceChanged);
+                RescanPhotos ();
+                DeactivateSource (old_source);
+            }
+            get {
+                return active_source;
+            }
+        }
+
+        void DeactivateSource (ImportSource source)
+        {
+            if (source == null)
+                return;
+            source.Deactivate ();
+        }
+
+        void RescanPhotos ()
+        {
+            Photos.Clear ();
+            ActiveSource.StartPhotoScan (this);
+            FireEvent (ImportEvent.PhotoScanStarted);
+        }
+
+#endregion
+
+#region Source Progress Signalling
+
+        // These are callbacks that should be called by the sources.
+
+        public void PhotoScanFinished ()
+        {
+            FireEvent (ImportEvent.PhotoScanFinished);
+        }
+
+#endregion
+
+#region Importing
+
+        Thread ImportThread;
+
+        public void StartImport ()
+        {
+            if (ImportThread != null)
+                throw new Exception ("Import already running!");
+
+            FireEvent (ImportEvent.ImportStarted);
+            ImportThread = ThreadAssist.Spawn (() => DoImport ());
+        }
+
+        public void CancelImport ()
+        {
+
+        }
+
+        Stack<SafeUri> created_directories;
+        List<uint> imported_photos;
+        PhotoStore store = App.Instance.Database.Photos;
+        RollStore rolls = FSpot.App.Instance.Database.Rolls;
+
+        void DoImport ()
+        {
+            created_directories = new Stack<SafeUri> ();
+            imported_photos = new List<uint> ();
+            Roll roll = rolls.Create ();
+
+            EnsureDirectory (Global.PhotoUri);
+
+            try {
+                int i = 0;
+                int total = Photos.Count;
+                foreach (var info in Photos.Items) {
+                    ThreadAssist.ProxyToMain (() => ReportProgress (i++, total));
+                    ImportPhoto (info, roll.Id);
+                }
+
+                FinishImport ();
+            } catch (Exception e) {
+                RollbackImport ();
+                throw e;
+            } finally {
+                if (imported_photos.Count == 0)
+                    rolls.Remove (roll);
+                imported_photos = null;
+                created_directories = null;
+                Photo.ResetMD5Cache ();
+                DeactivateSource (ActiveSource);
+                Photos.Clear ();
+                System.GC.Collect ();
+            }
+        }
+
+        void FinishImport ()
+        {
+            ImportThread = null;
+            FireEvent (ImportEvent.ImportFinished);
+        }
+
+        void RollbackImport ()
+        {
+        }
+
+        void ImportPhoto (IBrowsableItem item, uint roll_id)
+        {
+            var destination = FindImportDestination (item.DefaultVersion.Uri);
+
+            // Do duplicate detection
+            if (DuplicateDetect && store.CheckForDuplicate (item.DefaultVersion.Uri, destination)) {
+                return;
+            }
+
+            // Copy into photo folder.
+			if (item.DefaultVersion.Uri != destination) {
+				var file = GLib.FileFactory.NewForUri (item.DefaultVersion.Uri);
+				var new_file = GLib.FileFactory.NewForUri (destination);
+				file.Copy (new_file, GLib.FileCopyFlags.AllMetadata, null, null);
+            }
+
+            // Import photo
+            var photo = store.Create (destination,
+                                      item.DefaultVersion.Uri,
+                                      roll_id);
+
+            // FIXME: Add tags, import xmp crap
+            imported_photos.Add (photo.Id);
+        }
+
+        SafeUri FindImportDestination (SafeUri uri)
+        {
+            if (!CopyFiles)
+                return uri; // Keep it at the same place
+
+            // Find a new unique location inside the photo folder
+            string name = uri.GetFilename ();
+            DateTime time;
+            using (FSpot.ImageFile img = FSpot.ImageFile.Create (uri)) {
+                time = img.Date;
+            }
+
+            var dest_uri = Global.PhotoUri.Append (time.Year.ToString ())
+                                          .Append (String.Format ("{0:D2}", time.Month))
+                                          .Append (String.Format ("{0:D2}", time.Day));
+            EnsureDirectory (dest_uri);
+
+            // If the destination we'd like to use is the file itself return that
+            if (dest_uri.Append (name) == uri)
+                return uri;
+
+            // Find an unused name
+            int i = 1;
+            var dest = dest_uri.Append (name);
+            var file = GLib.FileFactory.NewForUri (dest);
+            while (file.Exists) {
+                var filename = uri.GetFilenameWithoutExtension ();
+                var extension = uri.GetExtension ();
+                dest = dest_uri.Append (String.Format ("{0}-{1}{2}", filename, i++, extension));
+                file = GLib.FileFactory.NewForUri (dest);
+            }
+
+            return dest;
+        }
+
+        void EnsureDirectory (SafeUri uri)
+        {
+            var parts = uri.AbsolutePath.Split('/');
+            SafeUri current = new SafeUri (uri.Scheme + ":///", true);
+            for (int i = 0; i < parts.Length; i++) {
+                current = current.Append (parts [i]);
+                var file = GLib.FileFactory.NewForUri (current);
+                if (!file.Exists) {
+                    created_directories.Push (current);
+                    Log.Debug ("Creating "+current);
+                    file.MakeDirectory (null);
+                }
+            }
+        }
+
+#endregion
+
+    }
+}
diff --git a/src/Import/ImportSource.cs b/src/Import/ImportSource.cs
new file mode 100644
index 0000000..5c6f370
--- /dev/null
+++ b/src/Import/ImportSource.cs
@@ -0,0 +1,22 @@
+using Hyena;
+using System;
+
+namespace FSpot.Import
+{
+    public interface ImportSource {
+        string Name { get; }
+        string IconName { get; }
+
+        void StartPhotoScan (ImportController controller);
+        void Deactivate ();
+    }
+
+	public class ImportInfoVersion : IBrowsableItemVersion {
+		public string Name { get { return String.Empty; } }
+		public bool IsProtected { get { return true; } }
+		public SafeUri BaseUri { get; set; }
+		public string Filename { get; set; }
+
+		public SafeUri Uri { get { return BaseUri.Append (Filename); } }
+	}
+}
diff --git a/src/ImportCommand.cs b/src/ImportCommand.cs
index cbd0119..97762bd 100644
--- a/src/ImportCommand.cs
+++ b/src/ImportCommand.cs
@@ -335,19 +335,19 @@ public class ImportCommand : GladeDialog
 
 	private void UpdateProgressBar (int count, int total)
 	{
-		if (progress_bar == null)
+/*		if (progress_bar == null)
 			return;
 
 		if (count > 0)
 			progress_bar.Show ();
 		progress_bar.Text = String.Format (loading_string, count, total);
-		progress_bar.Fraction = (double) count / System.Math.Max (total, 1);
+		progress_bar.Fraction = (double) count / System.Math.Max (total, 1);*/
 	}
 
 	private void HandleTraySelectionChanged (FSpot.IBrowsableCollection coll) 
 	{
-		if (tray.Selection.Count > 0)
-			photo_view.Item.Index = tray.Selection.Ids[0];
+//		if (tray.Selection.Count > 0)
+//			photo_view.Item.Index = tray.Selection.Ids[0];
 		
 	}
 
@@ -493,7 +493,7 @@ public class ImportCommand : GladeDialog
 	
 	private void LoadPreferences ()
 	{
-		if (FSpot.Preferences.Get<int> (FSpot.Preferences.IMPORT_WINDOW_WIDTH) > 0)
+	/*	if (FSpot.Preferences.Get<int> (FSpot.Preferences.IMPORT_WINDOW_WIDTH) > 0)
 			this.Dialog.Resize (FSpot.Preferences.Get<int> (FSpot.Preferences.IMPORT_WINDOW_WIDTH), FSpot.Preferences.Get<int> (FSpot.Preferences.IMPORT_WINDOW_HEIGHT));
 
 		if (FSpot.Preferences.Get<int> (FSpot.Preferences.IMPORT_WINDOW_PANE_POSITION) > 0)
@@ -501,7 +501,7 @@ public class ImportCommand : GladeDialog
 
 		copy_check.Active = Preferences.Get<bool> (Preferences.IMPORT_COPY_FILES);
 		recurse_check.Active = Preferences.Get<bool> (Preferences.IMPORT_INCLUDE_SUBFOLDERS);
-		duplicate_check.Active = Preferences.Get<bool> (Preferences.IMPORT_CHECK_DUPLICATES);
+		duplicate_check.Active = Preferences.Get<bool> (Preferences.IMPORT_CHECK_DUPLICATES);*/
 	}
 
 	public int ImportFromUri (PhotoStore store, SafeUri uri)
diff --git a/src/MainWindow.cs b/src/MainWindow.cs
index 3a95e94..d2f393b 100644
--- a/src/MainWindow.cs
+++ b/src/MainWindow.cs
@@ -27,6 +27,7 @@ using FSpot.Widgets;
 using FSpot.Utils;
 using FSpot.UI.Dialog;
 using FSpot.Platform;
+using FSpot.Import;
 
 namespace FSpot
 {
@@ -1149,11 +1150,12 @@ namespace FSpot
 	
 		public void ImportFile (SafeUri uri)
 		{
-			ImportCommand command = new ImportCommand (main_window);
-			if (command.ImportFromUri (Database.Photos, uri) > 0) {
-				query.RollSet = new RollSet (Database.Rolls.GetRolls (1)[0]);
-				UpdateQuery ();
-			}
+			// FIXME: disabled!
+			//ImportCommand command = new ImportCommand (main_window);
+			//if (command.ImportFromUri (Database.Photos, uri) > 0) {
+			//	query.RollSet = new RollSet (Database.Rolls.GetRolls (1)[0]);
+			//	UpdateQuery ();
+			//}
 		}
 	
 		void HandleIconViewDragDataReceived (object sender, DragDataReceivedArgs args)
@@ -1450,13 +1452,16 @@ namespace FSpot
 	
 		void HandleImportCommand (object sender, EventArgs e)
 		{
-			Database.Sync = false;
+			/*FIXMEDatabase.Sync = false;
 			ImportCommand command = new ImportCommand (main_window);
 			if (command.ImportFromUri (Database.Photos, null) > 0) {
 				query.RollSet = new RollSet (Database.Rolls.GetRolls (1)[0]);
 				UpdateQuery ();
 			}
-			Database.Sync = true;		
+			Database.Sync = true;		*/
+			var controller = new ImportController ();
+			var import_window = new ImportDialog (controller);
+			import_window.Show ();
 		}
 	
 		void HandlePageSetupActivated (object o, EventArgs e)
diff --git a/src/Makefile.am b/src/Makefile.am
index 9d46a94..8d48bb5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -82,6 +82,9 @@ SOURCES = \
 	ImageLoaderThread.cs \
 	ImportBackend.cs \
 	ImportCommand.cs \
+	Import/ImportController.cs \
+	Import/ImportSource.cs \
+	Import/FileImportSource.cs \
 	InfoOverlay.cs \
 	InternalProcess.cs \
 	IOChannel.cs \
@@ -157,6 +160,7 @@ SOURCES = \
 	UI.Dialog/ExceptionDialog.cs \
 	UI.Dialog/GladeDialog.cs \
 	UI.Dialog/HigMessageDialog.cs \
+	UI.Dialog/ImportDialog.cs \
 	UI.Dialog/LastRollDialog.cs \
 	UI.Dialog/PreferenceDialog.cs \
 	UI.Dialog/ProgressDialog.cs \
@@ -215,6 +219,7 @@ RESOURCES = \
 	dces.rdf \
 	f-spot.glade \
 	ui/main_window.ui \
+	ui/import.ui \
 	UI.Dialog/ui/AdjustTimeDialog.ui \
 	UI.Dialog/ui/DateRangeDialog.ui \
 	UI.Dialog/ui/EditTagDialog.ui \
diff --git a/src/PhotoStore.cs b/src/PhotoStore.cs
index 258c0cf..2880cea 100644
--- a/src/PhotoStore.cs
+++ b/src/PhotoStore.cs
@@ -99,13 +99,13 @@ public class PhotoStore : DbStore<Photo> {
 		Database.ExecuteNonQuery ("CREATE INDEX idx_photos_roll_id ON photos(roll_id)");
 	}
 
-	public Photo CheckForDuplicate (SafeUri uri) {
+	public bool CheckForDuplicate (SafeUri uri, SafeUri dest_uri) {
 		// Here we can go wild in comparing photos,
 		// for now we check on uri and md5
-		Photo found = GetByUri (uri);
+		Photo found = GetByUri (dest_uri);
 		
 		if (found != null)
-		 	return found;
+			return true;
 
 		string md5 = Photo.GenerateMD5 (uri);			
 		var file = GLib.FileFactory.NewForUri (uri);
@@ -126,10 +126,10 @@ public class PhotoStore : DbStore<Photo> {
 
 			// TODO? load pixbuf and compare sizes?	
 
-			return match;
+			return true;
 		}
 
-		return null;
+		return false;
 	}
 
 	public Photo Create (SafeUri uri, uint roll_id)
@@ -314,8 +314,6 @@ public class PhotoStore : DbStore<Photo> {
 	{
 		Photo photo = null;
 
-		uint timer = Log.DebugTimerStart ();
-
 		var base_uri = uri.GetBaseUri ();
 		var filename = uri.GetFilename ();
 
@@ -342,7 +340,6 @@ public class PhotoStore : DbStore<Photo> {
 		}
 		
 		reader.Close();
-		Log.DebugTimerPrint (timer, "GetByUri query took {0}");
 
 		if (photo == null)
 			return null;
diff --git a/src/UI.Dialog/ImportDialog.cs b/src/UI.Dialog/ImportDialog.cs
new file mode 100644
index 0000000..4fee53b
--- /dev/null
+++ b/src/UI.Dialog/ImportDialog.cs
@@ -0,0 +1,319 @@
+using FSpot.UI.Dialog;
+using FSpot.Widgets;
+using FSpot.Utils;
+using FSpot.Import;
+using Gtk;
+using Hyena;
+using System;
+using System.Collections.Generic;
+using Mono.Unix;
+
+namespace FSpot.UI.Dialog
+{
+    public class ImportDialog : BuilderDialog
+    {
+        static readonly string select_folder_label = Catalog.GetString ("Choose Folder...");
+        private ImportController Controller { get; set; }
+        private TreeStore Sources { get; set; }
+
+        private static Dictionary<string, ImportSource> history_sources = new Dictionary<string, ImportSource> ();
+
+        [GtkBeans.Builder.Object] Button cancel_button;
+        [GtkBeans.Builder.Object] Button import_button;
+        [GtkBeans.Builder.Object] CheckButton copy_check;
+        [GtkBeans.Builder.Object] CheckButton duplicate_check;
+        [GtkBeans.Builder.Object] CheckButton recurse_check;
+        [GtkBeans.Builder.Object] ComboBox sources_combo;
+        [GtkBeans.Builder.Object] HBox tagentry_box;
+        [GtkBeans.Builder.Object] HPaned import_hpaned;
+        [GtkBeans.Builder.Object] ProgressBar progress_bar;
+        [GtkBeans.Builder.Object] ScrolledWindow icon_scrolled;
+        [GtkBeans.Builder.Object] ScrolledWindow photo_scrolled;
+
+        private PhotoImageView photo_view;
+
+        public ImportDialog (ImportController controller) : base ("import.ui", "import_dialog")
+        {
+            Controller = controller;
+            BuildUI ();
+            ResetPreview ();
+            LoadPreferences ();
+            ScanSources ();
+            ConnectEvents ();
+        }
+
+        void BuildUI ()
+        {
+            photo_view = new PhotoImageView (Controller.Photos);
+            photo_scrolled.Add (photo_view);
+            photo_scrolled.SetSizeRequest (200, 200);
+            photo_view.Show ();
+
+            GtkUtil.ModifyColors (photo_scrolled);
+            GtkUtil.ModifyColors (photo_view);
+
+            var tray = new ScalingIconView (Controller.Photos);
+            tray.Selection.Changed += (c) => {
+                if (tray.Selection.Count > 0)
+                    photo_view.Item.Index = tray.Selection.Ids[0];
+            };
+            icon_scrolled.Add (tray);
+            tray.DisplayTags = false;
+            tray.Show ();
+
+            progress_bar.Hide ();
+
+            import_button.Sensitive = false;
+
+            var tag_entry = new FSpot.Widgets.TagEntry (App.Instance.Database.Tags, false);
+            tag_entry.UpdateFromTagNames (new string []{});
+            tagentry_box.Add (tag_entry);
+            tag_entry.Show ();
+        }
+
+        void ResetPreview ()
+        {
+            photo_view.Pixbuf = GtkUtil.TryLoadIcon (FSpot.Global.IconTheme, "f-spot", 128, (Gtk.IconLookupFlags)0);
+            photo_view.ZoomFit (false);
+        }
+
+        void LoadPreferences ()
+        {
+            if (Preferences.Get<int> (Preferences.IMPORT_WINDOW_WIDTH) > 0) {
+                Resize (Preferences.Get<int> (Preferences.IMPORT_WINDOW_WIDTH), Preferences.Get<int> (Preferences.IMPORT_WINDOW_HEIGHT));
+            }
+
+            if (FSpot.Preferences.Get<int> (Preferences.IMPORT_WINDOW_PANE_POSITION) > 0) {
+                import_hpaned.Position = Preferences.Get<int> (Preferences.IMPORT_WINDOW_PANE_POSITION);
+            }
+
+            copy_check.Active = Controller.CopyFiles;
+            recurse_check.Active = Controller.RecurseSubdirectories;
+            duplicate_check.Active = Controller.DuplicateDetect;
+        }
+
+        void ScanSources ()
+        {
+            // Populates the source combo box
+            Sources = new TreeStore (typeof(ImportSource), typeof(string), typeof(string), typeof(bool));
+            sources_combo.Model = Sources;
+            sources_combo.RowSeparatorFunc = (m, i) => m.GetValue (i, 1) == String.Empty;
+            var render = new CellRendererPixbuf ();
+            sources_combo.PackStart (render, false);
+            sources_combo.SetAttributes (render, "icon-name", 2, "sensitive", 3);
+            var render2 = new CellRendererText ();
+            sources_combo.PackStart (render2, true);
+            sources_combo.SetAttributes (render2, "text", 1, "sensitive", 3);
+
+            GLib.Idle.Add (() => {
+                PopulateSourceCombo (null);
+                return false;
+            });
+        }
+
+        void PopulateSourceCombo (ImportSource source_to_activate)
+        {
+            int activate_index = 0;
+            sources_combo.Changed -= OnSourceComboChanged;
+            Sources.Clear ();
+            Sources.AppendValues (null, Catalog.GetString ("Choose Import source..."), String.Empty, false);
+            Sources.AppendValues (null, select_folder_label, "folder", true);
+            Sources.AppendValues (null, String.Empty, String.Empty);
+            bool mount_added = false;
+            foreach (var source in Controller.Sources) {
+                if (source == source_to_activate) {
+                    activate_index = Sources.IterNChildren ();
+                }
+                Sources.AppendValues (source, source.Name, source.IconName, true);
+                mount_added = true;
+            }
+            if (!mount_added) {
+                Sources.AppendValues (null, Catalog.GetString ("(No Cameras Detected)"), String.Empty, false);
+            }
+
+            if (history_sources.Count > 0) {
+                Sources.AppendValues (null, String.Empty, String.Empty);
+                foreach (var source in history_sources.Values) {
+                    if (source == source_to_activate) {
+                        activate_index = Sources.IterNChildren ();
+                    }
+                    Sources.AppendValues (source, source.Name, source.IconName, true);
+                }
+            }
+            sources_combo.Changed += OnSourceComboChanged;
+            sources_combo.Active = activate_index;
+        }
+
+        void ConnectEvents ()
+        {
+            Controller.StatusEvent += OnControllerStatusEvent;
+            Controller.ProgressUpdated += OnControllerProgressUpdated;
+            copy_check.Toggled += (o, args) => { Controller.CopyFiles = copy_check.Active; };
+            recurse_check.Toggled += (o, args) => { Controller.RecurseSubdirectories = recurse_check.Active; };
+            duplicate_check.Toggled += (o, args) => { Controller.DuplicateDetect = duplicate_check.Active; };
+            import_button.Clicked += (o, args) => StartImport ();
+            cancel_button.Clicked += (o, args) => CancelImport ();
+            Response += (o, args) => {
+                if (args.ResponseId == ResponseType.DeleteEvent) {
+                    CancelImport ();
+                }
+            };
+        }
+
+        void ShowFolderSelector ()
+        {
+            var file_chooser = new FileChooserDialog (
+                Catalog.GetString ("Import"), this,
+                FileChooserAction.SelectFolder,
+                Stock.Cancel, ResponseType.Cancel,
+                Stock.Open, ResponseType.Ok);
+
+            file_chooser.SelectMultiple = false;
+            file_chooser.LocalOnly = false;
+
+            int response = file_chooser.Run ();
+            if ((ResponseType) response == ResponseType.Ok) {
+                var uri = new SafeUri (file_chooser.Uri, true);
+                SwitchToFolderSource (uri);
+            }
+
+            file_chooser.Destroy ();
+        }
+
+        void SwitchToFolderSource (SafeUri uri)
+        {
+            ImportSource source = null;
+            if (!history_sources.TryGetValue (uri, out source)) {
+                var name = uri.GetFilename ();
+                source = new FileImportSource (uri, name, "folder");
+                history_sources[uri] = source;
+            }
+
+            PopulateSourceCombo (source);
+            Controller.ActiveSource = source;
+        }
+
+        int current_index = -1;
+        void OnSourceComboChanged (object sender, EventArgs args)
+        {
+            // Prevent double firing.
+            if (sources_combo.Active == current_index) {
+                Log.Debug ("Skipping double fire!");
+                return;
+            } else {
+                current_index = sources_combo.Active;
+            }
+
+            TreeIter iter;
+            sources_combo.GetActiveIter (out iter);
+            var source = Sources.GetValue (iter, 0) as ImportSource;
+            if (source == null) {
+                var label = (string) Sources.GetValue (iter, 1);
+                if (label == select_folder_label) {
+                    ShowFolderSelector ();
+                    return;
+                } else {
+                    sources_combo.Active = 0;
+                    return;
+                }
+            }
+            Controller.ActiveSource = source;
+        }
+
+        void OnControllerStatusEvent (ImportEvent evnt)
+        {
+            Log.DebugFormat ("Received controller event: {0}", evnt);
+
+            switch (evnt) {
+                case ImportEvent.SourceChanged:
+                    HideScanSpinner ();
+                    ResetPreview ();
+                    import_button.Sensitive = true;
+                    break;
+
+                case ImportEvent.PhotoScanStarted:
+                    ShowScanSpinner ();
+                    break;
+
+                case ImportEvent.PhotoScanFinished:
+                    HideScanSpinner ();
+                    break;
+
+                case ImportEvent.ImportStarted:
+                    import_button.Sensitive = false;
+                    OptionsSensitive = false;
+                    ShowImportProgress ();
+                    break;
+
+                case ImportEvent.ImportFinished:
+                    Controller = null;
+                    Destroy ();
+                    break;
+
+                case ImportEvent.ImportError:
+                    // TODO
+                    break;
+            }
+        }
+
+        void OnControllerProgressUpdated (int current, int total)
+        {
+            var importing_label = Catalog.GetString ("Importing Photos: {0} of {1}...");
+            progress_bar.Text = String.Format (importing_label, current, total);
+            progress_bar.Fraction = (double) current / Math.Max (total, 1);
+        }
+
+        void StartImport ()
+        {
+            Controller.StartImport ();
+        }
+
+        void CancelImport ()
+        {
+            Controller.CancelImport ();
+            Controller = null;
+            Destroy ();
+        }
+
+        bool pulse_timeout_running = false;
+
+        void ShowImportProgress ()
+        {
+            progress_bar.Text = Catalog.GetString ("Importing photos...");
+            progress_bar.Show ();
+        }
+
+        void ShowScanSpinner ()
+        {
+            // TODO: Using a GtkSpinner would be nicer here.
+            progress_bar.Text = Catalog.GetString ("Searching for photos...");
+            progress_bar.Show ();
+            pulse_timeout_running = true;
+            GLib.Timeout.Add (40, () => {
+                if (!pulse_timeout_running) {
+                    return false;
+                }
+
+                progress_bar.Pulse ();
+                return pulse_timeout_running;
+            });
+        }
+
+        void HideScanSpinner ()
+        {
+            pulse_timeout_running = false;
+            progress_bar.Hide ();
+        }
+
+        public bool OptionsSensitive
+        {
+            set {
+                sources_combo.Sensitive = value;
+                copy_check.Sensitive = value;
+                recurse_check.Sensitive = value;
+                duplicate_check.Sensitive = value;
+                tagentry_box.Sensitive = value;
+            }
+        }
+    }
+}
diff --git a/src/UI.Dialog/PreferenceDialog.cs b/src/UI.Dialog/PreferenceDialog.cs
index c0191bd..bd4634f 100644
--- a/src/UI.Dialog/PreferenceDialog.cs
+++ b/src/UI.Dialog/PreferenceDialog.cs
@@ -17,6 +17,7 @@ using System.Collections.Generic;
 using System.Linq;
 using Gtk;
 using Mono.Unix;
+using Hyena;
 
 using FSpot.Widgets;
 
@@ -38,11 +39,11 @@ namespace FSpot.UI.Dialog {
 			TransientFor = parent;
 
 			//Photos Folder
-			if (Global.PhotoDirectory == Preferences.Get<string> (Preferences.STORAGE_PATH)) {
-				photosdir_chooser.SetCurrentFolder (Global.PhotoDirectory);
+			if (Global.PhotoUri == new SafeUri (Preferences.Get<string> (Preferences.STORAGE_PATH))) {
+				photosdir_chooser.SetCurrentFolderUri (Global.PhotoUri);
 				photosdir_chooser.CurrentFolderChanged += HandlePhotosdirChanged;
 			} else {
-				photosdir_chooser.SetCurrentFolder(Global.PhotoDirectory);
+				photosdir_chooser.SetCurrentFolderUri (Global.PhotoUri);
 				photosdir_chooser.Sensitive = false;
 			}
 
@@ -193,7 +194,7 @@ namespace FSpot.UI.Dialog {
 			photosdir_chooser.CurrentFolderChanged -= HandlePhotosdirChanged;
 			Preferences.Set (Preferences.STORAGE_PATH, photosdir_chooser.Filename);
 			photosdir_chooser.CurrentFolderChanged += HandlePhotosdirChanged;
-			Global.PhotoDirectory = photosdir_chooser.Filename;
+			Global.PhotoUri = new SafeUri (photosdir_chooser.Uri, true);
 		}
 
 		void HandleWritemetadataGroupChanged (object sender, System.EventArgs args)
diff --git a/src/f-spot.glade b/src/f-spot.glade
index 777d280..2513371 100644
--- a/src/f-spot.glade
+++ b/src/f-spot.glade
@@ -1816,311 +1816,6 @@
       </widget>
     </child>
   </widget>
-  <widget class="GtkDialog" id="import_dialog">
-    <property name="title" translatable="yes">Import</property>
-    <property name="modal">True</property>
-    <property name="default_width">664</property>
-    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
-    <property name="has_separator">False</property>
-    <child internal-child="vbox">
-      <widget class="GtkVBox" id="dialog-vbox14">
-        <property name="visible">True</property>
-        <child>
-          <widget class="GtkVBox" id="vbox62">
-            <property name="visible">True</property>
-            <property name="border_width">6</property>
-            <property name="spacing">6</property>
-            <child>
-              <widget class="GtkVBox" id="vbox65">
-                <property name="visible">True</property>
-                <property name="spacing">6</property>
-                <child>
-                  <widget class="GtkHBox" id="hbox59">
-                    <property name="visible">True</property>
-                    <property name="spacing">6</property>
-                    <child>
-                      <widget class="GtkLabel" id="import_label">
-                        <property name="visible">True</property>
-                        <property name="label" translatable="yes" comments="Translators: this string means 'source of import'">Import Source:</property>
-                      </widget>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">False</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <widget class="GtkOptionMenu" id="source_option_menu">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="response_id">0</property>
-                      </widget>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">False</property>
-                        <property name="position">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <placeholder/>
-                    </child>
-                  </widget>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                  </packing>
-                </child>
-                <child>
-                  <widget class="GtkHPaned" id="import_hpaned">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <child>
-                      <widget class="GtkScrolledWindow" id="icon_scrolled">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
-                        <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
-                        <property name="shadow_type">GTK_SHADOW_IN</property>
-                        <child>
-                          <placeholder/>
-                        </child>
-                      </widget>
-                      <packing>
-                        <property name="resize">False</property>
-                        <property name="shrink">True</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <widget class="GtkScrolledWindow" id="photo_scrolled">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
-                        <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
-                        <property name="shadow_type">GTK_SHADOW_IN</property>
-                        <child>
-                          <placeholder/>
-                        </child>
-                      </widget>
-                      <packing>
-                        <property name="resize">True</property>
-                        <property name="shrink">True</property>
-                      </packing>
-                    </child>
-                  </widget>
-                  <packing>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <widget class="GtkProgressBar" id="progress_bar">
-                    <property name="visible">True</property>
-                    <property name="pulse_step">0.10000000149</property>
-                  </widget>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">2</property>
-                  </packing>
-                </child>
-              </widget>
-              <packing>
-                <property name="position">1</property>
-              </packing>
-            </child>
-            <child>
-              <widget class="GtkTable" id="table19">
-                <property name="visible">True</property>
-                <property name="n_rows">2</property>
-                <property name="n_columns">2</property>
-                <property name="column_spacing">6</property>
-                <property name="row_spacing">6</property>
-                <child>
-                  <widget class="GtkHBox" id="hbox57">
-                    <property name="visible">True</property>
-                    <property name="spacing">6</property>
-                    <child>
-                      <widget class="GtkLabel" id="source_label">
-                        <property name="visible">True</property>
-                      </widget>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">False</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <placeholder/>
-                    </child>
-                  </widget>
-                  <packing>
-                    <property name="left_attach">1</property>
-                    <property name="right_attach">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <widget class="GtkLabel" id="label136">
-                    <property name="visible">True</property>
-                    <property name="xalign">0</property>
-                  </widget>
-                  <packing>
-                    <property name="x_options">GTK_FILL</property>
-                    <property name="y_options"></property>
-                  </packing>
-                </child>
-                <child>
-                  <widget class="GtkHBox" id="tagentry_box">
-                    <property name="visible">True</property>
-                  </widget>
-                  <packing>
-                    <property name="left_attach">1</property>
-                    <property name="right_attach">2</property>
-                    <property name="top_attach">1</property>
-                    <property name="bottom_attach">2</property>
-                    <property name="x_options">GTK_FILL</property>
-                    <property name="y_options">GTK_FILL</property>
-                  </packing>
-                </child>
-                <child>
-                  <widget class="GtkLabel" id="label228">
-                    <property name="visible">True</property>
-                    <property name="xalign">0</property>
-                    <property name="label" translatable="yes">Attach Tags:</property>
-                  </widget>
-                  <packing>
-                    <property name="top_attach">1</property>
-                    <property name="bottom_attach">2</property>
-                    <property name="x_options">GTK_FILL</property>
-                    <property name="y_options"></property>
-                  </packing>
-                </child>
-              </widget>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">2</property>
-              </packing>
-            </child>
-            <child>
-              <widget class="GtkCheckButton" id="duplicate_check">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="label" translatable="yes">Detect duplicates</property>
-                <property name="use_underline">True</property>
-                <property name="response_id">0</property>
-                <property name="active">True</property>
-                <property name="draw_indicator">True</property>
-              </widget>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">3</property>
-              </packing>
-            </child>
-            <child>
-              <widget class="GtkCheckButton" id="copy_check">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="label" translatable="yes">Copy files to the Photos folder</property>
-                <property name="use_underline">True</property>
-                <property name="response_id">0</property>
-                <property name="active">True</property>
-                <property name="draw_indicator">True</property>
-              </widget>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">4</property>
-              </packing>
-            </child>
-            <child>
-              <widget class="GtkCheckButton" id="recurse_check">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="label" translatable="yes">Include subfolders</property>
-                <property name="use_underline">True</property>
-                <property name="response_id">0</property>
-                <property name="active">True</property>
-                <property name="draw_indicator">True</property>
-              </widget>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="pack_type">GTK_PACK_END</property>
-                <property name="position">5</property>
-              </packing>
-            </child>
-          </widget>
-          <packing>
-            <property name="position">1</property>
-          </packing>
-        </child>
-        <child internal-child="action_area">
-          <widget class="GtkHButtonBox" id="dialog-action_area14">
-            <property name="visible">True</property>
-            <property name="layout_style">GTK_BUTTONBOX_END</property>
-            <child>
-              <widget class="GtkButton" id="okbutton7">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="label">gtk-cancel</property>
-                <property name="use_stock">True</property>
-                <property name="response_id">-6</property>
-              </widget>
-            </child>
-            <child>
-              <widget class="GtkButton" id="ok_button">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="response_id">-5</property>
-                <child>
-                  <widget class="GtkAlignment" id="alignment49">
-                    <property name="visible">True</property>
-                    <property name="xscale">0</property>
-                    <property name="yscale">0</property>
-                    <child>
-                      <widget class="GtkHBox" id="hbox64">
-                        <property name="visible">True</property>
-                        <property name="spacing">2</property>
-                        <child>
-                          <widget class="GtkImage" id="image15">
-                            <property name="visible">True</property>
-                            <property name="stock">gtk-ok</property>
-                          </widget>
-                          <packing>
-                            <property name="expand">False</property>
-                            <property name="fill">False</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <widget class="GtkLabel" id="label157">
-                            <property name="visible">True</property>
-                            <property name="label" translatable="yes">Import</property>
-                            <property name="use_underline">True</property>
-                          </widget>
-                          <packing>
-                            <property name="expand">False</property>
-                            <property name="fill">False</property>
-                            <property name="position">1</property>
-                          </packing>
-                        </child>
-                      </widget>
-                    </child>
-                  </widget>
-                </child>
-              </widget>
-              <packing>
-                <property name="position">1</property>
-              </packing>
-            </child>
-          </widget>
-          <packing>
-            <property name="expand">False</property>
-            <property name="pack_type">GTK_PACK_END</property>
-          </packing>
-        </child>
-      </widget>
-    </child>
-  </widget>
   <widget class="GtkWindow" id="single_view">
     <property name="visible">True</property>
     <property name="title" translatable="yes">F-Spot View</property>
diff --git a/src/main.cs b/src/main.cs
index d8cfba0..fba545f 100644
--- a/src/main.cs
+++ b/src/main.cs
@@ -62,6 +62,7 @@ namespace FSpot
 			List<string> uris = new List<string> ();
 			Unix.SetProcessName (Defines.PACKAGE);
             ThreadAssist.InitializeMainThread ();
+            ThreadAssist.ProxyToMainHandler = RunIdle;
             XdgThumbnailSpec.DefaultLoader = (uri) => {
                 using (var file = ImageFile.Create (uri))
                     return file.Load ();
@@ -76,7 +77,7 @@ namespace FSpot
 			GLib.GType.Init ();
 			Catalog.Init ("f-spot", Defines.LOCALE_DIR);
 			
-			FSpot.Global.PhotoDirectory = Preferences.Get<string> (Preferences.STORAGE_PATH);
+			FSpot.Global.PhotoUri = new SafeUri (Preferences.Get<string> (Preferences.STORAGE_PATH));
 			for (int i = 0; i < args.Length && !shutdown; i++) {
 				switch (args [i]) {
 				case "-h": case "-?": case "-help": case "--help": case "-usage":
@@ -102,8 +103,8 @@ namespace FSpot
 						Log.Error ("f-spot: -photodir option takes one argument");
 						return 1;
 					}
-					FSpot.Global.PhotoDirectory = System.IO.Path.GetFullPath (args [++i]);
-					Log.InformationFormat ("PhotoDirectory is now {0}", FSpot.Global.PhotoDirectory);
+					FSpot.Global.PhotoUri = new SafeUri (args [++i]);
+					Log.InformationFormat ("PhotoDirectory is now {0}", FSpot.Global.PhotoUri);
 					break;
 
 				case "-i": case "-import": case "--import":
@@ -253,5 +254,10 @@ namespace FSpot
 			}
 			return 0;
 		}
+
+        public static void RunIdle (InvokeHandler handler)
+        {
+            GLib.Idle.Add (delegate { handler (); return false; });
+        }
 	}
 }
diff --git a/src/ui/import.ui b/src/ui/import.ui
new file mode 100644
index 0000000..1c9cbf2
--- /dev/null
+++ b/src/ui/import.ui
@@ -0,0 +1,325 @@
+<?xml version="1.0"?>
+<interface>
+  <!-- interface-requires gtk+ 2.12 -->
+  <!-- interface-naming-policy toplevel-contextual -->
+  <object class="GtkDialog" id="import_dialog">
+    <property name="title" translatable="yes">Import</property>
+    <property name="modal">True</property>
+    <property name="default_width">664</property>
+    <property name="type_hint">dialog</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="dialog-vbox14">
+        <property name="visible">True</property>
+        <child>
+          <object class="GtkVBox" id="vbox62">
+            <property name="visible">True</property>
+            <property name="border_width">6</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkVBox" id="vbox65">
+                <property name="visible">True</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkHBox" id="hbox59">
+                    <property name="visible">True</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="GtkLabel" id="import_label">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes" comments="Translators: this string means 'source of import'">Import from:</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="sources_combo">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkHPaned" id="import_hpaned">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <child>
+                      <object class="GtkScrolledWindow" id="icon_scrolled">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="hscrollbar_policy">automatic</property>
+                        <property name="vscrollbar_policy">automatic</property>
+                        <property name="shadow_type">in</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="resize">False</property>
+                        <property name="shrink">True</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkScrolledWindow" id="photo_scrolled">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="hscrollbar_policy">automatic</property>
+                        <property name="vscrollbar_policy">automatic</property>
+                        <property name="shadow_type">in</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="resize">True</property>
+                        <property name="shrink">True</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkProgressBar" id="progress_bar">
+                    <property name="visible">True</property>
+                    <property name="pulse_step">0.02</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkTable" id="table19">
+                <property name="visible">True</property>
+                <property name="n_rows">2</property>
+                <property name="n_columns">2</property>
+                <property name="column_spacing">6</property>
+                <property name="row_spacing">6</property>
+                <child>
+                  <object class="GtkHBox" id="hbox57">
+                    <property name="visible">True</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="GtkLabel" id="source_label">
+                        <property name="visible">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label136">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                  </object>
+                  <packing>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options"></property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkHBox" id="tagentry_box">
+                    <property name="visible">True</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label228">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Attach Tags:</property>
+                  </object>
+                  <packing>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options"></property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkCheckButton" id="duplicate_check">
+                <property name="label" translatable="yes">Detect duplicates</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="use_underline">True</property>
+                <property name="active">True</property>
+                <property name="draw_indicator">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">3</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkCheckButton" id="copy_check">
+                <property name="label" translatable="yes">Copy files to the Photos folder</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="use_underline">True</property>
+                <property name="active">True</property>
+                <property name="draw_indicator">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">4</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkCheckButton" id="recurse_check">
+                <property name="label" translatable="yes">Include subfolders</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="use_underline">True</property>
+                <property name="active">True</property>
+                <property name="draw_indicator">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="pack_type">end</property>
+                <property name="position">5</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <object class="GtkHButtonBox" id="dialog-action_area14">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="cancel_button">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="receives_default">False</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="import_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="receives_default">False</property>
+                <child>
+                  <object class="GtkAlignment" id="alignment49">
+                    <property name="visible">True</property>
+                    <property name="xscale">0</property>
+                    <property name="yscale">0</property>
+                    <child>
+                      <object class="GtkHBox" id="hbox64">
+                        <property name="visible">True</property>
+                        <property name="spacing">2</property>
+                        <child>
+                          <object class="GtkImage" id="image15">
+                            <property name="visible">True</property>
+                            <property name="stock">gtk-ok</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="label157">
+                            <property name="visible">True</property>
+                            <property name="label" translatable="yes">Import</property>
+                            <property name="use_underline">True</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">cancel_button</action-widget>
+      <action-widget response="-5">import_button</action-widget>
+    </action-widgets>
+  </object>
+</interface>



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