[gnote] Add core synchronization support



commit 2e46bb3c5cb7593b902cf94a853b4e69989ebfaa
Author: Aurimas Äernius <aurisc4 gmail com>
Date:   Tue Jan 24 23:39:23 2012 +0200

    Add core synchronization support
    
    * Build libgnote as shared library, because some symbols will only
    be used by addins
    * Add core classes for synchronization support
    * Enable GTK+ threads
    * Initialize synchronization from application

 src/Makefile.am                              |   58 +-
 src/addinmanager.cpp                         |   14 +-
 src/addinmanager.hpp                         |    9 +-
 src/applet.cpp                               |    4 +-
 src/gnote.cpp                                |   27 +-
 src/gnote.hpp                                |    9 +-
 src/main.cpp                                 |    5 +
 src/synchronization/filesystemsyncserver.cpp |  616 +++++++++++++++++++
 src/synchronization/filesystemsyncserver.hpp |   78 +++
 src/synchronization/gnotesyncclient.cpp      |   97 +++
 src/synchronization/gnotesyncclient.hpp      |   68 ++
 src/synchronization/silentui.cpp             |  112 ++++
 src/synchronization/silentui.hpp             |   56 ++
 src/synchronization/syncdialog.cpp           |  799 ++++++++++++++++++++++++
 src/synchronization/syncdialog.hpp           |   94 +++
 src/synchronization/syncmanager.cpp          |  839 ++++++++++++++++++++++++++
 src/synchronization/syncmanager.hpp          |  158 +++++
 src/synchronization/syncserviceaddin.cpp     |   29 +
 src/synchronization/syncserviceaddin.hpp     |   64 ++
 src/synchronization/syncui.cpp               |  107 ++++
 src/synchronization/syncui.hpp               |   71 +++
 src/synchronization/syncutils.cpp            |  103 ++++
 src/synchronization/syncutils.hpp            |   86 +++
 23 files changed, 3460 insertions(+), 43 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index b375c7a..203010f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -16,14 +16,14 @@ AM_CPPFLAGS= LIBGTKMM_CFLAGS@ @LIBGLIBMM_CFLAGS@ \
 
 AM_LDFLAGS=-export-dynamic
 
-GNOTE_LIBS = libgnote.a $(top_builddir)/libtomboy/libtomboy.la \
+GNOTE_LIBS = libgnote.la $(top_builddir)/libtomboy/libtomboy.la \
 	@LIBGLIBMM_LIBS@ @LIBGTKMM_LIBS@ \
 	@LIBXSLT_LIBS@ \
 	@PCRE_LIBS@ \
 	@GTKSPELL_LIBS@ @GTK_LIBS@ \
 	@UUID_LIBS@
 
-noinst_LIBRARIES = libgnote.a
+lib_LTLIBRARIES = libgnote.la
 bin_PROGRAMS = gnote
 check_PROGRAMS = trietest stringtest notetest dttest uritest filestest \
 	fileinfotest xmlreadertest
@@ -31,40 +31,30 @@ TESTS = trietest stringtest notetest dttest uritest filestest \
 	fileinfotest xmlreadertest
 
 
-trietest_SOURCES = test/trietest.cpp \
-	sharp/string.cpp debug.cpp
-trietest_LDADD =  @PCRE_LIBS@ @LIBGLIBMM_LIBS@
+trietest_SOURCES = test/trietest.cpp
+trietest_LDADD = libgnote.la @PCRE_LIBS@ @LIBGLIBMM_LIBS@
 
-dttest_SOURCES = test/dttest.cpp \
-	sharp/datetime.cpp debug.cpp
-dttest_LDADD = @LIBGLIBMM_LIBS@
+dttest_SOURCES = test/dttest.cpp
+dttest_LDADD = libgnote.la @LIBGLIBMM_LIBS@
 
-stringtest_SOURCES = test/stringtest.cpp \
-	sharp/string.cpp debug.cpp
-stringtest_LDADD =  @PCRE_LIBS@ @LIBGLIBMM_LIBS@
+stringtest_SOURCES = test/stringtest.cpp
+stringtest_LDADD = libgnote.la @PCRE_LIBS@ @LIBGLIBMM_LIBS@
 
-filestest_SOURCES = test/filestest.cpp \
-	sharp/files.cpp
-filestest_LDADD = @LIBGLIBMM_LIBS@ -lgiomm-2.4
+filestest_SOURCES = test/filestest.cpp
+filestest_LDADD = libgnote.la @LIBGLIBMM_LIBS@ -lgiomm-2.4
 
-fileinfotest_SOURCES = test/fileinfotest.cpp \
-	sharp/fileinfo.cpp \
-	sharp/datetime.cpp
-fileinfotest_LDADD = @LIBGLIBMM_LIBS@ -lgiomm-2.4
+fileinfotest_SOURCES = test/fileinfotest.cpp
+fileinfotest_LDADD = libgnote.la @LIBGLIBMM_LIBS@ -lgiomm-2.4
 
-uritest_SOURCES = test/uritest.cpp \
-	sharp/string.cpp  sharp/uri.cpp debug.cpp
-uritest_LDADD =  @PCRE_LIBS@ @LIBGLIBMM_LIBS@
+uritest_SOURCES = test/uritest.cpp
+uritest_LDADD = libgnote.la @PCRE_LIBS@ @LIBGLIBMM_LIBS@
 
-xmlreadertest_SOURCES = test/xmlreadertest.cpp \
-	sharp/xmlreader.cpp debug.cpp
-xmlreadertest_LDADD = @LIBXML_LIBS@
+xmlreadertest_SOURCES = test/xmlreadertest.cpp
+xmlreadertest_LDADD = libgnote.la @LIBXML_LIBS@
 
 notetest_SOURCES = test/notetest.cpp
 notetest_LDADD =  $(GNOTE_LIBS) -lX11
 
-gnote_SOURCES = main.cpp
-
 
 if HAVE_PANELAPPLET
 APPLET_SOURCES=applet.hpp applet.cpp
@@ -89,7 +79,7 @@ DBUS_SOURCES=remotecontrolproxy.hpp remotecontrolproxy.cpp \
 	dbus/remotecontrol-glue.cpp \
 	$(NULL)
 
-libgnote_a_SOURCES = \
+libgnote_la_SOURCES = \
 	base/singleton.hpp \
 	base/macros.hpp \
 	base/inifile.hpp base/inifile.cpp \
@@ -107,6 +97,7 @@ libgnote_a_SOURCES = \
 	sharp/streamreader.hpp sharp/streamreader.cpp \
 	sharp/streamwriter.hpp sharp/streamwriter.cpp \
 	sharp/string.hpp sharp/string.cpp \
+	sharp/timespan.hpp sharp/timespan.cpp \
 	sharp/uri.hpp sharp/uri.cpp \
 	sharp/uuid.hpp \
 	sharp/xml.hpp sharp/xml.cpp \
@@ -158,6 +149,19 @@ libgnote_a_SOURCES = \
 	notebooks/notebooknewnotemenuitem.hpp notebooks/notebooknewnotemenuitem.cpp \
 	notebooks/notebooknoteaddin.hpp notebooks/notebooknoteaddin.cpp \
 	notebooks/notebookstreeview.hpp notebooks/notebookstreeview.cpp \
+	synchronization/filesystemsyncserver.hpp synchronization/filesystemsyncserver.cpp \
+	synchronization/gnotesyncclient.hpp synchronization/gnotesyncclient.cpp \
+	synchronization/silentui.hpp synchronization/silentui.cpp \
+	synchronization/syncdialog.hpp synchronization/syncdialog.cpp \
+	synchronization/syncmanager.hpp synchronization/syncmanager.cpp \
+	synchronization/syncui.hpp synchronization/syncui.cpp \
+        synchronization/syncutils.hpp synchronization/syncutils.cpp \
+	synchronization/syncserviceaddin.hpp synchronization/syncserviceaddin.cpp \
+	$(NULL)
+
+
+gnote_SOURCES = \
+	main.cpp \
 	$(DBUS_SOURCES)   \
 	$(APPLET_SOURCES) \
 	$(NULL)
diff --git a/src/addinmanager.cpp b/src/addinmanager.cpp
index f437444..3b1cbe1 100644
--- a/src/addinmanager.cpp
+++ b/src/addinmanager.cpp
@@ -1,7 +1,7 @@
 /*
  * gnote
  *
- * Copyright (C) 2010-2011 Aurimas Cernius
+ * Copyright (C) 2010-2012 Aurimas Cernius
  * Copyright (C) 2009, 2010 Debarshi Ray
  * Copyright (C) 2009 Hubert Figuiere
  *
@@ -38,6 +38,7 @@
 #include "watchers.hpp"
 #include "notebooks/notebookapplicationaddin.hpp"
 #include "notebooks/notebooknoteaddin.hpp"
+#include "synchronization/syncserviceaddin.hpp"
 
 
 #if 1
@@ -258,6 +259,11 @@ namespace gnote {
         ApplicationAddin * addin = dynamic_cast<ApplicationAddin*>((*f)());
         m_app_addins.insert(std::make_pair(dmod->id(), addin));
       }
+      f = dmod->query_interface(sync::SyncServiceAddin::IFACE_NAME);
+      if(f) {
+        sync::SyncServiceAddin * addin = dynamic_cast<sync::SyncServiceAddin*>((*f)());
+        m_sync_service_addins.insert(std::make_pair(dmod->id(), addin));
+      }
     }
   }
 
@@ -312,6 +318,12 @@ namespace gnote {
   }
 
 
+  void AddinManager::get_sync_service_addins(std::list<sync::SyncServiceAddin *> &l) const
+  {
+    sharp::map_get_values(m_sync_service_addins, l);
+  }
+
+
   void AddinManager::get_import_addins(std::list<ImportAddin*> & l) const
   {
     sharp::map_get_values(m_import_addins, l);
diff --git a/src/addinmanager.hpp b/src/addinmanager.hpp
index d6c1604..cb8d059 100644
--- a/src/addinmanager.hpp
+++ b/src/addinmanager.hpp
@@ -1,7 +1,7 @@
 /*
  * gnote
  *
- * Copyright (C) 2010 Aurimas Cernius
+ * Copyright (C) 2010,2012 Aurimas Cernius
  * Copyright (C) 2009 Debarshi Ray
  * Copyright (C) 2009 Hubert Figuiere
  *
@@ -41,6 +41,10 @@ class ApplicationAddin;
 class PreferenceTabAddin;
 class AddinPreferenceFactoryBase;
 
+namespace sync {
+class SyncServiceAddin;
+}
+
 
 class AddinManager
 {
@@ -60,6 +64,7 @@ public:
   ApplicationAddin * get_application_addin(const std::string & id)
                                            const;
   void get_preference_tab_addins(std::list<PreferenceTabAddin *> &) const;
+  void get_sync_service_addins(std::list<sync::SyncServiceAddin *> &) const;
   void get_import_addins(std::list<ImportAddin*> &) const;
   void initialize_application_addins() const;
   void shutdown_application_addins() const;
@@ -96,6 +101,8 @@ private:
   IdInfoMap                                m_note_addin_infos;
   typedef std::map<std::string, PreferenceTabAddin*> IdPrefTabAddinMap;
   IdPrefTabAddinMap                        m_pref_tab_addins;
+  typedef std::map<std::string, sync::SyncServiceAddin*> IdSyncServiceAddinMap;
+  IdSyncServiceAddinMap                    m_sync_service_addins;
   typedef std::map<std::string, ImportAddin *> IdImportAddinMap;
   IdImportAddinMap                         m_import_addins;
   typedef std::map<std::string, AddinPreferenceFactoryBase*> IdAddinPrefsMap;
diff --git a/src/applet.cpp b/src/applet.cpp
index 4e00af1..1ce54c1 100644
--- a/src/applet.cpp
+++ b/src/applet.cpp
@@ -1,7 +1,7 @@
 /*
  * gnote
  *
- * Copyright (C) 2011 Aurimas Cernius
+ * Copyright (C) 2011-2012 Aurimas Cernius
  * Copyright (C) 2009 Hubert Figuiere
  *
  * This program is free software: you can redistribute it and/or modify
@@ -427,8 +427,10 @@ int register_applet()
   NoteManager &manager = Gnote::obj().default_note_manager();
   GnotePanelAppletEventBox applet_event_box(manager);
   GnotePrefsKeybinder key_binder(manager, applet_event_box);
+  gdk_threads_enter();
   int returncode = panel_applet_factory_main(FACTORY_IID, PANEL_TYPE_APPLET,
                                              gnote_applet_fill, &applet_event_box);
+  gdk_threads_leave();
   return returncode;
 }
 
diff --git a/src/gnote.cpp b/src/gnote.cpp
index 8d424d0..1b40617 100644
--- a/src/gnote.cpp
+++ b/src/gnote.cpp
@@ -1,7 +1,7 @@
 /*
  * gnote
  *
- * Copyright (C) 2010-2011 Aurimas Cernius
+ * Copyright (C) 2010-2012 Aurimas Cernius
  * Copyright (C) 2010 Debarshi Ray
  * Copyright (C) 2009 Hubert Figuiere
  *
@@ -55,6 +55,7 @@
 #include "dbus/remotecontrolclient.hpp"
 #include "sharp/streamreader.hpp"
 #include "sharp/files.hpp"
+#include "synchronization/syncmanager.hpp"
 
 #if HAVE_PANELAPPLET
 #include "applet.hpp"
@@ -158,7 +159,9 @@ namespace gnote {
     }
     else {
       m_app = gnote_app_new();
+      gdk_threads_enter();
       g_application_run(G_APPLICATION(m_app), argc, argv);
+      gdk_threads_leave();
       g_object_unref(m_app);
       m_app = NULL;
     }
@@ -201,11 +204,8 @@ namespace gnote {
     std::string note_path = get_note_path(cmd_line.note_path());
     m_manager = new NoteManager(note_path, sigc::mem_fun(*this, &Gnote::start_note_created));
     m_keybinder = new XKeybinder();
-
-    // TODO
-    // SyncManager::init()
-
     ActionManager::obj().load_interface();
+    sync::SyncManager::init();
     setup_global_actions();
     m_manager->get_addin_manager().initialize_application_addins();
   }
@@ -472,15 +472,18 @@ namespace gnote {
 
   void Gnote::open_note_sync_window()
   {
-#if 0
-    // TODO
-    if (sync_dlg == null) {
-      sync_dlg = new SyncDialog ();
-      sync_dlg.Response += OnSyncDialogResponse;
+    if(m_sync_dlg == 0) {
+      m_sync_dlg = sync::SyncDialog::create();
+      m_sync_dlg->signal_response().connect(sigc::mem_fun(*this, &Gnote::on_sync_dialog_response));
     }
 
-    sync_dlg.Present 
-#endif
+    m_sync_dlg->present();
+  }
+
+
+  void Gnote::on_sync_dialog_response(int)
+  {
+    m_sync_dlg.reset();
   }
 
 
diff --git a/src/gnote.hpp b/src/gnote.hpp
index cecaea6..54dbd84 100644
--- a/src/gnote.hpp
+++ b/src/gnote.hpp
@@ -1,7 +1,7 @@
 /*
  * gnote
  *
- * Copyright (C) 2010-2011 Aurimas Cernius
+ * Copyright (C) 2010-2012 Aurimas Cernius
  * Copyright (C) 2009 Hubert Figuiere
  *
  * This program is free software: you can redistribute it and/or modify
@@ -36,6 +36,7 @@
 #include "keybinder.hpp"
 #include "remotecontrolproxy.hpp"
 #include "tray.hpp"
+#include "synchronization/syncdialog.hpp"
 
 namespace gnote {
 
@@ -162,12 +163,17 @@ public:
   sigc::signal<void> signal_quit;
   static void register_remote_control(NoteManager & manager, RemoteControlProxy::slot_name_acquire_finish on_finish);
   static void register_object();
+  sync::SyncDialog::Ptr sync_dialog()
+    {
+      return m_sync_dlg;
+    }
 private:
   void start_note_created(const Note::Ptr & start_note);
   std::string get_note_path(const std::string & override_path);
   void on_setting_changed(const Glib::ustring & key);
   void common_init();
   void end_main(bool bus_aquired, bool name_acquired);
+  void on_sync_dialog_response(int response_id);
 
   NoteManager *m_manager;
   IKeybinder  *m_keybinder;
@@ -179,6 +185,7 @@ private:
   PreferencesDialog *m_prefsdlg;
   GnoteCommandLine cmd_line;
   GnoteApp *m_app;
+  sync::SyncDialog::Ptr m_sync_dlg;
 };
 
 
diff --git a/src/main.cpp b/src/main.cpp
index 5f3d85a..e9ec7eb 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,6 +1,7 @@
 /*
  * gnote
  *
+ * Copyright (C) 2012 Aurimas Cernius
  * Copyright (C) 2009 Hubert Figuiere
  *
  * This program is free software: you can redistribute it and/or modify
@@ -34,6 +35,10 @@ int main(int argc, char **argv)
   bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
   textdomain(GETTEXT_PACKAGE);
 
+  if(!g_thread_supported()) {
+    g_thread_init(NULL);
+  }
+  gdk_threads_init();
   Gtk::Main kit(argc, argv);
   gnote::Gnote *app = &gnote::Gnote::obj();
   int retval = app->main(argc, argv);
diff --git a/src/synchronization/filesystemsyncserver.cpp b/src/synchronization/filesystemsyncserver.cpp
new file mode 100644
index 0000000..84a1754
--- /dev/null
+++ b/src/synchronization/filesystemsyncserver.cpp
@@ -0,0 +1,616 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2012 Aurimas Cernius
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <algorithm>
+#include <fstream>
+#include <stdexcept>
+
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include "debug.hpp"
+#include "filesystemsyncserver.hpp"
+#include "sharp/directory.hpp"
+#include "sharp/files.hpp"
+#include "sharp/uuid.hpp"
+#include "sharp/xml.hpp"
+#include "sharp/xmlwriter.hpp"
+
+
+#define XML_NODE_CONTENT(node) reinterpret_cast<char*>(XML_GET_CONTENT(xmlFirstElementChild(node)))
+
+
+namespace gnote {
+namespace sync {
+
+SyncServer::Ptr FileSystemSyncServer::create(const std::string & path)
+{
+  return SyncServer::Ptr(new FileSystemSyncServer(path));
+}
+
+
+FileSystemSyncServer::FileSystemSyncServer(const std::string & localSyncPath)
+  : m_server_path(localSyncPath)
+  , m_cache_path("/tmp/gnote")
+{
+  if(!sharp::directory_exists(m_server_path)) {
+    throw std::invalid_argument(("Directory not found: " + m_server_path).c_str());
+  }
+
+  m_lock_path = Glib::build_filename(m_server_path, "lock");
+  m_manifest_path = Glib::build_filename(m_server_path, "manifest.xml");
+
+  m_new_revision = latest_revision() + 1;
+  m_new_revision_path = get_revision_dir_path(m_new_revision);
+}
+
+
+void FileSystemSyncServer::upload_notes(const std::list<Note::Ptr> & notes)
+{
+  if(sharp::directory_exists(m_new_revision_path) == false) {
+    sharp::directory_create(m_new_revision_path);
+  }
+  DBG_OUT("UploadNotes: notes.Count = %d", notes.size());
+  for(std::list<Note::Ptr>::const_iterator iter = notes.begin(); iter != notes.end(); ++iter) {
+    try {
+      std::string serverNotePath = Glib::build_filename(m_new_revision_path, sharp::file_filename((*iter)->file_path()));
+      sharp::file_copy((*iter)->file_path(), serverNotePath);
+      m_updated_notes.push_back(sharp::file_basename((*iter)->file_path()));
+    }
+    catch(...) {
+      DBG_OUT("Sync: Error uploading note \"%s\"", (*iter)->get_title().c_str());
+    }
+  }
+}
+
+
+void FileSystemSyncServer::delete_notes(const std::list<std::string> & deletedNoteUUIDs)
+{
+  m_deleted_notes.insert(m_deleted_notes.end(), deletedNoteUUIDs.begin(), deletedNoteUUIDs.end());
+}
+
+
+std::list<std::string> FileSystemSyncServer::get_all_note_uuids()
+{
+  std::list<std::string> noteUUIDs;
+
+  if(is_valid_xml_file(m_manifest_path)) {
+    // TODO: Permission errors
+    xmlDocPtr xml_doc = xmlReadFile(m_manifest_path.c_str(), "UTF-8", 0);
+    xmlNodePtr root_node = xmlDocGetRootElement(xml_doc);
+    sharp::XmlNodeSet noteIds = sharp::xml_node_xpath_find(root_node, "//note/@id");
+    DBG_OUT("get_all_note_uuids has %d notes", noteIds.size());
+    for(sharp::XmlNodeSet::iterator iter = noteIds.begin(); iter != noteIds.end(); ++iter) {
+      noteUUIDs.push_back(reinterpret_cast<char*>(XML_NODE_CONTENT(xmlFirstElementChild(*iter))));
+    }
+    xmlFreeDoc(xml_doc);
+  }
+
+  return noteUUIDs;
+}
+
+
+bool FileSystemSyncServer::updates_available_since(int revision)
+{
+  return latest_revision() > revision; // TODO: Mounting, etc?
+}
+
+
+std::map<std::string, NoteUpdate> FileSystemSyncServer::get_note_updates_since(int revision)
+{
+  std::map<std::string, NoteUpdate> noteUpdates;
+
+  std::string tempPath = Glib::build_filename(m_cache_path, "sync_temp");
+  if(!sharp::directory_exists(tempPath)) {
+    sharp::directory_create(tempPath);
+  }
+  else {
+    // Empty the temp dir
+    try {
+      std::list<std::string> files;
+      sharp::directory_get_files(tempPath, files);
+      for(std::list<std::string>::iterator iter = files.begin(); iter != files.end(); ++iter) {
+        sharp::file_delete(*iter);
+      }
+    }
+    catch(...) {}
+  }
+
+  if(is_valid_xml_file(m_manifest_path)) {
+    xmlDocPtr xml_doc = xmlReadFile(m_manifest_path.c_str(), "UTF-8", 0);
+    xmlNodePtr root_node = xmlDocGetRootElement(xml_doc);
+
+    std::string xpath = str(boost::format("//note[ rev > %1%]") % revision);
+    sharp::XmlNodeSet noteNodes = sharp::xml_node_xpath_find(root_node, xpath.c_str());
+    DBG_OUT("get_note_updates_since xpath returned %d nodes", noteNodes.size());
+    for(sharp::XmlNodeSet::iterator iter = noteNodes.begin(); iter != noteNodes.end(); ++iter) {
+      std::string note_id = XML_NODE_CONTENT(sharp::xml_node_xpath_find_single_node(*iter, "@id"));
+      int rev = boost::lexical_cast<int>(XML_NODE_CONTENT(sharp::xml_node_xpath_find_single_node(*iter, "@rev")));
+      if(noteUpdates.find(note_id) == noteUpdates.end()) {
+        // Copy the file from the server to the temp directory
+        std::string revDir = get_revision_dir_path(rev);
+        std::string serverNotePath = Glib::build_filename(revDir, note_id + ".note");
+        std::string noteTempPath = Glib::build_filename(tempPath, note_id + ".note");
+        sharp::file_copy(serverNotePath, noteTempPath);
+
+        // Get the title, contents, etc.
+        std::string noteTitle;
+        std::string noteXml;
+        std::ifstream fin(noteTempPath.c_str());
+        if(fin.is_open()) {
+          do {
+            std::string line;
+            std::getline(fin, line);
+            if(!fin.eof()) {
+              noteXml += line + "\n";
+            }
+          }
+          while(!fin.eof());
+          fin.close();
+        }
+        NoteUpdate update(noteXml, noteTitle, note_id, rev);
+        noteUpdates.insert(std::make_pair(note_id, update));
+      }
+    }
+    xmlFreeDoc(xml_doc);
+  }
+
+  DBG_OUT("get_note_updates_since (%d) returning: %d", revision, noteUpdates.size());
+  return noteUpdates;
+}
+
+
+bool FileSystemSyncServer::begin_sync_transaction()
+{
+  // Lock expiration: If a lock file exists on the server, a client
+  // will never be able to synchronize on its first attempt.  The
+  // client should record the time elapsed
+  if(sharp::file_exists(m_lock_path)) {
+    SyncLockInfo currentSyncLock = current_sync_lock();
+    if(m_initial_sync_attempt == sharp::DateTime()) {
+      DBG_OUT("Sync: Discovered a sync lock file, wait at least %s before trying again.", currentSyncLock.duration.string().c_str());
+      // This is our initial attempt to sync and we've detected
+      // a sync file, so we're gonna have to wait.
+      m_initial_sync_attempt = sharp::DateTime::now();
+      m_last_sync_lock_hash = currentSyncLock.hash_string();
+      return false;
+    }
+    else if(m_last_sync_lock_hash != currentSyncLock.hash_string()) {
+      DBG_OUT("Sync: Updated sync lock file discovered, wait at least %s before trying again.", currentSyncLock.duration.string().c_str());
+      // The sync lock has been updated and is still a valid lock
+      m_initial_sync_attempt = sharp::DateTime::now();
+      m_last_sync_lock_hash = currentSyncLock.hash_string();
+      return false;
+    }
+    else {
+      if(m_last_sync_lock_hash == currentSyncLock.hash_string()) {
+        // The sync lock has is the same so check to see if the
+        // duration of the lock has expired.  If it hasn't, wait
+        // even longer.
+        if(sharp::DateTime::now() - currentSyncLock.duration < m_initial_sync_attempt) {
+          DBG_OUT("Sync: You haven't waited long enough for the sync file to expire.");
+          return false;
+        }
+      }
+
+      // Cleanup Old Sync Lock!
+      cleanup_old_sync(currentSyncLock);
+    }
+  }
+
+  // Reset the initialSyncAttempt
+  m_initial_sync_attempt = sharp::DateTime();
+  m_last_sync_lock_hash = "";
+
+  // Create a new lock file so other clients know another client is
+  // actively synchronizing right now.
+  m_sync_lock.renew_count = 0;
+  m_sync_lock.revision = m_new_revision;
+  update_lock_file(m_sync_lock);
+  // TODO: Verify that the lockTimeout is actually working or figure
+  // out some other way to automatically update the lock file.
+  // Reset the timer to 20 seconds sooner than the sync lock duration
+  m_lock_timeout = Glib::TimeoutSource::create(m_sync_lock.duration.total_milliseconds() - 20000);
+
+  m_updated_notes.clear();
+  m_deleted_notes.clear();
+
+  return true;
+}
+
+
+bool FileSystemSyncServer::commit_sync_transaction()
+{
+  bool commitSucceeded = false;
+
+  if(m_updated_notes.size() > 0 || m_deleted_notes.size() > 0) {
+    // TODO: error-checking, etc
+    std::string manifestFilePath = Glib::build_filename(m_new_revision_path, "manifest.xml");
+    if(!sharp::directory_exists(m_new_revision_path)) {
+      sharp::directory_create(m_new_revision_path);
+    }
+
+    sharp::XmlNodeSet noteNodes;
+    xmlDocPtr xml_doc = NULL;
+    if(is_valid_xml_file(m_manifest_path) == true) {
+      xml_doc = xmlReadFile(manifestFilePath.c_str(), "UTF-8", 0);
+      xmlNodePtr root_node = xmlDocGetRootElement(xml_doc);
+      noteNodes = sharp::xml_node_xpath_find(root_node, "//note");
+    }
+
+    // Write out the new manifest file
+    sharp::XmlWriter xml(manifestFilePath);
+    try {
+      xml.write_start_document();
+      xml.write_start_element("", "sync", "");
+      xml.write_attribute_string("", "revision", "", boost::lexical_cast<std::string>(m_new_revision));
+      xml.write_attribute_string("", "server-id", "", m_server_id);
+
+      for(sharp::XmlNodeSet::iterator iter = noteNodes.begin(); iter != noteNodes.end(); ++iter) {
+        std::string note_id = sharp::xml_node_xpath_find_single(*iter, "@id");
+        std::string rev = sharp::xml_node_xpath_find_single(*iter, "@rev");
+
+        // Don't write out deleted notes
+        if(std::find(m_deleted_notes.begin(), m_deleted_notes.end(), note_id) != m_deleted_notes.end()) {
+          continue;
+        }
+
+        // Skip updated notes, we'll update them in a sec
+        if(std::find(m_updated_notes.begin(), m_updated_notes.end(), note_id) != m_updated_notes.end()) {
+          continue;
+        }
+
+        xml.write_start_element("", "note", "");
+        xml.write_attribute_string("", "id", "", note_id);
+        xml.write_attribute_string("", "rev", "", rev);
+        xml.write_end_element();
+      }
+
+      // Write out all the updated notes
+      for(std::list<std::string>::iterator iter = m_updated_notes.begin(); iter != m_updated_notes.end(); ++iter) {
+        xml.write_start_element("", "note", "");
+        xml.write_attribute_string("", "id", "", *iter);
+        xml.write_attribute_string("", "rev", "", boost::lexical_cast<std::string>(m_new_revision));
+        xml.write_end_element();
+      }
+
+      xml.write_end_element();
+      xml.write_end_document();
+      xml.close();
+      xmlFreeDoc(xml_doc);
+    }
+    catch(...) {
+      xml.close();
+      throw;
+    }
+
+
+    // Rename original /manifest.xml to /manifest.xml.old
+    std::string oldManifestPath = m_manifest_path + ".old";
+    if(sharp::file_exists(m_manifest_path) == true) {
+      if(sharp::file_exists(oldManifestPath)) {
+        sharp::file_delete(oldManifestPath);
+      }
+      sharp::file_move(m_manifest_path, oldManifestPath);
+    }
+
+    // * * * Begin Cleanup Code * * *
+    // TODO: Consider completely discarding cleanup code, in favor
+    //       of periodic thorough server consistency checks (say every 30 revs).
+    //       Even if we do continue providing some cleanup, consistency
+    //       checks should be implemented.
+
+    // Copy the /${parent}/${rev}/manifest.xml -> /manifest.xml
+    sharp::file_copy(manifestFilePath, m_manifest_path);
+
+    try {
+      // Delete /manifest.xml.old
+      if(sharp::file_exists(oldManifestPath)) {
+        sharp::file_delete(oldManifestPath);
+      }
+
+      std::string oldManifestFilePath = Glib::build_filename(get_revision_dir_path(m_new_revision - 1), "manifest.xml");
+      if(sharp::file_exists(oldManifestFilePath)) {
+        // TODO: Do step #8 as described in http://bugzilla.gnome.org/show_bug.cgi?id=321037#c17
+        // Like this?
+        std::list<std::string> files;
+        sharp::directory_get_files(oldManifestFilePath, files);
+        for(std::list<std::string>::iterator iter = files.begin(); iter != files.end(); ++iter) {
+          std::string fileGuid = sharp::file_basename(*iter);
+          if(std::find(m_deleted_notes.begin(), m_deleted_notes.end(), fileGuid) != m_deleted_notes.end()
+             || std::find(m_updated_notes.begin(), m_updated_notes.end(), fileGuid) != m_updated_notes.end()) {
+            sharp::file_delete(Glib::build_filename(oldManifestFilePath, *iter));
+          }
+          // TODO: Need to check *all* revision dirs, not just previous (duh)
+          //       Should be a way to cache this from checking earlier.
+        }
+
+        // TODO: Leaving old empty dir for now.  Some stuff is probably easier
+        //       when you can guarantee the existence of each intermediate directory?
+      }
+    }
+    catch(std::exception & e) {
+      ERR_OUT("Exception during server cleanup while committing. Server integrity is OK, but \
+there may be some excess files floating around.  Here's the error:%s\n", e.what());
+    }
+    // * * * End Cleanup Code * * *
+  }
+
+  sharp::file_delete(m_lock_path);// TODO: Errors?
+  commitSucceeded = true;// TODO: When return false?
+  return commitSucceeded;
+}
+
+
+bool FileSystemSyncServer::cancel_sync_transaction()
+{
+  //m_lock_timeout.cancel(); TODO: what to do with this?
+  sharp::file_delete(m_lock_path);
+  return true;
+}
+
+
+int FileSystemSyncServer::latest_revision()
+{
+  int latestRev = -1;
+  int latestRevDir = -1;
+  xmlDocPtr xml_doc = NULL;
+  if(is_valid_xml_file(m_manifest_path) == true) {
+    xml_doc = xmlReadFile(m_manifest_path.c_str(), "UTF-8", 0);
+    xmlNodePtr root_node = xmlDocGetRootElement(xml_doc);
+    xmlNodePtr syncNode = sharp::xml_node_xpath_find_single_node(root_node, "//sync");
+    std::string latestRevStr = sharp::xml_node_get_attribute(syncNode, "revision");
+    if(latestRevStr != "") {
+      latestRev = boost::lexical_cast<int>(latestRevStr);
+    }
+  }
+
+  bool foundValidManifest = false;
+  while (!foundValidManifest) {
+    if(latestRev < 0) {
+      // Look for the highest revision parent path
+      std::list<std::string> directories;
+      sharp::directory_get_directories(m_server_path, directories);
+      for(std::list<std::string>::iterator iter = directories.begin(); iter != directories.end(); ++iter) {
+        try {
+          int currentRevParentDir = boost::lexical_cast<int>(sharp::file_filename(*iter));
+          if(currentRevParentDir > latestRevDir) {
+            latestRevDir = currentRevParentDir;
+          }
+        }
+        catch(...)
+        {}
+      }
+
+      if(latestRevDir >= 0) {
+        directories.clear();
+        sharp::directory_get_directories(
+          Glib::build_filename(m_server_path, boost::lexical_cast<std::string>(latestRevDir)),
+          directories);
+        for(std::list<std::string>::iterator iter = directories.begin(); iter != directories.end(); ++iter) {
+          try {
+            int currentRev = boost::lexical_cast<int>(*iter);
+            if(currentRev > latestRev) {
+              latestRev = currentRev;
+            }
+          }
+          catch(...)
+          {}
+        }
+      }
+
+      if(latestRev >= 0) {
+        // Validate that the manifest file inside the revision is valid
+        // TODO: Should we create the /manifest.xml file with a valid one?
+        std::string revDirPath = get_revision_dir_path(latestRev);
+        std::string revManifestPath = Glib::build_filename(revDirPath, "manifest.xml");
+        if(is_valid_xml_file(revManifestPath)) {
+          foundValidManifest = true;
+        }
+        else {
+          // TODO: Does this really belong here?
+          sharp::directory_delete(revDirPath, true);
+          // Continue looping
+        }
+      }
+      else {
+        foundValidManifest = true;
+      }
+    }
+    else {
+      foundValidManifest = true;
+    }
+  }
+
+  xmlFreeDoc(xml_doc);
+  return latestRev;
+}
+
+
+SyncLockInfo FileSystemSyncServer::current_sync_lock()
+{
+  SyncLockInfo syncLockInfo;
+
+  if(is_valid_xml_file(m_lock_path)) {
+    xmlDocPtr xml_doc = xmlReadFile(m_lock_path.c_str(), "UTF-8", 0);
+    xmlNodePtr root_node = xmlDocGetRootElement(xml_doc);
+
+    xmlNodePtr node = sharp::xml_node_xpath_find_single_node(root_node, "//transaction-id/text ()");
+    if(node != NULL) {
+      std::string transaction_id_txt = XML_NODE_CONTENT(node);
+      syncLockInfo.transaction_id = transaction_id_txt;
+    }
+
+    node = sharp::xml_node_xpath_find_single_node(root_node, "//client-id/text ()");
+    if(node != NULL) {
+      std::string client_id_txt = XML_NODE_CONTENT(node);
+      syncLockInfo.client_id = client_id_txt;
+    }
+
+    node = sharp::xml_node_xpath_find_single_node(root_node, "renew-count/text ()");
+    if(node != NULL) {
+      std::string renew_txt = XML_NODE_CONTENT(node);
+      syncLockInfo.renew_count = boost::lexical_cast<int>(renew_txt);
+    }
+
+    node = sharp::xml_node_xpath_find_single_node(root_node, "lock-expiration-duration/text ()");
+    if(node != NULL) {
+      std::string span_txt = XML_NODE_CONTENT(node);
+      syncLockInfo.duration = sharp::TimeSpan::parse(span_txt);
+    }
+
+    node = sharp::xml_node_xpath_find_single_node(root_node, "revision/text ()");
+    if(node != NULL) {
+      std::string revision_txt = XML_NODE_CONTENT(node);
+      syncLockInfo.revision = boost::lexical_cast<int>(revision_txt);
+    }
+
+    xmlFreeDoc(xml_doc);
+  }
+
+  return syncLockInfo;
+}
+
+
+std::string FileSystemSyncServer::id()
+{
+  m_server_id = "";
+
+  // Attempt to read from manifest file first
+  if(is_valid_xml_file(m_manifest_path)) {
+    xmlDocPtr xml_doc = xmlReadFile(m_lock_path.c_str(), "UTF-8", 0);
+    xmlNodePtr root_node = xmlDocGetRootElement(xml_doc);
+
+    xmlNodePtr syncNode = sharp::xml_node_xpath_find_single_node(root_node, "//sync");
+    m_server_id = sharp::xml_node_get_attribute(syncNode, "server-id");
+
+    xmlFreeDoc(xml_doc);
+  }
+
+  // Generate a new ID if there isn't already one
+  if(m_server_id == "") {
+    m_server_id = sharp::uuid().string();
+  }
+
+  return m_server_id;
+}
+
+
+std::string FileSystemSyncServer::get_revision_dir_path(int rev)
+{
+  return Glib::build_filename(m_server_path,
+                              boost::lexical_cast<std::string>(rev/100),
+                              boost::lexical_cast<std::string>(rev));
+}
+
+
+void FileSystemSyncServer::update_lock_file(const SyncLockInfo & syncLockInfo)
+{
+  sharp::XmlWriter xml(m_lock_path);
+  try {
+    xml.write_start_document();
+    xml.write_start_element("", "lock", "");
+
+    xml.write_start_element("", "transaction-id", "");
+    xml.write_string(syncLockInfo.transaction_id);
+    xml.write_end_element();
+
+    xml.write_start_element("", "client-id", "");
+    xml.write_string(syncLockInfo.client_id);
+    xml.write_end_element();
+
+    xml.write_start_element("", "renew-count", "");
+    xml.write_string(boost::lexical_cast<std::string>(syncLockInfo.renew_count));
+    xml.write_end_element();
+
+    xml.write_start_element("", "lock-expiration-duration", "");
+    xml.write_string(syncLockInfo.duration.string());
+    xml.write_end_element();
+
+    xml.write_start_element("", "revision", "");
+    xml.write_string(boost::lexical_cast<std::string>(syncLockInfo.revision));
+    xml.write_end_element();
+
+    xml.write_end_element();
+    xml.write_end_document();
+
+    xml.close();
+  }
+  catch(...) {
+    xml.close();
+    throw;
+  }
+}
+
+
+void FileSystemSyncServer::cleanup_old_sync(const SyncLockInfo &)
+{
+  DBG_OUT("Sync: Cleaning up a previous failed sync transaction");
+  int rev = latest_revision();
+  if(rev >= 0 && !is_valid_xml_file(m_manifest_path)) {
+    // Time to discover the latest valid revision
+    // If no manifest.xml file exists, that means we've got to
+    // figure out if there are any previous revisions with valid
+    // manifest.xml files around.
+    for (; rev >= 0; rev--) {
+      std::string revParentPath = get_revision_dir_path(rev);
+      std::string manPath = Glib::build_filename(revParentPath, "manifest.xml");
+
+      if(is_valid_xml_file(manPath) == false) {
+        continue;
+      }
+
+      // Restore a valid manifest path
+      sharp::file_copy(manPath, m_manifest_path);
+      break;
+    }
+  }
+
+  // Delete the old lock file
+  DBG_OUT("Sync: Deleting expired lockfile");
+  try {
+    sharp::file_delete(m_lock_path);
+  }
+  catch(std::exception & e) {
+    ERR_OUT("Error deleting the old sync lock \"%s\": %s", m_lock_path.c_str(), e.what());
+  }
+}
+
+
+bool FileSystemSyncServer::is_valid_xml_file(const std::string & xmlFilePath)
+{
+  // Check that file exists
+  if(!sharp::file_exists(xmlFilePath)) {
+    return false;
+  }
+
+  // TODO: Permissions errors
+  // Attempt to load the file and parse it as XML
+  xmlDocPtr xml_doc = xmlReadFile(xmlFilePath.c_str(), "UTF-8", 0);
+  if(!xml_doc) {
+    return false;
+  }
+  xmlFreeDoc(xml_doc);
+  return true;
+}
+
+
+}
+}
diff --git a/src/synchronization/filesystemsyncserver.hpp b/src/synchronization/filesystemsyncserver.hpp
new file mode 100644
index 0000000..63dcb84
--- /dev/null
+++ b/src/synchronization/filesystemsyncserver.hpp
@@ -0,0 +1,78 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2012 Aurimas Cernius
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef _SYNCHRONIZATION_FILESYSTEMSYNCSERVER_HPP_
+#define _SYNCHRONIZATION_FILESYSTEMSYNCSERVER_HPP_
+
+#include "syncmanager.hpp"
+#include "sharp/datetime.hpp"
+
+
+namespace gnote {
+namespace sync {
+
+
+class FileSystemSyncServer
+  : public SyncServer
+{
+public:
+  static SyncServer::Ptr create(const std::string & path);
+  virtual bool begin_sync_transaction();
+  virtual bool commit_sync_transaction();
+  virtual bool cancel_sync_transaction();
+  virtual std::list<std::string> get_all_note_uuids();
+  virtual std::map<std::string, NoteUpdate> get_note_updates_since(int revision);
+  virtual void delete_notes(const std::list<std::string> & deletedNoteUUIDs);
+  virtual void upload_notes(const std::list<Note::Ptr> & notes);
+  virtual int latest_revision(); // NOTE: Only reliable during a transaction
+  virtual SyncLockInfo current_sync_lock();
+  virtual std::string id();
+  virtual bool updates_available_since(int revision);
+private:
+  explicit FileSystemSyncServer(const std::string & path);
+
+  std::string get_revision_dir_path(int rev);
+  void cleanup_old_sync(const SyncLockInfo & syncLockInfo);
+  void update_lock_file(const SyncLockInfo & syncLockInfo);
+  bool is_valid_xml_file(const std::string & xmlFilePath);
+
+  std::list<std::string> m_updated_notes;
+  std::list<std::string> m_deleted_notes;
+
+  std::string m_server_id;
+
+  std::string m_server_path;
+  std::string m_cache_path;
+  std::string m_lock_path;
+  std::string m_manifest_path;
+
+  int m_new_revision;
+  std::string m_new_revision_path;
+
+  sharp::DateTime m_initial_sync_attempt;
+  std::string m_last_sync_lock_hash;
+  Glib::RefPtr<Glib::TimeoutSource> m_lock_timeout;
+  SyncLockInfo m_sync_lock;
+};
+
+}
+}
+
+#endif
diff --git a/src/synchronization/gnotesyncclient.cpp b/src/synchronization/gnotesyncclient.cpp
new file mode 100644
index 0000000..2f923bc
--- /dev/null
+++ b/src/synchronization/gnotesyncclient.cpp
@@ -0,0 +1,97 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2012 Aurimas Cernius
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef _SYNCHRONIZATION_GNOTESYNCCLIENT_HPP_
+#define _SYNCHRONIZATION_GNOTESYNCCLIENT_HPP_
+
+
+#include "gnotesyncclient.hpp"
+#include "sharp/files.hpp"
+
+
+namespace gnote {
+namespace sync {
+
+  const char * GnoteSyncClient::LOCAL_MANIFEST_FILE_NAME = "manifest.xml";
+
+  GnoteSyncClient::GnoteSyncClient()
+  {
+  }
+
+
+  void GnoteSyncClient::last_sync_date(const sharp::DateTime & date)
+  {
+    m_last_sync_date = date;
+    // If we just did a sync, we should be able to forget older deleted notes
+    m_deleted_notes.clear();
+    //Write(localManifestFilePath);  TODO
+  }
+
+
+  void GnoteSyncClient::last_synchronized_revision(int revision)
+  {
+    m_last_sync_rev = revision;
+    //Write(localManifestFilePath);  TODO
+  }
+
+
+  int GnoteSyncClient::get_revision(const Note::Ptr & note)
+  {
+    std::string note_guid = note->id();
+    std::map<std::string, int>::const_iterator iter = m_file_revisions.find(note_guid);
+    if(iter != m_file_revisions.end()) {
+      return iter->second;
+    }
+    else {
+      return -1;
+    }
+  }
+
+
+  void GnoteSyncClient::set_revision(const Note::Ptr & note, int revision)
+  {
+    m_file_revisions[note->id()] = revision;
+    // TODO: Should we write on each of these or no?
+    //Write(localManifestFilePath);  TODO
+  }
+
+
+  void GnoteSyncClient::reset()
+  {
+    if(sharp::file_exists(m_local_manifest_file_path)) {
+      sharp::file_delete(m_local_manifest_file_path);
+    }
+    //Parse(localManifestFilePath);  TODO
+  }
+
+
+  void GnoteSyncClient::associated_server_id(const std::string & server_id)
+  {
+    if(m_server_id != server_id) {
+      m_server_id = server_id;
+      //Write(localManifestFilePath);  TODO
+    }
+  }
+
+}
+}
+
+
+#endif
diff --git a/src/synchronization/gnotesyncclient.hpp b/src/synchronization/gnotesyncclient.hpp
new file mode 100644
index 0000000..e3ee245
--- /dev/null
+++ b/src/synchronization/gnotesyncclient.hpp
@@ -0,0 +1,68 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2012 Aurimas Cernius
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "syncmanager.hpp"
+
+
+
+namespace gnote {
+namespace sync {
+
+  class GnoteSyncClient
+    : public SyncClient
+  {
+  public:
+    GnoteSyncClient();
+
+    virtual sharp::DateTime last_sync_date()
+      {
+        return m_last_sync_date;
+      }
+    virtual void last_sync_date(const sharp::DateTime &);
+    virtual int last_synchronized_revision()
+      {
+        return m_last_sync_rev;
+      }
+    virtual void last_synchronized_revision(int);
+    virtual int get_revision(const Note::Ptr & note);
+    virtual void set_revision(const Note::Ptr & note, int revision);
+    virtual std::map<std::string, std::string> deleted_note_titles()
+      {
+        return m_deleted_notes;
+      }
+    virtual void reset();
+    virtual std::string associated_server_id()
+      {
+        return m_server_id;
+      }
+    virtual void associated_server_id(const std::string &);
+  private:
+    static const char *LOCAL_MANIFEST_FILE_NAME;
+
+    sharp::DateTime m_last_sync_date;
+    int m_last_sync_rev;
+    std::string m_server_id;
+    std::string m_local_manifest_file_path;
+    std::map<std::string, int> m_file_revisions;
+    std::map<std::string, std::string> m_deleted_notes;
+  };
+
+}
+}
diff --git a/src/synchronization/silentui.cpp b/src/synchronization/silentui.cpp
new file mode 100644
index 0000000..08570a0
--- /dev/null
+++ b/src/synchronization/silentui.cpp
@@ -0,0 +1,112 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2012 Aurimas Cernius
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "debug.hpp"
+#include "syncmanager.hpp"
+#include "silentui.hpp"
+
+
+namespace gnote {
+namespace sync {
+
+  SyncUI::Ptr SilentUI::create(NoteManager & nm)
+  {
+    return SyncUI::Ptr(new SilentUI(nm));
+  }
+
+
+  SilentUI::SilentUI(NoteManager & manager)
+    : m_manager(manager)
+    , m_ui_disabled(false)
+  {
+    signal_connecting_connect(sigc::mem_fun(*this, &SilentUI::on_connecting));
+    signal_idle_connect(sigc::mem_fun(*this, &SilentUI::on_idle));
+  }
+
+
+  void SilentUI::sync_state_changed(SyncState state)
+  {
+    // TODO: Update tray/applet icon
+    //       D-Bus event?
+    //       libnotify bubbles when appropriate
+    DBG_OUT("SilentUI: SyncStateChanged: %d", int(state));
+    switch(state) {
+    case CONNECTING:
+      m_ui_disabled = true;
+      // TODO: Disable all kinds of note editing
+      //         -New notes from server should be disabled, too
+      //         -Anyway we could skip this when uploading changes?
+      //         -Should store original Enabled state
+      signal_connecting_emit();
+      break;
+    case IDLE:
+      if(m_ui_disabled) {
+        signal_idle_emit();
+      }
+      break;
+    default:
+      break;
+    }
+  }
+
+
+  void SilentUI::note_synchronized(const std::string & noteTitle, NoteSyncType type)
+  {
+    DBG_OUT("note synchronized, Title: %s, Type: %d", noteTitle.c_str(), int(type));
+  }
+
+
+  void SilentUI::note_conflict_detected(NoteManager & manager,
+                                        const Note::Ptr & localConflictNote,
+                                        NoteUpdate remoteNote,
+                                        const std::list<std::string> &)
+  {
+    DBG_OUT("note conflict detected, overwriting without a care");
+    // TODO: At least respect conflict prefs
+    // TODO: Implement more useful conflict handling
+    if(localConflictNote->id() != remoteNote.m_uuid) {
+      manager.delete_note(localConflictNote);
+    }
+    SyncManager::obj().resolve_conflict(OVERWRITE_EXISTING);
+  }
+
+
+  void SilentUI::on_connecting()
+  {
+    m_manager.read_only(true);
+    std::list<Note::Ptr> notes = m_manager.get_notes();
+    for(std::list<Note::Ptr>::iterator iter = notes.begin(); iter != notes.end(); ++iter) {
+      (*iter)->enabled(false);
+    }
+  }
+
+
+  void SilentUI::on_idle()
+  {
+    m_manager.read_only(false);
+    std::list<Note::Ptr> notes = m_manager.get_notes();
+    for(std::list<Note::Ptr>::iterator iter = notes.begin(); iter != notes.end(); ++iter) {
+      (*iter)->enabled(true);
+    }
+    m_ui_disabled = false;
+  }
+
+}
+}
diff --git a/src/synchronization/silentui.hpp b/src/synchronization/silentui.hpp
new file mode 100644
index 0000000..947a060
--- /dev/null
+++ b/src/synchronization/silentui.hpp
@@ -0,0 +1,56 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2012 Aurimas Cernius
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef _SYNCHRONIZATION_SILENTUI_HPP_
+#define _SYNCHRONIZATION_SILENTUI_HPP_
+
+
+#include "notemanager.hpp"
+#include "syncui.hpp"
+
+
+
+namespace gnote {
+namespace sync {
+
+  class SilentUI
+    : public SyncUI
+  {
+  public:
+    static SyncUI::Ptr create(NoteManager &);
+  private:
+    explicit SilentUI(NoteManager &);
+    virtual void sync_state_changed(SyncState state);
+    virtual void note_synchronized(const std::string & noteTitle, NoteSyncType type);
+    virtual void note_conflict_detected(NoteManager & manager,
+                                        const Note::Ptr & localConflictNote,
+                                        NoteUpdate remoteNote,
+                                        const std::list<std::string> & noteUpdateTitles);
+    void on_connecting();
+    void on_idle();
+
+    NoteManager & m_manager;
+    bool m_ui_disabled;
+  };
+
+}
+}
+
+#endif
diff --git a/src/synchronization/syncdialog.cpp b/src/synchronization/syncdialog.cpp
new file mode 100644
index 0000000..bf87809
--- /dev/null
+++ b/src/synchronization/syncdialog.cpp
@@ -0,0 +1,799 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2012 Aurimas Cernius
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "debug.hpp"
+
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+#include <glibmm/i18n.h>
+#include <gtkmm/scrolledwindow.h>
+#include <gtkmm/stock.h>
+#include <gtkmm/treeview.h>
+
+#include "gnote.hpp"
+#include "notemanager.hpp"
+#include "notewindow.hpp"
+#include "preferences.hpp"
+#include "syncdialog.hpp"
+#include "syncmanager.hpp"
+
+
+namespace gnote {
+namespace sync {
+
+namespace {
+
+class TreeViewModel
+  : public Gtk::TreeModelColumnRecord
+{
+public:
+  TreeViewModel()
+    {
+      add(m_col1);
+      add(m_col2);
+    }
+
+    Gtk::TreeModelColumn<std::string> m_col1;
+    Gtk::TreeModelColumn<std::string> m_col2;
+};
+
+
+typedef GObject GnoteSyncDialog;
+typedef GObjectClass GnoteSyncDialogClass;
+
+G_DEFINE_TYPE(GnoteSyncDialog, gnote_sync_dialog, G_TYPE_OBJECT)
+
+void gnote_sync_dialog_init(GnoteSyncDialog*)
+{}
+
+void gnote_sync_dialog_class_init(GnoteSyncDialogClass *klass)
+{
+  g_signal_new("sync-state-changed", G_TYPE_FROM_CLASS(klass),
+                   GSignalFlags(G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS),
+                   0, NULL, NULL, g_cclosure_marshal_VOID__INT,
+                   G_TYPE_NONE, 1, G_TYPE_INT, NULL);
+  g_signal_new("note-synchronized", G_TYPE_FROM_CLASS(klass),
+                   GSignalFlags(G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS),
+                   0, NULL, NULL, g_cclosure_marshal_generic,
+                   G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT, NULL);
+  g_signal_new("note-conflict-detected", G_TYPE_FROM_CLASS(klass),
+                   GSignalFlags(G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS),
+                   0, NULL, NULL, g_cclosure_marshal_VOID__VOID,
+                   G_TYPE_NONE, 0, NULL);
+}
+
+GObject *gnote_sync_dialog_new()
+{
+  g_type_init();
+  return G_OBJECT(g_object_new(gnote_sync_dialog_get_type(), NULL));
+}
+
+
+struct NoteConflictDetectedArgs
+{
+  NoteManager *manager;
+  Note::Ptr localConflictNote;
+  NoteUpdate *remoteNote;
+  const std::list<std::string> *noteUpdateTitles;
+  SyncTitleConflictResolution savedBehavior;
+  SyncTitleConflictResolution resolution;
+  std::exception *mainThreadException;
+
+  NoteConflictDetectedArgs() : mainThreadException(NULL) {}
+  ~NoteConflictDetectedArgs()
+    {
+      if(mainThreadException) {
+        delete mainThreadException;
+      }
+    }
+};
+
+
+class SyncTitleConflictDialog
+  : public Gtk::Dialog
+{
+public:
+  SyncTitleConflictDialog(const Note::Ptr & existingNote, const std::list<std::string> & noteUpdateTitles)
+    : Gtk::Dialog(_("Note Conflict"), true)
+    , m_existing_note(existingNote)
+    , m_note_update_titles(noteUpdateTitles)
+    {
+      // Suggest renaming note by appending " (old)" to the existing title
+      std::string suggestedRenameBase = existingNote->get_title() + _(" (old)");
+      std::string suggestedRename = suggestedRenameBase;
+      for(int i = 1; !is_note_title_available(suggestedRename); i++) {
+        suggestedRename = suggestedRenameBase + " " + boost::lexical_cast<std::string>(i);
+      }
+
+      Gtk::VBox *outerVBox = manage(new Gtk::VBox(false, 12));
+      outerVBox->set_border_width(12);
+      outerVBox->set_spacing(8);
+
+      Gtk::HBox *hbox = manage(new Gtk::HBox(false, 8));
+      Gtk::Image *image = manage(new Gtk::Image);
+      image->set(Gtk::Stock::DIALOG_WARNING, Gtk::IconSize(48)); // TODO: Is this the right icon?
+      image->show();
+      hbox->pack_start(*image, false, false, 0);
+
+      Gtk::VBox *vbox = manage(new Gtk::VBox(false, 8));
+
+      m_header_label = manage(new Gtk::Label);
+      m_header_label->set_use_markup(true);
+      m_header_label->property_xalign() = 0;
+      m_header_label->set_use_underline(false);
+      m_header_label->show();
+      vbox->pack_start(*m_header_label, false, false, 0);
+
+      m_message_label = manage(new Gtk::Label);
+      m_message_label->property_xalign() = 0;
+      m_message_label->set_use_underline(false);
+      m_message_label->set_line_wrap(true);
+      m_message_label->property_wrap() = true;
+      m_message_label->show();
+      vbox->pack_start(*m_message_label, false, false, 0);
+
+      vbox->show();
+      hbox->pack_start(*vbox, true, true, 0);
+
+      hbox->show();
+      outerVBox->pack_start(*hbox);
+      get_vbox()->pack_start(*outerVBox);
+
+      Gtk::HBox *renameHBox = manage(new Gtk::HBox);
+      renameRadio = manage(new Gtk::RadioButton(m_radio_group, _("Rename local note:")));
+      renameRadio->signal_toggled().connect(sigc::mem_fun(*this, &SyncTitleConflictDialog::radio_toggled));
+      Gtk::VBox *renameOptionsVBox = manage(new Gtk::VBox);
+
+      renameEntry = manage(new Gtk::Entry);
+      renameEntry->set_text(suggestedRename);
+      renameEntry->signal_changed().connect(sigc::mem_fun(*this, &SyncTitleConflictDialog::rename_entry_changed));
+      renameUpdateCheck = manage(new Gtk::CheckButton(_("Update links in referencing notes")));
+      renameOptionsVBox->pack_start(*renameEntry);
+      //renameOptionsVBox->pack_start(*renameUpdateCheck); // This seems like a superfluous option
+      renameHBox->pack_start(*renameRadio);
+      renameHBox->pack_start(*renameOptionsVBox);
+      get_vbox()->pack_start(*renameHBox);
+
+      deleteExistingRadio = manage(new Gtk::RadioButton(m_radio_group, _("Overwrite local note")));
+      deleteExistingRadio->signal_toggled().connect(sigc::mem_fun(*this, &SyncTitleConflictDialog::radio_toggled));
+      get_vbox()->pack_start(*deleteExistingRadio);
+
+      alwaysDoThisCheck = manage(new Gtk::CheckButton(_("Always perform this action")));
+      get_vbox()->pack_start(*alwaysDoThisCheck);
+
+      continueButton = add_button(Gtk::Stock::GO_FORWARD, Gtk::RESPONSE_ACCEPT);
+
+      // Set initial dialog text
+      header_text(_("Note conflict detected"));
+      message_text(boost::str(boost::format(
+        _("The server version of \"%1%\" conflicts with your local note.  What do you want to do with your local note?"))
+        % existingNote->get_title()));
+
+      show_all();
+    }
+  void header_text(const std::string & value)
+    {
+      m_header_label->set_markup(boost::str(boost::format(
+        "<span size=\"large\" weight=\"bold\">%1%</span>") % value));
+    }
+  void message_text(const std::string & value)
+    {
+      m_message_label->set_text(value);
+    }
+  std::string renamed_title() const
+    {
+      return renameEntry->get_text();
+    }
+  bool always_perform_this_action() const
+    {
+      return alwaysDoThisCheck->get_active();
+    }
+  SyncTitleConflictResolution resolution() const
+    {
+      if(renameRadio->get_active()) {
+        if(renameUpdateCheck->get_active()) {
+          return RENAME_EXISTING_AND_UPDATE;
+        }
+        else {
+          return RENAME_EXISTING_NO_UPDATE;
+        }
+      }
+      else {
+        return OVERWRITE_EXISTING;
+      }
+    }
+private:
+  void rename_entry_changed()
+    {
+      if(renameRadio->get_active() && !is_note_title_available(renamed_title())) {
+        continueButton->set_sensitive(false);
+      }
+      else {
+        continueButton->set_sensitive(true);
+      }
+    }
+  bool is_note_title_available(const std::string & renamedTitle)
+    {
+      return std::find(m_note_update_titles.begin(), m_note_update_titles.end(), renamedTitle) != m_note_update_titles.end()
+             && m_existing_note->manager().find(renamedTitle) == 0;
+    }
+  void radio_toggled()
+    {
+      // Make sure Continue button has the right sensitivity
+      rename_entry_changed();
+
+      // Update sensitivity of rename-related widgets
+      renameEntry->set_sensitive(renameRadio->get_active());
+      renameUpdateCheck->set_sensitive(renameRadio->get_active());
+    }
+
+  Note::Ptr m_existing_note;
+  std::list<std::string> m_note_update_titles;
+
+  Gtk::Button *continueButton;
+
+  Gtk::Entry *renameEntry;
+  Gtk::CheckButton *renameUpdateCheck;
+  Gtk::RadioButton *renameRadio;
+  Gtk::RadioButton *deleteExistingRadio;
+  Gtk::CheckButton *alwaysDoThisCheck;
+  Gtk::RadioButtonGroup m_radio_group;
+
+  Gtk::Label *m_header_label;
+  Gtk::Label *m_message_label;
+};
+
+} // annonymous namespace
+
+
+
+
+SyncDialog::Ptr SyncDialog::create()
+{
+  return SyncDialog::Ptr(new SyncDialog);
+}
+
+
+SyncDialog::SyncDialog()
+{
+  m_obj = gnote_sync_dialog_new();
+  g_signal_connect(m_obj, "sync-state-changed", G_CALLBACK(on_sync_state_changed), this);
+  g_signal_connect(m_obj, "note-synchronized", G_CALLBACK(on_note_synchronized), this);
+  g_signal_connect(m_obj, "note-conflict-detected", G_CALLBACK(on_note_conflict_detected), this);
+  m_progress_bar_timeout_id = 0;
+
+  set_size_request(400, -1);
+
+  // Outer box. Surrounds all of our content.
+  Gtk::VBox *outerVBox = manage(new Gtk::VBox(false, 12));
+  outerVBox->set_border_width(6);
+  outerVBox->show();
+  get_vbox()->pack_start(*outerVBox, true, true, 0);
+
+  // Top image and label
+  Gtk::HBox *hbox = manage(new Gtk::HBox(false, 12));
+  hbox->show();
+  outerVBox->pack_start(*hbox, false, false, 0);
+
+  m_image = manage(new Gtk::Image(utils::get_icon("gnote", 48)));
+  m_image->set_alignment(0, 0);
+  m_image->show();
+  hbox->pack_start(*m_image, false, false, 0);
+
+  // Label header and message
+  Gtk::VBox *vbox = manage(new Gtk::VBox(false, 6));
+  vbox->show();
+  hbox->pack_start(*vbox, true, true, 0);
+
+  m_header_label = manage(new Gtk::Label);
+  m_header_label->set_use_markup(true);
+  float xalign, yalign;
+  m_header_label->get_alignment(xalign, yalign);
+  m_header_label->set_alignment(0, yalign);
+  m_header_label->set_use_underline(false);
+  m_header_label->set_line_wrap(true);
+  m_header_label->show();
+  vbox->pack_start(*m_header_label, false, false, 0);
+
+  m_message_label = manage(new Gtk::Label);
+  m_message_label->get_alignment(xalign, yalign);
+  m_message_label->set_alignment(0, yalign);
+  m_message_label->set_use_underline(false);
+  m_message_label->set_line_wrap(true);
+  m_message_label->set_size_request(250, -1);
+  m_message_label->show();
+  vbox->pack_start(*m_message_label, false, false, 0);
+
+  m_progress_bar = manage(new Gtk::ProgressBar);
+  m_progress_bar->set_orientation(Gtk::ORIENTATION_HORIZONTAL);
+  m_progress_bar->set_pulse_step(0.3);
+  m_progress_bar->show();
+  outerVBox->pack_start(*m_progress_bar, false, false, 0);
+
+  m_progress_label = manage(new Gtk::Label);
+  m_progress_label->set_use_markup(true);
+  m_progress_label->get_alignment(xalign, yalign);
+  m_progress_label->set_alignment(0, yalign);
+  m_progress_label->set_use_underline(false);
+  m_progress_label->set_line_wrap(true);
+  m_progress_label->property_wrap() = true;
+  m_progress_label->show();
+  outerVBox->pack_start(*m_progress_label, false, false, 0);
+
+  // Expander containing TreeView
+  m_expander = manage(new Gtk::Expander(_("Details")));
+  m_expander->set_spacing(6);
+  g_signal_connect(m_expander->gobj(), "activate", G_CALLBACK(SyncDialog::on_expander_activated), this);
+  m_expander->show();
+  outerVBox->pack_start(*m_expander, true, true, 0);
+
+  // Contents of expander
+  Gtk::VBox *expandVBox = manage(new Gtk::VBox);
+  expandVBox->show();
+  m_expander->add(*expandVBox);
+
+  // Scrolled window around TreeView
+  Gtk::ScrolledWindow *scrolledWindow = manage(new Gtk::ScrolledWindow);
+  scrolledWindow->set_shadow_type(Gtk::SHADOW_IN);
+  scrolledWindow->set_size_request(-1, 200);
+  scrolledWindow->show();
+  expandVBox->pack_start(*scrolledWindow, true, true, 0);
+
+  // Create model for TreeView
+  m_model = Gtk::TreeStore::create(TreeViewModel());
+
+  // Create TreeView, attach model
+  Gtk::TreeView *treeView = manage(new Gtk::TreeView);
+  treeView->set_model(m_model);
+  treeView->signal_row_activated().connect(sigc::mem_fun(*this, &SyncDialog::on_row_activated));
+  treeView->show();
+  scrolledWindow->add(*treeView);
+
+  // Set up TreeViewColumns
+  Gtk::CellRenderer *renderer = manage(new Gtk::CellRendererText);
+  Gtk::TreeViewColumn *column = manage(new Gtk::TreeViewColumn(_("Note Title"), *renderer));
+  column->set_sort_column(0);
+  column->set_resizable(true);
+  column->set_cell_data_func(*renderer, sigc::mem_fun(*this, &SyncDialog::treeview_col1_data_func));
+  treeView->append_column(*column);
+
+  renderer = manage(new Gtk::CellRendererText);
+  column = manage(new Gtk::TreeViewColumn(_("Status"), *renderer));
+  column->set_sort_column(1);
+  column->set_resizable(true);
+  treeView->append_column(*column);
+  column->set_cell_data_func(*renderer, sigc::mem_fun(*this, &SyncDialog::treeview_col2_data_func));
+
+  // Button to close dialog.
+  m_close_button = add_button(Gtk::Stock::CLOSE, static_cast<int>(Gtk::RESPONSE_CLOSE));
+  m_close_button->set_sensitive(false);
+}
+
+
+void SyncDialog::treeview_col1_data_func(Gtk::CellRenderer *renderer, const Gtk::TreeIter & iter)
+{
+  std::string text;
+  iter->get_value(0, text);
+  static_cast<Gtk::CellRendererText*>(renderer)->property_text() = text;
+}
+
+
+void SyncDialog::treeview_col2_data_func(Gtk::CellRenderer *renderer, const Gtk::TreeIter & iter)
+{
+  std::string text;
+  iter->get_value(1, text);
+  static_cast<Gtk::CellRendererText*>(renderer)->property_text() = text;
+}
+
+
+SyncDialog::~SyncDialog()
+{
+  g_object_unref(m_obj);
+}
+
+
+void SyncDialog::on_realize()
+{
+  Gtk::Dialog::on_realize();
+
+  SyncState state = SyncManager::obj().state();
+  if(state == IDLE) {
+    // Kick off a timer to keep the progress bar going
+    //m_progress_barTimeoutId = GLib.Timeout.Add (500, OnPulseProgressBar);
+    Glib::RefPtr<Glib::TimeoutSource> timeout = Glib::TimeoutSource::create(500);
+    timeout->connect(sigc::mem_fun(*this, &SyncDialog::on_pulse_progress_bar));
+    timeout->attach();
+
+    // Kick off a new synchronization
+    SyncManager::obj().perform_synchronization(this->shared_from_this());
+  }
+  else {
+    // Adjust the GUI accordingly
+    sync_state_changed(state);
+  }
+}
+
+
+bool SyncDialog::on_pulse_progress_bar()
+{
+  if(SyncManager::obj().state() == IDLE) {
+    return false;
+  }
+
+  m_progress_bar->pulse();
+
+  // Return true to keep things going well
+  return true;
+}
+
+
+void SyncDialog::on_expander_activated(GtkExpander*, gpointer data)
+{
+  SyncDialog *this_ = static_cast<SyncDialog*>(data);
+  if(this_->m_expander->get_expanded()) {
+    this_->set_resizable(true);
+  }
+  else {
+    this_->set_resizable(false);
+  }
+}
+
+
+void SyncDialog::on_row_activated(const Gtk::TreeModel::Path & path, Gtk::TreeViewColumn*)
+{
+  // TODO: Store GUID hidden in model; use instead of title
+  Gtk::TreeIter iter = m_model->get_iter(path);
+  if(!iter) {
+    return;
+  }
+
+  std::string noteTitle;
+  iter->get_value(0, noteTitle);
+
+  Note::Ptr note = Gnote::obj().default_note_manager().find(noteTitle);
+  if(note != 0) {
+    note->get_window()->present();
+  }
+}
+
+
+void SyncDialog::header_text(const std::string & value)
+{
+  m_header_label->set_markup(str(boost::format("<span size=\"large\" weight=\"bold\">%1%</span>") % value));
+}
+
+
+void SyncDialog::message_text(const std::string & value)
+{
+  m_message_label->set_text(value);
+}
+
+
+std::string SyncDialog::progress_text() const
+{
+  return m_progress_label->get_text();
+}
+
+
+void SyncDialog::progress_text(const std::string & value)
+{
+  m_progress_label->set_markup(str(
+    boost::format("<span style=\"italic\">%1%</span>") % value));
+}
+
+
+void SyncDialog::add_update_item(const std::string & title, std::string & status)
+{
+  Gtk::TreeIter iter = m_model->append();
+  iter->set_value(0, title);
+  iter->set_value(1 , status);
+}
+
+
+void SyncDialog::sync_state_changed(SyncState state)
+{
+  // This event handler will be called by the synchronization thread
+  gdk_threads_enter();
+  g_signal_emit_by_name(m_obj, "sync-state-changed", static_cast<int>(state));
+  gdk_threads_leave();
+}
+
+
+void SyncDialog::on_sync_state_changed(GObject*, int state, gpointer data)
+{
+  static_cast<SyncDialog*>(data)->sync_state_changed_(static_cast<SyncState>(state));
+}
+
+
+void SyncDialog::sync_state_changed_(SyncState state)
+{
+  // FIXME: Change these strings to be user-friendly
+  switch(state) {
+  case ACQUIRING_LOCK:
+    progress_text(_("Acquiring sync lock..."));
+    break;
+  case COMMITTING_CHANGES:
+    progress_text(_("Committing changes..."));
+    break;
+  case CONNECTING:
+    set_title(_("Synchronizing Notes"));
+    header_text(_("Synchronizing your notes..."));
+    message_text(_("This may take a while, kick back and enjoy!"));
+    m_model->clear();
+    progress_text(_("Connecting to the server..."));
+    m_progress_bar->set_fraction(0);
+    m_progress_bar->show();
+    m_progress_label->show();
+    break;
+  case DELETE_SERVER_NOTES:
+    progress_text(_("Deleting notes off of the server..."));
+    m_progress_bar->pulse();
+    break;
+  case DOWNLOADING:
+    progress_text(_("Downloading new/updated notes..."));
+    m_progress_bar->pulse();
+    break;
+  case IDLE:
+    //GLib.Source.Remove (m_progress_barTimeoutId);
+    //m_progress_barTimeoutId = 0;
+    m_progress_bar->set_fraction(0);
+    m_progress_bar->hide();
+    m_progress_label->hide();
+    m_close_button->set_sensitive(true);
+    break;
+  case LOCKED:
+    set_title(_("Server Locked"));
+    header_text(_("Server is locked"));
+    message_text(_("One of your other computers is currently synchronizing.  Please wait 2 minutes and try again."));
+    progress_text("");
+    break;
+  case PREPARE_DOWNLOAD:
+    progress_text(_("Preparing to download updates from server..."));
+    break;
+  case PREPARE_UPLOAD:
+    progress_text(_("Preparing to upload updates to server..."));
+    break;
+  case UPLOADING:
+    progress_text(_("Uploading notes to server..."));
+    break;
+  case FAILED:
+    set_title(_("Synchronization Failed"));
+    header_text(_("Failed to synchronize"));
+    message_text(_("Could not synchronize notes.  Check the details below and try again."));
+    progress_text("");
+    break;
+  case SUCCEEDED:
+    {
+      int count = m_model->children().size();
+      set_title(_("Synchronization Complete"));
+      header_text(_("Synchronization is complete"));
+      std::string numNotesUpdated = ngettext("%d note updated.", "%d notes updated.", count);
+      message_text(numNotesUpdated + "  " + _("Your notes are now up to date."));
+      progress_text("");
+    }
+    break;
+  case USER_CANCELLED:
+    set_title(_("Synchronization Canceled"));
+    header_text(_("Synchronization was canceled"));
+    message_text(_("You canceled the synchronization.  You may close the window now."));
+    progress_text("");
+    break;
+  case NO_CONFIGURED_SYNC_SERVICE:
+    set_title(_("Synchronization Not Configured"));
+    header_text(_("Synchronization is not configured"));
+    message_text(_("Please configure synchronization in the preferences dialog."));
+    progress_text("");
+    break;
+  case SYNC_SERVER_CREATION_FAILED:
+    set_title(_("Synchronization Service Error"));
+    header_text(_("Service error"));
+    message_text(_("Error connecting to the synchronization service.  Please try again."));
+    progress_text("");
+    break;
+  }
+}
+
+
+void SyncDialog::note_synchronized(const std::string & noteTitle, NoteSyncType type)
+{
+  // This event handler will be called by the synchronization thread
+  gdk_threads_enter();
+  g_signal_emit_by_name(m_obj, "note-synchronized", noteTitle.c_str(), static_cast<int>(type));
+  gdk_threads_leave();
+}
+
+
+void SyncDialog::on_note_synchronized(GObject*, const char * noteTitle, int type, gpointer data)
+{
+  static_cast<SyncDialog*>(data)->note_synchronized_(noteTitle, static_cast<NoteSyncType>(type));
+}
+
+
+void SyncDialog::note_synchronized_(const std::string & noteTitle, NoteSyncType type)
+{
+  // FIXME: Change these strings to be more user-friendly
+  // TODO: Update status for a note when status changes ("Uploading" -> "Uploaded", etc)
+  std::string statusText;
+  switch(type) {
+  case DELETE_FROM_CLIENT:
+    statusText = _("Deleted locally");
+    break;
+  case DELETE_FROM_SERVER:
+    statusText = _("Deleted from server");
+    break;
+  case DOWNLOAD_MODIFIED:
+    statusText = _("Updated");
+    break;
+  case DOWNLOAD_NEW:
+    statusText = _("Added");
+    break;
+  case UPLOAD_MODIFIED:
+    statusText = _("Uploaded changes to server");
+    break;
+  case UPLOAD_NEW:
+    statusText = _("Uploaded new note to server");
+    break;
+  }
+  add_update_item(noteTitle, statusText);
+}
+
+
+void SyncDialog::note_conflict_detected(NoteManager & manager,
+                                        const Note::Ptr & localConflictNote,
+                                        NoteUpdate remoteNote,
+                                        const std::list<std::string> & noteUpdateTitles)
+{
+  NoteConflictDetectedArgs args;
+  args.savedBehavior = CANCEL;
+  int dlgBehaviorPref = Preferences::obj()
+    .get_schema_settings(Preferences::SCHEMA_SYNC)->get_int(Preferences::SYNC_CONFIGURED_CONFLICT_BEHAVIOR);
+  // TODO: Check range of this int
+  args.savedBehavior = static_cast<SyncTitleConflictResolution>(dlgBehaviorPref);
+
+  args.resolution = OVERWRITE_EXISTING;
+  args.manager = &manager;
+  args.localConflictNote = localConflictNote;
+  args.remoteNote = &remoteNote;
+  args.noteUpdateTitles = &noteUpdateTitles;
+  // This event handler will be called by the synchronization thread
+  // so we have to use the delegate here to manipulate the GUI.
+  // To be consistent, any exceptions in the delgate will be caught
+  // and then rethrown in the synchronization thread.
+  gdk_threads_enter();
+  g_signal_emit_by_name(m_obj, "note-conflict-detected", &args);
+  gdk_threads_leave();
+  if(args.mainThreadException != NULL) {
+    throw *args.mainThreadException;
+  }
+}
+
+
+void SyncDialog::on_note_conflict_detected(GObject*, gpointer data)
+{
+  NoteConflictDetectedArgs *args = static_cast<NoteConflictDetectedArgs*>(data);
+  try {
+    SyncTitleConflictDialog conflictDlg(args->localConflictNote, *args->noteUpdateTitles);
+    Gtk::ResponseType reponse = Gtk::RESPONSE_OK;
+
+    bool noteSyncBitsMatch = SyncManager::obj().synchronized_note_xml_matches(
+      args->localConflictNote->get_complete_note_xml(), args->remoteNote->m_xml_content);
+
+    // If the synchronized note content is in conflict
+    // and there is no saved conflict handling behavior, show the dialog
+    if(!noteSyncBitsMatch && args->savedBehavior == 0) {
+      reponse = static_cast<Gtk::ResponseType>(conflictDlg.run());
+    }
+
+
+    if(reponse == Gtk::RESPONSE_CANCEL) {
+      args->resolution = CANCEL;
+    }
+    else {
+      if(noteSyncBitsMatch) {
+        args->resolution = OVERWRITE_EXISTING;
+      }
+      else if(args->savedBehavior == 0) {
+        args->resolution = conflictDlg.resolution();
+      }
+      else {
+        args->resolution = args->savedBehavior;
+      }
+
+      switch(args->resolution) {
+      case OVERWRITE_EXISTING:
+        if(conflictDlg.always_perform_this_action()) {
+          args->savedBehavior = args->resolution;
+        }
+        // No need to delete if sync will overwrite
+        if(args->localConflictNote->id() != args->remoteNote->m_uuid) {
+          args->manager->delete_note(args->localConflictNote);
+        }
+        break;
+      case RENAME_EXISTING_AND_UPDATE:
+        if(conflictDlg.always_perform_this_action()) {
+          args->savedBehavior = args->resolution;
+        }
+        rename_note(args->localConflictNote, conflictDlg.renamed_title(), true);
+        break;
+      case RENAME_EXISTING_NO_UPDATE:
+        if(conflictDlg.always_perform_this_action()) {
+          args->savedBehavior = args->resolution;
+        }
+        rename_note(args->localConflictNote, conflictDlg.renamed_title(), false);
+        break;
+      case CANCEL:
+        break;
+      }
+    }
+
+    Preferences::obj().get_schema_settings(Preferences::SCHEMA_SYNC)->set_int(
+      Preferences::SYNC_CONFIGURED_CONFLICT_BEHAVIOR, static_cast<int>(args->savedBehavior)); // TODO: Clean up
+
+    conflictDlg.hide();
+
+    // Let the SyncManager continue
+    SyncManager::obj().resolve_conflict(/*localConflictNote, */args->resolution);
+  }
+  catch(std::exception & e) {
+    args->mainThreadException = new std::exception(e);
+  }
+}
+
+
+void SyncDialog::rename_note(const Note::Ptr & note, const std::string & newTitle, bool)
+{
+  std::string oldTitle = note->get_title();
+  // Rename the note (skip for now...never using updateReferencingNotes option)
+  //if (updateReferencingNotes) // NOTE: This might never work, or lead to a ton of conflicts
+  // note.Title = newTitle;
+  //else
+  // note.RenameWithoutLinkUpdate (newTitle);
+  //string oldContent = note.XmlContent;
+  //note.XmlContent = NoteArchiver.Instance.GetRenamedNoteXml (oldContent, oldTitle, newTitle);
+
+  // Preserve note information
+  note->save(); // Write to file
+  bool noteOpen = note->is_opened();
+  std::string newContent = //note.XmlContent;
+    NoteArchiver::obj().get_renamed_note_xml(note->xml_content(), oldTitle, newTitle);
+  std::string newCompleteContent = //note.GetCompleteNoteXml ();
+    NoteArchiver::obj().get_renamed_note_xml(note->get_complete_note_xml(), oldTitle, newTitle);
+  //Logger.Debug ("RenameNote: newContent: " + newContent);
+  //Logger.Debug ("RenameNote: newCompleteContent: " + newCompleteContent);
+
+  // We delete and recreate the note to simplify content conflict handling
+  Gnote::obj().default_note_manager().delete_note(note);
+
+  // Create note with old XmlContent just in case GetCompleteNoteXml failed
+  DBG_OUT("RenameNote: about to create %s", newTitle.c_str());
+  Note::Ptr renamedNote = Gnote::obj().default_note_manager().create(newTitle, newContent);
+  if(newCompleteContent != "") {// TODO: Anything to do if it is null?
+    try {
+      renamedNote->load_foreign_note_xml(newCompleteContent, OTHER_DATA_CHANGED);
+    }
+    catch(...) {} // TODO: Handle exception in case that newCompleteContent is invalid XML
+  }
+  if(noteOpen) {
+    renamedNote->get_window()->present();
+  }
+}
+
+}
+}
diff --git a/src/synchronization/syncdialog.hpp b/src/synchronization/syncdialog.hpp
new file mode 100644
index 0000000..e7c9cb0
--- /dev/null
+++ b/src/synchronization/syncdialog.hpp
@@ -0,0 +1,94 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2012 Aurimas Cernius
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef _SYNCHRONIZATION_SYNCDIALOG_HPP_
+#define _SYNCHRONIZATION_SYNCDIALOG_HPP_
+
+
+#include <gtkmm/dialog.h>
+#include <gtkmm/expander.h>
+#include <gtkmm/progressbar.h>
+#include <gtkmm/treestore.h>
+#include <gtkmm/treeviewcolumn.h>
+
+#include "syncui.hpp"
+
+
+namespace gnote {
+namespace sync {
+
+  class SyncDialog
+    : public Gtk::Dialog
+    , public SyncUI
+  {
+  public:
+    typedef std::tr1::shared_ptr<SyncDialog> Ptr;
+
+    static Ptr create();
+
+    virtual ~SyncDialog();
+
+    virtual void sync_state_changed(SyncState state);
+    virtual void note_synchronized(const std::string & noteTitle, NoteSyncType type);
+    virtual void note_conflict_detected(NoteManager & manager,
+                                        const Note::Ptr & localConflictNote,
+                                        NoteUpdate remoteNote,
+                                        const std::list<std::string> & noteUpdateTitles);
+    void header_text(const std::string &);
+    void message_text(const std::string &);
+    std::string progress_text() const;
+    void progress_text(const std::string &);
+    void add_update_item(const std::string & title, std::string & status);
+  protected:
+    virtual void on_realize();
+  private:
+    static void on_expander_activated(GtkExpander*, gpointer);
+    static void on_sync_state_changed(GObject*, int, gpointer);
+    static void on_note_synchronized(GObject*, const char*, int, gpointer);
+    static void on_note_conflict_detected(GObject*, gpointer);
+    static void rename_note(const Note::Ptr & note, const std::string & newTitle, bool updateReferencingNotes);
+
+    SyncDialog();
+    bool on_pulse_progress_bar();
+    void on_row_activated(const Gtk::TreeModel::Path & path, Gtk::TreeViewColumn *column);
+    void treeview_col1_data_func(Gtk::CellRenderer *renderer, const Gtk::TreeIter & iter);
+    void treeview_col2_data_func(Gtk::CellRenderer *renderer, const Gtk::TreeIter & iter);
+    void sync_state_changed_(SyncState state);
+    void note_synchronized_(const std::string & noteTitle, NoteSyncType type);
+
+    Gtk::Image *m_image;
+    Gtk::Label *m_header_label;
+    Gtk::Label *m_message_label;
+    Gtk::ProgressBar *m_progress_bar;
+    Gtk::Label *m_progress_label;
+
+    Gtk::Expander *m_expander;
+    Gtk::Button *m_close_button;
+    unsigned m_progress_bar_timeout_id;
+
+    Glib::RefPtr<Gtk::TreeStore> m_model;
+    GObject *m_obj;
+  };
+
+}
+}
+
+
+#endif
diff --git a/src/synchronization/syncmanager.cpp b/src/synchronization/syncmanager.cpp
new file mode 100644
index 0000000..387021e
--- /dev/null
+++ b/src/synchronization/syncmanager.cpp
@@ -0,0 +1,839 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2012 Aurimas Cernius
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "config.h"
+
+#include <boost/format.hpp>
+#include <glibmm/i18n.h>
+#include <gtkmm/actiongroup.h>
+#include <sigc++/sigc++.h>
+
+#include "actionmanager.hpp"
+#include "addinmanager.hpp"
+#include "debug.hpp"
+#include "filesystemsyncserver.hpp"
+#include "gnote.hpp"
+#include "gnotesyncclient.hpp"
+#include "notemanager.hpp"
+#include "preferences.hpp"
+#include "silentui.hpp"
+#include "syncmanager.hpp"
+#include "syncserviceaddin.hpp"
+#include "sharp/uuid.hpp"
+#include "sharp/xmlreader.hpp"
+
+
+namespace gnote {
+namespace sync {
+
+  namespace {
+
+    typedef GObject SyncHelper;
+    typedef GObjectClass SyncHelperClass;
+
+    G_DEFINE_TYPE(SyncHelper, sync_helper, G_TYPE_OBJECT)
+
+    void sync_helper_init(SyncHelper*)
+    {}
+
+    void sync_helper_class_init(SyncHelperClass * klass)
+    {
+      g_signal_new("delete-notes", G_TYPE_FROM_CLASS(klass),
+                   GSignalFlags(G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS),
+                   0, NULL, NULL, g_cclosure_marshal_VOID__POINTER,
+                   G_TYPE_NONE, 1, G_TYPE_POINTER, NULL);
+      g_signal_new("create-note", G_TYPE_FROM_CLASS(klass),
+                   GSignalFlags(G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS),
+                   0, NULL, NULL, g_cclosure_marshal_VOID__POINTER,
+                   G_TYPE_NONE, 1, G_TYPE_POINTER, NULL);
+      g_signal_new("update-note", G_TYPE_FROM_CLASS(klass),
+                   GSignalFlags(G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS),
+                   0, NULL, NULL, g_cclosure_marshal_generic,
+                   G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER, NULL);
+      g_signal_new("delete-note", G_TYPE_FROM_CLASS(klass),
+                   GSignalFlags(G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS),
+                   0, NULL, NULL, g_cclosure_marshal_VOID__POINTER,
+                   G_TYPE_NONE, 1, G_TYPE_POINTER, NULL);
+    }
+
+    GObject * sync_helper_new()
+    {
+      g_type_init();
+      return G_OBJECT(g_object_new(sync_helper_get_type(), NULL));
+    }
+
+    void sync_helper_delete_notes(GObject * helper, gpointer server)
+    {
+      gdk_threads_enter();
+      g_signal_emit_by_name(helper, "delete-notes", server);
+      gdk_threads_leave();
+    }
+
+    void sync_helper_create_note(GObject * helper, gpointer note_update)
+    {
+      gdk_threads_enter();
+      g_signal_emit_by_name(helper, "create-note", note_update);
+      gdk_threads_leave();
+    }
+
+    void sync_helper_update_note(GObject * helper, gpointer existing_note, gpointer note_update)
+    {
+      gdk_threads_enter();
+      g_signal_emit_by_name(helper, "update-note", existing_note, note_update);
+      gdk_threads_leave();
+    }
+
+    void sync_helper_delete_note(GObject * helper, gpointer existing_note)
+    {
+      gdk_threads_enter();
+      g_signal_emit_by_name(helper, "delete-note", existing_note);
+      gdk_threads_leave();
+    }
+
+  }
+
+
+  SyncManager::~SyncManager()
+  {
+    g_object_unref(m_sync_helper);
+  }
+
+
+  void SyncManager::init()
+  {
+    SyncManager::obj()._init();
+  }
+
+
+  void SyncManager::_init()
+  {
+    m_sync_helper = sync_helper_new();
+    g_signal_connect(m_sync_helper, "delete-notes", G_CALLBACK(SyncManager::on_delete_notes), NULL);
+    g_signal_connect(m_sync_helper, "create-note", G_CALLBACK(SyncManager::on_create_note), NULL);
+    g_signal_connect(m_sync_helper, "update-note", G_CALLBACK(SyncManager::on_update_note), NULL);
+    g_signal_connect(m_sync_helper, "delete-note", G_CALLBACK(SyncManager::on_delete_note), NULL);
+    m_client = SyncClient::Ptr(new GnoteSyncClient);
+    // Add a "Synchronize Notes" to Tomboy's Main Menu
+    Glib::RefPtr<Gtk::ActionGroup> action_group = Gtk::ActionGroup::create("Sync");
+    action_group->add(Gtk::Action::create("ToolsMenuAction", _("_Tools"), ""));
+    Glib::RefPtr<Gtk::Action> sync_notes_action = Gtk::Action::create("SyncNotesAction", _("Synchronize Notes"), "");
+    sync_notes_action->signal_activate().connect(sigc::mem_fun(*this, &SyncManager::on_sync_notes_activate));
+    action_group->add(sync_notes_action);
+
+    ActionManager::obj().get_ui()->add_ui_from_string(
+      "<ui>"
+      "<menubar name='MainWindowMenubar'>"
+      "<placeholder name='MainWindowMenuPlaceholder'>"
+      "<menu name='ToolsMenu' action='ToolsMenuAction'>"
+      "<menuitem name='SyncNotes' action='SyncNotesAction' />"
+      "</menu>"
+      "</placeholder>"
+      "</menubar>"
+      "</ui>"
+    );
+
+    ActionManager::obj().get_ui()->insert_action_group(action_group, 0);
+
+    // Initialize all the SyncServiceAddins
+    std::list<SyncServiceAddin*> addins;
+    Gnote::obj().default_note_manager().get_addin_manager().get_sync_service_addins(addins);
+    for(std::list<SyncServiceAddin*>::iterator iter = addins.begin(); iter != addins.end(); ++iter) {
+      try {
+        (*iter)->initialize();
+      }
+      catch(std::exception & e) {
+        DBG_OUT("Error calling %s.initialize (): %s", (*iter)->id().c_str(), e.what());
+
+        // TODO: Call something like AddinManager.Disable (addin)
+      }
+    }
+
+    Preferences::obj().get_schema_settings(Preferences::SCHEMA_SYNC)->signal_changed()
+      .connect(sigc::mem_fun(*this, &SyncManager::preferences_setting_changed));
+    note_mgr().signal_note_saved.connect(sigc::mem_fun(*this, &SyncManager::handle_note_saved_or_deleted));
+    note_mgr().signal_note_deleted.connect(sigc::mem_fun(*this, &SyncManager::handle_note_saved_or_deleted));
+    note_mgr().signal_note_buffer_changed.connect(sigc::mem_fun(*this, &SyncManager::handle_note_buffer_changed));
+
+    // Update sync item based on configuration.
+    update_sync_action();
+  }
+
+
+  void SyncManager::reset_client()
+  {
+    try {
+      m_client->reset();
+    }
+    catch(std::exception & e) {
+      DBG_OUT("Error deleting client manifest during reset: %s", e.what());
+    }
+  }
+
+
+  void SyncManager::perform_synchronization(const std::tr1::shared_ptr<SyncUI> & sync_ui)
+  {
+    if(m_sync_thread != NULL) {
+      // A synchronization thread is already running
+      // TODO: Start new sync if existing dlg is for finished sync
+      // TODO: ISyncUI-ize this somehow
+      if(m_sync_ui == Gnote::obj().sync_dialog()) {
+        Gnote::obj().sync_dialog()->present();
+      }
+      return;
+    }
+
+    m_sync_ui = sync_ui;
+    m_sync_thread = Glib::Thread::create(sigc::mem_fun(*this, &SyncManager::synchronization_thread), false);
+  }
+
+
+  void SyncManager::synchronization_thread()
+  {
+    struct finally {
+      SyncServiceAddin *addin;
+      finally() : addin(NULL){}
+      ~finally()
+      {
+        SyncManager::obj().m_sync_thread = NULL;
+        try {
+          if(addin) {
+            addin->post_sync_cleanup();
+          }
+        }
+        catch(std::exception & e) {
+          ERR_OUT("Error cleaning up addin after sync: %s", e.what());
+        }
+      }
+    } f;
+    SyncServer::Ptr server;
+    try {
+      f.addin = get_configured_sync_service();
+      if(f.addin == NULL) {
+        set_state(NO_CONFIGURED_SYNC_SERVICE);
+        DBG_OUT("GetConfiguredSyncService is null");
+        set_state(IDLE);
+        m_sync_thread = NULL;
+        return;
+      }
+
+      DBG_OUT("SyncThread using SyncServiceAddin: %s", f.addin->name().c_str());
+
+      set_state(CONNECTING);
+      try {
+        server = f.addin->create_sync_server();
+        if(server == NULL)
+          throw new std::logic_error("addin.CreateSyncServer () returned null");
+      }
+      catch(std::exception & e) {
+        set_state(SYNC_SERVER_CREATION_FAILED);
+        ERR_OUT("Exception while creating SyncServer: %s", e.what());
+        set_state(IDLE);
+        m_sync_thread = NULL;
+        f.addin->post_sync_cleanup();// TODO: Needed?
+        return;
+        // TODO: Figure out a clever way to get the specific error up to the GUI
+      }
+
+      // TODO: Call something that processes all queued note saves!
+      //       For now, only saving before uploading (not sufficient for note conflict handling)
+
+      set_state(ACQUIRING_LOCK);
+      // TODO: We should really throw exceptions from BeginSyncTransaction ()
+      if(!server->begin_sync_transaction()) {
+        set_state(LOCKED);
+        DBG_OUT("Server locked, try again later");
+        set_state(IDLE);
+        m_sync_thread = NULL;
+        f.addin->post_sync_cleanup();
+        return;
+      }
+      DBG_OUT("8");
+      int latestServerRevision = server->latest_revision();
+      int newRevision = latestServerRevision + 1;
+
+      // If the server has been wiped or reinitialized by another client
+      // for some reason, our local manifest is inaccurate and could misguide
+      // sync into erroneously deleting local notes, etc.  We reset the client
+      // to prevent this situation.
+      std::string serverId = server->id();
+      if(m_client->associated_server_id() != serverId) {
+        m_client->reset();
+        m_client->associated_server_id(serverId);
+      }
+
+      set_state(PREPARE_DOWNLOAD);
+
+      // Handle notes modified or added on server
+      DBG_OUT("Sync: GetNoteUpdatesSince rev %d", m_client->last_synchronized_revision());
+      std::map<std::string, NoteUpdate> noteUpdates = server->get_note_updates_since(m_client->last_synchronized_revision());
+      DBG_OUT("Sync: %d updates since rev %d", noteUpdates.size(), m_client->last_synchronized_revision());
+
+      // Gather list of new/updated note titles
+      // for title conflict handling purposes.
+      std::list<std::string> noteUpdateTitles;
+      for(std::map<std::string, NoteUpdate>::iterator iter = noteUpdates.begin();
+          iter != noteUpdates.end(); ++iter) {
+        if(iter->second.m_title != "") {
+          noteUpdateTitles.push_back(iter->second.m_title);
+        }
+      }
+
+      // First, check for new local notes that might have title conflicts
+      // with the updates coming from the server.  Prompt the user if necessary.
+      // 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?
+      for(std::map<std::string, NoteUpdate>::iterator iter = noteUpdates.begin();
+          iter != noteUpdates.end(); ++iter) {
+        if(!find_note_by_uuid(iter->second.m_uuid) != 0) {
+          Note::Ptr existingNote = note_mgr().find(iter->second.m_title);
+          if(existingNote != 0 && !iter->second.basically_equal_to(existingNote)) {
+            // Logger.Debug ("Sync: Early conflict detection for '{0}'", noteUpdate.Title);
+            if(m_sync_ui != 0) {
+              m_sync_ui->note_conflict_detected(note_mgr(), existingNote, iter->second, noteUpdateTitles);
+
+              // Suspend this thread while the GUI is presented to
+              // the user.
+              //syncThread.Suspend ();  TODO: findout what to do with this!!!
+            }
+          }
+        }
+      }
+
+      if(noteUpdates.size() > 0)
+        set_state(DOWNLOADING);
+
+      // TODO: Figure out why GUI doesn't always update smoothly
+
+      // Process updates from the server; the bread and butter of sync!
+      for(std::map<std::string, NoteUpdate>::iterator iter = noteUpdates.begin();
+          iter != noteUpdates.end(); ++iter) {
+        Note::Ptr existingNote = find_note_by_uuid(iter->second.m_uuid);
+
+        if(existingNote == 0) {
+          // Actually, it's possible to have a conflict here
+          // because of automatically-created notes like
+          // template notes (if a note with a new tag syncs
+          // before its associated template). So check by
+          // title and delete if necessary.
+          existingNote = note_mgr().find(iter->second.m_title);
+          if(existingNote != 0) {
+            DBG_OUT("SyncManager: Deleting auto-generated note: %s", iter->second.m_title.c_str());
+            delete_note_in_main_thread(existingNote);
+          }
+          create_note_in_main_thread(iter->second);
+        }
+        else if(existingNote->metadata_change_date() <= m_client->last_sync_date()
+                || iter->second.basically_equal_to(existingNote)) {
+          // Existing note hasn't been modified since last sync; simply update it from server
+          update_note_in_main_thread(existingNote, iter->second);
+        }
+        else {
+          // Logger.Debug ("Sync: Late conflict detection for '{0}'", noteUpdate.Title);
+          DBG_OUT("SyncManager: Content conflict in note update for note '%s'", iter->second.m_title.c_str());
+          // Note already exists locally, but has been modified since last sync; prompt user
+          if(m_sync_ui != 0) {
+            m_sync_ui->note_conflict_detected(note_mgr(), existingNote, iter->second, noteUpdateTitles);
+
+            // Suspend this thread while the GUI is presented to
+            // the user.
+            //syncThread.Suspend ();  TODO find out what to do with this !!!
+          }
+
+          // Note has been deleted or okay'd for overwrite
+          existingNote = find_note_by_uuid(iter->second.m_uuid);
+          if(existingNote == 0)
+            create_note_in_main_thread(iter->second);
+          else
+            update_note_in_main_thread(existingNote, iter->second);
+        }
+      }
+
+      // Note deletion may affect the GUI, so we have to use the
+      // delegate to run in the main gtk thread.
+      // To be consistent, any exceptions in the delgate will be caught
+      // and then rethrown in the synchronization thread.
+      sync_helper_delete_notes(m_sync_helper, &server);
+
+      // TODO: Add following updates to syncDialog treeview
+
+      set_state(PREPARE_UPLOAD);
+      // Look through all the notes modified on the client
+      // and upload new or modified ones to the server
+      std::list<Note::Ptr> newOrModifiedNotes;
+      std::list<Note::Ptr> notes = note_mgr().get_notes();
+      for(std::list<Note::Ptr>::iterator iter = notes.begin(); iter != notes.end(); ++iter) {
+        if(m_client->get_revision(*iter) == -1) {
+          // This is a new note that has never been synchronized to the server
+          // TODO: *OR* this is a note that we lost revision info for!!!
+          // TODO: Do the above NOW!!! (don't commit this dummy)
+          (*iter)->save();
+          newOrModifiedNotes.push_back(*iter);
+          if(m_sync_ui != 0)
+            m_sync_ui->note_synchronized((*iter)->get_title(), UPLOAD_NEW);
+        }
+        else if(m_client->get_revision(*iter) <= m_client->last_synchronized_revision()
+                && (*iter)->metadata_change_date() > m_client->last_sync_date()) {
+          (*iter)->save();
+          newOrModifiedNotes.push_back(*iter);
+          if(m_sync_ui != 0) {
+            m_sync_ui->note_synchronized((*iter)->get_title(), UPLOAD_MODIFIED);
+          }
+        }
+      }
+
+      DBG_OUT("Sync: Uploading %d note updates", newOrModifiedNotes.size());
+      if(newOrModifiedNotes.size() > 0) {
+        set_state(UPLOADING);
+        server->upload_notes(newOrModifiedNotes); // TODO: Callbacks to update GUI as upload progresses
+      }
+
+      // Handle notes deleted on client
+      std::list<std::string> locallyDeletedUUIDs;
+      std::list<std::string> all_note_uuids = server->get_all_note_uuids();
+      for(std::list<std::string>::iterator iter = all_note_uuids.begin();
+          iter != all_note_uuids.end(); ++iter) {
+        if(find_note_by_uuid(*iter) == 0) {
+          locallyDeletedUUIDs.push_back(*iter);
+          if(m_sync_ui != 0) {
+            std::string deletedTitle = *iter;
+            std::map<std::string, std::string> deleted_note_titles = m_client->deleted_note_titles();
+            if(deleted_note_titles.find(*iter) != deleted_note_titles.end()) {
+              deletedTitle = deleted_note_titles[*iter];
+            }
+            m_sync_ui->note_synchronized(deletedTitle, DELETE_FROM_SERVER);
+          }
+        }
+      }
+      if(locallyDeletedUUIDs.size() > 0) {
+        set_state(DELETE_SERVER_NOTES);
+        server->delete_notes(locallyDeletedUUIDs);
+      }
+
+      set_state(COMMITTING_CHANGES);
+      bool commitResult = server->commit_sync_transaction();
+      if(commitResult) {
+        // Apply this revision number to all new/modified notes since last sync
+        // TODO: Is this the best place to do this (after successful server commit)
+        for(std::list<Note::Ptr>::iterator iter = newOrModifiedNotes.begin();
+            iter != newOrModifiedNotes.end(); ++iter) {
+          m_client->set_revision(*iter, newRevision);
+        }
+        set_state(SUCCEEDED);
+      }
+      else {
+        set_state(FAILED);
+        // TODO: Figure out a way to let the GUI know what exactly failed
+      }
+
+      // This should be equivalent to newRevision
+      m_client->last_synchronized_revision(server->latest_revision());
+
+      m_client->last_sync_date(sharp::DateTime::now());
+
+      DBG_OUT("Sync: New revision: %d", m_client->last_synchronized_revision());
+
+      set_state(IDLE);
+
+    }
+    catch(std::exception & e) { // top-level try
+      ERR_OUT("Synchronization failed with the following exception: %s", e.what());
+      // TODO: Report graphically to user
+      try {
+        set_state(IDLE); // stop progress
+        set_state(FAILED);
+        set_state(IDLE); // required to allow user to sync again
+        if(server != 0) {
+          // TODO: All I really want to do here is cancel
+          //       the update lock timeout, but in most cases
+          //       this will delete lock files, too.  Do better!
+          server->cancel_sync_transaction();
+        }
+      }
+      catch(...)
+      {}
+    }
+  }
+
+
+  void SyncManager::resolve_conflict(SyncTitleConflictResolution resolution)
+  {
+    if(m_sync_thread) {
+      m_conflict_resolution = resolution;
+    }
+  }
+
+
+  void SyncManager::handle_note_buffer_changed(const Note::Ptr &)
+  {
+    // Note changed, iff a sync is coming up we kill the
+    // timer to avoid interupting the user (we want to
+    // make sure not to sync more often than the user's pref)
+    if(m_sync_thread == NULL && m_autosync_timer != 0) {
+      sharp::TimeSpan time_since_last_check = sharp::DateTime::now() - m_last_background_check;
+      if(time_since_last_check.total_minutes() > m_autosync_timeout_pref_minutes - 1) {
+        DBG_OUT("Note edited...killing autosync timer until next save or delete event");
+        m_autosync_timer->destroy();
+        m_autosync_timer.reset();
+      }
+    }
+  }
+
+
+  void SyncManager::preferences_setting_changed(const Glib::ustring &)
+  {
+    // Update sync item based on configuration.
+    update_sync_action();
+  }
+
+
+  void SyncManager::update_sync_action()
+  {
+    Glib::RefPtr<Gio::Settings> settings = Preferences::obj().get_schema_settings(Preferences::SCHEMA_SYNC);
+    std::string sync_addin_id = settings->get_string(Preferences::SYNC_SELECTED_SERVICE_ADDIN);
+    ActionManager::obj()["SyncNotesAction"]->set_sensitive(sync_addin_id != "");
+
+    int timeoutPref = settings->get_int(Preferences::SYNC_AUTOSYNC_TIMEOUT);
+    if(timeoutPref != m_autosync_timeout_pref_minutes) {
+      m_autosync_timeout_pref_minutes = timeoutPref;
+      if(m_autosync_timer != 0) {
+        m_autosync_timer->destroy();
+        m_autosync_timer.reset();
+      }
+      if(m_autosync_timeout_pref_minutes > 0) {
+        DBG_OUT("Autosync pref changed...restarting sync timer");
+        m_autosync_timeout_pref_minutes = m_autosync_timeout_pref_minutes >= 5 ? m_autosync_timeout_pref_minutes : 5;
+        m_last_background_check = sharp::DateTime::now();
+        // Perform a sync no sooner than user specified
+        m_current_autosync_timeout_minutes = m_autosync_timeout_pref_minutes;
+        m_autosync_timer = Glib::TimeoutSource::create(m_current_autosync_timeout_minutes * 60000);
+        m_autosync_timer->connect(sigc::mem_fun(*this, &SyncManager::background_sync_checker));
+      }
+    }
+  }
+
+
+  void SyncManager::handle_note_saved_or_deleted(const Note::Ptr &)
+  {
+    if(m_sync_thread == NULL && m_autosync_timer != 0 && m_autosync_timeout_pref_minutes > 0) {
+      sharp::TimeSpan time_since_last_check(sharp::DateTime::now() - m_last_background_check);
+      sharp::TimeSpan time_until_next_check(
+        sharp::TimeSpan(0, m_current_autosync_timeout_minutes, 0) - time_since_last_check);
+      if(time_until_next_check.total_minutes() < 1) {
+        DBG_OUT("Note saved or deleted within a minute of next autosync...resetting sync timer");
+        m_current_autosync_timeout_minutes = 1;
+        m_autosync_timer = Glib::TimeoutSource::create(m_current_autosync_timeout_minutes * 60000);
+        m_autosync_timer->connect(sigc::mem_fun(*this, &SyncManager::background_sync_checker));
+      }
+    }
+    else if(m_sync_thread == NULL && m_autosync_timer == 0 && m_autosync_timeout_pref_minutes > 0) {
+      DBG_OUT("Note saved or deleted...restarting sync timer");
+      m_last_background_check = sharp::DateTime::now();
+      // Perform a sync one minute after setting change
+      m_current_autosync_timeout_minutes = 1;
+      m_autosync_timer = Glib::TimeoutSource::create(m_current_autosync_timeout_minutes * 60000);
+      m_autosync_timer->connect(sigc::mem_fun(*this, &SyncManager::background_sync_checker));
+    }
+  }
+
+
+  bool SyncManager::background_sync_checker()
+  {
+    m_last_background_check = sharp::DateTime::now();
+    m_current_autosync_timeout_minutes = m_autosync_timeout_pref_minutes;
+    if(m_sync_thread != NULL) {
+      return false;
+    }
+    SyncServiceAddin *addin = get_configured_sync_service();
+    if(addin) {
+      // TODO: block sync while checking
+      SyncServer::Ptr server;
+      try {
+        server = SyncServer::Ptr(addin->create_sync_server());
+        if(server == 0) {
+          throw std::logic_error("addin->create_sync_server() returned null");
+        }
+      }
+      catch(std::exception & e) {
+        DBG_OUT("Exception while creating SyncServer: %s\n", e.what());
+        addin->post_sync_cleanup();// TODO: Needed?
+        return false;
+        // TODO: Figure out a clever way to get the specific error up to the GUI
+      }
+      bool server_has_updates = false;
+      bool client_has_updates = m_client->deleted_note_titles().size() > 0;
+      if(!client_has_updates) {
+        std::list<Note::Ptr> notes = note_mgr().get_notes();
+        for(std::list<Note::Ptr>::iterator iter = notes.begin(); iter != notes.end(); ++iter) {
+          if(m_client->get_revision(*iter) == -1 || (*iter)->metadata_change_date() > m_client->last_sync_date()) {
+            client_has_updates = true;
+            break;
+          }
+        }
+      }
+
+      // NOTE: Important to check, at least to verify
+      //       that server is available
+      try {
+        DBG_OUT("Checking server for updates");
+        server_has_updates = server->updates_available_since(m_client->last_synchronized_revision());
+      }
+      catch(...) {
+        // TODO: A libnotify bubble might be nice
+        DBG_OUT("Error connecting to server");
+        addin->post_sync_cleanup();
+        return false;
+      }
+
+      addin->post_sync_cleanup(); // Let FUSE unmount, etc
+
+      if(client_has_updates || server_has_updates) {
+        DBG_OUT("Detected that sync would be a good idea now");
+        // TODO: Check that it's safe to sync, block other sync UIs
+        perform_synchronization(SilentUI::create(note_mgr()));
+      }
+    }
+
+    return false;
+  }
+
+
+  void SyncManager::set_state(SyncState new_state)
+  {
+    m_state = new_state;
+    if(m_sync_ui != 0) {
+      // Notify the event handlers
+      try {
+        m_sync_ui->sync_state_changed(m_state);
+      }
+      catch(...)
+      {}
+    }
+  }
+
+
+  SyncServiceAddin *SyncManager::get_configured_sync_service()
+  {
+    SyncServiceAddin *addin = NULL;
+
+    std::string sync_service_id = Preferences::obj()
+      .get_schema_settings(Preferences::SCHEMA_SYNC)->get_string(Preferences::SYNC_SELECTED_SERVICE_ADDIN);
+    if(sync_service_id != "") {
+      addin = get_sync_service_addin(sync_service_id);
+    }
+
+    return addin;
+  }
+
+
+  SyncServiceAddin *SyncManager::get_sync_service_addin(const std::string & sync_service_id)
+  {
+    SyncServiceAddin *addin = NULL;
+
+    std::list<SyncServiceAddin*> addins;
+    Gnote::obj().default_note_manager().get_addin_manager().get_sync_service_addins(addins);
+    for(std::list<SyncServiceAddin*>::iterator iter = addins.begin(); iter != addins.end(); ++iter) {
+      if((*iter)->id() == sync_service_id) {
+        addin = *iter;
+        break;
+      }
+    }
+
+    return addin;
+  }
+
+
+  void SyncManager::on_sync_notes_activate()
+  {
+    ActionManager::obj()["NoteSynchronizationAction"]->activate();
+  }
+
+
+  void SyncManager::create_note_in_main_thread(const NoteUpdate & noteUpdate)
+  {
+    // Note creation may affect the GUI, so we have to use the
+    // delegate to run in the main gtk thread.
+    // To be consistent, any exceptions in the delgate will be caught
+    // and then rethrown in the synchronization thread.
+    sync_helper_create_note(m_sync_helper, const_cast<NoteUpdate*>(&noteUpdate));
+  }
+
+
+  void SyncManager::update_note_in_main_thread(const Note::Ptr & existingNote, const NoteUpdate & noteUpdate)
+  {
+    // Note update may affect the GUI, so we have to use the
+    // delegate to run in the main gtk thread.
+    // To be consistent, any exceptions in the delgate will be caught
+    // and then rethrown in the synchronization thread.
+    sync_helper_update_note(m_sync_helper, const_cast<Note::Ptr*>(&existingNote), const_cast<NoteUpdate*>(&noteUpdate));
+  }
+
+
+  void SyncManager::delete_note_in_main_thread(const Note::Ptr & existingNote)
+  {
+    // Note deletion may affect the GUI, so we have to use the
+    // delegate to run in the main gtk thread.
+    // To be consistent, any exceptions in the delgate will be caught
+    // and then rethrown in the synchronization thread.
+    sync_helper_delete_note(m_sync_helper, const_cast<Note::Ptr*>(&existingNote));
+  }
+
+
+  void SyncManager::update_local_note(const Note::Ptr & localNote, const NoteUpdate & serverNote, NoteSyncType syncType)
+  {
+    // In each case, update existingNote's content and revision
+    try {
+      localNote->load_foreign_note_xml(serverNote.m_xml_content, OTHER_DATA_CHANGED);
+    }
+    catch(...)
+    {} // TODO: Handle exception in case that serverNote.XmlContent is invalid XML
+    m_client->set_revision(localNote, serverNote.m_latest_revision);
+
+    // Update dialog's sync status
+    if(m_sync_ui != 0) {
+      m_sync_ui->note_synchronized(localNote->get_title(), syncType);
+    }
+  }
+
+
+  Note::Ptr SyncManager::find_note_by_uuid(const std::string & uuid)
+  {
+    return note_mgr().find_by_uri("note://tomboy/" + uuid);
+  }
+
+
+  NoteManager & SyncManager::note_mgr()
+  {
+    return Gnote::obj().default_note_manager();
+  }
+
+
+  bool SyncManager::synchronized_note_xml_matches(const std::string & noteXml1, const std::string & noteXml2)
+  {
+    try {
+      std::string title1, tags1, content1;
+      std::string title2, tags2, content2;
+
+      get_synchronized_xml_bits(noteXml1, title1, tags1, content1);
+      get_synchronized_xml_bits(noteXml2, title2, tags2, content2);
+
+      return title1 == title2 && tags1 == tags2 && content1 == content2;
+    }
+    catch(std::exception & e) {
+      DBG_OUT("synchronized_note_xml_matches threw exception: %s", e.what());
+      return false;
+    }
+  }
+
+
+  void SyncManager::get_synchronized_xml_bits(const std::string & noteXml, std::string & title, std::string & tags, std::string & content)
+  {
+    title = "";
+    tags = "";
+    content = "";
+
+    sharp::XmlReader xml;
+    xml.load_buffer(noteXml);
+    while(xml.read()) {
+      switch(xml.get_node_type()) {
+      case XML_READER_TYPE_ELEMENT:
+        if(xml.get_name() == "title") {
+          title = xml.read_string();
+        }
+        else if(xml.get_name() == "tags") {
+          tags = xml.read_inner_xml();
+          DBG_OUT("In the bits: tags = %s", tags.c_str()); // TODO: Delete
+        }
+        else if(xml.get_name() == "text") {
+          content = xml.read_inner_xml();
+        }
+      default:
+        break;
+      }
+    }
+  }
+
+
+  void SyncManager::on_delete_notes(GObject*, gpointer serv, gpointer)
+  {
+    SyncServer::Ptr & server = *static_cast<SyncServer::Ptr*>(serv);
+    // Make list of all local notes
+    std::list<Note::Ptr> localNotes = SyncManager::obj().note_mgr().get_notes();
+
+    // Get all notes currently on server
+    std::list<std::string> serverNotes = server->get_all_note_uuids();
+
+    // Delete notes locally that have been deleted on the server
+    for(std::list<Note::Ptr>::iterator iter = localNotes.begin(); iter != localNotes.end(); ++iter) {
+      if(SyncManager::obj().m_client->get_revision(*iter) != -1
+         && std::find(serverNotes.begin(), serverNotes.end(), (*iter)->id()) == serverNotes.end()) {
+        if(SyncManager::obj().m_sync_ui != 0) {
+          SyncManager::obj().m_sync_ui->note_synchronized((*iter)->get_title(), DELETE_FROM_CLIENT);
+        }
+        SyncManager::obj().note_mgr().delete_note(*iter);
+      }
+    }
+  }
+
+
+  void SyncManager::on_create_note(GObject*, gpointer note_update, gpointer)
+  {
+    NoteUpdate & noteUpdate = *static_cast<NoteUpdate*>(note_update);
+    Note::Ptr existingNote = SyncManager::obj().note_mgr().create_with_guid(noteUpdate.m_title, noteUpdate.m_uuid);
+    SyncManager::obj().update_local_note(existingNote, noteUpdate, DOWNLOAD_NEW);
+  }
+
+
+  void SyncManager::on_update_note(GObject*, gpointer existing_note, gpointer note_update, gpointer)
+  {
+    Note::Ptr *existingNote = static_cast<Note::Ptr*>(existing_note);
+    NoteUpdate & noteUpdate = *static_cast<NoteUpdate*>(note_update);
+    SyncManager::obj().update_local_note(*existingNote, noteUpdate, DOWNLOAD_MODIFIED);
+  }
+
+
+  void SyncManager::on_delete_note(GObject*, gpointer existing_note, gpointer)
+  {
+    Note::Ptr *existingNote = static_cast<Note::Ptr*>(existing_note);
+    SyncManager::obj().note_mgr().delete_note(*existingNote);
+  }
+
+
+  SyncLockInfo::SyncLockInfo()
+    : client_id(Preferences::obj().get_schema_settings(Preferences::SCHEMA_SYNC)->get_string(Preferences::SYNC_CLIENT_ID))
+    , transaction_id(sharp::uuid().string())
+    , renew_count(0)
+    , duration(0, 2, 0) // default of 2 minutes
+    , revision(0)
+  {
+  }
+
+
+  std::string SyncLockInfo::hash_string()
+  {
+    return str(boost::format("%1%-%2%-%3%-%4%-%5%") % transaction_id % client_id % renew_count % duration.string() % revision);
+  }
+
+
+  SyncServer::~SyncServer()
+  {}
+
+}
+}
diff --git a/src/synchronization/syncmanager.hpp b/src/synchronization/syncmanager.hpp
new file mode 100644
index 0000000..a374c10
--- /dev/null
+++ b/src/synchronization/syncmanager.hpp
@@ -0,0 +1,158 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2012 Aurimas Cernius
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef _SYNCHRONIZATION_SYNCMANAGER_HPP_
+#define _SYNCHRONIZATION_SYNCMANAGER_HPP_
+
+
+#include <exception>
+#include <map>
+#include <string>
+
+#include <glibmm/thread.h>
+
+#include "note.hpp"
+#include "syncdialog.hpp"
+#include "base/singleton.hpp"
+#include "sharp/datetime.hpp"
+#include "sharp/timespan.hpp"
+
+
+namespace gnote {
+namespace sync {
+
+  class SyncServiceAddin;
+  class SyncUI;
+
+  class SyncClient
+  {
+  public:
+    typedef std::tr1::shared_ptr<SyncClient> Ptr;
+
+    virtual int last_synchronized_revision() = 0;
+    virtual void last_synchronized_revision(int) = 0;
+    virtual sharp::DateTime last_sync_date() = 0;
+    virtual void last_sync_date(const sharp::DateTime &) = 0;
+    virtual int get_revision(const Note::Ptr & note) = 0;
+    virtual void set_revision(const Note::Ptr & note, int revision) = 0;
+    virtual std::map<std::string, std::string> deleted_note_titles() = 0;
+    virtual void reset() = 0;
+    virtual std::string associated_server_id() = 0;
+    virtual void associated_server_id(const std::string &) = 0;
+  };
+
+  class SyncManager
+    : public base::Singleton<SyncManager>
+  {
+  public:
+    ~SyncManager();
+    static void init();
+    void reset_client();
+    void perform_synchronization(const std::tr1::shared_ptr<SyncUI> & sync_ui);
+    void synchronization_thread();
+    void resolve_conflict(SyncTitleConflictResolution resolution);
+    bool synchronized_note_xml_matches(const std::string & noteXml1, const std::string & noteXml2);
+    SyncState state() const
+      {
+        return m_state;
+      }
+  private:
+    void _init();
+    void handle_note_saved_or_deleted(const Note::Ptr & note);
+    void handle_note_buffer_changed(const Note::Ptr & note);
+    void preferences_setting_changed(const Glib::ustring & key);
+    void update_sync_action();
+    bool background_sync_checker();
+    void set_state(SyncState new_state);
+    SyncServiceAddin *get_configured_sync_service();
+    SyncServiceAddin *get_sync_service_addin(const std::string & sync_service_id);
+    void on_sync_notes_activate();
+    void create_note_in_main_thread(const NoteUpdate & noteUpdate);
+    void update_note_in_main_thread(const Note::Ptr & existingNote, const NoteUpdate & noteUpdate);
+    void delete_note_in_main_thread(const Note::Ptr & existingNote);
+    void update_local_note(const Note::Ptr & localNote, const NoteUpdate & serverNote, NoteSyncType syncType);
+    Note::Ptr find_note_by_uuid(const std::string & uuid);
+    NoteManager & note_mgr();
+    void get_synchronized_xml_bits(const std::string & noteXml, std::string & title, std::string & tags, std::string & content);
+    static void on_delete_notes(GObject*, gpointer, gpointer);
+    static void on_create_note(GObject*, gpointer, gpointer);
+    static void on_update_note(GObject*, gpointer, gpointer, gpointer);
+    static void on_delete_note(GObject*, gpointer, gpointer);
+
+    SyncUI::Ptr m_sync_ui;
+    SyncClient::Ptr m_client;
+    SyncState m_state;
+    Glib::Thread *m_sync_thread;
+    SyncTitleConflictResolution m_conflict_resolution;
+    Glib::RefPtr<Glib::TimeoutSource> m_autosync_timer;
+    int m_autosync_timeout_pref_minutes;
+    int m_current_autosync_timeout_minutes;
+    sharp::DateTime m_last_background_check;
+    GObject *m_sync_helper;
+  };
+
+
+  class SyncLockInfo
+  {
+  public:
+    std::string client_id;
+    std::string transaction_id;
+    int renew_count;
+    sharp::TimeSpan duration;
+    int revision;
+
+    SyncLockInfo();
+    std::string hash_string();
+  };
+
+
+  class SyncServer
+  {
+  public:
+    typedef std::tr1::shared_ptr<SyncServer> Ptr;
+
+    virtual ~SyncServer();
+
+    virtual bool begin_sync_transaction() = 0;
+    virtual bool commit_sync_transaction() = 0;
+    virtual bool cancel_sync_transaction() = 0;
+    virtual std::list<std::string> get_all_note_uuids() = 0;
+    virtual std::map<std::string, NoteUpdate> get_note_updates_since(int revision) = 0;
+    virtual void delete_notes(const std::list<std::string> & deletedNoteUUIDs) = 0;
+    virtual void upload_notes(const std::list<Note::Ptr> & notes) = 0;
+    virtual int latest_revision() = 0; // NOTE: Only reliable during a transaction
+    virtual SyncLockInfo current_sync_lock() = 0;
+    virtual std::string id() = 0;
+    virtual bool updates_available_since(int revision) = 0;
+  };
+
+
+  class GnoteSyncException
+    : public std::runtime_error
+  {
+  public:
+    GnoteSyncException(const char * what_arg) : std::runtime_error(what_arg){}
+    GnoteSyncException(const std::string & what_arg) : std::runtime_error(what_arg){}
+  };
+
+}
+}
+
+#endif
diff --git a/src/synchronization/syncserviceaddin.cpp b/src/synchronization/syncserviceaddin.cpp
new file mode 100644
index 0000000..2a02d5d
--- /dev/null
+++ b/src/synchronization/syncserviceaddin.cpp
@@ -0,0 +1,29 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2012 Aurimas Cernius
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "syncserviceaddin.hpp"
+
+namespace gnote {
+namespace sync {
+
+const char * SyncServiceAddin::IFACE_NAME = "gnote::sync::SyncServiceAddin";
+
+}
+}
diff --git a/src/synchronization/syncserviceaddin.hpp b/src/synchronization/syncserviceaddin.hpp
new file mode 100644
index 0000000..f966ff1
--- /dev/null
+++ b/src/synchronization/syncserviceaddin.hpp
@@ -0,0 +1,64 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2012 Aurimas Cernius
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef _SYNCHRONIZATION_SYNCSERVICEADDIN_HPP_
+#define _SYNCHRONIZATION_SYNCSERVICEADDIN_HPP_
+
+
+#include <gtkmm/widget.h>
+
+#include "abstractaddin.hpp"
+#include "syncmanager.hpp"
+
+
+
+namespace gnote {
+namespace sync {
+
+  class SyncServiceAddin
+    : public AbstractAddin
+  {
+  public:
+    typedef sigc::slot<void> EventHandler;
+    static const char * IFACE_NAME;
+
+    virtual SyncServer::Ptr create_sync_server() = 0;
+    virtual void post_sync_cleanup() = 0;
+    virtual Gtk::Widget *create_preferences_control(EventHandler requiredPrefChanged) = 0;
+    virtual bool save_configuration() = 0;
+    virtual void reset_configuration() = 0;
+    virtual bool is_configured() = 0;
+    virtual bool are_settings_valid()
+      {
+        return true;
+      }
+    virtual std::string name() = 0;
+    virtual std::string id() = 0;
+    virtual bool is_supported() = 0;
+    virtual void initialize () = 0;
+    virtual void shutdown () = 0;
+    virtual bool initialized () = 0;
+  };
+
+}
+}
+
+
+#endif
diff --git a/src/synchronization/syncui.cpp b/src/synchronization/syncui.cpp
new file mode 100644
index 0000000..80c2aaa
--- /dev/null
+++ b/src/synchronization/syncui.cpp
@@ -0,0 +1,107 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2012 Aurimas Cernius
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "syncui.hpp"
+
+
+namespace {
+
+  typedef GObject GnoteSyncUI;
+  typedef GObjectClass GnoteSyncUIClass;
+
+  G_DEFINE_TYPE(GnoteSyncUI, gnote_sync_ui, G_TYPE_OBJECT)
+
+  void gnote_sync_ui_init(GnoteSyncUI*)
+  {
+  }
+
+  void gnote_sync_ui_class_init(GnoteSyncUIClass * klass)
+  {
+    g_signal_new("connecting", G_TYPE_FROM_CLASS(klass),
+                 GSignalFlags(G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS),
+                 0, NULL, NULL, g_cclosure_marshal_VOID__VOID,
+                 G_TYPE_NONE, 0, NULL);
+    g_signal_new("idle", G_TYPE_FROM_CLASS(klass),
+                 GSignalFlags(G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS),
+                 0, NULL, NULL, g_cclosure_marshal_VOID__VOID,
+                 G_TYPE_NONE, 0, NULL);
+  }
+
+  GnoteSyncUI * gnote_sync_ui_new()
+  {
+    g_type_init();
+    return static_cast<GnoteSyncUI*>(g_object_new(gnote_sync_ui_get_type(), NULL));
+  }
+
+}
+
+
+namespace gnote {
+namespace sync {
+
+  SyncUI::SyncUI()
+    : m_obj(gnote_sync_ui_new())
+  {
+    g_signal_connect(m_obj, "connecting", G_CALLBACK(SyncUI::on_signal_connecting), this);
+    g_signal_connect(m_obj, "idle", G_CALLBACK(SyncUI::on_signal_idle), this);
+  }
+
+
+  void SyncUI::on_signal_connecting(GObject*, gpointer data)
+  {
+    static_cast<SyncUI*>(data)->m_signal_connecting.emit();
+  }
+
+
+  void SyncUI::on_signal_idle(GObject*, gpointer data)
+  {
+    static_cast<SyncUI*>(data)->m_signal_idle.emit();
+  }
+
+
+  sigc::connection SyncUI::signal_connecting_connect(const SlotConnecting & slot)
+  {
+    return m_signal_connecting.connect(slot);
+  }
+
+
+  void SyncUI::signal_connecting_emit()
+  {
+    gdk_threads_enter();
+    g_signal_emit_by_name(m_obj, "connecting");
+    gdk_threads_leave();
+  }
+
+
+  sigc::connection SyncUI::signal_idle_connect(const SlotIdle & slot)
+  {
+    return m_signal_idle.connect(slot);
+  }
+
+
+  void SyncUI::signal_idle_emit()
+  {
+    gdk_threads_enter();
+    g_signal_emit_by_name(m_obj, "idle");
+    gdk_threads_leave();
+  }
+
+}
+}
diff --git a/src/synchronization/syncui.hpp b/src/synchronization/syncui.hpp
new file mode 100644
index 0000000..fe878ad
--- /dev/null
+++ b/src/synchronization/syncui.hpp
@@ -0,0 +1,71 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2012 Aurimas Cernius
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef _SYNCHRONIZATION_SYNCUI_HPP_
+#define _SYNCHRONIZATION_SYNCUI_HPP_
+
+
+#include <list>
+#include <string>
+#include <tr1/memory>
+
+#include <glib-object.h>
+
+#include "syncutils.hpp"
+
+
+namespace gnote {
+namespace sync {
+
+  class SyncUI
+    : public std::tr1::enable_shared_from_this<SyncUI>
+  {
+  public:
+    typedef std::tr1::shared_ptr<SyncUI> Ptr;
+    typedef sigc::slot<void> SlotConnecting;
+    typedef sigc::slot<void> SlotIdle;
+
+    virtual void sync_state_changed(SyncState state) = 0;
+    virtual void note_synchronized(const std::string & noteTitle, NoteSyncType type) = 0;
+    virtual void note_conflict_detected(NoteManager & manager,
+                                        const Note::Ptr & localConflictNote,
+                                        NoteUpdate remoteNote,
+                                        const std::list<std::string> & noteUpdateTitles) = 0;
+
+    sigc::connection signal_connecting_connect(const SlotConnecting & slot);
+    void signal_connecting_emit();
+    sigc::connection signal_idle_connect(const SlotIdle & slot);
+    void signal_idle_emit();
+  protected:
+    SyncUI();
+  private:
+    static void on_signal_connecting(GObject*, gpointer);
+    static void on_signal_idle(GObject*, gpointer);
+
+    sigc::signal<void> m_signal_connecting;
+    sigc::signal<void> m_signal_idle;
+    GObject *m_obj;
+  };
+
+}
+}
+
+
+#endif
diff --git a/src/synchronization/syncutils.cpp b/src/synchronization/syncutils.cpp
new file mode 100644
index 0000000..98f27af
--- /dev/null
+++ b/src/synchronization/syncutils.cpp
@@ -0,0 +1,103 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2012 Aurimas Cernius
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "syncutils.hpp"
+#include "sharp/xmlreader.hpp"
+
+namespace gnote {
+namespace sync {
+
+  NoteUpdate::NoteUpdate(const std::string & xml_content, const std::string & title, const std::string & uuid, int latest_revision)
+  {
+    m_xml_content = xml_content;
+    m_title = title;
+    m_uuid = uuid;
+    m_latest_revision = latest_revision;
+
+    // TODO: Clean this up (and remove title parameter?)
+    if(m_xml_content.length() > 0) {
+      sharp::XmlReader xml;
+      xml.load_buffer(m_xml_content);
+      //xml.Namespaces = false;
+
+      while(xml.read()) {
+        if(xml.get_node_type() == XML_READER_TYPE_ELEMENT) {
+          if(xml.get_name() == "title") {
+            m_title = xml.read_string();
+          }
+        }
+      }
+    }
+  }
+
+
+  bool NoteUpdate::basically_equal_to(const Note::Ptr & existing_note)
+  {
+    // NOTE: This would be so much easier if NoteUpdate
+    //       was not just a container for a big XML string
+    sharp::XmlReader xml;
+    xml.load_buffer(m_xml_content);
+    std::auto_ptr<NoteData> update_data(NoteArchiver::obj().read(xml, m_uuid));
+    xml.close();
+
+    // NOTE: Mostly a hack to ignore missing version attributes
+    std::string existing_inner_content = get_inner_content(existing_note->data().text());
+    std::string update_inner_content = get_inner_content(update_data->text());
+
+    return existing_inner_content == update_inner_content &&
+           existing_note->data().title() == update_data->title() &&
+           compare_tags(existing_note->data().tags(), update_data->tags());
+           // TODO: Compare open-on-startup, pinned
+  }
+
+
+  std::string NoteUpdate::get_inner_content(const std::string & full_content_element) const
+  {
+    /*const string noteContentRegex =
+				"^<note-content([^>]+version=""(?<contentVersion>[^""]*)"")?[^>]*((/>)|(>(?<innerContent>.*)</note-content>))$";
+			Match m = Regex.Match (fullContentElement, noteContentRegex, RegexOptions.Singleline);
+			Group contentGroup = m.Groups ["innerContent"];
+			if (!contentGroup.Success)
+				return null;
+			return contentGroup.Value;*/
+    sharp::XmlReader xml;
+    xml.load_buffer(full_content_element);
+    if(xml.read() && xml.get_name() == "note-content") {
+      return xml.read_inner_xml();
+    }
+    return "";
+  }
+
+
+  bool NoteUpdate::compare_tags(const std::map<std::string, Tag::Ptr> set1, const std::map<std::string, Tag::Ptr> set2) const
+  {
+    if(set1.size() != set2.size()) {
+      return false;
+    }
+    for(std::map<std::string, Tag::Ptr>::const_iterator iter = set1.begin(); iter != set1.end(); ++iter) {
+      if(set2.find(iter->first) == set2.end()) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+}
+}
diff --git a/src/synchronization/syncutils.hpp b/src/synchronization/syncutils.hpp
new file mode 100644
index 0000000..e5a4efe
--- /dev/null
+++ b/src/synchronization/syncutils.hpp
@@ -0,0 +1,86 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2012 Aurimas Cernius
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef _SYNCHRONIZATION_SYNCUTILS_HPP_
+#define _SYNCHRONIZATION_SYNCUTILS_HPP_
+
+
+#include <string>
+
+#include "note.hpp"
+
+
+namespace gnote {
+namespace sync {
+
+  enum SyncState {
+    IDLE,
+    NO_CONFIGURED_SYNC_SERVICE,
+    SYNC_SERVER_CREATION_FAILED,
+    CONNECTING,
+    ACQUIRING_LOCK,
+    LOCKED,
+    PREPARE_DOWNLOAD,
+    DOWNLOADING,
+    PREPARE_UPLOAD,
+    UPLOADING,
+    DELETE_SERVER_NOTES,
+    COMMITTING_CHANGES,
+    SUCCEEDED,
+    FAILED,
+    USER_CANCELLED
+  };
+
+  enum NoteSyncType {
+    UPLOAD_NEW,
+    UPLOAD_MODIFIED,
+    DOWNLOAD_NEW,
+    DOWNLOAD_MODIFIED,
+    DELETE_FROM_SERVER,
+    DELETE_FROM_CLIENT
+  };
+
+  enum SyncTitleConflictResolution {
+    CANCEL,
+    OVERWRITE_EXISTING,
+    RENAME_EXISTING_NO_UPDATE,
+    RENAME_EXISTING_AND_UPDATE
+  };
+
+  class NoteUpdate
+  {
+  public:
+    std::string m_xml_content;//Empty if deleted?
+    std::string m_title;
+    std::string m_uuid; //needed?
+    int m_latest_revision;
+
+    NoteUpdate(const std::string & xml_content, const std::string & title, const std::string & uuid, int latest_revision);
+    bool basically_equal_to(const Note::Ptr & existing_note);
+  private:
+    std::string get_inner_content(const std::string & full_content_element) const;
+    bool compare_tags(const std::map<std::string, Tag::Ptr> set1, const std::map<std::string, Tag::Ptr> set2) const;
+  };
+
+}
+}
+
+
+#endif



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