diff --git a/Directory.Packages.props b/Directory.Packages.props index 2fc28662..dd3d247e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,36 +1,36 @@ - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/uSync.BackOffice/Extensions/uSyncActionExtensions.cs b/uSync.BackOffice/Extensions/uSyncActionExtensions.cs index 923eafb1..e9cf8146 100644 --- a/uSync.BackOffice/Extensions/uSyncActionExtensions.cs +++ b/uSync.BackOffice/Extensions/uSyncActionExtensions.cs @@ -137,7 +137,9 @@ public static void UpdateActions(this List actions, Guid k public static bool RequiresSave(this SyncAttempt attempt) => attempt.Success && attempt.Change > Core.ChangeType.NoChange && !attempt.Saved && attempt.Item != null; - + /// + /// return the uSyncAction as an ActionView (used in the controllers) + /// public static uSyncActionView AsActionView(this uSyncAction action) { var msg = string.IsNullOrWhiteSpace(action.Message) is false diff --git a/uSync.BackOffice/Services/ISyncVersionFileService.cs b/uSync.BackOffice/Services/ISyncVersionFileService.cs index b3355932..90152666 100644 --- a/uSync.BackOffice/Services/ISyncVersionFileService.cs +++ b/uSync.BackOffice/Services/ISyncVersionFileService.cs @@ -2,8 +2,20 @@ namespace uSync.BackOffice.Services; +/// +/// Controls the version file we write to disk on syncs (used to warn if sync is old) +/// public interface ISyncVersionFileService { + /// + /// get the Sync file version information for a folder. + /// Task GetSyncFileInfo(string folder); + + /// + /// write the version information to disk. + /// + /// + /// Task WriteVersionFileAsync(string folder); } \ No newline at end of file diff --git a/uSync.BackOffice/Services/SyncVersionFileService.cs b/uSync.BackOffice/Services/SyncVersionFileService.cs index af146b8e..fee5c640 100644 --- a/uSync.BackOffice/Services/SyncVersionFileService.cs +++ b/uSync.BackOffice/Services/SyncVersionFileService.cs @@ -119,9 +119,23 @@ private bool HmacValuesMatch(XElement node) } } +/// +/// results of a check of the version file +/// public class SyncFileVersionCheckResult { + /// + /// the sync on disk is current to the current format we are writing. + /// public bool IsCurrent { get; set; } + + /// + /// the version we are writing to disk + /// public string? FormatVersion { get; set; } + + /// + /// the hmac value for the folders matches. (reserved) + /// public bool HmacMatch { get; set; } } diff --git a/uSync.BackOffice/uSyncBackOffice.cs b/uSync.BackOffice/uSyncBackOffice.cs index 2ac79b1f..8f9f519e 100644 --- a/uSync.BackOffice/uSyncBackOffice.cs +++ b/uSync.BackOffice/uSyncBackOffice.cs @@ -7,6 +7,9 @@ namespace uSync.BackOffice; /// public class uSync { + /// + /// assembly version for uSync + /// public static Version Version => typeof(uSync).Assembly.GetName().Version ?? new Version(15, 0, 0); /// diff --git a/uSync.Backoffice.Management.Client/usync-assets/package-lock.json b/uSync.Backoffice.Management.Client/usync-assets/package-lock.json index 6f9c935c..a0c082e7 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/package-lock.json +++ b/uSync.Backoffice.Management.Client/usync-assets/package-lock.json @@ -1,12 +1,12 @@ { "name": "@jumoo/usync", - "version": "17.3.2", + "version": "17.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@jumoo/usync", - "version": "17.3.2", + "version": "17.3.3", "license": "MPL-2.0", "devDependencies": { "@hey-api/openapi-ts": "^0.95.0", diff --git a/uSync.Backoffice.Management.Client/usync-assets/package.json b/uSync.Backoffice.Management.Client/usync-assets/package.json index 9ac73a50..82b5cbd2 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/package.json +++ b/uSync.Backoffice.Management.Client/usync-assets/package.json @@ -8,7 +8,7 @@ "homepage": "https://jumoo.co.uk/uSync", "license": "MPL-2.0", "type": "module", - "version": "17.3.2", + "version": "17.3.3", "main": "./dist/usync.js", "types": "./dist/index.d.ts", "module": "./dist/usync.js", diff --git a/uSync.Core/Mapping/Mappers/ImagePathMapper.cs b/uSync.Core/Mapping/Mappers/ImagePathMapper.cs index 807fd9c7..a8730d28 100644 --- a/uSync.Core/Mapping/Mappers/ImagePathMapper.cs +++ b/uSync.Core/Mapping/Mappers/ImagePathMapper.cs @@ -2,8 +2,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System.Text.RegularExpressions; - using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Media; @@ -11,7 +9,6 @@ using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -using uSync.Core.Dependency; using uSync.Core.Extensions; using uSync.Core.Serialization; @@ -28,49 +25,33 @@ namespace uSync.Core.Mapping; /// becomes /// {"src":"/media/2cud1lzo/15656993711_ccd199b83e_k.jpg","crops":null} /// -public class ImagePathMapper : SyncValueMapperBase, ISyncMapper +public class ImagePathMapper : ImagePathMapperBase, ISyncMapper { - private const string _genericMediaPath = "/media"; - - private readonly string _siteRoot; - private string? _mediaFolder; - private readonly ILogger _logger; - private readonly IConfiguration _configuration; private readonly IImageUrlGenerator _imageUrlGenerator; public ImagePathMapper( - IConfiguration configuration, - IOptionsMonitor _globalOptions, IEntityService entityService, ILogger logger, - IImageUrlGenerator imageUrlGenerator) : base(entityService) + IConfiguration configuration, + IOptionsMonitor globalOptions, + IImageUrlGenerator imageUrlGenerator) : base(entityService, logger, configuration, globalOptions) { - _logger = logger; - _configuration = configuration; - - // todo: site root might need us to include extra NuGet. - _siteRoot = ""; - - _mediaFolder = GetMediaFolderSetting(_globalOptions.CurrentValue.UmbracoMediaPath.TrimStart('~')); - _globalOptions.OnChange(x => _mediaFolder = GetMediaFolderSetting(x.UmbracoMediaPath.TrimStart('~'))); - - if (logger.IsEnabled(LogLevel.Debug)) - logger.LogDebug("Media Folders: [{media}]", _mediaFolder ?? "(Blank)"); - _imageUrlGenerator = imageUrlGenerator; } public override string Name => "ImageCropper Mapper"; public override string[] Editors => [ - Constants.PropertyEditors.Aliases.ImageCropper, - Constants.PropertyEditors.Aliases.UploadField + Constants.PropertyEditors.Aliases.ImageCropper ]; public override Task GetExportValueAsync(object value, string editorAlias) { return uSyncTaskHelper.FromResultOf(() => { + if (_logger.IsEnabled(LogLevel.Debug)) + _logger.LogDebug("Getting export value for ImageCropper with value {Value}", value); + var stringValue = value?.ToString(); if (string.IsNullOrWhiteSpace(stringValue)) return stringValue; @@ -107,76 +88,6 @@ public ImagePathMapper( }); } - private string StripSitePath(string filePath) - { - var path = filePath; - if (_siteRoot.Length > 0 && !string.IsNullOrWhiteSpace(filePath) && filePath.InvariantStartsWith(_siteRoot)) - path = filePath.Substring(_siteRoot.Length); - - return ReplacePath(path, _mediaFolder, _genericMediaPath); - } - - private string PrePendSitePath(string filePath) - { - var path = filePath; - if (_siteRoot.Length > 0 && !string.IsNullOrEmpty(filePath)) - path = $"{_siteRoot}{filePath}"; - - return ReplacePath(path, _genericMediaPath, _mediaFolder); - } - - - /// - /// makes a specific media path generic. - /// - /// - /// sometimes paths may be defined by umbraco settings, (especially blob settings) - /// that mean they are not stored as /media - /// - /// for the sake of generic importing we want the folder stored to be /media. - /// so we re-write the setting on import and export - /// - /// assumes you have a app setting in the web.config - /// - /// /someFolder - /// - /// - /// - private static string ReplacePath(string filePath, string? currentPath, string? targetPath) - { - if (!string.IsNullOrWhiteSpace(targetPath) - && !string.IsNullOrWhiteSpace(currentPath) - && !currentPath.Equals(targetPath)) - { - return Regex.Replace(filePath, $"^{currentPath}", targetPath, RegexOptions.IgnoreCase); - } - - return filePath; - } - - /// - /// Get the media rewrite folder - /// - /// - /// looks in appSettings for uSync:mediaFolder - /// - /// - /// - /// or in uSync8.config for media setting - /// - /// - /// - /// /someFolder - /// - /// - /// - private string GetMediaFolderSetting(string umbracoMediaPath) - { - var folder = this._configuration.GetValue("uSync:MediaFolder", string.Empty); - if (!string.IsNullOrEmpty(folder)) return folder; - - return umbracoMediaPath; - } public override Task GetImportValueAsync(string value, string editorAlias, SyncSerializerOptions options) { @@ -202,49 +113,4 @@ private string GetMediaFolderSetting(string umbracoMediaPath) return json.SerializeJsonNode(true); }); } - - /// - /// Get the actual media file as a dependency. - /// - public override Task> GetDependenciesAsync(object value, string editorAlias, DependencyFlags flags) - { - return uSyncTaskHelper.FromResultOf>(() => - { - - var stringValue = value?.ToString(); - if (string.IsNullOrWhiteSpace(stringValue)) - return []; - - var stringPath = GetImagePath(stringValue).TrimStart('/').ToLower(); - - if (!string.IsNullOrWhiteSpace(stringPath)) - { - return [new uSyncDependency() - { - Name = $"File: {Path.GetFileName(stringPath)}", - Udi = Udi.Create(Constants.UdiEntityType.MediaFile, stringPath), - Flags = flags, - Order = DependencyOrders.OrderFromEntityType(Constants.UdiEntityType.MediaFile), - Level = 0 - }]; - } - - return []; - }); - } - - private string GetImagePath(string stringValue) - { - if (stringValue.TryParseToJsonObject(out var json) is false || json is null) - return StripSitePath(stringValue); - - - if (json.TryGetPropertyValue("src", out var srcNode) is true) - { - var source = srcNode?.GetValue() ?? string.Empty; - if (string.IsNullOrWhiteSpace(source) is false) return source; - } - - return string.Empty; - } } diff --git a/uSync.Core/Mapping/Mappers/ImagePathMapperBase.cs b/uSync.Core/Mapping/Mappers/ImagePathMapperBase.cs new file mode 100644 index 00000000..b60b5579 --- /dev/null +++ b/uSync.Core/Mapping/Mappers/ImagePathMapperBase.cs @@ -0,0 +1,163 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +using System.Text.RegularExpressions; + +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; + +using uSync.Core.Dependency; +using uSync.Core.Extensions; + +namespace uSync.Core.Mapping; + +public abstract class ImagePathMapperBase : SyncValueMapperBase +{ + private readonly IConfiguration _configuration; + protected readonly ILogger _logger; + + private const string _genericMediaPath = "/media"; + private readonly string _siteRoot; + private string? _mediaFolder; + + public ImagePathMapperBase( + IEntityService entityService, + ILogger logger, + IConfiguration configuration, + IOptionsMonitor globalOptions + ) : base(entityService) + { + _configuration = configuration; + _logger = logger; + + // todo: site root might need us to include extra NuGet. + _siteRoot = ""; + + _mediaFolder = GetMediaFolderSetting(globalOptions.CurrentValue.UmbracoMediaPath.TrimStart('~')); + globalOptions.OnChange(x => _mediaFolder = GetMediaFolderSetting(x.UmbracoMediaPath.TrimStart('~'))); + + if (logger.IsEnabled(LogLevel.Debug)) + logger.LogDebug("Media Folders: [{media}]", _mediaFolder ?? "(Blank)"); + + } + + + protected string StripSitePath(string filePath) + { + var path = filePath; + if (_siteRoot.Length > 0 && !string.IsNullOrWhiteSpace(filePath) && filePath.InvariantStartsWith(_siteRoot)) + path = filePath.Substring(_siteRoot.Length); + + return ReplacePath(path, _mediaFolder, _genericMediaPath); + } + + protected string PrePendSitePath(string filePath) + { + var path = filePath; + if (_siteRoot.Length > 0 && !string.IsNullOrEmpty(filePath)) + path = $"{_siteRoot}{filePath}"; + + return ReplacePath(path, _genericMediaPath, _mediaFolder); + } + + + /// + /// makes a specific media path generic. + /// + /// + /// sometimes paths may be defined by umbraco settings, (especially blob settings) + /// that mean they are not stored as /media + /// + /// for the sake of generic importing we want the folder stored to be /media. + /// so we re-write the setting on import and export + /// + /// assumes you have a app setting in the web.config + /// + /// /someFolder + /// + /// + /// + private static string ReplacePath(string filePath, string? currentPath, string? targetPath) + { + if (!string.IsNullOrWhiteSpace(targetPath) + && !string.IsNullOrWhiteSpace(currentPath) + && !currentPath.Equals(targetPath)) + { + return Regex.Replace(filePath, $"^{currentPath}", targetPath, RegexOptions.IgnoreCase); + } + + return filePath; + } + + /// + /// Get the media rewrite folder + /// + /// + /// looks in appSettings for uSync:mediaFolder + /// + /// + /// + /// or in uSync8.config for media setting + /// + /// + /// + /// /someFolder + /// + /// + /// + private string GetMediaFolderSetting(string umbracoMediaPath) + { + var folder = this._configuration.GetValue("uSync:MediaFolder", string.Empty); + if (!string.IsNullOrEmpty(folder)) return folder; + + return umbracoMediaPath; + } + + /// + /// Get the actual media file as a dependency. + /// + public override Task> GetDependenciesAsync(object value, string editorAlias, DependencyFlags flags) + { + return uSyncTaskHelper.FromResultOf>(() => + { + + var stringValue = value?.ToString(); + if (string.IsNullOrWhiteSpace(stringValue)) + return []; + + var stringPath = GetImagePath(stringValue).TrimStart('/').ToLower(); + + if (!string.IsNullOrWhiteSpace(stringPath)) + { + return [new uSyncDependency() + { + Name = $"File: {Path.GetFileName(stringPath)}", + Udi = Udi.Create(Constants.UdiEntityType.MediaFile, stringPath), + Flags = flags, + Order = DependencyOrders.OrderFromEntityType(Constants.UdiEntityType.MediaFile), + Level = 0 + }]; + } + + return []; + }); + } + + private string GetImagePath(string stringValue) + { + if (stringValue.TryParseToJsonObject(out var json) is false || json is null) + return StripSitePath(stringValue); + + + if (json.TryGetPropertyValue("src", out var srcNode) is true) + { + var source = srcNode?.GetValue() ?? string.Empty; + if (string.IsNullOrWhiteSpace(source) is false) return source; + } + + return string.Empty; + } +} diff --git a/uSync.Core/Mapping/Mappers/ImageUploadMapper.cs b/uSync.Core/Mapping/Mappers/ImageUploadMapper.cs new file mode 100644 index 00000000..bd47dbf4 --- /dev/null +++ b/uSync.Core/Mapping/Mappers/ImageUploadMapper.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Services; + +using uSync.Core.Extensions; +using uSync.Core.Serialization; + +namespace uSync.Core.Mapping; + +/// +/// image uploads don't store any of the json, stuff, so they are similar to image croppers, +/// but a bit simpler. +/// +public class ImageUploadMapper : ImagePathMapperBase, ISyncMapper +{ + public ImageUploadMapper( + IEntityService entityService, + ILogger logger, + IConfiguration configuration, + IOptionsMonitor globalOptions) : base(entityService, logger, configuration, globalOptions) + { } + + public override string Name => "Image Upload Mapper"; + public override string[] Editors => [Constants.PropertyEditors.Aliases.UploadField]; + public override Task GetExportValueAsync(object value, string editorAlias) + { + return uSyncTaskHelper.FromResultOf(() => + { + if (_logger.IsEnabled(LogLevel.Debug)) + _logger.LogDebug("Getting export value for ImageUpload with value {Value}", value); + + var stringValue = value?.ToString(); + if (string.IsNullOrWhiteSpace(stringValue)) return stringValue; + return StripSitePath(stringValue); + }); + } + + public override Task GetImportValueAsync(string value, string editorAlias, SyncSerializerOptions options) + { + return uSyncTaskHelper.FromResultOf(() => + { + var stringValue = value?.ToString(); + if (string.IsNullOrWhiteSpace(stringValue)) return stringValue; + return PrePendSitePath(stringValue); + }); + } +} diff --git a/uSync.Core/Mapping/SyncBlockMapperBase.cs b/uSync.Core/Mapping/SyncBlockMapperBase.cs index b9d24030..267d288e 100644 --- a/uSync.Core/Mapping/SyncBlockMapperBase.cs +++ b/uSync.Core/Mapping/SyncBlockMapperBase.cs @@ -69,7 +69,8 @@ public SyncBlockMapperBase( // and prevent double-encoding when the block value is re-serialized. if (result is string stringResult && value.IsNonStringJsonValue()) { - return stringResult.ConvertToJsonNode() ?? result; + return stringResult.ConvertStringToExpandedJson() ?? result; + // return stringResult.ConvertToJsonNode() ?? result; } return result; diff --git a/uSync.History/history-client/package-lock.json b/uSync.History/history-client/package-lock.json index c9bd6758..426fb256 100644 --- a/uSync.History/history-client/package-lock.json +++ b/uSync.History/history-client/package-lock.json @@ -1,12 +1,12 @@ { "name": "usync-history-client", - "version": "17.3.2", + "version": "17.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "usync-history-client", - "version": "17.3.2", + "version": "17.3.3", "devDependencies": { "@hey-api/openapi-ts": "^0.95.0", "@jumoo/translate": "^17.2.3", diff --git a/uSync.History/history-client/package.json b/uSync.History/history-client/package.json index bca2bcfc..9696c20e 100644 --- a/uSync.History/history-client/package.json +++ b/uSync.History/history-client/package.json @@ -1,6 +1,6 @@ { "name": "usync-history-client", - "version": "17.3.2", + "version": "17.3.3", "licence": "Custom", "description": "uSync history function", "type": "module", diff --git a/uSync.Tests/packages.lock.json b/uSync.Tests/packages.lock.json index 2e15ba6c..8c9df6ff 100644 --- a/uSync.Tests/packages.lock.json +++ b/uSync.Tests/packages.lock.json @@ -2616,7 +2616,7 @@ }, "Umbraco.Cms": { "type": "CentralTransitive", - "requested": "[17.3.0, )", + "requested": "[17.4.2, )", "resolved": "17.3.0", "contentHash": "yaG/li5/5RT7354r6rloErE96HojsDEHyYE+iVxcb+ySCLn2PF424qVSXEYq5ZXAbN4Zbx2/6oytTA4HX5w2rg==", "dependencies": {