=== added file 'b/.bzrignore'
--- /dev/null	
+++ b/.bzrignore	
@@ -0,0 +1,60 @@
+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

=== added file 'b/src/WorkerThread.cs'
--- /dev/null	
+++ b/src/WorkerThread.cs	
@@ -0,0 +1,198 @@
+/* 
+ * WorkerThread - Executes background jobs.
+ *
+ * This thread runs in the background, performing long duration tasks without
+ * blocking the main UI. All jobs are stored in the jobs table, with a Command
+ * column to distinguish the kind of job. Job parameters are serialized in the
+ * Options string, it's up to the job execution method to deserialize this
+ * data. Scheduled jobs gracefully survive F-Spot restarts.
+ */
+
+using Mono.Data.SqliteClient;
+using System;
+using System.Threading;
+
+public class WorkerThread {
+	private bool looping = false;
+	private int ticks = 0;
+	private Db db;
+	private Thread worker = null;
+
+	public WorkerThread(Db db) {
+		this.db = db;
+	}
+
+	/*
+	 * Installs the worker thread, this is called by the Organize() method
+	 * in Core, when f-spot is idle. Will obviously be called only once.
+	 */
+	public bool Install() {
+		if (!looping) {
+			looping = true;
+			worker = new Thread(new ThreadStart(Execute));
+			worker.Priority = ThreadPriority.Lowest;
+			worker.Start();
+		}
+		return false;
+	}
+
+	/*
+	 * Execution tick of the worker thread.
+	 *
+	 * First 15 seconds are slept through, when a user has just started f-spot,
+	 * he'll probably want to use it, so we wait for a while to start doing
+	 * background work. Also, f-spot is preloading images during the first
+	 * 10 seconds on my system.
+	 */
+	public void Execute() {
+		Thread.Sleep(15000);
+		while (looping) {
+			WorkerThreadItem job = null;
+			while (looping && (job = (WorkerThreadItem)db.Jobs.GetJob()) != null) {
+				bool quick_command = false;
+				if (job.Command == "WriteMetaData") {
+					WriteMetaData(job);
+				} else {
+					Console.WriteLine("Unknown command: {0}, ignoring");
+					db.Jobs.Remove(job);
+					quick_command = true;
+				}
+
+				// Sleep a while after each command, unless it's trivial
+				if (!quick_command)
+					Thread.Sleep(10);
+			}
+
+			Thread.Sleep(10000);
+		}
+	}
+
+	public void Shutdown()
+	{
+		looping = false; // signal any running worker to stop
+		worker.Join(); // Wait for any running jobs
+	}
+
+	public void ScheduleMetaDataWrites(DbItem [] items)
+	{
+		foreach (DbItem item in items) {
+			Photo p = item as Photo; 
+			if (p != null)
+				db.Jobs.Create("WriteMetaData", String.Format("{0}", p.Id));
+		}
+	}
+
+	private void WriteMetaData(WorkerThreadItem job) 
+	{
+		uint id = Convert.ToUInt32(job.Options);
+		Photo p = db.Photos.Get(id) as Photo;
+		p.WriteMetadataToImage();
+		db.Jobs.RemoveAll(job);
+
+	}
+}
+
+public class WorkerThreadItem : DbItem {
+	private string command;
+	public string Command {
+		get { return command; }
+		set { command = value; }
+	}
+
+	private string options;
+	public string Options {
+		get { return options; }
+		set { options = value; }
+	}
+
+	public WorkerThreadItem (uint id, string command, string options) : base (id)
+	{
+		this.command = command;
+		this.options = options;
+	}
+}
+
+public class WorkerThreadStore : DbStore {
+	private bool has_jobs = true;
+
+	public WorkerThreadStore (Db database, bool is_new)
+		: base (database, true)
+	{
+		// Ensure the table exists
+		bool exists = Database.TableExists("jobs");
+			
+		if (is_new || !exists)
+			CreateTable ();
+	}
+
+	private void CreateTable ()
+	{
+		Database.ExecuteNoWait(
+			@"CREATE TABLE jobs (
+				id		INTEGER PRIMARY KEY NOT NULL,
+				command		TEXT 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)
+	{
+		string query = String.Format ("DELETE FROM jobs WHERE id = {0}", item.Id);
+		Database.ExecuteNoWait(query);
+	}
+
+	/* Removes all similar items, jobs with identical parameters. Rather than testing for
+	 * duplicate jobs while inserting, we delete all duplicates here, as insertions are
+	 * much more urgent (and should happen faster than deletions).
+	 */
+	public void RemoveAll (WorkerThreadItem item)
+	{
+		string query = String.Format ("DELETE FROM jobs WHERE command = '{0}' " +
+					      "AND options = '{1}'", 
+					      SqlString (item.Command),
+					      SqlString (item.Options));
+		Database.ExecuteNoWait(query);
+	}
+
+	public override void Commit (DbItem dbitem)
+	{
+		// FIXME: implement, if needed
+	}
+
+	public void Create (string jobcommand, string options) {
+		string query = String.Format ("INSERT INTO jobs "
+				+ "(command, options, time) VALUES (\"{0}\", \"{1}\", \"{2}\")",
+				jobcommand, options, DateTime.Now.Ticks);
+		Database.ExecuteNoWait(query);
+		has_jobs = true;
+	}
+
+	/*
+	 * Return a job, if available, otherwise returns null.
+	 */
+	public DbItem GetJob() {
+		if (!has_jobs) 
+			return null;
+
+		WorkerThreadItem job = null;
+		string query = "SELECT id, command, options "
+				+ "FROM jobs ORDER BY time ASC LIMIT 1";
+		SqliteDataReader reader = Database.Query(query);
+
+		if (reader.Read ()) {
+			job = new WorkerThreadItem (Convert.ToUInt32(reader[0]), 
+						    reader[1].ToString(), 
+						    reader[2].ToString());
+		}
+
+		if (job == null)
+			has_jobs = false;
+		return job;
+	}
+}

=== modified file 'a/ChangeLog'
--- a/ChangeLog	
+++ b/ChangeLog	
@@ -1,3 +1,27 @@
+2006-04-07  Ruben Vermeersch  <ruben@Lambda1.be>
+
+	* src/Core.cs: Integrate WorkerThread.
+
+	* src/Db.cs: Rewrite Db to make the whole thing threaded. Based on
+	Aaron Bockover's QueuedSQLiteDatabase used in banshee (with
+	permission). 
+
+	* src/MainWindow.cs: Make Metadata writing a WorkerThread task,
+	accidental formatting cleanup.
+
+	* src/Makefile.am: Add WorkerThread.cs
+
+	* src/ImportStore.cs
+	* src/MetaStore.cs
+	* src/PhotoStore.cs
+	* src/TagStore.cs
+	* src/Updater.cs: Adapt to new Db API.
+
+	* src/TagCommands.cs
+	  src/TagStore.cs: Rewrite XMP Metadata when tag has been renamed.
+
+	* src/WorkerThread.cs: New worker thread system.
+
 2006-03-21  Larry Ewing  <lewing@novell.com>
 
 	* src/PhotoStore.cs: remove thumbnails when removing.

=== modified file 'a/src/Core.cs'
--- a/src/Core.cs	
+++ b/src/Core.cs	
@@ -22,8 +22,13 @@
 	{
 		MainWindow organizer;
 		private static Db db;
+		private static WorkerThread worker;
 		static DBus.Connection connection;
 		System.Collections.ArrayList toplevels;
+
+		public static WorkerThread Worker {
+			get { return worker; }
+		}
 
 		public Core ()
 		{
@@ -106,6 +111,9 @@
 		public override void Organize ()
 		{
 			MainWindow.Window.Present ();
+
+			worker = new WorkerThread(db);
+			GLib.Idle.Add (new GLib.IdleHandler (worker.Install));
 		}
 		
 		public override void View (string path)
@@ -229,6 +237,7 @@
 
 		public override void Shutdown ()
 		{
+			worker.Shutdown();
 			System.Environment.Exit (0);
 		}
 

=== modified file 'a/src/Db.cs'
--- a/src/Db.cs	
+++ b/src/Db.cs	
@@ -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,13 +180,23 @@
 
 // The Database puts the stores together.
 
-public class Db : IDisposable {
+public class Db : IDisposable
+{
+	private ArrayList command_queue = new ArrayList();
+	private ArrayList non_transaction_command_queue = new ArrayList();
+	private SqliteConnection connection;
+	private Thread queue_thread;
+	private bool dispose_requested = false;
+	private bool processing_queue = false;
+	private bool empty;
+
+	private Thread current_transaction = null;
 
 	TagStore tag_store;
 	PhotoStore photo_store;
  	ImportStore import_store;
  	MetaStore meta_store;
-	bool empty;
+	WorkerThreadStore jobs_store;
 
 	public TagStore Tags {
 		get { return tag_store; }
@@ -215,45 +214,85 @@
 		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 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; }
+	public WorkerThreadStore Jobs {
+		get { return jobs_store; }
+	}
+
+	public void Dispose()
+	{
+		dispose_requested = true;
+		while(processing_queue);
+	}
+
+	private void QueueCommand(QueuedSqliteCommand command)
+	{
+		if (current_transaction != null && current_transaction != Thread.CurrentThread) {
+			lock(non_transaction_command_queue.SyncRoot) {
+				non_transaction_command_queue.Add(command);
+			}
+		} else {
+			lock(command_queue.SyncRoot) {
+				command_queue.Add(command);
+			}
+		}
+	}
+
+	/*
+	 * 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)
@@ -284,7 +323,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.
@@ -298,20 +337,25 @@
 				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);
 
-		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);
+		jobs_store = new WorkerThreadStore (this, new_db);
 		
 		empty = new_db;
 	}
@@ -322,9 +366,173 @@
 		}
 	}
 
-	public void Dispose () {}
-}
-
+	private void ProcessQueue()
+	{		 
+		processing_queue = true;
+		bool in_dispose_transaction = false;
+
+		int sleep_time = 10;
+		bool queries = false;
+		
+		while(true) {
+			queries = false;
+			while(command_queue.Count > 0) {
+				queries = true;
+				if(dispose_requested && current_transaction == null && !in_dispose_transaction) {
+					(new SqliteCommand("BEGIN", connection)).ExecuteNonQuery();
+					in_dispose_transaction = true;
+				}
+				
+				QueuedSqliteCommand command = command_queue[0] as QueuedSqliteCommand;
+				command.Execute();
+				
+				// TODO: optimize (RemoveAt?)
+				lock(command_queue.SyncRoot) {
+					command_queue.Remove(command);
+				}
+			}
+
+			if(dispose_requested && current_transaction == null) {
+				if(in_dispose_transaction) {
+					(new SqliteCommand("COMMIT", connection)).ExecuteNonQuery();
+				}
+				connection.Close();
+				processing_queue = false;
+				return;
+			}
+
+			if (!queries && sleep_time < 20) {
+				sleep_time++;
+			} else if (queries && sleep_time > 1) {
+				sleep_time--;
+			}
+			
+			Thread.Sleep(sleep_time);
+		}
+	}
+	
+	// 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() {
+		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() {
+		while (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) {
+				QueuedSqliteCommand command = non_transaction_command_queue[0] as QueuedSqliteCommand;
+				command_queue.Add(command);
+				non_transaction_command_queue.Remove(command);
+			}
+			}
+		}
+	}
+}
+
+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 {
 	public static DateTime DateTimeFromUnixTime (long unix_time)

=== modified file 'a/src/ImportStore.cs'
--- a/src/ImportStore.cs	
+++ b/src/ImportStore.cs	
@@ -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,18 +61,13 @@
 		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 'a/src/MainWindow.cs'
--- a/src/MainWindow.cs	
+++ b/src/MainWindow.cs	
@@ -18,7 +18,7 @@
 
 public class MainWindow {
 
-        public static MainWindow Toplevel;
+	public static MainWindow Toplevel;
 
 	Db db;
 
@@ -442,15 +442,8 @@
 
 	private void HandleDbItemsChanged (object sender, DbItemEventArgs args)
 	{
-		foreach (DbItem item in args.Items) {
-			Photo p = item as Photo;
-			if (p == null)
-				continue;
-			
-			if (write_metadata)
-				p.WriteMetadataToImage ();
-			
-		}
+		if (write_metadata)
+			Core.Worker.ScheduleMetaDataWrites(args.Items);
 		
 		if (args is TimeChangedEventArgs)
 			query.RequestReload ();
@@ -2638,59 +2631,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.QueueDraw ();
-		       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 'a/src/Makefile.am'
--- a/src/Makefile.am	
+++ b/src/Makefile.am	
@@ -115,6 +115,7 @@
 	$(srcdir)/CameraSelectionDialog.cs	\
 	$(srcdir)/CameraFileSelectionDialog.cs	\
 	$(srcdir)/TagSelectionDialog.cs		\
+	$(srcdir)/WorkerThread.cs			\
 	$(srcdir)/X3fFile.cs			\
 	$(srcdir)/XmpFile.cs			\
 	$(srcdir)/main.cs

=== modified file 'a/src/MetaStore.cs'
--- a/src/MetaStore.cs	
+++ b/src/MetaStore.cs	
@@ -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;
+		string query = String.Format ("DELETE FROM meta WHERE id = {0}", item.Id);
+		Database.ExecuteNoWait(query);
 
-		command.CommandText = String.Format ("DELETE FROM meta WHERE id = {0}", item.Id);
-		command.ExecuteNonQuery ();
-
-		command.Dispose ();
 		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 'a/src/PhotoStore.cs'
--- a/src/PhotoStore.cs	
+++ b/src/PhotoStore.cs	
@@ -646,19 +646,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,	   	   " +
@@ -666,39 +663,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 ();
+			")");
 	}
 
 
@@ -712,22 +690,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;
@@ -741,43 +714,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]);
@@ -812,12 +772,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]);
@@ -842,13 +797,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]);
@@ -880,14 +831,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) {
@@ -910,17 +858,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,
@@ -933,8 +879,6 @@
 			AddToCache (photo);
 		}
 
-		command.Dispose ();
-
 		if (photo == null)
 			return null;
 		
@@ -950,25 +894,21 @@
 		//        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);
 
 		directory_path = directory_path.Trim();
 		filename = filename.Trim();
-		command.CommandText = String.Format ("SELECT id,                                   " +
-				                     "       time,                                 " +
-						     "       description,                          " +
-						     "       default_version_id                    " +
-						     "  FROM photos                                " +
-						     " WHERE directory_path = \"{0}\"              " +
-						     "   AND name = \"{1}\"                        ",
-						     directory_path,
-						     filename);
-
-		SqliteDataReader reader = command.ExecuteReader ();
+		string query = String.Format ("SELECT id,                                   " +
+					      "       time,                                 " +
+ 					      "       description,                          " +
+ 					      "       default_version_id                    " +
+ 					      "  FROM photos                                " +
+ 					      " WHERE directory_path = \"{0}\"              " +
+ 					      "   AND name = \"{1}\"                        ",
+ 					      directory_path,
+ 					      filename);
+ 		SqliteDataReader reader = Database.Query(query);
 
 		if (reader.Read ()) {
 			photo = new Photo (Convert.ToUInt32 (reader [0]),
@@ -980,8 +920,6 @@
 			photo.DefaultVersionId = Convert.ToUInt32 (reader[3]);
 			AddToCache (photo);
 		}
-
-		command.Dispose ();
 
 		if (photo == null)
 			return null;
@@ -1023,29 +961,14 @@
 			RemoveFromCache (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)
@@ -1075,45 +998,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)
@@ -1121,13 +1029,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);
 		}
 	}
 	
@@ -1166,10 +1071,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 ();
@@ -1207,8 +1109,6 @@
 		} else {
 			//Console.WriteLine ("Skipped Loading Data");
 		}
-
-		command.Dispose ();
 
 		return id_list.ToArray (typeof (Photo)) as Photo [];
 	}

=== modified file 'a/src/TagCommands.cs'
--- a/src/TagCommands.cs	
+++ b/src/TagCommands.cs	
@@ -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 'a/src/TagStore.cs'
--- a/src/TagStore.cs	
+++ b/src/TagStore.cs	
@@ -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,11 +517,21 @@
 						     tag.SortPriority,
 						     SqlString (GetIconString (tag)),
 						     tag.Id);
-		command.ExecuteNonQuery ();
-
-		command.Dispose ();
-		
+		Database.ExecuteNoWait(query);
+
 		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);
+			FSpot.Core.Worker.ScheduleMetaDataWrites(photos);
+		}
 	}
 
 

=== modified file 'a/src/Updater.cs'
--- a/src/Updater.cs	
+++ b/src/Updater.cs	
@@ -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
-			//AddUpdate (delegate (SqliteConnection connection) {
+			//AddUpdate (delegate (Db db) {
 			//	do update here
 			//});
 		}
@@ -157,24 +157,12 @@
 
 		private static void ExecuteNonQuery (string statement)
 		{
-			SqliteCommand command = new SqliteCommand ();
-			command.Connection = db.Connection;
-
-			command.CommandText = statement;
-
-			command.ExecuteNonQuery ();
-			command.Dispose ();
-		}
-		
-		private static void ExecuteScalar (string statement)
-		{
-			SqliteCommand command = new SqliteCommand ();
-			command.Connection = db.Connection;
-
-			command.CommandText = statement;
-
-			command.ExecuteScalar ();
-			command.Dispose ();
+			db.ExecuteNoWait(statement);
+		}
+		
+		private static uint ExecuteScalar (string statement)
+		{
+			return db.Execute(statement);
 		}
 		
 		private static string SelectSingleString (string statement)
@@ -182,18 +170,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;
@@ -218,7 +200,7 @@
 			return temp_name;
 		}
 
-		private delegate void UpdateCode (SqliteConnection connection);
+		private delegate void UpdateCode (Db db);
 
 		private class Update {
 			public int Version;
@@ -240,7 +222,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,

