[tomboy] Add initial NoteDirectoryWatcher add-in for watching external changes to note files.



commit a973685dfc6ba28cf907198296a5f16245d23aca
Author: Michael Fletcher <m fletcher theplanet ca>
Date:   Wed Apr 29 19:45:29 2009 -0600

    Add initial NoteDirectoryWatcher add-in for watching external changes to note files.
    
    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.
    
    This patch 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.
---
 Tomboy/Addins/Makefile.am                          |    1 +
 Tomboy/Addins/NoteDirectoryWatcher/Makefile.am     |   39 ++++
 .../NoteDirectoryWatcher.addin.xml                 |   22 ++
 .../NoteDirectoryWatcherApplicationAddin.cs        |  237 ++++++++++++++++++++
 configure.in                                       |    1 +
 5 files changed, 300 insertions(+), 0 deletions(-)

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..6584246
--- /dev/null
+++ b/Tomboy/Addins/NoteDirectoryWatcher/NoteDirectoryWatcherApplicationAddin.cs
@@ -0,0 +1,237 @@
+
+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
 ])



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