diff --git a/.editorconfig b/.editorconfig index 672edebf..5bfe4778 100644 --- a/.editorconfig +++ b/.editorconfig @@ -34,6 +34,75 @@ dotnet_diagnostic.IDE0066.severity = none # IDE0090: Use 'new(...)' dotnet_diagnostic.IDE0090.severity = none +# MA0002: IEqualityComparer or IComparer is missing +dotnet_diagnostic.MA0002.severity = none + +# MA0003: Add argument name to improve readability +dotnet_diagnostic.MA0003.severity = none + +# MA0004: Use Task.ConfigureAwait(false) +dotnet_diagnostic.MA0004.severity = none + +# MA0006: Use String.Equals instead of equality operator +dotnet_diagnostic.MA0006.severity = none + +# MA0007: Add a comma after the last value +dotnet_diagnostic.MA0007.severity = none + +# MA0008: Add StructLayoutAttribute +dotnet_diagnostic.MA0008.severity = none + +# MA0009: Add regex evaluation timeout +dotnet_diagnostic.MA0009.severity = none + +# MA0016: Prefer return collection abstraction instead of implementation +dotnet_diagnostic.MA0016.severity = none + +# MA0018: Do not declare static members on generic types +dotnet_diagnostic.MA0018.severity = none + +# MA0025: Implement the functionality instead of throwing NotImplementedException +dotnet_diagnostic.MA0025.severity = none + +# MA0028: Optimize StringBuilder usage +dotnet_diagnostic.MA0028.severity = none + +# MA0029: Combine LINQ methods +dotnet_diagnostic.MA0029.severity = none + +# MA0031: Optimize Enumerable.Count() usage +dotnet_diagnostic.MA0031.severity = none + +# MA0036: Make class static +dotnet_diagnostic.MA0036.severity = none + +# MA0038: Make method static +dotnet_diagnostic.MA0038.severity = none + +# MA0039: Do not write your own certificate validation method +dotnet_diagnostic.MA0039.severity = none + +# MA0048: File name must match type name +dotnet_diagnostic.MA0048.severity = none + +# MA0049: Type name should not match containing namespace +dotnet_diagnostic.MA0049.severity = none + +# MA0051: Method is too long +dotnet_diagnostic.MA0051.severity = none + +# MA0069: Non-constant static fields should not be visible +dotnet_diagnostic.MA0069.severity = none + +# MA0071: Avoid using redundant else +dotnet_diagnostic.MA0071.severity = none + +# MA0076: Do not use implicit culture-sensitive ToString in interpolated strings +dotnet_diagnostic.MA0076.severity = none + +# MA0097: A class that implements IComparable or IComparable should override comparison operators +dotnet_diagnostic.MA0097.severity = none + # RECS0129: Removes 'internal' modifiers that are not required dotnet_diagnostic.RECS0129.severity = none diff --git a/cli/Squidex.CLI/Squidex.CLI.Tests/Squidex.CLI.Tests.csproj b/cli/Squidex.CLI/Squidex.CLI.Tests/Squidex.CLI.Tests.csproj index d6fd316b..8b7d0354 100644 --- a/cli/Squidex.CLI/Squidex.CLI.Tests/Squidex.CLI.Tests.csproj +++ b/cli/Squidex.CLI/Squidex.CLI.Tests/Squidex.CLI.Tests.csproj @@ -4,6 +4,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/cli/Squidex.CLI/Squidex.CLI.Tests/TestDataGeneratorTests.cs b/cli/Squidex.CLI/Squidex.CLI.Tests/TestDataGeneratorTests.cs index 9a154300..93b99fb7 100644 --- a/cli/Squidex.CLI/Squidex.CLI.Tests/TestDataGeneratorTests.cs +++ b/cli/Squidex.CLI/Squidex.CLI.Tests/TestDataGeneratorTests.cs @@ -32,7 +32,7 @@ public void Should_generate_datetime_between_min_and_max() MaxValue = maxValue }); - var dates = values.Select(x => (DateTime)x); + var dates = values.Cast(); var min = dates.Min(); var max = dates.Max(); @@ -58,7 +58,7 @@ public void Should_generate_datetime_before_max() MaxValue = maxValue }); - var dates = values.Select(x => (DateTime)x); + var dates = values.Cast(); var min = dates.Min(); var max = dates.Max(); @@ -84,7 +84,7 @@ public void Should_generate_datetime_after_min() MaxValue = null }); - var dates = values.Select(x => (DateTime)x); + var dates = values.Cast(); var min = dates.Min(); var max = dates.Max(); @@ -108,7 +108,7 @@ public void Should_generate_datetime_with_default_range() Editor = DateTimeFieldEditor.DateTime }); - var dates = values.Select(x => (DateTime)x); + var dates = values.Cast(); var min = dates.Min(); var max = dates.Max(); @@ -129,7 +129,7 @@ public void Should_generate_date_with_default_range() CreateManyScalars( new DateTimeFieldPropertiesDto()); - var dates = values.Select(x => (DateTime)x); + var dates = values.Cast(); var min = dates.Min(); var max = dates.Max(); @@ -195,7 +195,7 @@ public void Should_generate_numbers_between_min_and_max() MaxValue = 550 }); - var numbers = values.Select(x => (double)x); + var numbers = values.Cast(); var min = numbers.Min(); var max = numbers.Max(); @@ -214,7 +214,7 @@ public void Should_generate_numbers_lower_than_max() MaxValue = 550 }); - var numbers = values.Select(x => (double)x); + var numbers = values.Cast(); var min = numbers.Min(); var max = numbers.Max(); @@ -233,7 +233,7 @@ public void Should_generate_numbers_greater_than_min() MinValue = 120 }); - var numbers = values.Select(x => (double)x); + var numbers = values.Cast(); var min = numbers.Min(); var max = numbers.Max(); @@ -249,7 +249,7 @@ public void Should_generate_numbers_with_default_range() CreateManyScalars( new NumberFieldPropertiesDto()); - var numbers = values.Select(x => (double)x); + var numbers = values.Cast(); var min = numbers.Min(); var max = numbers.Max(); @@ -366,7 +366,7 @@ public void Should_generate_string_as_color() Editor = StringFieldEditor.Color }); - var strings = values.Select(x => (string)x); + var strings = values.Cast(); Assert.True(strings.All(x => x.StartsWith("#", StringComparison.OrdinalIgnoreCase) && x.Length == 7)); } @@ -381,7 +381,7 @@ public void Should_generate_string_equals_to_max_length() MaxLength = 20 }); - var strings = values.Select(x => (string)x); + var strings = values.Cast(); Assert.Equal(new[] { 20 }, strings.Select(x => x.Length).Distinct().ToArray()); } @@ -396,7 +396,7 @@ public void Should_generate_string_equals_to_short_max_length() MaxLength = 2 }); - var strings = values.Select(x => (string)x); + var strings = values.Cast(); Assert.Equal(new[] { 2 }, strings.Select(x => x.Length).Distinct().ToArray()); } @@ -411,7 +411,7 @@ public void Should_generate_string_equals_default_max_length() Editor = StringFieldEditor.Input }); - var strings = values.Select(x => (string)x); + var strings = values.Cast(); Assert.Equal(new[] { 100 }, strings.Select(x => x.Length).Distinct().ToArray()); } @@ -426,7 +426,7 @@ public void Should_generate_string_equals_default_html_max_length() Editor = StringFieldEditor.Markdown }); - var strings = values.Select(x => (string)x); + var strings = values.Cast(); Assert.Equal(new[] { 1000 }, strings.Select(x => x.Length).Distinct().ToArray()); } @@ -441,7 +441,7 @@ public void Should_generate_string_equals_default_markdown_max_length() Editor = StringFieldEditor.Markdown }); - var strings = values.Select(x => (string)x); + var strings = values.Cast(); Assert.Equal(new[] { 1000 }, strings.Select(x => x.Length).Distinct().ToArray()); } @@ -458,7 +458,7 @@ public void Should_generate_slugify_text() Editor = StringFieldEditor.Slug }); - var strings = values.Select(x => (string)x).Distinct(); + var strings = values.Cast().Distinct(); Assert.Equal(new[] { "lorem-ipsum-dolorxxx" }, strings); } diff --git a/cli/Squidex.CLI/Squidex.CLI.sln b/cli/Squidex.CLI/Squidex.CLI.sln index 4412d95e..47175fc5 100644 --- a/cli/Squidex.CLI/Squidex.CLI.sln +++ b/cli/Squidex.CLI/Squidex.CLI.sln @@ -16,6 +16,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.ClientLibrary", ".. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeGeneration", "..\..\csharp\Squidex.ClientLibrary\CodeGeneration\CodeGeneration.csproj", "{362BCA53-EEC9-42ED-BEAF-682F8796F6AF}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.ClientLibrary.Tests", "..\..\csharp\Squidex.ClientLibrary\Squidex.ClientLibrary.Tests\Squidex.ClientLibrary.Tests.csproj", "{47A22427-3BFF-465D-9F87-8C58B2A72264}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -38,6 +40,10 @@ Global {362BCA53-EEC9-42ED-BEAF-682F8796F6AF}.Debug|Any CPU.Build.0 = Debug|Any CPU {362BCA53-EEC9-42ED-BEAF-682F8796F6AF}.Release|Any CPU.ActiveCfg = Release|Any CPU {362BCA53-EEC9-42ED-BEAF-682F8796F6AF}.Release|Any CPU.Build.0 = Release|Any CPU + {47A22427-3BFF-465D-9F87-8C58B2A72264}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47A22427-3BFF-465D-9F87-8C58B2A72264}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47A22427-3BFF-465D-9F87-8C58B2A72264}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47A22427-3BFF-465D-9F87-8C58B2A72264}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Assets.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Assets.cs index da482421..44d1d650 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Assets.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Assets.cs @@ -50,6 +50,8 @@ public async Task Import(ImportArguments arguments) { var folderTree = new FolderTree(session); + var assetQuery = new AssetQuery(); + foreach (var file in fs.GetFiles(FilePath.Root, ".*")) { var targetFolder = file.LocalFolderPath(); @@ -59,34 +61,37 @@ public async Task Import(ImportArguments arguments) targetFolder = Path.Combine(arguments.TargetFolder, targetFolder); } - var parentId = await folderTree.GetIdAsync(targetFolder); + assetQuery.ParentId = await folderTree.GetIdAsync(targetFolder); + assetQuery.Filter = $"fileName eq '{file.Name}'"; - var existings = await assets.GetAssetsAsync(session.App, new AssetQuery - { - ParentId = parentId, - Filter = $"fileName eq '{file.Name}'", - Top = 2 - }); + var existings = await assets.GetAssetsAsync(session.App, assetQuery); + var existing = existings.Items.FirstOrDefault(); + + var fileHash = file.GetFileHash(); try { var fileParameter = new FileParameter(file.OpenRead(), file.Name, MimeTypesMap.GetMimeType(file.Name)); - if (existings.Items.Count > 0) - { - var existing = existings.Items.First(); - - log.WriteLine($"Uploading: {file.FullName}"); + log.WriteLine($"Uploading: {file.FullName}"); + if (existings.Items.Any(x => string.Equals(x.FileHash, fileHash, StringComparison.Ordinal))) + { + log.StepSkipped("Same hash."); + } + else if (existings.Items.Count > 1) + { + log.StepSkipped("Multiple candidates found."); + } + else if (existing != null) + { await assets.PutAssetContentAsync(session.App, existing.Id, fileParameter); - log.StepSuccess(); + log.StepSuccess("Existing Asset"); } else { - log.WriteLine($"Uploading New: {file.FullName}"); - - var result = await assets.PostAssetAsync(session.App, parentId, duplicate: arguments.Duplicate, file: fileParameter); + var result = await assets.PostAssetAsync(session.App, assetQuery.ParentId, null, arguments.Duplicate, fileParameter); if (result._meta?.IsDuplicate == "true") { @@ -94,7 +99,7 @@ public async Task Import(ImportArguments arguments) } else { - log.StepSuccess(); + log.StepSuccess("New Asset"); } } } @@ -147,16 +152,21 @@ public async Task Export(ImportArguments arguments) } }; - await assets.GetAllByQueryAsync(session.App, async asset => + try { - await downloadPipeline.DownloadAsync(asset); - }, - new AssetQuery + await assets.GetAllByQueryAsync(session.App, async asset => + { + await downloadPipeline.DownloadAsync(asset); + }, + new AssetQuery + { + ParentId = parentId + }); + } + finally { - ParentId = parentId - }); - - await downloadPipeline.CompleteAsync(); + await downloadPipeline.CompleteAsync(); + } log.WriteLine("> Export completed"); } diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/ConsoleLogger.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/ConsoleLogger.cs index bcc2b494..a954f67b 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/ConsoleLogger.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/ConsoleLogger.cs @@ -46,9 +46,16 @@ public void StepStart(string message) Console.Write(message); } - public void StepSuccess() + public void StepSuccess(string details = null) { - Console.WriteLine("succeeded."); + if (!string.IsNullOrWhiteSpace(details)) + { + Console.WriteLine($"succeeded ({details})."); + } + else + { + Console.WriteLine("succeeded."); + } } public void StepSkipped(string reason) diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/FileSystem/Zip/ZipFileSystem.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/FileSystem/Zip/ZipFileSystem.cs index a29cfa84..bb94f8a5 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/FileSystem/Zip/ZipFileSystem.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/FileSystem/Zip/ZipFileSystem.cs @@ -33,7 +33,7 @@ public IFile GetFile(FilePath path) { string relativePath = GetRelativePath(path); - return new ZipFile(zipArchive, relativePath, path.Elements.Last(), FullName); + return new ZipFile(zipArchive, relativePath, path.Elements[^1], FullName); } public IEnumerable GetFiles(FilePath path, string extension) diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/ILogger.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/ILogger.cs index b28efee9..ea4c66ee 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/ILogger.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/ILogger.cs @@ -13,7 +13,7 @@ public interface ILogger void StepFailed(string reason); - void StepSuccess(); + void StepSuccess(string details = null); void StepSkipped(string reason); diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/ImExport/JsonMapping.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/ImExport/JsonMapping.cs index ae5c81de..24e7b84b 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/ImExport/JsonMapping.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/ImExport/JsonMapping.cs @@ -13,7 +13,7 @@ namespace Squidex.CLI.Commands.Implementation.ImExport { public sealed class JsonMapping : List<(string Name, JsonPath Path, string Format)> { - private static readonly Regex FormatRegex = new Regex("(?[^\\/=]*)(=(?[^\\/]*))?(\\/(?.*))?", RegexOptions.Compiled); + private static readonly Regex FormatRegex = new Regex("(?[^\\/=]*)(=(?[^\\/]*))?(\\/(?.*))?", RegexOptions.Compiled | RegexOptions.ExplicitCapture); public static JsonMapping ForJson2Csv(string fields) { diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/ImExport/Squidex2CsvConverter.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/ImExport/Squidex2CsvConverter.cs index 43f096f5..e8bdf7e6 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/ImExport/Squidex2CsvConverter.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/ImExport/Squidex2CsvConverter.cs @@ -30,9 +30,9 @@ public Squidex2CsvConverter(string fields) public IEnumerable GetValues(DynamicContent entity) { - foreach (var field in mapping) + foreach (var (_, path, _) in mapping) { - var value = GetValue(entity, field.Path); + var value = GetValue(entity, path); switch (value) { @@ -46,7 +46,7 @@ public IEnumerable GetValues(DynamicContent entity) if (value is string text) { - yield return text.Replace("\n", "\\n"); + yield return text.Replace("\n", "\\n", StringComparison.Ordinal); } else { @@ -61,7 +61,7 @@ private static object GetValue(object current, JsonPath path) { if (current is JObject obj) { - if (obj.TryGetValue(key, out var temp)) + if (obj.TryGetValue(key, StringComparison.Ordinal, out var temp)) { current = temp; } diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/AssetsSynchronizer.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/AssetsSynchronizer.cs index d808589e..fba1139e 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/AssetsSynchronizer.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/AssetsSynchronizer.cs @@ -45,45 +45,50 @@ public async Task ExportAsync(ISyncService sync, SyncOptions options, ISession s FilePathProvider = asset => asset.Id.GetBlobPath() }; - var assets = new List(); - var assetBatch = 0; - - async Task SaveAsync() + try { - var model = new AssetsModel - { - Assets = assets - }; + var assets = new List(); + var assetBatch = 0; - await log.DoSafeAsync($"Exporting Assets ({assetBatch})", async () => + async Task SaveAsync() { - await sync.WriteWithSchema(new FilePath("assets", $"{assetBatch}.json"), model, Ref); - }); - } + var model = new AssetsModel + { + Assets = assets + }; - var tree = new FolderTree(session); + await log.DoSafeAsync($"Exporting Assets ({assetBatch})", async () => + { + await sync.WriteWithSchema(new FilePath("assets", $"{assetBatch}.json"), model, Ref); + }); + } - await session.Assets.GetAllAsync(session.App, async asset => - { - assets.Add(await asset.ToModelAsync(tree)); + var tree = new FolderTree(session); - if (assets.Count > 50) + await session.Assets.GetAllAsync(session.App, async asset => { - await SaveAsync(); + assets.Add(await asset.ToModelAsync(tree)); - assets.Clear(); - assetBatch++; - } + if (assets.Count > 50) + { + await SaveAsync(); - await downloadPipeline.DownloadAsync(asset); - }); + assets.Clear(); + assetBatch++; + } - if (assets.Count > 0) + await downloadPipeline.DownloadAsync(asset); + }); + + if (assets.Count > 0) + { + await SaveAsync(); + } + } + finally { - await SaveAsync(); + await downloadPipeline.CompleteAsync(); } - - await downloadPipeline.CompleteAsync(); } public async Task ImportAsync(ISyncService sync, SyncOptions options, ISession session) @@ -103,8 +108,17 @@ public async Task ImportAsync(ISyncService sync, SyncOptions options, ISession s FilePathProvider = asset => asset.Id.GetBlobPath() }; - await uploader.UploadAsync(model.Assets); - await uploader.CompleteAsync(); + try + { + foreach (var asset in model.Assets) + { + await uploader.UploadAsync(asset); + } + } + finally + { + await uploader.CompleteAsync(); + } var request = new BulkUpdateAssetsDto(); diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/DownloadPipeline.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/DownloadPipeline.cs index 456303dd..841c8313 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/DownloadPipeline.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/DownloadPipeline.cs @@ -63,9 +63,9 @@ public DownloadPipeline(ISession session, ILogger log, IFileSystem fs) try { var assetFile = fs.GetFile(path); - var assetHash = GetFileHash(assetFile, asset); + var assetHash = assetFile.GetFileHash(asset); - if (assetHash == null || !string.Equals(asset.FileHash, assetHash)) + if (assetHash == null || !string.Equals(asset.FileHash, assetHash, StringComparison.Ordinal)) { var response = await session.Assets.GetAssetContentBySlugAsync(session.App, asset.Id, string.Empty); @@ -104,44 +104,6 @@ public DownloadPipeline(ISession session, ILogger log, IFileSystem fs) pipelineEnd = downloadStep; } - private static string GetFileHash(IFile file, AssetDto asset) - { - if (file == null) - { - return null; - } - - try - { - using (var fileStream = file.OpenRead()) - { - var incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256); - - var buffer = new byte[80000]; - var bytesRead = 0; - - while ((bytesRead = fileStream.Read(buffer)) > 0) - { - incrementalHash.AppendData(buffer, 0, bytesRead); - } - - var fileHash = Convert.ToBase64String(incrementalHash.GetHashAndReset()); - - var hash = $"{fileHash}{asset.FileName}{asset.FileSize}".Sha256Base64(); - - return hash; - } - } - catch (DirectoryNotFoundException) - { - return null; - } - catch (FileNotFoundException) - { - return null; - } - } - public Task DownloadAsync(AssetDto asset) { return pipelineStart.SendAsync(asset); diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/Extensions.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/Extensions.cs index 199e9877..8d339892 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/Extensions.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/Extensions.cs @@ -5,6 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; +using System.IO; +using System.Security.Cryptography; using System.Threading.Tasks; using Squidex.CLI.Commands.Implementation.FileSystem; using Squidex.ClientLibrary.Management; @@ -23,6 +26,54 @@ public static FilePath GetBlobPath(this string id) return new FilePath("assets", "files", $"{id}.blob"); } + public static string GetFileHash(this IFile file, AssetDto asset) + { + return GetFileHash(file, asset.FileName); + } + + public static string GetFileHash(this IFile file) + { + return GetFileHash(file, file.Name); + } + + public static string GetFileHash(this IFile file, string fileName) + { + if (file == null) + { + return null; + } + + try + { + using (var fileStream = file.OpenRead()) + { + var incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256); + + var buffer = new byte[80000]; + var bytesRead = 0; + + while ((bytesRead = fileStream.Read(buffer)) > 0) + { + incrementalHash.AppendData(buffer, 0, bytesRead); + } + + var fileHash = Convert.ToBase64String(incrementalHash.GetHashAndReset()); + + var hash = $"{fileHash}{fileName}{fileStream.Length}".Sha256Base64(); + + return hash; + } + } + catch (DirectoryNotFoundException) + { + return null; + } + catch (FileNotFoundException) + { + return null; + } + } + public static BulkUpdateAssetsJobDto ToMoveJob(this AssetModel model, string parentId) { return new BulkUpdateAssetsJobDto @@ -40,6 +91,8 @@ public static BulkUpdateAssetsJobDto ToAnnotateJob(this AssetModel model) Id = model.Id, Type = BulkUpdateAssetType.Annotate, FileName = model.FileName, + ParentId = null, + Permanent = false, IsProtected = model.IsProtected, Metadata = model.Metadata, Slug = model.Slug, diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/UploadPipeline.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/UploadPipeline.cs index 26604023..fbed9931 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/UploadPipeline.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/UploadPipeline.cs @@ -101,12 +101,9 @@ public UploadPipeline(ISession session, ILogger log, IFileSystem fs) pipelineEnd = uploadStep; } - public async Task UploadAsync(IEnumerable assets) + public Task UploadAsync(AssetModel asset) { - foreach (var asset in assets) - { - await pipelineStart.SendAsync(asset); - } + return pipelineStart.SendAsync(asset); } public Task CompleteAsync() diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/InheritanceAttribute.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/InheritanceAttribute.cs index f4a72dbb..af9e4d8f 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/InheritanceAttribute.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/InheritanceAttribute.cs @@ -9,6 +9,7 @@ namespace Squidex.CLI.Commands.Implementation.Sync { + [AttributeUsage(AttributeTargets.All)] public sealed class InheritanceAttribute : Attribute { public string Discriminator { get; } diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/TestData/TestDataGenerator.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/TestData/TestDataGenerator.cs index 8c7a5461..d9015349 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/TestData/TestDataGenerator.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/TestData/TestDataGenerator.cs @@ -183,7 +183,7 @@ private JToken GenerateValue(FieldDto field) if (stringField.Editor == StringFieldEditor.Slug) { - result = slugify.GenerateSlug(result).Replace(".", "x"); + result = slugify.GenerateSlug(result).Replace(".", "x", StringComparison.Ordinal); } return result; diff --git a/cli/Squidex.CLI/Squidex.CLI/Configuration/ConfigurationService.cs b/cli/Squidex.CLI/Squidex.CLI/Configuration/ConfigurationService.cs index 3f36f014..80f55346 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Configuration/ConfigurationService.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Configuration/ConfigurationService.cs @@ -131,7 +131,7 @@ public void UseAppInSession(string entry) sessionApp = entry; } - public ISession StartSession(bool emulate) + public ISession StartSession(bool emulate = false) { if (!string.IsNullOrWhiteSpace(sessionApp) && configuration.Apps.TryGetValue(sessionApp, out var app)) { diff --git a/cli/Squidex.CLI/Squidex.CLI/Helper.cs b/cli/Squidex.CLI/Squidex.CLI/Helper.cs index d2f6b2bf..e7036a60 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Helper.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Helper.cs @@ -40,14 +40,12 @@ public static IEnumerable OrEmpty(this IEnumerable source) return source ?? Enumerable.Empty(); } - public static Task WriteJsonAsync(this Stream stream, T value) + public static async Task WriteJsonAsync(this Stream stream, T value) { - using (var streamWriter = new StreamWriter(stream)) + await using (var streamWriter = new StreamWriter(stream)) { Serializer.Serialize(streamWriter, value); } - - return Task.CompletedTask; } public static async Task WriteJsonToFileAsync(T value, string path) diff --git a/cli/Squidex.CLI/Squidex.CLI/Squidex.CLI.csproj b/cli/Squidex.CLI/Squidex.CLI/Squidex.CLI.csproj index 5ba01fc7..23e002e3 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Squidex.CLI.csproj +++ b/cli/Squidex.CLI/Squidex.CLI/Squidex.CLI.csproj @@ -14,7 +14,7 @@ net5.0 true sq - 7.20 + 7.21 @@ -26,6 +26,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary.Tests/SerializationTests.cs b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary.Tests/SerializationTests.cs index 60ab58d6..d923c5c7 100644 --- a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary.Tests/SerializationTests.cs +++ b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary.Tests/SerializationTests.cs @@ -51,7 +51,7 @@ public void Should_serialize_datetime_to_iso8601() var serialized = source.ToJson(); - Assert.Contains("2012-11-10T09:08:07Z", serialized); + Assert.Contains("2012-11-10T09:08:07Z", serialized, StringComparison.Ordinal); } [Fact] @@ -66,7 +66,7 @@ public void Should_serialize_local_datetime_to_iso8601_utc() var serialized = source.ToJson(); - Assert.Contains("2012-11-10T09:08:07Z", serialized); + Assert.Contains("2012-11-10T09:08:07Z", serialized, StringComparison.Ordinal); } [Fact] @@ -81,7 +81,7 @@ public void Should_serialize_datetimeoffset_to_iso8601() var serialized = source.ToJson(); - Assert.Contains("2012-11-10T09:08:07Z", serialized); + Assert.Contains("2012-11-10T09:08:07Z", serialized, StringComparison.Ordinal); } [Fact] @@ -96,7 +96,7 @@ public void Should_serialize_local_datetimeoffset_to_iso8601_utc() var serialized = source.ToJson(); - Assert.Contains("2012-11-10T09:08:07Z", serialized); + Assert.Contains("2012-11-10T09:08:07Z", serialized, StringComparison.Ordinal); } [Fact] @@ -111,7 +111,7 @@ public void Should_serialize_local_datetime_to_iso8601_utc_with_ms() var serialized = source.ToJson(); - Assert.Contains("2010-12-29T13:32:27Z", serialized); + Assert.Contains("2010-12-29T13:32:27Z", serialized, StringComparison.Ordinal); } [Fact] @@ -124,7 +124,7 @@ public void Should_serialize_false() var serialized = source.ToJson(); - Assert.Contains("\"doNotScript\": false", serialized); + Assert.Contains("\"doNotScript\": false", serialized, StringComparison.Ordinal); } [Fact] @@ -137,7 +137,7 @@ public void Should_serialize_type() var serialized = source.ToJson(); - Assert.Contains("\"type\": \"ChangeStatus\"", serialized); + Assert.Contains("\"type\": \"ChangeStatus\"", serialized, StringComparison.Ordinal); } [Fact] @@ -150,7 +150,7 @@ public void Should_serialize_invariant() var serialized = source.ToJson(); - Assert.Contains("\"iv\": \"hello\"", serialized); + Assert.Contains("\"iv\": \"hello\"", serialized, StringComparison.Ordinal); } [Fact] @@ -163,7 +163,7 @@ public void Should_serialize_dynamic_properties_with_original_casing() var serialized = source.ToJson(); - Assert.Contains("\"Property1\": {}", serialized); + Assert.Contains("\"Property1\": {}", serialized, StringComparison.Ordinal); } [Fact] @@ -206,7 +206,7 @@ public void Should_serialize_with_camel_case() var serialized = source.ToJson(); - Assert.Contains("\"value\": \"hello\"", serialized); + Assert.Contains("\"value\": \"hello\"", serialized, StringComparison.Ordinal); } [Fact] @@ -219,7 +219,7 @@ public void Should_serialize_with_pascal_case() var serialized = source.ToJson(); - Assert.Contains("\"Value\": \"hello\"", serialized); + Assert.Contains("\"Value\": \"hello\"", serialized, StringComparison.Ordinal); } } } diff --git a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary.Tests/Squidex.ClientLibrary.Tests.csproj b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary.Tests/Squidex.ClientLibrary.Tests.csproj index b5a42595..2d2305cc 100644 --- a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary.Tests/Squidex.ClientLibrary.Tests.csproj +++ b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary.Tests/Squidex.ClientLibrary.Tests.csproj @@ -7,6 +7,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all diff --git a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Content.cs b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Content.cs index cbb5082e..3b64a5bd 100644 --- a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Content.cs +++ b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Content.cs @@ -111,9 +111,9 @@ public string Status return (hrefParts[0], hrefParts[1]); } - catch + catch (Exception ex) { - throw new InvalidOperationException($"Link {self.Href} is malformed."); + throw new InvalidOperationException($"Link {self.Href} is malformed.", ex); } } } diff --git a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/EnrichedEvents/EnrichedContentEvent.cs b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/EnrichedEvents/EnrichedContentEvent.cs index 972733af..999248f7 100644 --- a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/EnrichedEvents/EnrichedContentEvent.cs +++ b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/EnrichedEvents/EnrichedContentEvent.cs @@ -86,14 +86,7 @@ public class EnrichedContentEvent : EnrichedSchemaEventBase, IEnrichedEntityEven /// EnrichedContentEvent instance where Data and DataOld have type T. public EnrichedContentEvent ToTyped() { - var contentType = typeof(T); - - if (contentType == null) - { - throw new ArgumentNullException(nameof(contentType)); - } - - var typedEvent = typeof(EnrichedContentEvent<>).MakeGenericType(contentType); + var typedEvent = typeof(EnrichedContentEvent<>).MakeGenericType(typeof(T)); var obj = (EnrichedContentEvent)Activator.CreateInstance(typedEvent); @@ -104,12 +97,12 @@ public EnrichedContentEvent ToTyped() if (Data != null) { - obj.Data = (T)Data.ToObject(contentType); + obj.Data = (T)Data.ToObject(typeof(T)); } if (DataOld != null) { - obj.DataOld = (T)DataOld.ToObject(contentType); + obj.DataOld = (T)DataOld.ToObject(typeof(T)); } return obj; diff --git a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Management/JsonInheritanceConverter.cs b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Management/JsonInheritanceConverter.cs index 85f98318..2f78a6a5 100644 --- a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Management/JsonInheritanceConverter.cs +++ b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Management/JsonInheritanceConverter.cs @@ -117,7 +117,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist return null; } - var discriminator = jObject.GetValue(DiscriminatorName).Value(); + var discriminator = jObject.GetValue(DiscriminatorName, StringComparison.Ordinal).Value(); var subtype = GetObjectSubtype(objectType, discriminator); diff --git a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Squidex.ClientLibrary.csproj b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Squidex.ClientLibrary.csproj index 207a1575..8c6d039b 100644 --- a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Squidex.ClientLibrary.csproj +++ b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Squidex.ClientLibrary.csproj @@ -14,6 +14,10 @@ netstandard2.0;netcoreapp3.1;net5.0 + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/SquidexOptions.cs b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/SquidexOptions.cs index aa00a8db..212536c8 100644 --- a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/SquidexOptions.cs +++ b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/SquidexOptions.cs @@ -279,23 +279,24 @@ private void ThrowIfFrozen() internal void CheckAndFreeze() { +#pragma warning disable MA0015 // Specify the parameter name in ArgumentException if (!Uri.IsWellFormedUriString(url, UriKind.Absolute)) { - throw new ArgumentException("URL must be a valid absolute URL."); + throw new ArgumentException("URL must be a valid absolute URL.", nameof(Url)); } url = url.TrimEnd('/', ' '); if (string.IsNullOrWhiteSpace(appName)) { - throw new ArgumentException("App name is not defined."); + throw new ArgumentException("App name is not defined.", nameof(AppName)); } if (!string.IsNullOrWhiteSpace(assetCDN)) { if (!Uri.IsWellFormedUriString(assetCDN, UriKind.Absolute)) { - throw new ArgumentException("Asset CDN URL must be absolute if specified."); + throw new ArgumentException("Asset CDN URL must be absolute if specified.", nameof(AssetCDN)); } contentCDN = contentCDN.TrimEnd('/', ' '); @@ -305,7 +306,7 @@ internal void CheckAndFreeze() { if (!Uri.IsWellFormedUriString(contentCDN, UriKind.Absolute)) { - throw new ArgumentException("Content CDN URL must be absolute if specified."); + throw new ArgumentException("Content CDN URL must be absolute if specified.", nameof(ContentCDN)); } contentCDN = contentCDN.TrimEnd('/', ' '); @@ -330,12 +331,12 @@ internal void CheckAndFreeze() { if (string.IsNullOrWhiteSpace(clientId)) { - throw new ArgumentException("Client id is not defined."); + throw new ArgumentException("Client id is not defined.", nameof(ClientId)); } if (string.IsNullOrWhiteSpace(clientSecret)) { - throw new ArgumentException("Client secret is not defined."); + throw new ArgumentException("Client secret is not defined.", nameof(ClientSecret)); } var cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); @@ -346,6 +347,7 @@ internal void CheckAndFreeze() } isFrozen = true; +#pragma warning restore MA0015 // Specify the parameter name in ArgumentException } } } diff --git a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Status.cs b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Status.cs index 8c1840aa..bac1d981 100644 --- a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Status.cs +++ b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Status.cs @@ -59,13 +59,13 @@ public override bool Equals(object obj) /// public bool Equals(Status other) { - return string.Equals(Name, other.Name); + return string.Equals(Name, other.Name, StringComparison.Ordinal); } /// public override int GetHashCode() { - return Name.GetHashCode(); + return StringComparer.Ordinal.GetHashCode(Name); } ///