[tomboy] [sync] Implement automatic background synchronization (bug #562097)



commit fd70ff2d1db8392484a7e6fab6f29eafb1fd20e2
Author: Sandy Armstrong <sanfordarmstrong gmail com>
Date:   Mon Feb 8 11:54:50 2010 -0800

    [sync] Implement automatic background synchronization (bug #562097)
    
    The preference UI to enable this feature is desensitized for now, because
    this feature is still experimental.  It can be enabled by changing the
    /apps/tomboy/sync/autosync_timeout preference to a value greater than 0.

 Tomboy.mdp                            |    1 +
 Tomboy/Preferences.cs                 |    4 ++
 Tomboy/PreferencesDialog.cs           |   48 ++++++++++++++++
 Tomboy/Synchronization/SilentUI.cs    |   96 +++++++++++++++++++++++++++++++++
 Tomboy/Synchronization/SyncManager.cs |   55 +++++++++++++++++++
 data/tomboy.schemas.in                |   17 ++++++
 6 files changed, 221 insertions(+), 0 deletions(-)
---
diff --git a/Tomboy.mdp b/Tomboy.mdp
index d28779e..e1e2134 100644
--- a/Tomboy.mdp
+++ b/Tomboy.mdp
@@ -17,6 +17,7 @@
     <File subtype="Code" buildaction="Compile" name="Tomboy/IRemoteControl.cs" />
     <File subtype="Code" buildaction="Compile" name="Tomboy/RemoteControlWrapper.cs" />
     <File subtype="Code" buildaction="Compile" name="Tomboy/Synchronization/ISyncUI.cs" />
+    <File subtype="Code" buildaction="Compile" name="Tomboy/Synchronization/SilentUI.cs" />
     <File name="Tomboy/ActionManager.cs" subtype="Code" buildaction="Compile" />
     <File name="Tomboy/Applet.cs" subtype="Code" buildaction="Compile" />
     <File name="Tomboy/Logger.cs" subtype="Code" buildaction="Compile" />
diff --git a/Tomboy/Preferences.cs b/Tomboy/Preferences.cs
index 6268015..ab1996c 100644
--- a/Tomboy/Preferences.cs
+++ b/Tomboy/Preferences.cs
@@ -38,6 +38,7 @@ namespace Tomboy
 		public const string SYNC_LOCAL_PATH = "/apps/tomboy/sync/sync_local_path";
 		public const string SYNC_SELECTED_SERVICE_ADDIN = "/apps/tomboy/sync/sync_selected_service_addin";
 		public const string SYNC_CONFIGURED_CONFLICT_BEHAVIOR = "/apps/tomboy/sync/sync_conflict_behavior";
+		public const string SYNC_AUTOSYNC_TIMEOUT = "/apps/tomboy/sync/autosync_timeout";
 
 		public const string NOTE_RENAME_BEHAVIOR = "/apps/tomboy/note_rename_behavior";
 
@@ -135,6 +136,9 @@ namespace Tomboy
 			case SYNC_CONFIGURED_CONFLICT_BEHAVIOR:
 				return 0;
 
+			case SYNC_AUTOSYNC_TIMEOUT:
+				return -1;
+
 			case NOTE_RENAME_BEHAVIOR:
 				return 0;
 
diff --git a/Tomboy/PreferencesDialog.cs b/Tomboy/PreferencesDialog.cs
index 94a8806..4d34c42 100644
--- a/Tomboy/PreferencesDialog.cs
+++ b/Tomboy/PreferencesDialog.cs
@@ -18,6 +18,8 @@ namespace Tomboy
 		Gtk.Widget syncAddinPrefsWidget;
 		Gtk.Button resetSyncAddinButton;
 		Gtk.Button saveSyncAddinButton;
+		Gtk.CheckButton autosyncCheck;
+		Gtk.SpinButton autosyncSpinner;
 		Gtk.ComboBox rename_behavior_combo;
 		readonly AddinManager addin_manager;
 		
@@ -135,6 +137,17 @@ namespace Tomboy
 				}
 				if (rename_behavior_combo.Active != rename_behavior)
 					rename_behavior_combo.Active = rename_behavior;
+			} else if (args.Key == Preferences.SYNC_AUTOSYNC_TIMEOUT) {
+				int timeout = (int) args.Value;
+				if (timeout <= 0 && autosyncCheck.Active)
+					autosyncCheck.Active = false;
+				else if (timeout > 0) {
+					timeout = (timeout >= 5 && timeout < 1000) ? timeout : 5;
+					if (!autosyncCheck.Active)
+						autosyncCheck.Active = true;
+					if ((int) autosyncSpinner.Value != timeout)
+						autosyncSpinner.Value = timeout;
+				}
 			}
 		}
 		
@@ -485,6 +498,41 @@ namespace Tomboy
 			syncAddinPrefsContainer.Show ();
 			vbox.PackStart (syncAddinPrefsContainer, true, true, 10);
 
+			// Autosync preference
+			int timeout = (int) Preferences.Get (Preferences.SYNC_AUTOSYNC_TIMEOUT);
+			if (timeout > 0 && timeout < 5) {
+				timeout = 5;
+				Preferences.Set (Preferences.SYNC_AUTOSYNC_TIMEOUT, 5);
+			}
+			Gtk.HBox autosyncBox = new Gtk.HBox (false, 5);
+			// Translators: This is and the next string go together.
+			// Together they look like "Automatically Sync in Background Every [_] Minutes",
+			// where "[_]" is a GtkSpinButton.
+			autosyncCheck =
+				new Gtk.CheckButton (Catalog.GetString ("Automaticall_y Sync in Background Every"));
+			autosyncSpinner = new Gtk.SpinButton (5, 1000, 1);
+			autosyncSpinner.Value = timeout >= 5 ? timeout : 10;
+			Gtk.Label autosyncExtraText =
+				// Translators: See above comment for details on
+				// this string.
+				new Gtk.Label (Catalog.GetString ("Minutes"));
+			autosyncCheck.Active = autosyncSpinner.Sensitive = timeout >= 5;
+			EventHandler updateTimeoutPref = (o, e) => {
+				Preferences.Set (Preferences.SYNC_AUTOSYNC_TIMEOUT,
+				                 autosyncCheck.Active ? (int) autosyncSpinner.Value : -1);
+			};
+			autosyncCheck.Toggled += (o, e) => {
+				autosyncSpinner.Sensitive = autosyncCheck.Active;
+				updateTimeoutPref (o, e);
+			};
+			autosyncSpinner.ValueChanged += updateTimeoutPref;
+
+			autosyncBox.PackStart (autosyncCheck);
+			autosyncBox.PackStart (autosyncSpinner);
+			autosyncBox.PackStart (autosyncExtraText);
+			autosyncBox.Sensitive = false; // TODO: Remove when this feature is stable
+			vbox.PackStart (autosyncBox, false, true, 0);
+
 			Gtk.HButtonBox bbox = new Gtk.HButtonBox ();
 			bbox.Spacing = 4;
 			bbox.LayoutStyle = Gtk.ButtonBoxStyle.End;
diff --git a/Tomboy/Synchronization/SilentUI.cs b/Tomboy/Synchronization/SilentUI.cs
new file mode 100644
index 0000000..ef0b7fd
--- /dev/null
+++ b/Tomboy/Synchronization/SilentUI.cs
@@ -0,0 +1,96 @@
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the 
+// "Software"), to deal in the Software without restriction, including 
+// without limitation the rights to use, copy, modify, merge, publish, 
+// distribute, sublicense, and/or sell copies of the Software, and to 
+// permit persons to whom the Software is furnished to do so, subject to 
+// the following conditions: 
+//  
+// The above copyright notice and this permission notice shall be 
+// included in all copies or substantial portions of the Software. 
+//  
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
+// 
+// Copyright (c) 2010 Novell, Inc. (http://www.novell.com)
+// 
+// Authors: 
+//      Sandy Armstrong <sanfordarmstrong gmail com>
+// 
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Tomboy.Sync
+{
+	public class SilentUI : ISyncUI
+	{
+		private bool uiDisabled = false;
+		private NoteManager manager;
+
+		public SilentUI (NoteManager manager)
+		{
+			this.manager = manager;
+		}
+
+		#region ISyncUI implementation
+		public void SyncStateChanged (SyncState state)
+		{
+			// TODO: Update tray/applet icon
+			//       D-Bus event?
+			//       libnotify bubbles when appropriate
+			Logger.Debug ("SilentUI: SyncStateChanged: {0}", state);
+			AutoResetEvent evt;
+			switch (state) {
+			case SyncState.Connecting:
+				uiDisabled = 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
+				GuiUtils.GtkInvokeAndWait (() => {
+					manager.ReadOnly = true;
+					foreach (Note note in new List<Note> (manager.Notes)) {
+						note.Enabled = false;
+					}
+				});
+				break;
+			case SyncState.Idle:
+				if (uiDisabled) {
+					GuiUtils.GtkInvokeAndWait (() => {
+						manager.ReadOnly = false;
+						foreach (Note note in new List<Note> (manager.Notes)) {
+							note.Enabled = true;
+						}
+					});
+					uiDisabled = false;
+				}
+				break;
+			default:
+				break;
+			}
+		}
+
+		public void NoteSynchronized (string noteTitle, NoteSyncType type)
+		{
+			Logger.Debug ("SilentUI: NoteSynchronized, Title: {0}, Type: {1}", noteTitle, type);
+		}
+
+		public void NoteConflictDetected (NoteManager manager, Note localConflictNote, NoteUpdate remoteNote, IList<string> noteUpdateTitles)
+		{
+			Logger.Debug ("SilentUI: NoteConflictDetected, overwriting without a care");
+			// TODO: At least respect conflict prefs
+			// TODO: Implement more useful conflict handling
+			if (localConflictNote.Id != remoteNote.UUID)
+				manager.Delete (localConflictNote);
+			SyncManager.ResolveConflict (SyncTitleConflictResolution.OverwriteExisting);
+		}
+		#endregion
+	}
+}
diff --git a/Tomboy/Synchronization/SyncManager.cs b/Tomboy/Synchronization/SyncManager.cs
index 8c55a9f..efc3d32 100644
--- a/Tomboy/Synchronization/SyncManager.cs
+++ b/Tomboy/Synchronization/SyncManager.cs
@@ -205,10 +205,64 @@ namespace Tomboy.Sync
 			UpdateSyncAction ();
 		}
 
+		private static Timer autosyncTimer;
+		private static int autosyncTimeout = -1;
+
 		static void UpdateSyncAction ()
 		{
 			string sync_addin_id = Preferences.Get (Preferences.SYNC_SELECTED_SERVICE_ADDIN) as string;
 			Tomboy.ActionManager["SyncNotesAction"].Sensitive = !string.IsNullOrEmpty (sync_addin_id);
+
+			int timeoutPref = (int) Preferences.Get (Preferences.SYNC_AUTOSYNC_TIMEOUT);
+			if (timeoutPref != autosyncTimeout) {
+				autosyncTimeout = timeoutPref;
+				if (autosyncTimer != null) {
+					autosyncTimer.Dispose ();
+					autosyncTimer = null;
+				}
+				if (autosyncTimeout > 0) {
+					autosyncTimeout = autosyncTimeout >= 5 ? autosyncTimeout : 5;
+					autosyncTimer = new Timer ((o) => BackgroundSyncChecker (),
+					                           null,
+					                           60000, // Perform a sync one minute after setting change
+					                           autosyncTimeout * 60000);
+				}
+			}
+		}
+
+		static void BackgroundSyncChecker ()
+		{
+			if (syncThread != null)
+				return;
+			var addin = GetConfiguredSyncService ();
+			if (addin != null) {
+				// TODO: block sync while checking
+				var server = addin.CreateSyncServer ();
+				bool clientHasUpdates = client.DeletedNoteTitles.Count > 0;
+				bool serverHasUpdates = false;
+				if (!clientHasUpdates) {
+					foreach (Note note in new List<Note> (NoteMgr.Notes)) {
+						if (client.GetRevision (note) == -1 ||
+						    note.MetadataChangeDate > client.LastSyncDate) {
+							clientHasUpdates = true;
+							break;
+						}
+					}
+				}
+				// Wasteful to check when we'll sync anyway
+				// TODO: Unless we want to show a bubble when server has updates for users that don't autosync
+				if (!clientHasUpdates) {
+					Logger.Debug ("BackgroundSyncChecker: No client updates; checking with server");
+					serverHasUpdates = server.UpdatesAvailableSince (client.LastSynchronizedRevision);
+				}
+				addin.PostSyncCleanup (); // Let FUSE unmount, etc
+
+				if (clientHasUpdates || serverHasUpdates) {
+					Logger.Debug ("BackgroundSyncChecker: Detected that sync would be a good idea now");
+					// TODO: Check that it's safe to sync, block other sync UIs
+					PerformSynchronization (new SilentUI (NoteMgr));
+				}
+			}
 		}
 
 		public static void ResetClient ()
@@ -506,6 +560,7 @@ namespace Tomboy.Sync
 			}
 		}
 
+
 		/// <summary>
 		/// The GUI should call this after having the user resolve a conflict
 		/// so the synchronization thread can continue.
diff --git a/data/tomboy.schemas.in b/data/tomboy.schemas.in
index d403fe3..2692923 100644
--- a/data/tomboy.schemas.in
+++ b/data/tomboy.schemas.in
@@ -549,6 +549,23 @@
     </schema>
 
     <schema>
+      <key>/schemas/apps/tomboy/sync/autosync_timeout</key>
+      <applyto>/apps/tomboy/sync/autosync_timeout</applyto>
+      <owner>tomboy</owner>
+      <type>int</type>
+      <default>-1</default>
+      <locale name="C">
+         <short>Automatic Background Synchronization Timeout</short>
+         <long>
+	   Integer value indicating how frequently to perform a background sync
+	   of your notes (when sync is configured).  Any value less than 1
+	   indicates that autosync is disabled.  The lowest acceptable positive
+	   value is 5.  Value is in minutes.
+         </long>
+      </locale>
+    </schema>
+
+    <schema>
       <key>/schemas/apps/tomboy/note_rename_behavior</key>
       <applyto>/apps/tomboy/note_rename_behavior</applyto>
       <owner>tomboy</owner>



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