[gnote] Add core synchronization support
- From: Aurimas Äernius <aurimasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnote] Add core synchronization support
- Date: Wed, 25 Jan 2012 22:04:03 +0000 (UTC)
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 = ¬eUpdateTitles;
+ // 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*>(¬eUpdate));
+ }
+
+
+ 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*>(¬eUpdate));
+ }
+
+
+ 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]