[gnote] Add NoteDirectoryWatcher add-in



commit 696b6b2125e1d121358e3f84f2dceecd307b2ee7
Author: Aurimas Äernius <aurisc4 gmail com>
Date:   Thu Jul 5 23:28:46 2012 +0300

    Add NoteDirectoryWatcher add-in
    
    An add-in for monitoring external note directory modifications.

 configure.ac                                       |    1 +
 po/POTFILES.in                                     |    1 +
 src/addins/Makefile.am                             |    1 +
 src/addins/notedirectorywatcher/Makefile.am        |    9 +
 .../notedirectorywatcherapplicationaddin.cpp       |  319 ++++++++++++++++++++
 .../notedirectorywatcherapplicationaddin.hpp       |   94 ++++++
 6 files changed, 425 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 3720a32..0f09770 100644
--- a/configure.ac
+++ b/configure.ac
@@ -169,6 +169,7 @@ src/addins/exporttohtml/Makefile
 src/addins/filesystemsyncservice/Makefile
 src/addins/fixedwidth/Makefile
 src/addins/inserttimestamp/Makefile
+src/addins/notedirectorywatcher/Makefile
 src/addins/noteoftheday/Makefile
 src/addins/printnotes/Makefile
 src/addins/replacetitle/Makefile
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 811e376..5ce665e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -11,6 +11,7 @@ src/addins/fixedwidth/fixedwidthmenuitem.cpp
 src/addins/fixedwidth/fixedwidthnoteaddin.cpp
 src/addins/inserttimestamp/inserttimestampnoteaddin.cpp
 src/addins/inserttimestamp/inserttimestamppreferences.cpp
+src/addins/notedirectorywatcher/notedirectorywatcherapplicationaddin.cpp
 src/addins/noteoftheday/noteofthedayapplicationaddin.cpp
 src/addins/noteoftheday/noteoftheday.cpp
 src/addins/noteoftheday/noteofthedaypreferences.cpp
diff --git a/src/addins/Makefile.am b/src/addins/Makefile.am
index 01dee7d..4dc3f08 100644
--- a/src/addins/Makefile.am
+++ b/src/addins/Makefile.am
@@ -7,6 +7,7 @@ SUBDIRS = backlinks \
 	filesystemsyncservice \
 	fixedwidth \
 	inserttimestamp \
+	notedirectorywatcher \
 	noteoftheday \
 	printnotes \
 	replacetitle \
diff --git a/src/addins/notedirectorywatcher/Makefile.am b/src/addins/notedirectorywatcher/Makefile.am
new file mode 100644
index 0000000..19bfb34
--- /dev/null
+++ b/src/addins/notedirectorywatcher/Makefile.am
@@ -0,0 +1,9 @@
+
+include $(builddir)/../addins.mk
+
+addinsdir = $(ADDINSDIR)
+addins_LTLIBRARIES = notedirectorywatcher.la
+
+
+notedirectorywatcher_la_SOURCES = notedirectorywatcherapplicationaddin.hpp notedirectorywatcherapplicationaddin.cpp \
+	$(NULL)
diff --git a/src/addins/notedirectorywatcher/notedirectorywatcherapplicationaddin.cpp b/src/addins/notedirectorywatcher/notedirectorywatcherapplicationaddin.cpp
new file mode 100644
index 0000000..b39fd4b
--- /dev/null
+++ b/src/addins/notedirectorywatcher/notedirectorywatcherapplicationaddin.cpp
@@ -0,0 +1,319 @@
+/*
+ * 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 <fstream>
+
+#include <glibmm/i18n.h>
+
+#include "debug.hpp"
+#include "gnote.hpp"
+#include "notedirectorywatcherapplicationaddin.hpp"
+#include "notemanager.hpp"
+#include "sharp/files.hpp"
+#include "sharp/string.hpp"
+
+
+namespace notedirectorywatcher {
+
+NoteDirectoryWatcherModule::NoteDirectoryWatcherModule()
+{
+  ADD_INTERFACE_IMPL(NoteDirectoryWatcherApplicationAddin);
+}
+
+const char * NoteDirectoryWatcherModule::id() const
+{
+  return "NoteDirectoryWatcherAddin";
+}
+
+const char * NoteDirectoryWatcherModule::name() const
+{
+  return _("Note Directory Watcher");
+}
+
+const char * NoteDirectoryWatcherModule::description() const
+{
+  return _("Watch your Gnote note directory for changes to your notes.");
+}
+
+const char * NoteDirectoryWatcherModule::authors() const
+{
+  return _("Aurimas Äernius and Tomboy original authors");
+}
+
+int NoteDirectoryWatcherModule::category() const
+{
+  return gnote::ADDIN_CATEGORY_TOOLS;
+}
+
+const char * NoteDirectoryWatcherModule::version() const
+{
+  return "0.1";
+}
+
+
+
+
+NoteDirectoryWatcherApplicationAddin::NoteDirectoryWatcherApplicationAddin()
+  : m_initialized(false)
+{
+}
+
+void NoteDirectoryWatcherApplicationAddin::initialize()
+{
+  gnote::NoteManager & note_manager = gnote::Gnote::obj().default_note_manager();
+  std::string note_path = note_manager.get_notes_dir();
+  note_manager.signal_note_saved
+    .connect(sigc::mem_fun(*this, &NoteDirectoryWatcherApplicationAddin::handle_note_saved));
+
+  Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(note_path);
+  m_file_system_watcher = file->monitor_directory();
+
+  m_file_system_watcher->signal_changed()
+    .connect(sigc::mem_fun(*this, &NoteDirectoryWatcherApplicationAddin::handle_file_system_change_event));
+
+  m_initialized = true;
+}
+
+void NoteDirectoryWatcherApplicationAddin::shutdown()
+{
+  m_file_system_watcher->cancel();
+  m_initialized = false;
+}
+
+bool NoteDirectoryWatcherApplicationAddin::initialized()
+{
+  return m_initialized;
+}
+
+void NoteDirectoryWatcherApplicationAddin::handle_note_saved(const gnote::Note::Ptr & note)
+{
+  m_note_save_times[note->id()] = sharp::DateTime::now();
+}
+
+void NoteDirectoryWatcherApplicationAddin::handle_file_system_change_event(
+  const Glib::RefPtr<Gio::File> & file, const Glib::RefPtr<Gio::File> &, Gio::FileMonitorEvent event_type)
+{
+  switch(event_type) {
+  case Gio::FILE_MONITOR_EVENT_CHANGED:
+  case Gio::FILE_MONITOR_EVENT_DELETED:
+  case Gio::FILE_MONITOR_EVENT_CREATED:
+  case Gio::FILE_MONITOR_EVENT_MOVED:
+    break;
+  default:
+    return;
+  }
+
+  std::string note_id = get_id(file->get_path());
+
+  DBG_OUT("NoteDirectoryWatcher: %s has %d (note_id=%s)", file->get_path().c_str(), int(event_type), note_id.c_str());
+
+  // Record that the file has been added/changed/deleted.  Adds/changes trump
+  // deletes.  Record the date.
+  m_lock.lock();
+  try {
+    std::map<std::string, NoteFileChangeRecord>::iterator record = m_file_change_records.find(note_id);
+    if(record == m_file_change_records.end()) {
+      m_file_change_records[note_id] = NoteFileChangeRecord();
+      record = m_file_change_records.find(note_id);
+    }
+
+    if(event_type == Gio::FILE_MONITOR_EVENT_CHANGED) {
+      record->second.changed = true;
+      record->second.deleted = false;
+    }
+    else if(event_type == Gio::FILE_MONITOR_EVENT_CREATED) {
+      record->second.changed = true;
+      record->second.deleted = false;
+    }
+    else if(event_type == Gio::FILE_MONITOR_EVENT_MOVED) {
+      record->second.changed = true;
+      record->second.deleted = false;
+    }
+    else if(event_type == Gio::FILE_MONITOR_EVENT_DELETED) {
+      if(!record->second.changed) {
+	record->second.deleted = true;
+      }
+    }
+
+    record->second.last_change = sharp::DateTime::now();
+  }
+  catch(...)
+  {}
+  m_lock.unlock();
+
+  Glib::RefPtr<Glib::TimeoutSource> timeout = Glib::TimeoutSource::create(5000);
+  timeout->connect(sigc::mem_fun(*this, &NoteDirectoryWatcherApplicationAddin::handle_timeout));
+  timeout->attach();
+}
+
+std::string NoteDirectoryWatcherApplicationAddin::get_id(const std::string & path)
+{
+  std::string dir_separator;
+  dir_separator += G_DIR_SEPARATOR;
+  int last_slash = sharp::string_last_index_of(path, std::string(dir_separator));
+  int first_period = sharp::string_index_of(path, ".", last_slash);
+
+  return path.substr(last_slash + 1, first_period - last_slash - 1);
+}
+
+bool NoteDirectoryWatcherApplicationAddin::handle_timeout()
+{
+  m_lock.lock();
+  try {
+    std::vector<std::string> keysToRemove(m_file_change_records.size());
+
+    for(std::map<std::string, NoteFileChangeRecord>::iterator iter = m_file_change_records.begin();
+        iter != m_file_change_records.end(); ++iter) {
+      DBG_OUT("NoteDirectoryWatcher: Handling (timeout) %s", iter->first.c_str());
+
+      // Check that Note.Saved event didn't occur within 3 seconds of last write
+      if(m_note_save_times.find(iter->first) != m_note_save_times.end() &&
+          std::abs((m_note_save_times[iter->first] - iter->second.last_change).total_seconds()) <= 3) {
+        DBG_OUT("NoteDirectoryWatcher: Ignoring (timeout) because it was probably a Gnote write");
+        keysToRemove.push_back(iter->first);
+        continue;
+      }
+      // TODO: Take some actions to clear note_save_times? Not a large structure...
+
+      sharp::DateTime last_change(iter->second.last_change);
+      if(sharp::DateTime::now() > last_change.add_seconds(4)) {
+        if(iter->second.deleted) {
+          delete_note(iter->first);
+        }
+        else {
+          add_or_update_note(iter->first);
+        }
+
+        keysToRemove.push_back(iter->first);
+      }
+    }
+
+    for(std::vector<std::string>::iterator note_id = keysToRemove.begin(); note_id != keysToRemove.end(); ++note_id) {
+      m_file_change_records.erase(*note_id);
+    }
+  }
+  catch(...)
+  {}
+  m_lock.unlock();
+
+  return false;
+}
+
+void NoteDirectoryWatcherApplicationAddin::delete_note(const std::string & note_id)
+{
+  DBG_OUT("NoteDirectoryWatcher: deleting %s because file deleted.", note_id.c_str());
+
+  std::string note_uri = make_uri(note_id);
+
+  gnote::Note::Ptr note_to_delete = gnote::Gnote::obj().default_note_manager().find_by_uri(note_uri);
+  if(note_to_delete != 0) {
+    gnote::Gnote::obj().default_note_manager().delete_note(note_to_delete);
+  }
+  else {
+    DBG_OUT("notedirectorywatcher: did not delete %s because note not found.", note_id.c_str());
+  }
+}
+
+void NoteDirectoryWatcherApplicationAddin::add_or_update_note(const std::string & note_id)
+{
+  std::string note_path = Glib::build_filename(
+    gnote::Gnote::obj().default_note_manager().get_notes_dir(), note_id + ".note");
+  if (!sharp::file_exists(note_path)) {
+    DBG_OUT("NoteDirectoryWatcher: Not processing update of %s because file does not exist.", note_path.c_str());
+    return;
+  }
+
+  std::string noteXml;
+  try {
+    std::ifstream reader;
+    reader.exceptions(std::ios_base::badbit);
+    reader.open(note_path.c_str());
+    std::string line;
+    while(std::getline(reader, line)) {
+      noteXml += line + '\n';
+    }
+    reader.close();
+  }
+  catch(std::ios::failure & e) {
+    ERR_OUT("NoteDirectoryWatcher: Update aborted, error reading %s: %s", note_path.c_str(), e.what());
+    return;
+  }
+
+  if(noteXml == "") {
+    DBG_OUT("NoteDirectoryWatcher: Update aborted, %s had no contents.", note_path.c_str());
+    return;
+  }
+
+  std::string note_uri = make_uri(note_id);
+
+  gnote::Note::Ptr note = gnote::Gnote::obj().default_note_manager().find_by_uri(note_uri);
+
+  bool is_new_note = false;
+
+  if(note == 0) {
+    is_new_note = true;
+    DBG_OUT("NoteDirectoryWatcher: Adding %s because file changed.", note_id.c_str());
+
+    std::string title;
+    Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create("<title>([^<]+)</title>", Glib::REGEX_MULTILINE);
+    Glib::MatchInfo match_info;
+    if(regex->match(noteXml, match_info)) {
+      title = match_info.fetch(1);
+    }
+    else {
+      ERR_OUT("NoteDirectoryWatcher: Error reading note title from %s", note_path.c_str());
+      return;
+    }
+
+    try {
+      note = gnote::Gnote::obj().default_note_manager().create_with_guid(title, note_id);
+      if(note == 0) {
+        ERR_OUT("NoteDirectoryWatcher: Unknown error creating note from %s", note_path.c_str());
+        return;
+      }
+    }
+    catch(std::exception & e) {
+      ERR_OUT("NoteDirectoryWatcher: Error creating note from %s: %s", note_path.c_str(), e.what());
+      return;
+    }
+  }
+
+  if(is_new_note) {
+    DBG_OUT("NoteDirectoryWatcher: Updating %s because file changed.", note_id.c_str());
+  }
+  try {
+    note->load_foreign_note_xml(noteXml, gnote::CONTENT_CHANGED);
+  }
+  catch(std::exception & e) {
+    ERR_OUT("NoteDirectoryWatcher: Update aborted, error parsing %s: %s", note_path.c_str(), e.what());
+    if(is_new_note) {
+      gnote::Gnote::obj().default_note_manager().delete_note(note);
+    }
+  }
+}
+
+std::string NoteDirectoryWatcherApplicationAddin::make_uri(const std::string & note_id)
+{
+  return "note://gnote/" + note_id;
+}
+
+
+}
+
diff --git a/src/addins/notedirectorywatcher/notedirectorywatcherapplicationaddin.hpp b/src/addins/notedirectorywatcher/notedirectorywatcherapplicationaddin.hpp
new file mode 100644
index 0000000..8aabfde
--- /dev/null
+++ b/src/addins/notedirectorywatcher/notedirectorywatcherapplicationaddin.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 _NOTE_DIRECTORY_WATCHER_APPLICATION_ADDIN_
+#define _NOTE_DIRECTORY_WATCHER_APPLICATION_ADDIN_
+
+
+#include <map>
+
+#include <glibmm/threads.h>
+#include <giomm/filemonitor.h>
+
+#include "applicationaddin.hpp"
+#include "note.hpp"
+#include "sharp/dynamicmodule.hpp"
+
+
+namespace notedirectorywatcher {
+
+class NoteDirectoryWatcherModule
+  : public sharp::DynamicModule
+{
+public:
+  NoteDirectoryWatcherModule();
+  virtual const char * id() const;
+  virtual const char * name() const;
+  virtual const char * description() const;
+  virtual const char * authors() const;
+  virtual int          category() const;
+  virtual const char * version() const;
+};
+
+
+DECLARE_MODULE(NoteDirectoryWatcherModule);
+
+struct NoteFileChangeRecord
+{
+  sharp::DateTime last_change;
+  bool deleted;
+  bool changed;
+};
+
+
+class NoteDirectoryWatcherApplicationAddin
+  : public gnote::ApplicationAddin
+{
+public:
+  static NoteDirectoryWatcherApplicationAddin *create()
+    {
+      return new NoteDirectoryWatcherApplicationAddin;
+    }
+  virtual void initialize();
+  virtual void shutdown();
+  virtual bool initialized();
+private:
+  static std::string get_id(const std::string & path);
+  static std::string make_uri(const std::string & note_id);
+
+  NoteDirectoryWatcherApplicationAddin();
+  void handle_note_saved(const gnote::Note::Ptr &);
+  void handle_file_system_change_event(const Glib::RefPtr<Gio::File> & file,
+                                       const Glib::RefPtr<Gio::File> & other_file,
+                                       Gio::FileMonitorEvent event_type);
+  bool handle_timeout();
+  void delete_note(const std::string & note_id);
+  void add_or_update_note(const std::string & note_id);
+
+  Glib::RefPtr<Gio::FileMonitor> m_file_system_watcher;
+
+  std::map<std::string, NoteFileChangeRecord> m_file_change_records;
+  std::map<std::string, sharp::DateTime> m_note_save_times;
+  bool m_initialized;
+  Glib::Threads::Mutex m_lock;
+};
+
+}
+
+#endif



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