Index: src/PhotoStore.cs
===================================================================
--- src/PhotoStore.cs	(revision 3412)
+++ src/PhotoStore.cs	(working copy)
@@ -739,7 +739,7 @@
 			//WARNING: if you change this schema, reflect your changes 
 			//to Updater.cs, at revision 7.0
 			"CREATE TABLE photos (                                     " +
-			"	id                 INTEGER PRIMARY KEY NOT NULL,   " +
+			"	id                 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,   " +
 			"	time               INTEGER NOT NULL,	   	   " +
 			"	uri		   STRING NOT NULL,		   " +
 			"	description        TEXT NOT NULL,	           " +
@@ -810,6 +810,7 @@
 		return photo;
 	}
 
+
 	private void GetVersions (Photo photo)
 	{
 		SqliteDataReader reader = Database.Query(new DbCommand("SELECT version_id, name, uri FROM photo_versions WHERE photo_id = :id", "id", photo.Id));
@@ -1112,7 +1113,41 @@
 		}
 	}
 
+	// Dbus
+	public event ItemsAddedHandler ItemsAddedOverDBus;
+	public event ItemsRemovedHandler ItemsRemovedOverDBus;
 
+	internal Photo CreateOverDBus (string new_path, string orig_path, uint roll_id, out Gdk.Pixbuf pixbuf)  {
+		Photo photo = Create (new_path, orig_path, roll_id, out pixbuf);
+		EmitAddedOverDBus (photo);
+
+		return photo;
+	}
+
+	internal void RemoveOverDBus (Photo photo) {
+	 	Remove (photo);
+		EmitRemovedOverDBus (photo);
+	}
+
+
+	protected void EmitAddedOverDBus (Photo photo) {
+	 	EmitAddedOverDBus (new Photo [] { photo });
+	}
+
+	protected void EmitAddedOverDBus (Photo [] photos) {
+	 	if (ItemsAddedOverDBus != null)
+		 	ItemsAddedOverDBus (this, new DbItemEventArgs (photos));
+	}
+
+	protected void EmitRemovedOverDBus (Photo photo) {
+		EmitRemovedOverDBus (new Photo [] { photo });
+	}
+
+	protected void EmitRemovedOverDBus (Photo [] photos) {
+		if (ItemsRemovedOverDBus != null)
+		 	ItemsRemovedOverDBus (this, new DbItemEventArgs (photos)); 
+	}
+
 	// Queries.
 
 	[Obsolete ("drop this, use IQueryCondition correctly instead")]
Index: src/Db.cs
===================================================================
--- src/Db.cs	(revision 3412)
+++ src/Db.cs	(working copy)
@@ -250,12 +250,20 @@
 		export_store = new ExportStore (Database, new_db);
 		job_store = new JobStore (Database, new_db);
  		photo_store = new PhotoStore (Database, new_db);
+
+		FSpot.DBusPhotoProxy.Init (this);
+		FSpot.DBusTagProxy.Init (tag_store);
 		
 		Database.CommitTransaction ();
 
 		empty = new_db;
 	}
 
+	public void EmitDown () {
+		FSpot.DBusTagProxy.EmitRemoteDown ();
+		FSpot.DBusPhotoProxy.EmitRemoteDown ();
+	}
+
 	public bool Empty {
 		get {
 			return empty;
Index: src/DBusProxy.cs
===================================================================
--- src/DBusProxy.cs	(revision 0)
+++ src/DBusProxy.cs	(revision 0)
@@ -0,0 +1,418 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using NDesk.DBus;
+
+namespace FSpot {
+	public delegate void RemoteUp();
+	public delegate void RemoteDown();
+
+	public class DBusException : Exception { 
+		public DBusException (string message)
+			: base (message)
+		{} 
+
+		public DBusException (string message, params object [] format_params)
+			: this (string.Format (message, format_params))
+		{}
+	}
+
+ 	[Interface ("org.gnome.FSpot.TagRemoteControl")]
+	public interface DBusTagRemoteControl {
+	 	// get all
+		string[] GetTagNames ();
+		uint[] GetTagIds ();
+
+		// get info for one tag
+		IDictionary<string, object> GetTagByName (string name);
+		IDictionary<string, object> GetTagById (int id);
+
+		// tag creators
+		int CreateTag (string name);
+		int CreateTagWithParent (string parent, string name);
+
+		// tag removers
+		bool RemoveTagByName (string name);
+		bool RemoveTagById (int id);
+
+		event RemoteUp RemoteUp;
+		event RemoteDown RemoteDown;
+	}
+
+	// Class exposing all photos on the dbus
+	public class DBusTagProxy : DBusTagRemoteControl {
+		private const string SERVICE_PATH = "org.gnome.FSpot";
+		private const string PROXY_PATH = "/org/gnome/FSpot/TagRemoteControl";
+
+		private static DBusTagProxy instance;
+
+		private TagStore tag_store;
+
+		public static DBusTagProxy Instance {
+			get { return instance; } 
+		}
+
+		public TagStore Store {
+			get { return tag_store; } 
+		}
+
+		public static void Init (TagStore store) {
+			if (instance != null)
+			 	return;
+
+			instance = new DBusTagProxy (store);
+			Bus.Session.Register (SERVICE_PATH, new ObjectPath (PROXY_PATH), instance);
+
+			if (instance.RemoteUp != null)
+			 	instance.RemoteUp ();
+		}
+
+		public static void EmitRemoteDown () {
+		 	if (instance.RemoteDown != null)
+			 	instance.RemoteDown ();
+		}
+
+		private DBusTagProxy (TagStore store) {
+		 	tag_store = store;
+		}
+
+		#region Interface methods
+		public string[] GetTagNames () {
+		 	List<string> tags = new List<string> ();
+			AddTagNameToList (tags, tag_store.RootCategory);
+
+			return tags.ToArray ();
+		}
+
+		public uint[] GetTagIds () {
+		 	List<uint> tags = new List<uint> ();
+			AddTagIdToList (tags, tag_store.RootCategory);
+
+			return tags.ToArray ();
+		}
+
+		public IDictionary<string, object> GetTagByName (string name) {
+			Tag t = tag_store.GetTagByName (name);
+
+			if (t == null)
+			 	throw new DBusException ("Tag with name {0} does not exist.", name);
+
+			return CreateDictFromTag (t);
+		}
+
+		public IDictionary<string, object> GetTagById (int id) {
+			Tag t = tag_store.GetTagById (id);
+
+			if (t == null)
+			 	throw new DBusException ("Tag with id {0} does not exist.", id);
+
+			return CreateDictFromTag (t);
+		}
+
+		public int CreateTag (string name) {
+		 	return CreateTagPriv (null, name);
+		}
+
+		public int CreateTagWithParent (string parent, string name) {
+			Tag parent_tag = tag_store.GetTagByName (parent); 
+
+			if (!(parent_tag is Category))
+			 	parent_tag = null;
+
+		 	return CreateTagPriv (parent_tag as Category, name);
+		}
+
+		public bool RemoveTagByName (string name) {
+		 	Tag tag = tag_store.GetTagByName (name);
+
+			return RemoveTag (tag);
+		}
+
+		public bool RemoveTagById (int id) {
+			Tag tag = tag_store.GetTagById (id);
+			
+			return RemoveTag (tag); 
+		}
+
+		public event RemoteUp RemoteUp;
+
+		public event RemoteDown RemoteDown;
+		#endregion
+
+		#region Helper methods
+		private int CreateTagPriv (Category parent_tag, string name)
+		{
+			try {
+				Tag created = tag_store.CreateCategory (parent_tag, name);			
+				return (int)created.Id; 
+			}
+			catch {
+				throw new DBusException ("Failed to create tag."); 
+			}
+		}
+
+		private bool RemoveTag (Tag t) {
+			if (t == null)
+			 	return false;
+				
+			try {
+			 	// remove tags from photos first
+			 	DBusPhotoProxy.Instance.Database.Photos.Remove (new Tag [] { t });
+				// then remove tag
+				tag_store.Remove (t);
+				return true; 
+			} 
+			catch {
+				return false; 
+			}
+		}
+
+
+		private void AddTagNameToList (List<string> list, Tag tag) {
+			if (tag != tag_store.RootCategory)
+			 	list.Add (tag.Name);
+
+			if (tag is Category) {
+				foreach (Tag child in (tag as Category).Children)
+					AddTagNameToList (list, child);
+			}
+		}
+
+		private void AddTagIdToList (List<uint> list, Tag tag) {
+			if (tag != tag_store.RootCategory)
+				list.Add (tag.Id);
+
+			if (tag is Category) {
+				foreach (Tag child in (tag as Category).Children)
+					AddTagIdToList (list, child);
+			}
+		}
+
+		private IDictionary<string, object> CreateDictFromTag (Tag t) {
+			Dictionary<string, object> result = new Dictionary<string, object> ();
+
+			result.Add ("Id", t.Id);
+			result.Add ("Name", t.Name);
+			
+			StringBuilder builder = new StringBuilder ();
+
+			if (t is Category) {
+				foreach (Tag child in (t as Category).Children) {
+					if (builder.Length > 0)
+					 	builder.Append (",");
+
+					builder.Append (child.Name);
+				}
+			}
+			
+			result.Add ("Children", builder.ToString ());
+
+			return result;
+		}
+		#endregion
+	}
+
+
+
+ 	[Interface ("org.gnome.FSpot.PhotoRemoteControl")]
+	public interface DBusPhotoRemoteControl {
+	 	// get all
+		uint[] GetPhotoIds ();
+
+		// import prepare
+		void PrepareRoll ();
+		void FinishRoll ();
+
+		// import 
+		int ImportPhoto (string path, bool copy, string []tags);
+
+		// photo properties
+		IDictionary<string, object> GetPhotoProperties (uint id); 
+		
+		// photo remove
+		void RemovePhoto (uint id);
+
+		// query
+		uint[] Query (string []tags);
+
+		// events
+		event RemoteUp RemoteUp;
+		event RemoteDown RemoteDown;
+	}
+
+	// Class exposing all photos on the dbus
+	public class DBusPhotoProxy : DBusPhotoRemoteControl {
+		private const string SERVICE_PATH = "org.gnome.FSpot";
+		private const string PROXY_PATH = "/org/gnome/FSpot/PhotoRemoteControl";
+
+		private static DBusPhotoProxy instance;
+	
+		private Db db;
+		private Roll current_roll = null;
+
+		public static DBusPhotoProxy Instance {
+			get { return instance; } 
+		}
+
+		public Db Database {
+			get { return db; } 
+		}
+
+		public static void Init (Db db) {
+			if (instance != null)
+			 	return;
+
+			instance = new DBusPhotoProxy (db);
+			Bus.Session.Register (SERVICE_PATH, new ObjectPath (PROXY_PATH), instance);
+
+			if (instance.RemoteUp != null)
+			 	instance.RemoteUp();
+		}
+
+		public static void EmitRemoteDown () {
+		 	if (instance.RemoteDown != null)
+			 	instance.RemoteDown ();
+		}
+
+		private DBusPhotoProxy (Db db) {
+			this.db = db;
+		}
+
+		public IDictionary<string, object> GetPhotoProperties (uint id) {
+			Dictionary<string, object> dict = new Dictionary<string, object> ();
+
+			Photo p = db.Photos.Get (id) as Photo;
+
+			if (p == null)
+			 	throw new DBusException ("Photo with id {0} does not exist.", id);
+
+			dict.Add ("Uri", p.DefaultVersionUri.ToString());
+			dict.Add ("Id", p.Id);
+			dict.Add ("Description", p.Description ?? string.Empty);
+
+			StringBuilder builder = new StringBuilder ();
+
+			foreach (Tag t in p.Tags) {
+				if (builder.Length > 0)
+				 	builder.Append (",");
+
+				builder.AppendFormat (t.Name);
+			}
+
+			dict.Add ("Tags", builder.ToString ());
+
+			return dict;
+		}
+
+		public uint[] GetPhotoIds () {
+		 	List<uint> ids = new List<uint> ();
+
+			foreach (Photo p in QueryAll ())
+				ids.Add (p.Id);
+
+			return ids.ToArray ();
+		}
+
+		public void PrepareRoll () {
+			if (current_roll != null)
+			 	return;
+
+			current_roll = db.Rolls.Create (); 
+		}
+
+		public void FinishRoll () {
+			current_roll = null; 
+		}
+
+		public int ImportPhoto (string path, bool copy, string []tags) {
+			if (current_roll == null)
+			 	throw new DBusException ("You must use PrepareRoll before you can import a photo.");
+
+			// add tags that exist in tag store
+			List<Tag> tag_list = GetTagsByNames (tags);
+
+			Gdk.Pixbuf pixbuf = null;
+			
+			// FIXME: this is more or less a copy of the file import backend code
+			// this should be streamlined
+			try {
+				string new_path = path;
+
+				if (copy)
+				 	new_path = FileImportBackend.ChooseLocation (path);
+
+				if (new_path != path)
+				 	System.IO.File.Copy (path, new_path);
+
+			 	Photo created = db.Photos.CreateOverDBus (new_path, path, current_roll.Id, out pixbuf);
+
+				try {
+					File.SetAttributes (new_path, File.GetAttributes (new_path) & ~FileAttributes.ReadOnly);
+					DateTime create = File.GetCreationTime (path);
+					File.SetCreationTime (new_path, create);
+					DateTime mod = File.GetLastWriteTime (path);
+					File.SetLastWriteTime (new_path, mod);
+				} catch (IOException) { 
+				 	// we don't want an exception here to be fatal.
+				}
+				
+				// attach tags we got
+				if (tag_list.Count > 0) {
+				 	created.AddTag (tag_list.ToArray ());
+					db.Photos.Commit (created);
+				}
+
+				return (int)created.Id;
+			// indicate failure
+			} catch {
+			 	throw new DBusException ("Failed to import the photo.");
+			}
+		}
+
+		public void RemovePhoto (uint id) {
+		 	Photo p = db.Photos.Get (id) as Photo;
+
+			if (p == null)
+			 	throw new DBusException ("Photo with id {0} does not exist.", id);
+
+			db.Photos.RemoveOverDBus (p);
+		}
+
+		public uint[] Query (string []tags) {
+			List<Tag> tag_list = GetTagsByNames (tags);
+
+			Photo []photos = db.Photos.Query (tag_list.ToArray ());
+
+			uint []ids = new uint[photos.Length];
+
+		 	for (int i = 0; i < ids.Length; i++)
+			 	ids[i] = photos[i].Id;
+
+			return ids;
+		}
+
+		public event RemoteUp RemoteUp;
+		public event RemoteDown RemoteDown;
+
+		private Photo[] QueryAll () {
+			return db.Photos.Query ((Tag [])null);
+		}
+
+		private List<Tag> GetTagsByNames (string []names) {
+			// add tags that exist in tag store
+			List<Tag> tag_list = new List<Tag> ();
+
+			foreach (string tag_name in names) {
+				Tag t = db.Tags.GetTagByName (tag_name);
+
+				if (t == null)
+				 	continue;
+
+				tag_list.Add (t);
+			} 
+
+			return tag_list;
+		}
+ 	}
+}
Index: src/Updater.cs
===================================================================
--- src/Updater.cs	(revision 3412)
+++ src/Updater.cs	(working copy)
@@ -173,7 +173,27 @@
 
 			}, true);
 			
-			// Update to version 9.0
+			// Update to version 9.0, make id autoincrement
+			AddUpdate (new Version (9,0),delegate () {
+				MoveTableToTemp ("photos");
+				ExecuteNonQuery (
+					"CREATE TABLE photos (                                     " +
+					"	id                 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
+					"	time               INTEGER NOT NULL,	   	   " +
+					"	uri		   STRING NOT NULL,		   " +
+					"	description        TEXT NOT NULL,	           " +
+					"	roll_id            INTEGER NOT NULL,		   " +
+					"	default_version_id INTEGER NOT NULL		   " +
+					")");
+
+				ExecuteNonQuery (
+					"INSERT INTO photos (id, time, uri, description, roll_id, default_version_id) " +
+					"SELECT id, time, uri, description, roll_id, default_version_id  " + 
+					"FROM   photos_temp "
+				);
+			}, false);
+
+			// Update to version 10.0
 			//AddUpdate (new Version (0,0),delegate () {
 			//	do update here
 			//});
Index: src/PhotoQuery.cs
===================================================================
--- src/PhotoQuery.cs	(revision 3412)
+++ src/PhotoQuery.cs	(working copy)
@@ -17,6 +17,11 @@
 		public PhotoQuery (PhotoStore store)
 		{
 			this.store = store;
+			// Note: this is to let the query pick up
+			// 	 photos that were added or removed over dbus
+			this.store.ItemsAddedOverDBus += delegate { RequestReload(); };
+			this.store.ItemsRemovedOverDBus += delegate { RequestReload(); };
+
 			photos = store.Query ((Tag [])null, null, range, roll_set);
 		}
 
Index: src/Makefile.am
===================================================================
--- src/Makefile.am	(revision 3412)
+++ src/Makefile.am	(working copy)
@@ -61,6 +61,7 @@
 	$(srcdir)/Core.cs			\
 	$(srcdir)/DateCommands.cs		\
 	$(srcdir)/Db.cs				\
+	$(srcdir)/DBusProxy.cs			\
 	$(srcdir)/Delay.cs			\
 	$(srcdir)/DependentListStore.cs			\
 	$(srcdir)/DirectoryAdaptor.cs		\
Index: src/Core.cs
===================================================================
--- src/Core.cs	(revision 3412)
+++ src/Core.cs	(working copy)
@@ -14,6 +14,8 @@
 		void View (string list);
 
 		void Shutdown ();
+
+		string Version ();
 	}
 
 	public class Core : ICore
@@ -252,10 +254,16 @@
 			window.Destroyed += HandleDestroyed;
 		}
 
+		public string Version () {
+			return FSpot.Defines.VERSION; 
+		}
+
 		public void HandleDestroyed (object sender, System.EventArgs args)
 		{
 			toplevels.Remove (sender);
 			if (toplevels.Count == 0) {
+				if (db != null)
+					db.EmitDown ();
 				// FIXME
 				// Should use Application.Quit(), but for that to work we need to terminate the threads
 				// first too.
Index: ChangeLog
===================================================================
--- ChangeLog	(revision 3412)
+++ ChangeLog	(working copy)
@@ -1,3 +1,15 @@
+2007-09-26  Thomas Van Machelen <thomas.vanmachelen@gmail.com>:
+	* src/Core.cs:
+	* src/Db.cs: 
+	* src/DBusProxy.cs: 
+	* src/Makefile.am:
+	* src/PhotoStore: 
+	* src/PhotoQuery.cs: Added two new classes that expose 	f-spot tag 
+	and photo functionality over dbus, adapt other classes so that 
+	everthing behaves as expected when manipulating f-spot over dbus 
+
+	* src/Updater.cs: Make sure photo id's are unique over time
+
 2007-09-26  Stephane Delcroix  <stephane@delcroix.org>
 
 	* src/RotateCommand.cs: Connect the Stop button on the rotate progress
