« Return to Thread: WIP Note Directory Watcher Patch - v2

WIP Note Directory Watcher Patch - v2

by Michael Fletcher-3 :: Rate this Message:

Reply to Author | View in Thread

The NoteDirectoryWatcher Addin is nearly ready.  Its useful and there
is little chance of you losing your data.  Please check out the
updated patch and preamble.



If you use an external process to modify you ".note" files (ie
DropBox) there is a significant risk that you will lose you note data.
 Tomboy will overwrite the note on disk with its representation in
memory.

NoteDirectoryWatcher will watch your ~/.tomboy directory for changes
and update Tomboy's in-memory copy of the note if the note changes.
This reduces the change of losing notes.

Unfortunately there is still a small chance you will lose data.  If you
a) Update a note externally.
b) Change the note in Tomboy more than 4 seconds but less than five
seconds after A.  I'll work on this later.

There is still one corner case that needs to be handled
(FileSystemWatcher.Error) but its highly unlikely you will encounter
it.  Running Tomboy under windows with your notes on a SMB share is a
good way to try :).

[watch.patch]

diff --git a/Tomboy/Addins/Makefile.am b/Tomboy/Addins/Makefile.am
index 57f5f56..9520b67 100644
--- a/Tomboy/Addins/Makefile.am
+++ b/Tomboy/Addins/Makefile.am
@@ -13,5 +13,6 @@ SUBDIRS = \
  SshSyncService \
  StickyNoteImport \
  Tasque \
+ NoteDirectoryWatcher \
  WebDavSyncService
 
diff --git a/Tomboy/Addins/NoteDirectoryWatcher/Makefile.am b/Tomboy/Addins/NoteDirectoryWatcher/Makefile.am
new file mode 100644
index 0000000..8250fd2
--- /dev/null
+++ b/Tomboy/Addins/NoteDirectoryWatcher/Makefile.am
@@ -0,0 +1,39 @@
+include $(top_srcdir)/Makefile.include
+
+CSFLAGS = \
+ -debug \
+ -define:DEBUG \
+ -target:library
+
+ASSEMBLIES = \
+ $(LINK_TOMBOY_EXE) \
+ $(GTKSHARP_LIBS) \
+ $(LINK_MONO_ADDINS) \
+ -r:Mono.Posix
+
+ADDIN_NAME = NoteDirectoryWatcher
+TARGET = $(ADDIN_NAME).dll
+CSFILES = \
+ $(srcdir)/NoteDirectoryWatcherApplicationAddin.cs
+RESOURCES = \
+ -resource:$(srcdir)/$(ADDIN_NAME).addin.xml
+
+$(TARGET).mdb: $(TARGET)
+
+$(TARGET): $(CSFILES) $(top_builddir)/Tomboy/Tomboy.exe
+ $(CSC) -out:$@ $(CSFLAGS) $(ASSEMBLIES) $(CSFILES) $(RESOURCES)
+
+
+addinsdir = $(pkglibdir)/addins
+addins_DATA = \
+ $(TARGET) \
+ $(TARGET).mdb
+
+EXTRA_DIST =             \
+ $(CSFILES) \
+ $(srcdir)/$(ADDIN_NAME).addin.xml
+
+CLEANFILES = \
+ $(TARGET).mdb \
+ $(TARGET)
+
diff --git a/Tomboy/Addins/NoteDirectoryWatcher/NoteDirectoryWatcher.addin.xml b/Tomboy/Addins/NoteDirectoryWatcher/NoteDirectoryWatcher.addin.xml
new file mode 100644
index 0000000..6bfd500
--- /dev/null
+++ b/Tomboy/Addins/NoteDirectoryWatcher/NoteDirectoryWatcher.addin.xml
@@ -0,0 +1,22 @@
+<Addin id="NoteDirectoryWatcher"
+ namespace="Tomboy"
+ name="Note Directory Watcher"
+ author="Tomboy Project"
+ description="Watch your Tomboy note directory for changes to your notes."
+ category="Tools"
+ defaultEnabled="false"
+ version="0.1">
+
+ <Runtime>
+ <Import assembly="NoteDirectoryWatcher.dll" />
+ </Runtime>
+
+ <Dependencies>
+ <Addin id="Tomboy" version="0.10" />
+ </Dependencies>
+
+ <Extension path="/Tomboy/ApplicationAddins">
+ <ApplicationAddin type="Tomboy.NoteDirectoryWatcher.NoteDirectoryWatcherApplicationAddin" />
+ </Extension>
+
+</Addin>
diff --git a/Tomboy/Addins/NoteDirectoryWatcher/NoteDirectoryWatcherApplicationAddin.cs b/Tomboy/Addins/NoteDirectoryWatcher/NoteDirectoryWatcherApplicationAddin.cs
new file mode 100644
index 0000000..07ab267
--- /dev/null
+++ b/Tomboy/Addins/NoteDirectoryWatcher/NoteDirectoryWatcherApplicationAddin.cs
@@ -0,0 +1,233 @@
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using Tomboy;
+
+namespace Tomboy.NoteDirectoryWatcher
+{
+ class NoteFileChangeRecord
+ {
+ public DateTime last_change;
+ public bool deleted;
+ public bool changed;
+ }
+
+ public class NoteDirectoryWatcherApplicationAddin : ApplicationAddin
+ {
+ private static bool VERBOSE_LOGGING = false;
+
+ private FileSystemWatcher file_system_watcher;
+ private bool initialized;
+
+ private Dictionary<string, NoteFileChangeRecord> file_change_records;
+
+ public override void Initialize ()
+ {
+ string note_path = Tomboy.DefaultNoteManager.NoteDirectoryPath;
+
+ file_change_records = new Dictionary<string, NoteFileChangeRecord>();
+
+ file_system_watcher = new FileSystemWatcher (note_path);
+
+ file_system_watcher.Changed += HandleFileSystemChangeEvent;
+ file_system_watcher.Deleted += HandleFileSystemChangeEvent;
+ file_system_watcher.Created += HandleFileSystemChangeEvent;
+ file_system_watcher.Renamed += HandleFileSystemChangeEvent;
+
+ file_system_watcher.Error += HandleFileSystemErrorEvent;
+
+ // Setting to true will starts the FileSystemWatcher.
+ file_system_watcher.EnableRaisingEvents = true;
+
+ initialized = true;
+ }
+
+ public override void Shutdown ()
+ {
+ file_system_watcher.EnableRaisingEvents = false;
+ initialized = false;
+ }
+
+ public override bool Initialized
+ {
+ get
+ {
+ return initialized;
+ }
+ }
+
+ private void HandleFileSystemErrorEvent (Object sender, ErrorEventArgs arg)
+ {
+ // TODO Rescan the local notes in case some of them have changed.
+ }
+
+ private void HandleFileSystemChangeEvent (Object sender, FileSystemEventArgs arg)
+ {
+ string note_id = GetId(arg.FullPath);
+
+ if (VERBOSE_LOGGING)
+ {
+ Logger.Debug ("{0} has {1} (note_id={2})", arg.FullPath, arg.ChangeType, note_id);
+ }
+
+ // If the note_id is long 36 characters then the file probably wasn't a note.
+ if (note_id.Length != 36)
+ {
+ if (VERBOSE_LOGGING)
+ {
+ Logger.Debug ("Ignoring change to {0}", arg.FullPath);
+ }
+
+ return;
+ }
+
+ // Record that the file has been added/changed/deleted.  Adds/changes trump
+ // deletes.  Record the date.
+ lock (file_change_records)
+ {
+ NoteFileChangeRecord record = null;
+
+ if (file_change_records.ContainsKey (note_id))
+ {
+ record = file_change_records[note_id];
+ }
+ else
+ {
+ record = new NoteFileChangeRecord ();
+ file_change_records[note_id] = record;
+ }
+
+ if (arg.ChangeType == WatcherChangeTypes.Changed)
+ {
+ record.changed = true;
+ record.deleted = false;
+ }
+ else if (arg.ChangeType == WatcherChangeTypes.Created)
+ {
+ record.changed = true;
+ record.deleted = false;
+ }
+ else if (arg.ChangeType == WatcherChangeTypes.Renamed)
+ {
+ record.changed = true;
+ record.deleted = false;
+ }
+ else if (arg.ChangeType == WatcherChangeTypes.Deleted)
+ {
+ if (!record.changed)
+ {
+ record.deleted = true;
+ }
+ }
+ else
+ {
+ String message = "Unexpected WatcherChangeType " + arg.ChangeType;
+ Logger.Error (message);
+ throw new Exception (message);
+ }
+
+ record.last_change = DateTime.Now;
+ }
+
+ GLib.Timeout.Add (5000, new GLib.TimeoutHandler (HandleTimeout));
+ }
+
+ private bool HandleTimeout ()
+ {
+ lock (file_change_records)
+ {
+ List<string> keysToRemove = new List<string> (file_change_records.Count);
+
+ foreach (KeyValuePair<string, NoteFileChangeRecord> pair in file_change_records)
+ {
+ if (VERBOSE_LOGGING)
+ {
+ Logger.Debug ("Handling (timeout) {0}", pair.Key);
+ }
+
+ if (DateTime.Now > pair.Value.last_change.Add (new TimeSpan (4000)) )
+ {
+ if (pair.Value.deleted)
+ {
+ DeleteNote (pair.Key);
+ }
+ else
+ {
+ AddOrUpdateNote (pair.Key);
+ }
+
+ keysToRemove.Add (pair.Key);
+ }
+ }
+
+ foreach (string note_id in keysToRemove)
+ {
+ file_change_records.Remove (note_id);
+ }
+ }
+
+ return false;
+ }
+
+ private static void DeleteNote (string note_id)
+ {
+ Logger.Debug ("Deleting {0} because file deleted.", note_id);
+
+ string note_uri = MakeUri (note_id);
+
+ Note note_to_delete = Tomboy.DefaultNoteManager.FindByUri (note_uri);
+
+ Tomboy.DefaultNoteManager.Notes.Remove (note_to_delete);
+
+ note_to_delete.Delete ();
+ }
+
+ private static void AddOrUpdateNote (string note_id)
+ {
+ string note_path = Tomboy.DefaultNoteManager.NoteDirectoryPath +
+ Path.DirectorySeparatorChar + note_id + ".note";
+
+ string note_uri = MakeUri (note_id);
+
+ Note note = Tomboy.DefaultNoteManager.FindByUri (note_uri);
+
+ if (note == null)
+ {
+ Logger.Debug ("Adding {0} because file changed.", note_id);
+ Note new_note = Note.Load (note_path, Tomboy.DefaultNoteManager);
+ Tomboy.DefaultNoteManager.Notes.Add (new_note);
+ }
+ else
+ {
+ NoteData data = NoteArchiver.Instance.ReadFile (note_path, note_uri);
+
+ // Only record changes if the note actually changes.  This prevents the Addin from
+ // noticing changes from Tomboy itself.
+ if (data.Text == note.XmlContent)
+ {
+ if (VERBOSE_LOGGING)
+ {
+ Logger.Debug ("Ignoring {0} because contents identical", note_id);
+ }
+ }
+                               else
+                               {
+ Logger.Debug ("Updating {0} because file changed.", note_id);
+ note.XmlContent = data.Text;
+ note.Title = data.Title;
+ }
+ }
+ }
+
+ private static String MakeUri (string note_id)
+               {
+ return "note://tomboy/" + note_id;
+ }
+
+ private static string GetId (string path)
+               {
+ int last_slash = path.LastIndexOf (Path.DirectorySeparatorChar);
+ int first_period = path.IndexOf ('.', last_slash);
+
+ return path.Substring (last_slash + 1, first_period - last_slash - 1);
+ }
+ }
+}
diff --git a/configure.in b/configure.in
index ab9b951..8563e58 100644
--- a/configure.in
+++ b/configure.in
@@ -333,6 +333,7 @@ Tomboy/Addins/SshSyncService/Makefile
 Tomboy/Addins/StickyNoteImport/Makefile
 Tomboy/Addins/Tasque/Makefile
 Tomboy/Addins/WebDavSyncService/Makefile
+Tomboy/Addins/NoteDirectoryWatcher/Makefile
 test/Makefile
 po/Makefile.in
 ])


_______________________________________________
Tomboy-list mailing list
Tomboy-list@...
http://lists.beatniksoftware.com/listinfo.cgi/tomboy-list-beatniksoftware.com

 « Return to Thread: WIP Note Directory Watcher Patch - v2