Index: Tomboy.mdp
===================================================================
--- Tomboy.mdp	(revision 1220)
+++ Tomboy.mdp	(working copy)
@@ -49,6 +49,7 @@
     <File name="./Tomboy/Defines.cs" subtype="Code" buildaction="Compile" />
     <File name="./Tomboy/PreferencesDialog.cs" subtype="Code" buildaction="Compile" />
     <File name="./Tomboy/NoteEditor.cs" subtype="Code" buildaction="Compile" />
+    <File name="./Tomboy/SyncManager.cs" subtype="Code" buildaction="Compile" />
     <File name="./Tomboy/Addins/Backlinks" subtype="Directory" buildaction="Compile" />
     <File name="./Tomboy/Addins/Backlinks/BacklinksNoteAddin.cs" subtype="Code" buildaction="Compile" />
     <File name="./Tomboy/Addins/Tasks" subtype="Directory" buildaction="Compile" />
@@ -68,6 +69,9 @@
     <File name="./Tomboy/Addins/Backlinks/BacklinkMenuItem.cs" subtype="Code" buildaction="Compile" />
     <File name="./Tomboy/AddinManager.cs" subtype="Code" buildaction="Compile" />
     <File name="./Tomboy/Addins/Tasks/TasksNoteAddin.cs" subtype="Code" buildaction="Compile" />
+    <File name="./Tomboy/SyncDialog.cs" subtype="Code" buildaction="Compile" />
+    <File name="./Tomboy/FileSystemSyncServer.cs" subtype="Code" buildaction="Compile" />
+    <File name="./Tomboy/TomboySyncClient.cs" subtype="Code" buildaction="Compile" />
   </Contents>
   <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="True" RelativeMakefileName="./Makefile.am" ExecuteTargetName="run">
     <BuildFilesVar />
Index: Tomboy/PreferencesDialog.cs
===================================================================
--- Tomboy/PreferencesDialog.cs	(revision 1220)
+++ Tomboy/PreferencesDialog.cs	(working copy)
@@ -59,6 +59,8 @@
 				new Gtk.Label (Catalog.GetString ("Editing")));
 			notebook.AppendPage (MakeHotkeysPane (), 
 				new Gtk.Label (Catalog.GetString ("Hotkeys")));
+			notebook.AppendPage (MakeSyncPane (), 
+				new Gtk.Label (Catalog.GetString ("Synchronization")));
 			notebook.AppendPage (MakePluginsPane (), 
 				new Gtk.Label (Catalog.GetString ("Plugins")));
 
@@ -308,6 +310,30 @@
 
 			return hotkeys_list;
 		}
+		
+		public Gtk.Widget MakeSyncPane ()
+		{
+			Gtk.HPaned pane;
+			
+			pane = new Gtk.HPaned ();
+			
+			Gtk.Label label = new Gtk.Label (Catalog.GetString ("Path:"));
+			Gtk.Entry entry = new Gtk.Entry ();
+			label.MnemonicWidget = entry;
+			entry.Show ();
+			PropertyEditorEntry peditor =
+				new PropertyEditorEntry (Preferences.SYNC_URL,
+				                         entry);
+			SetupPropertyEditor (peditor);
+			
+			pane.Pack2 (entry, true, false);
+			pane.Pack1 (label, true, false);
+			
+			pane.BorderWidth = 6;
+			pane.ShowAll ();
+			
+			return pane;
+		}
 
 		// Page 3
 		// Plugin Preferences
Index: Tomboy/SyncDialog.cs
===================================================================
--- Tomboy/SyncDialog.cs	(revision 0)
+++ Tomboy/SyncDialog.cs	(revision 0)
@@ -0,0 +1,165 @@
+using System;
+
+namespace Tomboy
+{
+	public class SyncDialog : Gtk.Dialog
+	{
+		private Gtk.ProgressBar progressBar;
+		private Gtk.Expander expander;
+		private Gtk.Button closeButton;
+		
+		private Gtk.ListStore model;
+		
+		// TODO: Possible to make Tomboy not crash if quit while dialog is up?
+		public SyncDialog () : base ("Synchronization Progress", null, Gtk.DialogFlags.Modal)
+		{
+			SetSizeRequest (400, -1);
+			
+			VBox.PackStart (new Gtk.Label ("Tomboy synchronization is currently in progress"),
+			                false, false, 5);
+			
+			progressBar = new Gtk.ProgressBar ();
+			//progressBar.Text = "Contacting Server...";
+			progressBar.Orientation = Gtk.ProgressBarOrientation.LeftToRight;				
+			VBox.PackStart (progressBar, false, false, 0);
+			
+			// Create model for TreeView
+			model = new Gtk.ListStore (typeof (string), typeof (string));
+			
+			// Create TreeView, attach model
+			Gtk.TreeView treeView = new Gtk.TreeView (model);
+			treeView.Model = model;
+			
+			// Set up TreeViewColumns
+			Gtk.TreeViewColumn column = new Gtk.TreeViewColumn ("Note Title", new Gtk.CellRendererText (), "text", 0);
+			column.SortColumnId = 0;
+			column.Resizable = true;
+			treeView.AppendColumn (column);
+			
+			column = new Gtk.TreeViewColumn ("Status", new Gtk.CellRendererText (), "text", 1);
+			column.SortColumnId = 1;
+			column.Resizable = true;
+			treeView.AppendColumn (column);
+			
+			// Drop TreeView into a ScrolledWindow into a VBox
+			Gtk.ScrolledWindow scrolledWindow = new Gtk.ScrolledWindow ();
+			scrolledWindow.SetSizeRequest (-1, 200);
+			scrolledWindow.Add (treeView);
+			Gtk.VBox expandVBox = new Gtk.VBox ();
+			expandVBox.PackStart (scrolledWindow, true, true, 5);
+			
+			// Drop all that into into the Expander
+			expander = new Gtk.Expander ("Details");
+			expander.Add (expandVBox);
+			VBox.PackStart (expander, true, true, 5);
+			
+			closeButton = (Gtk.Button) AddButton (Gtk.Stock.Close, Gtk.ResponseType.Close);
+			closeButton.Sensitive = false;
+			
+			HasSeparator = false;
+			
+			expander.Activated += OnExpanderActivated;
+			
+			VBox.ShowAll ();
+		}
+
+		private void OnExpanderActivated (object sender, EventArgs e)
+		{
+			if (expander.Expanded)
+				this.Resizable = true;
+			else
+				this.Resizable = false;
+		}
+		
+		public string ProgressText
+		{
+			get { return progressBar.Text; }
+			set { progressBar.Text = value; }
+		}
+		
+		public double ProgressFraction
+		{
+			get { return progressBar.Fraction; }
+			set { progressBar.Fraction = value;}
+		}
+		
+		public bool CloseSensitive
+		{
+			get { return closeButton.Sensitive; }
+			set { closeButton.Sensitive = value; }
+		}
+		
+		public void AddUpdateItem (string title, string status)
+		{
+			model.AppendValues (title, status);
+		}
+	}
+
+
+	public class SyncTitleConflictDialog : Gtk.Dialog
+	{
+		private Note existingNote;
+		
+		private Gtk.Button continueButton;
+		
+		private Gtk.Entry renameEntry;
+		private Gtk.CheckButton renameUpdateCheck;
+		private Gtk.RadioButton renameRadio;
+		private Gtk.RadioButton deleteExistingRadio;
+					
+		public SyncTitleConflictDialog (Note existingNote) :
+			base ("Note Title Conflict", null, Gtk.DialogFlags.Modal)
+		{
+			this.existingNote = existingNote;
+			
+			VBox.PackStart (new Gtk.Label ("The server already has a note called " +
+			                               existingNote.Title + ".  What do you want to do?"));
+			
+			Gtk.HBox renameHBox = new Gtk.HBox ();
+			renameRadio = new Gtk.RadioButton ("Rename existing note");
+			Gtk.VBox renameOptionsVBox = new Gtk.VBox ();
+			renameEntry = new Gtk.Entry (existingNote.Title);
+			renameUpdateCheck = new Gtk.CheckButton ("Update referencing notes to match new note title");
+			renameOptionsVBox.PackStart (renameEntry);
+			renameOptionsVBox.PackStart (renameUpdateCheck);
+			renameHBox.PackStart (renameRadio);
+			renameHBox.PackStart (renameOptionsVBox);
+			VBox.PackStart (renameHBox);
+			
+			deleteExistingRadio = new Gtk.RadioButton (renameRadio, "Delete existing note");
+			VBox.PackStart (deleteExistingRadio);
+			
+			continueButton = (Gtk.Button) AddButton (Gtk.Stock.GoForward, Gtk.ResponseType.Accept);
+			AddButton (Gtk.Stock.Cancel, Gtk.ResponseType.Cancel);
+			
+			ShowAll ();
+		}
+		
+		public string RenamedTitle
+		{
+			get { return renameEntry.Text; }
+		}
+		
+		public TitleConflictResolution Resolution
+		{
+			get
+			{
+				if (renameRadio.Active) {
+					if (renameUpdateCheck.Active)
+					        return TitleConflictResolution.RenameExistingAndUpdate;
+					else
+						return TitleConflictResolution.RenameExistingNoUpdate;
+				}
+				else
+					return TitleConflictResolution.DeleteExisting;
+			}
+		}
+		
+		public enum TitleConflictResolution
+		{
+			RenameExistingNoUpdate,
+			RenameExistingAndUpdate,
+			DeleteExisting
+		}
+	}	
+}
\ No newline at end of file
Index: Tomboy/SyncManager.cs
===================================================================
--- Tomboy/SyncManager.cs	(revision 0)
+++ Tomboy/SyncManager.cs	(revision 0)
@@ -0,0 +1,247 @@
+using System;
+using System.IO;
+using System.Xml;
+using System.Collections.Generic;
+
+namespace Tomboy
+{
+	public class SyncManager
+	{
+		//private static SyncServer server;
+		private static SyncClient client;
+		
+		static SyncManager ()
+		{
+			client = new TomboySyncClient ();
+			//server = new FileSystemSyncServer ();
+		}
+		
+		public static void PerformSynchronization ()
+		{			
+			SyncServer server;
+			try {
+				server = new FileSystemSyncServer ();
+			} catch (Exception e) {
+				Logger.Log ("Exception while creating SyncServer: " + e.Message);
+				return;
+				// TODO: This should be a GUI dialog
+			}
+			
+			SyncDialog syncDialog = new SyncDialog ();
+			syncDialog.ProgressText = "Contacting Server...";
+			syncDialog.Show ();
+			
+			// TODO: How to actually do transactions?  Is it even possible?
+			if (!server.BeginSyncTransaction ()) {
+				Logger.Log ("PerformSynchronization: Server locked, try again later");
+				syncDialog.ProgressText = "Sync failed: Server locked, try again later";
+				syncDialog.CloseSensitive = true;
+				return;
+			}
+			int latestServerRevision = server.LatestRevision;
+			int newRevision = latestServerRevision + 1;
+			
+			syncDialog.ProgressText = "Getting note updates...";
+			syncDialog.ProgressFraction = 0.1;
+			
+			// Handle notes modified or added on server
+			Logger.Debug ("Sync: GetNoteUpdatesSince rev " + client.LastSynchronizedRevision.ToString ());
+			IDictionary<string, NoteUpdate> noteUpdates =
+				server.GetNoteUpdatesSince (client.LastSynchronizedRevision);
+			Logger.Debug ("Sync: " + noteUpdates.Count + " updates since rev " + client.LastSynchronizedRevision.ToString ());
+			// TODO: Before actually doing updates, do this:
+			//      1. Loop through NoteUpdates, look for instances where
+			//         there is an existing note with the same title but
+			//         different UUID.
+			//      2. For each note like this, prompt the user to do either:
+			//              a. Rename existing note (should this work like
+			//                 a normal rename and update links, or no?
+			//                 No seems like a good choice if you're getting
+			//                 a new note with that name)
+			//              b. Delete existing note (server copy replaces)
+			//              c. Delete server note? (how would this work?)
+			//              d. Cancel sync (remember, no updates have happened yet)
+			//      This way, it should be impossible to upload two notes
+			//      with the same name.  Still not sure how irritating this
+			//      might be for NotD users.  And will the Start Note be
+			//      OK with this?  (apparently it's OK to delete 
+			
+			// TODO: Lots of searching here and in the next foreach...
+			//       Want this stuff to happen all at once first, but
+			//       maybe there's a way to store this info and pass it on?
+			foreach (NoteUpdate noteUpdate in noteUpdates.Values)
+			{
+				if (FindNoteByUUID (noteUpdate.UUID) == null) {
+					Note existingNote = NoteMgr.Find (noteUpdate.Title);
+					if (existingNote != null) {
+						SyncTitleConflictDialog conflictDlg =
+							new SyncTitleConflictDialog (existingNote);
+						Gtk.ResponseType reponse = (Gtk.ResponseType) conflictDlg.Run ();
+						
+						if (reponse == Gtk.ResponseType.Cancel)
+							// TODO: Message?
+							return;
+						switch (conflictDlg.Resolution) {
+						case SyncTitleConflictDialog.TitleConflictResolution.DeleteExisting:
+							NoteMgr.Delete (existingNote);
+							break;
+						case SyncTitleConflictDialog.TitleConflictResolution.RenameExistingAndUpdate:
+							existingNote.Title = conflictDlg.RenamedTitle;  // TODO: What if note is open?
+							break;
+						case SyncTitleConflictDialog.TitleConflictResolution.RenameExistingNoUpdate:
+							existingNote.Data.Title = conflictDlg.RenamedTitle;     // TODO: Does this work?
+							break;
+						}
+						
+						conflictDlg.Hide ();
+						conflictDlg.Destroy (); // TODO: Necessary?
+					}
+				}
+			}
+			
+			double updateProgressInterval = 0.4 / (double)noteUpdates.Count;
+			
+			foreach (NoteUpdate note in noteUpdates.Values) {
+				syncDialog.ProgressFraction += updateProgressInterval;
+				Note existingNote = FindNoteByUUID (note.UUID);
+				
+				if (existingNote == null) {// TODO: not so simple...what if I deleted the note?
+					if (note.XmlContent == string.Empty)
+						continue; // Deletion of note that doesn't exist
+					
+					string newNotePath = Path.Combine (NoteMgr.NoteDirectoryPath,
+					                                   note.UUID + ".note");
+					FileInfo info = new FileInfo (newNotePath);
+					StreamWriter writer = info.CreateText ();
+					writer.Write (note.XmlContent);
+					writer.Close ();
+					Logger.Debug ("Copied new note here: " + newNotePath);
+					Logger.Debug ("New note contents are: " + note.XmlContent);
+					
+					existingNote = Note.Load (newNotePath, NoteMgr);
+					NoteMgr.Notes.Add (existingNote);
+					
+					syncDialog.AddUpdateItem (existingNote.Title, "Added");
+				}
+				else if (note.XmlContent == string.Empty &&
+				         existingNote.ChangeDate.CompareTo (client.LastSyncDate) <= 0) {
+					syncDialog.AddUpdateItem (existingNote.Title, "Deleted");
+					NoteMgr.Delete (existingNote);
+				}
+				else if (existingNote.ChangeDate.CompareTo (client.LastSyncDate) <= 0) {
+					existingNote.XmlContent = note.XmlContent;// TODO: live update of note XML
+					syncDialog.AddUpdateItem (existingNote.Title, "Updated");
+				}
+				else
+					;// TODO: handle conflicts
+			}
+			
+			// TODO: Add following updates to syncDialog treeview
+			syncDialog.ProgressText = "Sending note updates...";
+			syncDialog.ProgressFraction = 0.5;
+
+			// Upload notes modified or added on client
+			List<Note> newOrModifiedNotes = new List<Note> ();			
+			foreach (Note note in NoteMgr.Notes) {
+				if (note.Revision <= client.LastSynchronizedRevision &&// NOTE: Confused...?
+				    note.ChangeDate.CompareTo (client.LastSyncDate) > 0)
+					newOrModifiedNotes.Add (note);
+			}
+			// Apply this revision number to all new/modified notes since last sync
+			// TODO: Should revision info be stored in note, or a seperate file?
+			foreach (Note note in newOrModifiedNotes) {
+				note.Revision = newRevision;
+				note.Save ();
+			}
+			Logger.Debug ("Sync: Uploading " + newOrModifiedNotes.Count.ToString () + " note updates");
+			server.UploadNotes (newOrModifiedNotes);
+			
+			
+			// Handle notes deleted on client
+			List<string> locallyDeletedUUIDs = new List<string> ();			
+			foreach (string noteUUID in server.GetAllNoteUUIDs ()) {
+				if (FindNoteByUUID (noteUUID) == null)
+					locallyDeletedUUIDs.Add (noteUUID);
+			}			
+			server.DeleteNotes (locallyDeletedUUIDs);
+			
+			bool commitResult = server.CommitSyncTransaction ();
+			if (commitResult) {
+				syncDialog.ProgressFraction = 1.0;
+				syncDialog.ProgressText = "Sync completed successfully!";
+			}
+			else {
+				syncDialog.ProgressFraction = 0;
+				syncDialog.ProgressText = "Sync failed!";
+			}
+			syncDialog.CloseSensitive = true;
+			
+			// This should be equivalent to newRevision
+			client.LastSynchronizedRevision = server.LatestRevision;
+			
+			client.LastSyncDate = DateTime.Now;			
+		}
+		
+		private static Note FindNoteByUUID (string uuid)
+		{
+			return NoteMgr.FindByUri ("note://tomboy/" + uuid);
+		}
+		
+		private static NoteManager NoteMgr
+		{
+			get { return Tomboy.DefaultNoteManager; }
+		}
+	}
+	
+	public class NoteUpdate
+	{
+		public string XmlContent;//string.Empty if deleted?
+		public string Title;
+		public string UUID; //needed?
+		public int LatestRevision;
+		
+		public NoteUpdate (string xmlContent, string title, string uuid, int latestRevision)
+		{
+			XmlContent = xmlContent;
+			Title = title;
+			UUID = uuid;
+			LatestRevision = latestRevision;
+			
+			// TODO: Clean this up (and remove title parameter?)
+			if (xmlContent != null && xmlContent.Length > 0) {
+				XmlTextReader xml = new XmlTextReader (new StringReader (XmlContent));
+				xml.Namespaces = false;
+
+				while (xml.Read ()) {
+					switch (xml.NodeType) {
+					case XmlNodeType.Element:
+						switch (xml.Name) {
+					case "title":
+						Title = xml.ReadString ();
+						break;
+					}
+						break;
+					}
+				}
+			}
+		}
+	}
+	
+	
+	public interface SyncServer
+	{
+		bool BeginSyncTransaction ();
+		bool CommitSyncTransaction ();
+		IList<string> GetAllNoteUUIDs ();
+		IDictionary<string, NoteUpdate> GetNoteUpdatesSince (int revision);
+		void DeleteNotes (IList<string> deletedNoteUUIDs);
+		void UploadNotes (IList<Note> notes);
+		int LatestRevision { get; }
+	}
+	
+	public interface SyncClient
+	{
+		int LastSynchronizedRevision { get; set; }
+		DateTime LastSyncDate { get; set; }		
+	}
+}
Index: Tomboy/FileSystemSyncServer.cs
===================================================================
--- Tomboy/FileSystemSyncServer.cs	(revision 0)
+++ Tomboy/FileSystemSyncServer.cs	(revision 0)
@@ -0,0 +1,230 @@
+using System;
+using System.IO;
+using System.Xml;
+using System.Collections.Generic;
+
+namespace Tomboy
+{
+	public class FileSystemSyncServer : SyncServer
+	{
+		private class RevisionDetails
+		{
+			public int Revision;
+			public IList<string> UpdatedNotes;
+			public IList<string> DeletedNotes;
+			
+			private FileSystemSyncServer server;
+			
+			public RevisionDetails (int revision, FileSystemSyncServer server)
+			{
+				Revision = revision;
+				this.server = server;
+				UpdatedNotes = new List<string> ();
+				DeletedNotes = new List<string> ();
+				
+				int revisionParentDir = revision / 100;
+				string manifestFileParentDir = Path.Combine (server.serverPath,
+				                                             revisionParentDir.ToString ());
+				string manifestFilePath = Path.Combine (manifestFileParentDir,
+				                                        revision.ToString () + ".rev");
+				
+				StreamReader reader = new StreamReader (manifestFilePath);
+				
+				// TODO: Read details from file
+				string section = "";
+				while (!reader.EndOfStream) {
+					string line = reader.ReadLine ();
+					if (line == "U")
+						section = "U";
+					else if (line == "D")
+						section = "D";
+					else if (line.Length > 0 && section == "U")
+						UpdatedNotes.Add (line);
+					else if (line.Length > 0 && section == "D")
+						DeletedNotes.Add (line);
+				}
+				
+				reader.Close ();
+			}
+		}
+		
+		private List<string> updatedNotes;
+		private List<string> deletedNotes;
+		
+		private string serverPath;
+		private string notePath;
+		private string lockPath;
+		
+		public FileSystemSyncServer ()
+		{			
+			// TODO: Handle changes to serverPath...
+			//       Should there be a new instance of this class
+			//       for each sync?			
+			serverPath = (string) Preferences.Get (Preferences.SYNC_URL);
+			
+			if (!Directory.Exists (serverPath))
+				throw new DirectoryNotFoundException (serverPath);
+			
+			notePath = Tomboy.DefaultNoteManager.NoteDirectoryPath;
+			lockPath = Path.Combine (serverPath, "lock");
+		}
+		
+		public virtual void UploadNotes (IList<Note> notes)
+		{
+			foreach (Note note in notes) {
+				try {
+					string serverNotePath = Path.Combine (serverPath, Path.GetFileName (note.FilePath));
+					File.Copy (note.FilePath, serverNotePath, true);
+					Mono.Unix.Native.Syscall.chmod (serverNotePath, Mono.Unix.Native.FilePermissions.ACCESSPERMS);
+					updatedNotes.Add (Path.GetFileNameWithoutExtension (note.FilePath));
+				} catch (Exception e) {
+					Logger.Log ("Sync: Error uploading note: " + e.Message);
+				}
+			}
+		}
+
+		public virtual void DeleteNotes (IList<string> deletedNoteUUIDs)
+		{
+			foreach (string uuid in deletedNoteUUIDs) {
+				try {
+					File.Delete (Path.Combine (serverPath, uuid + ".note"));
+					deletedNotes.Add (uuid);
+				} catch (Exception e) {
+					Logger.Log ("Sync: Error deleting note: " + e.Message);
+				}
+			}				
+		}
+
+		public IList<string> GetAllNoteUUIDs ()
+		{
+			List<string> noteUUIDs = new List<string> ();
+			
+			foreach (string fileName in Directory.GetFiles (serverPath, "*.note"))
+				noteUUIDs.Add (Path.GetFileNameWithoutExtension (fileName));
+			
+			return noteUUIDs;
+		}
+
+		public virtual IDictionary<string, NoteUpdate> GetNoteUpdatesSince (int revision)
+		{
+			// TODO: Empty temp dir each time
+			string tempPath = Path.Combine (notePath, "sync_temp");
+			if (!Directory.Exists (tempPath))
+				Directory.CreateDirectory (tempPath);
+			
+			Dictionary<string, NoteUpdate> noteUpdates = new Dictionary<string, NoteUpdate> ();
+			// TODO: Read manifest file
+			for (int i = LatestRevision; i> revision; i--) {
+				RevisionDetails revDetails = new RevisionDetails (i, this);
+				// TODO: Pull info into NoteUpdate objects
+				foreach (string uuid in revDetails.DeletedNotes) {
+					if (!noteUpdates.ContainsKey (uuid)) {
+						NoteUpdate update = new NoteUpdate (string.Empty,string.Empty,uuid,i);
+						noteUpdates[uuid] = update;
+					}
+				}
+				
+				foreach (string uuid in revDetails.UpdatedNotes) {
+					if (!noteUpdates.ContainsKey (uuid)) {
+						// TODO: Put file in temp dir
+						File.Copy (Path.Combine (serverPath, uuid + ".note"),
+						           Path.Combine (tempPath, uuid + ".note"),
+						           true);
+						// TODO: Get title, contents, etc
+						string noteTitle = "";
+						StreamReader reader = new StreamReader (Path.Combine (tempPath, uuid + ".note"));
+						string noteXml = reader.ReadToEnd ();
+						reader.Close ();
+						NoteUpdate update = new NoteUpdate (noteXml, noteTitle, uuid, i);
+						noteUpdates[uuid] = update;
+					}
+				}
+			}
+			
+			return noteUpdates;
+		}
+
+		public virtual bool BeginSyncTransaction ()
+		{
+			// TODO: Lock expiration
+			if (File.Exists (lockPath))
+				return false;
+			else
+				File.Create (lockPath).Close ();
+			
+			updatedNotes = new List<string> ();
+			deletedNotes = new List<string> ();
+			
+			return true;
+		}
+
+		public virtual bool CommitSyncTransaction ()
+		{
+			if (updatedNotes.Count > 0 || deletedNotes.Count > 0)
+			{
+				// TODO: error-checking, etc
+				int newRevision = LatestRevision + 1;
+				int newRevisionParentDir = newRevision / 100;
+				string manifestFileParentDir = Path.Combine (serverPath,
+				                                             newRevisionParentDir.ToString ());
+				string manifestFilePath = Path.Combine (manifestFileParentDir,
+				                                        newRevision.ToString () + ".rev");
+				if (!Directory.Exists (manifestFileParentDir))
+				{
+					Directory.CreateDirectory (manifestFileParentDir);
+					Mono.Unix.Native.Syscall.chmod (manifestFileParentDir,
+					                                Mono.Unix.Native.FilePermissions.ACCESSPERMS);
+				}
+				
+				FileInfo info = new FileInfo (manifestFilePath);
+				StreamWriter writer = info.CreateText ();
+				if (updatedNotes.Count > 0) {
+					writer.WriteLine ("U");
+					foreach (string uuid in updatedNotes)
+						writer.WriteLine (uuid);
+				}
+				if (deletedNotes.Count > 0) {
+					writer.WriteLine ("D");
+					foreach (string uuid in deletedNotes)
+						writer.WriteLine (uuid);
+				}
+
+				writer.Close ();
+				Mono.Unix.Native.Syscall.chmod (manifestFilePath,
+				                                Mono.Unix.Native.FilePermissions.ACCESSPERMS);
+			}
+			
+			File.Delete (lockPath);//TODO: Errors?  When return false?
+			return true;
+		}
+		
+		public virtual int LatestRevision
+		{
+			get
+			{
+				int latestRevDir = -1;
+				foreach (string dir in Directory.GetDirectories (serverPath)) {
+					try {
+						int currentRevDir = int.Parse (Path.GetFileName (dir));// TODO: Do this better!
+						if (currentRevDir > latestRevDir)
+							latestRevDir = currentRevDir;
+					} catch {}
+				}
+				
+				int latestRev = -1;
+				if (latestRevDir > -1) {
+					foreach (string revFile in Directory.GetFiles (Path.Combine (serverPath,
+					                                                                   latestRevDir.ToString ()))) {
+						try {
+							int currentRevFile = int.Parse (Path.GetFileNameWithoutExtension (revFile));
+							if (currentRevFile > latestRev)
+								latestRev = currentRevFile;
+						} catch {}
+					}
+				}
+				
+				return latestRev;
+			}
+		}
+	}
+}
\ No newline at end of file
Index: Tomboy/NoteWindow.cs
===================================================================
--- Tomboy/NoteWindow.cs	(revision 1220)
+++ Tomboy/NoteWindow.cs	(working copy)
@@ -475,9 +475,21 @@
 			// Don't allow deleting the "Start Here" note...
 			if (note.IsSpecial)
 				delete.Sensitive = false;
+ 
+                        Gtk.ImageMenuItem item = 
+                                new Gtk.ImageMenuItem (Catalog.GetString ("Synchronize Notes"));
+                        item.Image = new Gtk.Image (Gtk.Stock.Convert, Gtk.IconSize.Menu);
+                        item.Activated += SyncItemSelcted;
+                        item.Show ();
+                        PluginMenu.Add (item);
 
 			return toolbar;
 		}
+ 
+                void SyncItemSelcted (object sender, EventArgs args)
+                {
+                       SyncManager.PerformSynchronization ();
+                }
 
 		//
 		// Plugin toolbar menu
Index: Tomboy/Preferences.cs
===================================================================
--- Tomboy/Preferences.cs	(revision 1220)
+++ Tomboy/Preferences.cs	(working copy)
@@ -33,6 +33,8 @@
 		public const string EXPORTHTML_EXPORT_LINKED_ALL = "/apps/tomboy/export_html/export_linked_all";
 
 		public const string STICKYNOTEIMPORTER_FIRST_RUN = "/apps/tomboy/sticky_note_importer/sticky_importer_first_run";
+		
+		public const string SYNC_URL = "/apps/tomboy/sync_url"; // TODO: Rename, put in schema, etc
 
 		static GConf.Client client;
 		static GConf.NotifyEventHandler changed_handler;
@@ -104,6 +106,9 @@
 			
 			case ENABLE_STARTUP_NOTES:
 				return true;
+			
+			case SYNC_URL:
+				return "";
 			}
 
 			return null;
Index: Tomboy/Makefile.am
===================================================================
--- Tomboy/Makefile.am	(revision 1220)
+++ Tomboy/Makefile.am	(working copy)
@@ -42,6 +42,7 @@
 	$(srcdir)/AddinManager.cs		\
 	$(srcdir)/Applet.cs 			\
 	$(srcdir)/ApplicationAddin.cs		\
+	$(srcdir)/FileSystemSyncServer.cs	\
 	$(srcdir)/Logger.cs			\
 	$(srcdir)/Note.cs 			\
 	$(srcdir)/NoteAddin.cs			\
@@ -53,10 +54,13 @@
 	$(srcdir)/PluginManager.cs		\
 	$(srcdir)/Preferences.cs		\
 	$(srcdir)/PreferencesDialog.cs		\
-	$(srcdir)/RecentChanges.cs		\
-	$(srcdir)/Tag.cs				\
+	$(srcdir)/RecentChanges.cs		\	
+	$(srcdir)/SyncDialog.cs			\
+	$(srcdir)/SyncManager.cs		\
+	$(srcdir)/Tag.cs			\
 	$(srcdir)/TagButton.cs			\
 	$(srcdir)/TagManager.cs			\
+	$(srcdir)/TomboySyncClient.cs		\
 	$(srcdir)/Tray.cs 			\
 	$(srcdir)/Trie.cs			\
 	$(srcdir)/Undo.cs 			\
Index: Tomboy/Note.cs
===================================================================
--- Tomboy/Note.cs	(revision 1220)
+++ Tomboy/Note.cs	(working copy)
@@ -28,6 +28,7 @@
 		int width, height;
 		int x, y;
 		bool open_on_startup;
+		int revision;
 		
 		Dictionary<string, Tag> tags;
 
@@ -44,6 +45,8 @@
 			
 			create_date = DateTime.MinValue;
 			change_date = DateTime.MinValue;
+			
+			revision = -1;
 		}
 
 		public string Uri
@@ -120,6 +123,12 @@
 			set { open_on_startup = value; }
 		}
 		
+		public int Revision
+		{
+			get { return revision; }
+			set { revision = value; }
+		}
+		
 		public void SetPositionExtent (int x, int y, int width, int height)
 		{
 			Debug.Assert (x >= 0 && y >= 0);
@@ -318,6 +327,7 @@
 			data.Title = title;
 			data.CreateDate = DateTime.Now;
 			data.ChangeDate = data.CreateDate;
+			data.Revision = 0;// TODO: How best?
 			return new Note (data, filepath, manager);
 		}
 
@@ -554,7 +564,7 @@
 				else
 					data.Text = value; 
 			}
-		}
+		}modif
 
 		public string TextContent
 		{
@@ -737,6 +747,17 @@
 			}
 		}
 		
+		public int Revision
+		{
+			get { return Data.Revision; }
+			set {
+				if (Data.Revision != value) {
+					Data.Revision = value;
+					save_needed = true;
+				}
+			}
+		}
+		
 		public List<Tag> Tags
 		{
 			get { return new List<Tag> (data.Data.Tags.Values); }
@@ -849,6 +870,9 @@
 					case "open-on-startup":
 						note.IsOpenOnStartup = bool.Parse (xml.ReadString ());
 						break;
+				case "revision":
+					note.Revision = int.Parse (xml.ReadString ());
+					break;
 					}
 					break;
 				}
@@ -984,6 +1008,10 @@
 			xml.WriteStartElement (null, "open-on-startup", null);
 			xml.WriteString (note.IsOpenOnStartup.ToString ());
 			xml.WriteEndElement ();
+			
+			xml.WriteStartElement (null, "revision", null);
+			xml.WriteString (note.Revision.ToString ());
+			xml.WriteEndElement ();
 
 			xml.WriteEndElement (); // Note
 			xml.WriteEndDocument ();
Index: Tomboy/TomboySyncClient.cs
===================================================================
--- Tomboy/TomboySyncClient.cs	(revision 0)
+++ Tomboy/TomboySyncClient.cs	(revision 0)
@@ -0,0 +1,113 @@
+using System;
+using System.IO;
+using System.Xml;
+using System.Collections.Generic;
+
+namespace Tomboy
+{
+	public class TomboySyncClient : SyncClient
+	{
+		private const string localManifestFileName = "manifest.xml";
+		
+		private DateTime lastSyncDate;
+		private int lastSyncRev;
+		private string localManifestFilePath;
+		
+		public TomboySyncClient ()
+		{			
+			FileSystemWatcher w = new FileSystemWatcher ();
+			w.Path = Tomboy.DefaultNoteManager.NoteDirectoryPath;
+			w.Filter = localManifestFileName;
+			w.Changed += OnChanged;
+			
+			localManifestFilePath =
+				Path.Combine (Tomboy.DefaultNoteManager.NoteDirectoryPath,
+				              localManifestFileName);
+			Parse (localManifestFilePath);
+		}
+				
+		private void OnChanged(object source, FileSystemEventArgs e)
+		{
+			Parse (localManifestFilePath);
+		}
+		
+		private void Parse (string manifestPath)
+		{
+			// blah blah blah, read XML
+			lastSyncDate = DateTime.Today.AddDays (-1);
+			lastSyncRev = 0;
+			
+			if (!File.Exists (manifestPath)) {
+				lastSyncDate = new DateTime (0);
+				lastSyncRev = 0;
+				Write (manifestPath);
+			}
+			
+			StreamReader reader = new StreamReader (manifestPath,
+			                                        System.Text.Encoding.UTF8);
+			XmlTextReader xml = new XmlTextReader (reader);
+			xml.Namespaces = false;
+
+			while (xml.Read ()) {
+				switch (xml.NodeType) {
+				case XmlNodeType.Element:
+					switch (xml.Name) {
+					case "last-sync-rev":
+						lastSyncRev = int.Parse (xml.ReadString ());
+						break;
+					case "last-sync-date":
+						lastSyncDate = XmlConvert.ToDateTime (xml.ReadString (), NoteArchiver.DATE_TIME_FORMAT);
+						break;
+				}
+				break;
+				}
+			}
+			
+			xml.Close ();
+		}
+		
+		private void Write (string manifestPath)
+		{
+			XmlTextWriter xml = new XmlTextWriter (manifestPath, System.Text.Encoding.UTF8);
+			
+			xml.Formatting = Formatting.Indented;
+
+			xml.WriteStartDocument ();
+			xml.WriteStartElement (null, "manifest", "http://beatniksoftware.com/tomboy");
+			
+			xml.WriteStartElement (null, "last-sync-date", null);
+			xml.WriteString (XmlConvert.ToString (lastSyncDate, NoteArchiver.DATE_TIME_FORMAT));
+			xml.WriteEndElement ();
+			
+			xml.WriteStartElement (null, "last-sync-rev", null);
+			xml.WriteString (lastSyncRev.ToString ());
+			xml.WriteEndElement ();
+			
+			xml.WriteEndElement ();
+			
+			xml.Close ();
+		}
+		
+		public virtual DateTime LastSyncDate
+		{
+			get { return lastSyncDate; }
+			set
+			{
+				lastSyncDate = value;
+				Write (localManifestFilePath);
+			}
+		}
+
+		public virtual int LastSynchronizedRevision
+		{
+			get { return lastSyncRev; }
+			set
+			{
+				lastSyncRev = value;
+				Write (localManifestFilePath);
+			}
+		}
+
+		
+	}
+}
\ No newline at end of file
