private static void DeleteNoteInMainThread(Note existingNote) { // 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(() => { NoteMgr.Delete(existingNote); }); }
private static void CreateNoteInMainThread(NoteUpdate noteUpdate) { // Note creation 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(() => { Note existingNote = NoteMgr.CreateWithGuid(noteUpdate.Title, noteUpdate.UUID); UpdateLocalNote(existingNote, noteUpdate, NoteSyncType.DownloadNew); }); }
private static Note FindNoteByUUID(string uuid) { return(NoteMgr.FindByUri("note://tomboy/" + uuid)); }
/// <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); } } }