diff --git a/src/ByteSync.Client/Assets/Resources/Resources.Designer.cs b/src/ByteSync.Client/Assets/Resources/Resources.Designer.cs index 31d07002..3016310f 100644 --- a/src/ByteSync.Client/Assets/Resources/Resources.Designer.cs +++ b/src/ByteSync.Client/Assets/Resources/Resources.Designer.cs @@ -270,27 +270,27 @@ internal static string AtomicActionEdit_Action { /// /// Recherche une chaîne localisée semblable à Copy content. /// - internal static string AtomicActionEdit_SynchronizeContent { + internal static string AtomicActionEdit_CopyContent { get { - return ResourceManager.GetString("AtomicActionEdit_SynchronizeContent", resourceCulture); + return ResourceManager.GetString("AtomicActionEdit_CopyContent", resourceCulture); } } /// /// Recherche une chaîne localisée semblable à Copy content and date. /// - internal static string AtomicActionEdit_SynchronizeContentAndDate { + internal static string AtomicActionEdit_Copy { get { - return ResourceManager.GetString("AtomicActionEdit_SynchronizeContentAndDate", resourceCulture); + return ResourceManager.GetString("AtomicActionEdit_Copy", resourceCulture); } } /// /// Recherche une chaîne localisée semblable à Copy date. /// - internal static string AtomicActionEdit_SynchronizeDate { + internal static string AtomicActionEdit_CopyDate { get { - return ResourceManager.GetString("AtomicActionEdit_SynchronizeDate", resourceCulture); + return ResourceManager.GetString("AtomicActionEdit_CopyDate", resourceCulture); } } @@ -1596,27 +1596,27 @@ internal static string Status_Error { /// /// Recherche une chaîne localisée semblable à Copy content. /// - internal static string SynchronizationActionDescription_Action_SynchronizeContent { + internal static string SynchronizationActionDescription_Action_CopyContent { get { - return ResourceManager.GetString("SynchronizationActionDescription_Action_SynchronizeContent", resourceCulture); + return ResourceManager.GetString("SynchronizationActionDescription_Action_CopyContent", resourceCulture); } } /// /// Recherche une chaîne localisée semblable à Copy content and date. /// - internal static string SynchronizationActionDescription_Action_SynchronizeContentAndDate { + internal static string SynchronizationActionDescription_Action_Copy { get { - return ResourceManager.GetString("SynchronizationActionDescription_Action_SynchronizeContentAndDate", resourceCulture); + return ResourceManager.GetString("SynchronizationActionDescription_Action_Copy", resourceCulture); } } /// /// Recherche une chaîne localisée semblable à Copy date. /// - internal static string SynchronizationActionDescription_Action_SynchronizeDate { + internal static string SynchronizationActionDescription_Action_CopyDate { get { - return ResourceManager.GetString("SynchronizationActionDescription_Action_SynchronizeDate", resourceCulture); + return ResourceManager.GetString("SynchronizationActionDescription_Action_CopyDate", resourceCulture); } } @@ -2910,14 +2910,23 @@ internal static string ValidationFailure_DuplicateActionNotAllowed { get { return ResourceManager.GetString("ValidationFailure_DuplicateActionNotAllowed", resourceCulture); } + } + + /// + /// Looks up a localized string similar to Multiple sources found. + /// + internal static string ValidationFailure_SourceHasMultipleIdentities { + get { + return ResourceManager.GetString("ValidationFailure_SourceHasMultipleIdentities", resourceCulture); + } } /// - /// Looks up a localized string similar to Invalid source data. + /// Looks up a localized string similar to Source not found. /// - internal static string ValidationFailure_InvalidSourceCount { + internal static string ValidationFailure_SourceMissing { get { - return ResourceManager.GetString("ValidationFailure_InvalidSourceCount", resourceCulture); + return ResourceManager.GetString("ValidationFailure_SourceMissing", resourceCulture); } } @@ -3201,3 +3210,6 @@ internal static string SynchronizationConfirmation_Cancel { } } } + + + diff --git a/src/ByteSync.Client/Assets/Resources/Resources.fr.resx b/src/ByteSync.Client/Assets/Resources/Resources.fr.resx index 95a924af..05aff7fd 100644 --- a/src/ByteSync.Client/Assets/Resources/Resources.fr.resx +++ b/src/ByteSync.Client/Assets/Resources/Resources.fr.resx @@ -235,13 +235,13 @@ Destination - + Copier - + Copier le contenu uniquement - + Copier les dates uniquement @@ -289,13 +289,13 @@ Effectuer l'action suivante : - + Copier le contenu uniquement - + Copier - + Copier les dates uniquement @@ -1654,8 +1654,11 @@ Voulez-vous enregistrer ce nouveau Profil de Session avec ce nom ? Destination requise pour la création - - Données source invalides + + Source introuvable + + + Plusieurs sources trouvées Erreur d'analyse de la source diff --git a/src/ByteSync.Client/Assets/Resources/Resources.resx b/src/ByteSync.Client/Assets/Resources/Resources.resx index 7b239a17..27402526 100644 --- a/src/ByteSync.Client/Assets/Resources/Resources.resx +++ b/src/ByteSync.Client/Assets/Resources/Resources.resx @@ -131,7 +131,7 @@ of the following conditions are met: - Contents + Content Last write time @@ -232,13 +232,13 @@ Destination - + Copy - - Copy contents only + + Copy content only - + Copy dates only @@ -289,13 +289,13 @@ Do the following action: - - Copy contents only + + Copy content only - + Copy dates only - + Copy @@ -353,7 +353,7 @@ Would you like to continue ? If - Contents + Content Date @@ -1696,8 +1696,11 @@ Do you want to save this new Session Profile with this name? Destination required for create - - Invalid source data + + Source not found + + + Multiple sources found Source has analysis error diff --git a/src/ByteSync.Client/Business/Actions/Shared/SharedActionsGroup.cs b/src/ByteSync.Client/Business/Actions/Shared/SharedActionsGroup.cs index ba75d516..5851e510 100644 --- a/src/ByteSync.Client/Business/Actions/Shared/SharedActionsGroup.cs +++ b/src/ByteSync.Client/Business/Actions/Shared/SharedActionsGroup.cs @@ -11,43 +11,34 @@ public SharedActionsGroup() { Targets = new HashSet(); } - + public PathIdentity PathIdentity { get; set; } = null!; - + public SharedDataPart? Source { get; set; } - + public HashSet Targets { get; set; } - + public SynchronizationTypes? SynchronizationType { get; set; } - + public SynchronizationStatus? SynchronizationStatus { get; set; } - + public bool IsFromSynchronizationRule { get; set; } - + public string LinkingKeyValue { - get - { - return PathIdentity.LinkingKeyValue; - } + get { return PathIdentity.LinkingKeyValue; } } - + public bool IsFile { - get - { - return PathIdentity.FileSystemType == FileSystemTypes.File; - } + get { return PathIdentity.FileSystemType == FileSystemTypes.File; } } - + public bool IsDirectory { - get - { - return PathIdentity.FileSystemType == FileSystemTypes.Directory; - } + get { return PathIdentity.FileSystemType == FileSystemTypes.Directory; } } - + public string Key { get @@ -62,27 +53,27 @@ public string Key + SynchronizationType; } } - + public string GetSourceFullName() { string sourceFileName = GetFullName(Source!); - + return sourceFileName; } - + public HashSet GetTargetsFullNames(ByteSyncEndpoint endpoint) { HashSet result = new HashSet(); - + foreach (var target in Targets.Where(sdp => Equals(sdp.ClientInstanceId, endpoint.ClientInstanceId))) { var fullName = GetFullName(target); result.Add(fullName); } - + return result; } - + public string GetFullName(SharedDataPart sharedDataPart) { string sourceFileName; @@ -94,36 +85,37 @@ public string GetFullName(SharedDataPart sharedDataPart) { sourceFileName = sharedDataPart.RootPath; } - + return sourceFileName; } - + private bool Equals(SharedActionsGroup other) { return ActionsGroupId == other.ActionsGroupId; } - + public override bool Equals(object? obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; - return Equals((SharedActionsGroup) obj); + + return Equals((SharedActionsGroup)obj); } - + public override int GetHashCode() { return ActionsGroupId.GetHashCode(); } - + public ActionsGroupDefinition GetDefinition() { var actionsGroupDefinition = new ActionsGroupDefinition(); - + actionsGroupDefinition.ActionsGroupId = ActionsGroupId; actionsGroupDefinition.FileSystemType = PathIdentity.FileSystemType; actionsGroupDefinition.Operator = Operator; - actionsGroupDefinition.AppliesOnlySynchronizeDate = AppliesOnlySynchronizeDate; + actionsGroupDefinition.AppliesOnlyCopyDate = AppliesOnlyCopyDate; actionsGroupDefinition.SourceClientInstanceId = Source?.ClientInstanceId; actionsGroupDefinition.TargetClientInstanceAndNodeIds = Targets .Select(t => new ClientInstanceIdAndNodeId @@ -135,7 +127,7 @@ public ActionsGroupDefinition GetDefinition() actionsGroupDefinition.Size = Size; actionsGroupDefinition.CreationTimeUtc = CreationTimeUtc; actionsGroupDefinition.LastWriteTimeUtc = LastWriteTimeUtc; - + return actionsGroupDefinition; } } \ No newline at end of file diff --git a/src/ByteSync.Client/Business/Comparisons/AtomicActionValidationFailureReason.cs b/src/ByteSync.Client/Business/Comparisons/AtomicActionValidationFailureReason.cs index dbaa4f83..87d0d760 100644 --- a/src/ByteSync.Client/Business/Comparisons/AtomicActionValidationFailureReason.cs +++ b/src/ByteSync.Client/Business/Comparisons/AtomicActionValidationFailureReason.cs @@ -17,9 +17,10 @@ public enum AtomicActionValidationFailureReason DestinationRequiredForCreateOperation = 22, // Advanced Consistency - Source Issues - InvalidSourceCount = 30, SourceHasAnalysisError = 31, SourceNotAccessible = 32, + SourceMissing = 33, + SourceHasMultipleIdentities = 34, // Advanced Consistency - Target Issues TargetFileNotPresent = 40, diff --git a/src/ByteSync.Client/Business/Filtering/Evaluators/ActionComparisonExpressionEvaluator.cs b/src/ByteSync.Client/Business/Filtering/Evaluators/ActionComparisonExpressionEvaluator.cs index f01d20c4..e4535297 100644 --- a/src/ByteSync.Client/Business/Filtering/Evaluators/ActionComparisonExpressionEvaluator.cs +++ b/src/ByteSync.Client/Business/Filtering/Evaluators/ActionComparisonExpressionEvaluator.cs @@ -1,16 +1,16 @@ -using ByteSync.Business.Actions.Local; +using System.Text.RegularExpressions; +using ByteSync.Business.Actions.Local; using ByteSync.Business.Filtering.Expressions; using ByteSync.Business.Filtering.Parsing; -using ByteSync.Models.Comparisons.Result; -using System.Text.RegularExpressions; using ByteSync.Interfaces.Repositories; +using ByteSync.Models.Comparisons.Result; namespace ByteSync.Business.Filtering.Evaluators; public class ActionComparisonExpressionEvaluator : ExpressionEvaluator { private readonly IAtomicActionRepository _actionRepository; - + public ActionComparisonExpressionEvaluator(IAtomicActionRepository actionRepository) { _actionRepository = actionRepository; @@ -35,7 +35,7 @@ public override bool Evaluate(ActionComparisonExpression expression, ComparisonI _ => throw new ArgumentException($"Unsupported operator for actions: {expression.Operator}") }; } - + private int GetActionCount(ComparisonItem item, string[] pathParts) { // The base of the path is always "actions" @@ -43,10 +43,10 @@ private int GetActionCount(ComparisonItem item, string[] pathParts) { return 0; } - + // Get the full list of actions var allActions = _actionRepository.GetAtomicActions(item); - + if (!allActions.Any()) { return 0; @@ -82,7 +82,7 @@ private int GetActionCount(ComparisonItem item, string[] pathParts) private int FilterActionsByType(List actions, string actionType) { var actionTypePattern = new Regex( - @"^([a-zA-Z-]+)$", + @"^([a-zA-Z-]+)$", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(500)); var match = actionTypePattern.Match(actionType); @@ -94,13 +94,13 @@ private int FilterActionsByType(List actions, string actionType) return normalizedActionType switch { - Identifiers.ACTION_COPY_CONTENTS => actions.Count(a => a.IsSynchronizeContent), - Identifiers.ACTION_COPY => actions.Count(a => a.IsSynchronizeContentAndDate), - Identifiers.ACTION_COPY_DATES => actions.Count(a => a.IsSynchronizeDate), + Identifiers.ACTION_COPY_CONTENT => actions.Count(a => a.IsCopyContent), + Identifiers.ACTION_COPY => actions.Count(a => a.IsFullCopy), + Identifiers.ACTION_COPY_DATES => actions.Count(a => a.IsCopyDates), Identifiers.ACTION_SYNCHRONIZE_DELETE => actions.Count(a => a.IsDelete), Identifiers.ACTION_SYNCHRONIZE_CREATE => actions.Count(a => a.IsCreate), Identifiers.ACTION_DO_NOTHING => actions.Count(a => a.IsDoNothing), _ => 0 }; } -} +} \ No newline at end of file diff --git a/src/ByteSync.Client/Business/Filtering/Evaluators/PropertyValueExtractor.cs b/src/ByteSync.Client/Business/Filtering/Evaluators/PropertyValueExtractor.cs index 0247d068..c9a1040d 100644 --- a/src/ByteSync.Client/Business/Filtering/Evaluators/PropertyValueExtractor.cs +++ b/src/ByteSync.Client/Business/Filtering/Evaluators/PropertyValueExtractor.cs @@ -1,4 +1,5 @@ -using ByteSync.Business.Comparisons; +using System.IO; +using ByteSync.Business.Comparisons; using ByteSync.Business.Filtering.Parsing; using ByteSync.Business.Filtering.Values; using ByteSync.Interfaces.Services.Filtering; @@ -15,50 +16,50 @@ public PropertyValueCollection GetPropertyValue(ComparisonItem item, DataPart? d { return GetGeneralPropertyValue(item, property); } - + // Find content identities for the specific data source var contentIdentities = item.GetContentIdentities(dataPart.GetApplicableInventoryPart()); - + var propertyLower = property.ToLowerInvariant(); var propertyActions = new Dictionary> { - { Identifiers.PROPERTY_CONTENTS, () => ExtractContent(contentIdentities) }, - { Identifiers.PROPERTY_CONTENTS_AND_DATE, () => ExtractContentAndDate(contentIdentities, dataPart) }, + { Identifiers.PROPERTY_CONTENT, () => ExtractContent(contentIdentities) }, + { Identifiers.PROPERTY_CONTENT_AND_DATE, () => ExtractContentAndDate(contentIdentities, dataPart) }, { Identifiers.PROPERTY_SIZE, () => ExtractSize(contentIdentities, dataPart) }, { Identifiers.PROPERTY_LAST_WRITE_TIME, () => ExtractLastWriteTime(contentIdentities, dataPart) }, }; - + if (propertyActions.TryGetValue(propertyLower, out var action)) { return action(); } - + throw new ArgumentException($"Unknown property: {property}"); } - + private PropertyValueCollection ExtractContent(List contentIdentities) { var contents = new HashSet(); - + foreach (var contentIdentity in contentIdentities) { contents.Add(contentIdentity.Core!.SignatureHash!); } - + var result = new PropertyValueCollection(); foreach (var content in contents) { result.Add(new PropertyValue(content)); } - + return result; } private PropertyValueCollection ExtractContentAndDate(List contentIdentities, DataPart dataPart) { var contents = new HashSet(); - + foreach (var contentIdentity in contentIdentities) { var signatureHash = contentIdentity.Core!.SignatureHash; @@ -66,20 +67,20 @@ private PropertyValueCollection ExtractContentAndDate(List cont contents.Add($"{signatureHash}_{lastWriteTime?.Ticks}"); } - + var result = new PropertyValueCollection(); foreach (var content in contents) { result.Add(new PropertyValue(content)); } - + return result; } private PropertyValueCollection ExtractSize(List contentIdentities, DataPart dataPart) { var contents = new HashSet(); - + foreach (var contentIdentity in contentIdentities) { foreach (var fileSystemDescription in contentIdentity.GetFileSystemDescriptions(dataPart.GetApplicableInventoryPart())) @@ -90,20 +91,20 @@ private PropertyValueCollection ExtractSize(List contentIdentit } } } - + var result = new PropertyValueCollection(); foreach (var content in contents) { result.Add(new PropertyValue(content)); } - + return result; } private PropertyValueCollection ExtractLastWriteTime(List contentIdentities, DataPart dataPart) { var contents = new HashSet(); - + foreach (var contentIdentity in contentIdentities) { foreach (var fileSystemDescription in contentIdentity.GetFileSystemDescriptions(dataPart.GetApplicableInventoryPart())) @@ -114,39 +115,42 @@ private PropertyValueCollection ExtractLastWriteTime(List conte } } } - + var result = new PropertyValueCollection(); foreach (var content in contents) { result.Add(new PropertyValue(content)); } - + return result; } - + /// /// Gets property value that's not specific to a data source /// private PropertyValueCollection GetGeneralPropertyValue(ComparisonItem item, string property) { var propertyLower = property.ToLowerInvariant(); - + object value; switch (propertyLower) { case "ext": - value = System.IO.Path.GetExtension(item.PathIdentity.FileName).TrimStart('.'); + value = Path.GetExtension(item.PathIdentity.FileName).TrimStart('.'); + break; case "name": - value = System.IO.Path.GetFileNameWithoutExtension(item.PathIdentity.FileName); + value = Path.GetFileNameWithoutExtension(item.PathIdentity.FileName); + break; case "path": value = item.PathIdentity.FileName; + break; default: throw new ArgumentException($"Property '{property}' requires a data source"); } - + var result = new PropertyValueCollection(); result.Add(new PropertyValue(value)); diff --git a/src/ByteSync.Client/Business/Filtering/Parsing/Identifiers.cs b/src/ByteSync.Client/Business/Filtering/Parsing/Identifiers.cs index dfbd5de4..1e56087f 100644 --- a/src/ByteSync.Client/Business/Filtering/Parsing/Identifiers.cs +++ b/src/ByteSync.Client/Business/Filtering/Parsing/Identifiers.cs @@ -1,9 +1,11 @@ -namespace ByteSync.Business.Filtering.Parsing; +using System.Reflection; + +namespace ByteSync.Business.Filtering.Parsing; public class Identifiers { public const string ACTION_COPY = "copy"; - public const string ACTION_COPY_CONTENTS = "copy-contents"; + public const string ACTION_COPY_CONTENT = "copy-content"; public const string ACTION_COPY_DATES = "copy-dates"; public const string ACTION_SYNCHRONIZE_CREATE = "create"; public const string ACTION_SYNCHRONIZE_DELETE = "delete"; @@ -18,8 +20,8 @@ public class Identifiers public const string OPERATOR_NAME = "name"; public const string OPERATOR_PATH = "path"; - public const string PROPERTY_CONTENTS = "contents"; - public const string PROPERTY_CONTENTS_AND_DATE = "contents-and-date"; + public const string PROPERTY_CONTENT = "content"; + public const string PROPERTY_CONTENT_AND_DATE = "content-and-date"; public const string PROPERTY_LAST_WRITE_TIME = "last-write-time"; public const string PROPERTY_SIZE = "size"; @@ -28,15 +30,15 @@ public class Identifiers public const string PROPERTY_DIR = "dir"; public const string PROPERTY_PLACEHOLDER = "_"; - + private static List? _cachedAll; - + public static List All() { if (_cachedAll == null) { _cachedAll = typeof(Identifiers) - .GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.FlattenHierarchy) + .GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy) .Where(field => field.IsLiteral && !field.IsInitOnly && field.FieldType == typeof(string)) .Select(field => (string)field.GetValue(null)) .ToList(); @@ -44,4 +46,4 @@ public static List All() return _cachedAll; } -} +} \ No newline at end of file diff --git a/src/ByteSync.Client/Services/Actions/SharedActionsGroupComputer.cs b/src/ByteSync.Client/Services/Actions/SharedActionsGroupComputer.cs index 9c1af3c2..b2bc6409 100644 --- a/src/ByteSync.Client/Services/Actions/SharedActionsGroupComputer.cs +++ b/src/ByteSync.Client/Services/Actions/SharedActionsGroupComputer.cs @@ -1,9 +1,6 @@ using System.Threading; -using System.Threading.Tasks; using ByteSync.Business.Actions.Shared; -using ByteSync.Business.Inventories; using ByteSync.Common.Business.Actions; -using ByteSync.Common.Helpers; using ByteSync.Interfaces.Controls.Actions; using ByteSync.Interfaces.Repositories; @@ -13,37 +10,40 @@ public class SharedActionsGroupComputer : ISharedActionsGroupComputer { private readonly ISharedAtomicActionRepository _sharedAtomicActionRepository; private readonly ISharedActionsGroupRepository _sharedActionsGroupRepository; - + private List _buffer; - private int _counter; + private int _counter; private readonly object _lock = new(); - - public SharedActionsGroupComputer(ISharedAtomicActionRepository sharedAtomicActionRepository, ISharedActionsGroupRepository sharedActionsGroupRepository) + + public SharedActionsGroupComputer(ISharedAtomicActionRepository sharedAtomicActionRepository, + ISharedActionsGroupRepository sharedActionsGroupRepository) { _sharedAtomicActionRepository = sharedAtomicActionRepository; _sharedActionsGroupRepository = sharedActionsGroupRepository; _buffer = new List(); - + _counter = 0; } - + public async Task ComputeSharedActionsGroups() { var sharedAtomicActions = _sharedAtomicActionRepository.Elements; - + var dictionary = sharedAtomicActions.GroupBy(saa => saa.PathIdentity) .ToDictionary(g => g.Key, g => g.ToList()); - await Parallel.ForEachAsync(dictionary, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount * 2 }, (pair, _) => - { - ComputeGroups_CopyContentAndDate(pair.Value); - ComputeGroups_CopyContent(pair.Value); - ComputeGroups_CopyDate(pair.Value); - ComputeGroups_Create(pair.Value); - ComputeGroups_Delete(pair.Value); - return ValueTask.CompletedTask; - }); - + await Parallel.ForEachAsync(dictionary, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount * 2 }, + (pair, _) => + { + ComputeGroups_CopyContentAndDate(pair.Value); + ComputeGroups_CopyContent(pair.Value); + ComputeGroups_CopyDate(pair.Value); + ComputeGroups_Create(pair.Value); + ComputeGroups_Delete(pair.Value); + + return ValueTask.CompletedTask; + }); + lock (_lock) { if (_buffer.Count > 0) @@ -53,18 +53,18 @@ public async Task ComputeSharedActionsGroups() } } } - + private void ComputeGroups_CopyContentAndDate(List atomicActions) { - var sharedAtomicActions = GetSharedAtomicActions(atomicActions, ActionOperatorTypes.SynchronizeContentAndDate); + var sharedAtomicActions = GetSharedAtomicActions(atomicActions, ActionOperatorTypes.Copy); var groups = GetCopyGroups(sharedAtomicActions, true); - + var sharedActionsGroups = new List(); foreach (var group in groups) { - var sharedActionsGroup = BuildSharedActionsGroup(ActionOperatorTypes.SynchronizeContentAndDate); - + var sharedActionsGroup = BuildSharedActionsGroup(ActionOperatorTypes.Copy); + sharedActionsGroup.Source = group.First().Source; sharedActionsGroup.Targets.AddAll(group.Select(saa => saa.Target!)); sharedActionsGroup.PathIdentity = group.First().PathIdentity; @@ -73,30 +73,30 @@ private void ComputeGroups_CopyContentAndDate(List atomicAct sharedActionsGroup.Size = group.First().Size; sharedActionsGroup.SynchronizationType = group.First().SynchronizationType; sharedActionsGroup.IsFromSynchronizationRule = group.First().IsFromSynchronizationRule; - - if (sharedActionsGroup.Targets.All(t => t.SignatureHash != null + + if (sharedActionsGroup.Targets.All(t => t.SignatureHash != null && t.SignatureHash!.Equals(sharedActionsGroup.Source!.SignatureHash))) { - sharedActionsGroup.AppliesOnlySynchronizeDate = true; + sharedActionsGroup.AppliesOnlyCopyDate = true; } - + sharedActionsGroups.Add(sharedActionsGroup); } AddSharedActionsGroups(sharedActionsGroups); } - + private void ComputeGroups_CopyContent(List atomicActions) { - var sharedAtomicActions = GetSharedAtomicActions(atomicActions, ActionOperatorTypes.SynchronizeContentOnly); - + var sharedAtomicActions = GetSharedAtomicActions(atomicActions, ActionOperatorTypes.CopyContentOnly); + var groups = GetCopyGroups(sharedAtomicActions, false); - + var sharedActionsGroups = new List(); foreach (var group in groups) { - var sharedActionsGroup = BuildSharedActionsGroup(ActionOperatorTypes.SynchronizeContentOnly); - + var sharedActionsGroup = BuildSharedActionsGroup(ActionOperatorTypes.CopyContentOnly); + sharedActionsGroup.Source = group.First().Source; sharedActionsGroup.Targets.AddAll(group.Select(saa => saa.Target!)); sharedActionsGroup.PathIdentity = group.First().PathIdentity; @@ -111,19 +111,19 @@ private void ComputeGroups_CopyContent(List atomicActions) AddSharedActionsGroups(sharedActionsGroups); } - + private void ComputeGroups_CopyDate(List atomicActions) { - var sharedAtomicActions = GetSharedAtomicActions(atomicActions, ActionOperatorTypes.SynchronizeDate); - + var sharedAtomicActions = GetSharedAtomicActions(atomicActions, ActionOperatorTypes.CopyDatesOnly); + var sharedActionsGroups = new List(); - foreach (KeyValuePair> pair in + foreach (KeyValuePair> pair in sharedAtomicActions.GroupBy(aa => aa.Source!) .ToDictionary(g => g.Key, g => g.ToList())) { - var sharedActionsGroup = BuildSharedActionsGroup(ActionOperatorTypes.SynchronizeDate); - + var sharedActionsGroup = BuildSharedActionsGroup(ActionOperatorTypes.CopyDatesOnly); + sharedActionsGroup.Source = pair.Key; sharedActionsGroup.Targets.AddAll(pair.Value.Select(saa => saa.Target!)); sharedActionsGroup.PathIdentity = pair.Value.First().PathIdentity; @@ -132,7 +132,7 @@ private void ComputeGroups_CopyDate(List atomicActions) sharedActionsGroup.IsFromSynchronizationRule = pair.Value.First().IsFromSynchronizationRule; AffectSharedActionsGroupId(sharedActionsGroup, pair.Value); - + sharedActionsGroups.Add(sharedActionsGroup); } @@ -148,23 +148,23 @@ private void ComputeGroups_Delete(List atomicActions) { DoComputeGroups_CreateDelete(ActionOperatorTypes.Delete, atomicActions); } - + private void DoComputeGroups_CreateDelete(ActionOperatorTypes operatorType, List atomicActions) { if (!operatorType.In(ActionOperatorTypes.Create, ActionOperatorTypes.Delete)) { throw new ArgumentOutOfRangeException(nameof(operatorType)); } - + var sharedAtomicActions = GetSharedAtomicActions(atomicActions, operatorType).ToList(); - + if (sharedAtomicActions.Count == 0) { return; } - + var sharedActionsGroup = BuildSharedActionsGroup(operatorType); - + sharedActionsGroup.Source = null; sharedActionsGroup.Targets.AddAll(sharedAtomicActions.Select(saa => saa.Target!)); sharedActionsGroup.PathIdentity = sharedAtomicActions.First().PathIdentity; @@ -174,15 +174,16 @@ private void DoComputeGroups_CreateDelete(ActionOperatorTypes operatorType, List AddSharedActionsGroup(sharedActionsGroup); } - - private IEnumerable GetSharedAtomicActions(List sharedAtomicActions, ActionOperatorTypes operatorType) + + private IEnumerable GetSharedAtomicActions(List sharedAtomicActions, + ActionOperatorTypes operatorType) { var automaticActions = sharedAtomicActions .Where(vm => vm.Operator == operatorType); return automaticActions; } - + private SharedActionsGroup BuildSharedActionsGroup(ActionOperatorTypes operatorType) { var group = new SharedActionsGroup(); @@ -196,6 +197,7 @@ private SharedActionsGroup BuildSharedActionsGroup(ActionOperatorTypes operatorT private string GenerateUniqueId() { var newCounter = Interlocked.Increment(ref _counter); + return $"AGID_{newCounter}"; } @@ -212,12 +214,11 @@ private void AddSharedActionsGroups(List sharedActionsGroups lock (_lock) { _buffer.AddAll(sharedActionsGroups); - + CheckBuffer(); } - } - + private void AddSharedActionsGroup(SharedActionsGroup sharedActionsGroup) { lock (_lock) @@ -227,7 +228,7 @@ private void AddSharedActionsGroup(SharedActionsGroup sharedActionsGroup) CheckBuffer(); } } - + private void CheckBuffer() { if (_buffer.Count > 500) @@ -236,7 +237,7 @@ private void CheckBuffer() _buffer.Clear(); } } - + private static List> GetCopyGroups(IEnumerable sharedAtomicActions, bool isContentAndDate) { var root = sharedAtomicActions @@ -261,7 +262,7 @@ private static List> GetCopyGroups(IEnumerable> lists = new List>(); foreach (var perSource in root) @@ -284,7 +285,7 @@ private static List> GetCopyGroups(IEnumerable> ComputeSharedAtomicActions() { var atomicActions = _atomicActionRepository.Elements; - + foreach (var atomicAction in atomicActions.Where(a => !a.IsDoNothing)) { CreateSharedAtomicActions(atomicAction, atomicAction.Destination, atomicAction.Source); } - + return Task.FromResult(_sharedAtomicActionRepository.Elements.ToList()); } - + private void CreateSharedAtomicActions(AtomicAction atomicAction, DataPart? target, DataPart? source) { SharedDataPart? sourceSharedDataPart = null; HashSet? targetSharedDataParts = null; - + SynchronizationTypes? synchronizationType = null; long? size = null; DateTime? lastWriteTimeUtc = null; DateTime? creationTimeUtc = null; - + if (atomicAction.IsDelete) { var targetContentIdentities = atomicAction.ComparisonItem!.GetContentIdentities(target!.GetApplicableInventoryPart()); - - targetSharedDataParts = new HashSet(); + + targetSharedDataParts = []; foreach (var contentIdentity in targetContentIdentities) { targetSharedDataParts.AddAll(BuildSharedDataPart(target, contentIdentity)); } } - + if (atomicAction.IsCreate) { - SharedDataPart targetSharedDataPart = DoBuildSharedDataPart(target!, atomicAction.PathIdentity!.LinkingKeyValue); - - targetSharedDataParts = new HashSet { targetSharedDataPart }; + var targetSharedDataPart = DoBuildSharedDataPart(target!, atomicAction.PathIdentity!.LinkingKeyValue); + + targetSharedDataParts = [targetSharedDataPart]; } - else if (atomicAction.IsSynchronizeContent || atomicAction.IsSynchronizeDate) + else if (atomicAction.IsCopyContent || atomicAction.IsCopyDates) { var sourceContentIdentities = atomicAction.ComparisonItem!.GetContentIdentities(source!.GetApplicableInventoryPart()); var targetContentIdentities = atomicAction.ComparisonItem!.GetContentIdentities(target!.GetApplicableInventoryPart()); - + if (sourceContentIdentities.Count != 1) { throw new ApplicationException("sourceContentIdentityViews.Count != 1 -- " + sourceContentIdentities.Count); } - + var sourceContentIdentity = sourceContentIdentities.Single(); - + size = sourceContentIdentity.Core?.Size; lastWriteTimeUtc = sourceContentIdentity.GetLastWriteTimeUtc(source.GetApplicableInventoryPart()); creationTimeUtc = sourceContentIdentity.GetCreationTimeUtc(source.GetApplicableInventoryPart()); - + if (targetContentIdentities.Count > 0) { synchronizationType = SynchronizationTypes.Delta; - + sourceSharedDataPart = BuildSharedDataPart(source, sourceContentIdentity).First(); - - targetSharedDataParts = new HashSet(); + + targetSharedDataParts = []; foreach (var targetContentIdentity in targetContentIdentities) { if (!Equals(targetContentIdentity.Core?.SignatureHash, @@ -100,93 +98,93 @@ private void CreateSharedAtomicActions(AtomicAction atomicAction, DataPart? targ else { synchronizationType = SynchronizationTypes.Full; - + sourceSharedDataPart = BuildSharedDataPart(source, sourceContentIdentity).First(); - - SharedDataPart targetSharedDataPart = DoBuildSharedDataPart(target, + + var targetSharedDataPart = DoBuildSharedDataPart(target, sourceSharedDataPart.RelativePath); - - targetSharedDataParts = new HashSet { targetSharedDataPart }; + + targetSharedDataParts = [targetSharedDataPart]; } } - + if (targetSharedDataParts == null || targetSharedDataParts.Count == 0) { - CreateSharedAtomicAction(atomicAction.AtomicActionId, sourceSharedDataPart, null, - atomicAction.Operator, atomicAction.PathIdentity!, synchronizationType, + CreateSharedAtomicAction(atomicAction.AtomicActionId, sourceSharedDataPart, null, + atomicAction.Operator, atomicAction.PathIdentity!, synchronizationType, size, lastWriteTimeUtc, creationTimeUtc, atomicAction.IsFromSynchronizationRule); } else { - foreach (SharedDataPart targetSharedDataPart in targetSharedDataParts) + foreach (var targetSharedDataPart in targetSharedDataParts) { - CreateSharedAtomicAction(atomicAction.AtomicActionId, sourceSharedDataPart, targetSharedDataPart, - atomicAction.Operator, atomicAction.PathIdentity!, synchronizationType, + CreateSharedAtomicAction(atomicAction.AtomicActionId, sourceSharedDataPart, targetSharedDataPart, + atomicAction.Operator, atomicAction.PathIdentity!, synchronizationType, size, lastWriteTimeUtc, creationTimeUtc, atomicAction.IsFromSynchronizationRule); } } } - - private void CreateSharedAtomicAction(string atomicActionId, SharedDataPart? sourceSharedDataPart, - SharedDataPart? targetSharedDataPart, ActionOperatorTypes operatorType, PathIdentity pathIdentity, + + private void CreateSharedAtomicAction(string atomicActionId, SharedDataPart? sourceSharedDataPart, + SharedDataPart? targetSharedDataPart, ActionOperatorTypes operatorType, PathIdentity pathIdentity, SynchronizationTypes? synchronizationType, long? size, DateTime? lastWriteTimeUtc, DateTime? creationTimeUtc, bool isFromSynchronizationRule) { var sharedAtomicAction = new SharedAtomicAction(atomicActionId); - + sharedAtomicAction.Operator = operatorType; sharedAtomicAction.PathIdentity = pathIdentity; - + sharedAtomicAction.Source = sourceSharedDataPart; sharedAtomicAction.Target = targetSharedDataPart; - + sharedAtomicAction.SynchronizationType = synchronizationType; sharedAtomicAction.Size = size; sharedAtomicAction.CreationTimeUtc = creationTimeUtc; sharedAtomicAction.LastWriteTimeUtc = lastWriteTimeUtc; - + sharedAtomicAction.IsFromSynchronizationRule = isFromSynchronizationRule; _sharedAtomicActionRepository.AddOrUpdate(sharedAtomicAction); } - + private HashSet BuildSharedDataPart(DataPart dataPart, ContentIdentity contentIdentity) { var result = new HashSet(); - + var fileSystemDescriptions = contentIdentity.GetFileSystemDescriptions(dataPart.GetApplicableInventoryPart()); - + foreach (var fileSystemDescription in fileSystemDescriptions) { // var signatureInfo = BuildSignatureInfo(fileDescription); - + string? signatureGuid = null; string? signatureHash = null; - bool hasAnalysisError = false; - + var hasAnalysisError = false; + if (fileSystemDescription is FileDescription fileDescription) { signatureGuid = fileDescription.SignatureGuid; signatureHash = contentIdentity.Core?.SignatureHash; hasAnalysisError = fileDescription.HasAnalysisError; } - - SharedDataPart sharedDataPart = DoBuildSharedDataPart(dataPart, + + var sharedDataPart = DoBuildSharedDataPart(dataPart, fileSystemDescription.RelativePath, signatureGuid, signatureHash, hasAnalysisError); - + result.Add(sharedDataPart); } - + return result; } - - private SharedDataPart DoBuildSharedDataPart(DataPart dataPart, string? relativePath = null, string? signatureGuid = null, + + private SharedDataPart DoBuildSharedDataPart(DataPart dataPart, string? relativePath = null, string? signatureGuid = null, string? signatureHash = null, bool hasAnalysisError = false) { var inventory = dataPart.GetAppliableInventory(); var inventoryPart = dataPart.GetApplicableInventoryPart(); - - SharedDataPart sharedDataPart = new SharedDataPart( + + var sharedDataPart = new SharedDataPart( dataPart.Name, inventory, inventoryPart, @@ -194,7 +192,7 @@ private SharedDataPart DoBuildSharedDataPart(DataPart dataPart, string? relative signatureGuid, signatureHash, hasAnalysisError); - + return sharedDataPart; } } \ No newline at end of file diff --git a/src/ByteSync.Client/Services/Communications/Transfers/Downloading/DownloadTargetBuilder.cs b/src/ByteSync.Client/Services/Communications/Transfers/Downloading/DownloadTargetBuilder.cs index 927699ac..dfa973bf 100644 --- a/src/ByteSync.Client/Services/Communications/Transfers/Downloading/DownloadTargetBuilder.cs +++ b/src/ByteSync.Client/Services/Communications/Transfers/Downloading/DownloadTargetBuilder.cs @@ -16,14 +16,13 @@ namespace ByteSync.Services.Communications.Transfers.Downloading; public class DownloadTargetBuilder : IDownloadTargetBuilder, IDisposable { - private readonly ICloudSessionLocalDataManager _cloudSessionLocalDataManager; private readonly ISessionProfileLocalDataManager _sessionProfileLocalDataManager; private readonly ISharedActionsGroupRepository _sharedActionsGroupRepository; private readonly IConnectionService _connectionService; private readonly ITemporaryFileManagerFactory _temporaryFileManagerFactory; private readonly ISessionService _sessionService; - + // Add a cache to ensure singleton DownloadTarget per fileId private readonly Dictionary _downloadTargetCache = new(); @@ -31,8 +30,9 @@ public class DownloadTargetBuilder : IDownloadTargetBuilder, IDisposable private readonly IDisposable _sessionSubscription; private readonly IDisposable _sessionStatusSubscription; private bool _disposed = false; - - public DownloadTargetBuilder(ICloudSessionLocalDataManager cloudSessionLocalDataManager, ISessionProfileLocalDataManager sessionProfileLocalDataManager, + + public DownloadTargetBuilder(ICloudSessionLocalDataManager cloudSessionLocalDataManager, + ISessionProfileLocalDataManager sessionProfileLocalDataManager, ISharedActionsGroupRepository sharedActionsGroupRepository, IConnectionService connectionService, ITemporaryFileManagerFactory temporaryFileManagerFactory, ISessionService sessionService) { @@ -47,7 +47,7 @@ public DownloadTargetBuilder(ICloudSessionLocalDataManager cloudSessionLocalData _sessionSubscription = _sessionService.SessionObservable .Where(session => session == null) .Subscribe(_ => ClearCache()); - + _sessionStatusSubscription = _sessionService.SessionStatusObservable .Where(status => status == SessionStatus.Preparation) .Subscribe(_ => ClearCache()); @@ -58,7 +58,7 @@ public DownloadTarget BuildDownloadTarget(SharedFileDefinition sharedFileDefinit // Check cache first if (_downloadTargetCache.TryGetValue(sharedFileDefinition.Id, out var existing)) return existing; - + LocalSharedFile? sharedFile = null; var downloadDestinations = new HashSet(); string destinationPath; @@ -66,7 +66,7 @@ public DownloadTarget BuildDownloadTarget(SharedFileDefinition sharedFileDefinit Dictionary>? finalDestinationsPerActionsGroupId = null; Dictionary? datesPerActionsGroupId = null; List? temporaryFileManagers = null; - + DownloadTarget downloadTarget; if (sharedFileDefinition.IsInventory) { @@ -98,15 +98,15 @@ public DownloadTarget BuildDownloadTarget(SharedFileDefinition sharedFileDefinit { var zipDestinationPath = _cloudSessionLocalDataManager.GetSynchronizationTempZipPath(sharedFileDefinition); downloadDestinations.Add(zipDestinationPath); - + foreach (var actionsGroupId in sharedFileDefinition.ActionsGroupIds!) { var sharedActionsGroup = _sharedActionsGroupRepository.GetSharedActionsGroup(actionsGroupId); var actionsGroupDestinations = sharedActionsGroup!.GetTargetsFullNames(_connectionService.CurrentEndPoint!); finalDestinationsPerActionsGroupId.Add(actionsGroupId, actionsGroupDestinations); - - if (sharedActionsGroup.IsSynchronizeContentAndDate) + + if (sharedActionsGroup.IsFullCopy) { datesPerActionsGroupId.Add(actionsGroupId, DownloadTargetDates.FromSharedActionsGroup(sharedActionsGroup)); } @@ -114,7 +114,8 @@ public DownloadTarget BuildDownloadTarget(SharedFileDefinition sharedFileDefinit } else { - var sharedActionsGroup = _sharedActionsGroupRepository.GetSharedActionsGroup(sharedFileDefinition.ActionsGroupIds!.Single()); + var sharedActionsGroup = + _sharedActionsGroupRepository.GetSharedActionsGroup(sharedFileDefinition.ActionsGroupIds!.Single()); var finalDestinations = sharedActionsGroup!.GetTargetsFullNames(_connectionService.CurrentEndPoint!); finalDestinationsPerActionsGroupId.Add(sharedActionsGroup.ActionsGroupId, finalDestinations); @@ -126,7 +127,7 @@ public DownloadTarget BuildDownloadTarget(SharedFileDefinition sharedFileDefinit { var temporaryFileManager = _temporaryFileManagerFactory.Create(finalDestination); var destinationTemporaryPath = temporaryFileManager.GetDestinationTemporaryPath(); - + downloadDestinations.Add(destinationTemporaryPath); temporaryFileManagers.Add(temporaryFileManager); } @@ -139,9 +140,10 @@ public DownloadTarget BuildDownloadTarget(SharedFileDefinition sharedFileDefinit downloadDestinations.Add(deltaDestination); } - if (sharedActionsGroup.IsSynchronizeContentAndDate) + if (sharedActionsGroup.IsFullCopy) { - datesPerActionsGroupId.Add(sharedActionsGroup.ActionsGroupId, DownloadTargetDates.FromSharedActionsGroup(sharedActionsGroup)); + datesPerActionsGroupId.Add(sharedActionsGroup.ActionsGroupId, + DownloadTargetDates.FromSharedActionsGroup(sharedActionsGroup)); } } } @@ -155,10 +157,10 @@ public DownloadTarget BuildDownloadTarget(SharedFileDefinition sharedFileDefinit downloadTarget.FinalDestinationsPerActionsGroupId = finalDestinationsPerActionsGroupId; downloadTarget.LastWriteTimeUtcPerActionsGroupId = datesPerActionsGroupId; downloadTarget.TemporaryFileManagers = temporaryFileManagers; - + // Store in cache _downloadTargetCache[sharedFileDefinition.Id] = downloadTarget; - + return downloadTarget; } diff --git a/src/ByteSync.Client/Services/Comparisons/AtomicActionConsistencyChecker.cs b/src/ByteSync.Client/Services/Comparisons/AtomicActionConsistencyChecker.cs index 67a6f491..6e1a850f 100644 --- a/src/ByteSync.Client/Services/Comparisons/AtomicActionConsistencyChecker.cs +++ b/src/ByteSync.Client/Services/Comparisons/AtomicActionConsistencyChecker.cs @@ -104,8 +104,8 @@ private AtomicActionValidationResult CanApply(AtomicAction atomicAction, Compari private static AtomicActionValidationResult CheckBasicConsistency(AtomicAction atomicAction, ComparisonItem comparisonItem) { - if (atomicAction.Operator.In(ActionOperatorTypes.SynchronizeContentAndDate, ActionOperatorTypes.SynchronizeContentOnly, - ActionOperatorTypes.SynchronizeDate)) + if (atomicAction.Operator.In(ActionOperatorTypes.Copy, ActionOperatorTypes.CopyContentOnly, + ActionOperatorTypes.CopyDatesOnly)) { if (comparisonItem.FileSystemType == FileSystemTypes.Directory) { @@ -168,13 +168,13 @@ private AtomicActionValidationResult CheckAdvancedConsistency(AtomicAction atomi { var enforceInventoryPartAccessGuard = ShouldEnforceInventoryPartAccessGuard(); - if (atomicAction.Operator.In(ActionOperatorTypes.SynchronizeContentAndDate, ActionOperatorTypes.SynchronizeContentOnly, - ActionOperatorTypes.SynchronizeDate)) + if (atomicAction.Operator.In(ActionOperatorTypes.Copy, ActionOperatorTypes.CopyContentOnly, + ActionOperatorTypes.CopyDatesOnly)) { return ValidateSynchronize(atomicAction, comparisonItem, enforceInventoryPartAccessGuard); } - if (atomicAction.IsSynchronizeDate || atomicAction.IsDelete) + if (atomicAction.IsCopyDates || atomicAction.IsDelete) { return ValidateSynchronizeDateOrDelete(atomicAction, comparisonItem, enforceInventoryPartAccessGuard); } @@ -229,9 +229,14 @@ private AtomicActionValidationResult ValidateSynchronize(AtomicAction atomicActi return AtomicActionValidationResult.Failure(AtomicActionValidationFailureReason.SourceNotAccessible); } - if (sourceContentIdentities.Count != 1) + if (sourceContentIdentities.Count == 0) { - return AtomicActionValidationResult.Failure(AtomicActionValidationFailureReason.InvalidSourceCount); + return AtomicActionValidationResult.Failure(AtomicActionValidationFailureReason.SourceMissing); + } + + if (sourceContentIdentities.Count >= 2) + { + return AtomicActionValidationResult.Failure(AtomicActionValidationFailureReason.SourceHasMultipleIdentities); } var sourceContentIdentity = sourceContentIdentities.Single(); @@ -284,7 +289,7 @@ private AtomicActionValidationResult ValidateSynchronize(AtomicAction atomicActi return AtomicActionValidationResult.Failure(AtomicActionValidationFailureReason.AtLeastOneTargetsNotAccessible); } - if (atomicAction.IsSynchronizeContentOnly && targetContentIdentities.Count != 0 && + if (atomicAction.IsCopyContentOnly && targetContentIdentities.Count != 0 && sourceContentIdentity.Core != null && targetContentIdentities.All(t => t.Core != null && sourceContentIdentity.Core.Equals(t.Core))) { @@ -469,8 +474,8 @@ private AtomicActionValidationResult CheckConsistencyAgainstAlreadySetActions(At { var alreadySetAtomicAction = alreadySetAtomicActions.Single(); - if ((!alreadySetAtomicAction.IsSynchronizeDate || !atomicAction.IsSynchronizeContentOnly) - && (!alreadySetAtomicAction.IsSynchronizeContentOnly || !atomicAction.IsSynchronizeDate)) + if ((!alreadySetAtomicAction.IsCopyDates || !atomicAction.IsCopyContentOnly) + && (!alreadySetAtomicAction.IsCopyContentOnly || !atomicAction.IsCopyDates)) { return AtomicActionValidationResult.Failure(AtomicActionValidationFailureReason .DestinationAlreadyUsedByNonComplementaryAction); diff --git a/src/ByteSync.Client/Services/Comparisons/DescriptionBuilders/AbstractDescriptionBuilder.cs b/src/ByteSync.Client/Services/Comparisons/DescriptionBuilders/AbstractDescriptionBuilder.cs index f41f2228..539cde08 100644 --- a/src/ByteSync.Client/Services/Comparisons/DescriptionBuilders/AbstractDescriptionBuilder.cs +++ b/src/ByteSync.Client/Services/Comparisons/DescriptionBuilders/AbstractDescriptionBuilder.cs @@ -1,7 +1,6 @@ using System.Text; using ByteSync.Assets.Resources; using ByteSync.Common.Business.Actions; -using ByteSync.Interfaces; using ByteSync.Interfaces.Services.Localizations; namespace ByteSync.Services.Comparisons.DescriptionBuilders; @@ -12,19 +11,19 @@ protected AbstractDescriptionBuilder(ILocalizationService localizationService) { LocalizationService = localizationService; } - + protected ILocalizationService LocalizationService { get; } - + public string GetDescription(T element) { var stringBuilder = new StringBuilder(); AppendDescription(stringBuilder, element); var description = stringBuilder.ToString(); - + return description; } - + public abstract void AppendDescription(StringBuilder stringBuilder, T element); protected string GetAction(AbstractAction action) @@ -35,34 +34,40 @@ protected string GetAction(AbstractAction action) protected string GetAction(ActionOperatorTypes operatorType) { var result = ""; - + switch (operatorType) { - case ActionOperatorTypes.SynchronizeContentOnly: - result = LocalizationService[nameof(Resources.SynchronizationActionDescription_Action_SynchronizeContent)]; + case ActionOperatorTypes.CopyContentOnly: + result = LocalizationService[nameof(Resources.SynchronizationActionDescription_Action_CopyContent)]; + break; - case ActionOperatorTypes.SynchronizeContentAndDate: - result = LocalizationService[nameof(Resources.SynchronizationActionDescription_Action_SynchronizeContentAndDate)]; + case ActionOperatorTypes.Copy: + result = LocalizationService[nameof(Resources.SynchronizationActionDescription_Action_Copy)]; + break; - case ActionOperatorTypes.SynchronizeDate: - result = LocalizationService[nameof(Resources.SynchronizationActionDescription_Action_SynchronizeDate)]; + case ActionOperatorTypes.CopyDatesOnly: + result = LocalizationService[nameof(Resources.SynchronizationActionDescription_Action_CopyDate)]; + break; case ActionOperatorTypes.Create: result = LocalizationService[nameof(Resources.SynchronizationActionDescription_Action_Create)]; + break; case ActionOperatorTypes.Delete: result = LocalizationService[nameof(Resources.SynchronizationActionDescription_Action_Delete)]; + break; case ActionOperatorTypes.DoNothing: result = LocalizationService[nameof(Resources.SynchronizationActionDescription_Action_DoNothing)]; + break; } - + if (result.IsEmpty()) { throw new ApplicationException("Unknown actionsGroup.Operator " + operatorType); } - + return result; } diff --git a/src/ByteSync.Client/Services/Synchronizations/SynchronizationActionHandler.cs b/src/ByteSync.Client/Services/Synchronizations/SynchronizationActionHandler.cs index 155e84eb..ec538544 100644 --- a/src/ByteSync.Client/Services/Synchronizations/SynchronizationActionHandler.cs +++ b/src/ByteSync.Client/Services/Synchronizations/SynchronizationActionHandler.cs @@ -25,7 +25,7 @@ public class SynchronizationActionHandler : ISynchronizationActionHandler private readonly ISynchronizationApiClient _synchronizationApiClient; private readonly IFileDatesSetter _fileDatesSetter; private readonly ILogger _logger; - + public SynchronizationActionHandler(ISessionService sessionService, IConnectionService connectionService, IDeltaManager deltaManager, ISynchronizationActionServerInformer synchronizationActionServerInformer, ISynchronizationActionRemoteUploader synchronizationActionRemoteUploader, @@ -43,20 +43,20 @@ public SynchronizationActionHandler(ISessionService sessionService, IConnectionS _fileDatesSetter = fileDatesSetter; _logger = logger; } - + public ByteSyncEndpoint CurrentEndPoint => _connectionService.CurrentEndPoint!; - + public async Task RunSynchronizationAction(SharedActionsGroup sharedActionsGroup, CancellationToken cancellationToken = default) { try { cancellationToken.ThrowIfCancellationRequested(); - - if (sharedActionsGroup.IsSynchronizeContentOnly || sharedActionsGroup.IsFinallySynchronizeContentAndDate) + + if (sharedActionsGroup.IsCopyContentOnly || sharedActionsGroup.IsFinallyCopyContentAndDate) { await RunCopyContentSynchronizationAction(sharedActionsGroup, cancellationToken); } - else if (sharedActionsGroup.IsSynchronizeDate || sharedActionsGroup.IsFinallySynchronizeDate) + else if (sharedActionsGroup.IsCopyDates || sharedActionsGroup.IsFinallySynchronizeDate) { await RunCopyDateSynchronizationAction(sharedActionsGroup, cancellationToken); } @@ -76,70 +76,70 @@ public async Task RunSynchronizationAction(SharedActionsGroup sharedActionsGroup catch (OperationCanceledException) { _logger.LogInformation("SynchronizationAction cancelled for {ActionsGroupId}", sharedActionsGroup.ActionsGroupId); - + throw; } catch (Exception) { await _synchronizationActionServerInformer.HandleCloudActionError(sharedActionsGroup); - + throw; } } - + public async Task RunPendingSynchronizationActions(CancellationToken cancellationToken = default) { _logger.LogInformation("Running pending synchronization actions"); - + cancellationToken.ThrowIfCancellationRequested(); - + if (_sessionService.CurrentSession is CloudSession) { if (_synchronizationService.SynchronizationProcessData.SynchronizationAbortRequest.Value == null) { await _synchronizationActionRemoteUploader.Complete(); - + await _synchronizationActionServerInformer.HandlePendingActions(); } else { await _synchronizationActionRemoteUploader.Abort(); - + await _synchronizationActionServerInformer.ClearPendingActions(); } } } - + private async Task RunCopyContentSynchronizationAction(SharedActionsGroup sharedActionsGroup, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - + var localTargets = GetLocalTargets(sharedActionsGroup); if (localTargets.Count > 0) { await RunCopyContentLocal(sharedActionsGroup, localTargets, cancellationToken); } - + cancellationToken.ThrowIfCancellationRequested(); - + var remoteTargets = GetRemoteTargets(sharedActionsGroup); if (remoteTargets.Count > 0) { await _synchronizationActionRemoteUploader.UploadForRemote(sharedActionsGroup); } } - + private async Task RunCopyContentLocal(SharedActionsGroup sharedActionsGroup, HashSet localTargets, CancellationToken cancellationToken) { var sourceFullName = sharedActionsGroup.GetSourceFullName(); - + foreach (var localTarget in localTargets) { cancellationToken.ThrowIfCancellationRequested(); - + var destinationFullName = sharedActionsGroup.GetFullName(localTarget); - + long? transferredBytes; if (sharedActionsGroup.SynchronizationType == SynchronizationTypes.Full) { @@ -148,29 +148,29 @@ private async Task RunCopyContentLocal(SharedActionsGroup sharedActionsGroup, Ha { _logger.LogInformation("{Type:l}: creating directory {directory}", $"Synchronization.{sharedActionsGroup.Operator}", destinationFileInfo.Directory); - + destinationFileInfo.Directory.Create(); } - + _logger.LogInformation("{Type:l}: copying from {source} to {destination}", $"Synchronization.{sharedActionsGroup.Operator}", sourceFullName, destinationFullName); - + // For full copy, the transferred volume equals the file size transferredBytes = new FileInfo(sourceFullName).Length; File.Copy(sourceFullName, destinationFullName, true); - + await ApplyDatesFromLocalSource(sharedActionsGroup, destinationFullName); } else { var deltaFullName = await _deltaManager.BuildDelta(sharedActionsGroup, localTarget, sourceFullName); - + try { // For delta copy, the transferred volume equals the delta size transferredBytes = new FileInfo(deltaFullName).Length; await _deltaManager.ApplyDelta(destinationFullName, deltaFullName); - + await ApplyDatesFromSharedActionsGroup(sharedActionsGroup, destinationFullName); } finally @@ -179,7 +179,7 @@ private async Task RunCopyContentLocal(SharedActionsGroup sharedActionsGroup, Ha File.Delete(deltaFullName); } } - + var metrics = new Dictionary { [sharedActionsGroup.ActionsGroupId] = new() @@ -187,15 +187,15 @@ private async Task RunCopyContentLocal(SharedActionsGroup sharedActionsGroup, Ha TransferredBytes = transferredBytes } }; - + await _synchronizationActionServerInformer.HandleCloudActionDone(sharedActionsGroup, localTarget, _synchronizationApiClient.AssertLocalCopyIsDone, metrics); } } - + private async Task ApplyDatesFromLocalSource(SharedActionsGroup sharedActionsGroup, string destinationFullName) { - if (sharedActionsGroup.IsSynchronizeContentOnly) + if (sharedActionsGroup.IsCopyContentOnly) { await _fileDatesSetter.SetDates(sharedActionsGroup, destinationFullName, null); } @@ -205,23 +205,23 @@ private async Task ApplyDatesFromLocalSource(SharedActionsGroup sharedActionsGro await _fileDatesSetter.SetDates(sharedActionsGroup, destinationFullName, downloadTargetDates); } } - + private async Task ApplyDatesFromSharedActionsGroup(SharedActionsGroup sharedActionsGroup, string destinationFullName) { DownloadTargetDates? downloadTargetDates = null; - - if (sharedActionsGroup.IsSynchronizeContentAndDate || sharedActionsGroup.IsSynchronizeDate) + + if (sharedActionsGroup.IsFullCopy || sharedActionsGroup.IsCopyDates) { downloadTargetDates = DownloadTargetDates.FromSharedActionsGroup(sharedActionsGroup); } - + await _fileDatesSetter.SetDates(sharedActionsGroup, destinationFullName, downloadTargetDates); } - + private HashSet GetLocalTargets(SharedActionsGroup sharedActionsGroup) { var localTargets = new HashSet(); - + foreach (var sharedDataPart in sharedActionsGroup.Targets) { if (sharedDataPart.ClientInstanceId.Equals(CurrentEndPoint.ClientInstanceId)) @@ -229,14 +229,14 @@ private HashSet GetLocalTargets(SharedActionsGroup sharedActions localTargets.Add(sharedDataPart); } } - + return localTargets; } - + private HashSet GetRemoteTargets(SharedActionsGroup sharedActionsGroup) { var remoteTargets = new HashSet(); - + foreach (var sharedDataPart in sharedActionsGroup.Targets) { if (!sharedDataPart.ClientInstanceId.Equals(CurrentEndPoint.ClientInstanceId)) @@ -244,24 +244,24 @@ private HashSet GetRemoteTargets(SharedActionsGroup sharedAction remoteTargets.Add(sharedDataPart); } } - + return remoteTargets; } - + private async Task RunCopyDateSynchronizationAction(SharedActionsGroup sharedActionsGroup, CancellationToken cancellationToken) { var localTargets = GetLocalTargets(sharedActionsGroup); - + foreach (var localTarget in localTargets) { var destinationPath = sharedActionsGroup.GetFullName(localTarget); - + cancellationToken.ThrowIfCancellationRequested(); - + if (File.Exists(destinationPath)) { await ApplyDatesFromSharedActionsGroup(sharedActionsGroup, destinationPath); - + await _synchronizationActionServerInformer.HandleCloudActionDone(sharedActionsGroup, localTarget, _synchronizationApiClient.AssertDateIsCopied); } @@ -269,26 +269,26 @@ await _synchronizationActionServerInformer.HandleCloudActionDone(sharedActionsGr { _logger.LogWarning("{Type:l}: can not apply last write time on {fileInfo}. This file does not exist", $"Synchronization.{sharedActionsGroup.Operator}", destinationPath); - + await _synchronizationActionServerInformer.HandleCloudActionError(sharedActionsGroup, localTarget); } } } - + private async Task RunDeleteSynchronizationAction(SharedActionsGroup sharedActionsGroup, CancellationToken cancellationToken) { var localTargets = GetLocalTargets(sharedActionsGroup); - + foreach (var localTarget in localTargets) { var destinationPath = sharedActionsGroup.GetFullName(localTarget); - + cancellationToken.ThrowIfCancellationRequested(); - + if (sharedActionsGroup.IsFile && File.Exists(destinationPath)) { var fileInfo = new FileInfo(destinationPath); - + _logger.LogInformation("{Type:l}: deleting {fileInfo}", $"Synchronization.{sharedActionsGroup.Operator}", fileInfo.FullName); fileInfo.Delete(); @@ -296,10 +296,10 @@ private async Task RunDeleteSynchronizationAction(SharedActionsGroup sharedActio else if (sharedActionsGroup.IsDirectory && Directory.Exists(destinationPath)) { var directoryInfo = new DirectoryInfo(destinationPath); - + _logger.LogInformation("{Type:l}: deleting {fileInfo}", $"Synchronization.{sharedActionsGroup.Operator}", directoryInfo.FullName); - + var subFilesCount = directoryInfo.GetFiles("*", SearchOption.AllDirectories).Length; directoryInfo.Delete(subFilesCount == 0); } @@ -308,30 +308,30 @@ private async Task RunDeleteSynchronizationAction(SharedActionsGroup sharedActio _logger.LogWarning("{Type:l}: {fileInfo} is already missing, will not try to delete it", $"Synchronization.{sharedActionsGroup.Operator}", destinationPath); } - + await _synchronizationActionServerInformer.HandleCloudActionDone(sharedActionsGroup, localTarget, _synchronizationApiClient.AssertFileOrDirectoryIsDeleted); } } - + private async Task RunCreateSynchronizationAction(SharedActionsGroup sharedActionsGroup, CancellationToken cancellationToken) { var localTargets = GetLocalTargets(sharedActionsGroup); - + foreach (var localTarget in localTargets) { var destinationPath = sharedActionsGroup.GetFullName(localTarget); cancellationToken.ThrowIfCancellationRequested(); - + if (sharedActionsGroup.PathIdentity.FileSystemType == FileSystemTypes.Directory) { var directoryInfo = new DirectoryInfo(destinationPath); - + if (!directoryInfo.Exists) { _logger.LogInformation("{Type:l}: creating {directoryInfo}", $"Synchronization.{sharedActionsGroup.Operator}", directoryInfo.FullName); - + directoryInfo.Create(); } else @@ -339,7 +339,7 @@ private async Task RunCreateSynchronizationAction(SharedActionsGroup sharedActio _logger.LogInformation("{Type:l}: {directoryInfo} already exists", $"Synchronization.{sharedActionsGroup.Operator}", directoryInfo.FullName); } - + await _synchronizationActionServerInformer.HandleCloudActionDone(sharedActionsGroup, localTarget, _synchronizationApiClient.AssertDirectoryIsCreated); } diff --git a/src/ByteSync.Client/ViewModels/Sessions/Comparisons/Actions/AtomicActionEditViewModel.cs b/src/ByteSync.Client/ViewModels/Sessions/Comparisons/Actions/AtomicActionEditViewModel.cs index 1cb33c93..827d918e 100644 --- a/src/ByteSync.Client/ViewModels/Sessions/Comparisons/Actions/AtomicActionEditViewModel.cs +++ b/src/ByteSync.Client/ViewModels/Sessions/Comparisons/Actions/AtomicActionEditViewModel.cs @@ -21,11 +21,11 @@ public class AtomicActionEditViewModel : BaseAtomicEditViewModel private ObservableAsPropertyHelper _isDestinationToVisible; private ObservableAsPropertyHelper _isDestinationOnVisible; private readonly IDataPartIndexer _dataPartIndexer; - + public AtomicActionEditViewModel() { } - + public AtomicActionEditViewModel(FileSystemTypes fileSystemTypes, bool showDeleteButton, List? comparisonItems, IDataPartIndexer dataPartIndexer) { @@ -34,7 +34,7 @@ public AtomicActionEditViewModel(FileSystemTypes fileSystemTypes, bool showDelet FileSystemType = fileSystemTypes; ShowDeleteButton = showDeleteButton; ComparisonItems = comparisonItems; - + Actions = new ObservableCollection(); Sources = new ObservableCollection(); Destinations = new ObservableCollection(); @@ -44,10 +44,10 @@ public AtomicActionEditViewModel(FileSystemTypes fileSystemTypes, bool showDelet var canSwapSides = this.WhenAnyValue(x => x.SelectedSource, x => x.SelectedDestination, (source, destination) => source != null && destination != null); SwapSidesCommand = ReactiveCommand.Create(SwapSides, canSwapSides); - + this.WhenAnyValue( x => x.SelectedAction, - (selectedAction) => selectedAction != null + (selectedAction) => selectedAction != null && !selectedAction.ActionOperatorType .In(ActionOperatorTypes.DoNothing, ActionOperatorTypes.Delete, ActionOperatorTypes.Create)) .ObserveOn(RxApp.MainThreadScheduler) @@ -61,9 +61,9 @@ public AtomicActionEditViewModel(FileSystemTypes fileSystemTypes, bool showDelet this.WhenAnyValue( x => x.SelectedAction, - (selectedAction) => selectedAction != null && - selectedAction.ActionOperatorType.In(ActionOperatorTypes.SynchronizeContentAndDate, ActionOperatorTypes.SynchronizeContentOnly, - ActionOperatorTypes.SynchronizeDate)) + (selectedAction) => selectedAction != null && + selectedAction.ActionOperatorType.In(ActionOperatorTypes.Copy, ActionOperatorTypes.CopyContentOnly, + ActionOperatorTypes.CopyDatesOnly)) .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, x => x.IsDestinationToVisible, out _isDestinationToVisible); @@ -74,12 +74,9 @@ public AtomicActionEditViewModel(FileSystemTypes fileSystemTypes, bool showDelet .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, x => x.IsDestinationOnVisible, out _isDestinationOnVisible); - + this.WhenAnyValue(x => x.SelectedSource) - .Subscribe(_ => - { - FillDestinations(); - }); + .Subscribe(_ => { FillDestinations(); }); this.WhenAnyValue(x => x.SelectedAction, (selectedAction) => selectedAction != null && selectedAction.ActionOperatorType.In(ActionOperatorTypes.Delete)) @@ -89,31 +86,31 @@ public AtomicActionEditViewModel(FileSystemTypes fileSystemTypes, bool showDelet SelectedDestination = null; FillDestinations(); }); - + FillActions(); FillSources(); FillDestinations(); } - + public List? ComparisonItems { get; set; } - + internal ObservableCollection Actions { get; set; } - + internal ObservableCollection Sources { get; set; } - + internal ObservableCollection Destinations { get; set; } private FileSystemTypes FileSystemType { get; } - + [Reactive] internal ActionViewModel? SelectedAction { get; set; } - + [Reactive] internal DataPart? SelectedSource { get; set; } - + [Reactive] internal DataPart? SelectedDestination { get; set; } - + public bool IsSourceVisible => _isSourceVisible.Value; public bool IsDestinationVisible => _isDestinationVisible.Value; @@ -121,27 +118,27 @@ public AtomicActionEditViewModel(FileSystemTypes fileSystemTypes, bool showDelet public bool IsDestinationToVisible => _isDestinationToVisible.Value; public bool IsDestinationOnVisible => _isDestinationOnVisible.Value; - + [Reactive] public bool ShowDeleteButton { get; set; } - + public ReactiveCommand RemoveCommand { get; set; } public ReactiveCommand SwapSidesCommand { get; set; } - + private void FillActions() { ActionViewModel actionViewModel; if (FileSystemType == FileSystemTypes.File) { - actionViewModel = new ActionViewModel(ActionOperatorTypes.SynchronizeContentAndDate, Resources.AtomicActionEdit_SynchronizeContentAndDate); + actionViewModel = new ActionViewModel(ActionOperatorTypes.Copy, Resources.AtomicActionEdit_Copy); Actions.Add(actionViewModel); - - actionViewModel = new ActionViewModel(ActionOperatorTypes.SynchronizeContentOnly, Resources.AtomicActionEdit_SynchronizeContent); + + actionViewModel = new ActionViewModel(ActionOperatorTypes.CopyContentOnly, Resources.AtomicActionEdit_CopyContent); Actions.Add(actionViewModel); - - actionViewModel = new ActionViewModel(ActionOperatorTypes.SynchronizeDate, Resources.AtomicActionEdit_SynchronizeDate); + + actionViewModel = new ActionViewModel(ActionOperatorTypes.CopyDatesOnly, Resources.AtomicActionEdit_CopyDate); Actions.Add(actionViewModel); } else if (FileSystemType == FileSystemTypes.Directory) @@ -157,40 +154,41 @@ private void FillActions() // Actions communes actionViewModel = new ActionViewModel(ActionOperatorTypes.Delete, Resources.AtomicActionEdit_Delete); Actions.Add(actionViewModel); - + actionViewModel = new ActionViewModel(ActionOperatorTypes.DoNothing, Resources.AtomicActionEdit_DoNothing); Actions.Add(actionViewModel); } - + private void FillSources() { Sources.Clear(); Sources.AddAll(_dataPartIndexer.GetAllDataParts()); } - + private void FillDestinations() { var selectedDestination = SelectedDestination; - + var destinations = _dataPartIndexer.GetAllDataParts().ToHashSet(); if (SelectedSource != null) { destinations.Remove(SelectedSource); } - + Destinations.Clear(); Destinations.AddAll(destinations); - + if (selectedDestination != null) { SelectedDestination = Destinations.FirstOrDefault(ad => ad.Equals(selectedDestination)); } + if (SelectedDestination == null && SelectedSource != null && Destinations.Count == 1) { SelectedDestination = Destinations.First(); } - - if (SelectedDestination == null && ComparisonItems != null && + + if (SelectedDestination == null && ComparisonItems != null && SelectedAction != null && SelectedAction.ActionOperatorType.In(ActionOperatorTypes.Create, ActionOperatorTypes.Delete)) { // We look if only one destination can be determined @@ -200,43 +198,43 @@ private void FillDestinations() foreach (var dataPart in Destinations) { var contentIdentities = comparisonItem.GetContentIdentities(dataPart.GetApplicableInventoryPart()); - - if (SelectedAction is { ActionOperatorType: ActionOperatorTypes.Create } + + if (SelectedAction is { ActionOperatorType: ActionOperatorTypes.Create } && contentIdentities.Count == 0) { possibleDataParts.Add(dataPart); } - - if (SelectedAction is { ActionOperatorType: ActionOperatorTypes.Delete } + + if (SelectedAction is { ActionOperatorType: ActionOperatorTypes.Delete } && contentIdentities.Count != 0) { possibleDataParts.Add(dataPart); } } } - + if (possibleDataParts.Count == 1) { SelectedDestination = possibleDataParts.Single(); } } } - + internal AtomicAction? ExportSynchronizationAction() { if (SelectedAction == null) { return null; } - - if (SelectedAction.ActionOperatorType.In(ActionOperatorTypes.Create, ActionOperatorTypes.Delete) + + if (SelectedAction.ActionOperatorType.In(ActionOperatorTypes.Create, ActionOperatorTypes.Delete) && SelectedDestination == null) { return null; } if (SelectedAction.ActionOperatorType - .In(ActionOperatorTypes.SynchronizeDate, ActionOperatorTypes.SynchronizeContentOnly, ActionOperatorTypes.SynchronizeContentAndDate) + .In(ActionOperatorTypes.CopyDatesOnly, ActionOperatorTypes.CopyContentOnly, ActionOperatorTypes.Copy) && (SelectedSource == null || SelectedDestination == null)) { return null; @@ -245,23 +243,23 @@ private void FillDestinations() var id = $"AAID_{Guid.NewGuid()}"; var atomicAction = new AtomicAction(); atomicAction.AtomicActionId = id; - + atomicAction.Operator = SelectedAction.ActionOperatorType; atomicAction.Source = SelectedSource; atomicAction.Destination = SelectedDestination; - + return atomicAction; } - + internal void SetAtomicAction(AtomicAction atomicAction) { SelectedAction = Actions.FirstOrDefault(a => Equals(a.ActionOperatorType, atomicAction.Operator)); - + SelectedSource = atomicAction.Source; SelectedDestination = atomicAction.Destination; } - - + + private void Remove() { RaiseRemoveRequested(); @@ -271,7 +269,7 @@ private void SwapSides() { var source = SelectedSource; var destination = SelectedDestination; - + SelectedSource = destination; SelectedDestination = source; } diff --git a/src/ByteSync.Client/ViewModels/Sessions/Comparisons/Actions/SynchronizationRuleGlobalViewModel.cs b/src/ByteSync.Client/ViewModels/Sessions/Comparisons/Actions/SynchronizationRuleGlobalViewModel.cs index 91fce2ad..0618602c 100644 --- a/src/ByteSync.Client/ViewModels/Sessions/Comparisons/Actions/SynchronizationRuleGlobalViewModel.cs +++ b/src/ByteSync.Client/ViewModels/Sessions/Comparisons/Actions/SynchronizationRuleGlobalViewModel.cs @@ -6,7 +6,6 @@ using ByteSync.Assets.Resources; using ByteSync.Business.Actions.Local; using ByteSync.Business.Sessions; -using ByteSync.Interfaces; using ByteSync.Interfaces.Controls.Synchronizations; using ByteSync.Interfaces.Dialogs; using ByteSync.Interfaces.Factories.ViewModels; @@ -16,7 +15,6 @@ using ByteSync.ViewModels.Sessions.Comparisons.Actions.Misc; using ReactiveUI; using ReactiveUI.Fody.Helpers; -using Serilog; namespace ByteSync.ViewModels.Sessions.Comparisons.Actions; @@ -27,32 +25,35 @@ public class SynchronizationRuleGlobalViewModel : FlyoutElementViewModel private readonly ILocalizationService _localizationService = null!; private readonly IActionEditViewModelFactory _actionEditViewModelFactory = null!; private readonly ISynchronizationRulesService _synchronizationRulesService = null!; - - public SynchronizationRuleGlobalViewModel() + private readonly ILogger _logger = null!; + + public SynchronizationRuleGlobalViewModel() { } - public SynchronizationRuleGlobalViewModel(IDialogService dialogService, - ISessionService sessionService, ILocalizationService localizationService, IActionEditViewModelFactory actionEditViewModelFactory, - ISynchronizationRulesService synchronizationRulesService, SynchronizationRule? baseAutomaticAction, bool isCloneMode) + public SynchronizationRuleGlobalViewModel(IDialogService dialogService, + ISessionService sessionService, ILocalizationService localizationService, IActionEditViewModelFactory actionEditViewModelFactory, + ISynchronizationRulesService synchronizationRulesService, ILogger logger, + SynchronizationRule? baseAutomaticAction, bool isCloneMode) { _dialogService = dialogService; _sessionService = sessionService; _localizationService = localizationService; _actionEditViewModelFactory = actionEditViewModelFactory; _synchronizationRulesService = synchronizationRulesService; - + _logger = logger; + ShowFileSystemTypeSelection = _sessionService.CurrentSessionSettings!.DataType == DataTypes.FilesDirectories; - + Conditions = new ObservableCollection(); Actions = new ObservableCollection(); - + AddConditionCommand = ReactiveCommand.Create(AddCondition); AddActionCommand = ReactiveCommand.Create(AddAction); SaveCommand = ReactiveCommand.Create(Save); ResetCommand = ReactiveCommand.Create(Reset); CancelCommand = ReactiveCommand.Create(Cancel); - + FileSystemTypes = BuildFileSystemTypes(); SelectedFileSystemType = _sessionService.CurrentSessionSettings!.DataType == DataTypes.Directories @@ -61,16 +62,16 @@ public SynchronizationRuleGlobalViewModel(IDialogService dialogService, ConditionModes = BuildConditionModes(); SelectedConditionMode = ConditionModes.Single(cm => cm.IsAll); - + BaseAutomaticAction = baseAutomaticAction; IsCloneMode = isCloneMode; - + Reset(); - + this.WhenAnyValue(x => x.SelectedFileSystemType) .Skip(1) .Subscribe(_ => Reset()); - + this.WhenAnyValue(x => x.SelectedConditionMode) .Subscribe(x => { @@ -92,30 +93,34 @@ public SynchronizationRuleGlobalViewModel(IDialogService dialogService, .DisposeWith(disposables); }); } - + public ReactiveCommand AddConditionCommand { get; set; } = null!; + public ReactiveCommand AddActionCommand { get; set; } = null!; + public ReactiveCommand SaveCommand { get; set; } = null!; + public ReactiveCommand ResetCommand { get; set; } = null!; + public ReactiveCommand CancelCommand { get; set; } = null!; - + public ObservableCollection FileSystemTypes { get; set; } = null!; - + public ObservableCollection ConditionModes { get; set; } = null!; - + public ObservableCollection Conditions { get; } = null!; - + public ObservableCollection Actions { get; } = null!; - + [Reactive] public FileSystemTypeViewModel SelectedFileSystemType { get; set; } = null!; - + [Reactive] public ConditionModeViewModel SelectedConditionMode { get; set; } = null!; - + [Reactive] public string TextAfterConditionModesComboBox { get; set; } = null!; - + [Reactive] public bool ShowFileSystemTypeSelection { get; set; } @@ -124,7 +129,7 @@ public SynchronizationRuleGlobalViewModel(IDialogService dialogService, [Reactive] public string SaveWarning { get; set; } = null!; - + public SynchronizationRule? BaseAutomaticAction { get; } public bool IsCloneMode { get; } @@ -132,51 +137,55 @@ public SynchronizationRuleGlobalViewModel(IDialogService dialogService, private ObservableCollection BuildFileSystemTypes() { var fileSystemTypes = new ObservableCollection(); - - var file = new FileSystemTypeViewModel { - FileSystemType = Common.Business.Inventories.FileSystemTypes.File, - Description = Resources.General_Files}; - var directory = new FileSystemTypeViewModel { - FileSystemType = Common.Business.Inventories.FileSystemTypes.Directory, - Description = Resources.General_Directories}; + var file = new FileSystemTypeViewModel + { + FileSystemType = Common.Business.Inventories.FileSystemTypes.File, + Description = Resources.General_Files + }; + + var directory = new FileSystemTypeViewModel + { + FileSystemType = Common.Business.Inventories.FileSystemTypes.Directory, + Description = Resources.General_Directories + }; fileSystemTypes.Add(file); fileSystemTypes.Add(directory); - + return fileSystemTypes; } private ObservableCollection BuildConditionModes() { var conditionModes = new ObservableCollection(); - + var any = new ConditionModeViewModel { - Mode = ByteSync.Business.Comparisons.ConditionModes.Any, + Mode = Business.Comparisons.ConditionModes.Any, Description = Resources.SynchronizationRulesGlobal_Any }; - + var all = new ConditionModeViewModel { - Mode = ByteSync.Business.Comparisons.ConditionModes.All, + Mode = Business.Comparisons.ConditionModes.All, Description = Resources.SynchronizationRulesGlobal_All }; - + conditionModes.Add(any); conditionModes.Add(all); - + return conditionModes; } - + private void AddCondition() { var atomicConditionEditViewModel = _actionEditViewModelFactory.BuildAtomicConditionEditViewModel( SelectedFileSystemType.FileSystemType); - + AddCondition(atomicConditionEditViewModel); } - + private void AddCondition(AtomicConditionEditViewModel atomicConditionEditViewModel) { atomicConditionEditViewModel.PropertyChanged += OnConditionOrActionPropertyChanged; @@ -184,15 +193,15 @@ private void AddCondition(AtomicConditionEditViewModel atomicConditionEditViewMo Conditions.Add(atomicConditionEditViewModel); } - + private void AddAction() { var atomicActionEditViewModel = _actionEditViewModelFactory.BuildAtomicActionEditViewModel( SelectedFileSystemType.FileSystemType, true); - + AddAction(atomicActionEditViewModel); } - + private void AddAction(AtomicActionEditViewModel atomicActionEditViewModel) { atomicActionEditViewModel.PropertyChanged += OnConditionOrActionPropertyChanged; @@ -200,19 +209,20 @@ private void AddAction(AtomicActionEditViewModel atomicActionEditViewModel) Actions.Add(atomicActionEditViewModel); } - + private void Save() { try { var synchronizationRule = Export(); - + if (synchronizationRule != null) { ShowWarning = false; _synchronizationRulesService.AddOrUpdateSynchronizationRule(synchronizationRule); - + LogSaveSuccess(synchronizationRule); + _dialogService.CloseFlyout(); } else @@ -222,24 +232,25 @@ private void Save() } catch (Exception ex) { - Log.Error(ex, "Error during saving"); + _logger.LogError(ex, "Error while saving synchronization rule"); } } - + private void ShowMissingFieldsWarning() { ShowWarning = true; SaveWarning = Resources.TargetedActionEditionGlobal_MissingFields; + _logger.LogWarning("Synchronization rule validation failed: missing fields"); } - + private SynchronizationRule? Export() { var synchronizationRule = new SynchronizationRule(SelectedFileSystemType.FileSystemType, SelectedConditionMode.Mode); - if (BaseAutomaticAction != null && ! IsCloneMode) + if (BaseAutomaticAction != null && !IsCloneMode) { synchronizationRule.SynchronizationRuleId = BaseAutomaticAction.SynchronizationRuleId; } - + var isMissingField = false; // Conditions @@ -255,12 +266,12 @@ private void ShowMissingFieldsWarning() isMissingField = true; } } - + // Actions foreach (var automaticActionsActionEditViewModel in Actions) { var atomicAction = automaticActionsActionEditViewModel.ExportSynchronizationAction(); - + if (atomicAction != null) { synchronizationRule.AddAction(atomicAction); @@ -270,7 +281,7 @@ private void ShowMissingFieldsWarning() isMissingField = true; } } - + if (isMissingField) { return null; @@ -280,7 +291,7 @@ private void ShowMissingFieldsWarning() return synchronizationRule; } } - + private void Reset() { ShowWarning = false; @@ -294,7 +305,7 @@ private void Reset() ResetToEdition(); } } - + private void ResetToCreation() { Conditions.Clear(); @@ -303,23 +314,24 @@ private void ResetToCreation() AddCondition(); AddAction(); } - - + + private void ResetToEdition() { Conditions.Clear(); Actions.Clear(); - + SelectedFileSystemType = FileSystemTypes.First(fst => Equals(fst.FileSystemType, BaseAutomaticAction!.FileSystemType)); SelectedConditionMode = ConditionModes.First(cm => Equals(cm.Mode, BaseAutomaticAction!.ConditionMode)); - + foreach (var condition in BaseAutomaticAction!.Conditions) { - var atomicConditionEditViewModel = _actionEditViewModelFactory.BuildAtomicConditionEditViewModel(SelectedFileSystemType.FileSystemType, condition); + var atomicConditionEditViewModel = + _actionEditViewModelFactory.BuildAtomicConditionEditViewModel(SelectedFileSystemType.FileSystemType, condition); AddCondition(atomicConditionEditViewModel); } - + foreach (var action in BaseAutomaticAction.Actions) { var automaticActionsActionEditViewModel = _actionEditViewModelFactory.BuildAtomicActionEditViewModel( @@ -328,7 +340,7 @@ private void ResetToEdition() AddAction(automaticActionsActionEditViewModel); } } - + private void OnConditionOrActionPropertyChanged(object? sender, PropertyChangedEventArgs e) { ShowWarning = false; @@ -353,18 +365,30 @@ private void OnConditionRemoveRequested(object? sender, BaseAtomicEditViewModel Conditions.Remove(atomicConditionEditViewModel); } - + private void Cancel() { _dialogService.CloseFlyout(); - + Reset(); } private void OnLocaleChanged() { ConditionModes.Single(cm => cm.IsAny).Description = Resources.SynchronizationRulesGlobal_Any; - + ConditionModes.Single(cm => cm.IsAll).Description = Resources.SynchronizationRulesGlobal_All; } + + private void LogSaveSuccess(SynchronizationRule synchronizationRule) + { + _logger.LogInformation( + "Synchronization rule saved. RuleId={RuleId} FileSystemType={FileSystemType} ConditionMode={ConditionMode} Conditions={ConditionsCount} Actions={ActionsCount} IsCloneMode={IsCloneMode}", + synchronizationRule.SynchronizationRuleId ?? string.Empty, + synchronizationRule.FileSystemType, + synchronizationRule.ConditionMode, + synchronizationRule.Conditions.Count, + synchronizationRule.Actions.Count, + IsCloneMode); + } } \ No newline at end of file diff --git a/src/ByteSync.Client/ViewModels/Sessions/Comparisons/Actions/TargetedActionGlobalViewModel.cs b/src/ByteSync.Client/ViewModels/Sessions/Comparisons/Actions/TargetedActionGlobalViewModel.cs index 9b975052..fd5e232d 100644 --- a/src/ByteSync.Client/ViewModels/Sessions/Comparisons/Actions/TargetedActionGlobalViewModel.cs +++ b/src/ByteSync.Client/ViewModels/Sessions/Comparisons/Actions/TargetedActionGlobalViewModel.cs @@ -7,14 +7,12 @@ using ByteSync.Business.Actions.Local; using ByteSync.Business.Comparisons; using ByteSync.Common.Business.Inventories; -using ByteSync.Interfaces; using ByteSync.Interfaces.Controls.Comparisons; using ByteSync.Interfaces.Dialogs; using ByteSync.Interfaces.Factories.ViewModels; using ByteSync.Interfaces.Services.Localizations; using ByteSync.Models.Comparisons.Result; using ByteSync.ViewModels.Misc; -using ByteSync.Services.Localizations; using ReactiveUI; using ReactiveUI.Fody.Helpers; @@ -23,27 +21,28 @@ namespace ByteSync.ViewModels.Sessions.Comparisons.Actions; public class TargetedActionGlobalViewModel : FlyoutElementViewModel { readonly ObservableAsPropertyHelper _canEditAction = null!; - + private readonly ILocalizationService _localizationService = null!; private readonly IDialogService _dialogService = null!; private readonly ITargetedActionsService _targetedActionsService = null!; private readonly IAtomicActionConsistencyChecker _atomicActionConsistencyChecker = null!; private readonly IActionEditViewModelFactory _actionEditViewModelFactory = null!; private readonly IAtomicActionValidationFailureReasonService _failureReasonLocalizer = null!; - - public TargetedActionGlobalViewModel() + private readonly ILogger _logger = null!; + + public TargetedActionGlobalViewModel() { - } - - public TargetedActionGlobalViewModel(List comparisonItems, + + public TargetedActionGlobalViewModel(List comparisonItems, IDialogService dialogService, ILocalizationService localizationService, ITargetedActionsService targetedActionsService, IAtomicActionConsistencyChecker atomicActionConsistencyChecker, - IActionEditViewModelFactory actionEditViewModelFactory, + IActionEditViewModelFactory actionEditViewModelFactory, + ILogger logger, IAtomicActionValidationFailureReasonService failureReasonLocalizer) { ComparisonItems = comparisonItems; - + FileSystemType = ComparisonItems.Select(civm => civm.FileSystemType).ToHashSet().Single(); _dialogService = dialogService; @@ -52,38 +51,39 @@ public TargetedActionGlobalViewModel(List comparisonItems, _atomicActionConsistencyChecker = atomicActionConsistencyChecker; _actionEditViewModelFactory = actionEditViewModelFactory; _failureReasonLocalizer = failureReasonLocalizer; - + _logger = logger; + // Initialize localized messages ActionIssuesHeaderMessage = _localizationService[nameof(Resources.TargetedActionEditionGlobal_ActionIssues)]; AffectedItemsTooltipHeader = _localizationService[nameof(Resources.TargetedActionEditionGlobal_AffectedItemsTooltip)]; - + Actions = new ObservableCollection(); - + var canSave = this .WhenAnyValue( - x => x.ShowWarning, x=> x.ShowSaveValidItemsCommand, + x => x.ShowWarning, x => x.ShowSaveValidItemsCommand, (showWarning, showSaveValidItemsCommand) => !showWarning || !showSaveValidItemsCommand) .ObserveOn(RxApp.MainThreadScheduler); - + canSave .ToProperty(this, x => x.CanEditAction, out _canEditAction); - + this .WhenAnyValue(x => x.AreMissingFields, x => x.IsInconsistentWithValidItems, x => x.IsInconsistentWithNoValidItems, (areMissingFields, isInconsistentWithValidItems, isInconsistentWithNoValidItems) => areMissingFields || isInconsistentWithValidItems != null || isInconsistentWithNoValidItems) .ObserveOn(RxApp.MainThreadScheduler) .ToPropertyEx(this, x => x.ShowWarning); - + AddActionCommand = ReactiveCommand.Create(AddAction); SaveCommand = ReactiveCommand.Create(Save, canSave); ResetCommand = ReactiveCommand.Create(Reset); CancelCommand = ReactiveCommand.Create(Cancel); - + SaveValidItemsCommand = ReactiveCommand.Create(SaveValidItems); - + ResetWarning(); - + Reset(); this.WhenActivated(disposables => @@ -94,29 +94,32 @@ public TargetedActionGlobalViewModel(List comparisonItems, .DisposeWith(disposables); }); } - + public List ComparisonItems { get; private set; } = null!; - + public ReactiveCommand AddActionCommand { get; set; } = null!; - + public ReactiveCommand SaveCommand { get; set; } = null!; + public ReactiveCommand ResetCommand { get; set; } = null!; + public ReactiveCommand CancelCommand { get; set; } = null!; - + public ReactiveCommand SaveValidItemsCommand { get; set; } = null!; - + internal AtomicAction? BaseAtomicAction { get; set; } + internal ObservableCollection Actions { get; } = null!; - + private FileSystemTypes FileSystemType { get; } - public extern bool ShowWarning { [ObservableAsProperty]get; } - + public extern bool ShowWarning { [ObservableAsProperty] get; } + [Reactive] public bool ShowSaveValidItemsCommand { get; set; } - + public bool CanEditAction => _canEditAction.Value; - + [Reactive] public string SaveWarning { get; set; } = string.Empty; @@ -131,32 +134,32 @@ public TargetedActionGlobalViewModel(List comparisonItems, public ObservableCollection FailureSummaries { get; set; } = new(); - [Reactive] + [Reactive] public string ActionIssuesHeaderMessage { get; set; } = string.Empty; - [Reactive] + [Reactive] public string AffectedItemsTooltipHeader { get; set; } = string.Empty; - + private void AddAction() { - var atomicActionEditViewModel = + var atomicActionEditViewModel = _actionEditViewModelFactory.BuildAtomicActionEditViewModel(FileSystemType, false, null, ComparisonItems); atomicActionEditViewModel.PropertyChanged += AtomicActionEditViewModelOnPropertyChanged; - + Actions.Add(atomicActionEditViewModel); } - + private void AtomicActionEditViewModelOnPropertyChanged(object? sender, PropertyChangedEventArgs e) { ResetWarning(); } - - + + private void Save() { var atomicAction = Export(); - + if (atomicAction == null) { ShowMissingFieldsWarning(); @@ -170,16 +173,17 @@ private void Save() ResetWarning(); _targetedActionsService.AddTargetedAction(atomicAction, ComparisonItems); - + LogConsistencySuccess(atomicAction, ComparisonItems); + _dialogService.CloseFlyout(); } else { - ShowConsistencyWarning(result); + ShowConsistencyWarning(atomicAction, result); } } } - + private void SaveValidItems() { var atomicAction = Export(); @@ -192,18 +196,19 @@ private void SaveValidItems() var result = _atomicActionConsistencyChecker.CheckCanAdd(atomicAction, ComparisonItems); ResetWarning(); - + _targetedActionsService.AddTargetedAction(atomicAction, result.GetValidComparisonItems()); - + LogConsistencySuccess(atomicAction, result.GetValidComparisonItems()); + _dialogService.CloseFlyout(); } } - + private AtomicAction? Export() { var automaticActionsActionEditViewModel = Actions.Single(); // (AutomaticActionsActionEditViewModel)region.Views.Single(); var atomicAction = automaticActionsActionEditViewModel.ExportSynchronizationAction(); - + if (atomicAction != null) { if (BaseAtomicAction != null) @@ -211,11 +216,11 @@ private void SaveValidItems() atomicAction.AtomicActionId = BaseAtomicAction.AtomicActionId; } } - - + + return atomicAction; } - + private void Reset() { if (BaseAtomicAction == null) @@ -226,25 +231,26 @@ private void Reset() { ResetToEdition(); } - + ResetWarning(); } - + private void ResetToCreation() { ClearActions(); - + AddAction(); } - + private void ResetToEdition() { ClearActions(); - - var atomicActionEditViewModel = _actionEditViewModelFactory.BuildAtomicActionEditViewModel(FileSystemType, false, BaseAtomicAction, ComparisonItems); - + + var atomicActionEditViewModel = + _actionEditViewModelFactory.BuildAtomicActionEditViewModel(FileSystemType, false, BaseAtomicAction, ComparisonItems); + atomicActionEditViewModel.PropertyChanged += AtomicActionEditViewModelOnPropertyChanged; - + atomicActionEditViewModel.SetAtomicAction(BaseAtomicAction!); Actions.Add(atomicActionEditViewModel); @@ -259,11 +265,11 @@ private void ClearActions() Actions.Clear(); } - + private void Cancel() { RaiseCloseFlyoutRequested(); - + Reset(); } @@ -288,17 +294,17 @@ private void ShowMissingFieldsWarning() IsInconsistentWithValidItems = null; IsInconsistentWithNoValidItems = false; FailureSummaries.Clear(); // Clear previous validation failure summaries - + DoShowWarning(); } - private void ShowConsistencyWarning(AtomicActionConsistencyCheckCanAddResult checkCanAddResult) + private void ShowConsistencyWarning(AtomicAction atomicAction, AtomicActionConsistencyCheckCanAddResult checkCanAddResult) { ShowSaveValidItemsCommand = checkCanAddResult.GetValidComparisonItems().Count > 0; - + if (ShowSaveValidItemsCommand) { - IsInconsistentWithValidItems = new Tuple(checkCanAddResult.GetValidComparisonItems().Count, + IsInconsistentWithValidItems = new Tuple(checkCanAddResult.GetValidComparisonItems().Count, checkCanAddResult.GetInvalidComparisonItems().Count); IsInconsistentWithNoValidItems = false; } @@ -312,25 +318,28 @@ private void ShowConsistencyWarning(AtomicActionConsistencyCheckCanAddResult che FailureSummaries.Clear(); var summaries = checkCanAddResult.FailedValidations .GroupBy(f => f.FailureReason!.Value) - .Select(g => new ValidationFailureSummary + .Select(g => new ValidationFailureSummary { Reason = g.Key, Count = g.Count(), LocalizedMessage = _failureReasonLocalizer.GetLocalizedMessage(g.Key), AffectedItems = g.Select(f => f.ComparisonItem).ToList() }) - .OrderByDescending(s => s.Count); // Most frequent failures first + .OrderByDescending(s => s.Count) + .ToList(); // Most frequent failures first foreach (var summary in summaries) { FailureSummaries.Add(summary); } + LogConsistencyFailure(atomicAction, checkCanAddResult, summaries); + AreMissingFields = false; - + DoShowWarning(); } - + private void DoShowWarning() { var saveWarning = ""; @@ -349,15 +358,15 @@ private void DoShowWarning() { saveWarning = Resources.TargetedActionEditionGlobal_SaveWarningLocked; } - + SaveWarning = saveWarning; } - + private void OnAtomicInputChanged() { ResetWarning(); } - + private void ResetWarning() { AreMissingFields = false; @@ -365,4 +374,62 @@ private void ResetWarning() IsInconsistentWithNoValidItems = false; FailureSummaries.Clear(); } + + private void LogConsistencyFailure(AtomicAction atomicAction, AtomicActionConsistencyCheckCanAddResult result, + IEnumerable summaries) + { + var validItemsCount = result.GetValidComparisonItems().Count; + var invalidItemsCount = result.GetInvalidComparisonItems().Count; + var failureDetails = BuildFailureDetails(summaries); + + _logger.LogWarning( + "Targeted action validation failed. Operator={Operator} Source={Source} Destination={Destination} FileSystemType={FileSystemType} TotalItems={TotalItems} ValidItems={ValidItems} InvalidItems={InvalidItems} Failures={Failures}", + atomicAction.Operator, + atomicAction.SourceName ?? string.Empty, + atomicAction.DestinationName ?? string.Empty, + FileSystemType, + result.ComparisonItems.Count, + validItemsCount, + invalidItemsCount, + failureDetails); + } + + private void LogConsistencySuccess(AtomicAction atomicAction, IEnumerable comparisonItems) + { + var itemList = BuildItemList(comparisonItems); + + _logger.LogInformation( + "Targeted action created. Operator={Operator} Source={Source} Destination={Destination} FileSystemType={FileSystemType} Items={Items}", + atomicAction.Operator, + atomicAction.SourceName ?? string.Empty, + atomicAction.DestinationName ?? string.Empty, + FileSystemType, + itemList); + } + + private static string BuildItemList(IEnumerable comparisonItems) + { + return string.Join(", ", comparisonItems.Select(item => item.PathIdentity?.LinkingKeyValue ?? "unknown")); + } + + private static string BuildFailureDetails(IEnumerable summaries) + { + const int maxItemsPerReason = 3; + + var details = summaries + .Select(summary => + { + var items = summary.AffectedItems + .Select(item => item.PathIdentity.LinkingKeyValue) + .Take(maxItemsPerReason) + .ToList(); + + var suffix = summary.AffectedItems.Count > maxItemsPerReason ? ", ..." : string.Empty; + + return $"{summary.Reason}={summary.Count} [{string.Join(", ", items)}{suffix}]"; + }) + .ToList(); + + return details.Count == 0 ? "none" : string.Join("; ", details); + } } \ No newline at end of file diff --git a/src/ByteSync.Client/ViewModels/Sessions/Synchronizations/SynchronizationConfirmationViewModel.cs b/src/ByteSync.Client/ViewModels/Sessions/Synchronizations/SynchronizationConfirmationViewModel.cs index 93baa33c..f931e195 100644 --- a/src/ByteSync.Client/ViewModels/Sessions/Synchronizations/SynchronizationConfirmationViewModel.cs +++ b/src/ByteSync.Client/ViewModels/Sessions/Synchronizations/SynchronizationConfirmationViewModel.cs @@ -115,8 +115,8 @@ private void ComputeStatistics(List actions) DestinationCode = dataNode?.Code ?? "?", MachineName = sessionMember?.MachineName ?? "Unknown", CreateCount = group.Count(a => a.IsCreate), - SynchronizeContentCount = group.Count(a => a.IsSynchronizeContent), - SynchronizeDateCount = group.Count(a => a.IsSynchronizeDate), + SynchronizeContentCount = group.Count(a => a.IsCopyContent), + SynchronizeDateCount = group.Count(a => a.IsCopyDates), DeleteCount = group.Count(a => a.IsDelete) }; diff --git a/src/ByteSync.Common/Business/Actions/AbstractAction.cs b/src/ByteSync.Common/Business/Actions/AbstractAction.cs index bda437b5..e5b291ba 100644 --- a/src/ByteSync.Common/Business/Actions/AbstractAction.cs +++ b/src/ByteSync.Common/Business/Actions/AbstractAction.cs @@ -6,59 +6,38 @@ public abstract class AbstractAction { public ActionOperatorTypes Operator { get; set; } - public bool IsSynchronizeContent + public bool IsCopyContent { - get - { - return Operator.In(ActionOperatorTypes.SynchronizeContentOnly, ActionOperatorTypes.SynchronizeContentAndDate); - } + get { return Operator.In(ActionOperatorTypes.CopyContentOnly, ActionOperatorTypes.Copy); } } - public bool IsSynchronizeContentOnly + public bool IsCopyContentOnly { - get - { - return Operator.In(ActionOperatorTypes.SynchronizeContentOnly); - } + get { return Operator.In(ActionOperatorTypes.CopyContentOnly); } } - public bool IsSynchronizeContentAndDate + public bool IsFullCopy { - get - { - return Operator.In(ActionOperatorTypes.SynchronizeContentAndDate); - } + get { return Operator.In(ActionOperatorTypes.Copy); } } - public bool IsSynchronizeDate + public bool IsCopyDates { - get - { - return Operator.In(ActionOperatorTypes.SynchronizeDate); - } + get { return Operator.In(ActionOperatorTypes.CopyDatesOnly); } } public bool IsDelete { - get - { - return Operator.In(ActionOperatorTypes.Delete); - } + get { return Operator.In(ActionOperatorTypes.Delete); } } public bool IsCreate { - get - { - return Operator.In(ActionOperatorTypes.Create); - } + get { return Operator.In(ActionOperatorTypes.Create); } } public bool IsDoNothing { - get - { - return Operator.In(ActionOperatorTypes.DoNothing); - } + get { return Operator.In(ActionOperatorTypes.DoNothing); } } } \ No newline at end of file diff --git a/src/ByteSync.Common/Business/Actions/AbstractActionsGroup.cs b/src/ByteSync.Common/Business/Actions/AbstractActionsGroup.cs index d33f2e57..6585538a 100644 --- a/src/ByteSync.Common/Business/Actions/AbstractActionsGroup.cs +++ b/src/ByteSync.Common/Business/Actions/AbstractActionsGroup.cs @@ -7,50 +7,35 @@ public class AbstractActionsGroup : AbstractAction public string ActionsGroupId { get; set; } = null!; public long? Size { get; set; } - + public DateTime? CreationTimeUtc { get; set; } public DateTime? LastWriteTimeUtc { get; set; } - public bool AppliesOnlySynchronizeDate { get; set; } + public bool AppliesOnlyCopyDate { get; set; } - public bool IsFinallySynchronizeContentAndDate + public bool IsFinallyCopyContentAndDate { - get - { - return IsSynchronizeContentAndDate && !AppliesOnlySynchronizeDate; - } + get { return IsFullCopy && !AppliesOnlyCopyDate; } } public bool IsFinallySynchronizeDate { - get - { - return IsSynchronizeContentAndDate && AppliesOnlySynchronizeDate; - } + get { return IsFullCopy && AppliesOnlyCopyDate; } } - + public bool IsInitialOperatingOnSourceNeeded { - get - { - return IsSynchronizeContentOnly || IsFinallySynchronizeContentAndDate; - } + get { return IsCopyContentOnly || IsFinallyCopyContentAndDate; } } public bool NeedsOperatingOnSourceAndTargets { - get - { - return !NeedsOnlyOperatingOnTargets; - } + get { return !NeedsOnlyOperatingOnTargets; } } public bool NeedsOnlyOperatingOnTargets { - get - { - return IsSynchronizeDate || IsCreate || IsDelete || IsFinallySynchronizeDate; - } + get { return IsCopyDates || IsCreate || IsDelete || IsFinallySynchronizeDate; } } } \ No newline at end of file diff --git a/src/ByteSync.Common/Business/Actions/ActionOperatorTypes.cs b/src/ByteSync.Common/Business/Actions/ActionOperatorTypes.cs index 4144ba3a..6f2e6f14 100644 --- a/src/ByteSync.Common/Business/Actions/ActionOperatorTypes.cs +++ b/src/ByteSync.Common/Business/Actions/ActionOperatorTypes.cs @@ -3,13 +3,13 @@ public enum ActionOperatorTypes { // File only - SynchronizeContentOnly = 1, - SynchronizeDate = 2, - SynchronizeContentAndDate = 3, - + CopyContentOnly = 1, + CopyDatesOnly = 2, + Copy = 3, + // Directory only Create = 5, - + // Common Delete = 10, DoNothing = 11 diff --git a/tests/ByteSync.Client.IntegrationTests/Business/Filtering/BaseTestFiltering.cs b/tests/ByteSync.Client.IntegrationTests/Business/Filtering/BaseTestFiltering.cs index e2bcf8a2..5fb64efa 100644 --- a/tests/ByteSync.Client.IntegrationTests/Business/Filtering/BaseTestFiltering.cs +++ b/tests/ByteSync.Client.IntegrationTests/Business/Filtering/BaseTestFiltering.cs @@ -25,7 +25,7 @@ public abstract class BaseTestFiltering : IntegrationTest protected FilterParser _filterParser = null!; protected ExpressionEvaluatorFactory _evaluatorFactory = null!; protected ILogger _logger = null!; - + // [SetUp] protected void SetupBase() { @@ -59,14 +59,15 @@ protected void SetupBase() _evaluatorFactory = Container.Resolve(); _logger = Container.Resolve>(); } - + protected ComparisonItem CreateBasicComparisonItem(FileSystemTypes fileSystemType = FileSystemTypes.File, string filePath = "/file1.txt", string fileName = "file1.txt") { var pathIdentity = new PathIdentity(fileSystemType, filePath, fileName, filePath); + return new ComparisonItem(pathIdentity); } - + protected (FileDescription, InventoryPart) CreateFileDescription( string inventoryId, string rootPath, @@ -75,13 +76,13 @@ protected ComparisonItem CreateBasicComparisonItem(FileSystemTypes fileSystemTyp long size = 100, string relativePath = "/unset") { - string letter = inventoryId.Replace("Id_", ""); + var letter = inventoryId.Replace("Id_", ""); var inventory = new Inventory { InventoryId = inventoryId, Code = letter }; - string code = $"{letter}1"; + var code = $"{letter}1"; var inventoryPart = new InventoryPart(inventory, rootPath, FileSystemTypes.Directory); inventoryPart.Code = code; - + var fileDescription = new FileDescription { InventoryPart = inventoryPart, @@ -92,7 +93,7 @@ protected ComparisonItem CreateBasicComparisonItem(FileSystemTypes fileSystemTyp Sha256 = hash, RelativePath = relativePath }; - + return (fileDescription, inventoryPart); } @@ -100,26 +101,26 @@ protected ComparisonItem CreateBasicComparisonItem(FileSystemTypes fileSystemTyp string inventoryId, string rootPath) { - string letter = inventoryId.Replace("Id_", ""); + var letter = inventoryId.Replace("Id_", ""); var inventory = new Inventory { InventoryId = inventoryId, Code = letter }; - string code = $"{letter}1"; + var code = $"{letter}1"; var inventoryPart = new InventoryPart(inventory, rootPath, FileSystemTypes.Directory); inventoryPart.Code = code; - + var directoryDescription = new DirectoryDescription { InventoryPart = inventoryPart, }; - + return (directoryDescription, inventoryPart); } - + protected void ConfigureDataPartIndex( Dictionary dataParts) { var mockDataPartIndexer = Container.Resolve>(); - + foreach (var pair in dataParts) { var dataPart = new DataPart(pair.Key, pair.Value.Item1); @@ -132,7 +133,7 @@ protected void ConfigureDataPartIndex( Dictionary dataParts) { var mockDataPartIndexer = Container.Resolve>(); - + foreach (var pair in dataParts) { var dataPart = new DataPart(pair.Key, pair.Value.Item1); @@ -140,7 +141,7 @@ protected void ConfigureDataPartIndex( .Returns(dataPart); } } - + protected ComparisonItem PrepareComparisonWithTwoContents( string leftDataPartId, string leftHash, @@ -148,7 +149,7 @@ protected ComparisonItem PrepareComparisonWithTwoContents( string rightDataPartId, string rightHash, DateTime rightDateTime - ) + ) { return PrepareComparisonWithTwoContents( leftDataPartId, @@ -160,7 +161,7 @@ DateTime rightDateTime rightDateTime, 100); } - + protected ComparisonItem PrepareComparisonWithTwoContents( string leftDataPartId, string leftHash, @@ -172,21 +173,21 @@ protected ComparisonItem PrepareComparisonWithTwoContents( long rightSize) { var comparisonItem = CreateBasicComparisonItem(); - + var (fileDescA, inventoryPartA) = CreateFileDescription( "Id_A", "/testRootA", leftDateTime, leftHash, leftSize); - + var (fileDescB, inventoryPartB) = CreateFileDescription( "Id_B", "/testRootB", rightDateTime, rightHash, rightSize); - + // Créer et ajouter les identités de contenu var contentIdCoreA = new ContentIdentityCore { @@ -196,7 +197,7 @@ protected ComparisonItem PrepareComparisonWithTwoContents( var contentIdA = new ContentIdentity(contentIdCoreA); comparisonItem.AddContentIdentity(contentIdA); contentIdA.Add(fileDescA); - + if (leftHash == rightHash) { contentIdA.Add(fileDescB); @@ -212,19 +213,19 @@ protected ComparisonItem PrepareComparisonWithTwoContents( comparisonItem.AddContentIdentity(contentIdB); contentIdB.Add(fileDescB); } - + // Configurer l'indexeur de DataPart var dataParts = new Dictionary { { leftDataPartId, (inventoryPartA, fileDescA) }, { rightDataPartId, (inventoryPartB, fileDescB) } }; - + ConfigureDataPartIndex(dataParts); - + return comparisonItem; } - + protected ComparisonItem PrepareComparisonWithOneContent( string dataPartId, string leftHash, @@ -233,9 +234,9 @@ protected ComparisonItem PrepareComparisonWithOneContent( string fileName = "file1.txt") { var comparisonItem = CreateBasicComparisonItem(FileSystemTypes.File, "/" + fileName.TrimStart('/'), fileName); - + var letter = new string(dataPartId.TakeWhile(char.IsLetter).ToArray()); - + var (fileDesc, inventoryPart) = CreateFileDescription( $"Id_{letter}", $"/testRoot{letter}", @@ -257,18 +258,18 @@ protected ComparisonItem PrepareComparisonWithOneContent( { { dataPartId, (inventoryPart, fileDesc) } }; - + ConfigureDataPartIndex(dataParts); - + return comparisonItem; } protected ComparisonItem PrepareComparisonWithOneDirectory(string dataPartId) { var comparisonItem = CreateBasicComparisonItem(FileSystemTypes.Directory); - - string letter = dataPartId[0].ToString(); - + + var letter = dataPartId[0].ToString(); + var (dirDesc, inventoryPart) = CreateDirectoryDescription( $"Id_{letter}", $"/testRoot{letter}"); @@ -282,9 +283,9 @@ protected ComparisonItem PrepareComparisonWithOneDirectory(string dataPartId) { { dataPartId, (inventoryPart, dirDesc) } }; - + ConfigureDataPartIndex(dataParts); - + return comparisonItem; } @@ -299,6 +300,7 @@ protected bool EvaluateFilterExpression(string filterText, ComparisonItem item) var expression = parseResult.Expression!; var evaluator = _evaluatorFactory.GetEvaluator(expression); + return evaluator.Evaluate(expression, item); } } \ No newline at end of file diff --git a/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering.cs b/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering.cs index 3833de97..b0f79521 100644 --- a/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering.cs +++ b/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering.cs @@ -22,7 +22,7 @@ public void Setup() public void TestParse_Complete_Expression() { // Arrange - var filterText = "A1.contents==B1.contents"; + var filterText = "A1.content==B1.content"; ConfigureDataPartIndexer(); @@ -39,7 +39,7 @@ public void TestParse_Complete_Expression() public void TestParse_Complete_Expression_MultiDataNode() { // Arrange - var filterText = "Aa1.contents==Ba1.contents"; + var filterText = "Aa1.content==Ba1.content"; ConfigureDataPartIndexer("Aa", "Ba"); @@ -56,7 +56,7 @@ public void TestParse_Complete_Expression_MultiDataNode() public void TestParse_CompleteLowerCase_Expression() { // Arrange - var filterText = "a1.contents==b1.contents"; + var filterText = "a1.content==b1.content"; ConfigureDataPartIndexer(); @@ -73,7 +73,7 @@ public void TestParse_CompleteLowerCase_Expression() public void TestParse_CompleteWithSpaces1_Expression() { // Arrange - var filterText = "A1.contents == B1.contents"; + var filterText = "A1.content == B1.content"; ConfigureDataPartIndexer(); @@ -107,7 +107,7 @@ public void TestParse_CompleteWithSpaces2_Expression() public void TestParse_Incomplete_Expression() { // Arrange - var filterText = "A1.contents=="; + var filterText = "A1.content=="; ConfigureDataPartIndexer(); @@ -156,7 +156,7 @@ public void TestParse_Incomplete_DotExpression() public void TestParse_Incomplete_Operator() { // Arrange - var filterText = "A1.contents"; + var filterText = "A1.content"; // Act var parseResult = _filterParser.TryParse(filterText); @@ -201,7 +201,7 @@ public void TestParse_Incomplete_NotExpression() public void TestParse_Incomplete_ParenthesesExpression() { // Arrange - var filterText = "(A1.contents==B1.contents"; + var filterText = "(A1.content==B1.content"; ConfigureDataPartIndexer(); @@ -218,7 +218,7 @@ public void TestParse_Incomplete_ParenthesesExpression() public void TestParse_Incomplete_WithAndOperator() { // Arrange - var filterText = "A1.contents==B1.contents AND on:"; + var filterText = "A1.content==B1.content AND on:"; ConfigureDataPartIndexer(); @@ -255,7 +255,7 @@ public void TestFilterService_HandlesIncompleteExpression_WithoutException() var comparisonItem = CreateBasicComparisonItem(); // Act & Assert - Should not throw - var filter = filterService.BuildFilter("A1.content=="); + var filter = filterService.BuildFilter("A1.contents=="); // By default, incomplete filters should accept everything filter(comparisonItem).Should().BeTrue(); @@ -271,7 +271,7 @@ public void TestFilterService_BuildFilter_ListWithIncompleteExpressions() // A complete expression and an incomplete one var filterTexts = new List { - "A1.content==B1.content", // complete + "A1.contents==B1.contents", // complete "A1.content==" // incomplete }; diff --git a/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering_Actions.cs b/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering_Actions.cs index 1d06d369..34c18f36 100644 --- a/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering_Actions.cs +++ b/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering_Actions.cs @@ -28,7 +28,7 @@ public void TestFiltering_Actions_CountGreaterThanZero() var actions = new List { - CreateAtomicAction(comparisonItem, ActionOperatorTypes.SynchronizeContentOnly, false), + CreateAtomicAction(comparisonItem, ActionOperatorTypes.CopyContentOnly, false), CreateAtomicAction(comparisonItem, ActionOperatorTypes.Delete, true) }; @@ -51,7 +51,7 @@ public void TestFiltering_Actions_CountGreaterThanZer_Simplified() var actions = new List { - CreateAtomicAction(comparisonItem, ActionOperatorTypes.SynchronizeContentOnly, false), + CreateAtomicAction(comparisonItem, ActionOperatorTypes.CopyContentOnly, false), CreateAtomicAction(comparisonItem, ActionOperatorTypes.Delete, true) }; @@ -105,136 +105,136 @@ public void TestFiltering_TargetedActions() { // Arrange var comparisonItem = CreateBasicComparisonItem(); - + var actions = new List { - CreateAtomicAction(comparisonItem, ActionOperatorTypes.SynchronizeContentOnly, false), + CreateAtomicAction(comparisonItem, ActionOperatorTypes.CopyContentOnly, false), CreateAtomicAction(comparisonItem, ActionOperatorTypes.Delete, true) }; - + _mockActionRepository.AddOrUpdate(actions); - + var filterText = "actions.targeted>0"; - + // Act var result = EvaluateFilterExpression(filterText, comparisonItem); - + // Assert result.Should().BeTrue(); } - - + + [Test] public void TestFiltering_RuleActions() { // Arrange var comparisonItem = CreateBasicComparisonItem(); - + var actions = new List { - CreateAtomicAction(comparisonItem, ActionOperatorTypes.SynchronizeContentOnly, false), + CreateAtomicAction(comparisonItem, ActionOperatorTypes.CopyContentOnly, false), CreateAtomicAction(comparisonItem, ActionOperatorTypes.Delete, true) }; - + _mockActionRepository.AddOrUpdate(actions); - + var filterText = "actions.rules>0"; - + // Act var result = EvaluateFilterExpression(filterText, comparisonItem); - + // Assert result.Should().BeTrue(); } - + [Test] public void TestFiltering_ActionsByType_Delete() { // Arrange var comparisonItem = CreateBasicComparisonItem(); - + var actions = new List { - CreateAtomicAction(comparisonItem, ActionOperatorTypes.SynchronizeContentOnly, false), + CreateAtomicAction(comparisonItem, ActionOperatorTypes.CopyContentOnly, false), CreateAtomicAction(comparisonItem, ActionOperatorTypes.Delete, true) }; - + _mockActionRepository.AddOrUpdate(actions); - + var filterText = "actions.delete>0"; - + // Act var result = EvaluateFilterExpression(filterText, comparisonItem); - + // Assert result.Should().BeTrue(); } - + [Test] public void TestFiltering_TargetedActionsByType_Delete() { // Arrange var comparisonItem = CreateBasicComparisonItem(); - + var actions = new List { - CreateAtomicAction(comparisonItem, ActionOperatorTypes.SynchronizeContentOnly, false), + CreateAtomicAction(comparisonItem, ActionOperatorTypes.CopyContentOnly, false), CreateAtomicAction(comparisonItem, ActionOperatorTypes.Delete, true) }; - + _mockActionRepository.AddOrUpdate(actions); - + var filterText = "actions.targeted.delete>0"; - + // Act var result = EvaluateFilterExpression(filterText, comparisonItem); - + // Assert result.Should().BeTrue(); } - + [Test] public void TestFiltering_TargetedActionsByType_Delete_Simplified() { // Arrange var comparisonItem = CreateBasicComparisonItem(); - + var actions = new List { - CreateAtomicAction(comparisonItem, ActionOperatorTypes.SynchronizeContentOnly, false), + CreateAtomicAction(comparisonItem, ActionOperatorTypes.CopyContentOnly, false), CreateAtomicAction(comparisonItem, ActionOperatorTypes.Delete, true) }; - + _mockActionRepository.AddOrUpdate(actions); - + var filterText = "actions.targeted.delete"; - + // Act var result = EvaluateFilterExpression(filterText, comparisonItem); - + // Assert result.Should().BeTrue(); } - + [Test] public void TestFiltering_RuleActionsByType_SynchronizeContent() { // Arrange var comparisonItem = CreateBasicComparisonItem(); - + var actions = new List { - CreateAtomicAction(comparisonItem, ActionOperatorTypes.SynchronizeContentOnly, false), + CreateAtomicAction(comparisonItem, ActionOperatorTypes.CopyContentOnly, false), CreateAtomicAction(comparisonItem, ActionOperatorTypes.Delete, true) }; - + _mockActionRepository.AddOrUpdate(actions); - - var filterText = "actions.rules.copy-contents>0"; - + + var filterText = "actions.rules.copy-content>0"; + // Act var result = EvaluateFilterExpression(filterText, comparisonItem); - + // Assert result.Should().BeTrue(); } @@ -244,104 +244,104 @@ public void TestFiltering_RuleActionsByType_SynchronizeContent_Simplified() { // Arrange var comparisonItem = CreateBasicComparisonItem(); - + var actions = new List { - CreateAtomicAction(comparisonItem, ActionOperatorTypes.SynchronizeContentOnly, false), + CreateAtomicAction(comparisonItem, ActionOperatorTypes.CopyContentOnly, false), CreateAtomicAction(comparisonItem, ActionOperatorTypes.Delete, true) }; - + _mockActionRepository.AddOrUpdate(actions); - - var filterText = "actions.rules.copy-contents"; - + + var filterText = "actions.rules.copy-content"; + // Act var result = EvaluateFilterExpression(filterText, comparisonItem); - + // Assert result.Should().BeTrue(); } - + [Test] public void TestFiltering_NoSuchActions() { // Arrange var comparisonItem = CreateBasicComparisonItem(); - + var actions = new List { - CreateAtomicAction(comparisonItem, ActionOperatorTypes.SynchronizeContentOnly, false), + CreateAtomicAction(comparisonItem, ActionOperatorTypes.CopyContentOnly, false), CreateAtomicAction(comparisonItem, ActionOperatorTypes.Delete, true) }; - + _mockActionRepository.AddOrUpdate(actions); - + var filterText = "actions.targeted.create==0"; - + // Act var result = EvaluateFilterExpression(filterText, comparisonItem); - + // Assert result.Should().BeTrue(); } - + [Test] public void TestFiltering_ComplexCondition() { // Arrange var comparisonItem = CreateBasicComparisonItem(); - + var actions = new List { - CreateAtomicAction(comparisonItem, ActionOperatorTypes.SynchronizeContentOnly, false), + CreateAtomicAction(comparisonItem, ActionOperatorTypes.CopyContentOnly, false), CreateAtomicAction(comparisonItem, ActionOperatorTypes.Delete, true) }; - + _mockActionRepository.AddOrUpdate(actions); - + var filterText = "actions.delete>0 AND actions.create==0"; - + // Act var result = EvaluateFilterExpression(filterText, comparisonItem); - + // Assert result.Should().BeTrue(); } - + [Test] public void TestFiltering_ComplexCondition_Simplified() { // Arrange var comparisonItem = CreateBasicComparisonItem(); - + var actions = new List { - CreateAtomicAction(comparisonItem, ActionOperatorTypes.SynchronizeContentOnly, false), + CreateAtomicAction(comparisonItem, ActionOperatorTypes.CopyContentOnly, false), CreateAtomicAction(comparisonItem, ActionOperatorTypes.Delete, true) }; - + _mockActionRepository.AddOrUpdate(actions); - + var filterText = "actions.delete AND NOT actions.create"; - + // Act var result = EvaluateFilterExpression(filterText, comparisonItem); - + // Assert result.Should().BeTrue(); } - + private AtomicAction CreateAtomicAction(ComparisonItem comparisonItem, ActionOperatorTypes operatorType, bool isTargeted) { var action = new AtomicAction($"AAID_{Guid.NewGuid()}", comparisonItem); action.Operator = operatorType; - + if (!isTargeted) { action.SynchronizationRule = new SynchronizationRule(comparisonItem.FileSystemType, ConditionModes.All); } - + return action; } } \ No newline at end of file diff --git a/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering_Contents.cs b/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering_Contents.cs index 31a7f87b..80164c5b 100644 --- a/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering_Contents.cs +++ b/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering_Contents.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Autofac; using ByteSync.Business; using ByteSync.Business.Comparisons; @@ -19,12 +20,12 @@ public void Setup() { SetupBase(); } - + [Test] public void Test_Contents() { // Arrange - var filterText = "A1.contents==B1.contents"; + var filterText = "A1.content==B1.content"; var pathIdentity = new PathIdentity(FileSystemTypes.File, "/file1.txt", "file1.txt", "/file1.txt"); var comparisonItem = new ComparisonItem(pathIdentity); @@ -33,7 +34,8 @@ public void Test_Contents() var inventoryA = new Inventory(); inventoryA.InventoryId = "Id_A"; var inventoryPartA1 = new InventoryPart(inventoryA, "/testRootA1", FileSystemTypes.Directory); - var fileDescriptionA1 = new FileDescription { + var fileDescriptionA1 = new FileDescription + { InventoryPart = inventoryPartA1, LastWriteTimeUtc = lastWriteTime1, Size = 100, @@ -45,7 +47,8 @@ public void Test_Contents() var inventoryB = new Inventory(); inventoryB.InventoryId = "Id_B"; var inventoryPartB1 = new InventoryPart(inventoryB, "/testRootB1", FileSystemTypes.Directory); - var fileDescriptionB1 = new FileDescription { + var fileDescriptionB1 = new FileDescription + { InventoryPart = inventoryPartB1, LastWriteTimeUtc = lastWriteTime1, Size = 100, @@ -61,7 +64,7 @@ public void Test_Contents() comparisonItem.AddContentIdentity(contentIdentity); contentIdentity.Add(fileDescriptionA1); contentIdentity.Add(fileDescriptionB1); - + var mockDataPartIndexer = Container.Resolve>(); var dataPartA1 = new DataPart("A1", inventoryPartA1); mockDataPartIndexer.Setup(m => m.GetDataPart("A1")) @@ -69,10 +72,10 @@ public void Test_Contents() var dataPartA2 = new DataPart("B1", inventoryPartB1); mockDataPartIndexer.Setup(m => m.GetDataPart("B1")) .Returns(dataPartA2); - + // Act var result = EvaluateFilterExpression(filterText, comparisonItem); - + // Assert result.Should().BeTrue(); } @@ -90,18 +93,18 @@ public void Test_Contents_Simplified(string leftHash, string leftDateTimeStr, st string @operator, bool expectedResult) { // Arrange - var filterText = $"A1.contents{@operator}B1._"; + var filterText = $"A1.content{@operator}B1._"; - DateTime leftDateTime = DateTime.Parse(leftDateTimeStr, System.Globalization.CultureInfo.InvariantCulture); - DateTime rightDateTime = DateTime.Parse(rightDateTimeStr, System.Globalization.CultureInfo.InvariantCulture); + var leftDateTime = DateTime.Parse(leftDateTimeStr, CultureInfo.InvariantCulture); + var rightDateTime = DateTime.Parse(rightDateTimeStr, CultureInfo.InvariantCulture); var comparisonItem = PrepareComparisonWithTwoContents( "A1", leftHash, leftDateTime, "B1", rightHash, rightDateTime); - + // Act var result = EvaluateFilterExpression(filterText, comparisonItem); - + // Assert result.Should().Be(expectedResult); } diff --git a/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering_ContentsAndDate.cs b/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering_ContentsAndDate.cs index 95f21d9d..1dde292a 100644 --- a/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering_ContentsAndDate.cs +++ b/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering_ContentsAndDate.cs @@ -1,4 +1,5 @@ -using FluentAssertions; +using System.Globalization; +using FluentAssertions; namespace ByteSync.Client.IntegrationTests.Business.Filtering; @@ -9,7 +10,7 @@ public void Setup() { SetupBase(); } - + [TestCase("sameHash", "2023-10-01", "sameHash", "2023-10-01", "==", true)] [TestCase("sameHash", "2023-10-01", "sameHash", "2023-10-01", "<>", false)] [TestCase("sameHash", "2023-10-01", "sameHash", "2023-10-01", "!=", false)] @@ -23,18 +24,18 @@ public void Test_ContentsAndDate(string leftHash, string leftDateTimeStr, string string @operator, bool expectedResult) { // Arrange - var filterText = $"A1.contents-and-date{@operator}B1.contents-and-date"; + var filterText = $"A1.content-and-date{@operator}B1.content-and-date"; - DateTime leftDateTime = DateTime.Parse(leftDateTimeStr, System.Globalization.CultureInfo.InvariantCulture); - DateTime rightDateTime = DateTime.Parse(rightDateTimeStr, System.Globalization.CultureInfo.InvariantCulture); + var leftDateTime = DateTime.Parse(leftDateTimeStr, CultureInfo.InvariantCulture); + var rightDateTime = DateTime.Parse(rightDateTimeStr, CultureInfo.InvariantCulture); var comparisonItem = PrepareComparisonWithTwoContents( "A1", leftHash, leftDateTime, "B1", rightHash, rightDateTime); - + // Act var result = EvaluateFilterExpression(filterText, comparisonItem); - + // Assert result.Should().Be(expectedResult); } @@ -52,18 +53,18 @@ public void Test_ContentsAndDate_Simplified(string leftHash, string leftDateTime string @operator, bool expectedResult) { // Arrange - var filterText = $"A1.contents-and-date{@operator}B1._"; + var filterText = $"A1.content-and-date{@operator}B1._"; - DateTime leftDateTime = DateTime.Parse(leftDateTimeStr, System.Globalization.CultureInfo.InvariantCulture); - DateTime rightDateTime = DateTime.Parse(rightDateTimeStr, System.Globalization.CultureInfo.InvariantCulture); + var leftDateTime = DateTime.Parse(leftDateTimeStr, CultureInfo.InvariantCulture); + var rightDateTime = DateTime.Parse(rightDateTimeStr, CultureInfo.InvariantCulture); var comparisonItem = PrepareComparisonWithTwoContents( "A1", leftHash, leftDateTime, "B1", rightHash, rightDateTime); - + // Act var result = EvaluateFilterExpression(filterText, comparisonItem); - + // Assert result.Should().Be(expectedResult); } diff --git a/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering_DocumentationExamples.cs b/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering_DocumentationExamples.cs index 7aec7a65..2770c642 100644 --- a/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering_DocumentationExamples.cs +++ b/tests/ByteSync.Client.IntegrationTests/Business/Filtering/TestFiltering_DocumentationExamples.cs @@ -84,7 +84,7 @@ public void TestEvaluate_LogFilesWithActions(string filterText, bool expectedRes var comparisonItem = PrepareComparisonWithOneContent("A1", "sameHash", DateTime.Now, 1024, "app.log"); // Add copy action - var copyAction = CreateAtomicAction(comparisonItem, ActionOperatorTypes.SynchronizeContentAndDate, false); + var copyAction = CreateAtomicAction(comparisonItem, ActionOperatorTypes.Copy, false); _mockActionRepository.AddOrUpdate(new List { copyAction }); // Act @@ -167,7 +167,7 @@ public void TestParse_FileAndDirectoryWithSize() public void TestParse_DuplicatedFiles() { // Arrange - var filterText = "A1.contents==A2.contents AND A1.size==A2.size AND is:file"; + var filterText = "A1.content==A2.content AND A1.size==A2.size AND is:file"; ConfigureDataPartIndexer("A", "A"); // Need A1 and A2 from same inventory A @@ -254,20 +254,20 @@ private void ConfigureDataPartIndexer(string inventoryACode = "A", string invent var mockDataPartIndexer = Container.Resolve>(); var inventoryA = new Inventory { - InventoryId = $"Id_{inventoryACode}", + InventoryId = $"Id_{inventoryACode}", Code = inventoryACode }; var inventoryB = new Inventory { - InventoryId = $"Id_{inventoryBCode}", + InventoryId = $"Id_{inventoryBCode}", Code = inventoryBCode }; - - var inventoryPartA = new InventoryPart(inventoryA, $"/testRoot{inventoryACode}", + + var inventoryPartA = new InventoryPart(inventoryA, $"/testRoot{inventoryACode}", FileSystemTypes.Directory); - var inventoryPartB = new InventoryPart(inventoryB, $"/testRoot{inventoryBCode}", + var inventoryPartB = new InventoryPart(inventoryB, $"/testRoot{inventoryBCode}", FileSystemTypes.Directory); - + var dataPartAName = $"{inventoryACode}1"; var dataPartBName = $"{inventoryBCode}1"; var dataPartA2Name = $"{inventoryACode}2"; // For A2 if needed @@ -275,7 +275,7 @@ private void ConfigureDataPartIndexer(string inventoryACode = "A", string invent var dataPartA = new DataPart(dataPartAName, inventoryPartA); var dataPartB = new DataPart(dataPartBName, inventoryPartB); var dataPartA2 = new DataPart(dataPartA2Name, inventoryPartA); - + mockDataPartIndexer.Setup(m => m.GetDataPart(dataPartAName)).Returns(dataPartA); mockDataPartIndexer.Setup(m => m.GetDataPart(dataPartBName)).Returns(dataPartB); mockDataPartIndexer.Setup(m => m.GetDataPart(dataPartA2Name)).Returns(dataPartA2); @@ -285,12 +285,12 @@ private AtomicAction CreateAtomicAction(ComparisonItem comparisonItem, ActionOpe { var action = new AtomicAction($"AAID_{Guid.NewGuid()}", comparisonItem); action.Operator = operatorType; - + if (!isTargeted) { action.SynchronizationRule = new SynchronizationRule(comparisonItem.FileSystemType, ConditionModes.All); } - + return action; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/tests/ByteSync.Client.IntegrationTests/Services/Actions/TestSharedAtomicActionComputer.cs b/tests/ByteSync.Client.IntegrationTests/Services/Actions/TestSharedAtomicActionComputer.cs index a2375f0d..52817ecf 100644 --- a/tests/ByteSync.Client.IntegrationTests/Services/Actions/TestSharedAtomicActionComputer.cs +++ b/tests/ByteSync.Client.IntegrationTests/Services/Actions/TestSharedAtomicActionComputer.cs @@ -21,13 +21,12 @@ namespace ByteSync.Client.IntegrationTests.Services.Actions; - [TestFixture] public class TestSharedAtomicActionComputer : IntegrationTest { private ComparisonResultPreparer _comparisonResultPreparer; private SharedAtomicActionComputer _sharedAtomicActionComputer; - + [SetUp] public void Setup() { @@ -43,17 +42,17 @@ public void Setup() contextHelper.GenerateSession(); contextHelper.GenerateCurrentEndpoint(); var testDirectory = _testDirectoryService.CreateTestDirectory(); - + var mockEnvironmentService = Container.Resolve>(); mockEnvironmentService.Setup(m => m.AssemblyFullName).Returns(IOUtils.Combine(testDirectory.FullName, "Assembly", "Assembly.exe")); - + var mockLocalApplicationDataManager = Container.Resolve>(); - mockLocalApplicationDataManager.Setup(m => m.ApplicationDataPath).Returns(IOUtils.Combine(testDirectory.FullName, + mockLocalApplicationDataManager.Setup(m => m.ApplicationDataPath).Returns(IOUtils.Combine(testDirectory.FullName, "ApplicationDataPath")); // var atomicActionRepository = Container.Resolve>(); // atomicActionRepository.Setup(m => m.GetAtomicActions(It.IsAny())).Returns(new List()); - + _testDirectoryService.CreateTestDirectory(); _comparisonResultPreparer = Container.Resolve(); _sharedAtomicActionComputer = Container.Resolve(); @@ -73,14 +72,15 @@ public async Task Test_OneComparisonItem() _testDirectoryService.CreateFileInDirectory(dataA, "file1.txt", "contentA"); var dataB = _testDirectoryService.CreateSubTestDirectory("dataB"); _testDirectoryService.CreateFileInDirectory(dataB, "file1.txt", "contentB_"); - + InventoryData inventoryDataA = new InventoryData(dataA); InventoryData inventoryDataB = new InventoryData(dataB); - + SessionSettings sessionSettings = SessionSettingsGenerator.GenerateSessionSettings(analysisMode: AnalysisModes.Smart); - ComparisonResult comparisonResult = await _comparisonResultPreparer.BuildAndCompare(sessionSettings, inventoryDataA, inventoryDataB); - + ComparisonResult comparisonResult = + await _comparisonResultPreparer.BuildAndCompare(sessionSettings, inventoryDataA, inventoryDataB); + // List comparisonItemViewModels = new List(); // foreach (var comparisonItem in comparisonResult.ComparisonItems) // { @@ -89,80 +89,81 @@ public async Task Test_OneComparisonItem() // // comparisonItemViewModels.Add(comparisonItemViewModel); // } - + var atomicActions = new List() { new() { AtomicActionId = $"AAID_{Guid.NewGuid()}", - Operator = ActionOperatorTypes.SynchronizeContentAndDate, + Operator = ActionOperatorTypes.Copy, Source = inventoryDataA.GetSingleDataPart(), Destination = inventoryDataB.GetSingleDataPart(), ComparisonItem = comparisonResult.ComparisonItems.First() } }; - + var atomicActionRepository = Container.Resolve(); atomicActionRepository.AddOrUpdate(atomicActions); + // atomicActionRepository.Setup(m => m.Elements).Returns(atomicActions); // var synchronizationActionViewModel = BuildSynchronizationAction(ActionOperatorTypes.SynchronizeContentAndDate, inventoryDataA, inventoryDataB); // comparisonItemViewModels.First().TD_SynchronizationActions.Add(synchronizationActionViewModel); - + // var synchronizationActionConverter = BuildSharedAtomicActionComputer(comparisonItemViewModels); var sharedAtomicActions = await _sharedAtomicActionComputer.ComputeSharedAtomicActions(); - + ClassicAssert.AreEqual(1, sharedAtomicActions.Count); var sharedAtomicAction = sharedAtomicActions.Single(); - + ClassicAssert.IsNotEmpty(inventoryDataA.AllFileDescriptions[0].SignatureGuid); ClassicAssert.AreEqual(inventoryDataA.AllFileDescriptions[0].SignatureGuid, sharedAtomicAction.Source!.SignatureGuid); - + ClassicAssert.IsNotEmpty(inventoryDataB.AllFileDescriptions[0].SignatureGuid); ClassicAssert.AreEqual(inventoryDataB.AllFileDescriptions[0].SignatureGuid, sharedAtomicAction.Target!.SignatureGuid); } - + /* [Test] public async Task Test_OneComparisonItem_DoNothing() { CreateTestDirectory(); - + var dataA = _testDirectoryService.CreateSubTestDirectory("dataA"); CreateFileInDirectory(dataA, "file1.txt", "contentA"); var dataB = _testDirectoryService.CreateSubTestDirectory("dataB"); CreateFileInDirectory(dataB, "file1.txt", "contentB_"); - + InventoryData inventoryDataA = new InventoryData(dataA); InventoryData inventoryDataB = new InventoryData(dataB); - + SessionSettings sessionSettings = new SessionSettings(); sessionSettings.AnalysisMode = AnalysisModes.Smart; - + ComparisonResultPreparer comparisonResultPreparer = new ComparisonResultPreparer(TestDirectory); ComparisonResult comparisonResult = await comparisonResultPreparer.BuildAndCompare(sessionSettings, inventoryDataA, inventoryDataB); - + List comparisonItemViewModels = new List(); foreach (var comparisonItem in comparisonResult.ComparisonItems) { ComparisonItemViewModel comparisonItemViewModel = new ComparisonItemViewModel(comparisonItem, new List { inventoryDataA.Inventory, inventoryDataB.Inventory }, _mockObjectsGenerator.SessionDataHolder.Object); - + comparisonItemViewModels.Add(comparisonItemViewModel); } - + var synchronizationActionViewModel = BuildSynchronizationAction(ActionOperatorTypes.SynchronizeContentAndDate, inventoryDataA, inventoryDataB); comparisonItemViewModels.First().TD_SynchronizationActions.Add(synchronizationActionViewModel); synchronizationActionViewModel = BuildSynchronizationAction(ActionOperatorTypes.DoNothing); comparisonItemViewModels.First().TD_SynchronizationActions.Add(synchronizationActionViewModel); - + var synchronizationActionConverter = BuildSharedAtomicActionComputer(comparisonItemViewModels); var sharedAtomicActions = synchronizationActionConverter.ComputeSharedAtomicActions(); - + ClassicAssert.AreEqual(0, sharedAtomicActions.Count); } - + // private SynchronizationActionViewModel BuildSynchronizationAction(ActionOperatorTypes @operator) // { // @@ -179,12 +180,12 @@ public async Task Test_OneComparisonItem_DoNothing() // // return synchronizationActionViewModel; // } - + private SynchronizationActionViewModel BuildSynchronizationAction(ActionOperatorTypes @operator, InventoryData source, InventoryData target) { return BuildSynchronizationAction(@operator, source.InventoryParts.Single(), target.InventoryParts.Single()); } - + // private SynchronizationActionViewModel BuildSynchronizationAction(ActionOperatorTypes @operator, InventoryPart source, InventoryPart target) // { // // todo 050523 @@ -202,19 +203,19 @@ private SynchronizationActionViewModel BuildSynchronizationAction(ActionOperator // // return synchronizationActionViewModel; // } - + [SetUp] public void Setup() { _mockObjectsGenerator = new MockObjectsGenerator(this); _mockObjectsGenerator.GenerateCloudSessionManager(); } - + private SharedAtomicActionComputer BuildSharedAtomicActionComputer( params ComparisonItemViewModel[] comparisonItems) { List comparisonItemsList = new List(comparisonItems); - + return BuildSharedAtomicActionComputer(comparisonItemsList); }*/ diff --git a/tests/ByteSync.Client.IntegrationTests/Services/Comparisons/TargetInaccessible_IntegrationTests.cs b/tests/ByteSync.Client.IntegrationTests/Services/Comparisons/TargetInaccessible_IntegrationTests.cs index 9df2c7b5..5aba9cce 100644 --- a/tests/ByteSync.Client.IntegrationTests/Services/Comparisons/TargetInaccessible_IntegrationTests.cs +++ b/tests/ByteSync.Client.IntegrationTests/Services/Comparisons/TargetInaccessible_IntegrationTests.cs @@ -90,7 +90,7 @@ public async Task Synchronize_Fails_When_Target_File_Inaccessible_Windows() var action = new AtomicAction { - Operator = ActionOperatorTypes.SynchronizeContentOnly, + Operator = ActionOperatorTypes.CopyContentOnly, Source = invA.GetSingleDataPart(), Destination = invB.GetSingleDataPart(), ComparisonItem = targetItem @@ -145,7 +145,7 @@ public async Task Synchronize_Fails_When_Target_File_Inaccessible_Posix() var action = new AtomicAction { - Operator = ActionOperatorTypes.SynchronizeContentOnly, + Operator = ActionOperatorTypes.CopyContentOnly, Source = invA.GetSingleDataPart(), Destination = invB.GetSingleDataPart(), ComparisonItem = targetItem diff --git a/tests/ByteSync.Client.IntegrationTests/Services/Comparisons/TestAtomicActionConsistencyChecker.cs b/tests/ByteSync.Client.IntegrationTests/Services/Comparisons/TestAtomicActionConsistencyChecker.cs index e9f41e00..2a821e14 100644 --- a/tests/ByteSync.Client.IntegrationTests/Services/Comparisons/TestAtomicActionConsistencyChecker.cs +++ b/tests/ByteSync.Client.IntegrationTests/Services/Comparisons/TestAtomicActionConsistencyChecker.cs @@ -198,7 +198,7 @@ public async Task Test_FileOnlyOnA_1() atomicAction = new AtomicAction { Source = inventoryDataA.GetSingleDataPart(), - Operator = ActionOperatorTypes.SynchronizeContentAndDate, + Operator = ActionOperatorTypes.Copy, Destination = inventoryDataB.GetSingleDataPart() }; @@ -212,9 +212,9 @@ public async Task Test_FileOnlyOnA_1() } [Test] - [TestCase(ActionOperatorTypes.SynchronizeContentAndDate, 0)] - [TestCase(ActionOperatorTypes.SynchronizeContentOnly, 0)] - [TestCase(ActionOperatorTypes.SynchronizeDate, 0)] + [TestCase(ActionOperatorTypes.Copy, 0)] + [TestCase(ActionOperatorTypes.CopyContentOnly, 0)] + [TestCase(ActionOperatorTypes.CopyDatesOnly, 0)] public async Task Test_FileOnAAndB_SameContentAndDate(ActionOperatorTypes actionOperator, int expectedValidItems) { AtomicAction atomicAction; @@ -268,7 +268,7 @@ public void Test_SynchronizeOperationOnDirectoryNotAllowed() var atomicAction = new AtomicAction { Source = new DataPart("A1", inventoryPartA), - Operator = ActionOperatorTypes.SynchronizeContentAndDate, + Operator = ActionOperatorTypes.Copy, Destination = new DataPart("B1", inventoryPartB) }; @@ -326,7 +326,7 @@ public void Test_SourceRequiredForSynchronizeOperation() var atomicAction = new AtomicAction { Source = null, // Missing source - Operator = ActionOperatorTypes.SynchronizeContentAndDate, + Operator = ActionOperatorTypes.Copy, Destination = new DataPart("B1", inventoryPartB) }; @@ -353,7 +353,7 @@ public void Test_DestinationRequiredForSynchronizeOperation() var atomicAction = new AtomicAction { Source = new DataPart("A1", inventoryPartA), - Operator = ActionOperatorTypes.SynchronizeContentAndDate, + Operator = ActionOperatorTypes.Copy, Destination = null // Missing destination }; @@ -643,7 +643,7 @@ public void Test_CheckAdvancedConsistency_AllTargetsHaveAnalysisError() var atomicAction = new AtomicAction { Source = new DataPart("A1", inventoryPartA), - Operator = ActionOperatorTypes.SynchronizeContentAndDate, + Operator = ActionOperatorTypes.Copy, Destination = new DataPart("B1", inventoryPartB) }; @@ -850,7 +850,7 @@ public async Task Test_SourceCannotBeDestinationOfAnotherAction() var newAction = new AtomicAction { Source = commonDataPart, // Same as the destination of existingAction - Operator = ActionOperatorTypes.SynchronizeContentAndDate, + Operator = ActionOperatorTypes.Copy, Destination = inventoryDataB.GetSingleDataPart() }; @@ -893,7 +893,7 @@ public async Task Test_DestinationCannotBeSourceOfAnotherAction() var existingAction = new AtomicAction { Source = commonDataPart, // This source will be the destination of the new action - Operator = ActionOperatorTypes.SynchronizeContentAndDate, + Operator = ActionOperatorTypes.Copy, Destination = inventoryDataB.GetSingleDataPart() }; @@ -1001,7 +1001,7 @@ public async Task Test_ComplementaryActions_SynchronizeDateAndSynchronizeContent var existingSynchronizeDateAction = new AtomicAction { Source = inventoryDataA.GetSingleDataPart(), - Operator = ActionOperatorTypes.SynchronizeDate, + Operator = ActionOperatorTypes.CopyDatesOnly, Destination = commonDestination }; @@ -1009,7 +1009,7 @@ public async Task Test_ComplementaryActions_SynchronizeDateAndSynchronizeContent var newSynchronizeContentOnlyAction = new AtomicAction { Source = inventoryDataA.GetSingleDataPart(), // Same source - Operator = ActionOperatorTypes.SynchronizeContentOnly, + Operator = ActionOperatorTypes.CopyContentOnly, Destination = commonDestination // Same destination }; @@ -1059,7 +1059,7 @@ public async Task Test_DestinationAlreadyUsedByNonComplementaryAction_MultipleAc var existingAction2 = new AtomicAction { Source = inventoryDataA.GetSingleDataPart(), - Operator = ActionOperatorTypes.SynchronizeDate, + Operator = ActionOperatorTypes.CopyDatesOnly, Destination = commonDestination }; @@ -1067,7 +1067,7 @@ public async Task Test_DestinationAlreadyUsedByNonComplementaryAction_MultipleAc var newAction = new AtomicAction { Source = inventoryDataA.GetSingleDataPart(), - Operator = ActionOperatorTypes.SynchronizeContentOnly, + Operator = ActionOperatorTypes.CopyContentOnly, Destination = commonDestination }; @@ -1114,7 +1114,7 @@ public async Task Test_CannotDeleteItemAlreadyUsedInAnotherAction() var existingAction = new AtomicAction { Source = sourceItem, - Operator = ActionOperatorTypes.SynchronizeContentAndDate, + Operator = ActionOperatorTypes.Copy, Destination = itemToDelete // itemToDelete is used as destination }; @@ -1177,7 +1177,7 @@ public async Task Test_CannotOperateOnItemBeingDeleted() var newAction = new AtomicAction { Source = itemBeingDeleted, // Tries to use the item being deleted as source - Operator = ActionOperatorTypes.SynchronizeContentAndDate, + Operator = ActionOperatorTypes.Copy, Destination = otherItem }; diff --git a/tests/ByteSync.Client.IntegrationTests/Services/Synchronizations/TestSynchronizationActionHandler.cs b/tests/ByteSync.Client.IntegrationTests/Services/Synchronizations/TestSynchronizationActionHandler.cs index c27aa5c6..a1f4cc82 100644 --- a/tests/ByteSync.Client.IntegrationTests/Services/Synchronizations/TestSynchronizationActionHandler.cs +++ b/tests/ByteSync.Client.IntegrationTests/Services/Synchronizations/TestSynchronizationActionHandler.cs @@ -43,34 +43,34 @@ public void SetUp() RegisterType(); RegisterType(); BuildMoqContainer(); - + var contextHelper = new TestContextGenerator(Container); contextHelper.GenerateSession(); _currentEndPoint = contextHelper.GenerateCurrentEndpoint(); var testDirectory = _testDirectoryService.CreateTestDirectory(); - + // Ensure SynchronizationActionHandler sees the expected CurrentEndPoint var mockConnectionService = Container.Resolve>(); mockConnectionService.Setup(m => m.CurrentEndPoint).Returns(_currentEndPoint); - + var mockEnvironmentService = Container.Resolve>(); mockEnvironmentService.Setup(m => m.AssemblyFullName).Returns(IOUtils.Combine(testDirectory.FullName, "Assembly", "Assembly.exe")); - + var mockLocalApplicationDataManager = Container.Resolve>(); - mockLocalApplicationDataManager.Setup(m => m.ApplicationDataPath).Returns(IOUtils.Combine(testDirectory.FullName, + mockLocalApplicationDataManager.Setup(m => m.ApplicationDataPath).Returns(IOUtils.Combine(testDirectory.FullName, "ApplicationDataPath")); - + _synchronizationActionHandler = Container.Resolve(); } [Test] public async Task Test_FailUnknownOperator() - { + { var sharedActionsGroup = new SharedActionsGroup { ActionsGroupId = "ACI_Test", }; - + await FluentActions.Awaiting(() => _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup)) .Should().ThrowAsync(); } @@ -86,16 +86,16 @@ public async Task Test_SynchronizeContentOnly_Full_LocalTarget(bool isLocalSessi var sourceA = sourceRoot.CreateSubdirectory("A"); var sourceB = sourceRoot.CreateSubdirectory("B"); - + var fileADirectory = sourceA; if (fileParentPathComplement.IsNotEmpty()) { fileADirectory = new DirectoryInfo(sourceA.Combine(fileParentPathComplement!)); } - + var fileA = _testDirectoryService.CreateFileInDirectory(fileADirectory, "fileToCopy1.txt", "fileToCopy1_content"); fileA.LastWriteTimeUtc = DateTime.UtcNow.AddHours(-8); - + var fileB = new FileInfo(sourceB.Combine(fileParentPathComplement + "fileToCopy1.txt")); fileB.Exists.Should().BeFalse(); @@ -105,7 +105,7 @@ public async Task Test_SynchronizeContentOnly_Full_LocalTarget(bool isLocalSessi var sessionSettings = SessionSettingsGenerator.GenerateSessionSettings(); var comparisonResultPreparer = Container.Resolve(); _ = await comparisonResultPreparer.BuildAndCompare(sessionSettings, inventoryDataA, inventoryDataB); - + var source = SharedDataPartTestFactory.Create("fileToCopy1.txt", inventoryDataA.Inventory, _currentEndPoint.ClientInstanceId, FileSystemTypes.Directory, sourceA.FullName, fileParentPathComplement + "/fileToCopy1.txt", null, null, false); @@ -115,13 +115,13 @@ public async Task Test_SynchronizeContentOnly_Full_LocalTarget(bool isLocalSessi var sharedActionsGroup = new SharedActionsGroup { ActionsGroupId = "ACI_Test", - Operator = ActionOperatorTypes.SynchronizeContentOnly, + Operator = ActionOperatorTypes.CopyContentOnly, Targets = [target], Source = source, SynchronizationType = SynchronizationTypes.Full, PathIdentity = new PathIdentity(FileSystemTypes.File, "/fileToCopy1.txt", "fileToCopy1.txt", "/fileToCopy1.txt") }; - + await _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup); fileA.Refresh(); @@ -135,7 +135,7 @@ public async Task Test_SynchronizeContentOnly_Full_LocalTarget(bool isLocalSessi fileB.LastWriteTimeUtc.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(30)); } - + [Test] [TestCase(true)] [TestCase(false)] @@ -147,29 +147,31 @@ public async Task Test_SynchronizeContentOnly_Delta_LocalTarget(bool isLocalSess var sourceA = sourceRoot.CreateSubdirectory("A"); var sourceB = sourceRoot.CreateSubdirectory("B"); - + var fileADirectory = sourceA; if (fileParentPathComplement.IsNotEmpty()) { fileADirectory = new DirectoryInfo(sourceA.Combine(fileParentPathComplement!)); } + var fileA = _testDirectoryService.CreateFileInDirectory(fileADirectory, "fileToCopy1.txt", "fileToCopy1_versionA_content"); fileA.LastWriteTimeUtc = DateTime.UtcNow.AddHours(-8); - + var fileBDirectory = sourceB; if (fileParentPathComplement.IsNotEmpty()) { fileBDirectory = new DirectoryInfo(sourceB.Combine(fileParentPathComplement!)); } + var fileB = _testDirectoryService.CreateFileInDirectory(fileBDirectory, "fileToCopy1.txt", "fileToCopy1_versionB_content"); - + var inventoryDataA = new InventoryData(sourceA); var inventoryDataB = new InventoryData(sourceB); var sessionSettings = SessionSettingsGenerator.GenerateSessionSettings(); var comparisonResultPreparer = Container.Resolve(); var comparisonResult = await comparisonResultPreparer.BuildAndCompare(sessionSettings, inventoryDataA, inventoryDataB); - + var contentIdentityA = comparisonResult .ComparisonItems.Single(ci => ci.FileSystemType == FileSystemTypes.File) .ContentIdentities.Single(ci => ci.IsPresentIn(inventoryDataA.Inventory)); @@ -191,15 +193,17 @@ public async Task Test_SynchronizeContentOnly_Delta_LocalTarget(bool isLocalSess var signatureHashB = contentIdentityB.Core!.SignatureHash; var source = SharedDataPartTestFactory.Create("fileToCopy1.txt", inventoryDataA.Inventory, _currentEndPoint.ClientInstanceId, - FileSystemTypes.Directory, sourceA.FullName, fileParentPathComplement + "fileToCopy1.txt", signatureGuidA, signatureHashA, false); + FileSystemTypes.Directory, sourceA.FullName, fileParentPathComplement + "fileToCopy1.txt", signatureGuidA, signatureHashA, + false); var target = SharedDataPartTestFactory.Create("fileToCopy1.txt", inventoryDataB.Inventory, _currentEndPoint.ClientInstanceId, - FileSystemTypes.Directory, sourceB.FullName, fileParentPathComplement + "fileToCopy1.txt", signatureGuidB, signatureHashB, false); + FileSystemTypes.Directory, sourceB.FullName, fileParentPathComplement + "fileToCopy1.txt", signatureGuidB, signatureHashB, + false); var sharedActionsGroup = new SharedActionsGroup { ActionsGroupId = "ACI_Test", - Operator = ActionOperatorTypes.SynchronizeContentOnly, + Operator = ActionOperatorTypes.CopyContentOnly, Targets = [target], Source = source, SynchronizationType = SynchronizationTypes.Delta, @@ -207,7 +211,7 @@ public async Task Test_SynchronizeContentOnly_Delta_LocalTarget(bool isLocalSess }; await _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup); - + fileA.Refresh(); fileB.Refresh(); @@ -217,7 +221,7 @@ public async Task Test_SynchronizeContentOnly_Delta_LocalTarget(bool isLocalSess var contentB = await File.ReadAllTextAsync(fileB.FullName); contentB.Should().Be("fileToCopy1_versionA_content"); } - + [Test] [TestCase(true)] [TestCase(false)] @@ -227,15 +231,15 @@ public async Task Test_SynchronizeDate_LocalTarget(bool isLocalSession) var sourceA = sourceRoot.CreateSubdirectory("A"); var sourceB = sourceRoot.CreateSubdirectory("B"); - + var fileA = _testDirectoryService.CreateFileInDirectory(sourceA, "fileToSync.txt", "content_A"); var originalDateA = DateTime.UtcNow.AddHours(-8); fileA.LastWriteTimeUtc = originalDateA; - + var fileB = _testDirectoryService.CreateFileInDirectory(sourceB, "fileToSync.txt", "content_B"); var originalDateB = DateTime.UtcNow.AddHours(-4); fileB.LastWriteTimeUtc = originalDateB; - + var source = SharedDataPartTestFactory.Create("fileToSync.txt", FileSystemTypes.Directory, _currentEndPoint.ClientInstanceId, "A", sourceA.FullName, "/fileToSync.txt", null, null, false); @@ -245,7 +249,7 @@ public async Task Test_SynchronizeDate_LocalTarget(bool isLocalSession) var sharedActionsGroup = new SharedActionsGroup { ActionsGroupId = "ACI_SyncDate", - Operator = ActionOperatorTypes.SynchronizeDate, + Operator = ActionOperatorTypes.CopyDatesOnly, Targets = [target], Source = source, SynchronizationType = SynchronizationTypes.Full, @@ -253,7 +257,7 @@ public async Task Test_SynchronizeDate_LocalTarget(bool isLocalSession) CreationTimeUtc = DateTime.UtcNow.AddHours(-10), LastWriteTimeUtc = originalDateA }; - + await _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup); fileA.Refresh(); @@ -269,7 +273,7 @@ public async Task Test_SynchronizeDate_LocalTarget(bool isLocalSession) // But date should be synchronized fileB.LastWriteTimeUtc.Should().BeCloseTo(originalDateA, TimeSpan.FromHours(4.1)); // Allow for timezone differences } - + [Test] [TestCase(true)] [TestCase(false)] @@ -279,15 +283,15 @@ public async Task Test_SynchronizeContentAndDate_Full_LocalTarget(bool isLocalSe var sourceA = sourceRoot.CreateSubdirectory("A"); var sourceB = sourceRoot.CreateSubdirectory("B"); - + var fileA = _testDirectoryService.CreateFileInDirectory(sourceA, "fileToSync.txt", "content_from_A"); var originalDateA = DateTime.UtcNow.AddHours(-8); fileA.LastWriteTimeUtc = originalDateA; - + var fileBPath = Path.Combine(sourceB.FullName, "fileToSync.txt"); var fileB = new FileInfo(fileBPath); fileB.Exists.Should().BeFalse(); - + var source = SharedDataPartTestFactory.Create("fileToSync.txt", FileSystemTypes.Directory, _currentEndPoint.ClientInstanceId, "A", sourceA.FullName, "/fileToSync.txt", null, null, false); @@ -297,7 +301,7 @@ public async Task Test_SynchronizeContentAndDate_Full_LocalTarget(bool isLocalSe var sharedActionsGroup = new SharedActionsGroup { ActionsGroupId = "ACI_SyncContentAndDate", - Operator = ActionOperatorTypes.SynchronizeContentAndDate, + Operator = ActionOperatorTypes.Copy, Targets = [target], Source = source, SynchronizationType = SynchronizationTypes.Full, @@ -305,7 +309,7 @@ public async Task Test_SynchronizeContentAndDate_Full_LocalTarget(bool isLocalSe CreationTimeUtc = DateTime.UtcNow.AddHours(-10), LastWriteTimeUtc = originalDateA }; - + await _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup); fileA.Refresh(); @@ -319,7 +323,7 @@ public async Task Test_SynchronizeContentAndDate_Full_LocalTarget(bool isLocalSe contentB.Should().Be("content_from_A"); fileB.LastWriteTimeUtc.Should().BeCloseTo(originalDateA, TimeSpan.FromHours(4.1)); // Allow for timezone differences } - + [Test] public async Task Test_Create_Directory_LocalTarget() { @@ -329,7 +333,7 @@ public async Task Test_Create_Directory_LocalTarget() var targetDirectory = new DirectoryInfo(targetDirectoryPath); targetDirectory.Exists.Should().BeFalse(); - + var target = SharedDataPartTestFactory.Create("NewDirectory", FileSystemTypes.Directory, _currentEndPoint.ClientInstanceId, "B", sourceB.FullName, "/NewDirectory", null, null, false); @@ -342,13 +346,13 @@ public async Task Test_Create_Directory_LocalTarget() SynchronizationType = SynchronizationTypes.Full, PathIdentity = new PathIdentity(FileSystemTypes.Directory, "/NewDirectory", "NewDirectory", "/NewDirectory") }; - + await _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup); targetDirectory.Refresh(); targetDirectory.Exists.Should().BeTrue(); } - + [Test] public async Task Test_Create_Directory_AlreadyExists_LocalTarget() { @@ -357,7 +361,7 @@ public async Task Test_Create_Directory_AlreadyExists_LocalTarget() var existingDirectory = sourceB.CreateSubdirectory("ExistingDirectory"); existingDirectory.Exists.Should().BeTrue(); - + var target = SharedDataPartTestFactory.Create("ExistingDirectory", FileSystemTypes.Directory, _currentEndPoint.ClientInstanceId, "B", sourceB.FullName, "/ExistingDirectory", null, null, false); @@ -370,14 +374,14 @@ public async Task Test_Create_Directory_AlreadyExists_LocalTarget() SynchronizationType = SynchronizationTypes.Full, PathIdentity = new PathIdentity(FileSystemTypes.Directory, "/ExistingDirectory", "ExistingDirectory", "/ExistingDirectory") }; - + // Should not throw exception even if directory already exists await _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup); existingDirectory.Refresh(); existingDirectory.Exists.Should().BeTrue(); } - + [Test] public async Task Test_Delete_File_LocalTarget() { @@ -386,7 +390,7 @@ public async Task Test_Delete_File_LocalTarget() var fileToDelete = _testDirectoryService.CreateFileInDirectory(sourceB, "fileToDelete.txt", "content"); fileToDelete.Exists.Should().BeTrue(); - + var target = SharedDataPartTestFactory.Create("fileToDelete.txt", FileSystemTypes.Directory, _currentEndPoint.ClientInstanceId, "B", sourceB.FullName, "/fileToDelete.txt", null, null, false); @@ -399,13 +403,13 @@ public async Task Test_Delete_File_LocalTarget() SynchronizationType = SynchronizationTypes.Full, PathIdentity = new PathIdentity(FileSystemTypes.File, "/fileToDelete.txt", "fileToDelete.txt", "/fileToDelete.txt") }; - + await _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup); fileToDelete.Refresh(); fileToDelete.Exists.Should().BeFalse(); } - + [Test] public async Task Test_Delete_Directory_LocalTarget() { @@ -414,7 +418,7 @@ public async Task Test_Delete_Directory_LocalTarget() var directoryToDelete = sourceB.CreateSubdirectory("DirectoryToDelete"); directoryToDelete.Exists.Should().BeTrue(); - + var target = SharedDataPartTestFactory.Create("DirectoryToDelete", FileSystemTypes.Directory, _currentEndPoint.ClientInstanceId, "B", sourceB.FullName, "/DirectoryToDelete", null, null, false); @@ -427,13 +431,13 @@ public async Task Test_Delete_Directory_LocalTarget() SynchronizationType = SynchronizationTypes.Full, PathIdentity = new PathIdentity(FileSystemTypes.Directory, "/DirectoryToDelete", "DirectoryToDelete", "/DirectoryToDelete") }; - + await _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup); directoryToDelete.Refresh(); directoryToDelete.Exists.Should().BeFalse(); } - + [Test] public async Task Test_Delete_NonExistentFile_LocalTarget_ShouldNotThrow() { @@ -442,7 +446,7 @@ public async Task Test_Delete_NonExistentFile_LocalTarget_ShouldNotThrow() var nonExistentFilePath = Path.Combine(sourceB.FullName, "nonExistent.txt"); File.Exists(nonExistentFilePath).Should().BeFalse(); - + var target = SharedDataPartTestFactory.Create("nonExistent.txt", FileSystemTypes.Directory, _currentEndPoint.ClientInstanceId, "B", sourceB.FullName, "/nonExistent.txt", null, null, false); @@ -455,11 +459,11 @@ public async Task Test_Delete_NonExistentFile_LocalTarget_ShouldNotThrow() SynchronizationType = SynchronizationTypes.Full, PathIdentity = new PathIdentity(FileSystemTypes.File, "/nonExistent.txt", "nonExistent.txt", "/nonExistent.txt") }; - + // Should not throw exception for non-existent files await _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup); } - + [Test] public async Task Test_SynchronizeDate_FileNotExists_ShouldLogWarning() { @@ -468,30 +472,30 @@ public async Task Test_SynchronizeDate_FileNotExists_ShouldLogWarning() var nonExistentFilePath = Path.Combine(sourceB.FullName, "nonExistent.txt"); File.Exists(nonExistentFilePath).Should().BeFalse(); - + var target = SharedDataPartTestFactory.Create("nonExistent.txt", FileSystemTypes.Directory, _currentEndPoint.ClientInstanceId, "B", sourceB.FullName, "/nonExistent.txt", null, null, false); var sharedActionsGroup = new SharedActionsGroup { ActionsGroupId = "ACI_SyncDateNonExistent", - Operator = ActionOperatorTypes.SynchronizeDate, + Operator = ActionOperatorTypes.CopyDatesOnly, Targets = [target], Source = null, SynchronizationType = SynchronizationTypes.Full, PathIdentity = new PathIdentity(FileSystemTypes.File, "/nonExistent.txt", "nonExistent.txt", "/nonExistent.txt") }; - + // Should not throw but should handle the missing file gracefully await _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup); } - + [Test] public async Task Test_Create_InvalidFileSystemType_ShouldThrowException() { var sourceRoot = _testDirectoryService.CreateSubTestDirectory("Source"); var sourceB = sourceRoot.CreateSubdirectory("B"); - + var target = SharedDataPartTestFactory.Create("invalidFile.txt", FileSystemTypes.Directory, _currentEndPoint.ClientInstanceId, "B", sourceB.FullName, "/invalidFile.txt", null, null, false); @@ -504,12 +508,12 @@ public async Task Test_Create_InvalidFileSystemType_ShouldThrowException() SynchronizationType = SynchronizationTypes.Full, PathIdentity = new PathIdentity(FileSystemTypes.File, "/invalidFile.txt", "invalidFile.txt", "/invalidFile.txt") }; - + // Should throw exception because Create operation should only work for directories await FluentActions.Awaiting(() => _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup)) .Should().ThrowAsync(); } - + [Test] public async Task Test_Delete_Directory_WithFiles_ShouldThrowIOException() { @@ -522,7 +526,7 @@ public async Task Test_Delete_Directory_WithFiles_ShouldThrowIOException() directoryToDelete.Exists.Should().BeTrue(); directoryToDelete.GetFiles().Length.Should().Be(1); - + var target = SharedDataPartTestFactory.Create("DirectoryWithFiles", FileSystemTypes.Directory, _currentEndPoint.ClientInstanceId, "B", sourceB.FullName, "/DirectoryWithFiles", null, null, false); @@ -535,7 +539,7 @@ public async Task Test_Delete_Directory_WithFiles_ShouldThrowIOException() SynchronizationType = SynchronizationTypes.Full, PathIdentity = new PathIdentity(FileSystemTypes.Directory, "/DirectoryWithFiles", "DirectoryWithFiles", "/DirectoryWithFiles") }; - + // Should throw IOException when trying to delete non-empty directory with recursive=false await FluentActions.Awaiting(() => _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup)) .Should().ThrowAsync(); @@ -544,53 +548,54 @@ await FluentActions.Awaiting(() => _synchronizationActionHandler.RunSynchronizat directoryToDelete.Refresh(); directoryToDelete.Exists.Should().BeTrue(); } - + [Test] public async Task Test_RunSynchronizationAction_CancelledImmediately_ShouldThrowOperationCancelledException() { var cancellationTokenSource = new CancellationTokenSource(); await cancellationTokenSource.CancelAsync(); - + var sharedActionsGroup = new SharedActionsGroup { ActionsGroupId = "ACI_Cancelled", - Operator = ActionOperatorTypes.SynchronizeContentOnly, + Operator = ActionOperatorTypes.CopyContentOnly, Targets = [], Source = null, SynchronizationType = SynchronizationTypes.Full, PathIdentity = new PathIdentity(FileSystemTypes.File, "/test.txt", "test.txt", "/test.txt") }; - - await FluentActions.Awaiting(() => _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup, cancellationTokenSource.Token)) + + await FluentActions.Awaiting(() => + _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup, cancellationTokenSource.Token)) .Should().ThrowAsync(); } - + [Test] public async Task Test_SynchronizeContentOnly_RemoteTarget_ShouldCallUploader() { var sourceRoot = _testDirectoryService.CreateSubTestDirectory("Source"); var sourceA = sourceRoot.CreateSubdirectory("A"); var fileA = _testDirectoryService.CreateFileInDirectory(sourceA, "fileToSync.txt", "content_A"); - + // Create a remote target with different ClientInstanceId var remoteClientInstanceId = Guid.NewGuid().ToString(); var source = SharedDataPartTestFactory.Create("fileToSync.txt", FileSystemTypes.Directory, _currentEndPoint.ClientInstanceId, "A", sourceA.FullName, "/fileToSync.txt", null, null, false); - var remoteTarget = SharedDataPartTestFactory.Create("fileToSync.txt", FileSystemTypes.File, remoteClientInstanceId, + var remoteTarget = SharedDataPartTestFactory.Create("fileToSync.txt", FileSystemTypes.File, remoteClientInstanceId, "RemoteB", "C:\\RemotePath", "/fileToSync.txt", null, null, false); var sharedActionsGroup = new SharedActionsGroup { ActionsGroupId = "ACI_Remote", - Operator = ActionOperatorTypes.SynchronizeContentOnly, + Operator = ActionOperatorTypes.CopyContentOnly, Targets = [remoteTarget], Source = source, SynchronizationType = SynchronizationTypes.Full, PathIdentity = new PathIdentity(FileSystemTypes.File, "/fileToSync.txt", "fileToSync.txt", "/fileToSync.txt") }; - + // This should call the remote uploader (mocked in integration tests) await _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup); @@ -598,7 +603,7 @@ public async Task Test_SynchronizeContentOnly_RemoteTarget_ShouldCallUploader() fileA.Refresh(); fileA.Exists.Should().BeTrue(); } - + [Test] public async Task Test_SynchronizeContentOnly_MixedLocalAndRemoteTargets() { @@ -610,28 +615,28 @@ public async Task Test_SynchronizeContentOnly_MixedLocalAndRemoteTargets() var fileBPath = Path.Combine(sourceB.FullName, "fileToSync.txt"); var fileB = new FileInfo(fileBPath); fileB.Exists.Should().BeFalse(); - + var remoteClientInstanceId = Guid.NewGuid().ToString(); - var source = SharedDataPartTestFactory.Create("fileToSync.txt", FileSystemTypes.Directory, _currentEndPoint.ClientInstanceId, + var source = SharedDataPartTestFactory.Create("fileToSync.txt", FileSystemTypes.Directory, _currentEndPoint.ClientInstanceId, "A", sourceA.FullName, "/fileToSync.txt", null, null, false); var localTarget = SharedDataPartTestFactory.Create("fileToSync.txt", FileSystemTypes.Directory, _currentEndPoint.ClientInstanceId, "B", sourceB.FullName, "/fileToSync.txt", null, null, false); - - var remoteTarget = SharedDataPartTestFactory.Create("fileToSync.txt", FileSystemTypes.Directory, remoteClientInstanceId, + + var remoteTarget = SharedDataPartTestFactory.Create("fileToSync.txt", FileSystemTypes.Directory, remoteClientInstanceId, "RemoteC", "C:\\RemotePath", "/fileToSync.txt", null, null, false); var sharedActionsGroup = new SharedActionsGroup { ActionsGroupId = "ACI_Mixed", - Operator = ActionOperatorTypes.SynchronizeContentOnly, + Operator = ActionOperatorTypes.CopyContentOnly, Targets = [localTarget, remoteTarget], Source = source, SynchronizationType = SynchronizationTypes.Full, PathIdentity = new PathIdentity(FileSystemTypes.File, "/fileToSync.txt", "fileToSync.txt", "/fileToSync.txt") }; - + await _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup); // Verify local target was synchronized @@ -644,7 +649,7 @@ public async Task Test_SynchronizeContentOnly_MixedLocalAndRemoteTargets() fileA.Refresh(); fileA.Exists.Should().BeTrue(); } - + [Test] public async Task Test_RunSynchronizationAction_MultipleTargets_CancelledDuringProcessing() { @@ -662,27 +667,27 @@ public async Task Test_RunSynchronizationAction_MultipleTargets_CancelledDuringP var targetB = SharedDataPartTestFactory.Create("fileToSync.txt", FileSystemTypes.Directory, _currentEndPoint.ClientInstanceId, "B", sourceB.FullName, "/fileToSync.txt", null, null, false); - + var targetC = SharedDataPartTestFactory.Create("fileToSync.txt", FileSystemTypes.Directory, _currentEndPoint.ClientInstanceId, "C", sourceC.FullName, "/fileToSync.txt", null, null, false); var sharedActionsGroup = new SharedActionsGroup { ActionsGroupId = "ACI_MultipleTargets", - Operator = ActionOperatorTypes.SynchronizeContentOnly, + Operator = ActionOperatorTypes.CopyContentOnly, Targets = [targetB, targetC], Source = source, SynchronizationType = SynchronizationTypes.Full, PathIdentity = new PathIdentity(FileSystemTypes.File, "/fileToSync.txt", "fileToSync.txt", "/fileToSync.txt") }; - + // Cancel after a short delay to potentially interrupt processing _ = Task.Run(async () => { await Task.Delay(50, CancellationToken.None); await cancellationTokenSource.CancelAsync(); }, cancellationTokenSource.Token).ConfigureAwait(false); - + try { await _synchronizationActionHandler.RunSynchronizationAction(sharedActionsGroup, cancellationTokenSource.Token); @@ -696,7 +701,7 @@ public async Task Test_RunSynchronizationAction_MultipleTargets_CancelledDuringP fileA.Refresh(); fileA.Exists.Should().BeTrue(); } - + [Test] public async Task Test_RunPendingSynchronizationActions_CloudSession_NoAbortRequest() { @@ -713,14 +718,14 @@ public async Task Test_RunPendingSynchronizationActions_CloudSession_NoAbortRequ var synchronizationProcessData = new SynchronizationProcessData(); synchronizationProcessData.SynchronizationAbortRequest.OnNext(null); mockSynchronizationService.Setup(m => m.SynchronizationProcessData).Returns(synchronizationProcessData); - + // This should call Complete and HandlePendingActions await _synchronizationActionHandler.RunPendingSynchronizationActions(); // Verify the appropriate methods were called // Note: In integration tests, these are typically mocked services } - + [Test] public async Task Test_RunPendingSynchronizationActions_CloudSession_WithAbortRequest() { @@ -738,13 +743,13 @@ public async Task Test_RunPendingSynchronizationActions_CloudSession_WithAbortRe var abortRequest = new SynchronizationAbortRequest(); synchronizationProcessData.SynchronizationAbortRequest.OnNext(abortRequest); mockSynchronizationService.Setup(m => m.SynchronizationProcessData).Returns(synchronizationProcessData); - + // This should call Abort and ClearPendingActions await _synchronizationActionHandler.RunPendingSynchronizationActions(); // Verify the appropriate abort methods were called } - + [Test] public async Task Test_RunPendingSynchronizationActions_LocalSession_ShouldDoNothing() { @@ -753,19 +758,19 @@ public async Task Test_RunPendingSynchronizationActions_LocalSession_ShouldDoNot var mockSessionService = Container.Resolve>(); mockSessionService.Setup(m => m.CurrentSession).Returns(localSession); - + // This should do nothing for local sessions await _synchronizationActionHandler.RunPendingSynchronizationActions(); // Since it's a local session, no remote operations should be performed } - + [Test] public async Task Test_RunPendingSynchronizationActions_CancelledImmediately() { var cancellationTokenSource = new CancellationTokenSource(); await cancellationTokenSource.CancelAsync(); - + await FluentActions.Awaiting(() => _synchronizationActionHandler.RunPendingSynchronizationActions(cancellationTokenSource.Token)) .Should().ThrowAsync(); } diff --git a/tests/ByteSync.Client.UnitTests/Business/Filtering/IdentifiersTest.cs b/tests/ByteSync.Client.UnitTests/Business/Filtering/IdentifiersTest.cs index afb0a01c..a2debe15 100644 --- a/tests/ByteSync.Client.UnitTests/Business/Filtering/IdentifiersTest.cs +++ b/tests/ByteSync.Client.UnitTests/Business/Filtering/IdentifiersTest.cs @@ -28,7 +28,7 @@ public void All_ShouldContainAllPublicConstStrings() public void All_ShouldContain_ACTION_SYNCHRONIZE_CONTENT() { // Arrange - var expectedConstant = Identifiers.ACTION_COPY_CONTENTS; + var expectedConstant = Identifiers.ACTION_COPY_CONTENT; // Act var actualConstants = Identifiers.All(); diff --git a/tests/ByteSync.Client.UnitTests/Services/Actions/SharedActionsGroupOrganizer_UnitTests.cs b/tests/ByteSync.Client.UnitTests/Services/Actions/SharedActionsGroupOrganizer_UnitTests.cs index 56912ad3..57085136 100644 --- a/tests/ByteSync.Client.UnitTests/Services/Actions/SharedActionsGroupOrganizer_UnitTests.cs +++ b/tests/ByteSync.Client.UnitTests/Services/Actions/SharedActionsGroupOrganizer_UnitTests.cs @@ -54,7 +54,7 @@ public async Task OrganizeSharedActionsGroups_WhenSynchronizeContentAndDate_Shou { new() { - Operator = ActionOperatorTypes.SynchronizeContentAndDate, + Operator = ActionOperatorTypes.Copy, ActionsGroupId = "AGId1", PathIdentity = new PathIdentity(FileSystemTypes.File, "file1", "file1", "file1"), Source = SharedDataPartTestFactory.Create("A1", FileSystemTypes.Directory, "CID1", "A", "root", "relative", null, null, @@ -69,7 +69,7 @@ public async Task OrganizeSharedActionsGroups_WhenSynchronizeContentAndDate_Shou Size = null, CreationTimeUtc = null, LastWriteTimeUtc = null, - AppliesOnlySynchronizeDate = false, + AppliesOnlyCopyDate = false, } }; @@ -99,7 +99,7 @@ public async Task OrganizeSharedActionsGroups_WhenSynchronizeContentAndDate_Shou [Test] [TestCase("CID1", 0)] [TestCase("CID2", 1)] - public async Task OrganizeSharedActionsGroups_When_SynchronizeContentAndDate_And_AppliesOnlySynchronizeDate_ShouldSortCorrectly( + public async Task OrganizeSharedActionsGroups_When_Copy_And_AppliesOnlyCopyDate_ShouldSortCorrectly( string clientInstanceId, int expectedCount) { // Arrange @@ -107,7 +107,7 @@ public async Task OrganizeSharedActionsGroups_When_SynchronizeContentAndDate_And { new() { - Operator = ActionOperatorTypes.SynchronizeContentAndDate, + Operator = ActionOperatorTypes.Copy, ActionsGroupId = "AGId1", PathIdentity = new PathIdentity(FileSystemTypes.File, "file1", "file1", "file1"), Source = SharedDataPartTestFactory.Create("A1", FileSystemTypes.Directory, "CID1", "A", "root", "relative", null, null, @@ -122,7 +122,7 @@ public async Task OrganizeSharedActionsGroups_When_SynchronizeContentAndDate_And Size = null, CreationTimeUtc = null, LastWriteTimeUtc = null, - AppliesOnlySynchronizeDate = true, + AppliesOnlyCopyDate = true, } }; diff --git a/tests/ByteSync.Client.UnitTests/Services/Actions/TestSharedActionsGroupComputer.cs b/tests/ByteSync.Client.UnitTests/Services/Actions/TestSharedActionsGroupComputer.cs index 617d0b1b..ee13b893 100644 --- a/tests/ByteSync.Client.UnitTests/Services/Actions/TestSharedActionsGroupComputer.cs +++ b/tests/ByteSync.Client.UnitTests/Services/Actions/TestSharedActionsGroupComputer.cs @@ -50,7 +50,7 @@ public async Task Test_1Action_1Source_1Target() SharedDataPartTestFactory.Create("A1", FileSystemTypes.Directory, "CII_A", "A", "D:\\", "file1.txt", null, null, false); sharedAtomicAction.Target = SharedDataPartTestFactory.Create("B1", FileSystemTypes.Directory, "CII_B", "B", "D:\\", "file1.txt", null, null, false); - sharedAtomicAction.Operator = ActionOperatorTypes.SynchronizeContentAndDate; + sharedAtomicAction.Operator = ActionOperatorTypes.Copy; sharedAtomicAction.SynchronizationType = SynchronizationTypes.Full; sharedAtomicAction.Size = 10; sharedAtomicAction.LastWriteTimeUtc = dateTime; @@ -71,9 +71,9 @@ public async Task Test_1Action_1Source_1Target() Assert.That(capturedGroup.Count, Is.EqualTo(1)); var sharedActionGroup = capturedGroup.Single(); Assert.That(sharedActionGroup.ActionsGroupId, Does.StartWith("AGID_")); - Assert.That(sharedActionGroup.IsSynchronizeContent, Is.True); - Assert.That(sharedActionGroup.IsSynchronizeContentAndDate, Is.True); - Assert.That(sharedActionGroup.IsFinallySynchronizeContentAndDate, Is.True); + Assert.That(sharedActionGroup.IsCopyContent, Is.True); + Assert.That(sharedActionGroup.IsFullCopy, Is.True); + Assert.That(sharedActionGroup.IsFinallyCopyContentAndDate, Is.True); Assert.That(sharedActionGroup.IsInitialOperatingOnSourceNeeded, Is.True); Assert.That(sharedActionGroup.NeedsOnlyOperatingOnTargets, Is.False); Assert.That(sharedActionGroup.IsFile, Is.True); @@ -81,12 +81,12 @@ public async Task Test_1Action_1Source_1Target() Assert.That(sharedActionGroup.IsCreate, Is.False); Assert.That(sharedActionGroup.IsDelete, Is.False); Assert.That(sharedActionGroup.IsDoNothing, Is.False); - Assert.That(sharedActionGroup.IsSynchronizeContentOnly, Is.False); - Assert.That(sharedActionGroup.IsSynchronizeDate, Is.False); + Assert.That(sharedActionGroup.IsCopyContentOnly, Is.False); + Assert.That(sharedActionGroup.IsCopyDates, Is.False); Assert.That(sharedActionGroup.IsFinallySynchronizeDate, Is.False); - Assert.That(sharedActionGroup.AppliesOnlySynchronizeDate, Is.False); + Assert.That(sharedActionGroup.AppliesOnlyCopyDate, Is.False); Assert.That(sharedActionGroup.LinkingKeyValue, Is.EqualTo("file1.txt")); - Assert.That(sharedActionGroup.Operator, Is.EqualTo(ActionOperatorTypes.SynchronizeContentAndDate)); + Assert.That(sharedActionGroup.Operator, Is.EqualTo(ActionOperatorTypes.Copy)); Assert.That(sharedActionGroup.PathIdentity, Is.EqualTo(sharedAtomicAction.PathIdentity)); Assert.That(sharedActionGroup.Size, Is.EqualTo(10)); Assert.That(sharedActionGroup.LastWriteTimeUtc, Is.EqualTo(dateTime)); @@ -146,7 +146,7 @@ public async Task Test_2Actions_1Source_2Targets_1() SharedDataPartTestFactory.Create("A1", FileSystemTypes.Directory, "CII_A", "A", "D:\\", "file1.txt", null, null, false); sharedAtomicAction1.Target = SharedDataPartTestFactory.Create("B1", FileSystemTypes.Directory, "CII_B", "B", "D:\\", "file1.txt", null, null, false); - sharedAtomicAction1.Operator = ActionOperatorTypes.SynchronizeContentAndDate; + sharedAtomicAction1.Operator = ActionOperatorTypes.Copy; sharedAtomicAction1.SynchronizationType = SynchronizationTypes.Full; sharedAtomicAction1.Size = 10; sharedAtomicAction1.LastWriteTimeUtc = dateTime; @@ -158,7 +158,7 @@ public async Task Test_2Actions_1Source_2Targets_1() SharedDataPartTestFactory.Create("A1", FileSystemTypes.Directory, "CII_A", "A", "D:\\", "file1.txt", null, null, false); sharedAtomicAction2.Target = SharedDataPartTestFactory.Create("C1", FileSystemTypes.Directory, "CII_C", "C", "D:\\", "file1.txt", null, null, false); - sharedAtomicAction2.Operator = ActionOperatorTypes.SynchronizeContentAndDate; + sharedAtomicAction2.Operator = ActionOperatorTypes.Copy; sharedAtomicAction2.SynchronizationType = SynchronizationTypes.Full; sharedAtomicAction2.Size = 10; sharedAtomicAction2.LastWriteTimeUtc = dateTime; @@ -184,24 +184,24 @@ public async Task Test_2Actions_1Source_2Targets_1() ClassicAssert.IsTrue(sharedActionsGroup.ActionsGroupId.StartsWith("AGID_")); - ClassicAssert.IsFalse(sharedActionsGroup.AppliesOnlySynchronizeDate); + ClassicAssert.IsFalse(sharedActionsGroup.AppliesOnlyCopyDate); ClassicAssert.IsFalse(sharedActionsGroup.IsCreate); ClassicAssert.IsFalse(sharedActionsGroup.IsDelete); ClassicAssert.IsFalse(sharedActionsGroup.IsDirectory); ClassicAssert.IsFalse(sharedActionsGroup.IsDoNothing); ClassicAssert.IsTrue(sharedActionsGroup.IsFile); - ClassicAssert.IsTrue(sharedActionsGroup.IsFinallySynchronizeContentAndDate); + ClassicAssert.IsTrue(sharedActionsGroup.IsFinallyCopyContentAndDate); ClassicAssert.IsFalse(sharedActionsGroup.IsFinallySynchronizeDate); ClassicAssert.IsTrue(sharedActionsGroup.IsInitialOperatingOnSourceNeeded); ClassicAssert.IsFalse(sharedActionsGroup.NeedsOnlyOperatingOnTargets); - ClassicAssert.IsTrue(sharedActionsGroup.IsSynchronizeContent); - ClassicAssert.IsTrue(sharedActionsGroup.IsSynchronizeContentAndDate); - ClassicAssert.IsFalse(sharedActionsGroup.IsSynchronizeContentOnly); - ClassicAssert.IsFalse(sharedActionsGroup.IsSynchronizeDate); + ClassicAssert.IsTrue(sharedActionsGroup.IsCopyContent); + ClassicAssert.IsTrue(sharedActionsGroup.IsFullCopy); + ClassicAssert.IsFalse(sharedActionsGroup.IsCopyContentOnly); + ClassicAssert.IsFalse(sharedActionsGroup.IsCopyDates); ClassicAssert.AreEqual(dateTime, sharedActionsGroup.LastWriteTimeUtc); ClassicAssert.AreEqual("file1.txt", sharedActionsGroup.LinkingKeyValue); - ClassicAssert.AreEqual(ActionOperatorTypes.SynchronizeContentAndDate, sharedActionsGroup.Operator); + ClassicAssert.AreEqual(ActionOperatorTypes.Copy, sharedActionsGroup.Operator); ClassicAssert.AreEqual(sharedAtomicAction1.PathIdentity, sharedActionsGroup.PathIdentity); ClassicAssert.AreEqual(10, sharedActionsGroup.Size); ClassicAssert.AreEqual(sharedAtomicAction1.Source, sharedActionsGroup.Source); @@ -225,7 +225,7 @@ public async Task Test_2Actions_1Source_2Targets_2() "SigGuidA", "SigHashA", false); sharedAtomicAction1.Target = SharedDataPartTestFactory.Create("B1", FileSystemTypes.Directory, "CII_B", "B", "D:\\", "file1.txt", "SigGuidB", "SigHashBC", false); - sharedAtomicAction1.Operator = ActionOperatorTypes.SynchronizeContentAndDate; + sharedAtomicAction1.Operator = ActionOperatorTypes.Copy; sharedAtomicAction1.SynchronizationType = SynchronizationTypes.Delta; sharedAtomicAction1.Size = 10; sharedAtomicAction1.LastWriteTimeUtc = dateTime; @@ -237,7 +237,7 @@ public async Task Test_2Actions_1Source_2Targets_2() "SigGuidA", "SigHashA", false); sharedAtomicAction2.Target = SharedDataPartTestFactory.Create("C1", FileSystemTypes.Directory, "CII_C", "C", "D:\\", "file1.txt", "SigGuidC", "SigHashBC", false); - sharedAtomicAction2.Operator = ActionOperatorTypes.SynchronizeContentAndDate; + sharedAtomicAction2.Operator = ActionOperatorTypes.Copy; sharedAtomicAction2.SynchronizationType = SynchronizationTypes.Delta; sharedAtomicAction2.Size = 10; sharedAtomicAction2.LastWriteTimeUtc = dateTime; @@ -262,24 +262,24 @@ public async Task Test_2Actions_1Source_2Targets_2() ClassicAssert.IsTrue(sharedActionsGroup.ActionsGroupId.StartsWith("AGID_")); - ClassicAssert.IsFalse(sharedActionsGroup.AppliesOnlySynchronizeDate); + ClassicAssert.IsFalse(sharedActionsGroup.AppliesOnlyCopyDate); ClassicAssert.IsFalse(sharedActionsGroup.IsCreate); ClassicAssert.IsFalse(sharedActionsGroup.IsDelete); ClassicAssert.IsFalse(sharedActionsGroup.IsDirectory); ClassicAssert.IsFalse(sharedActionsGroup.IsDoNothing); ClassicAssert.IsTrue(sharedActionsGroup.IsFile); - ClassicAssert.IsTrue(sharedActionsGroup.IsFinallySynchronizeContentAndDate); + ClassicAssert.IsTrue(sharedActionsGroup.IsFinallyCopyContentAndDate); ClassicAssert.IsFalse(sharedActionsGroup.IsFinallySynchronizeDate); ClassicAssert.IsTrue(sharedActionsGroup.IsInitialOperatingOnSourceNeeded); ClassicAssert.IsFalse(sharedActionsGroup.NeedsOnlyOperatingOnTargets); - ClassicAssert.IsTrue(sharedActionsGroup.IsSynchronizeContent); - ClassicAssert.IsTrue(sharedActionsGroup.IsSynchronizeContentAndDate); - ClassicAssert.IsFalse(sharedActionsGroup.IsSynchronizeContentOnly); - ClassicAssert.IsFalse(sharedActionsGroup.IsSynchronizeDate); + ClassicAssert.IsTrue(sharedActionsGroup.IsCopyContent); + ClassicAssert.IsTrue(sharedActionsGroup.IsFullCopy); + ClassicAssert.IsFalse(sharedActionsGroup.IsCopyContentOnly); + ClassicAssert.IsFalse(sharedActionsGroup.IsCopyDates); ClassicAssert.AreEqual(dateTime, sharedActionsGroup.LastWriteTimeUtc); ClassicAssert.AreEqual("file1.txt", sharedActionsGroup.LinkingKeyValue); - ClassicAssert.AreEqual(ActionOperatorTypes.SynchronizeContentAndDate, sharedActionsGroup.Operator); + ClassicAssert.AreEqual(ActionOperatorTypes.Copy, sharedActionsGroup.Operator); ClassicAssert.AreEqual(sharedAtomicAction1.PathIdentity, sharedActionsGroup.PathIdentity); ClassicAssert.AreEqual(10, sharedActionsGroup.Size); ClassicAssert.AreEqual(sharedAtomicAction1.Source, sharedActionsGroup.Source); @@ -302,7 +302,7 @@ public async Task Test_2Actions_1Source_2Targets_3() "SigGuidA", "SigHashA", false); sharedAtomicAction1.Target = SharedDataPartTestFactory.Create("B1", FileSystemTypes.Directory, "CII_B", "B", "D:\\", "file1.txt", "SigGuidB", "SigHashB", false); - sharedAtomicAction1.Operator = ActionOperatorTypes.SynchronizeContentAndDate; + sharedAtomicAction1.Operator = ActionOperatorTypes.Copy; sharedAtomicAction1.SynchronizationType = SynchronizationTypes.Delta; sharedAtomicAction1.Size = 10; sharedAtomicAction1.LastWriteTimeUtc = dateTime; @@ -314,7 +314,7 @@ public async Task Test_2Actions_1Source_2Targets_3() "SigGuidA", "SigHashA", false); sharedAtomicAction2.Target = SharedDataPartTestFactory.Create("C1", FileSystemTypes.Directory, "CII_C", "C", "D:\\", "file1.txt", "SigGuidC", "SigHashC", false); - sharedAtomicAction2.Operator = ActionOperatorTypes.SynchronizeContentAndDate; + sharedAtomicAction2.Operator = ActionOperatorTypes.Copy; sharedAtomicAction2.SynchronizationType = SynchronizationTypes.Delta; sharedAtomicAction2.Size = 10; sharedAtomicAction2.LastWriteTimeUtc = dateTime; @@ -339,24 +339,24 @@ public async Task Test_2Actions_1Source_2Targets_3() { ClassicAssert.IsTrue(sharedActionsGroup.ActionsGroupId.StartsWith("AGID_")); - ClassicAssert.IsFalse(sharedActionsGroup.AppliesOnlySynchronizeDate); + ClassicAssert.IsFalse(sharedActionsGroup.AppliesOnlyCopyDate); ClassicAssert.IsFalse(sharedActionsGroup.IsCreate); ClassicAssert.IsFalse(sharedActionsGroup.IsDelete); ClassicAssert.IsFalse(sharedActionsGroup.IsDirectory); ClassicAssert.IsFalse(sharedActionsGroup.IsDoNothing); ClassicAssert.IsTrue(sharedActionsGroup.IsFile); - ClassicAssert.IsTrue(sharedActionsGroup.IsFinallySynchronizeContentAndDate); + ClassicAssert.IsTrue(sharedActionsGroup.IsFinallyCopyContentAndDate); ClassicAssert.IsFalse(sharedActionsGroup.IsFinallySynchronizeDate); ClassicAssert.IsTrue(sharedActionsGroup.IsInitialOperatingOnSourceNeeded); ClassicAssert.IsFalse(sharedActionsGroup.NeedsOnlyOperatingOnTargets); - ClassicAssert.IsTrue(sharedActionsGroup.IsSynchronizeContent); - ClassicAssert.IsTrue(sharedActionsGroup.IsSynchronizeContentAndDate); - ClassicAssert.IsFalse(sharedActionsGroup.IsSynchronizeContentOnly); - ClassicAssert.IsFalse(sharedActionsGroup.IsSynchronizeDate); + ClassicAssert.IsTrue(sharedActionsGroup.IsCopyContent); + ClassicAssert.IsTrue(sharedActionsGroup.IsFullCopy); + ClassicAssert.IsFalse(sharedActionsGroup.IsCopyContentOnly); + ClassicAssert.IsFalse(sharedActionsGroup.IsCopyDates); ClassicAssert.AreEqual(dateTime, sharedActionsGroup.LastWriteTimeUtc); ClassicAssert.AreEqual("file1.txt", sharedActionsGroup.LinkingKeyValue); - ClassicAssert.AreEqual(ActionOperatorTypes.SynchronizeContentAndDate, sharedActionsGroup.Operator); + ClassicAssert.AreEqual(ActionOperatorTypes.Copy, sharedActionsGroup.Operator); ClassicAssert.AreEqual(sharedAtomicAction1.PathIdentity, sharedActionsGroup.PathIdentity); ClassicAssert.AreEqual(10, sharedActionsGroup.Size); ClassicAssert.AreEqual(sharedAtomicAction1.Source, sharedActionsGroup.Source); @@ -379,7 +379,7 @@ public async Task Test_2Actions_1Source_2Targets_4() "SigGuidA", "SigHashABC", false); sharedAtomicAction1.Target = SharedDataPartTestFactory.Create("B1", FileSystemTypes.Directory, "CII_B", "B", "D:\\", "file1.txt", "SigGuidB", "SigHashABC", false); - sharedAtomicAction1.Operator = ActionOperatorTypes.SynchronizeContentAndDate; + sharedAtomicAction1.Operator = ActionOperatorTypes.Copy; sharedAtomicAction1.SynchronizationType = SynchronizationTypes.Delta; sharedAtomicAction1.Size = 10; sharedAtomicAction1.LastWriteTimeUtc = dateTime; @@ -391,7 +391,7 @@ public async Task Test_2Actions_1Source_2Targets_4() "SigGuidA", "SigHashABC", false); sharedAtomicAction2.Target = SharedDataPartTestFactory.Create("C1", FileSystemTypes.Directory, "CII_C", "C", "D:\\", "file1.txt", "SigGuidC", "SigHashABC", false); - sharedAtomicAction2.Operator = ActionOperatorTypes.SynchronizeContentAndDate; + sharedAtomicAction2.Operator = ActionOperatorTypes.Copy; sharedAtomicAction2.SynchronizationType = SynchronizationTypes.Delta; sharedAtomicAction2.Size = 10; sharedAtomicAction2.LastWriteTimeUtc = dateTime; @@ -416,24 +416,24 @@ public async Task Test_2Actions_1Source_2Targets_4() ClassicAssert.IsTrue(sharedActionsGroup.ActionsGroupId.StartsWith("AGID_")); - ClassicAssert.IsTrue(sharedActionsGroup.AppliesOnlySynchronizeDate); + ClassicAssert.IsTrue(sharedActionsGroup.AppliesOnlyCopyDate); ClassicAssert.IsFalse(sharedActionsGroup.IsCreate); ClassicAssert.IsFalse(sharedActionsGroup.IsDelete); ClassicAssert.IsFalse(sharedActionsGroup.IsDirectory); ClassicAssert.IsFalse(sharedActionsGroup.IsDoNothing); ClassicAssert.IsTrue(sharedActionsGroup.IsFile); - ClassicAssert.IsFalse(sharedActionsGroup.IsFinallySynchronizeContentAndDate); + ClassicAssert.IsFalse(sharedActionsGroup.IsFinallyCopyContentAndDate); ClassicAssert.IsTrue(sharedActionsGroup.IsFinallySynchronizeDate); ClassicAssert.IsFalse(sharedActionsGroup.IsInitialOperatingOnSourceNeeded); ClassicAssert.IsTrue(sharedActionsGroup.NeedsOnlyOperatingOnTargets); - ClassicAssert.IsTrue(sharedActionsGroup.IsSynchronizeContent); - ClassicAssert.IsTrue(sharedActionsGroup.IsSynchronizeContentAndDate); - ClassicAssert.IsFalse(sharedActionsGroup.IsSynchronizeContentOnly); - ClassicAssert.IsFalse(sharedActionsGroup.IsSynchronizeDate); + ClassicAssert.IsTrue(sharedActionsGroup.IsCopyContent); + ClassicAssert.IsTrue(sharedActionsGroup.IsFullCopy); + ClassicAssert.IsFalse(sharedActionsGroup.IsCopyContentOnly); + ClassicAssert.IsFalse(sharedActionsGroup.IsCopyDates); ClassicAssert.AreEqual(dateTime, sharedActionsGroup.LastWriteTimeUtc); ClassicAssert.AreEqual("file1.txt", sharedActionsGroup.LinkingKeyValue); - ClassicAssert.AreEqual(ActionOperatorTypes.SynchronizeContentAndDate, sharedActionsGroup.Operator); + ClassicAssert.AreEqual(ActionOperatorTypes.Copy, sharedActionsGroup.Operator); ClassicAssert.AreEqual(sharedAtomicAction1.PathIdentity, sharedActionsGroup.PathIdentity); ClassicAssert.AreEqual(10, sharedActionsGroup.Size); ClassicAssert.AreEqual(sharedAtomicAction1.Source, sharedActionsGroup.Source); @@ -455,7 +455,7 @@ public async Task Test_2Actions_1Source_2Targets_5() sharedAtomicAction1.Source = SharedDataPartTestFactory.Create("A1", FileSystemTypes.Directory, "CII_A", "A", "D:\\", "file1.txt", "SigGuidA", "SigHashABC", false); sharedAtomicAction1.Target = null; - sharedAtomicAction1.Operator = ActionOperatorTypes.SynchronizeContentAndDate; + sharedAtomicAction1.Operator = ActionOperatorTypes.Copy; sharedAtomicAction1.SynchronizationType = SynchronizationTypes.Delta; sharedAtomicAction1.Size = 10; sharedAtomicAction1.LastWriteTimeUtc = dateTime; @@ -467,7 +467,7 @@ public async Task Test_2Actions_1Source_2Targets_5() "SigGuidA", "SigHashABC", false); sharedAtomicAction2.Target = SharedDataPartTestFactory.Create("C1", FileSystemTypes.Directory, "CII_C", "C", "D:\\", "file1.txt", "SigGuidC", "SigHashABC", false); - sharedAtomicAction2.Operator = ActionOperatorTypes.SynchronizeContentAndDate; + sharedAtomicAction2.Operator = ActionOperatorTypes.Copy; sharedAtomicAction2.SynchronizationType = SynchronizationTypes.Delta; sharedAtomicAction2.Size = 10; sharedAtomicAction2.LastWriteTimeUtc = dateTime; @@ -492,24 +492,24 @@ public async Task Test_2Actions_1Source_2Targets_5() ClassicAssert.IsTrue(sharedActionsGroup.ActionsGroupId.StartsWith("AGID_")); - ClassicAssert.IsTrue(sharedActionsGroup.AppliesOnlySynchronizeDate); + ClassicAssert.IsTrue(sharedActionsGroup.AppliesOnlyCopyDate); ClassicAssert.IsFalse(sharedActionsGroup.IsCreate); ClassicAssert.IsFalse(sharedActionsGroup.IsDelete); ClassicAssert.IsFalse(sharedActionsGroup.IsDirectory); ClassicAssert.IsFalse(sharedActionsGroup.IsDoNothing); ClassicAssert.IsTrue(sharedActionsGroup.IsFile); - ClassicAssert.IsFalse(sharedActionsGroup.IsFinallySynchronizeContentAndDate); + ClassicAssert.IsFalse(sharedActionsGroup.IsFinallyCopyContentAndDate); ClassicAssert.IsTrue(sharedActionsGroup.IsFinallySynchronizeDate); ClassicAssert.IsFalse(sharedActionsGroup.IsInitialOperatingOnSourceNeeded); ClassicAssert.IsTrue(sharedActionsGroup.NeedsOnlyOperatingOnTargets); - ClassicAssert.IsTrue(sharedActionsGroup.IsSynchronizeContent); - ClassicAssert.IsTrue(sharedActionsGroup.IsSynchronizeContentAndDate); - ClassicAssert.IsFalse(sharedActionsGroup.IsSynchronizeContentOnly); - ClassicAssert.IsFalse(sharedActionsGroup.IsSynchronizeDate); + ClassicAssert.IsTrue(sharedActionsGroup.IsCopyContent); + ClassicAssert.IsTrue(sharedActionsGroup.IsFullCopy); + ClassicAssert.IsFalse(sharedActionsGroup.IsCopyContentOnly); + ClassicAssert.IsFalse(sharedActionsGroup.IsCopyDates); ClassicAssert.AreEqual(dateTime, sharedActionsGroup.LastWriteTimeUtc); ClassicAssert.AreEqual("file1.txt", sharedActionsGroup.LinkingKeyValue); - ClassicAssert.AreEqual(ActionOperatorTypes.SynchronizeContentAndDate, sharedActionsGroup.Operator); + ClassicAssert.AreEqual(ActionOperatorTypes.Copy, sharedActionsGroup.Operator); ClassicAssert.AreEqual(sharedAtomicAction1.PathIdentity, sharedActionsGroup.PathIdentity); ClassicAssert.AreEqual(10, sharedActionsGroup.Size); ClassicAssert.AreEqual(sharedAtomicAction1.Source, sharedActionsGroup.Source); diff --git a/tests/ByteSync.Client.UnitTests/Services/Comparisons/AtomicActionConsistencyCheckerAccessTests.cs b/tests/ByteSync.Client.UnitTests/Services/Comparisons/AtomicActionConsistencyCheckerAccessTests.cs index 6860ee42..cf3575dc 100644 --- a/tests/ByteSync.Client.UnitTests/Services/Comparisons/AtomicActionConsistencyCheckerAccessTests.cs +++ b/tests/ByteSync.Client.UnitTests/Services/Comparisons/AtomicActionConsistencyCheckerAccessTests.cs @@ -69,7 +69,7 @@ public void Synchronize_Fails_When_Source_Not_Accessible() var action = new AtomicAction { - Operator = ActionOperatorTypes.SynchronizeContentOnly, + Operator = ActionOperatorTypes.CopyContentOnly, Source = new DataPart("A", src), Destination = new DataPart("B", dst), ComparisonItem = item @@ -94,7 +94,7 @@ public void Synchronize_Fails_When_Source_Part_Incomplete_In_Flat_Mode() var action = new AtomicAction { - Operator = ActionOperatorTypes.SynchronizeContentOnly, + Operator = ActionOperatorTypes.CopyContentOnly, Source = new DataPart("A", src), Destination = new DataPart("B", dst), ComparisonItem = item @@ -144,7 +144,7 @@ public void Synchronize_WithSourceCoreNull_DoesNotThrowException() var action = new AtomicAction { - Operator = ActionOperatorTypes.SynchronizeContentAndDate, + Operator = ActionOperatorTypes.Copy, Source = new DataPart("A", src), Destination = new DataPart("B", dst), ComparisonItem = item @@ -168,7 +168,7 @@ public void Synchronize_Fails_When_Target_Part_Incomplete_In_Flat_Mode() var action = new AtomicAction { - Operator = ActionOperatorTypes.SynchronizeContentOnly, + Operator = ActionOperatorTypes.CopyContentOnly, Source = new DataPart("A", src), Destination = new DataPart("B", dst), ComparisonItem = item @@ -218,7 +218,7 @@ public void Synchronize_WithTargetCoreNull_DoesNotThrowException() var action = new AtomicAction { - Operator = ActionOperatorTypes.SynchronizeContentAndDate, + Operator = ActionOperatorTypes.Copy, Source = new DataPart("A", src), Destination = new DataPart("B", dst), ComparisonItem = item @@ -267,7 +267,7 @@ public void SynchronizeContentOnly_WithBothCoresNull_DoesNotThrowException() var action = new AtomicAction { - Operator = ActionOperatorTypes.SynchronizeContentOnly, + Operator = ActionOperatorTypes.CopyContentOnly, Source = new DataPart("A", src), Destination = new DataPart("B", dst), ComparisonItem = item @@ -292,7 +292,7 @@ public void Synchronize_Allows_Incomplete_Parts_In_Tree_Mode_When_Items_Accessib var action = new AtomicAction { - Operator = ActionOperatorTypes.SynchronizeContentOnly, + Operator = ActionOperatorTypes.CopyContentOnly, Source = new DataPart("A", src), Destination = new DataPart("B", dst), ComparisonItem = item @@ -341,7 +341,7 @@ public void Synchronize_WithInaccessibleTargetAndNullCore_DoesNotThrowException( var action = new AtomicAction { - Operator = ActionOperatorTypes.SynchronizeContentAndDate, + Operator = ActionOperatorTypes.Copy, Source = new DataPart("A", src), Destination = new DataPart("B", dst), ComparisonItem = item diff --git a/tests/ByteSync.Client.UnitTests/ViewModels/Sessions/Comparisons/Actions/AtomicActionEditViewModelTests.cs b/tests/ByteSync.Client.UnitTests/ViewModels/Sessions/Comparisons/Actions/AtomicActionEditViewModelTests.cs new file mode 100644 index 00000000..264316e6 --- /dev/null +++ b/tests/ByteSync.Client.UnitTests/ViewModels/Sessions/Comparisons/Actions/AtomicActionEditViewModelTests.cs @@ -0,0 +1,204 @@ +using System.Collections; +using System.Collections.ObjectModel; +using System.Reflection; +using ByteSync.Business.Actions.Local; +using ByteSync.Business.Comparisons; +using ByteSync.Common.Business.Actions; +using ByteSync.Common.Business.EndPoints; +using ByteSync.Common.Business.Inventories; +using ByteSync.Common.Business.Misc; +using ByteSync.Interfaces.Services.Sessions; +using ByteSync.Models.Inventories; +using ByteSync.ViewModels.Sessions.Comparisons.Actions; +using FluentAssertions; +using NUnit.Framework; + +namespace ByteSync.Client.UnitTests.ViewModels.Sessions.Comparisons.Actions; + +[TestFixture] +public class AtomicActionEditViewModelTests +{ + private sealed class TestDataPartIndexer : IDataPartIndexer + { + private readonly ReadOnlyCollection _dataParts; + + public TestDataPartIndexer(IReadOnlyCollection dataParts) + { + _dataParts = new ReadOnlyCollection(dataParts.ToList()); + } + + public void BuildMap(List inventories) + { + } + + public ReadOnlyCollection GetAllDataParts() + { + return _dataParts; + } + + public DataPart? GetDataPart(string? dataPartName) + { + return _dataParts.FirstOrDefault(dp => dp.Name == dataPartName); + } + + public void Remap(ICollection synchronizationRules) + { + } + } + + private static IDataPartIndexer BuildDataPartIndexer() + { + var endpoint = new ByteSyncEndpoint + { + ClientId = "c", + ClientInstanceId = "ci", + Version = "v", + OSPlatform = OSPlatforms.Windows, + IpAddress = "127.0.0.1" + }; + + var inventoryA = new Inventory { InventoryId = "INV_A", Code = "A", Endpoint = endpoint, MachineName = "M" }; + var inventoryB = new Inventory { InventoryId = "INV_B", Code = "B", Endpoint = endpoint, MachineName = "M" }; + var partA = new InventoryPart(inventoryA, "c:\\a", FileSystemTypes.Directory) { Code = "A1" }; + var partB = new InventoryPart(inventoryB, "c:\\b", FileSystemTypes.Directory) { Code = "B1" }; + + var dataParts = new List + { + new("A", partA), + new("B", partB) + }; + + return new TestDataPartIndexer(dataParts); + } + + [Test] + public void Constructor_ForFile_ShouldExposeExpectedActions() + { + var viewModel = new AtomicActionEditViewModel(FileSystemTypes.File, true, null, BuildDataPartIndexer()); + + var actions = GetActionOperators(viewModel); + + actions.Should().Contain(ActionOperatorTypes.Copy); + actions.Should().Contain(ActionOperatorTypes.CopyContentOnly); + actions.Should().Contain(ActionOperatorTypes.CopyDatesOnly); + actions.Should().Contain(ActionOperatorTypes.Delete); + actions.Should().Contain(ActionOperatorTypes.DoNothing); + } + + [Test] + public void Constructor_ForDirectory_ShouldExposeExpectedActions() + { + var viewModel = new AtomicActionEditViewModel(FileSystemTypes.Directory, true, null, BuildDataPartIndexer()); + + var actions = GetActionOperators(viewModel); + + actions.Should().Contain(ActionOperatorTypes.Create); + actions.Should().Contain(ActionOperatorTypes.Delete); + actions.Should().Contain(ActionOperatorTypes.DoNothing); + actions.Should().NotContain(ActionOperatorTypes.Copy); + } + + [Test] + public void ExportSynchronizationAction_WhenMissingSelections_ShouldReturnNull() + { + var viewModel = new AtomicActionEditViewModel(FileSystemTypes.File, true, null, BuildDataPartIndexer()); + + var result = InvokeExport(viewModel); + + result.Should().BeNull(); + } + + [Test] + public void ExportSynchronizationAction_WithValidSelections_ShouldReturnAction() + { + var viewModel = new AtomicActionEditViewModel(FileSystemTypes.File, true, null, BuildDataPartIndexer()); + + var action = GetActionByOperator(viewModel, ActionOperatorTypes.Copy); + var sources = GetInternalEnumerable(viewModel, "Sources"); + var destinations = GetInternalEnumerable(viewModel, "Destinations"); + + SetInternalProperty(viewModel, "SelectedAction", action); + SetInternalProperty(viewModel, "SelectedSource", FirstItem(sources)); + SetInternalProperty(viewModel, "SelectedDestination", FirstItem(destinations)); + + var result = InvokeExport(viewModel); + + result.Should().NotBeNull(); + result.Operator.Should().Be(ActionOperatorTypes.Copy); + result.Source.Should().NotBeNull(); + result.Destination.Should().NotBeNull(); + } + + [Test] + public void RemoveCommand_ShouldRaiseRemoveRequested() + { + var viewModel = new AtomicActionEditViewModel(FileSystemTypes.File, true, null, BuildDataPartIndexer()); + var raised = false; + + viewModel.RemoveRequested += (_, _) => raised = true; + + viewModel.RemoveCommand.Execute().Subscribe(); + + raised.Should().BeTrue(); + } + + private static AtomicAction? InvokeExport(AtomicActionEditViewModel viewModel) + { + var method = typeof(AtomicActionEditViewModel).GetMethod("ExportSynchronizationAction", + BindingFlags.Instance | BindingFlags.NonPublic); + return (AtomicAction?)method!.Invoke(viewModel, null); + } + + private static IList GetActionOperators(AtomicActionEditViewModel viewModel) + { + var actions = GetInternalEnumerable(viewModel, "Actions"); + var results = new List(); + + foreach (var action in actions) + { + var property = action!.GetType().GetProperty("ActionOperatorType", BindingFlags.Instance | BindingFlags.Public); + results.Add((ActionOperatorTypes)property!.GetValue(action)!); + } + + return results.ToList(); + } + + private static object GetActionByOperator(AtomicActionEditViewModel viewModel, ActionOperatorTypes operatorType) + { + var actions = GetInternalEnumerable(viewModel, "Actions"); + + foreach (var action in actions) + { + var property = action!.GetType().GetProperty("ActionOperatorType", BindingFlags.Instance | BindingFlags.Public); + var value = (ActionOperatorTypes)property!.GetValue(action)!; + if (value == operatorType) + { + return action; + } + } + + throw new InvalidOperationException("Action not found"); + } + + private static IEnumerable GetInternalEnumerable(object target, string propertyName) + { + var property = target.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic); + return (IEnumerable)property!.GetValue(target)!; + } + + private static object FirstItem(IEnumerable items) + { + foreach (var item in items) + { + return item!; + } + + throw new InvalidOperationException("Empty collection"); + } + + private static void SetInternalProperty(object target, string propertyName, object? value) + { + var property = target.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic); + property!.SetValue(target, value); + } +} diff --git a/tests/ByteSync.Client.UnitTests/ViewModels/Sessions/Comparisons/Actions/SynchronizationRuleGlobalViewModelTests.cs b/tests/ByteSync.Client.UnitTests/ViewModels/Sessions/Comparisons/Actions/SynchronizationRuleGlobalViewModelTests.cs index e3c54e22..f3fe822f 100644 --- a/tests/ByteSync.Client.UnitTests/ViewModels/Sessions/Comparisons/Actions/SynchronizationRuleGlobalViewModelTests.cs +++ b/tests/ByteSync.Client.UnitTests/ViewModels/Sessions/Comparisons/Actions/SynchronizationRuleGlobalViewModelTests.cs @@ -1,20 +1,28 @@ +using System.Collections; +using System.Collections.ObjectModel; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Reflection; +using Autofac; using ByteSync.Business; using ByteSync.Business.Actions.Local; using ByteSync.Business.Comparisons; using ByteSync.Business.Sessions; using ByteSync.Common.Business.Actions; +using ByteSync.Common.Business.EndPoints; using ByteSync.Common.Business.Inventories; +using ByteSync.Common.Business.Misc; using ByteSync.Interfaces.Controls.Synchronizations; using ByteSync.Interfaces.Dialogs; using ByteSync.Interfaces.Factories.ViewModels; using ByteSync.Interfaces.Services.Localizations; using ByteSync.Interfaces.Services.Sessions; using ByteSync.Models.Comparisons.Result; +using ByteSync.Models.Inventories; +using ByteSync.Services; using ByteSync.ViewModels.Sessions.Comparisons.Actions; using FluentAssertions; +using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; @@ -28,6 +36,7 @@ public class SynchronizationRuleGlobalViewModelTests private Mock _localizationService = null!; private Mock _actionEditViewModelFactory = null!; private Mock _synchronizationRulesService = null!; + private Mock> _logger = null!; private Subject _cultureSubject = null!; @@ -39,6 +48,7 @@ public void SetUp() _localizationService = new Mock(MockBehavior.Strict); _actionEditViewModelFactory = new Mock(MockBehavior.Strict); _synchronizationRulesService = new Mock(MockBehavior.Strict); + _logger = new Mock>(); _cultureSubject = new Subject(); @@ -51,6 +61,128 @@ private void SetupSession(DataTypes dataType) _sessionService.SetupGet(s => s.CurrentSessionSettings).Returns(new SessionSettings { DataType = dataType }); } + private sealed class TestDataPartIndexer : IDataPartIndexer + { + private readonly ReadOnlyCollection _dataParts; + + public TestDataPartIndexer(IReadOnlyCollection dataParts) + { + _dataParts = new ReadOnlyCollection(dataParts.ToList()); + } + + public void BuildMap(List inventories) + { + } + + public ReadOnlyCollection GetAllDataParts() + { + return _dataParts; + } + + public DataPart? GetDataPart(string? dataPartName) + { + return _dataParts.FirstOrDefault(dp => dp.Name == dataPartName); + } + + public void Remap(ICollection synchronizationRules) + { + } + } + + private static TestDataPartIndexer BuildDataPartIndexer() + { + var endpoint = new ByteSyncEndpoint + { + ClientId = "c", + ClientInstanceId = "ci", + Version = "v", + OSPlatform = OSPlatforms.Windows, + IpAddress = "127.0.0.1" + }; + + var inventoryA = new Inventory { InventoryId = "INV_A", Code = "A", Endpoint = endpoint, MachineName = "M" }; + var inventoryB = new Inventory { InventoryId = "INV_B", Code = "B", Endpoint = endpoint, MachineName = "M" }; + var partA = new InventoryPart(inventoryA, "c:\\a", FileSystemTypes.Directory) { Code = "A1" }; + var partB = new InventoryPart(inventoryB, "c:\\b", FileSystemTypes.Directory) { Code = "B1" }; + + var dataParts = new List + { + new("A", partA), + new("B", partB) + }; + + return new TestDataPartIndexer(dataParts); + } + + private static AtomicConditionEditViewModel BuildValidConditionViewModel(IDataPartIndexer dataPartIndexer) + { + var conditionVm = new AtomicConditionEditViewModel(FileSystemTypes.File, dataPartIndexer); + + var sourceOrProperties = (IEnumerable)GetInternalProperty(conditionVm, "SourceOrProperties"); + var comparisonOperators = (IEnumerable)GetInternalProperty(conditionVm, "ComparisonOperators"); + var destinations = (IEnumerable)GetInternalProperty(conditionVm, "ConditionDestinations"); + + SetInternalProperty(conditionVm, "SelectedSourceOrProperty", FirstWhereBoolProperty(sourceOrProperties, "IsDataPart", true)); + SetInternalProperty(conditionVm, "SelectedComparisonOperator", FirstItem(comparisonOperators)); + SetInternalProperty(conditionVm, "SelectedDestination", FirstWhereBoolProperty(destinations, "IsVirtual", false)); + + return conditionVm; + } + + private static AtomicActionEditViewModel BuildValidActionViewModel(IDataPartIndexer dataPartIndexer) + { + var actionVm = new AtomicActionEditViewModel(FileSystemTypes.File, true, null, dataPartIndexer); + + var actions = (IEnumerable)GetInternalProperty(actionVm, "Actions"); + var sources = (IEnumerable)GetInternalProperty(actionVm, "Sources"); + + SetInternalProperty(actionVm, "SelectedAction", FirstItem(actions)); + SetInternalProperty(actionVm, "SelectedSource", FirstItem(sources)); + + var destinations = (IEnumerable)GetInternalProperty(actionVm, "Destinations"); + SetInternalProperty(actionVm, "SelectedDestination", FirstItem(destinations)); + + return actionVm; + } + + private static object GetInternalProperty(object target, string propertyName) + { + var property = target.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic); + + return property!.GetValue(target)!; + } + + private static void SetInternalProperty(object target, string propertyName, object? value) + { + var property = target.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic); + property!.SetValue(target, value); + } + + private static object FirstItem(IEnumerable items) + { + foreach (var item in items) + { + return item!; + } + + throw new InvalidOperationException("Empty collection"); + } + + private static object FirstWhereBoolProperty(IEnumerable items, string propertyName, bool expectedValue) + { + foreach (var item in items) + { + var property = item!.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (property != null && property.PropertyType == typeof(bool) + && (bool)property.GetValue(item)! == expectedValue) + { + return item; + } + } + + throw new InvalidOperationException($"No item found with {propertyName}={expectedValue}"); + } + // Test doubles to surface protected helpers private class TestAtomicActionEditViewModel : AtomicActionEditViewModel { @@ -95,6 +227,7 @@ public void Constructor_WithFilesDirectories_ShouldInitDefaults_AndAddOneOfEach( _localizationService.Object, _actionEditViewModelFactory.Object, _synchronizationRulesService.Object, + _logger.Object, null, false); @@ -129,6 +262,7 @@ public void Constructor_WithDirectories_ShouldSelectDirectory_AndHideTypeSelecti _localizationService.Object, _actionEditViewModelFactory.Object, _synchronizationRulesService.Object, + _logger.Object, null, false); @@ -150,6 +284,7 @@ public void ChangingConditionMode_ShouldUpdateTrailingText() _localizationService.Object, _actionEditViewModelFactory.Object, _synchronizationRulesService.Object, + _logger.Object, null, false); @@ -175,6 +310,7 @@ public void AddCommands_ShouldCreateNewAtomicVMs() _localizationService.Object, _actionEditViewModelFactory.Object, _synchronizationRulesService.Object, + _logger.Object, null, false); @@ -200,6 +336,7 @@ public void RemoveRequested_ShouldRemoveItems_FromCollections() _localizationService.Object, _actionEditViewModelFactory.Object, _synchronizationRulesService.Object, + _logger.Object, null, false); @@ -228,6 +365,7 @@ public void Save_WithMissingFields_ShouldShowWarning_AndNotPersist() _localizationService.Object, _actionEditViewModelFactory.Object, _synchronizationRulesService.Object, + _logger.Object, null, false); @@ -241,6 +379,57 @@ public void Save_WithMissingFields_ShouldShowWarning_AndNotPersist() _dialogService.Verify(d => d.CloseFlyout(), Times.Never); } + [Test] + public void Save_WithValidFields_ShouldPersistAndLogSuccess() + { + SetupSession(DataTypes.FilesDirectories); + _localizationService.Setup(l => l[It.IsAny()]).Returns("unit"); + + var containerBuilder = new ContainerBuilder(); + containerBuilder.RegisterInstance(_localizationService.Object).As(); + ContainerProvider.Container = containerBuilder.Build(); + + var dataPartIndexer = BuildDataPartIndexer(); + var conditionVm = BuildValidConditionViewModel(dataPartIndexer); + var actionVm = BuildValidActionViewModel(dataPartIndexer); + + _actionEditViewModelFactory + .Setup(f => f.BuildAtomicConditionEditViewModel(It.IsAny(), It.IsAny())) + .Returns(conditionVm); + + _actionEditViewModelFactory + .Setup(f => f.BuildAtomicActionEditViewModel(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny?>())) + .Returns(actionVm); + + _synchronizationRulesService + .Setup(s => s.AddOrUpdateSynchronizationRule(It.IsAny())); + + _dialogService.Setup(d => d.CloseFlyout()); + + var vm = new SynchronizationRuleGlobalViewModel( + _dialogService.Object, + _sessionService.Object, + _localizationService.Object, + _actionEditViewModelFactory.Object, + _synchronizationRulesService.Object, + _logger.Object, + null, + false); + + vm.SaveCommand.Execute().Subscribe(); + + _synchronizationRulesService.Verify(s => s.AddOrUpdateSynchronizationRule(It.IsAny()), Times.Once); + _dialogService.Verify(d => d.CloseFlyout(), Times.Once); + _logger.Verify(l => l.Log( + LogLevel.Information, + It.IsAny(), + It.Is((state, _) => state.ToString()!.Contains("Synchronization rule saved.")), + It.IsAny(), + It.IsAny>()), + Times.Once); + } + [Test] public void SelectedFileSystemType_Change_ShouldResetCollections() { @@ -254,6 +443,7 @@ public void SelectedFileSystemType_Change_ShouldResetCollections() _localizationService.Object, _actionEditViewModelFactory.Object, _synchronizationRulesService.Object, + _logger.Object, null, false); @@ -297,6 +487,7 @@ public void Reset_WithBaseRule_ShouldRebuildFromRule() _localizationService.Object, _actionEditViewModelFactory.Object, _synchronizationRulesService.Object, + _logger.Object, baseRule, false); @@ -322,6 +513,7 @@ public void Cancel_ShouldCloseFlyout_AndReset() _localizationService.Object, _actionEditViewModelFactory.Object, _synchronizationRulesService.Object, + _logger.Object, null, false); @@ -350,6 +542,7 @@ public void WhenActivated_ShouldSubscribeToCultureChanges() _localizationService.Object, _actionEditViewModelFactory.Object, _synchronizationRulesService.Object, + _logger.Object, null, false); diff --git a/tests/ByteSync.Client.UnitTests/ViewModels/Sessions/Comparisons/Actions/TargetedActionGlobalViewModelTests.cs b/tests/ByteSync.Client.UnitTests/ViewModels/Sessions/Comparisons/Actions/TargetedActionGlobalViewModelTests.cs index 633d6e41..b74bfc27 100644 --- a/tests/ByteSync.Client.UnitTests/ViewModels/Sessions/Comparisons/Actions/TargetedActionGlobalViewModelTests.cs +++ b/tests/ByteSync.Client.UnitTests/ViewModels/Sessions/Comparisons/Actions/TargetedActionGlobalViewModelTests.cs @@ -1,3 +1,5 @@ +using System.Collections; +using System.Collections.ObjectModel; using System.Reactive; using System.Reactive.Linq; using System.Reactive.Subjects; @@ -7,15 +9,21 @@ using ByteSync.Business.Comparisons; using ByteSync.Business.Inventories; using ByteSync.Client.UnitTests.Helpers; +using ByteSync.Common.Business.Actions; +using ByteSync.Common.Business.EndPoints; using ByteSync.Common.Business.Inventories; +using ByteSync.Common.Business.Misc; using ByteSync.Interfaces.Controls.Comparisons; using ByteSync.Interfaces.Dialogs; using ByteSync.Interfaces.Factories.ViewModels; using ByteSync.Interfaces.Services.Localizations; +using ByteSync.Interfaces.Services.Sessions; using ByteSync.Models.Comparisons.Result; +using ByteSync.Models.Inventories; using ByteSync.TestsCommon; using ByteSync.ViewModels.Sessions.Comparisons.Actions; using FluentAssertions; +using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; @@ -30,6 +38,7 @@ public class TargetedActionGlobalViewModelTests : AbstractTester private Mock _mockAtomicActionConsistencyChecker = null!; private Mock _mockActionEditViewModelFactory = null!; private Mock _mockFailureReasonService = null!; + private Mock> _mockLogger = null!; private Subject _cultureSubject = null!; private List _comparisonItems = null!; @@ -42,6 +51,7 @@ public void SetUp() _mockAtomicActionConsistencyChecker = new Mock(); _mockActionEditViewModelFactory = new Mock(); _mockFailureReasonService = new Mock(); + _mockLogger = new Mock>(); _cultureSubject = new Subject(); // Setup basic mocks @@ -69,10 +79,10 @@ public void SetUp() .Returns(mockActionEditViewModel.Object); } - private ComparisonItem CreateMockComparisonItem(FileSystemTypes fileSystemType) + private ComparisonItem CreateMockComparisonItem(FileSystemTypes fileSystemType, string linkingKey = "test-file") { // Create a real ComparisonItem instance since it cannot be mocked (no parameterless constructor) - var pathIdentity = new PathIdentity(fileSystemType, "test-file", "test-file", "test-file"); + var pathIdentity = new PathIdentity(fileSystemType, linkingKey, linkingKey, linkingKey); var comparisonItem = new ComparisonItem(pathIdentity); return comparisonItem; @@ -89,6 +99,7 @@ public void Constructor_ShouldInitializeProperties() _mockTargetedActionsService.Object, _mockAtomicActionConsistencyChecker.Object, _mockActionEditViewModelFactory.Object, + _mockLogger.Object, _mockFailureReasonService.Object ); @@ -124,6 +135,7 @@ public void AddAction_ShouldCallActionEditViewModelFactory() _mockTargetedActionsService.Object, _mockAtomicActionConsistencyChecker.Object, _mockActionEditViewModelFactory.Object, + _mockLogger.Object, _mockFailureReasonService.Object ); @@ -147,6 +159,7 @@ public void OnLocaleChanged_ShouldUpdateLocalizedMessages() _mockTargetedActionsService.Object, _mockAtomicActionConsistencyChecker.Object, _mockActionEditViewModelFactory.Object, + _mockLogger.Object, _mockFailureReasonService.Object ); @@ -178,6 +191,7 @@ public async Task OnLocaleChanged_WithFailureSummaries_ShouldUpdateLocalizedMess _mockTargetedActionsService.Object, _mockAtomicActionConsistencyChecker.Object, _mockActionEditViewModelFactory.Object, + _mockLogger.Object, _mockFailureReasonService.Object ); @@ -186,7 +200,7 @@ public async Task OnLocaleChanged_WithFailureSummaries_ShouldUpdateLocalizedMess var failureSummary = new ValidationFailureSummary { - Reason = AtomicActionValidationFailureReason.InvalidSourceCount, + Reason = AtomicActionValidationFailureReason.SourceMissing, Count = 2, LocalizedMessage = "Old message", AffectedItems = [] @@ -194,7 +208,7 @@ public async Task OnLocaleChanged_WithFailureSummaries_ShouldUpdateLocalizedMess viewModel.FailureSummaries.Add(failureSummary); // Set up the new localized message before triggering the culture change - _mockFailureReasonService.Setup(x => x.GetLocalizedMessage(AtomicActionValidationFailureReason.InvalidSourceCount)) + _mockFailureReasonService.Setup(x => x.GetLocalizedMessage(AtomicActionValidationFailureReason.SourceMissing)) .Returns("New localized message"); // Act @@ -205,7 +219,7 @@ public async Task OnLocaleChanged_WithFailureSummaries_ShouldUpdateLocalizedMess // Assert viewModel.FailureSummaries[0].LocalizedMessage.Should().Be("New localized message"); - _mockFailureReasonService.Verify(x => x.GetLocalizedMessage(AtomicActionValidationFailureReason.InvalidSourceCount), Times.Once); + _mockFailureReasonService.Verify(x => x.GetLocalizedMessage(AtomicActionValidationFailureReason.SourceMissing), Times.Once); } [Test] @@ -219,12 +233,13 @@ public void ResetWarning_ShouldClearAllWarningProperties() _mockTargetedActionsService.Object, _mockAtomicActionConsistencyChecker.Object, _mockActionEditViewModelFactory.Object, + _mockLogger.Object, _mockFailureReasonService.Object ); viewModel.FailureSummaries.Add(new ValidationFailureSummary { - Reason = AtomicActionValidationFailureReason.InvalidSourceCount, + Reason = AtomicActionValidationFailureReason.SourceHasMultipleIdentities, Count = 1, LocalizedMessage = "Test", AffectedItems = [] @@ -255,6 +270,7 @@ public void ShowConsistencyWarning_WithValidAndInvalidItems_ShouldSetCorrectProp _mockTargetedActionsService.Object, _mockAtomicActionConsistencyChecker.Object, _mockActionEditViewModelFactory.Object, + _mockLogger.Object, _mockFailureReasonService.Object ); @@ -262,14 +278,15 @@ public void ShowConsistencyWarning_WithValidAndInvalidItems_ShouldSetCorrectProp var result = new AtomicActionConsistencyCheckCanAddResult(_comparisonItems); result.ValidationResults.Add(new ComparisonItemValidationResult(_comparisonItems[0], true)); // Valid result.ValidationResults.Add(new ComparisonItemValidationResult(_comparisonItems[1], - AtomicActionValidationFailureReason.InvalidSourceCount)); // Invalid + AtomicActionValidationFailureReason.SourceMissing)); // Invalid + var atomicAction = new AtomicAction { Operator = ActionOperatorTypes.CopyContentOnly }; // Use reflection to access private method var showConsistencyWarningMethod = typeof(TargetedActionGlobalViewModel) .GetMethod("ShowConsistencyWarning", BindingFlags.NonPublic | BindingFlags.Instance); // Act - showConsistencyWarningMethod?.Invoke(viewModel, [result]); + showConsistencyWarningMethod?.Invoke(viewModel, [atomicAction, result]); // Assert viewModel.ShowSaveValidItemsCommand.Should().BeTrue(); @@ -280,7 +297,7 @@ public void ShowConsistencyWarning_WithValidAndInvalidItems_ShouldSetCorrectProp viewModel.AreMissingFields.Should().BeFalse(); viewModel.FailureSummaries.Should().HaveCount(1); - viewModel.FailureSummaries[0].Reason.Should().Be(AtomicActionValidationFailureReason.InvalidSourceCount); + viewModel.FailureSummaries[0].Reason.Should().Be(AtomicActionValidationFailureReason.SourceMissing); viewModel.FailureSummaries[0].Count.Should().Be(1); viewModel.FailureSummaries[0].LocalizedMessage.Should().Be("Test failure message"); viewModel.FailureSummaries[0].AffectedItems.Should().HaveCount(1); @@ -297,22 +314,24 @@ public void ShowConsistencyWarning_WithNoValidItems_ShouldSetNoValidItemsFlag() _mockTargetedActionsService.Object, _mockAtomicActionConsistencyChecker.Object, _mockActionEditViewModelFactory.Object, + _mockLogger.Object, _mockFailureReasonService.Object ); // Create real instance instead of mock - all items fail validation var result = new AtomicActionConsistencyCheckCanAddResult(_comparisonItems); result.ValidationResults.Add(new ComparisonItemValidationResult(_comparisonItems[0], - AtomicActionValidationFailureReason.InvalidSourceCount)); // Invalid + AtomicActionValidationFailureReason.SourceHasMultipleIdentities)); // Invalid result.ValidationResults.Add(new ComparisonItemValidationResult(_comparisonItems[1], - AtomicActionValidationFailureReason.InvalidSourceCount)); // Invalid + AtomicActionValidationFailureReason.SourceHasMultipleIdentities)); // Invalid + var atomicAction = new AtomicAction { Operator = ActionOperatorTypes.CopyContentOnly }; // Use reflection to access private method var showConsistencyWarningMethod = typeof(TargetedActionGlobalViewModel) .GetMethod("ShowConsistencyWarning", BindingFlags.NonPublic | BindingFlags.Instance); // Act - showConsistencyWarningMethod?.Invoke(viewModel, [result]); + showConsistencyWarningMethod?.Invoke(viewModel, [atomicAction, result]); // Assert viewModel.ShowSaveValidItemsCommand.Should().BeFalse(); @@ -333,17 +352,19 @@ public void ShowConsistencyWarning_WithMultipleFailureReasons_ShouldGroupByReaso _mockTargetedActionsService.Object, _mockAtomicActionConsistencyChecker.Object, _mockActionEditViewModelFactory.Object, + _mockLogger.Object, _mockFailureReasonService.Object ); // Create real instance instead of mock with multiple failure reasons var result = new AtomicActionConsistencyCheckCanAddResult(_comparisonItems); result.ValidationResults.Add(new ComparisonItemValidationResult(_comparisonItems[0], - AtomicActionValidationFailureReason.InvalidSourceCount)); // First InvalidSourceCount + AtomicActionValidationFailureReason.SourceMissing)); // First SourceMissing result.ValidationResults.Add(new ComparisonItemValidationResult(_comparisonItems[1], AtomicActionValidationFailureReason.CreateOperationOnFileNotAllowed)); // Different reason result.ValidationResults.Add(new ComparisonItemValidationResult(_comparisonItems[0], - AtomicActionValidationFailureReason.InvalidSourceCount)); // Duplicate InvalidSourceCount + AtomicActionValidationFailureReason.SourceMissing)); // Duplicate SourceMissing + var atomicAction = new AtomicAction { Operator = ActionOperatorTypes.CopyContentOnly }; _mockFailureReasonService.Setup(x => x.GetLocalizedMessage(AtomicActionValidationFailureReason.CreateOperationOnFileNotAllowed)) .Returns("Cannot create files"); @@ -353,14 +374,14 @@ public void ShowConsistencyWarning_WithMultipleFailureReasons_ShouldGroupByReaso .GetMethod("ShowConsistencyWarning", BindingFlags.NonPublic | BindingFlags.Instance); // Act - showConsistencyWarningMethod?.Invoke(viewModel, [result]); + showConsistencyWarningMethod?.Invoke(viewModel, [atomicAction, result]); // Assert viewModel.FailureSummaries.Should().HaveCount(2); // Should be ordered by count (most frequent first) - viewModel.FailureSummaries[0].Count.Should().Be(2); // InvalidSourceCount appears twice - viewModel.FailureSummaries[0].Reason.Should().Be(AtomicActionValidationFailureReason.InvalidSourceCount); + viewModel.FailureSummaries[0].Count.Should().Be(2); // SourceMissing appears twice + viewModel.FailureSummaries[0].Reason.Should().Be(AtomicActionValidationFailureReason.SourceMissing); viewModel.FailureSummaries[1].Count.Should().Be(1); // CreateOperationOnFileNotAllowed appears once viewModel.FailureSummaries[1].Reason.Should().Be(AtomicActionValidationFailureReason.CreateOperationOnFileNotAllowed); @@ -378,12 +399,13 @@ public void ShowMissingFieldsWarning_ShouldClearFailureSummariesAndSetMissingFie _mockTargetedActionsService.Object, _mockAtomicActionConsistencyChecker.Object, _mockActionEditViewModelFactory.Object, + _mockLogger.Object, _mockFailureReasonService.Object ); viewModel.FailureSummaries.Add(new ValidationFailureSummary { - Reason = AtomicActionValidationFailureReason.InvalidSourceCount, + Reason = AtomicActionValidationFailureReason.SourceMissing, Count = 1, LocalizedMessage = "Test", AffectedItems = [] @@ -414,6 +436,7 @@ public void Reset_ShouldCallActionEditViewModelFactory() _mockTargetedActionsService.Object, _mockAtomicActionConsistencyChecker.Object, _mockActionEditViewModelFactory.Object, + _mockLogger.Object, _mockFailureReasonService.Object ); @@ -437,6 +460,7 @@ public void Cancel_ShouldCallActionEditViewModelFactory() _mockTargetedActionsService.Object, _mockAtomicActionConsistencyChecker.Object, _mockActionEditViewModelFactory.Object, + _mockLogger.Object, _mockFailureReasonService.Object ); @@ -460,6 +484,7 @@ public void WhenActivated_ShouldSubscribeToCultureChanges() _mockTargetedActionsService.Object, _mockAtomicActionConsistencyChecker.Object, _mockActionEditViewModelFactory.Object, + _mockLogger.Object, _mockFailureReasonService.Object ); @@ -481,6 +506,7 @@ public void WhenDeactivated_ShouldDisposeSubscriptions() _mockTargetedActionsService.Object, _mockAtomicActionConsistencyChecker.Object, _mockActionEditViewModelFactory.Object, + _mockLogger.Object, _mockFailureReasonService.Object ); @@ -526,4 +552,155 @@ public void ValidationFailureSummary_AffectedItemsTooltip_ShouldGenerateCorrectT // When FileName is null, it should use LinkingKeyValue tooltip.Should().Be("file1.txt\ndirectory1"); } + + [Test] + public void Save_WithValidAction_ShouldLogConsistencySuccess() + { + var comparisonItems = new List + { + CreateMockComparisonItem(FileSystemTypes.File, "file1"), + CreateMockComparisonItem(FileSystemTypes.File, "file2") + }; + + var dataPartIndexer = BuildDataPartIndexer(); + var actionEditViewModel = new AtomicActionEditViewModel(FileSystemTypes.File, false, comparisonItems, dataPartIndexer); + + _mockActionEditViewModelFactory.Setup(x => x.BuildAtomicActionEditViewModel( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + .Returns(actionEditViewModel); + + var viewModel = new TargetedActionGlobalViewModel( + comparisonItems, + _mockDialogService.Object, + _mockLocalizationService.Object, + _mockTargetedActionsService.Object, + _mockAtomicActionConsistencyChecker.Object, + _mockActionEditViewModelFactory.Object, + _mockLogger.Object, + _mockFailureReasonService.Object + ); + + ConfigureValidAction(actionEditViewModel); + + var result = new AtomicActionConsistencyCheckCanAddResult(comparisonItems); + _mockAtomicActionConsistencyChecker.Setup(x => x.CheckCanAdd(It.IsAny(), comparisonItems)) + .Returns(result); + + viewModel.SaveCommand.Execute(Unit.Default).Subscribe(); + + _mockLogger.Verify( + x => x.Log( + LogLevel.Information, + It.IsAny(), + It.Is((v, t) => + v.ToString()!.Contains("Targeted action created.") + && v.ToString()!.Contains("Items=file1, file2")), + It.IsAny(), + It.IsAny>()), + Times.Once); + } + + private static void ConfigureValidAction(AtomicActionEditViewModel actionEditViewModel) + { + var actions = GetInternalEnumerable(actionEditViewModel, "Actions"); + var sources = GetInternalEnumerable(actionEditViewModel, "Sources"); + var destinations = GetInternalEnumerable(actionEditViewModel, "Destinations"); + + var selectedAction = GetActionByOperator(actions, ActionOperatorTypes.Copy); + + SetInternalProperty(actionEditViewModel, "SelectedAction", selectedAction); + SetInternalProperty(actionEditViewModel, "SelectedSource", FirstItem(sources)); + SetInternalProperty(actionEditViewModel, "SelectedDestination", FirstItem(destinations)); + } + + private sealed class TestDataPartIndexer : IDataPartIndexer + { + private readonly ReadOnlyCollection _dataParts; + + public TestDataPartIndexer(IReadOnlyCollection dataParts) + { + _dataParts = new ReadOnlyCollection(dataParts.ToList()); + } + + public void BuildMap(List inventories) + { + } + + public ReadOnlyCollection GetAllDataParts() + { + return _dataParts; + } + + public DataPart? GetDataPart(string? dataPartName) + { + return _dataParts.FirstOrDefault(dp => dp.Name == dataPartName); + } + + public void Remap(ICollection synchronizationRules) + { + } + } + + private static IDataPartIndexer BuildDataPartIndexer() + { + var endpoint = new ByteSyncEndpoint + { + ClientId = "c", + ClientInstanceId = "ci", + Version = "v", + OSPlatform = OSPlatforms.Windows, + IpAddress = "127.0.0.1" + }; + + var inventoryA = new Inventory { InventoryId = "INV_A", Code = "A", Endpoint = endpoint, MachineName = "M" }; + var inventoryB = new Inventory { InventoryId = "INV_B", Code = "B", Endpoint = endpoint, MachineName = "M" }; + var partA = new InventoryPart(inventoryA, "c:\\a", FileSystemTypes.Directory) { Code = "A1" }; + var partB = new InventoryPart(inventoryB, "c:\\b", FileSystemTypes.Directory) { Code = "B1" }; + + var dataParts = new List + { + new("A", partA), + new("B", partB) + }; + + return new TestDataPartIndexer(dataParts); + } + + private static IEnumerable GetInternalEnumerable(object target, string propertyName) + { + var property = target.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic); + + return (IEnumerable)property!.GetValue(target)!; + } + + private static object FirstItem(IEnumerable items) + { + foreach (var item in items) + { + return item!; + } + + throw new InvalidOperationException("Empty collection"); + } + + private static void SetInternalProperty(object target, string propertyName, object? value) + { + var property = target.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic); + property!.SetValue(target, value); + } + + private static object GetActionByOperator(IEnumerable actions, ActionOperatorTypes operatorType) + { + foreach (var action in actions) + { + var property = action!.GetType().GetProperty("ActionOperatorType", BindingFlags.Instance | BindingFlags.Public); + var value = (ActionOperatorTypes)property!.GetValue(action)!; + if (value == operatorType) + { + return action; + } + } + + throw new InvalidOperationException("Action not found"); + } } \ No newline at end of file diff --git a/tests/ByteSync.Client.UnitTests/ViewModels/Sessions/Synchronizations/SynchronizationConfirmationViewModelTests.cs b/tests/ByteSync.Client.UnitTests/ViewModels/Sessions/Synchronizations/SynchronizationConfirmationViewModelTests.cs index 6eae01f1..66deca38 100644 --- a/tests/ByteSync.Client.UnitTests/ViewModels/Sessions/Synchronizations/SynchronizationConfirmationViewModelTests.cs +++ b/tests/ByteSync.Client.UnitTests/ViewModels/Sessions/Synchronizations/SynchronizationConfirmationViewModelTests.cs @@ -67,7 +67,7 @@ public void Constructor_ShouldGroupActionsByDestination() { CreateActionWithTarget("1", "client1", "node1", ActionOperatorTypes.Create), CreateActionWithTarget("2", "client1", "node1", ActionOperatorTypes.Delete), - CreateActionWithTarget("3", "client2", "node2", ActionOperatorTypes.SynchronizeContentOnly) + CreateActionWithTarget("3", "client2", "node2", ActionOperatorTypes.CopyContentOnly) }; SetupDataNodes( @@ -89,8 +89,8 @@ public void Constructor_ShouldComputeCorrectActionCounts() { CreateActionWithTarget("1", "client1", "node1", ActionOperatorTypes.Create), CreateActionWithTarget("2", "client1", "node1", ActionOperatorTypes.Create), - CreateActionWithTarget("3", "client1", "node1", ActionOperatorTypes.SynchronizeContentOnly), - CreateActionWithTarget("4", "client1", "node1", ActionOperatorTypes.SynchronizeDate), + CreateActionWithTarget("3", "client1", "node1", ActionOperatorTypes.CopyContentOnly), + CreateActionWithTarget("4", "client1", "node1", ActionOperatorTypes.CopyDatesOnly), CreateActionWithTarget("5", "client1", "node1", ActionOperatorTypes.Delete) }; diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/SynchronizationRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/SynchronizationRepositoryTests.cs index 044b4133..d7cc1eff 100644 --- a/tests/ByteSync.ServerCommon.Tests/Repositories/SynchronizationRepositoryTests.cs +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/SynchronizationRepositoryTests.cs @@ -18,7 +18,7 @@ public class SynchronizationRepositoryTests private CacheRepository _synchronizationEntityCacheRepository; private RedisInfrastructureService _redisInfrastructureService; private CacheRepository _trackingActionEntityCacheRepository; - + [SetUp] public void SetUp() { @@ -27,19 +27,19 @@ public void SetUp() var loggerFactoryMock = A.Fake(); _redisInfrastructureService = new RedisInfrastructureService( - Options.Create(redisSettings), - cacheKeyFactory, + Options.Create(redisSettings), + cacheKeyFactory, loggerFactoryMock); - + _synchronizationEntityCacheRepository = new CacheRepository(_redisInfrastructureService); _trackingActionEntityCacheRepository = new CacheRepository(_redisInfrastructureService); _repository = new SynchronizationRepository( - _redisInfrastructureService, - _synchronizationEntityCacheRepository, + _redisInfrastructureService, + _synchronizationEntityCacheRepository, _trackingActionEntityCacheRepository); } - + [Test] public async Task AddSynchronization_IntegrationTest() @@ -53,10 +53,10 @@ public async Task AddSynchronization_IntegrationTest() new() { ActionsGroupId = "group1" }, new() { ActionsGroupId = "group2" } }; - + // Act await _repository.AddSynchronization(synchronizationEntity, actionsGroupDefinitions); - + // Assert var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.Synchronization, sessionId); var savedEntity = await _synchronizationEntityCacheRepository.Get(cacheKey); @@ -64,47 +64,49 @@ public async Task AddSynchronization_IntegrationTest() savedEntity.Should().NotBeNull(); savedEntity.Should().BeEquivalentTo(synchronizationEntity); } - + [Test] public async Task AddSynchronization_ShouldSet_SourceClientInstanceId_BasedOn_IsInitialOperatingOnSourceNeeded() { // Arrange string sessionId = "testSession_" + DateTime.Now.Ticks; var synchronizationEntity = new SynchronizationEntity { SessionId = sessionId }; - + var actionsGroupDefinitions = new List { new() { ActionsGroupId = "group_source_needed", - Operator = ActionOperatorTypes.SynchronizeContentOnly, + Operator = ActionOperatorTypes.CopyContentOnly, SourceClientInstanceId = "source-client-1", - TargetClientInstanceAndNodeIds = new List { new ClientInstanceIdAndNodeId { ClientInstanceId = "t1", NodeId = "n1" } } + TargetClientInstanceAndNodeIds = new List + { new ClientInstanceIdAndNodeId { ClientInstanceId = "t1", NodeId = "n1" } } }, new() { ActionsGroupId = "group_source_not_needed", - Operator = ActionOperatorTypes.SynchronizeDate, + Operator = ActionOperatorTypes.CopyDatesOnly, SourceClientInstanceId = "source-client-2", - TargetClientInstanceAndNodeIds = new List { new ClientInstanceIdAndNodeId { ClientInstanceId = "t2", NodeId = "n2" } } + TargetClientInstanceAndNodeIds = new List + { new ClientInstanceIdAndNodeId { ClientInstanceId = "t2", NodeId = "n2" } } } }; - + // Act await _repository.AddSynchronization(synchronizationEntity, actionsGroupDefinitions); - + // Assert var cacheKey1 = _redisInfrastructureService.ComputeCacheKey(EntityType.TrackingAction, $"{sessionId}_group_source_needed"); var trackingEntity1 = await _trackingActionEntityCacheRepository.Get(cacheKey1); trackingEntity1.Should().NotBeNull(); trackingEntity1!.SourceClientInstanceId.Should().Be("source-client-1"); - + var cacheKey2 = _redisInfrastructureService.ComputeCacheKey(EntityType.TrackingAction, $"{sessionId}_group_source_not_needed"); var trackingEntity2 = await _trackingActionEntityCacheRepository.Get(cacheKey2); trackingEntity2.Should().NotBeNull(); trackingEntity2!.SourceClientInstanceId.Should().BeNull(); } - + [Test] public async Task ResetSession_IntegrationTest() { @@ -114,16 +116,16 @@ public async Task ResetSession_IntegrationTest() var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.Synchronization, sessionId); await _synchronizationEntityCacheRepository.Save(cacheKey, synchronizationEntity); - + // Verify entity exists before resetting var entityBeforeReset = await _synchronizationEntityCacheRepository.Get(cacheKey); entityBeforeReset.Should().NotBeNull(); - + // Act await _repository.ResetSession(sessionId); - + // Assert var entityAfterReset = await _synchronizationEntityCacheRepository.Get(cacheKey); entityAfterReset.Should().BeNull(); } -} +} \ No newline at end of file diff --git a/tests/ByteSync.TestsCommon/TestFileSystemUtils.cs b/tests/ByteSync.TestsCommon/TestFileSystemUtils.cs index e8869af5..45c5a6e5 100644 --- a/tests/ByteSync.TestsCommon/TestFileSystemUtils.cs +++ b/tests/ByteSync.TestsCommon/TestFileSystemUtils.cs @@ -5,45 +5,48 @@ namespace ByteSync.TestsCommon; public static class TestFileSystemUtils { private static Random _random = new Random(); - - public static FileInfo CreateSubTestFile(DirectoryInfo rootDirectory, string relativeFilePath, string contents, DateTime? lastWriteTime = null, DateTime? lastWriteTimeUtc = null) + + public static FileInfo CreateSubTestFile(DirectoryInfo rootDirectory, string relativeFilePath, string content, + DateTime? lastWriteTime = null, DateTime? lastWriteTimeUtc = null) { relativeFilePath = relativeFilePath.Replace("/", Path.DirectorySeparatorChar.ToString()); - - string fileFullName = IOUtils.Combine(rootDirectory.FullName, relativeFilePath); - - FileInfo fileInfo = new FileInfo(fileFullName); - fileInfo.Directory.Create(); - - File.WriteAllText(fileFullName, contents); - + + var fileFullName = IOUtils.Combine(rootDirectory.FullName, relativeFilePath); + + var fileInfo = new FileInfo(fileFullName); + fileInfo.Directory!.Create(); + + File.WriteAllText(fileFullName, content); + fileInfo.Refresh(); if (lastWriteTime != null) { fileInfo.LastWriteTime = lastWriteTime.Value; } + if (lastWriteTimeUtc != null) { fileInfo.LastWriteTimeUtc = lastWriteTimeUtc.Value; } - + return fileInfo; } - + public static DirectoryInfo CreateSubTestDirectory(DirectoryInfo rootDirectory, string relativeDirectoryPath) { relativeDirectoryPath = relativeDirectoryPath.Replace("/", Path.DirectorySeparatorChar.ToString()); - - string directoryFullName = IOUtils.Combine(rootDirectory.FullName, relativeDirectoryPath); - + + var directoryFullName = IOUtils.Combine(rootDirectory.FullName, relativeDirectoryPath); + var result = Directory.CreateDirectory(directoryFullName); - + return result; } - + public static string GenerateRandomTextContent(int length) { const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + return new string(Enumerable.Repeat(chars, length) .Select(s => s[_random.Next(s.Length)]).ToArray()); }