/// <summary> /// Read the preferences and load the specified SyncServiceAddin to /// perform synchronization. /// </summary> private static SyncServiceAddin GetConfiguredSyncService() { SyncServiceAddin addin = null; string syncServiceId = Preferences.Get(Preferences.SYNC_SELECTED_SERVICE_ADDIN) as String; if (syncServiceId != null) { addin = GetSyncServiceAddin(syncServiceId); } return(addin); }
/// <summary> /// Return the specified SyncServiceAddin /// </summary> private static SyncServiceAddin GetSyncServiceAddin(string syncServiceId) { SyncServiceAddin anAddin = null; SyncServiceAddin [] addins = Tomboy.DefaultNoteManager.AddinManager.GetSyncServiceAddins(); foreach (SyncServiceAddin addin in addins) { if (addin.Id.CompareTo(syncServiceId) == 0) { anAddin = addin; break; } } return(anAddin); }
/// <summary> /// The function that does all of the work /// TODO: Factor some nice methods out of here; this is just garbage to read right now /// </summary> public static void SynchronizationThread() { SyncServiceAddin addin = null; SyncServer server = null; suspendEvent.Reset(); try { addin = GetConfiguredSyncService(); if (addin == null) { SetState(SyncState.NoConfiguredSyncService); Logger.Debug("GetConfiguredSyncService is null"); SetState(SyncState.Idle); syncThread = null; return; } Logger.Debug("SyncThread using SyncServiceAddin: {0}", addin.Name); SetState(SyncState.Connecting); try { server = addin.CreateSyncServer(); if (server == null) { throw new Exception("addin.CreateSyncServer () returned null"); } } catch (Exception e) { SetState(SyncState.SyncServerCreationFailed); Logger.Error("Exception while creating SyncServer: {0}\n{1}", e.Message, e.StackTrace); SetState(SyncState.Idle); syncThread = null; addin.PostSyncCleanup(); // 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) SetState(SyncState.AcquiringLock); // TODO: We should really throw exceptions from BeginSyncTransaction () if (!server.BeginSyncTransaction()) { SetState(SyncState.Locked); Logger.Warn("PerformSynchronization: Server locked, try again later"); SetState(SyncState.Idle); syncThread = null; addin.PostSyncCleanup(); return; } Logger.Debug("8"); int latestServerRevision = server.LatestRevision; 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. string serverId = server.Id; if (client.AssociatedServerId != serverId) { client.Reset(); client.AssociatedServerId = serverId; } SetState(SyncState.PrepareDownload); // Handle notes modified or added on server Logger.Debug("Sync: GetNoteUpdatesSince rev " + client.LastSynchronizedRevision.ToString()); IDictionary <string, NoteUpdate> noteUpdates = server.GetNoteUpdatesSince(client.LastSynchronizedRevision); Logger.Debug("Sync: " + noteUpdates.Count + " updates since rev " + client.LastSynchronizedRevision.ToString()); // Gather list of new/updated note titles // for title conflict handling purposes. List <string> noteUpdateTitles = new List <string> (); foreach (NoteUpdate noteUpdate in noteUpdates.Values) { if (!string.IsNullOrEmpty(noteUpdate.Title)) { noteUpdateTitles.Add(noteUpdate.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? foreach (NoteUpdate noteUpdate in noteUpdates.Values) { if (FindNoteByUUID(noteUpdate.UUID) == null) { Note existingNote = NoteMgr.Find(noteUpdate.Title); if (existingNote != null && !noteUpdate.BasicallyEqualTo(existingNote)) { // Logger.Debug ("Sync: Early conflict detection for '{0}'", noteUpdate.Title); if (syncUI != null) { syncUI.NoteConflictDetected(NoteMgr, existingNote, noteUpdate, noteUpdateTitles); // Suspend this thread while the GUI is presented to // the user. suspendEvent.WaitOne(); } } } } if (noteUpdates.Count > 0) { SetState(SyncState.Downloading); } // TODO: Figure out why GUI doesn't always update smoothly // Process updates from the server; the bread and butter of sync! foreach (NoteUpdate noteUpdate in noteUpdates.Values) { Note existingNote = FindNoteByUUID(noteUpdate.UUID); if (existingNote == null) { // 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. GuiUtils.GtkInvokeAndWait(() => { existingNote = NoteMgr.Find(noteUpdate.Title); }); if (existingNote != null) { Logger.Debug("SyncManager: Deleting auto-generated note: " + noteUpdate.Title); RecreateNoteInMainThread(existingNote, noteUpdate); } else { CreateNoteInMainThread(noteUpdate); } } else if (existingNote.MetadataChangeDate.CompareTo(client.LastSyncDate) <= 0 || noteUpdate.BasicallyEqualTo(existingNote)) { // Existing note hasn't been modified since last sync; simply update it from server UpdateNoteInMainThread(existingNote, noteUpdate); } else { // Logger.Debug ("Sync: Late conflict detection for '{0}'", noteUpdate.Title); Logger.Debug(string.Format( "SyncManager: Content conflict in note update for note '{0}'", noteUpdate.Title)); // Note already exists locally, but has been modified since last sync; prompt user if (syncUI != null) { syncUI.NoteConflictDetected(NoteMgr, existingNote, noteUpdate, noteUpdateTitles); // Suspend this thread while the GUI is presented to // the user. suspendEvent.WaitOne(); } // Note has been deleted or okay'd for overwrite existingNote = FindNoteByUUID(noteUpdate.UUID); if (existingNote == null) { CreateNoteInMainThread(noteUpdate); } else { UpdateNoteInMainThread(existingNote, noteUpdate); } } } // 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. GuiUtils.GtkInvokeAndWait(() => { // Make list of all local notes List <Note> localNotes = new List <Note> (NoteMgr.Notes); // Get all notes currently on server IList <string> serverNotes = server.GetAllNoteUUIDs(); // Delete notes locally that have been deleted on the server foreach (Note note in localNotes) { if (client.GetRevision(note) != -1 && !serverNotes.Contains(note.Id)) { if (syncUI != null) { syncUI.NoteSynchronized(note.Title, NoteSyncType.DeleteFromClient); } NoteMgr.Delete(note); } } }); // TODO: Add following updates to syncDialog treeview SetState(SyncState.PrepareUpload); // Look through all the notes modified on the client // and upload new or modified ones to the server List <Note> newOrModifiedNotes = new List <Note> (); foreach (Note note in new List <Note> (NoteMgr.Notes)) { if (client.GetRevision(note) == -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) GuiUtils.GtkInvokeAndWait(() => { note.Save(); }); newOrModifiedNotes.Add(note); if (syncUI != null) { syncUI.NoteSynchronized(note.Title, NoteSyncType.UploadNew); } } else if (client.GetRevision(note) <= client.LastSynchronizedRevision && note.MetadataChangeDate > client.LastSyncDate) { GuiUtils.GtkInvokeAndWait(() => { note.Save(); }); newOrModifiedNotes.Add(note); if (syncUI != null) { syncUI.NoteSynchronized(note.Title, NoteSyncType.UploadModified); } } } Logger.Debug("Sync: Uploading " + newOrModifiedNotes.Count.ToString() + " note updates"); if (newOrModifiedNotes.Count > 0) { SetState(SyncState.Uploading); server.UploadNotes(newOrModifiedNotes); // TODO: Callbacks to update GUI as upload progresses } // Handle notes deleted on client List <string> locallyDeletedUUIDs = new List <string> (); foreach (string noteUUID in server.GetAllNoteUUIDs()) { if (FindNoteByUUID(noteUUID) == null) { locallyDeletedUUIDs.Add(noteUUID); if (syncUI != null) { string deletedTitle = noteUUID; if (client.DeletedNoteTitles.ContainsKey(noteUUID)) { deletedTitle = client.DeletedNoteTitles [noteUUID]; } syncUI.NoteSynchronized(deletedTitle, NoteSyncType.DeleteFromServer); } } } if (locallyDeletedUUIDs.Count > 0) { SetState(SyncState.DeleteServerNotes); server.DeleteNotes(locallyDeletedUUIDs); } SetState(SyncState.CommittingChanges); bool commitResult = server.CommitSyncTransaction(); 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) foreach (Note note in newOrModifiedNotes) { client.SetRevision(note, newRevision); } SetState(SyncState.Succeeded); } else { SetState(SyncState.Failed); // TODO: Figure out a way to let the GUI know what exactly failed } // This should be equivalent to newRevision client.LastSynchronizedRevision = server.LatestRevision; client.LastSyncDate = DateTime.Now; Logger.Debug("Sync: New revision: {0}", client.LastSynchronizedRevision); SetState(SyncState.Idle); } catch (Exception e) { // top-level try Logger.Error("Synchronization failed with the following exception: " + e.Message + "\n" + e.StackTrace); // TODO: Report graphically to user try { SetState(SyncState.Idle); // stop progress SetState(SyncState.Failed); SetState(SyncState.Idle); // required to allow user to sync again if (server != null) { // 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.CancelSyncTransaction(); } } catch {} } finally { syncThread = null; try { addin.PostSyncCleanup(); } catch (Exception e) { Logger.Error("Error cleaning up addin after sync: " + e.Message + "\n" + e.StackTrace); } } }
/// <summary> /// Returns an array of SyncServiceAddin objects /// </summary> public SyncServiceAddin [] GetSyncServiceAddins () { SyncServiceAddin [] addins; try { addins = (SyncServiceAddin []) Mono.Addins.AddinManager.GetExtensionObjects ( "/Tomboy/SyncServiceAddins", typeof (SyncServiceAddin)); } catch (Exception e) { Logger.Debug ("No SyncServiceAddins found: {0}", e.Message); addins = new SyncServiceAddin [0]; } return addins; }
private int CompareSyncAddinsByName (SyncServiceAddin addin1, SyncServiceAddin addin2) { return addin1.Name.CompareTo (addin2.Name); }
public Gtk.Widget MakeSyncPane () { Gtk.VBox vbox = new Gtk.VBox (false, 0); vbox.Spacing = 4; vbox.BorderWidth = 8; Gtk.HBox hbox = new Gtk.HBox (false, 4); Gtk.Label label = new Gtk.Label (Catalog.GetString ("Ser_vice:")); label.Xalign = 0; label.Show (); hbox.PackStart (label, false, false, 0); // Populate the store with all the available SyncServiceAddins syncAddinStore = new Gtk.ListStore (typeof (SyncServiceAddin)); syncAddinIters = new Dictionary<string,Gtk.TreeIter> (); SyncServiceAddin [] addins = Tomboy.DefaultNoteManager.AddinManager.GetSyncServiceAddins (); Array.Sort (addins, CompareSyncAddinsByName); foreach (SyncServiceAddin addin in addins) { Gtk.TreeIter iter = syncAddinStore.Append (); syncAddinStore.SetValue (iter, 0, addin); syncAddinIters [addin.Id] = iter; } syncAddinCombo = new Gtk.ComboBox (syncAddinStore); label.MnemonicWidget = syncAddinCombo; Gtk.CellRendererText crt = new Gtk.CellRendererText (); syncAddinCombo.PackStart (crt, true); syncAddinCombo.SetCellDataFunc (crt, new Gtk.CellLayoutDataFunc (ComboBoxTextDataFunc)); // Read from Preferences which service is configured and select it // by default. Otherwise, just select the first one in the list. string addin_id = Preferences.Get ( Preferences.SYNC_SELECTED_SERVICE_ADDIN) as String; Gtk.TreeIter active_iter; if (addin_id != null && syncAddinIters.ContainsKey (addin_id)) { active_iter = syncAddinIters [addin_id]; syncAddinCombo.SetActiveIter (active_iter); } else { if (syncAddinStore.GetIterFirst (out active_iter) == true) { syncAddinCombo.SetActiveIter (active_iter); } } syncAddinCombo.Changed += OnSyncAddinComboChanged; syncAddinCombo.Show (); hbox.PackStart (syncAddinCombo, true, true, 0); hbox.Show (); vbox.PackStart (hbox, false, false, 0); // Get the preferences GUI for the Sync Addin if (active_iter.Stamp != Gtk.TreeIter.Zero.Stamp) selectedSyncAddin = syncAddinStore.GetValue (active_iter, 0) as SyncServiceAddin; if (selectedSyncAddin != null) syncAddinPrefsWidget = selectedSyncAddin.CreatePreferencesControl (OnSyncAddinPrefsChanged); if (syncAddinPrefsWidget == null) { Gtk.Label l = new Gtk.Label (Catalog.GetString ("Not configurable")); l.Yalign = 0.5f; l.Yalign = 0.5f; syncAddinPrefsWidget = l; } if (syncAddinPrefsWidget != null && addin_id != null && syncAddinIters.ContainsKey (addin_id) && selectedSyncAddin.IsConfigured) syncAddinPrefsWidget.Sensitive = false; syncAddinPrefsWidget.Show (); syncAddinPrefsContainer = new Gtk.VBox (false, 0); syncAddinPrefsContainer.PackStart (syncAddinPrefsWidget, false, false, 0); 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); vbox.PackStart (autosyncBox, false, true, 0); Gtk.HButtonBox bbox = new Gtk.HButtonBox (); bbox.Spacing = 4; bbox.LayoutStyle = Gtk.ButtonBoxStyle.End; // "Advanced..." button to bring up extra sync config dialog Gtk.Button advancedConfigButton = new Gtk.Button (Catalog.GetString ("_Advanced...")); advancedConfigButton.Clicked += OnAdvancedSyncConfigButton; advancedConfigButton.Show (); bbox.PackStart (advancedConfigButton, false, false, 0); bbox.SetChildSecondary (advancedConfigButton, true); resetSyncAddinButton = new Gtk.Button (Gtk.Stock.Clear); resetSyncAddinButton.Clicked += OnResetSyncAddinButton; resetSyncAddinButton.Sensitive = (selectedSyncAddin != null && addin_id == selectedSyncAddin.Id && selectedSyncAddin.IsConfigured); resetSyncAddinButton.Show (); bbox.PackStart (resetSyncAddinButton, false, false, 0); // TODO: Tabbing should go directly from sync prefs widget to here // TODO: Consider connecting to "Enter" pressed in sync prefs widget saveSyncAddinButton = new Gtk.Button (Gtk.Stock.Save); saveSyncAddinButton.Clicked += OnSaveSyncAddinButton; saveSyncAddinButton.Sensitive = (selectedSyncAddin != null && (addin_id != selectedSyncAddin.Id || !selectedSyncAddin.IsConfigured)); saveSyncAddinButton.Show (); bbox.PackStart (saveSyncAddinButton, false, false, 0); syncAddinCombo.Sensitive = (selectedSyncAddin == null || addin_id != selectedSyncAddin.Id || !selectedSyncAddin.IsConfigured); bbox.Show (); vbox.PackStart (bbox, false, false, 0); vbox.ShowAll (); return vbox; }
void OnSyncAddinComboChanged (object sender, EventArgs args) { if (syncAddinPrefsWidget != null) { syncAddinPrefsContainer.Remove (syncAddinPrefsWidget); syncAddinPrefsWidget.Hide (); syncAddinPrefsWidget.Destroy (); syncAddinPrefsWidget = null; } Gtk.TreeIter iter; if (syncAddinCombo.GetActiveIter (out iter)) { SyncServiceAddin newAddin = syncAddinStore.GetValue (iter, 0) as SyncServiceAddin; if (newAddin != null) { selectedSyncAddin = newAddin; syncAddinPrefsWidget = selectedSyncAddin.CreatePreferencesControl (OnSyncAddinPrefsChanged); if (syncAddinPrefsWidget == null) { Gtk.Label l = new Gtk.Label (Catalog.GetString ("Not configurable")); l.Yalign = 0.5f; l.Yalign = 0.5f; syncAddinPrefsWidget = l; } syncAddinPrefsWidget.Show (); syncAddinPrefsContainer.PackStart (syncAddinPrefsWidget, false, false, 0); resetSyncAddinButton.Sensitive = false; saveSyncAddinButton.Sensitive = false; } } else { selectedSyncAddin = null; resetSyncAddinButton.Sensitive = false; saveSyncAddinButton.Sensitive = false; } }