Example #1
0
        /// <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);
        }
Example #2
0
        /// <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);
        }
Example #3
0
        /// <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);
                }
            }
        }
Example #4
0
		/// <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;
		}
Example #5
0
		private int CompareSyncAddinsByName (SyncServiceAddin addin1, SyncServiceAddin addin2)
		{
			return addin1.Name.CompareTo (addin2.Name);
		}
Example #6
0
		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;
		}
Example #7
0
		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;
			}

		}