bool ResetSyncStateIfNecessary(IGroup group, IMultiFileSystemDiff diff) { var syncPointService = group.GetSyncPointService(); var syncActionService = group.GetSyncActionService(); var conflictService = group.GetSyncConflictService(); var syncFolders = group.GetConfigurationService().Items.ToArray(); var latestSyncPoint = syncPointService.LatestSyncPoint; if (ContainsNewFolders(diff) || WasFilterModified(syncFolders, latestSyncPoint)) { // insert "Reset" sync point var resetSyncPoint = new MutableSyncPoint() { Id = GetNextSyncPointId(latestSyncPoint), MultiFileSystemSnapshotId = null, FilterConfigurations = syncFolders.ToDictionary(f => f.Name, f => f.Filter) }; syncPointService.AddItem(resetSyncPoint); // cancel all pending sync actions var cancelledSyncActions = syncActionService.PendingItems .Select(a => a.WithState(SyncActionState.Cancelled)); syncActionService.UpdateItems(cancelledSyncActions); // remove all conflicts conflictService.RemoveItems(conflictService.Items.ToArray()); return true; } return false; }
public void Synchronize(IGroup group) { var syncFolders = group.GetConfigurationService().Items.ToArray(); // there need to be at least 2 sync folders, otherwise, syncing makes no sense if (syncFolders.Length < 2) { return; } var historyService = group.GetService<IMultiFileSystemHistoryService>(); historyService.CreateSnapshot(); // we cannot sync if there isn't at least one snapshot if(!historyService.Snapshots.Any()) { return; } // we cannot sync if there is not at least one snapshot per history if (historyService.LatestSnapshot.HistoryNames.Any(name => historyService.LatestSnapshot.GetSnapshot(name) == null)) { return; } // get required services var syncPointService = group.GetSyncPointService(); var conflictService = group.GetSyncConflictService(); var syncActionService = group.GetSyncActionService(); var filter = syncFolders.ToMultiFileSystemChangeFilter(m_FilterFactory); var latestSyncPoint = syncPointService.LatestSyncPoint; var diff = GetDiff(historyService, latestSyncPoint, filter); var wasReset = ResetSyncStateIfNecessary(group, diff); if (wasReset) { diff = GetDiff(historyService, latestSyncPoint, filter); latestSyncPoint = syncPointService.LatestSyncPoint; } // for all folders, get the changes since the last sync var newSyncPoint = new MutableSyncPoint() { Id = GetNextSyncPointId(syncPointService.LatestSyncPoint), MultiFileSystemSnapshotId = diff.ToSnapshot.Id, FilterConfigurations = syncFolders.ToDictionary(f => f.Name, f => f.Filter) }; var syncStateUpdater = new SyncActionUpdateBuilder(); var changeGraphBuilder = new ChangeGraphBuilder(m_FileReferenceComparer); foreach (var graph in changeGraphBuilder.GetChangeGraphs(diff)) { var path = graph.ValueNodes.First(node => node.Value != null).Value.Path; // skip if there is a conflict for the current file if (conflictService.ItemExists(path)) { continue; } // check if all pending sync actions can be applied to the change graph var unapplicaleSyncActions = GetUnapplicableSyncActions(graph, syncActionService[path].Where(IsPendingSyncAction)); // pending sync actions could not be applied => skip file if (unapplicaleSyncActions.Any()) { // cancel unapplicable actions syncStateUpdater.UpdateSyncActions(unapplicaleSyncActions.Select(a => a.WithState(SyncActionState.Cancelled))); // add a conflict for the file (the snapshot id of the conflict can be determined from the oldest unapplicable sync action) var oldestSyncPointId = unapplicaleSyncActions.Min(a => a.SyncPointId); var snapshotId = oldestSyncPointId > 1 ? syncPointService[oldestSyncPointId - 1].MultiFileSystemSnapshotId : null; syncStateUpdater.AddConflict(new ConflictInfo(unapplicaleSyncActions.First().Path, snapshotId)); continue; } //in the change graph, detect conflicts // if there is only one sink, no conflicts exist var acylicGraph = graph.ToAcyclicGraph(); var sinks = acylicGraph.GetSinks().ToArray(); if (!sinks.Any()) { // not possible (in this case the graph would be empty, which cannot happen) throw new InvalidOperationException(); } if (sinks.Length == 1) { // no conflict => generate sync actions, to replace the outdated file versions or add the file to a target var sink = sinks.Single(); //TODO: Use C# 7 tuples after it was released IEnumerable<Tuple<string, IFile>> currentFiles = diff.ToSnapshot.GetFiles(path); foreach (var historyFileTuple in currentFiles) { var targetSyncFolderName = historyFileTuple.Item1; var currentVersion = historyFileTuple.Item2; var syncAction = m_SyncActionFactory.GetSyncAction(targetSyncFolderName, newSyncPoint.Id, currentVersion?.ToReference(), sink); if (syncAction != null && filter.GetFilter(targetSyncFolderName).IncludeInResult(syncAction)) { syncStateUpdater.AddSyncAction(syncAction); } } } else { // multiple sinks in the change graph => conflict // if there are pending actions for this file, they need to be cancelled var pendingSyncActions = syncActionService[path] .Where(IsPendingSyncAction) .ToArray(); if (pendingSyncActions.Any()) { //cancel actions syncStateUpdater.UpdateSyncActions(pendingSyncActions.Select(a => a.WithState(SyncActionState.Cancelled))); //determine the oldest sync action to determine the snapshot ids for the conflict var syncPointId = pendingSyncActions.Min(x => x.SyncPointId); var snapshotId = syncPointId > 1 ? syncPointService[syncPointId - 1].MultiFileSystemSnapshotId : null; // generate conflict; syncStateUpdater.AddConflict(new ConflictInfo(path, snapshotId)); } else { // no pending action => the snapshot ids for the conflict are the start snapshots of the current sync // generate conflict var conflictInfo = new ConflictInfo(path, diff.FromSnapshot?.Id); syncStateUpdater.AddConflict(conflictInfo); } } } // save actions, conflicts and sync point syncPointService.AddItem(newSyncPoint); syncStateUpdater.Apply(syncActionService, conflictService); }
public void Running_Sychronize_without_any_changes_since_the_last_sync_point_produces_no_actions_or_conflicts() { //ARRANGE var state1 = new Directory(null, "root") { d => new EmptyFile(d, "file1") }; var state2 = new Directory(null, "root") { d => new EmptyFile(d, "file2")}; var configService = m_Group.GetConfigurationService(); configService.AddItem(new SyncFolder("folder1") { Path = "Irrelevant"}); configService.AddItem(new SyncFolder("folder2") { Path = "Irrelevant"}); var historyService = m_Group.GetHistoryService(); historyService.CreateHistory("folder1"); historyService.CreateHistory("folder2"); var snapshot1 = historyService["folder1"].CreateSnapshot(state1); var snapshot2 = historyService["folder2"].CreateSnapshot(state2); // save a sync point var syncPoint = new MutableSyncPoint() { Id = 1, MultiFileSystemSnapshotId = m_MultiFileSystemHistory.CreateSnapshot().Id, FilterConfigurations = new Dictionary<string, FilterConfiguration>() { {"folder1", FilterConfiguration.Empty }, {"folder2", FilterConfiguration.Empty } } }; m_Group.GetSyncPointService().AddItem(syncPoint); //ACT m_Instance.Synchronize(m_Group); //ASSERT Assert.Empty(m_Group.GetSyncConflictService().Items); Assert.Empty(m_Group.GetSyncActionService().AllItems); Assert.Equal(2, m_Group.GetSyncPointService().Items.Count()); }