Esempio n. 1
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);
                }
            }
        }