diff --git a/uSync.BackOffice/Services/SyncService.cs b/uSync.BackOffice/Services/SyncService.cs index e65c6161..67604ef2 100644 --- a/uSync.BackOffice/Services/SyncService.cs +++ b/uSync.BackOffice/Services/SyncService.cs @@ -1,8 +1,5 @@ using Microsoft.Extensions.Logging; -using Org.BouncyCastle.Asn1.Ocsp; -using Org.BouncyCastle.Tls; - using System; using System.Collections.Generic; using System.Diagnostics; @@ -26,6 +23,7 @@ using uSync.BackOffice.SyncHandlers.Models; using uSync.Core; using uSync.Core.Extensions; +using uSync.Core.Notifications; using uSync.Core.Serialization; namespace uSync.BackOffice; @@ -297,6 +295,9 @@ public bool CleanExportFolder(string folder) { if (_syncFileService.DirectoryExists(folder)) _syncFileService.CleanFolder(folder); + + // tell the migrations table to clean itself too. + _eventAggregator.Publish(new SyncExportCleanNotification()); } catch (Exception ex) { diff --git a/uSync.Core/DataTypes/ConfigurationSerializerCollectionBuilder.cs b/uSync.Core/DataTypes/ConfigurationSerializerCollectionBuilder.cs index de738376..81509ef6 100644 --- a/uSync.Core/DataTypes/ConfigurationSerializerCollectionBuilder.cs +++ b/uSync.Core/DataTypes/ConfigurationSerializerCollectionBuilder.cs @@ -18,6 +18,7 @@ public ConfigurationSerializerCollection(Func this.FirstOrDefault(x => x.IsSerializer(editorAlias)); @@ -39,4 +40,19 @@ public IEnumerable GetSerializers(string editorAlias) } return null; } + + /// + /// tells serializers that care about it that this is a rename + /// + /// + /// + public async Task TrackRenamedEditorAsync(string oldEditorAlias, string newEditorAlias) { + + foreach(var serializer in GetSerializers(oldEditorAlias)) + { + if (serializer is IConfigurationTrackingSerializer trackingSerializer) + await trackingSerializer.TrackRenamedEditorAsync(oldEditorAlias, newEditorAlias); + } + + } } diff --git a/uSync.Core/DataTypes/IConfigurationSerializer.cs b/uSync.Core/DataTypes/IConfigurationSerializer.cs index f96d1f9d..342c2fec 100644 --- a/uSync.Core/DataTypes/IConfigurationSerializer.cs +++ b/uSync.Core/DataTypes/IConfigurationSerializer.cs @@ -35,3 +35,8 @@ Task> GetConfigurationImportAsync(string name, IDict bool IsSerializer(string propertyName) => Editors.InvariantContains(propertyName); } + +public interface IConfigurationTrackingSerializer : IConfigurationSerializer +{ + Task TrackRenamedEditorAsync(string oldEditorAlias, string newEditorAlias); +} \ No newline at end of file diff --git a/uSync.Core/Mapping/ISyncMapper.cs b/uSync.Core/Mapping/ISyncMapper.cs index 1b73eee6..bc79e111 100644 --- a/uSync.Core/Mapping/ISyncMapper.cs +++ b/uSync.Core/Mapping/ISyncMapper.cs @@ -9,6 +9,9 @@ public interface ISyncMapper string Name { get; } string[] Editors { get; } + bool IsMapper(string editorAlias) + => Editors.Contains(editorAlias, StringComparer.OrdinalIgnoreCase); + bool IsMapper(PropertyType propertyType); Task GetExportValueAsync(object value, string editorAlias); diff --git a/uSync.Core/Mapping/SyncValueMapperBase.cs b/uSync.Core/Mapping/SyncValueMapperBase.cs index 55baf07f..6521a45f 100644 --- a/uSync.Core/Mapping/SyncValueMapperBase.cs +++ b/uSync.Core/Mapping/SyncValueMapperBase.cs @@ -28,6 +28,9 @@ public SyncValueMapperBase(IEntityService entityService) public abstract string[] Editors { get; } + public virtual bool IsMapper(string editorAlias) + => Editors.InvariantContains(editorAlias); + public virtual bool IsMapper(PropertyType propertyType) => Editors.InvariantContains(propertyType.PropertyEditorAlias); diff --git a/uSync.Core/Mapping/SyncValueMapperCollection.cs b/uSync.Core/Mapping/SyncValueMapperCollection.cs index 8c7453f9..3074e2ea 100644 --- a/uSync.Core/Mapping/SyncValueMapperCollection.cs +++ b/uSync.Core/Mapping/SyncValueMapperCollection.cs @@ -6,29 +6,31 @@ using uSync.Core.Cache; using uSync.Core.Extensions; -using uSync.Core.Migrations; +using uSync.Core.Mapping.Tracking; +using uSync.Core.Tracking; namespace uSync.Core.Mapping; public class SyncValueMapperCollection : BuilderCollectionBase { - private readonly ConcurrentDictionary _customMappings = new(StringComparer.InvariantCultureIgnoreCase); - private readonly ISyncMigratedDataService _migratedDataService; + private readonly SyncMapperTrackerCollection _mapperTrackers; + private readonly ConcurrentDictionary _customMappings = new(StringComparer.InvariantCultureIgnoreCase); + public SyncEntityCache EntityCache { get; private set; } public SyncValueMapperCollection( SyncEntityCache entityCache, Func> items, - ISyncMigratedDataService migratedDataService) + SyncMapperTrackerCollection mapperTrackers) : base(items) { EntityCache = entityCache; // todo, load these from config. _customMappings = []; - _migratedDataService = migratedDataService; + _mapperTrackers = mapperTrackers; } /// @@ -37,21 +39,17 @@ public SyncValueMapperCollection( public IEnumerable GetSyncMappers(string editorAlias) { var mappedAlias = GetMapperAlias(editorAlias); - return this.Where(x => x.Editors.InvariantContains(mappedAlias)); + return this.Where(m => m.IsMapper(mappedAlias)); } /// - /// will get any mappers and any mappers associated with the editor alias that have been migrated (if any) + /// will get any mappers and any mappers that are tracked for the editor alias, this is important because /// this allows us to support old mappers for a property editor, even if the property editor alias has changed. /// public async Task> GetImportingSyncMappers(string editorAlias) { - var mappers = new List(); - var importingAlias = await _migratedDataService.GetAsync(editorAlias); - if (importingAlias is not null) - mappers.AddRange(this.Where(x => x.Editors.InvariantContains(importingAlias.Orginal))); - - return [.. mappers, ..GetSyncMappers(editorAlias)]; + var mappers = await _mapperTrackers.GetMappersAsync(editorAlias); + return [.. mappers, .. GetSyncMappers(editorAlias)]; } /// diff --git a/uSync.Core/Mapping/Tracking/ISyncMapperTracker.cs b/uSync.Core/Mapping/Tracking/ISyncMapperTracker.cs new file mode 100644 index 00000000..57a049d9 --- /dev/null +++ b/uSync.Core/Mapping/Tracking/ISyncMapperTracker.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace uSync.Core.Mapping.Tracking; + +/// +/// tracking for when editors might change. +/// +/// +/// A tracker mapper can return additional ISyncMappers for an editor Alias +/// typically we do this when migrations has tracked that a new editorAlias +/// was once an a different editor alias. +/// +public interface ISyncMapperTracker +{ + Task> GetTrackingMappers(string editorAlias); +} diff --git a/uSync.Core/Mapping/Tracking/SyncMapperTrackerCollection.cs b/uSync.Core/Mapping/Tracking/SyncMapperTrackerCollection.cs new file mode 100644 index 00000000..89af6fde --- /dev/null +++ b/uSync.Core/Mapping/Tracking/SyncMapperTrackerCollection.cs @@ -0,0 +1,26 @@ +using Umbraco.Cms.Core.Composing; + +namespace uSync.Core.Mapping.Tracking; + +public class SyncMapperTrackerCollection + : BuilderCollectionBase +{ + public SyncMapperTrackerCollection(Func> items) + : base(items) + { } + + public async Task> GetMappersAsync(string editorAlias) + { + if (this.Count == 0) return Enumerable.Empty(); + + var tasks = this.Select(x => x.GetTrackingMappers(editorAlias)); + var results = await Task.WhenAll(tasks); + return results.SelectMany(x => x); + } +} + +public class SyncMapperTrackerCollectionBuilder + : LazyCollectionBuilderBase +{ + protected override SyncMapperTrackerCollectionBuilder This => this; +} diff --git a/uSync.Core/Migrations/ISyncMigratedDataRepository.cs b/uSync.Core/Migrations/ISyncMigratedDataRepository.cs deleted file mode 100644 index d26005a3..00000000 --- a/uSync.Core/Migrations/ISyncMigratedDataRepository.cs +++ /dev/null @@ -1,7 +0,0 @@ -using uSync.Core.Persistance; - -namespace uSync.Core.Migrations; - -public interface ISyncMigratedDataRepository : ISyncDataRespository -{ -} diff --git a/uSync.Core/Migrations/ISyncMigratedDataService.cs b/uSync.Core/Migrations/ISyncMigratedDataService.cs deleted file mode 100644 index 4311a500..00000000 --- a/uSync.Core/Migrations/ISyncMigratedDataService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using uSync.Core.Persistance; - -namespace uSync.Core.Migrations; - -public interface ISyncMigratedDataService : ISyncDataService -{ - Task AddRename(string newKey, string oldKey, string? additionalData); - Task GetOriginalKeyAsync(string key); -} \ No newline at end of file diff --git a/uSync.Core/Migrations/Migrations/CreateMigratedDataTable.cs b/uSync.Core/Migrations/Migrations/CreateMigratedDataTable.cs deleted file mode 100644 index 476c32c7..00000000 --- a/uSync.Core/Migrations/Migrations/CreateMigratedDataTable.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Umbraco.Cms.Infrastructure.Migrations; - -namespace uSync.Core.Migrations.Migrations; - -internal class CreateMigratedDataTable : AsyncMigrationBase -{ - public CreateMigratedDataTable(IMigrationContext context) : base(context) - { } - - protected override Task MigrateAsync() - { - if (!TableExists(SyncMigrations.MigratedDataTableName)) - Create.Table().Do(); - - return Task.CompletedTask; - } -} diff --git a/uSync.Core/Migrations/Migrations/SyncMigratedDataMigrationPlan.cs b/uSync.Core/Migrations/Migrations/SyncMigratedDataMigrationPlan.cs deleted file mode 100644 index 6370d2cb..00000000 --- a/uSync.Core/Migrations/Migrations/SyncMigratedDataMigrationPlan.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Umbraco.Cms.Infrastructure.Migrations; - -namespace uSync.Core.Migrations.Migrations; - -internal class SyncMigratedDataMigrationPlan : MigrationPlan -{ - public SyncMigratedDataMigrationPlan() - : base(SyncMigrations.AppName) - { - From(string.Empty) - .To("Add SyncMigratedData Table"); - } -} diff --git a/uSync.Core/Migrations/SyncMigratedData.cs b/uSync.Core/Migrations/SyncMigratedData.cs deleted file mode 100644 index bcb23d1b..00000000 --- a/uSync.Core/Migrations/SyncMigratedData.cs +++ /dev/null @@ -1,32 +0,0 @@ -using NPoco; - -using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations; - -using uSync.Core.Persistance; - -namespace uSync.Core.Migrations; - -/// -/// basic information about the migrated item. -/// -[TableName("uSyncMigratedData")] -[PrimaryKey("Id")] -[ExplicitColumns] -public class SyncMigratedData : ISyncDataEntity -{ - [Column("Id")] - [PrimaryKeyColumn] - public int Id { get; set; } - - [Column("Key")] - public required string Key { get; set; } - - [Column("Original")] - public required string Orginal { get; set; } - - [Column("Additional")] - [NullSetting(NullSetting = NullSettings.Null)] - [SpecialDbType(SpecialDbTypes.NVARCHARMAX)] - public string? AdditionalData { get; set; } - -} diff --git a/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs b/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs deleted file mode 100644 index af31ff21..00000000 --- a/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Migrations; -using Umbraco.Cms.Core.Notifications; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Infrastructure.Migrations.Upgrade; - -using uSync.Core.Migrations.Migrations; -using uSync.Core.Persistance; - -namespace uSync.Core.Migrations; - -internal static class SyncMigratedDataBuilderExtensions -{ - public static IUmbracoBuilder AddSyncMigratedData(this IUmbracoBuilder builder) - { - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.AddNotificationAsyncHandler(); - - return builder; - } -} - -internal class SyncMigratedDataMigrationHandler : INotificationAsyncHandler -{ - private readonly ICoreScopeProvider _scopeProvider; - private readonly IKeyValueService _keyValueService; - private readonly IRuntimeState _runtimeState; - private readonly IMigrationPlanExecutor _migrationPlanExecutor; - - public SyncMigratedDataMigrationHandler( - ICoreScopeProvider scopeProvider, - IKeyValueService keyValueService, - IRuntimeState runtimeState, - IMigrationPlanExecutor migrationPlanExecutor) - { - _scopeProvider = scopeProvider; - _keyValueService = keyValueService; - _runtimeState = runtimeState; - _migrationPlanExecutor = migrationPlanExecutor; - } - - public async Task HandleAsync(UmbracoApplicationStartingNotification notification, CancellationToken cancellationToken) - { - // we don't run our migration until the site has been installed / isn't upgrading. - if (_runtimeState.Level < RuntimeLevel.Run) return; - - var upgrader = new Upgrader(new SyncMigratedDataMigrationPlan()); - await upgrader.ExecuteAsync(_migrationPlanExecutor, _scopeProvider, _keyValueService); - - } -} \ No newline at end of file diff --git a/uSync.Core/Migrations/SyncMigratedDataCachePolicy.cs b/uSync.Core/Migrations/SyncMigratedDataCachePolicy.cs deleted file mode 100644 index 54738ea4..00000000 --- a/uSync.Core/Migrations/SyncMigratedDataCachePolicy.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Infrastructure.Scoping; - -using uSync.Core.Persistance; - -namespace uSync.Core.Migrations; - -internal class SyncMigratedDataCachePolicy : SyncDataRepositoryCachePolicy - , ISyncMigratedDataCachePolicy -{ - public SyncMigratedDataCachePolicy( - IAppPolicyCache globalCache, - IScopeAccessor scopeAccessor, - IRepositoryCacheVersionService repositoryCacheVersionService, - ICacheSyncService cacheSyncService) - : base(globalCache, scopeAccessor, repositoryCacheVersionService, cacheSyncService) - { } -} - -public interface ISyncMigratedDataCachePolicy - : ISyncDataRepositoryCachePolicy -{ } \ No newline at end of file diff --git a/uSync.Core/Migrations/SyncMigratedDataRepository.cs b/uSync.Core/Migrations/SyncMigratedDataRepository.cs deleted file mode 100644 index 337f6cc1..00000000 --- a/uSync.Core/Migrations/SyncMigratedDataRepository.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Infrastructure.Scoping; - -using uSync.Core.Persistance; - -namespace uSync.Core.Migrations; - -internal class SyncMigratedDataRepository - : SyncDataRespositoryBase, - ISyncMigratedDataRepository -{ - public SyncMigratedDataRepository( - IScopeAccessor scopeAccessor, - AppCaches appCaches, - ISyncMigratedDataCachePolicy cachePolicy) - : base(scopeAccessor, appCaches, cachePolicy, - SyncMigrations.MigratedDataTableName) - { } -} diff --git a/uSync.Core/Migrations/SyncMigratedDataService.cs b/uSync.Core/Migrations/SyncMigratedDataService.cs deleted file mode 100644 index 3725f297..00000000 --- a/uSync.Core/Migrations/SyncMigratedDataService.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Umbraco.Cms.Core.Scoping; - -using uSync.Core.Persistance; - -namespace uSync.Core.Migrations; - -internal class SyncMigratedDataService : SyncDataServiceBase, - ISyncMigratedDataService -{ - public SyncMigratedDataService( - ISyncMigratedDataRepository repository, - ICoreScopeProvider scopeProvider) : base(repository, scopeProvider) - { } - - /// - /// tells us if this property has had it's id migrated, - /// - public async Task GetOriginalKeyAsync(string key) - { - var item = await GetAsync(key); - return item?.Orginal ?? key; - } - - public async Task AddRename(string newKey, string oldKey, string? additionalData) - { - var item = new SyncMigratedData - { - Key = newKey, - Orginal = oldKey, - AdditionalData = additionalData - }; - await SaveAsync(item); - } -} diff --git a/uSync.Core/Migrations/SyncMigrations.cs b/uSync.Core/Migrations/SyncMigrations.cs deleted file mode 100644 index 2f56a735..00000000 --- a/uSync.Core/Migrations/SyncMigrations.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace uSync.Core.Migrations; - -internal static class SyncMigrations -{ - public const string AppName = "SyncMigratedData"; - public const string MigratedDataTableName = "uSyncMigratedData"; -} diff --git a/uSync.Core/Notifications/SyncExportCleanNotification.cs b/uSync.Core/Notifications/SyncExportCleanNotification.cs new file mode 100644 index 00000000..da4f6edf --- /dev/null +++ b/uSync.Core/Notifications/SyncExportCleanNotification.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Core.Notifications; + +namespace uSync.Core.Notifications; + +public class SyncExportCleanNotification : INotification +{ +} diff --git a/uSync.Core/Persistance/ISyncDataEntity.cs b/uSync.Core/Persistance/ISyncDataEntity.cs deleted file mode 100644 index ed4cf03c..00000000 --- a/uSync.Core/Persistance/ISyncDataEntity.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace uSync.Core.Persistance; - -public interface ISyncDataEntity -{ - int Id { get; set; } - - TId Key { get; } - - bool HasIdentity => Id > 0; -} diff --git a/uSync.Core/Persistance/ISyncDataRepositoryCachePolicy.cs b/uSync.Core/Persistance/ISyncDataRepositoryCachePolicy.cs deleted file mode 100644 index 211f665d..00000000 --- a/uSync.Core/Persistance/ISyncDataRepositoryCachePolicy.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace uSync.Core.Persistance; - -public interface ISyncDataRepositoryCachePolicy where TModel : class, ISyncDataEntity -{ - void ClearAllAsync(); - Task CreateAsync(TModel model, Func persistNewAsync, CancellationToken cancellationToken = default); - Task DeleteAsync(TModel model, Func persistDeleteAsync, CancellationToken cancellationToken = default); - Task ExistsAsync(TKey key, Func> performExistsAsync, CancellationToken cancellationToken = default); - Task GetAllAsync(TKey[]? keys, Func>> performGetAllAsync, CancellationToken cancellationToken = default); - Task GetAsync(TKey key, Func> performGetAsync, CancellationToken cancellationToken = default); - Task UpdateAsync(TModel model, Func persistUpdateAsync, CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/uSync.Core/Persistance/ISyncDataRespository.cs b/uSync.Core/Persistance/ISyncDataRespository.cs deleted file mode 100644 index e4269d9f..00000000 --- a/uSync.Core/Persistance/ISyncDataRespository.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace uSync.Core.Persistance; - -public interface ISyncDataRespository where TModel : class, ISyncDataEntity -{ - Task CreateAsync(TModel item); - Task DeleteAsync(TModel item); - Task ExistsAsync(TKey key); - Task> GetAllAsync(params TKey[] keys); - Task GetAsync(TKey key); - Task UpdateAsync(TModel item); -} \ No newline at end of file diff --git a/uSync.Core/Persistance/ISyncDataService.cs b/uSync.Core/Persistance/ISyncDataService.cs deleted file mode 100644 index 78fe74ed..00000000 --- a/uSync.Core/Persistance/ISyncDataService.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace uSync.Core.Persistance; - -public interface ISyncDataService where TModel : class, ISyncDataEntity -{ - Task CreateAsync(TModel item); - Task DeleteAsync(TModel item); - Task ExistsAsync(TKey key); - Task> GetAllAsync(params TKey[] keys); - Task GetAsync(TKey key); - Task SaveAsync(TModel item); - Task UpdateAsync(TModel item); -} \ No newline at end of file diff --git a/uSync.Core/Persistance/SyncDataRepositoryCachePolicy.cs b/uSync.Core/Persistance/SyncDataRepositoryCachePolicy.cs deleted file mode 100644 index 3e2f25eb..00000000 --- a/uSync.Core/Persistance/SyncDataRepositoryCachePolicy.cs +++ /dev/null @@ -1,229 +0,0 @@ -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Infrastructure.Scoping; -using Umbraco.Extensions; - -using IScope = Umbraco.Cms.Infrastructure.Scoping.IScope; - -namespace uSync.Core.Persistance; - -internal class SyncDataRepositoryCachePolicy : ISyncDataRepositoryCachePolicy where TModel : class, ISyncDataEntity -{ - private readonly IAppPolicyCache _globalCache; - private readonly IScopeAccessor _scopeAccessor; - private readonly IRepositoryCacheVersionService _repositoryCacheVersionService; - private readonly ICacheSyncService _cacheSyncService; - - public SyncDataRepositoryCachePolicy( - IAppPolicyCache globalCache, - IScopeAccessor scopeAccessor, - IRepositoryCacheVersionService repositoryCacheVersionService, - ICacheSyncService cacheSyncService) - { - _globalCache = globalCache; - _scopeAccessor = scopeAccessor; - _repositoryCacheVersionService = repositoryCacheVersionService; - _cacheSyncService = cacheSyncService; - } - - private IAppPolicyCache Cache - { - get - { - IScope? ambientScope = _scopeAccessor.AmbientScope; - return (ambientScope?.RepositoryCacheMode) switch - { - RepositoryCacheMode.Default => _globalCache, - RepositoryCacheMode.Scoped => ambientScope.IsolatedCaches.GetOrCreate(), - RepositoryCacheMode.None => NoAppCache.Instance, - _ => throw new NotSupportedException($"RepositoryCacheMode {ambientScope?.RepositoryCacheMode} is not supported"), - }; - } - } - - - private string _modelCacheKey => $"{nameof(TModel)}"; - private static readonly TModel[] _emptyArray = []; - - public async Task GetAsync(TKey key, Func> performGetAsync, CancellationToken cancellationToken = default) - { - await EnsureCacheIsSyncedAsync(); - var cacheKey = GetCacheKey(key); - - TModel? fromCache = Cache.GetCacheItem(cacheKey); - if (fromCache is not null) return fromCache; - - // Cache the null result to prevent repeated lookups for missing items - if (Cache.GetCacheItem(cacheKey) == Constants.Cache.NullRepresentationInCache) - return null; - - TModel? model = await performGetAsync(key); - - InsertIntoCache(cacheKey, model); - - return model; - } - - public async Task ExistsAsync(TKey key, Func> performExistsAsync, CancellationToken cancellationToken = default) - { - await EnsureCacheIsSyncedAsync(); - var cacheKey = GetCacheKey(key); - TModel? fromCache = Cache.GetCacheItem(cacheKey); - return fromCache is not null || await performExistsAsync(key); - } - - public async Task CreateAsync(TModel model, Func persistNewAsync, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(model); - - try - { - await persistNewAsync(model); - Cache.Insert(GetCacheKey(model), () => model, TimeSpan.FromMinutes(5), true); - } - catch - { - Cache.Clear(GetCacheKey(model)); - Cache.Clear(_modelCacheKey); - throw; - } - - await RegisterCacheChangeAsync(); - } - - public async Task UpdateAsync(TModel model, Func persistUpdateAsync, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(model); - try - { - await persistUpdateAsync(model); - Cache.Insert(GetCacheKey(model), () => model, TimeSpan.FromMinutes(5), true); - } - catch - { - Cache.Clear(GetCacheKey(model)); - Cache.Clear(_modelCacheKey); - throw; - } - - await RegisterCacheChangeAsync(); - } - - public async Task DeleteAsync(TModel model, Func persistDeleteAsync, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(model); - - try - { - await persistDeleteAsync(model); - } - finally - { - Cache.Clear(GetCacheKey(model)); - Cache.Clear(_modelCacheKey); - } - - await RegisterCacheChangeAsync(); - } - - public virtual async Task GetAllAsync(TKey[]? keys, Func>> performGetAllAsync, CancellationToken cancellationToken = default) - { - await EnsureCacheIsSyncedAsync(); - - if (keys?.Length > 0) - { - var items = await keys.ToAsyncEnumerable().Select(async (x, ct) => await GetCached(x, ct)) - .Where(x => x != null) - .ToArrayAsync(); - - if (items != null && keys.Length.Equals(items.Length)) - return [.. items.WhereNotNull()]; - } - else - { - // TODO - we could choose to do a count, (cached vs db) to verify ? - var items = Cache.GetCacheItemsByKeySearch(_modelCacheKey) - .WhereNotNull().ToArray(); - - if (items.Length > 0) - return items; - else - { - // when the cache is empty we check for the special 'empty' one. - TModel[]? empty = Cache.GetCacheItem(_modelCacheKey); - if (empty != null) - return empty; - } - } - - var models = (await performGetAllAsync(keys)) - .WhereNotNull() - .ToArray(); - - InsertIntoCache(models); - - return models ?? []; - } - - private string GetCacheKey(TModel model) => GetCacheKey(model.Key); - - private string GetCacheKey(TKey key) - { - if (EqualityComparer.Default.Equals(key, default)) - return string.Empty; - - if (typeof(TKey).IsValueType) - return $"{_modelCacheKey}_{key}"; - - return $"{_modelCacheKey}_{key?.ToString()?.ToUpperInvariant()}"; - } - - private async Task EnsureCacheIsSyncedAsync() - { - var synced = await _repositoryCacheVersionService.IsCacheSyncedAsync(); - if (synced) return; - - _cacheSyncService.SyncInternal(CancellationToken.None); - } - - - private async Task RegisterCacheChangeAsync() - => await _repositoryCacheVersionService.SetCacheUpdatedAsync(); - - private void InsertIntoCache(string cacheKey, TModel? model) - { - if (model is null) - { - Cache.Insert(cacheKey, () => Constants.Cache.NullRepresentationInCache, TimeSpan.FromMinutes(5), true); - } - else - { - Cache.Insert(cacheKey, () => model, TimeSpan.FromMinutes(5), true); - } - } - - private void InsertIntoCache(TModel[]? models) - { - if (models == null || models.Length == 0) - { - Cache.Insert(_modelCacheKey, () => _emptyArray, TimeSpan.FromMinutes(5), true); - return; - } - - foreach (var model in models) - { - InsertIntoCache(GetCacheKey(model), model); - } - } - - private async Task GetCached(TKey key, CancellationToken cancellationToken) - { - await EnsureCacheIsSyncedAsync(); - var cacheKey = GetCacheKey(key); - return Cache.GetCacheItem(cacheKey); - } - - public void ClearAllAsync() - => Cache.ClearByKey(_modelCacheKey); -} diff --git a/uSync.Core/Persistance/SyncDataRespositoryBase.cs b/uSync.Core/Persistance/SyncDataRespositoryBase.cs deleted file mode 100644 index de239cc7..00000000 --- a/uSync.Core/Persistance/SyncDataRespositoryBase.cs +++ /dev/null @@ -1,143 +0,0 @@ -using NPoco; - -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; -using Umbraco.Cms.Infrastructure.Scoping; -using Umbraco.Extensions; - -namespace uSync.Core.Persistance; - -internal abstract class SyncDataRespositoryBase : ISyncDataRespository - where TModel : class, ISyncDataEntity -{ - protected readonly ISyncDataRepositoryCachePolicy _cachePolicy; - protected readonly IScopeAccessor _scopeAccessor; - protected readonly AppCaches _appCaches; - - protected readonly string _tableName; - - public SyncDataRespositoryBase( - IScopeAccessor scopeAccessor, - AppCaches appCaches, - ISyncDataRepositoryCachePolicy cachePolicy, - string tableName) - { - _scopeAccessor = scopeAccessor; - _appCaches = appCaches; - _cachePolicy = cachePolicy; - - _tableName = tableName; - } - - protected IScope AmbientScope - { - get - { - - { - var scope = _scopeAccessor.AmbientScope - ?? throw new InvalidOperationException("No ambient scope found"); - return scope; - } - } - } - - protected IUmbracoDatabase Database => AmbientScope.Database; - protected ISqlContext SqlContext => AmbientScope.SqlContext; - protected ISqlSyntaxProvider SqlSyntax => SqlContext.SqlSyntax; - protected Sql Sql() => SqlContext.Sql(); - protected Sql Sql(string sql, params object[] args) - => SqlContext.Sql(sql, args); - - protected virtual Sql GetBaseQuery(bool isCount) - => isCount - ? Sql().SelectCount().From() - : Sql().SelectAll().From(); - - protected virtual string GetBaseWhereClause() - => $"{SqlSyntax.GetQuotedColumnName("Key")} = @Key"; - - protected virtual IEnumerable GetDeleteClauses() - => [$"DELETE FROM {_tableName} WHERE {SqlSyntax.GetQuotedColumnName("Key")} = @Key"]; - - public virtual async Task CreateAsync(TModel item) - => await _cachePolicy.CreateAsync(item, PersistNewItemAsync); - - public virtual async Task UpdateAsync(TModel item) - => await _cachePolicy.UpdateAsync(item, PersistUpdatedItemAsync); - - public virtual async Task DeleteAsync(TModel item) - => await _cachePolicy.DeleteAsync(item, PersistDeletedItemAsync); - - public virtual async Task ExistsAsync(Key key) - => await _cachePolicy.ExistsAsync(key, PerformExistsAsync); - - public virtual async Task GetAsync(Key key) - => await _cachePolicy.GetAsync(key, PerformGetAsync); - - public virtual async Task> GetAllAsync(params Key[] keys) - => await _cachePolicy.GetAllAsync(keys, PerformGetAllAsync); - - private async Task PersistNewItemAsync(TModel model) - { - if (await ExistsAsync(model.Key)) - throw new InvalidOperationException($"An item with the id {model.Key} already exists."); - - using (var transaction = Database.GetTransaction()) - { - await Database.InsertAsync(model); - transaction.Complete(); - } - } - - private async Task PersistUpdatedItemAsync(TModel model) - { - if (await ExistsAsync(model.Key) == false) - throw new InvalidOperationException($"An item with the key {model.Key} does not exist."); - - using (var transaction = Database.GetTransaction()) - { - await Database.UpdateAsync(model); - transaction.Complete(); - } - } - - private async Task PersistDeletedItemAsync(TModel model) - { - var deletes = GetDeleteClauses(); - foreach (var delete in deletes) - { - await Database.ExecuteAsync(delete, new { model.Key }); - } - } - - private async Task PerformExistsAsync(Key key) - { - var sql = GetBaseQuery(true) - .Where(GetBaseWhereClause(), new { Key = key }); - return await Database.ExecuteScalarAsync(sql) > 0; - } - - private async Task PerformGetAsync(Key id) - { - var sql = GetBaseQuery(false) - .Where(GetBaseWhereClause(), new { Key = id }); - - return await Database.FirstOrDefaultAsync(sql); - } - - private async Task> PerformGetAllAsync(Key[]? keys) - { - var sql = GetBaseQuery(false); - - if (keys is null || keys.Length == 0) - return await Database.FetchAsync(sql); - - var uniqueIds = keys.Distinct().ToArray(); - sql.Where($"{SqlSyntax.GetQuotedColumnName("Key")} IN (@Keys)", new { Keys = uniqueIds }); - - return await Database.FetchAsync(sql); - } - -} \ No newline at end of file diff --git a/uSync.Core/Persistance/SyncDataServiceBase.cs b/uSync.Core/Persistance/SyncDataServiceBase.cs deleted file mode 100644 index ef530a15..00000000 --- a/uSync.Core/Persistance/SyncDataServiceBase.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Umbraco.Cms.Core.Scoping; - -namespace uSync.Core.Persistance; - -internal class SyncDataServiceBase : ISyncDataService - where TModel : class, ISyncDataEntity -{ - protected readonly ISyncDataRespository Repository; - protected readonly ICoreScopeProvider ScopeProvider; - - public SyncDataServiceBase( - ISyncDataRespository repository, - ICoreScopeProvider scopeProvider) - { - Repository = repository; - ScopeProvider = scopeProvider; - } - - public virtual async Task CreateAsync(TModel item) - { - using var scope = ScopeProvider.CreateCoreScope(autoComplete: true); - await Repository.CreateAsync(item); - scope.Complete(); - } - - public virtual async Task UpdateAsync(TModel item) - { - using var scope = ScopeProvider.CreateCoreScope(autoComplete: true); - await Repository.UpdateAsync(item); - scope.Complete(); - } - - public virtual async Task SaveAsync(TModel item) - { - var existing = await GetAsync(item.Key); - if (existing != null) - { - item.Id = existing.Id; - await UpdateAsync(item); - } - else - { - await CreateAsync(item); - } - } - - public virtual async Task DeleteAsync(TModel item) - { - using var scope = ScopeProvider.CreateCoreScope(autoComplete: true); - await Repository.DeleteAsync(item); - scope.Complete(); - } - - public virtual async Task ExistsAsync(TKey key) - { - using var scope = ScopeProvider.CreateCoreScope(autoComplete: true); - return await Repository.ExistsAsync(key); - } - - public virtual async Task GetAsync(TKey key) - { - using var scope = ScopeProvider.CreateCoreScope(autoComplete: true); - return await Repository.GetAsync(key); - } - - public virtual async Task> GetAllAsync(params TKey[] keys) - { - using var scope = ScopeProvider.CreateCoreScope(autoComplete: true); - return await Repository.GetAllAsync(keys); - } -} diff --git a/uSync.Core/Serialization/Serializers/DataTypeSerializer.cs b/uSync.Core/Serialization/Serializers/DataTypeSerializer.cs index 898cf574..4be86480 100644 --- a/uSync.Core/Serialization/Serializers/DataTypeSerializer.cs +++ b/uSync.Core/Serialization/Serializers/DataTypeSerializer.cs @@ -12,7 +12,6 @@ using uSync.Core.DataTypes; using uSync.Core.Extensions; -using uSync.Core.Migrations; using uSync.Core.Models; namespace uSync.Core.Serialization.Serializers; @@ -26,7 +25,6 @@ public class DataTypeSerializer : SyncContainerSerializerBase, ISyncS private readonly ConfigurationSerializerCollection _configurationSerializers; private readonly PropertyEditorCollection _propertyEditors; private readonly IConfigurationEditorJsonSerializer _jsonSerializer; - private readonly ISyncMigratedDataService _migratedDataService; public DataTypeSerializer(IEntityService entityService, ILogger logger, IDataTypeService dataTypeService, @@ -34,17 +32,15 @@ public DataTypeSerializer(IEntityService entityService, ILogger @@ -120,10 +116,10 @@ protected override async Task> DeserializeCoreAsync(XElem var editor = FindDataEditor(editorAlias); if (editorAlias != item.EditorAlias) { - // change the editor type..... - - // we put this in the migrator service, because it means the value has been migrated. - await _migratedDataService.AddRename(item.EditorAlias, editorAlias, null); + // use the configuration serializers to track the rename. + // uSync.migrations can use this to hook into the rename here, so we don't + // have to track it in the core. + await _configurationSerializers.TrackRenamedEditorAsync(item.EditorAlias, editorAlias); if (editor is not null) { diff --git a/uSync.Core/uSync.Core.csproj b/uSync.Core/uSync.Core.csproj index b4bd0c1e..a11102e6 100644 --- a/uSync.Core/uSync.Core.csproj +++ b/uSync.Core/uSync.Core.csproj @@ -28,4 +28,8 @@ + + + + diff --git a/uSync.Core/uSyncCoreBuilderExtensions.cs b/uSync.Core/uSyncCoreBuilderExtensions.cs index be18bd73..66192bee 100644 --- a/uSync.Core/uSyncCoreBuilderExtensions.cs +++ b/uSync.Core/uSyncCoreBuilderExtensions.cs @@ -8,7 +8,7 @@ using uSync.Core.Dependency; using uSync.Core.Documents; using uSync.Core.Mapping; -using uSync.Core.Migrations; +using uSync.Core.Mapping.Tracking; using uSync.Core.Roots.Configs; using uSync.Core.Serialization; using uSync.Core.Tracking; @@ -35,9 +35,6 @@ public static IUmbracoBuilder AdduSyncCore(this IUmbracoBuilder builder) builder.Services.AddSingleton(); - // migration data, so we can see when things go between types. - builder.AddSyncMigratedData(); - // document url cleaner, for key changes builder.Services.AddSingleton(); @@ -50,10 +47,15 @@ public static IUmbracoBuilder AdduSyncCore(this IUmbracoBuilder builder) builder.WithCollectionBuilder() .Add(builder.TypeLoader.GetTypes()); + // mapping trackers for when editor aliases might have changed + builder.WithCollectionBuilder() + .Add(() => builder.TypeLoader.GetTypes()); + // value mappers, (map internal things in properties in and out of syncing process) builder.WithCollectionBuilder() .Add(builder.TypeLoader.GetTypes()); + // serializers - turn umbraco objects into / from xml in memory. builder.WithCollectionBuilder() .Add(builder.TypeLoader.GetTypes());