private void Button_TakeLeftWithConflict_Click(object sender, RoutedEventArgs e) { try { var note = _repo.FindNoteByID(_noteID); if (note == null) { LoggerSingleton.Inst.Warn("ConflictWindow", "Note not found", $"Could not update note {_noteID}, because it no longer exists in the repository"); Close(); return; } if (note.Text != _dataLeft.Item1) { note.Text = _dataLeft.Item1; } if (note.Title != _dataLeft.Item2) { note.Title = _dataLeft.Item2; } if (!note.Tags.UnorderedCollectionEquals(_dataLeft.Item3)) { note.Tags.Synchronize(_dataLeft.Item3); } if (note.Path != _dataLeft.Item4) { note.Path = _dataLeft.Item4; } var conflict = _repo.CreateNewNote(_dataRight.Item4); conflict.Title = string.Format("{0}_conflict_manual-{1:yyyy-MM-dd_HH:mm:ss}", _dataRight.Item2, DateTime.Now); conflict.Text = _dataRight.Item1; conflict.Tags.Synchronize(_dataRight.Item3); conflict.IsConflictNote = true; _repo.SaveNote(conflict); note.SetRemoteDirty("Data updated in ConflictWindow [Take Left With Conflict]"); _repo.SyncNow(); Close(); } catch (Exception ex) { LoggerSingleton.Inst.Error("ConflictWindow", "Error in conflict resolution", ex); } }
private void DoSyncThreaded(Stopwatch sw, long t1, List <INote> entriesNoteRepo, Dictionary <INote, INote> realNoteMapping) { List <Tuple <string, DateTimeOffset> > deletes; lock (_repoDeletedNotes) { deletes = _repoDeletedNotes.ToList(); _repoDeletedNotes.Clear(); } var unmodifiedBoth = new List <Tuple <PathEntry, INote> >(); var modifiedFilesystem = new List <Tuple <PathEntry, INote> >(); var modifiedNoteRepo = new List <Tuple <PathEntry, INote> >(); var createdFilesystem = new List <Tuple <PathEntry, INote> >(); var deletedFilesystem = new List <Tuple <PathEntry, INote> >(); var createdNoteRepo = new List <Tuple <PathEntry, INote> >(); var deletedNoteRepo = new List <Tuple <PathEntry, INote> >(); var entriesFilesystem = EnumerateFolder(DirectoryPath.Root(), _path, 0).ToList(); LoggerSingleton.Inst.Debug("RawFolderSync", $"Found {entriesFilesystem.Count} entries in filesystem", string.Join("\n", entriesFilesystem.Select(e => e.FilesystemPath))); LoggerSingleton.Inst.Debug("RawFolderSync", $"Found {entriesNoteRepo.Count} entries in repository", string.Join("\n", entriesNoteRepo.Select(e => e.UniqueName + "\t" + e.Title))); #region Step 1: Analyze foreach (var map in _pathMapping.ToList()) { var entryNR = entriesNoteRepo.FirstOrDefault(n => n.UniqueName == map.Key); var entryFS = entriesFilesystem.FirstOrDefault(n => n.FilesystemPath.ToLower() == map.Value.ToLower()); if (entryNR == null && entryFS == null) { continue; // mkay... } if (entryNR == null && entryFS != null) { // file removed in NR deletedNoteRepo.Add(Tuple.Create(entryFS, (INote)null)); entriesFilesystem.Remove(entryFS); continue; } if (entryNR != null && entryFS == null) { // file removed in FS deletedFilesystem.Add(Tuple.Create((PathEntry)null, entryNR)); entriesNoteRepo.Remove(entryNR); continue; } if (entryNR != null && entryFS != null) { if (entryNR.Text == entryFS.Content && entryFS.FilesystemPath == GetFilesystemPath(entryNR)) { // nothing changed unmodifiedBoth.Add(Tuple.Create(entryFS, entryNR)); entriesFilesystem.Remove(entryFS); entriesNoteRepo.Remove(entryNR); continue; } else { if (_lastSync == null || entryNR.ModificationDate > _lastSync) { // NoteRepo Changed modifiedNoteRepo.Add(Tuple.Create(entryFS, entryNR)); entriesFilesystem.Remove(entryFS); entriesNoteRepo.Remove(entryNR); continue; } else if (entryNR.ModificationDate >= entryFS.MDate) { // NoteRepo Changed modifiedNoteRepo.Add(Tuple.Create(entryFS, entryNR)); entriesFilesystem.Remove(entryFS); entriesNoteRepo.Remove(entryNR); continue; } else { // Filesystem Changed modifiedFilesystem.Add(Tuple.Create(entryFS, entryNR)); entriesFilesystem.Remove(entryFS); entriesNoteRepo.Remove(entryNR); continue; } } } throw new Exception("Invalid control flow in DoSyncThreaded::Step1"); } foreach (var entryFS in entriesFilesystem.ToList()) { var entryNR = entriesNoteRepo.FirstOrDefault(e => e.Path.EqualsIgnoreCase(entryFS.NotePath) && (e.Title.ToLower() == entryFS.Title.ToLower() || (entryFS.Title == e.UniqueName && e.Title == ""))); if (entryNR != null) { // data exists in both if (entryNR.Text == entryFS.Content && entryNR.Title == entryFS.Title) { // nothing changed unmodifiedBoth.Add(Tuple.Create(entryFS, entryNR)); entriesFilesystem.Remove(entryFS); entriesNoteRepo.Remove(entryNR); } else { if (entryNR.ModificationDate >= entryFS.MDate) { // NoteRepo Changed modifiedNoteRepo.Add(Tuple.Create(entryFS, entryNR)); entriesFilesystem.Remove(entryFS); entriesNoteRepo.Remove(entryNR); } else { // Filesystem Changed modifiedFilesystem.Add(Tuple.Create(entryFS, entryNR)); entriesFilesystem.Remove(entryFS); entriesNoteRepo.Remove(entryNR); } } } else { // data missing in noterepo if (deletes.Any(d => d.Item1.ToLower() == entryFS.FilesystemPath.ToLower())) { // file removed in NR deletedNoteRepo.Add(Tuple.Create(entryFS, (INote)null)); entriesFilesystem.Remove(entryFS); } else { // new file in FS createdFilesystem.Add(Tuple.Create(entryFS, (INote)null)); entriesFilesystem.Remove(entryFS); } } } foreach (var entryNR in entriesNoteRepo.ToList()) { // data missing in FS if (_lastSync == null || entryNR.ModificationDate > _lastSync) { // new file in NR createdNoteRepo.Add(Tuple.Create((PathEntry)null, entryNR)); entriesNoteRepo.Remove(entryNR); } else { // file removed in FS deletedFilesystem.Add(Tuple.Create((PathEntry)null, entryNR)); entriesNoteRepo.Remove(entryNR); } } #endregion if (entriesFilesystem.Any()) { throw new Exception("entriesFilesystem must be empty after analyze"); } if (entriesNoteRepo.Any()) { throw new Exception("entriesNoteRepo must be empty after analyze"); } #region Step 2: Apply changes int countSuccess = 0; int countError = 0; int countIgnored = 0; var newMapping = new List <Tuple <string, string> >(); // path -> UUID foreach (var data in unmodifiedBoth) { newMapping.Add(Tuple.Create(data.Item1.FilesystemPath, data.Item2.UniqueName)); } foreach (var data in modifiedNoteRepo) // Change [Filesystem] <-- [NoteRepo] { try { var p2 = GetFilesystemPath(data.Item2); var domove = (data.Item1.FilesystemPath.ToLower() != p2.ToLower()); LoggerSingleton.Inst.Debug("RawFolderSync", $"Synchronized [modification{(domove?"+rename":"")}] to Filesystem ({data.Item2.Title})", $"Filesystem:\n{Fmt(data.Item1)}\n\nRepository:\n{Fmt(data.Item2)}"); if (domove) { if (File.Exists(p2)) { File.Delete(p2); } File.Move(data.Item1.FilesystemPath, p2); } File.WriteAllText(p2, data.Item2.Text, _encoding); newMapping.Add(Tuple.Create(p2, data.Item2.UniqueName)); countSuccess++; } catch (Exception e) { LoggerSingleton.Inst.Error("RawFolderSync", $"Synchronize [modification] to Filesystem failed ({data.Item2.Title})", e); LoggerSingleton.Inst.ShowSyncErrorDialog($"Synchronize [modification] to Filesystem failed ({data.Item2.Title})", e); countError++; } } foreach (var data in createdNoteRepo) // Created [Filesystem] <-- [NoteRepo] { try { LoggerSingleton.Inst.Debug("RawFolderSync", $"Synchronized [creation] to Filesystem ({data.Item2.Title})", $"Filesystem:\n{"NULL"}\n\nRepository:\n{Fmt(data.Item2)}"); var tpath = GetFilesystemPath(data.Item2); Directory.CreateDirectory(Path.GetDirectoryName(tpath) ?? ""); File.WriteAllText(tpath, data.Item2.Text, _encoding); newMapping.Add(Tuple.Create(tpath, data.Item2.UniqueName)); countSuccess++; } catch (Exception e) { LoggerSingleton.Inst.Error("RawFolderSync", $"Synchronize [creation] to Filesystem failed ({data.Item2.Title})", e); LoggerSingleton.Inst.ShowSyncErrorDialog($"Synchronize [creation] to Filesystem failed ({data.Item2.Title})", e); countError++; } } foreach (var data in deletedNoteRepo) // Deleted [Filesystem] <-- [NoteRepo] { try { LoggerSingleton.Inst.Debug("RawFolderSync", $"Synchronized [deletion] to Filesystem ({data.Item1.Title})", $"Filesystem:\n{Fmt(data.Item1)}\n\nRepository:\n{"NULL"}"); File.Delete(data.Item1.FilesystemPath); countSuccess++; } catch (Exception e) { LoggerSingleton.Inst.Error("RawFolderSync", $"Synchronize [deletion] to Filesystem failed ({data.Item1.Title})", e); LoggerSingleton.Inst.ShowSyncErrorDialog($"Synchronize [deletion] to Filesystem failed ({data.Item1.Title})", e); countError++; } } foreach (var data in modifiedFilesystem) // Change [Filesystem] --> [NoteRepo] { if (!_syncModifications) { countIgnored++; LoggerSingleton.Inst.Debug("RawFolderSync", $"Event [modification] from filesystem to repository ignored due to settings", $"Filesystem:\n{Fmt(data.Item1)}\n\nRepository:\n{Fmt(data.Item2)}"); } try { LoggerSingleton.Inst.Debug("RawFolderSync", $"Synchronized [modification] to Repository ({data.Item2.Title})", $"Filesystem:\n{Fmt(data.Item1)}\n\nRepository:\n{Fmt(data.Item2)}"); _dispatcher.Invoke(() => { var note = realNoteMapping[data.Item2]; note.Text = data.Item1.Content; note.Title = data.Item1.Title; newMapping.Add(Tuple.Create(data.Item1.FilesystemPath, note.UniqueName)); }); countSuccess++; } catch (Exception e) { LoggerSingleton.Inst.Error("RawFolderSync", $"Synchronize [modification] to Repository failed ({data.Item2.Title})", e); LoggerSingleton.Inst.ShowSyncErrorDialog($"Synchronize [modification] to Repository failed ({data.Item2.Title})", e); countError++; } } foreach (var data in createdFilesystem) // Created [Filesystem] --> [NoteRepo] { if (!_syncCreation) { countIgnored++; LoggerSingleton.Inst.Debug("RawFolderSync", $"Event [creation] from filesystem to repository ignored due to settings", $"Filesystem:\n{Fmt(data.Item1)}\n\nRepository:\n{"NULL"}"); } try { LoggerSingleton.Inst.Debug("RawFolderSync", $"Synchronized [creation] to Repository ({data.Item1.Title})", $"Filesystem:\n{Fmt(data.Item1)}\n\nRepository:\n{"NULL"}"); _dispatcher.Invoke(() => { var n = _repo.CreateNewNote(data.Item1.NotePath); n.Title = data.Item1.Title; n.Text = data.Item1.Content; newMapping.Add(Tuple.Create(data.Item1.FilesystemPath, n.UniqueName)); }); countSuccess++; } catch (Exception e) { LoggerSingleton.Inst.Error("RawFolderSync", $"Synchronize [creation] to Repository failed ({data.Item1.Title})", e); LoggerSingleton.Inst.ShowSyncErrorDialog($"Synchronize [creation] to Repository failed ({data.Item1.Title})", e); countError++; } } foreach (var data in deletedFilesystem) // Deleted [Filesystem] --> [NoteRepo] { if (!_syncDeletion) { countIgnored++; LoggerSingleton.Inst.Debug("RawFolderSync", $"Event [deletion] from filesystem to repository ignored due to settings", $"Filesystem:\n{"NULL"}\n\nRepository:\n{Fmt(data.Item2)}"); } try { LoggerSingleton.Inst.Debug("RawFolderSync", $"Synchronized [deletion] to Repository ({data.Item2.Title})", $"Filesystem:\n{"NULL"}\n\nRepository:\n{Fmt(data.Item2)}"); _dispatcher.Invoke(() => { var note = realNoteMapping[data.Item2]; _repo.DeleteNote(note, true); }); countSuccess++; } catch (Exception e) { LoggerSingleton.Inst.Error("RawFolderSync", $"Synchronize [deletion] to Repository failed ({data.Item2.Title})", e); LoggerSingleton.Inst.ShowSyncErrorDialog($"Synchronize [deletion] to Repository failed ({data.Item2.Title})", e); countError++; } } #endregion _pathMapping.Clear(); foreach (var newmap in newMapping) { _pathMapping[newmap.Item2] = newmap.Item1; } _lastSync = DateTimeOffset.Now; sw.Stop(); LoggerSingleton.Inst.Info("RawFolderSync", $"Synchronization with local folder finished in {sw.ElapsedMilliseconds}ms ({t1}ms synchronous) ({countSuccess} changes | {countError} errors)", $"Successful changes: {countSuccess}\n" + $"Errored changes: {countError}\n" + $"Ignored changes: {countIgnored}\n" + "\n\n" + $"Unchanged entries: {unmodifiedBoth.Count}\n" + $"Modified(FS): {modifiedFilesystem.Count}\n" + $"Modified(NR): {modifiedNoteRepo .Count}\n" + $"Created(FS): {createdFilesystem.Count}\n" + $"Created(NR): {createdNoteRepo.Count}\n" + $"Deleted(FS): {deletedFilesystem.Count}\n" + $"Deleted(NR): {deletedNoteRepo.Count}\n" + "\n\n" + "PathMapping(new):\n" + string.Join("\n", newMapping.Select(m => $" {m.Item2} {m.Item1}"))); }