From fa9bfac8ab5ef6652dfba1d852d912e624f451c9 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 5 May 2021 15:30:09 +0200 Subject: [PATCH] New CLI function to import assets. --- .../Squidex.CLI/Commands/App_Assets.cs | 90 +++++++++++++++++++ .../Implementation/Sync/Assets/FolderTree.cs | 6 +- cli/Squidex.CLI/Squidex.CLI/Program.cs | 1 + .../Squidex.CLI/Squidex.CLI.csproj | 2 +- .../Squidex.ClientLibrary/Content.cs | 2 +- .../Squidex.ClientLibrary/ContentsClient.cs | 6 +- .../Management/Custom.cs | 63 ++++++++----- .../Management/JsonInheritanceConverter.cs | 2 +- .../Squidex.ClientLibrary.csproj | 4 +- 9 files changed, 144 insertions(+), 32 deletions(-) create mode 100644 cli/Squidex.CLI/Squidex.CLI/Commands/App_Assets.cs diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Assets.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Assets.cs new file mode 100644 index 00000000..a6790a1a --- /dev/null +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Assets.cs @@ -0,0 +1,90 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.IO; +using System.Threading.Tasks; +using CommandDotNet; +using FluentValidation; +using FluentValidation.Attributes; +using Squidex.CLI.Commands.Implementation; +using Squidex.CLI.Commands.Implementation.Sync.Assets; +using Squidex.CLI.Configuration; + +namespace Squidex.CLI.Commands +{ + public partial class App + { + [Command(Name = "assets", Description = "Manages assets.")] + [SubCommand] + public sealed class Assets + { + private readonly IConfigurationService configuration; + private readonly ILogger log; + + public Assets(IConfigurationService configuration, ILogger log) + { + this.configuration = configuration; + + this.log = log; + } + + [Command(Name = "import", Description = "Import all files from the source folder.")] + public async Task List(ImportArguments arguments) + { + var session = configuration.StartSession(); + + var assets = session.Assets; + + var folder = new DirectoryInfo(arguments.Folder); + var folderTree = new FolderTree(session); + + foreach (var file in folder.GetFiles("*.*", SearchOption.AllDirectories)) + { + var relativeFolder = Path.GetRelativePath(folder.FullName, file.Directory.FullName); + var relativePath = Path.GetRelativePath(folder.FullName, file.FullName); + + var targetFolder = arguments.TargetFolder; + + if (!string.IsNullOrWhiteSpace(relativePath) && relativePath != ".") + { + targetFolder = Path.Combine(targetFolder, relativeFolder); + } + + var parentId = await folderTree.GetIdAsync(targetFolder); + + await log.DoSafeLineAsync($"Uploading {relativePath}", async () => + { + await assets.PostAssetAsync(session.App, parentId, duplicate: arguments.Duplicate, file: file); + }); + } + + log.WriteLine("> Import completed"); + } + + [Validator(typeof(Validator))] + public sealed class ImportArguments : IArgumentModel + { + [Operand(Name = "folder", Description = "The source folder.")] + public string Folder { get; set; } + + [Option(ShortName = "t", LongName = "target", Description = "Path to the target folder.")] + public string TargetFolder { get; set; } + + [Option(ShortName = "d", LongName = "duplicate", Description = "Duplicate the asset.")] + public bool Duplicate { get; set; } + + public sealed class Validator : AbstractValidator + { + public Validator() + { + RuleFor(x => x.Folder).NotEmpty(); + } + } + } + } + } +} diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/FolderTree.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/FolderTree.cs index 2d767bfa..979379ae 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/FolderTree.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Sync/Assets/FolderTree.cs @@ -14,8 +14,8 @@ namespace Squidex.CLI.Commands.Implementation.Sync.Assets { public sealed class FolderTree { - private static readonly char[] TrimChars = { '/', '\\', ' ' }; - private static readonly char[] SplitChars = { ' ', '/', '\\' }; + private static readonly char[] TrimChars = { '/', '\\', ' ', '.' }; + private static readonly char[] SplitChars = { '/', '\\' }; private static readonly string RootId = Guid.Empty.ToString(); private readonly Dictionary nodes = new Dictionary(); private readonly ISession session; @@ -57,7 +57,7 @@ public async Task GetPathAsync(string id) public async Task GetIdAsync(string path) { - if (path == null) + if (path == null || path.Equals(".", StringComparison.OrdinalIgnoreCase)) { return null; } diff --git a/cli/Squidex.CLI/Squidex.CLI/Program.cs b/cli/Squidex.CLI/Squidex.CLI/Program.cs index 043897c3..76d630fc 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Program.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Program.cs @@ -32,6 +32,7 @@ public static int Main(string[] args) .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/cli/Squidex.CLI/Squidex.CLI/Squidex.CLI.csproj b/cli/Squidex.CLI/Squidex.CLI/Squidex.CLI.csproj index eaee5aa6..5b313e19 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.5 + 7.6 diff --git a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Content.cs b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Content.cs index 6434f9c0..cbb5082e 100644 --- a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Content.cs +++ b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Content.cs @@ -29,7 +29,7 @@ namespace Squidex.ClientLibrary /// /// This property is not supported anymore. A content item is pending when the property is not null. /// - [Obsolete] + [Obsolete("Use NewStatus")] public bool IsPending { get; set; } /// diff --git a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/ContentsClient.cs b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/ContentsClient.cs index 8be54f9e..9ef96e25 100644 --- a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/ContentsClient.cs +++ b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/ContentsClient.cs @@ -377,15 +377,15 @@ private static string BuildQuery(object request) { if (queryBuilder.Length > 0) { - queryBuilder.Append("&"); + queryBuilder.Append('&'); } else { - queryBuilder.Append("?"); + queryBuilder.Append('?'); } queryBuilder.Append(kvp.Key); - queryBuilder.Append("="); + queryBuilder.Append('='); queryBuilder.Append(Uri.EscapeUriString(kvp.Value.ToString())); } diff --git a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Management/Custom.cs b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Management/Custom.cs index 32440ac1..d9b9672c 100644 --- a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Management/Custom.cs +++ b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Management/Custom.cs @@ -7,8 +7,11 @@ using System; using System.Collections.Generic; +using System.IO; using System.Text; +using System.Threading; using System.Threading.Tasks; +using HeyRed.Mime; using Squidex.ClientLibrary.Utils; #pragma warning disable RECS0096 // Type parameter is never used @@ -33,10 +36,10 @@ public override string ToString() !Message.EndsWith(":", StringComparison.OrdinalIgnoreCase) && !Message.EndsWith(",", StringComparison.OrdinalIgnoreCase)) { - sb.Append(":"); + sb.Append(':'); } - sb.Append(" "); + sb.Append(' '); sb.Append(string.Join(", ", Details)); } @@ -149,47 +152,63 @@ internal string ToIdString() public partial interface IAssetsClient #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member { + /// + /// Upload a new asset. + /// + /// The name of the app. + /// The optional parent folder id. + /// The optional custom asset id. + /// True to duplicate the asset, event if the file has been uploaded. + /// The file to upload. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Asset created. + /// + /// A server side error occurred. + Task PostAssetAsync(string app, string parentId = null, string id = null, bool? duplicate = null, FileInfo file = null, CancellationToken cancellationToken = default(CancellationToken)); + /// Get assets. /// The name of the app. /// The optional asset query. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// Assets returned. /// A server side error occurred. - Task GetAssetsAsync(string app, AssetQuery query = null, System.Threading.CancellationToken cancellationToken = default); + Task GetAssetsAsync(string app, AssetQuery query = null, CancellationToken cancellationToken = default); - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// Get assets. + /// + /// Get assets. + /// /// The name of the app. /// The callback that is invoke for each asset. /// The number of assets per request. - /// Assets returned. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Assets returned. + /// /// A server side error occurred. - Task GetAllAsync(string app, Func callback, int batchSize = 200, System.Threading.CancellationToken cancellationToken = default); + Task GetAllAsync(string app, Func callback, int batchSize = 200, CancellationToken cancellationToken = default); } #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public partial class AssetsClient #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member { - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// Get assets. - /// The name of the app. - /// The optional asset query. - /// Assets returned. - /// A server side error occurred. - public Task GetAssetsAsync(string app, AssetQuery query = null, System.Threading.CancellationToken cancellationToken = default) + /// + public Task PostAssetAsync(string app, string parentId = null, string id = null, bool? duplicate = null, FileInfo file = null, CancellationToken cancellationToken = default(CancellationToken)) + { + Guard.NotNull(file, nameof(file)); + + return PostAssetAsync(app, parentId, id, duplicate, new FileParameter(file.OpenRead(), file.Name, MimeTypesMap.GetMimeType(file.Name))); + } + + /// + public Task GetAssetsAsync(string app, AssetQuery query = null, CancellationToken cancellationToken = default) { return GetAssetsAsync(app, query?.Top, query?.Skip, query?.OrderBy, query?.Filter, query?.ParentId, query?.ToIdString(), query?.ToQueryJson(), cancellationToken); } - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// Get assets. - /// The name of the app. - /// The callback that is invoke for each asset. - /// The number of assets per request. - /// Assets returned. - /// A server side error occurred. - public async Task GetAllAsync(string app, Func callback, int batchSize = 200, System.Threading.CancellationToken cancellationToken = default) + /// + public async Task GetAllAsync(string app, Func callback, int batchSize = 200, CancellationToken cancellationToken = default) { var query = new AssetQuery { Top = batchSize, Skip = 0 }; var added = new HashSet(); diff --git a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Management/JsonInheritanceConverter.cs b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Management/JsonInheritanceConverter.cs index e66e6c3e..85f98318 100644 --- a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Management/JsonInheritanceConverter.cs +++ b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Management/JsonInheritanceConverter.cs @@ -144,7 +144,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist /// /// The discriminator value for the specified type. /// - public string GetDiscriminatorValue(Type type) + public virtual string GetDiscriminatorValue(Type type) { return GetSubtypeDiscriminator(type); } diff --git a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Squidex.ClientLibrary.csproj b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Squidex.ClientLibrary.csproj index 0d564411..15e60978 100644 --- a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Squidex.ClientLibrary.csproj +++ b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Squidex.ClientLibrary.csproj @@ -10,10 +10,12 @@ MIT https://github.com/Squidex/squidex/ true - 7.2.0 + 7.3.0 netstandard2.0;netcoreapp3.1;net5.0 + +