=== added file 'src/JobScheduler.cs'
--- src/JobScheduler.cs	1970-01-01 00:00:00 +0000
+++ src/JobScheduler.cs	2006-07-22 14:49:41 +0000
@@ -0,0 +1,593 @@
+/*
+ * JobScheduler - Takes execution time out of the UI and into the background.
+ *
+ * There's a lot of waiting/locking in here:
+ *	- A Monitor on controller is used to sleep the main controller when it's 
+ *	  idle.
+ *	- A Monitor on threads is used to sleep idle job threads.
+ *	- next_job is locked when being set/get to ensure that it'll only get 
+ *	  picked up by one job. A seperate object (next_job_monitor) is used to
+ *	  avoid ArgumentNullExceptions.
+ *	- The queue is synchronized.
+ *
+ * TODO: should we use AutoResetEvent instead of monitors?
+ */
+
+#define JOBSCHED_DEBUG_ASSERTS
+#define JOBSCHED_DEBUG_MSGS
+#define JOBSCHED_DEBUG_STRICT_BYPASS_CHECK
+
+using GLib;
+using Mono.Data.SqliteClient;
+using System;
+using System.Collections;
+using System.Threading;
+#if JOBSCHED_DEBUG_ASSERTS
+using System.Diagnostics;
+#endif
+
+public class JobScheduler {
+	/*
+	 * Amount of maximum concurrent threads. At most 1 thread will be used for
+	 * background tasks, all other threads will be used for foreground actions.
+	 * Threads are started when needed and re-used. Inactive threads will be
+	 * terminated.
+	 *
+	 * FIXME: We should make sure atleast one thread is always reserved for
+	 * foreground tasks.
+	 */
+	private const uint MAX_THREADS = 3;
+
+	private const uint IDLE_THREAD_COLLECT_INTERVAL = 10000;
+	private const uint LOAD_PERSISTANT_JOBS_TIMEOUT = 20000;
+	private const uint BACKGROUND_WAKEUP_TIMEOUT = 10000;
+
+	private static Queue job_queue = Queue.Synchronized (new Queue ());
+	private static ArrayList threads = new ArrayList ();
+	private static System.Threading.Thread controller;
+	private static bool persistant_jobs_loaded = false;
+	private static System.Object next_job_monitor = new System.Object ();
+	private static Job next_job = null;
+	private static uint collect_idle_timeout = 0;
+	private static uint wakeup_timeout = 0;
+
+	/*
+	 * Signalling flag. Setting this to true will cause the main controller
+	 * thread and all worker threads to exit.
+	 */
+	private static bool controller_exit = false;
+
+	/*
+	 * Start the jobscheduler
+	 */
+	public static void Start () {
+		DebugMsg ("Starting");
+
+		controller = new System.Threading.Thread (ProcessJobs);
+		controller.IsBackground = true;
+		controller.Start ();
+
+		// Wait some time before continuing stored jobs, improves
+		// responsiveness during startup of f-spot.
+		GLib.Timeout.Add (LOAD_PERSISTANT_JOBS_TIMEOUT, LoadPersistantJobs);
+	}
+
+	/*
+	 * Stop the jobscheduler. 
+	 *
+	 * If there are still jobs running, this function will block until all jobs
+	 * have exited or aborted. All state is saved when this function returns.
+	 *
+	 * Don't do anything without starting it again, f-spot won't do anything
+	 * without it. Should only be called right before exiting the application.
+	 */
+	public static void Stop () {
+		controller_exit = true;
+		WakeUpController ();
+		controller.Join ();
+	}
+
+	/*
+	 * Submit a job to the scheduler.
+	 *
+	 * Loads stored jobs first to preserve order somewhat (note that, because
+	 * jobs are multiplexed over threads, there is no guarantee jobs will be
+	 * executed in the exact order they were submitted).
+	 */
+	public static void Schedule (Job job) {
+		if (job.Persistant && !persistant_jobs_loaded) {
+			LoadPersistantJobs ();
+		}
+
+		DebugMsg ("Scheduling new job");
+		job_queue.Enqueue (job);
+		if (job.Type != JobType.Background) {
+			WakeUpController ();
+		} else {
+			// Schedule wake up
+			if (wakeup_timeout == 0) {
+				DebugMsg ("Scheduling controller wakeup");
+				wakeup_timeout = 
+					GLib.Timeout.Add (BACKGROUND_WAKEUP_TIMEOUT, WakeUpController);
+			}
+		}
+	}
+
+	/*
+	 * Wake up the controller
+	 */
+	private static bool WakeUpController () {
+		DebugMsg ("Waking controller");
+		Monitor.Enter (controller);
+		Monitor.Pulse (controller);
+		Monitor.Exit (controller);
+		wakeup_timeout = 0;
+		return false;
+	}
+
+	/*
+	 * Kill idle threads.
+	 *
+	 * Will be executed periodically if there is more than one worker thread
+	 * running (a last worker thread is always kept open to ensure quick
+	 * response). A worker thread that has been woken up three times while
+	 * being idle will exit.
+	 */
+	private static bool CollectIdleThreads () {
+		DebugMsg ("Collecting threads");
+
+		Monitor.Enter (threads);
+		Monitor.PulseAll (threads);
+		Monitor.Exit (threads);
+
+		ArrayList dead_threads = new ArrayList ();
+		foreach (System.Threading.Thread t in threads) {
+			if ((t.ThreadState & System.Threading.ThreadState.Stopped) == 
+					System.Threading.ThreadState.Stopped) {
+				t.Join ();
+				dead_threads.Add (t);
+			}
+		}
+
+		// FIXME: do this with some enumerator magic in one step.
+		foreach (System.Threading.Thread t in dead_threads) {
+			threads.Remove (t);
+		}
+
+		if (threads.Count == 1) {
+			DebugMsg ("Only one thread left, halting idle threads collector");
+			collect_idle_timeout = 0;
+			return false;
+		}
+
+		return !controller_exit;
+	}
+
+	/*
+	 * Load the persistant job queue
+	 */
+	private static bool LoadPersistantJobs () {
+		if (persistant_jobs_loaded) {
+			return false;
+		}
+
+		DebugMsg ("Loading persistant jobs");
+		persistant_jobs_loaded = true;
+
+		ArrayList jobs = FSpot.Core.Database.PersistantJobs.GetJobs ();
+		foreach (PersistantJobItem pj in jobs) {
+			Schedule (pj.Job);
+		}
+
+		return false;
+	}
+
+	/*
+	 * Main controller loop
+	 */
+	private static void ProcessJobs () {
+		while (!controller_exit) {
+			if (job_queue.Count == 0) {
+				// Sleep until we get a job
+				Monitor.Enter (controller);
+				Monitor.Wait (controller);
+				Monitor.Exit (controller);
+			}
+
+			// check if we should exit (getting here means the controller has
+			// been woken up without a job, which probably means exit).
+			if (job_queue.Count == 0) {
+				continue;
+			}
+
+			lock (next_job_monitor) {
+				next_job = job_queue.Dequeue () as Job;
+				next_job.RemoveFromDatabase ();
+			}
+
+			Monitor.Enter (threads);
+			// Start a thread to handle the job j
+			bool thread_waiting = false;
+			foreach (System.Threading.Thread t in threads) {
+				if ((t.ThreadState & System.Threading.ThreadState.WaitSleepJoin) ==
+						System.Threading.ThreadState.WaitSleepJoin) {
+					thread_waiting = true;
+				}
+			}
+
+			if (!thread_waiting && threads.Count < MAX_THREADS) {
+				// Spawn new thread
+				System.Threading.Thread t = new System.Threading.Thread (ProcessJob);
+				t.IsBackground = true;
+				threads.Add (t);
+				t.Start ();
+
+				// Start collector if there's more than one thread.
+				if (threads.Count == 2 && collect_idle_timeout == 0) {
+					collect_idle_timeout = GLib.Timeout.Add (IDLE_THREAD_COLLECT_INTERVAL, CollectIdleThreads);
+				}
+			} else {
+				// Wake one of the idle threads
+				Monitor.Pulse (threads);
+			}
+			Monitor.Exit (threads);
+
+			// Wait till the thread picks up the job
+			while (next_job != null) {
+				System.Threading.Thread.Sleep (1);
+			}
+		}
+
+		// Bring down the job scheduler.
+
+		// Wake all threads and let them exit.
+		DebugMsg ("Waking all threads");
+		Monitor.Enter (threads);
+		Monitor.PulseAll (threads);
+		Monitor.Exit (threads);
+
+		DebugMsg ("Joining all threads");
+		foreach (System.Threading.Thread t in threads) {
+			DebugMsg ("Joining Thread {0}", threads.IndexOf (t)); 
+			t.Join ();
+			DebugMsg ("Joined Thread {0}", threads.IndexOf (t)); 
+		}
+
+		// Save all nonfinished persistant jobs to the database
+		DebugMsg ("Saving persistant jobs, count: {0}", job_queue.Count);
+		while (job_queue.Count > 0) {
+			DebugMsg ("Save loop iteration, count: {0}", job_queue.Count);
+			Job j = job_queue.Dequeue () as Job;
+			if (j.Persistant && !j.InDatabase) {
+				string classname = j.GetType ().ToString ();
+				string options = j.GetOptions ();
+				FSpot.Core.Database.PersistantJobs.Create (classname, options);
+			}
+		}
+		DebugMsg ("Job saving done");
+	}
+
+	/*
+	 * Job execution method (worker thread main loop).
+	 */
+	private static void ProcessJob () {
+		DebugMsg ("Child thread started");
+
+		int idle_count = 0;
+		while (!controller_exit && idle_count < 3) {
+			Job j = null;
+			lock (next_job_monitor) {
+				j = next_job;
+				next_job = null;
+			}
+
+			// No next job found, which means we got woken up by the thread collector. 
+			// Increase the idle count, if we get collected three times without getting
+			// a job, the thread will exit.
+			if (j == null) {
+				// FIXME: the last thread will also exit if there's more than
+				// one thread idle, prevent this from happening!
+				DebugMsg("Thread idle: {0}", String.Format("{0} - Count: {1}", 
+					threads.IndexOf (System.Threading.Thread.CurrentThread), idle_count+1));
+				idle_count++;
+			} else {
+				idle_count = 0;
+
+				while (j != null) {
+					DebugMsg("Execution on Thread {0}", 
+							threads.IndexOf (System.Threading.Thread.CurrentThread));
+					j.Execute ();
+
+					// Yield before going for next job (allows the scheduler to
+					// jump back to the UI, greatly improves responsiveness on
+					// uniprocessor machines).
+					System.Threading.Thread.Sleep (0);
+
+					lock (next_job_monitor) {
+						j = next_job;
+						next_job = null;
+					}
+				}
+			}
+
+			if (!controller_exit && idle_count < 3) {
+				Monitor.Enter (threads);
+				Monitor.Wait (threads);
+				Monitor.Exit (threads);
+			}
+		}
+		DebugMsg ("Child thread exiting");
+	}
+
+	/*
+	 * Remove all duplicate jobs
+	 */
+	public static void RemoveEqual (Job job) {
+		lock (job_queue.SyncRoot) {
+			Queue temp = new Queue();
+			while (job_queue.Count > 0) {
+				Job j = job_queue.Dequeue () as Job;
+				if (!j.Equals (job)) {
+					temp.Enqueue (j);
+				} else {
+					j.RemoveFromDatabase ();
+				}
+			}
+			
+			// Everything back on the queue
+			while (temp.Count > 0) {
+				job_queue.Enqueue (temp.Dequeue ());
+			}
+		}
+	}
+
+	/*
+	 * Signal a deleted photo.
+	 */
+	public static void PhotoDeleted (Photo p) {
+		lock (job_queue.SyncRoot) {
+			Queue temp = new Queue();
+			while (job_queue.Count > 0) {
+				Job j = job_queue.Dequeue () as Job;
+				if (!j.PhotoDeleted (p)) {
+					temp.Enqueue (j);
+				} else {
+					j.RemoveFromDatabase ();
+				}
+			}
+			
+			// Everything back on the queue
+			while (temp.Count > 0) {
+				job_queue.Enqueue (temp.Dequeue ());
+			}
+		}
+	}
+
+	/*
+	 * Debug routines
+	 */
+#if JOBSCHED_DEBUG_MSGS
+	public static void DebugMsg (string msg) {
+		Console.WriteLine (String.Format ("### JobScheduler: {0}", msg));
+	}
+
+	public static void DebugMsg (string msg, int arg) {
+		DebugMsg (String.Format (msg, arg));
+	}
+
+	public static void DebugMsg (string msg, uint arg) {
+		DebugMsg (String.Format (msg, arg));
+	}
+
+	public static void DebugMsg (string msg, string arg) {
+		DebugMsg (String.Format (msg, arg));
+	}
+#else
+	public static void DebugMsg (string msg)             { return; }
+	public static void DebugMsg (string msg, int arg)    { return; }
+	public static void DebugMsg (string msg, uint arg)   { return; }
+	public static void DebugMsg (string msg, string arg) { return; }
+#endif
+
+	/*
+	 * Disallow calling the calling method directly.
+	 *
+	 * This assertion will throw an exception if a user calls the method
+	 * without going through the jobscheduler. Performs a stack walk (and
+	 * should thus only be enabled on debug builds).
+	 *
+	 * Yes, this is a very dirty hack, it should really be fixed.
+	 */
+#if JOBSCHED_DEBUG_ASSERTS
+	public static void DisallowByPass () {
+		bool valid = true;
+
+		StackTrace st = new StackTrace ();
+		DebugMsg ("Checking stack for bypasses");
+		DebugMsg ("   Number of frames in stack: {0}", st.FrameCount);
+		for (int i = 0; i < st.FrameCount; i++) {
+			StackFrame sf = st.GetFrame (i);
+
+			if (i == st.FrameCount - 1) {
+				valid = valid && sf.GetMethod ().ToString ().Equals ("Void invoke_void()");
+			} else if (i == st.FrameCount - 2) {
+				valid = valid && sf.GetMethod ().ToString ().Equals ("Void ProcessJob()");
+			} else if (i == st.FrameCount - 3) {
+				valid = valid && sf.GetMethod ().ToString ().Equals ("Void Execute()");
+			}
+		}
+
+		if (valid) {
+			DebugMsg ("Valid stack");
+		} else {
+#if JOBSCHED_DEBUG_STRICT_BYPASS_CHECK
+			DebugMsg ("Invalid stack");
+			throw new Exception ("Stack bypassed!");
+#else	// JOBSCHED_DEBUG_STRICT_BYPASS_CHECK 
+			DebugMsg ("Invalid stack: {0}", st.ToString ());
+#endif	// JOBSCHED_DEBUG_STRICT_BYPASS_CHECK
+		}
+	}
+#else	// JOBSCHED_DEBUG_ASSERTS
+	public static void DisallowByPass () { return; }
+#endif	// JOBSCHED_DEBUG_ASSERTS
+}
+
+public enum JobType {
+	Immediate,
+	Background
+}
+
+public abstract class Job {
+	private JobType type;
+	public JobType Type {
+		get { return type; }
+		set { type = value; }
+	}
+
+	private bool persistant = false;
+	public bool Persistant {
+		get { return persistant; }
+		set { persistant = value; }
+	}
+
+	private bool in_database = false;
+	public bool InDatabase {
+		get { return in_database; }
+		// This is false by default and can only be set to true.
+		set { in_database = true; }
+	}
+
+	private uint database_id = 0;
+	public uint DatabaseId {
+		get { return database_id; }
+		set { if (!in_database) { database_id = value; } }
+	}
+
+	public Job (bool persistant, JobType type) {
+		this.type = type;
+		this.persistant = persistant;
+	}
+
+	public abstract void Execute ();
+
+	public void RemoveFromDatabase () {
+		if (InDatabase) {
+			JobScheduler.DebugMsg ("Removing job {0} from database", database_id);
+			FSpot.Core.Database.PersistantJobs.Remove (DatabaseId);
+		}
+	}
+
+	public virtual string GetOptions () {
+		return "";
+	}
+
+	public virtual void LoadOptions (string options) {
+		return;
+	}
+
+	/*
+	 * Whenever a photo is deleted from f-spot, this function is called on each
+	 * job. Make sure the subclass implements this method when needed.
+	 *
+	 * Should return a boolean value whether this job has become obsolete (in
+	 * other words, true if the job should be deleted).
+	 */
+	public virtual bool PhotoDeleted (Photo p) {
+		return false;
+	}
+}
+
+class PersistantJobItem : DbItem {
+	private string options;
+	public string Options {
+		get { return options; }
+		set { options = value; }
+	}
+
+	private string classname;
+	public string ClassName {
+		get { return classname; }
+		set { classname = value; }
+	}
+
+	public PersistantJobItem (uint id, string classname, string options) : base (id)
+	{
+		this.classname = classname;
+		this.options = options;
+	}
+
+	public Job Job {
+		get {
+			Job j = Activator.CreateInstance (Type.GetType (ClassName)) as Job;
+			j.DatabaseId = Id;
+			j.InDatabase = true;
+			j.LoadOptions (Options);
+			return j;
+		}
+	}
+}
+
+public class PersistantJobStore : DbStore {
+	public PersistantJobStore (Db database, bool is_new)
+		: base (database, true) {
+		bool exists = Database.TableExists ("persistant_jobs");
+
+		if (is_new || !exists)
+			CreateTable ();
+	}
+
+	private void CreateTable () {
+		Database.ExecuteNoWait (
+			@"CREATE TABLE persistant_jobs (
+				id          INTEGER PRIMARY KEY NOT NULL,
+				classname   STRING NOT NULL,
+				options     TEXT,
+				time        INTEGER
+			)");
+	}
+
+	public override DbItem Get (uint id) {
+		return null;	// FIXME: do we need this?
+	}
+
+	public override void Remove (DbItem item) {
+		Remove (item.Id);
+	}
+
+	public void Remove (uint id) {
+		JobScheduler.DebugMsg ("Removing job {0} from store", id);
+		string query = String.Format ("DELETE FROM persistant_jobs WHERE id = {0}", id);
+		Database.ExecuteNoWait (query);
+	}
+
+	public override void Commit (DbItem dbitem) { /* FIXME: implement, if needed */ }
+
+	public void Create (string classname, string options) {
+		Console.WriteLine ("Inserting job: {0} {1}", classname, options);
+		string query = String.Format ("INSERT INTO persistant_jobs "
+				+ "(classname, options, time) VALUES (\"{0}\", \"{1}\", \"{2}\")",
+				classname, options, DateTime.Now.Ticks);
+		Database.ExecuteNoWait (query);
+	}
+
+	/*
+	 * Load all jobs, used by LoadPersistantJobs.
+	 */
+	public ArrayList GetJobs () {
+		ArrayList jobs = new ArrayList ();
+		PersistantJobItem job = null;
+		string query = "SELECT id, classname, options "
+				+ "FROM persistant_jobs ORDER BY time ASC";
+		SqliteDataReader reader = Database.Query (query);
+
+		while (reader.Read ()) {
+			job = new PersistantJobItem (Convert.ToUInt32 (reader[0]), 
+							reader[1].ToString (), 
+							reader[2].ToString ());
+			jobs.Add (job);
+		}
+
+		return jobs;
+	}
+}

=== modified file '.bzrignore'
--- .bzrignore	2006-09-01 14:37:48 +0000
+++ .bzrignore	2006-09-03 10:37:26 +0000
@@ -1,1 +1,61 @@
-*/CVS
+ABOUT-NLS
+aclocal.m4
+autom4te.cache
+cat-id-tbl.c
+config.cache
+config.guess
+config.h
+config.h.in
+config.log
+config.rpath
+config.status
+config.sub
+configure
+Defines.cs
+.deps
+*.dll*
+f-marshal.c
+f-marshal.h
+src/f-spot
+f-spot.desktop
+f-spot.desktop.in
+src/f-spot.exe.config
+src/f-spot.exe.mdb
+f-spot.spec
+f-spot-view.desktop
+*.gmo
+install-sh
+intl
+intltool-extract
+intltool-extract.in
+intltool-merge
+intltool-merge.in
+intltool-update
+intltool-update.in
+libeog-marshal.c
+libeog-marshal.h
+.libs
+libtool
+ltconfig
+ltmain.sh
+m4
+Makefile
+Makefile.in
+Makefile.in.in
+messages
+missing
+mkinstalldirs
+*.mo
+notexist
+po2tbl.sed
+po2tbl.sed.in
+*.pot
+POTFILES
+stamp-cat-id
+stamp-h1
+xltmain.sh
+xml-i18n-*
+po/.intltool-merge-cache
+po/stamp-it
+tools/f-spot-screensaver.desktop
+CVS

=== modified file 'FlickrNet/Makefile.am'
--- FlickrNet/Makefile.am	2006-05-06 17:32:12 +0000
+++ FlickrNet/Makefile.am	2006-07-08 12:02:50 +0000
@@ -1,4 +1,4 @@
-CSC = mcs
+CSC = gmcs
 
 LIBFLICKRNET_CSFILES =				\
 	$(srcdir)/AssemblyInfo.cs \

=== modified file 'configure.in'
--- configure.in	2006-10-01 10:36:57 +0000
+++ configure.in	2006-10-01 10:38:05 +0000
@@ -16,7 +16,7 @@
 GNOME_COMPILE_WARNINGS
 AM_PATH_GLIB_2_0
 
-dnl --- Check for mono and mcs
+dnl --- Check for mono and gmcs
 
 AC_PATH_PROG(MONO, mono)
 AC_PATH_PROG(MCS, gmcs)

=== modified file 'libgphoto2-sharp/Makefile.am'
--- libgphoto2-sharp/Makefile.am	2006-05-06 17:32:12 +0000
+++ libgphoto2-sharp/Makefile.am	2006-07-08 12:02:49 +0000
@@ -1,4 +1,4 @@
-CSC = mcs --unsafe -g
+CSC = gmcs --unsafe -g
 
 LIBGPHOTO2_SHARP_CSFILES =				\
 	$(srcdir)/AssemblyInfo.cs			\

=== modified file 'semweb/Makefile.am'
--- semweb/Makefile.am	2006-10-01 10:36:57 +0000
+++ semweb/Makefile.am	2006-10-01 10:38:06 +0000
@@ -1,4 +1,4 @@
-CSC = mcs -g
+CSC = gmcs -g
 
 SEMWEB_CSFILES =            			\
                 $(srcdir)/AssemblyInfo.cs       \

=== modified file 'src/Core.cs'
--- src/Core.cs	2006-06-06 17:29:05 +0000
+++ src/Core.cs	2006-07-08 09:55:58 +0000
@@ -24,6 +24,7 @@
 		private static Db db;
 		static DBus.Connection connection;
 		System.Collections.ArrayList toplevels;
+
 		const string ServicePath = "org.gnome.FSpot";
 		const string CorePath = "/org/gnome/FSpot/Core";
 
@@ -140,6 +141,8 @@
 		public override void Organize ()
 		{
 			MainWindow.Window.Present ();
+
+			JobScheduler.Start ();
 		}
 		
 		public override void View (string list)
@@ -261,6 +264,8 @@
 
 		public override void Shutdown ()
 		{
+			JobScheduler.Stop ();
+			Database.Dispose ();
 			System.Environment.Exit (0);
 		}
 
@@ -277,7 +282,7 @@
 				// FIXME
 				// Should use Application.Quit(), but for that to work we need to terminate the threads
 				// first too.
-				System.Environment.Exit (0);
+				Shutdown ();
 			}
 			if (organizer != null && organizer.Window == sender)
 				organizer = null;

=== modified file 'src/Db.cs'
--- src/Db.cs	2006-09-01 14:32:33 +0000
+++ src/Db.cs	2006-09-01 14:33:42 +0000
@@ -1,5 +1,6 @@
 using Mono.Data.SqliteClient;
 using System.Collections;
+using System.Threading;
 using System.IO;
 using System;
 
@@ -126,20 +127,20 @@
 
 	// Sqlite stuff.
 
-	SqliteConnection connection;
-	protected SqliteConnection Connection {
+	Db database;
+	protected Db Database {
 		get {
-			return connection;
+			return database;
 		}
 	}
 
 
 	// Constructor.
 
-	public DbStore (SqliteConnection connection,
+	public DbStore (Db database,
 			bool cache_is_immortal)
 	{
-		this.connection = connection;
+		this.database = database;
 		this.cache_is_immortal = cache_is_immortal;
 
 		item_cache = new Hashtable ();
@@ -163,27 +164,15 @@
 	}
 
 	public void BeginTransaction () {
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-		command.CommandText = "BEGIN TRANSACTION";
-		command.ExecuteScalar ();
-		command.Dispose ();
+		Database.BeginTransaction ();
 	}
 	
 	public void CommitTransaction () {
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-		command.CommandText = "COMMIT TRANSACTION";
-		command.ExecuteScalar ();
-		command.Dispose ();
+		Database.CommitTransaction ();
 	}
 	
 	public void RollbackTransaction () {
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-		command.CommandText = "ROLLBACK";
-		command.ExecuteScalar ();
-		command.Dispose ();
+		Database.RollbackTransaction ();
 	}
 
 }
@@ -191,14 +180,24 @@
 
 // The Database puts the stores together.
 
-public class Db : IDisposable {
+public class Db : IDisposable
+{
+	private Queue command_queue = new Queue();
+	private Queue non_transaction_command_queue = new Queue();
+	private SqliteConnection connection;
+	private Thread queue_thread;
+	private bool dispose_requested = false;
+	private bool processing_queue = false;
+	private bool empty;
+	private string path;
+
+	private Thread current_transaction = null;
 
 	TagStore tag_store;
 	PhotoStore photo_store;
  	ImportStore import_store;
  	MetaStore meta_store;
-	bool empty;
-	string path;
+	PersistantJobStore persistant_jobs_store;
 
 	public TagStore Tags {
 		get { return tag_store; }
@@ -216,45 +215,91 @@
 		get { return meta_store; }
 	}
 
-	// This affects how often the database writes data back to disk, and
-	// therefore how likely corruption is in the event of power loss.
-	public bool Sync {
-		set {
-			SqliteCommand command = new SqliteCommand ();
-			command.Connection = sqlite_connection;
-			command.CommandText = "PRAGMA synchronous = " + (value ? "ON" : "OFF");
-			command.ExecuteScalar ();
-			command.Dispose ();
+	public PersistantJobStore PersistantJobs {
+		get { return persistant_jobs_store; }
+	}
+
+	public void Dispose()
+	{
+		dispose_requested = true;
+		Monitor.Enter (command_queue);
+		Monitor.Pulse (command_queue);
+		Monitor.Exit (command_queue);
+		while(processing_queue) Thread.Sleep (0);
+	}
+
+	private void QueueCommand(QueuedSqliteCommand command)
+	{
+		if (current_transaction != null && current_transaction != Thread.CurrentThread) {
+			lock(non_transaction_command_queue.SyncRoot) {
+				non_transaction_command_queue.Enqueue(command);
+			}
+		} else {
+			lock(command_queue.SyncRoot) {
+				command_queue.Enqueue(command);
+				Monitor.Enter (command_queue);
+				Monitor.Pulse (command_queue);
+				Monitor.Exit (command_queue);
+			}
 		}
 	}
 
-	public void BeginTransaction () {
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = sqlite_connection;
-		command.CommandText = "BEGIN TRANSACTION";
-		command.ExecuteScalar ();
-		command.Dispose ();
-	}
-	
-	public void CommitTransaction () {
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = sqlite_connection;
-		command.CommandText = "COMMIT TRANSACTION";
-		command.ExecuteScalar ();
-		command.Dispose ();
-	}
-	
-	public void RollbackTransaction () {
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = sqlite_connection;
-		command.CommandText = "ROLLBACK";
-		command.ExecuteScalar ();
-		command.Dispose ();
-	}
-
-	SqliteConnection sqlite_connection;
-	public SqliteConnection Connection {
-		get { return sqlite_connection; }
+	/*
+	 * Performs query and returns the selected data (use for SELECT)
+	 */
+	public SqliteDataReader Query(object query)
+	{
+		QueuedSqliteCommand command = new QueuedSqliteCommand(connection, 
+		query.ToString(), DbCommandType.Reader);
+		QueueCommand(command);
+		return command.WaitForResult() as SqliteDataReader;
+	}
+		
+	/*
+	 * Performs query and returns a single result
+	 */
+	public object QuerySingle(object query)
+	{
+		QueuedSqliteCommand command = new QueuedSqliteCommand(connection, 
+		query.ToString(), DbCommandType.Scalar);
+		QueueCommand(command);
+		return command.WaitForResult(); 
+	}
+
+	/*
+	 * Performs query and returns the insert ID (if any)
+	 */
+	public uint Execute(object query)
+	{
+		QueuedSqliteCommand command = new QueuedSqliteCommand(connection, 
+		query.ToString(), DbCommandType.Execute);
+		QueueCommand(command);
+		command.WaitForResult();
+		return command.InsertID;
+	}
+
+	/*
+	 * Performs a query and doesn't wait for a result. This does not mean it is
+	 * happened out of order. Query order for a thread is always maintained.
+	 *
+	 * This is meant for queries that are not expected to return a result. When
+	 * using this function over the regular Execute, the calling thread will not
+	 * wait until the database thread has finished the query.
+	 */
+	public void ExecuteNoWait(object query)
+	{
+		QueuedSqliteCommand command = new QueuedSqliteCommand(connection, 
+		query.ToString(), DbCommandType.ExecuteNoWait);
+		QueueCommand(command);
+	}
+
+	public bool TableExists(string table)
+	{
+		return Convert.ToInt32(QuerySingle(String.Format(@"
+		SELECT COUNT(*) 
+			FROM sqlite_master
+			WHERE Type='table' AND Name='{0}'", 
+			table))) > 0;
 	}
 
 	private static int GetFileVersion (string path)
@@ -305,7 +350,7 @@
 			throw new Exception (path + ": File not found");
 
 		if (! new_db) {
-			int version = Db.GetFileVersion (path);
+			int version = GetFileVersion (path);
 			// FIXME: we should probably display and error dialog if the version
 			// is anything other than the one we were built with, but for now at least
 			// use the right version.
@@ -319,23 +364,28 @@
 				version_string += ",encoding=UTF-8";
 		}
 		
-		sqlite_connection = new SqliteConnection ();
-		sqlite_connection.ConnectionString = "URI=file:" + path + version_string;
-
-		sqlite_connection.Open ();
+		connection = new SqliteConnection ();
+		connection.ConnectionString = "URI=file:" + path + version_string;
+
+		connection.Open ();
+
+		queue_thread = new Thread(ProcessQueue);
+		queue_thread.IsBackground = true;
+		queue_thread.Start();
 		
 
 		// Load or create the meta table
- 		meta_store = new MetaStore (sqlite_connection, new_db);
+ 		meta_store = new MetaStore (this, new_db);
 
 		// Update the database schema if necessary
 		FSpot.Database.Updater.Run (this);
 
 		BeginTransaction ();
 
-		tag_store = new TagStore (sqlite_connection, new_db);
-		import_store = new ImportStore (sqlite_connection, new_db);
- 		photo_store = new PhotoStore (sqlite_connection, new_db, tag_store);
+		tag_store = new TagStore (this, new_db);
+		import_store = new ImportStore (this, new_db);
+ 		photo_store = new PhotoStore (this, new_db, tag_store);
+		persistant_jobs_store = new PersistantJobStore (this, new_db);
 		
 		CommitTransaction ();
 
@@ -348,9 +398,169 @@
 		}
 	}
 
-	public void Dispose () {}
-}
-
+	private void ProcessQueue()
+	{		 
+		processing_queue = true;
+		bool in_dispose_transaction = false;
+
+		while(true) {
+			while(command_queue.Count > 0) {
+				if(dispose_requested && current_transaction == null && !in_dispose_transaction) {
+					(new SqliteCommand("BEGIN", connection)).ExecuteNonQuery();
+					in_dispose_transaction = true;
+				}
+
+				QueuedSqliteCommand command;
+				lock(command_queue.SyncRoot) {
+					command = command_queue.Dequeue() as QueuedSqliteCommand;
+				}
+				command.Execute();
+			}
+
+			if(dispose_requested && current_transaction == null) {
+				if(in_dispose_transaction) {
+					(new SqliteCommand("COMMIT", connection)).ExecuteNonQuery();
+				}
+				connection.Close();
+				processing_queue = false;
+				return;
+			}
+
+			// Sleep till we got new queries
+			Monitor.Enter (command_queue);
+			Monitor.Wait (command_queue);
+			Monitor.Exit (command_queue);
+		}
+	}
+	
+	// This affects how often the database writes data back to disk, and
+	// therefore how likely corruption is in the event of power loss.
+	public bool Sync {
+		set {
+			ExecuteNoWait("PRAGMA synchronous = " + (value ? "ON" : "OFF"));
+		}
+	}
+
+	public void BeginTransaction() {
+		if (current_transaction == Thread.CurrentThread)
+			return;
+
+		while (current_transaction != null);
+		current_transaction = Thread.CurrentThread;
+		ExecuteNoWait("BEGIN TRANSACTION");
+	}
+
+	public void CommitTransaction () {
+		if (current_transaction != Thread.CurrentThread) {
+			throw new ApplicationException("Can't commit when not in a transaction!");
+		}
+		ExecuteNoWait("COMMIT TRANSACTION");
+		current_transaction = null;
+		PushNonTransactionQueue();
+	}
+
+	public void RollbackTransaction () {
+		if (current_transaction != Thread.CurrentThread) {
+			throw new ApplicationException("Can't rollback when not in a transaction!");
+		}
+		ExecuteNoWait("ROLLBACK");
+		current_transaction = null;
+		PushNonTransactionQueue();
+	}
+
+	// Pull out everything that was sent in during the transaction
+	private void PushNonTransactionQueue() {
+		if (non_transaction_command_queue.Count > 0) {
+			// FIXME: better check we are really really sure this can never
+			//        cause deadlocks!
+			lock(command_queue.SyncRoot) {
+			lock (non_transaction_command_queue.SyncRoot) {
+				while (non_transaction_command_queue.Count > 0) {
+					command_queue.Enqueue(non_transaction_command_queue.Dequeue());
+				}
+			}
+			}
+			Monitor.Enter (command_queue);
+			Monitor.Pulse (command_queue);
+			Monitor.Exit (command_queue);
+		}
+	}
+}
+
+internal enum DbCommandType {
+	Reader,
+	Scalar,
+	Execute,
+	ExecuteNoWait
+}
+
+internal class QueuedSqliteCommand : SqliteCommand
+{
+	private DbCommandType command_type;
+	private object result;
+	private uint insert_id;
+	private Exception execution_exception;
+	private bool finished = false;
+
+	public QueuedSqliteCommand(SqliteConnection connection, string command, DbCommandType commandType)
+		: base(command, connection)
+	{
+		this.command_type = commandType;
+	}
+
+	public void Execute()
+	{
+		if(result != null) {
+			throw new ApplicationException("Command has alread been executed");
+		}
+
+		try {
+			switch(command_type) {
+				case DbCommandType.Reader:
+					result = ExecuteReader();
+					break;
+				case DbCommandType.Scalar:
+					result = ExecuteScalar();
+					break;
+				case DbCommandType.Execute:
+				case DbCommandType.ExecuteNoWait:
+				default:
+					result = ExecuteNonQuery();
+					insert_id = (uint) LastInsertRowID();
+					break;
+			}
+		} catch(Exception e) {
+			execution_exception = e;
+
+			// Throw exception anyway, else it will go by unnoticed.
+			if (command_type == DbCommandType.ExecuteNoWait) {
+				throw e;
+			}
+		}
+		
+		finished = true;
+	}
+
+	public object WaitForResult()
+	{
+		while(!finished);
+		
+		if(execution_exception != null) {
+			throw execution_exception;
+		}
+		
+		return result;
+	}
+
+	public object Result {
+		get { return result; }
+		internal set { result = value; }
+	}
+
+	public uint InsertID {
+		get { return insert_id; }
+	}
+}
 
 public class DbUtils {
 #if USE_CORRECT_FUNCTION

=== modified file 'src/ImportStore.cs'
--- src/ImportStore.cs	2006-05-06 17:32:12 +0000
+++ src/ImportStore.cs	2006-07-01 09:08:41 +0000
@@ -30,38 +30,25 @@
 
 	// Constructor
 
-	public ImportStore (SqliteConnection connection, bool is_new)
-		: base (connection, false)
+	public ImportStore (Db database, bool is_new)
+		: base (database, false)
 	{
-		if (! is_new)
+		bool exists = database.TableExists("imports");
+		if (!is_new || exists)
 			return;
 		
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText =
+		Database.QuerySingle(
 			"CREATE TABLE imports (                            " +
 			"	id          INTEGER PRIMARY KEY NOT NULL,  " +
 			"       time        INTEGER			   " +
-			")";
-
-		command.ExecuteNonQuery ();
-		command.Dispose ();
+			")");
 	}
 
 	public Import Create (DateTime time_in_utc)
 	{
 		long unix_time = DbUtils.UnixTimeFromDateTime (time_in_utc);
 
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText = String.Format ("INSERT INTO import (time) VALUES ({0})  ",
-						     unix_time);
-		command.ExecuteScalar ();
-		command.Dispose ();
-
-		uint id = (uint) Connection.LastInsertRowId;
+		uint id = Database.Execute(String.Format ("INSERT INTO import (time) VALUES ({0})  ", unix_time));
 		Import import = new Import (id, unix_time);
 		AddToCache (import);
 
@@ -74,19 +61,14 @@
 		if (import != null)
 			return import;
 
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText = String.Format ("SELECT time FROM imports WHERE id = {0}", id);
-		SqliteDataReader reader = command.ExecuteReader ();
+		string query = String.Format ("SELECT time FROM imports WHERE id = {0}", id);
+		SqliteDataReader reader = Database.Query (query);
 
 		if (reader.Read ()) {
 			import = new Import (id, Convert.ToUInt32 (reader [0]));
 			AddToCache (import);
 		}
 
-		command.Dispose ();
-
 		return import;
 	}
 
@@ -94,13 +76,8 @@
 	{
 		RemoveFromCache (item);
 
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText = String.Format ("DELETE FROM imports WHERE id = {0}", item.Id);
-		command.ExecuteNonQuery ();
-
-		command.Dispose ();
+		string query = String.Format ("DELETE FROM imports WHERE id = {0}", item.Id);
+		Database.ExecuteNoWait (query);
 	}
 
 	public override void Commit (DbItem item)
@@ -113,11 +90,7 @@
 	{
 		ArrayList list = new ArrayList ();
 
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText = "SELECT id, time FROM imports";
-		SqliteDataReader reader = command.ExecuteReader ();
+		SqliteDataReader reader = Database.Query ("SELECT id, time FROM imports");
 
 		while (reader.Read ()) {
 			// Note that we get both time and ID from the database, but we have to see
@@ -135,8 +108,6 @@
 			list.Add (import);
 		}
 
-		command.Dispose ();
-
 		list.Sort (new ImportComparerByDate ());
 		return list;
 	}

=== modified file 'src/MainWindow.cs'
--- src/MainWindow.cs	2006-10-06 20:39:43 +0000
+++ src/MainWindow.cs	2006-10-06 20:40:37 +0000
@@ -477,11 +477,7 @@
 				continue;
 
 			if (write_metadata) {
-				try {
-					p.WriteMetadataToImage ();
-				} catch (System.Exception e) {
-					Console.WriteLine ("Error syncing metadata to file\n{0}", e);
-				}
+				JobScheduler.Schedule (new WriteMetadataJob (p));
 			}
 		}
 		
@@ -2899,58 +2895,59 @@
                        return;
                }
 
-	       // Add any new tags to the selected photos
-	       Category default_category = null;
-	       Tag [] selection = tag_selection_widget.TagHighlight;
-	       if (selection.Length > 0) {
-		       if (selection [0] is Category)
-			       default_category = (Category) selection [0];
-		       else
-			       default_category = selection [0].Category;
-	       }
+		// Add any new tags to the selected photos
+		Category default_category = null;
+		Tag [] selection = tag_selection_widget.TagHighlight;
+		if (selection.Length > 0) {
+			if (selection [0] is Category)
+				default_category = (Category) selection [0];
+			else
+				default_category = selection [0].Category;
+		}
 
 		db.BeginTransaction ();
-	       for (int i = 0; i < tagnames.Length; i ++) {
-		       if (tagnames [i].Length == 0)
-			       continue;
-
-		       Tag t = db.Tags.GetTagByName (tagnames [i]);
-
-		       if (t == null) {
-			       t = db.Tags.CreateCategory (default_category, tagnames [i]) as Tag;
-			       db.Tags.Commit (t);
-		       }
-
-		       // Correct for capitalization differences
-		       tagnames [i] = t.Name;
-
-		       Tag [] tags = new Tag [1];
-		       tags [0] = t;
-
-		       foreach (int num in selected_photos)
-			       AddTagExtended (num, tags);
-	       }
+		for (int i = 0; i < tagnames.Length; i ++) {
+			if (tagnames [i].Length == 0)
+				continue;
+
+			Tag t = db.Tags.GetTagByName (tagnames [i]);
+
+			if (t == null) {
+				t = db.Tags.CreateCategory (default_category, tagnames [i]) as Tag;
+				db.Tags.Commit (t);
+			}
+
+			// Correct for capitalization differences
+			tagnames [i] = t.Name;
+
+			Tag [] tags = new Tag [1];
+			tags [0] = t;
+
+			foreach (int num in selected_photos)
+				AddTagExtended (num, tags);
+		}
 		db.CommitTransaction ();
-	       
-	       // Remove any removed tags from the selected photos
-	       foreach (string tagname in selected_photos_tagnames) {
-		       if (! IsTagInList (tagnames, tagname)) {
+		
+		// Remove any removed tags from the selected photos
+		foreach (string tagname in selected_photos_tagnames) {
+			if (! IsTagInList (tagnames, tagname)) {
 				
-			       Tag tag = db.Tags.GetTagByName (tagname);
-
-			       foreach (int num in selected_photos) {
-				       query.Photos [num].RemoveTag (tag);
-				       query.Commit (num);
-			       }
-		       }
-	       }
-
-	       if (view_mode == ModeType.IconView) {
-		       icon_view.GrabFocus ();
-	       } else {
-		       photo_view.QueueDraw ();
-		       photo_view.View.GrabFocus ();
-	       }
+				Tag tag = db.Tags.GetTagByName (tagname);
+
+				foreach (int num in selected_photos) {
+					query.Photos [num].RemoveTag (tag);
+					query.Commit (num);
+				}
+			}
+		}
+
+		if (view_mode == ModeType.IconView) {
+			icon_view.QueueDraw ();
+			icon_view.GrabFocus ();
+		} else {
+			photo_view.QueueDraw ();
+			photo_view.View.GrabFocus ();
+		}
 	}
 
 	private void HideTagbar ()

=== modified file 'src/Makefile.am'
--- src/Makefile.am	2006-10-06 20:39:43 +0000
+++ src/Makefile.am	2006-10-06 20:40:12 +0000
@@ -54,6 +54,7 @@
 	$(srcdir)/InternalProcess.cs		\
 	$(srcdir)/IOChannel.cs			\
 	$(srcdir)/IptcFile.cs			\
+	$(srcdir)/JobScheduler.cs		\
 	$(srcdir)/JpegFile.cs			\
 	$(srcdir)/JpegHeader.cs			\
 	$(srcdir)/JpegUtils.cs			\

=== modified file 'src/MetaStore.cs'
--- src/MetaStore.cs	2006-05-06 17:32:12 +0000
+++ src/MetaStore.cs	2006-07-01 09:08:40 +0000
@@ -60,18 +60,12 @@
 
 	private void CreateTable ()
 	{
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText =
+		Database.ExecuteNoWait(
 			@"CREATE TABLE meta (
 				id		INTEGER PRIMARY KEY NOT NULL,
 				name		TEXT UNIQUE NOT NULL,
 				data		TEXT
-			)";
-
-		command.ExecuteNonQuery ();
-		command.Dispose ();
+			)");
 	}
 
 	private void CreateDefaultItems (bool is_new)
@@ -80,29 +74,19 @@
 		Create (db_version, (is_new) ? FSpot.Database.Updater.LatestVersion.ToString () : "0");
 		
 		// Get the hidden tag id, if it exists
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-		command.CommandText = "SELECT id FROM tags WHERE name = 'Hidden'";
-		
 		try {
-			SqliteDataReader reader = command.ExecuteReader ();
+			SqliteDataReader reader = Database.Query("SELECT id FROM tags WHERE name = 'Hidden'");
 		
 			if (reader.Read ())
 				Create (hidden, reader [0].ToString ());
 
 			reader.Close ();
 		} catch (Exception e) {}
-	
-		command.Dispose ();
 	}
 	
 	private void LoadAllItems ()
 	{
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText = "SELECT id, name, data FROM meta";
-		SqliteDataReader reader = command.ExecuteReader ();
+		SqliteDataReader reader = Database.Query("SELECT id, name, data FROM meta");
 
 		while (reader.Read ()) {
 			uint id = Convert.ToUInt32 (reader [0]);
@@ -119,7 +103,6 @@
 		}
 
 		reader.Close ();
-		command.Dispose ();
 
 		if (FSpotVersion.Value != FSpot.Defines.VERSION) {
 			FSpotVersion.Value = FSpot.Defines.VERSION;
@@ -129,16 +112,11 @@
 
 	private MetaItem Create (string name, string data)
 	{
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText = String.Format ("INSERT INTO meta (name, data) VALUES ('{0}', {1})",
+		string query = String.Format ("INSERT INTO meta (name, data) VALUES ('{0}', {1})",
 				name, (data == null) ? "NULL" : "'" + data + "'");
 		
-		MetaItem item = new MetaItem ((uint) Connection.LastInsertRowId, name, data);
-
-		command.ExecuteScalar ();
-		command.Dispose ();
+		uint insert_id = Database.Execute(query);
+		MetaItem item = new MetaItem (insert_id, name, data);
 		
 		AddToCache (item);
 		EmitAdded (item);
@@ -150,13 +128,8 @@
 	{
 		MetaItem item = dbitem as MetaItem;
 
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText = String.Format ("UPDATE meta SET data = '{1}' WHERE name = '{0}'", item.Name, item.Value);
-
-		command.ExecuteNonQuery ();
-		command.Dispose ();
+		string query = String.Format ("UPDATE meta SET data = '{1}' WHERE name = '{0}'", item.Name, item.Value);
+		Database.ExecuteNoWait(query);
 		
 		EmitChanged (item);
 	}
@@ -170,33 +143,19 @@
 	{
 		RemoveFromCache (item);
 		
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText = String.Format ("DELETE FROM meta WHERE id = {0}", item.Id);
-		command.ExecuteNonQuery ();
-
-		command.Dispose ();
+		string query = String.Format ("DELETE FROM meta WHERE id = {0}", item.Id);
+		Database.ExecuteNoWait(query);
+
 		EmitRemoved (item);
 	}
 
 	// Constructor
 
-	public MetaStore (SqliteConnection connection, bool is_new)
-		: base (connection, true)
+	public MetaStore (Db database, bool is_new)
+		: base (database, true)
 	{
 		// Ensure the table exists
-		bool exists = true;
-		try {
-			SqliteCommand command = new SqliteCommand ();
-			command.Connection = connection;
-			command.CommandText = "UPDATE meta SET id = 1 WHERE 1 = 2";
-			command.ExecuteScalar ();
-			command.Dispose ();
-		} catch (Exception e) {
-			// Table doesn't exist, so create it
-			exists = false;
-		}
+		bool exists = Database.TableExists("meta");
 			
 		if (is_new || !exists) {
 			CreateTable ();

=== modified file 'src/PhotoStore.cs'
--- src/PhotoStore.cs	2006-10-06 20:39:43 +0000
+++ src/PhotoStore.cs	2006-10-06 20:40:12 +0000
@@ -498,6 +498,8 @@
 
 	public void WriteMetadataToImage ()
 	{
+		JobScheduler.DisallowByPass ();
+
 		string path = this.DefaultVersionUri.LocalPath;
 
 		FSpot.ImageFile img = FSpot.ImageFile.Create (DefaultVersionUri);
@@ -650,19 +652,16 @@
 
 	// Constructor
 
-	public PhotoStore (SqliteConnection connection, bool is_new, TagStore tag_store)
-		: base (connection, false)
+	public PhotoStore (Db database, bool is_new, TagStore tag_store)
+		: base (database, false)
 	{
 		this.tag_store = tag_store;
 		EnsureThumbnailDirectory ();
 
 		if (! is_new)
 			return;
-		
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
 
-		command.CommandText =
+		Database.ExecuteNoWait(
 			"CREATE TABLE photos (                                     " +
 			"	id                 INTEGER PRIMARY KEY NOT NULL,   " +
 			"       time               INTEGER NOT NULL,	   	   " +
@@ -670,39 +669,20 @@
 			"       name               STRING NOT NULL,		   " +
 			"       description        TEXT NOT NULL,	           " +
 			"       default_version_id INTEGER NOT NULL		   " +
-			")";
-
-		command.ExecuteNonQuery ();
-		command.Dispose ();
-
-		// FIXME: No need to do Dispose here?
-
-		command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText =
+			")");
+
+		Database.ExecuteNoWait(
 			"CREATE TABLE photo_tags (     " +
 			"	photo_id      INTEGER, " +
 			"       tag_id        INTEGER  " +
-			")";
-
-		command.ExecuteNonQuery ();
-		command.Dispose ();
-
-		// FIXME: No need to do Dispose here?
-
-		command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText =
+			")");
+
+		Database.ExecuteNoWait(
 			"CREATE TABLE photo_versions (    " +
 			"       photo_id        INTEGER,  " +
 			"       version_id      INTEGER,  " +
 			"       name            STRING    " +
-			")";
-
-		command.ExecuteNonQuery ();
-		command.Dispose ();
+			")");
 	}
 
 
@@ -716,22 +696,17 @@
 		FSpot.ImageFile img = FSpot.ImageFile.Create (origPath);
 		long unix_time = DbUtils.UnixTimeFromDateTime (img.Date);
 		string description = img.Description != null  ? img.Description.Split ('\0') [0] : "";
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText = String.Format ("INSERT INTO photos (time, " +
-						     "directory_path, name, description, default_version_id) " +
-						     "       VALUES ({0}, '{1}', '{2}', '{3}', {4})",
-						     unix_time,
-						     SqlString (System.IO.Path.GetDirectoryName (newPath)),
-						     SqlString (System.IO.Path.GetFileName (newPath)),
-						     SqlString (description),
-						     Photo.OriginalVersionId);
-
-		command.ExecuteScalar ();
-		command.Dispose ();
-
-		uint id = (uint) Connection.LastInsertRowId;
+
+		string query = String.Format ("INSERT INTO photos (time, " +
+					      "directory_path, name, description, default_version_id) " +
+					      "       VALUES ({0}, '{1}', '{2}', '{3}', {4})",
+					      unix_time,
+					      SqlString (System.IO.Path.GetDirectoryName (newPath)),
+					      SqlString (System.IO.Path.GetFileName (newPath)),
+					      SqlString (description),
+					      Photo.OriginalVersionId);
+		uint id = Database.Execute (query);
+
 		Photo photo = new Photo (id, unix_time, newPath);
 		AddToCache (photo);
 		photo.Loaded = true;
@@ -745,43 +720,30 @@
 	private void GetVersions (Photo photo)
 	{
 
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-		command.CommandText = String.Format ("SELECT version_id, name FROM photo_versions WHERE photo_id = {0}", photo.Id);
-		SqliteDataReader reader = command.ExecuteReader ();
+		string query = String.Format ("SELECT version_id, name FROM photo_versions WHERE photo_id = {0}", photo.Id);
+		SqliteDataReader reader = Database.Query (query);
 
 		while (reader.Read ()) {
 			uint version_id = Convert.ToUInt32 (reader [0]);
 			string name = reader[1].ToString ();
 			photo.AddVersionUnsafely (version_id, name);
 		}
-
-		command.Dispose ();
 	}
 
 	private void GetTags (Photo photo)
 	{
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-		command.CommandText = String.Format ("SELECT tag_id FROM photo_tags WHERE photo_id = {0}", photo.Id);
-		SqliteDataReader reader = command.ExecuteReader ();
+		string query = String.Format ("SELECT tag_id FROM photo_tags WHERE photo_id = {0}", photo.Id);
+		SqliteDataReader reader = Database.Query (query); 
 
 		while (reader.Read ()) {
 			uint tag_id = Convert.ToUInt32 (reader [0]);
 			Tag tag = tag_store.Get (tag_id) as Tag;
 			photo.AddTagUnsafely (tag);
 		}
-
-		command.Dispose ();
 	}		
 	
 	private void GetAllVersions  () {
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-		command.CommandText = String.Format ("SELECT photo_id, version_id, name " +
-						     "FROM photo_versions ");
-		
-		SqliteDataReader reader = command.ExecuteReader ();
+		SqliteDataReader reader = Database.Query("SELECT photo_id, version_id, name FROM photo_versions");
 		
 		while (reader.Read ()) {
 			uint id = Convert.ToUInt32 (reader [0]);
@@ -816,12 +778,7 @@
 	}
 
 	private void GetAllTags () {
-			SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-		command.CommandText = String.Format ("SELECT photo_id, tag_id " +
-						     "FROM photo_tags ");
-		
-		SqliteDataReader reader = command.ExecuteReader ();
+		SqliteDataReader reader = Database.Query("SELECT photo_id, tag_id FROM photo_tags");
 
 		while (reader.Read ()) {
 			uint id = Convert.ToUInt32 (reader [0]);
@@ -846,13 +803,9 @@
 	}
 
 	private void GetAllData () {
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-		command.CommandText = String.Format ("SELECT photo_tags.photo_id, tag_id, version_id, name " +
-						     "FROM photo_tags, photo_versions " +
-						     "WHERE photo_tags.photo_id = photo_versions.photo_id");
-		
-		SqliteDataReader reader = command.ExecuteReader ();
+		string query = "SELECT photo_tags.photo_id, tag_id, version_id, name " +
+			       "FROM photo_tags, photo_versions WHERE photo_tags.photo_id = photo_versions.photo_id";
+		SqliteDataReader reader = Database.Query (query);
 
 		while (reader.Read ()) {
 			uint id = Convert.ToUInt32 (reader [0]);
@@ -884,14 +837,11 @@
 
 	private void GetData (Photo photo)
 	{
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-		command.CommandText = String.Format ("SELECT tag_id, version_id, name " +
-						     "  FROM photo_tags, photo_versions " +
-						     " WHERE photo_tags.photo_id = photo_versions.photo_id " +
-						     "   AND photo_tags.photo_id = {0}", photo.Id);
-
-		SqliteDataReader reader = command.ExecuteReader ();
+		string query = String.Format ("SELECT tag_id, version_id, name " +
+					      "  FROM photo_tags, photo_versions " +
+					      " WHERE photo_tags.photo_id = photo_versions.photo_id " +
+					      "   AND photo_tags.photo_id = {0}", photo.Id);
+		SqliteDataReader reader = Database.Query (query);
 
 		while (reader.Read ()) {
 		        if (reader [0] != null) {
@@ -914,17 +864,15 @@
 		if (photo != null)
 			return photo;
 
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-		command.CommandText = String.Format ("SELECT time,                                 " +
-						     "       directory_path,                       " +
-						     "       name,                                 " +
-						     "       description,                          " +
-						     "       default_version_id                    " +
-						     "     FROM photos                             " +
-						     "     WHERE id = {0}                          ",
-						     id);
-		SqliteDataReader reader = command.ExecuteReader ();
+		string query = String.Format ("SELECT time,                                 " +
+					      "       directory_path,                       " +
+					      "       name,                                 " +
+					      "       description,                          " +
+					      "       default_version_id                    " +
+					      "     FROM photos                             " +
+					      "     WHERE id = {0}                          ",
+					      id);
+		SqliteDataReader reader = Database.Query (query);
 
 		if (reader.Read ()) {
 			photo = new Photo (id,
@@ -937,8 +885,6 @@
 			AddToCache (photo);
 		}
 
-		command.Dispose ();
-
 		if (photo == null)
 			return null;
 		
@@ -954,13 +900,12 @@
 		//        this is only used for DND
 
 		Photo photo = null;
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
 		string directory_path = System.IO.Path.GetDirectoryName (path);
 		string filename = System.IO.Path.GetFileName (path);
 
-		command.CommandText = String.Format ("SELECT id,                                   " +
+		directory_path = directory_path.Trim();
+		filename = filename.Trim();
+		string query = String.Format ("SELECT id,                                   " +
 				                     "       time,                                 " +
 						     "       description,                          " +
 						     "       default_version_id                    " +
@@ -969,8 +914,7 @@
 						     "   AND name = \"{1}\"                        ",
 						     directory_path,
 						     filename);
-
-		SqliteDataReader reader = command.ExecuteReader ();
+		SqliteDataReader reader = Database.Query (query);
 
 		if (reader.Read ()) {
 			photo = new Photo (Convert.ToUInt32 (reader [0]),
@@ -983,8 +927,6 @@
 			AddToCache (photo);
 		}
 
-		command.Dispose ();
-
 		if (photo == null)
 			return null;
 		
@@ -1023,31 +965,17 @@
 			query_builder.Append (String.Format ("id = {0}", items[i].Id));
 			tv_query_builder.Append (String.Format ("photo_id = {0}", items[i].Id));
 			RemoveFromCache (items[i]);
+			JobScheduler.PhotoDeleted (items[i]);
 		}
 
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText = String.Format ("DELETE FROM photos WHERE {0}", query_builder.ToString ());
-		command.ExecuteNonQuery ();
-
-		command.Dispose ();
-
-		command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText = String.Format ("DELETE FROM photo_tags WHERE {0}", tv_query_builder.ToString ());
-		command.ExecuteNonQuery ();
-
-		command.Dispose ();
-
-		command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText = String.Format ("DELETE FROM photo_versions WHERE {0}", tv_query_builder.ToString ());
-		command.ExecuteNonQuery ();
-
-		command.Dispose ();
+		string query = String.Format ("DELETE FROM photos WHERE {0}", query_builder.ToString ());
+		Database.ExecuteNoWait(query);
+
+		query = String.Format ("DELETE FROM photo_tags WHERE {0}", tv_query_builder.ToString ());
+		Database.ExecuteNoWait(query);
+
+		query = String.Format ("DELETE FROM photo_versions WHERE {0}", tv_query_builder.ToString ());
+		Database.ExecuteNoWait(query);
 	}
 
 	public override void Remove (DbItem item)
@@ -1077,45 +1005,30 @@
 	
         private void Update (Photo photo) {
 		// Update photo.
-
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-		command.CommandText = String.Format ("UPDATE photos SET description = '{0}',     " +
-						     "                  default_version_id = {1}, " +
-						     "                  time = {2} " +
-						     "              WHERE id = {3}",
-						     SqlString (photo.Description),
-						     photo.DefaultVersionId,
-						     DbUtils.UnixTimeFromDateTime (photo.Time),
-						     photo.Id);
-		command.ExecuteNonQuery ();
-		command.Dispose ();
+		string query = String.Format ("UPDATE photos SET description = '{0}',     " +
+					      "                  default_version_id = {1}, " +
+					      "                  time = {2} " +
+					      "              WHERE id = {3}",
+					      SqlString (photo.Description),
+					      photo.DefaultVersionId,
+					      DbUtils.UnixTimeFromDateTime (photo.Time),
+					      photo.Id);
+		Database.ExecuteNoWait(query);
 
 		// Update tags.
-
-		command = new SqliteCommand ();
-		command.Connection = Connection;
-		command.CommandText = String.Format ("DELETE FROM photo_tags WHERE photo_id = {0}", photo.Id);
-		command.ExecuteNonQuery ();
-		command.Dispose ();
+		query = String.Format ("DELETE FROM photo_tags WHERE photo_id = {0}", photo.Id);
+		Database.ExecuteNoWait(query);
 
 		foreach (Tag tag in photo.Tags) {
-			command = new SqliteCommand ();
-			command.Connection = Connection;
-			command.CommandText = String.Format ("INSERT INTO photo_tags (photo_id, tag_id) " +
-							     "       VALUES ({0}, {1})",
-							     photo.Id, tag.Id);
-			command.ExecuteNonQuery ();
-			command.Dispose ();
+			query = String.Format ("INSERT INTO photo_tags (photo_id, tag_id) " +
+					       "       VALUES ({0}, {1})",
+					       photo.Id, tag.Id);
+			Database.ExecuteNoWait(query);
 		}
 
 		// Update versions.
-
-		command = new SqliteCommand ();
-		command.Connection = Connection;
-		command.CommandText = String.Format ("DELETE FROM photo_versions WHERE photo_id = {0}", photo.Id);
-		command.ExecuteNonQuery ();
-		command.Dispose ();
+		query = String.Format ("DELETE FROM photo_versions WHERE photo_id = {0}", photo.Id);
+		Database.ExecuteNoWait(query);
 
 		foreach (uint version_id in photo.VersionIds) {
 			if (version_id == Photo.OriginalVersionId)
@@ -1123,13 +1036,10 @@
 
 			string version_name = photo.GetVersionName (version_id);
 
-			command = new SqliteCommand ();
-			command.Connection = Connection;
-			command.CommandText = String.Format ("INSERT INTO photo_versions (photo_id, version_id, name) " +
-							     "       VALUES ({0}, {1}, '{2}')",
-							     photo.Id, version_id, SqlString (version_name));
-			command.ExecuteNonQuery ();
-			command.Dispose ();
+			query = String.Format ("INSERT INTO photo_versions (photo_id, version_id, name) " +
+					       "       VALUES ({0}, {1}, '{2}')",
+					       photo.Id, version_id, SqlString (version_name));
+			Database.ExecuteNoWait(query);
 		}
 	}
 	
@@ -1168,10 +1078,7 @@
 
 	public Photo [] Query (string query)
 	{
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-		command.CommandText = query;
-		SqliteDataReader reader = command.ExecuteReader ();
+		SqliteDataReader reader = Database.Query (query);
 		
 		ArrayList version_list = new ArrayList ();
 		ArrayList id_list = new ArrayList ();
@@ -1210,8 +1117,6 @@
 			//Console.WriteLine ("Skipped Loading Data");
 		}
 
-		command.Dispose ();
-
 		return id_list.ToArray (typeof (Photo)) as Photo [];
 	}
 
@@ -1449,3 +1354,51 @@
 
 #endif
 }
+
+public class WriteMetadataJob : Job {
+	private Photo photo;
+
+	public WriteMetadataJob (Photo p) : base (true, JobType.Background) {
+		photo = p;
+	}
+
+	public WriteMetadataJob () : this (null) {
+	}
+
+	public override void Execute () {
+		JobScheduler.DisallowByPass();
+
+		JobScheduler.DebugMsg ("Writing image metadata: {0}", photo.Id);
+		try {
+			photo.WriteMetadataToImage ();
+		} catch (System.Exception e) {
+			Console.WriteLine ("Error syncing metadata to file\n{0}", e);
+		}
+
+		JobScheduler.RemoveEqual (this);
+	}
+
+	public override bool Equals (System.Object obj) {
+		if (obj == null || obj.GetType () != GetType ()) {
+			return false;
+		}
+
+		return ((WriteMetadataJob)obj).photo.CompareTo (photo) == 0;
+	}
+
+	public override int GetHashCode () {
+		return photo.GetHashCode ();
+	}
+
+	public override string GetOptions () {
+		return photo.Id.ToString ();
+	}
+
+	public override void LoadOptions (string options) {
+		photo = FSpot.Core.Database.Photos.Get (Convert.ToUInt32 (options)) as Photo;
+	}
+
+	public override bool PhotoDeleted (Photo p) {
+		return (p == photo);
+	}
+}

=== modified file 'src/TagCommands.cs'
--- src/TagCommands.cs	2006-05-06 17:32:12 +0000
+++ src/TagCommands.cs	2006-07-22 13:59:15 +0000
@@ -290,11 +290,12 @@
 
 			if (response == ResponseType.Ok) {
 				try {
+					bool name_changed = (t.Name != last_valid_name);
 					t.Name = last_valid_name;
 					t.Category = categories [category_option_menu.History] as Category;
 					t.Icon = icon_image.Pixbuf;
 
-					db.Tags.Commit (t);
+					db.Tags.Commit (t, name_changed);
 					success = true;
 				} catch (Exception ex) {
 					// FIXME error dialog.

=== modified file 'src/TagStore.cs'
--- src/TagStore.cs	2006-06-07 09:23:09 +0000
+++ src/TagStore.cs	2006-07-22 14:01:50 +0000
@@ -303,13 +303,10 @@
 	// base class.
 	private void LoadAllTags ()
 	{
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
 		// Pass 1, get all the tags.
 
-		command.CommandText = "SELECT id, name, is_category, sort_priority, icon FROM tags";
-		SqliteDataReader reader = command.ExecuteReader ();
+		string query = "SELECT id, name, is_category, sort_priority, icon FROM tags";
+		SqliteDataReader reader = Database.Query (query);
 
 		while (reader.Read ()) {
 			uint id = Convert.ToUInt32 (reader [0]);
@@ -330,12 +327,11 @@
 		}
 
 		reader.Close ();
-		command.Dispose ();
 
 		// Pass 2, set the parents.
 
-		command.CommandText = "SELECT id, category_id FROM tags";
-		reader = command.ExecuteReader ();
+		query = "SELECT id, category_id FROM tags";
+		reader = Database.Query (query);
 
 		while (reader.Read ()) {
 			uint id = Convert.ToUInt32 (reader [0]);
@@ -354,7 +350,6 @@
 
 		}
 		reader.Close ();
-		command.Dispose ();
 
 		if (FSpot.Core.Database.Meta.HiddenTagId.Value != null)
 			hidden = LookupInCache ((uint) FSpot.Core.Database.Meta.HiddenTagId.ValueAsInt) as Tag;
@@ -363,10 +358,7 @@
 
 	private void CreateTable ()
 	{
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText =
+		string query =
 			"CREATE TABLE tags (                               " +
 			"	id            INTEGER PRIMARY KEY NOT NULL," +
 			"       name          TEXT UNIQUE,                 " +
@@ -375,9 +367,7 @@
 			"       sort_priority INTEGER,			   " +
 			"       icon          TEXT			   " +
 			")";
-
-		command.ExecuteNonQuery ();
-		command.Dispose ();
+		Database.ExecuteNoWait(query);
 	}
 
 
@@ -415,8 +405,8 @@
 
 	// Constructor
 
-	public TagStore (SqliteConnection connection, bool is_new)
-		: base (connection, true)
+	public TagStore (Db database, bool is_new)
+		: base (database, true)
 	{
 		// The label for the root category is used in new and edit tag dialogs
 		root_category = new Category (null, 0, Mono.Posix.Catalog.GetString ("(None)"));
@@ -431,22 +421,15 @@
 
 	private uint InsertTagIntoTable (Category parent_category, string name, bool is_category)
 	{
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
 		uint parent_category_id = parent_category.Id;
 
-		command.CommandText = String.Format
+		string query = String.Format
 			("INSERT INTO tags (name, category_id, is_category, sort_priority) " +
 			 "            VALUES ('{0}', {1}, {2}, 0)                          ",
 			 SqlString (name),
 			 parent_category_id,
 			 is_category ? 1 : 0);
-
-		command.ExecuteScalar ();
-		command.Dispose ();
-
-		return (uint) Connection.LastInsertRowId;
+		return Database.Execute(query);
 	}
 
 	public Tag CreateTag (Category category, string name)
@@ -498,14 +481,10 @@
 		RemoveFromCache (item);
 		
 		((Tag)item).Category = null;
-		
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText = String.Format ("DELETE FROM tags WHERE id = {0}", item.Id);
-		command.ExecuteNonQuery ();
-
-		command.Dispose ();
+
+		string query = String.Format ("DELETE FROM tags WHERE id = {0}", item.Id);
+		Database.ExecuteNoWait(query);
+
 		EmitRemoved (item);
 	}
 
@@ -525,10 +504,7 @@
 	{
 		Tag tag = item as Tag;
 
-		SqliteCommand command = new SqliteCommand ();
-		command.Connection = Connection;
-
-		command.CommandText = String.Format ("UPDATE tags SET          " +
+		string query = String.Format ("UPDATE tags SET          " +
 						     "    name = '{0}',        " +
 						     "    category_id = {1},   " +
 						     "    is_category = {2},   " +
@@ -541,13 +517,26 @@
 						     tag.SortPriority,
 						     SqlString (GetIconString (tag)),
 						     tag.Id);
-		command.ExecuteNonQuery ();
+		Database.ExecuteNoWait(query);
 
-		command.Dispose ();
-		
 		EmitChanged (tag);
 	}
 
+	// Call this with the boolean value true to update the XMP data on all
+	// pictures with this tag attached.
+	public void Commit (DbItem item, bool update_xmp)
+	{
+		Commit (item);
+		if (update_xmp) {
+			Tag [] tags = { item as Tag };
+			Photo [] photos = Database.Photos.Query (tags);
+			
+			foreach (Photo photo in photos) {
+				JobScheduler.Schedule (new WriteMetadataJob (photo));
+			}
+		}
+	}
+
 
 #if TEST_TAG_STORE
 

=== modified file 'src/Updater.cs'
--- src/Updater.cs	2006-06-07 09:23:09 +0000
+++ src/Updater.cs	2006-07-01 09:08:40 +0000
@@ -19,7 +19,7 @@
 			// The order these are added is important as they will be run sequentially
 			
 			// Update from version 0 to 1: Remove empty Other tags
-			AddUpdate (delegate (SqliteConnection connection) {
+			AddUpdate (delegate (Db db) {
 				string other_id = SelectSingleString ("SELECT id FROM tags WHERE name = 'Other'");
 
 				if (other_id == null)
@@ -44,26 +44,26 @@
 			});
 
 			// Update from version 1 to 2: Restore Other tags that were removed leaving dangling child tags
-			AddUpdate (delegate (SqliteConnection connection) {
+			AddUpdate (delegate (Db db) {
 				string tag_count = SelectSingleString ("SELECT COUNT(*) FROM tags WHERE category_id != 0 AND category_id NOT IN (SELECT id FROM tags)");
 
 				// If there are no dangling tags, then don't do anything
 				if (tag_count == null || System.Int32.Parse (tag_count) == 0)
 					return;
 
-				ExecuteScalar ("INSERT INTO tags (name, category_id, is_category, icon) VALUES ('Other', 0, 1, 'stock_icon:f-spot-other.png')");
+				uint id = ExecuteScalar ("INSERT INTO tags (name, category_id, is_category, icon) VALUES ('Other', 0, 1, 'stock_icon:f-spot-other.png')");
 
 				ExecuteNonQuery (String.Format (
 					@"UPDATE tags SET category_id = {0} WHERE id IN 
 					(SELECT id FROM tags WHERE category_id != 0 AND category_id 
 					NOT IN (SELECT id FROM tags))",
-					connection.LastInsertRowId));
+					id));
 
 				System.Console.WriteLine ("Other tag restored.  Sorry about that!");
 			});
 			
 			// Update from version 2 to 3: ensure that Hidden is the only tag left which is a real tag (not category)
-			AddUpdate (delegate (SqliteConnection connection) {
+			AddUpdate (delegate (Db db) {
 				ExecuteNonQuery ("UPDATE tags SET is_category = 1 WHERE name != 'Hidden'");
 			});
 			
@@ -162,24 +162,12 @@
 
 		private static void ExecuteNonQuery (string statement)
 		{
-			SqliteCommand command = new SqliteCommand ();
-			command.Connection = db.Connection;
-
-			command.CommandText = statement;
-
-			command.ExecuteNonQuery ();
-			command.Dispose ();
+			db.ExecuteNoWait(statement);
 		}
 		
-		private static void ExecuteScalar (string statement)
+		private static uint ExecuteScalar (string statement)
 		{
-			SqliteCommand command = new SqliteCommand ();
-			command.Connection = db.Connection;
-
-			command.CommandText = statement;
-
-			command.ExecuteScalar ();
-			command.Dispose ();
+			return db.Execute(statement);
 		}
 		
 		private static string SelectSingleString (string statement)
@@ -187,18 +175,12 @@
 			string result = null;
 
 			try {
-				SqliteCommand command = new SqliteCommand ();
-				command.Connection = db.Connection;
-
-				command.CommandText = statement;
-
-				SqliteDataReader reader = command.ExecuteReader ();
+				SqliteDataReader reader = db.Query(statement);
 				
 				if (reader.Read ())
 					result = reader [0].ToString ();
 
 				reader.Close ();
-				command.Dispose ();
 			} catch (Exception e) {}
 
 			return result;
@@ -223,7 +205,7 @@
 			return temp_name;
 		}
 
-		private delegate void UpdateCode (SqliteConnection connection);
+		private delegate void UpdateCode (Db db);
 
 		private class Update {
 			public int Version;
@@ -245,7 +227,7 @@
 
 			public void Execute (Db db, MetaItem db_version)
 			{
-				code (db.Connection);
+				code (db);
 				
 				Console.WriteLine ("Updated database from version {0} to {1}",
 						db_version.ValueAsInt,

