From c605d3f167178771aff380560746901ac14a14da Mon Sep 17 00:00:00 2001 From: Aaron Sadler Date: Sun, 30 Jun 2024 20:28:35 +0100 Subject: [PATCH 1/3] Moved Database read, insert and cache refresher to reusable services --- .../Caching/CacheRefresher.cs | 8 ++- .../Composers/RegisterServicesComposer.cs | 16 ++++++ .../Config/PageNotFoundConfig.cs | 53 ++++-------------- .../Services/CacheService.cs | 25 +++++++++ .../Services/DatabaseService.cs | 56 +++++++++++++++++++ .../Services/ICacheService.cs | 7 +++ .../Services/IDatabaseService.cs | 11 ++++ 7 files changed, 133 insertions(+), 43 deletions(-) create mode 100644 src/HC.PageNotFoundManager/Composers/RegisterServicesComposer.cs create mode 100644 src/HC.PageNotFoundManager/Services/CacheService.cs create mode 100644 src/HC.PageNotFoundManager/Services/DatabaseService.cs create mode 100644 src/HC.PageNotFoundManager/Services/ICacheService.cs create mode 100644 src/HC.PageNotFoundManager/Services/IDatabaseService.cs diff --git a/src/HC.PageNotFoundManager/Caching/CacheRefresher.cs b/src/HC.PageNotFoundManager/Caching/CacheRefresher.cs index e817603..5af7c63 100644 --- a/src/HC.PageNotFoundManager/Caching/CacheRefresher.cs +++ b/src/HC.PageNotFoundManager/Caching/CacheRefresher.cs @@ -1,6 +1,7 @@ using System; using HC.PageNotFoundManager.Config; using HC.PageNotFoundManager.Models; +using HC.PageNotFoundManager.Services; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; @@ -14,15 +15,18 @@ public class PageNotFoundCacheRefresher : PayloadCacheRefresherBase "PageNotFoundManager Cache Refresher"; @@ -36,7 +40,7 @@ public override void Refresh(PageNotFoundRequest[] payloads) config.SetNotFoundPage(payload.ParentId, payload.NotFoundPageId, false); } - config.RefreshCache(); + cacheService.RefreshCache(); } } } \ No newline at end of file diff --git a/src/HC.PageNotFoundManager/Composers/RegisterServicesComposer.cs b/src/HC.PageNotFoundManager/Composers/RegisterServicesComposer.cs new file mode 100644 index 0000000..321452f --- /dev/null +++ b/src/HC.PageNotFoundManager/Composers/RegisterServicesComposer.cs @@ -0,0 +1,16 @@ +using HC.PageNotFoundManager.Services; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; + +namespace HC.PageNotFoundManager.Composers +{ + public class RegisterServicesComposer : IComposer + { + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddTransient(); + builder.Services.AddTransient(); + } + } +} diff --git a/src/HC.PageNotFoundManager/Config/PageNotFoundConfig.cs b/src/HC.PageNotFoundManager/Config/PageNotFoundConfig.cs index 30103a3..f39e8ed 100644 --- a/src/HC.PageNotFoundManager/Config/PageNotFoundConfig.cs +++ b/src/HC.PageNotFoundManager/Config/PageNotFoundConfig.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using HC.PageNotFoundManager.Services; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Infrastructure.Scoping; -using Umbraco.Extensions; namespace HC.PageNotFoundManager.Config { @@ -18,15 +18,21 @@ public class PageNotFoundConfig : IPageNotFoundConfig private readonly IUmbracoContextFactory umbracoContextFactory; + private readonly IDatabaseService databaseService; + + private readonly ICacheService cacheService; + public PageNotFoundConfig( IScopeProvider scopeProvider, IUmbracoContextFactory umbracoContextFactory, - IAppPolicyCache appPolicyCache) + IAppPolicyCache appPolicyCache, IDatabaseService databaseService, ICacheService cacheService) { this.scopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider)); this.umbracoContextFactory = umbracoContextFactory ?? throw new ArgumentNullException(nameof(umbracoContextFactory)); this.appPolicyCache = appPolicyCache ?? throw new ArgumentNullException(nameof(appPolicyCache)); + this.databaseService = databaseService; + this.cacheService = cacheService; } private List ConfiguredPages @@ -56,12 +62,6 @@ public int GetNotFoundPage(Guid parentKey) return page != null ? page.Id : 0; } - public void RefreshCache() - { - appPolicyCache.ClearByKey(CacheKey); - appPolicyCache.Insert(CacheKey, LoadFromDb); - } - public void SetNotFoundPage(int parentId, int pageNotFoundId, bool refreshCache) { using var umbracoContext = umbracoContextFactory.EnsureUmbracoContext(); @@ -72,44 +72,17 @@ public void SetNotFoundPage(int parentId, int pageNotFoundId, bool refreshCache) public void SetNotFoundPage(Guid parentKey, Guid pageNotFoundKey, bool refreshCache) { - using (var scope = scopeProvider.CreateScope()) - { - var db = scope.Database; - var page = db.Query().Where(p => p.ParentId == parentKey).FirstOrDefault(); - if (page == null && !Guid.Empty.Equals(pageNotFoundKey)) - { - // create the page - db.Insert(new Models.PageNotFound { ParentId = parentKey, NotFoundPageId = pageNotFoundKey }); - } - else if (page != null) - { - if (Guid.Empty.Equals(pageNotFoundKey)) - { - db.Delete(page); - } - else - { - // update the existing page - page.NotFoundPageId = pageNotFoundKey; - db.Update(Models.PageNotFound.TableName, "ParentId", page); - } - } - - scope.Complete(); - } + databaseService.InsertToDb(parentKey, pageNotFoundKey); if (refreshCache) { - RefreshCache(); + cacheService.RefreshCache(); } } - private List LoadFromDb() + private IEnumerable LoadFromDb() { - using var scope = scopeProvider.CreateScope(autoComplete: true); - var sql = scope.SqlContext.Sql().Select("*").From(); - var pages = scope.Database.Fetch(sql); - scope.Complete(); + var pages = databaseService.LoadFromDb(); return pages; } } @@ -120,8 +93,6 @@ public interface IPageNotFoundConfig int GetNotFoundPage(Guid parentKey); - void RefreshCache(); - void SetNotFoundPage(int parentId, int pageNotFoundId, bool refreshCache); void SetNotFoundPage(Guid parentKey, Guid pageNotFoundKey, bool refreshCache); diff --git a/src/HC.PageNotFoundManager/Services/CacheService.cs b/src/HC.PageNotFoundManager/Services/CacheService.cs new file mode 100644 index 0000000..20f5a09 --- /dev/null +++ b/src/HC.PageNotFoundManager/Services/CacheService.cs @@ -0,0 +1,25 @@ +using Umbraco.Cms.Core.Cache; + +namespace HC.PageNotFoundManager.Services +{ + internal class CacheService : ICacheService + { + private const string CacheKey = "PageNotFoundConfig"; + + private readonly IAppPolicyCache appPolicyCache; + + private readonly IDatabaseService databaseService; + + public CacheService(IAppPolicyCache appPolicyCache, IDatabaseService databaseService) + { + this.appPolicyCache = appPolicyCache; + this.databaseService = databaseService; + } + + public void RefreshCache() + { + appPolicyCache.ClearByKey(CacheKey); + appPolicyCache.Insert(CacheKey, databaseService.LoadFromDb); + } + } +} diff --git a/src/HC.PageNotFoundManager/Services/DatabaseService.cs b/src/HC.PageNotFoundManager/Services/DatabaseService.cs new file mode 100644 index 0000000..9401abd --- /dev/null +++ b/src/HC.PageNotFoundManager/Services/DatabaseService.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using HC.PageNotFoundManager.Notifications; +using Umbraco.Cms.Infrastructure.Scoping; +using Umbraco.Extensions; + +namespace HC.PageNotFoundManager.Services +{ + internal class DatabaseService : IDatabaseService + { + private readonly IScopeProvider scopeProvider; + + public DatabaseService(IScopeProvider scopeProvider) + { + this.scopeProvider = scopeProvider; + } + + public IEnumerable LoadFromDb() + { + using var scope = scopeProvider.CreateScope(autoComplete: true); + var sql = scope.SqlContext.Sql().Select("*").From(); + var pages = scope.Database.Fetch(sql); + scope.Complete(); + return pages; + } + + public void InsertToDb(Guid parentKey, Guid pageNotFoundKey) + { + using var scope = scopeProvider.CreateScope(); + var db = scope.Database; + var page = db.Query().Where(p => p.ParentId == parentKey).FirstOrDefault(); + if (page == null && !Guid.Empty.Equals(pageNotFoundKey)) + { + // create the page + db.Insert(new Models.PageNotFound { ParentId = parentKey, NotFoundPageId = pageNotFoundKey }); + } + else if (page != null) + { + if (Guid.Empty.Equals(pageNotFoundKey)) + { + db.Delete(page); + } + else + { + // update the existing page + page.NotFoundPageId = pageNotFoundKey; + db.Update(Models.PageNotFound.TableName, "ParentId", page); + } + } + + scope.Notifications.Publish(new OnConfigurationSavedNotification(LoadFromDb())); + + scope.Complete(); + } + } +} diff --git a/src/HC.PageNotFoundManager/Services/ICacheService.cs b/src/HC.PageNotFoundManager/Services/ICacheService.cs new file mode 100644 index 0000000..b080010 --- /dev/null +++ b/src/HC.PageNotFoundManager/Services/ICacheService.cs @@ -0,0 +1,7 @@ +namespace HC.PageNotFoundManager.Services +{ + public interface ICacheService + { + void RefreshCache(); + } +} diff --git a/src/HC.PageNotFoundManager/Services/IDatabaseService.cs b/src/HC.PageNotFoundManager/Services/IDatabaseService.cs new file mode 100644 index 0000000..3380b98 --- /dev/null +++ b/src/HC.PageNotFoundManager/Services/IDatabaseService.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; + +namespace HC.PageNotFoundManager.Services +{ + public interface IDatabaseService + { + IEnumerable LoadFromDb(); + void InsertToDb(Guid parentKey, Guid pageNotFoundKey); + } +} From 60b8978afa4b0522af299947b77e295a9d9f038d Mon Sep 17 00:00:00 2001 From: Aaron Sadler Date: Sun, 30 Jun 2024 20:28:48 +0100 Subject: [PATCH 2/3] Added a Notification for uSync to listed out for --- .../OnConfigurationSavedNotification.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/HC.PageNotFoundManager/Notifications/OnConfigurationSavedNotification.cs diff --git a/src/HC.PageNotFoundManager/Notifications/OnConfigurationSavedNotification.cs b/src/HC.PageNotFoundManager/Notifications/OnConfigurationSavedNotification.cs new file mode 100644 index 0000000..01d319d --- /dev/null +++ b/src/HC.PageNotFoundManager/Notifications/OnConfigurationSavedNotification.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Notifications; + +namespace HC.PageNotFoundManager.Notifications +{ + public class OnConfigurationSavedNotification : INotification + { + public IEnumerable? Configuration { get; } + + public OnConfigurationSavedNotification(IEnumerable? configuration) + { + Configuration = configuration; + } + } +} From 326e73c3833e0e60c4fba34d2715567fca53e65f Mon Sep 17 00:00:00 2001 From: Aaron Sadler Date: Sun, 30 Jun 2024 20:29:01 +0100 Subject: [PATCH 3/3] Added uSync Project --- .../Composers/RegisterNotifications.cs | 15 +++ src/HC.PageNotFoundManager.uSync/Consts.cs | 22 +++++ .../HC.PageNotFoundManager.uSync.csproj | 54 ++++++++++ .../Handlers/PageNotFoundManagerHandler.cs | 99 +++++++++++++++++++ .../PageNotFoundManagerSerializer.cs | 91 +++++++++++++++++ 5 files changed, 281 insertions(+) create mode 100644 src/HC.PageNotFoundManager.uSync/Composers/RegisterNotifications.cs create mode 100644 src/HC.PageNotFoundManager.uSync/Consts.cs create mode 100644 src/HC.PageNotFoundManager.uSync/HC.PageNotFoundManager.uSync.csproj create mode 100644 src/HC.PageNotFoundManager.uSync/Handlers/PageNotFoundManagerHandler.cs create mode 100644 src/HC.PageNotFoundManager.uSync/Serializers/PageNotFoundManagerSerializer.cs diff --git a/src/HC.PageNotFoundManager.uSync/Composers/RegisterNotifications.cs b/src/HC.PageNotFoundManager.uSync/Composers/RegisterNotifications.cs new file mode 100644 index 0000000..2cefb96 --- /dev/null +++ b/src/HC.PageNotFoundManager.uSync/Composers/RegisterNotifications.cs @@ -0,0 +1,15 @@ +using HC.PageNotFoundManager.Notifications; +using HC.PageNotFoundManager.uSync.Handlers; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; + +namespace HC.PageNotFoundManager.uSync.Composers +{ + public class RegisterNotifications : IComposer + { + public void Compose(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + } + } +} diff --git a/src/HC.PageNotFoundManager.uSync/Consts.cs b/src/HC.PageNotFoundManager.uSync/Consts.cs new file mode 100644 index 0000000..dbfe2c9 --- /dev/null +++ b/src/HC.PageNotFoundManager.uSync/Consts.cs @@ -0,0 +1,22 @@ +namespace HC.PageNotFoundManager.uSync +{ + internal static class Consts + { + public const string Group = "PageNotFoundManager"; + + public static class Configuration + { + public const string ItemType = "PageNotFoundManagerConfiguration"; + + public const string SerializerName = "PageNotFoundManager Configuration Serializer"; + + public const string HandlerName = "Configuration"; + + public const string SerializerFolder = "PageNotFoundManager"; + + public const string EntityType = "PageNotFoundManager-Configuration"; + + public const string FileName = "PageNotFoundManagerConfiguration"; + } + } +} diff --git a/src/HC.PageNotFoundManager.uSync/HC.PageNotFoundManager.uSync.csproj b/src/HC.PageNotFoundManager.uSync/HC.PageNotFoundManager.uSync.csproj new file mode 100644 index 0000000..04fc0de --- /dev/null +++ b/src/HC.PageNotFoundManager.uSync/HC.PageNotFoundManager.uSync.csproj @@ -0,0 +1,54 @@ + + + + enable + enable + 1.0.0 + 1.0.0 + 1.0.0 + net6.0;net7.0;net8.0 + 2024 Nik Rimington + Nik Rimington, Aaron Sadler + . + HotChilli.Umbraco.PageNotFound.uSync + HotChilli.Umbraco.PageNotFound.uSync + HotChilli.Umbraco.PageNotFound.uSync + Umbraco packaged for setting 404 response pages. + HotChilli.Umbraco.PageNotFound + umbraco plugin package Umbraco-Marketplace usync pagenotfound pagenotfoundmanager + https://github.com/NikRimington/HotChilli.Umbraco.PageNotFoundManager + logo.png + https://raw.githubusercontent.com/NikRimington/HotChilli.Umbraco.PageNotFoundManager/main/docs/img/logo.png + https://github.com/NikRimington/HotChilli.Umbraco.PageNotFoundManager + Git + LICENSE + ReadMe.md + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/HC.PageNotFoundManager.uSync/Handlers/PageNotFoundManagerHandler.cs b/src/HC.PageNotFoundManager.uSync/Handlers/PageNotFoundManagerHandler.cs new file mode 100644 index 0000000..039e90d --- /dev/null +++ b/src/HC.PageNotFoundManager.uSync/Handlers/PageNotFoundManagerHandler.cs @@ -0,0 +1,99 @@ +using HC.PageNotFoundManager.Notifications; +using HC.PageNotFoundManager.Services; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Strings; +using uSync.BackOffice; +using uSync.BackOffice.Configuration; +using uSync.BackOffice.Services; +using uSync.BackOffice.SyncHandlers; +using uSync.Core; +using PageNotFound = HC.PageNotFoundManager.Models.PageNotFound; + +namespace HC.PageNotFoundManager.uSync.Handlers +{ + [SyncHandler("pageNotFoundManagerHander", Consts.Configuration.HandlerName, Consts.Configuration.SerializerFolder, 1, + Icon = "icon-settings usync-addon-icon", EntityType = Consts.Configuration.EntityType)] + public class PageNotFoundManagerHandler : SyncHandlerRoot, IEnumerable>, ISyncHandler, + INotificationHandler + { + public override string Group => Consts.Group; + + private readonly IDatabaseService databaseService; + + public PageNotFoundManagerHandler(ILogger, IEnumerable>> logger, AppCaches appCaches, IShortStringHelper shortStringHelper, SyncFileService syncFileService, uSyncEventService mutexService, uSyncConfigService uSyncConfig, ISyncItemFactory itemFactory, IDatabaseService databaseService) : base(logger, appCaches, shortStringHelper, syncFileService, mutexService, uSyncConfig, itemFactory) + { + this.databaseService = databaseService; + + itemContainerType = UmbracoObjectTypes.Unknown; + } + + public override IEnumerable ExportAll(IEnumerable items, string folder, HandlerSettings config, + SyncUpdateCallback callback) + { + var item = databaseService.LoadFromDb(); + + var actions = new List(); + if (item != null) + { + actions.AddRange(Export(item, Path.Combine(rootFolder, DefaultFolder), DefaultConfig)); + } + + return actions; + } + + protected override IEnumerable> GetChildItems(IEnumerable parent) + { + throw new NotImplementedException(); + } + + protected override IEnumerable> GetFolders(IEnumerable parent) + { + throw new NotImplementedException(); + } + + public void Handle(OnConfigurationSavedNotification notification) + { + if (!ShouldProcess()) return; + + try + { + if (notification.Configuration != null) + { + var attempts = Export(notification.Configuration, Path.Combine(rootFolder, DefaultFolder), DefaultConfig); + foreach (var attempt in attempts.Where(x => x.Success)) + { + CleanUp(notification.Configuration, attempt.FileName, Path.Combine(rootFolder, DefaultFolder)); + } + } + } + catch (Exception ex) + { + logger.LogWarning(ex, "uSync Save error"); + } + } + + protected override IEnumerable DeleteMissingItems(IEnumerable items, IEnumerable keysToKeep, bool reportOnly) + => Enumerable.Empty(); + + protected override IEnumerable GetFromService(IEnumerable items) + => databaseService.LoadFromDb() ?? new List(); + + protected override string GetItemFileName(IEnumerable items) + => Consts.Configuration.FileName; + + protected override string GetItemName(IEnumerable item) + { + throw new NotImplementedException(); + } + + private bool ShouldProcess() + { + if (_mutexService.IsPaused) return false; + if (!DefaultConfig.Enabled) return false; + return true; + } + } +} diff --git a/src/HC.PageNotFoundManager.uSync/Serializers/PageNotFoundManagerSerializer.cs b/src/HC.PageNotFoundManager.uSync/Serializers/PageNotFoundManagerSerializer.cs new file mode 100644 index 0000000..e20268b --- /dev/null +++ b/src/HC.PageNotFoundManager.uSync/Serializers/PageNotFoundManagerSerializer.cs @@ -0,0 +1,91 @@ +using System.Xml.Linq; +using HC.PageNotFoundManager.Models; +using HC.PageNotFoundManager.Services; +using Microsoft.Extensions.Logging; +using uSync.Core; +using uSync.Core.Models; +using uSync.Core.Serialization; + +namespace HC.PageNotFoundManager.uSync.Serializers +{ + [SyncSerializer("6ca9a472-98f3-42f1-82ad-d1a8cbde5fc5", Consts.Configuration.SerializerName, + Consts.Configuration.ItemType)] + public class PageNotFoundManagerSerializer : SyncSerializerRoot>, + ISyncSerializer> + { + private readonly IDatabaseService databaseService; + + private readonly ICacheService cacheService; + + public PageNotFoundManagerSerializer(ILogger>> logger, IDatabaseService databaseService, ICacheService cacheService) : base(logger) + { + this.databaseService = databaseService; + this.cacheService = cacheService; + } + + protected override SyncAttempt SerializeCore(IEnumerable item, + SyncSerializerOptions options) + { + var node = new XElement(ItemType, + new XAttribute("Alias", ItemAlias(item)), + new XAttribute("Key", ItemKey(item))); + + node.Add(item.Select(item => + new XElement("PageNotFound", + new XElement("ParentId", item.ParentId), + new XElement("NotFoundPageId", item.NotFoundPageId) + ) + )); + + return SyncAttempt.Succeed(Consts.Configuration.ItemType, node, typeof(IEnumerable), ChangeType.Export); + } + + protected override SyncAttempt> DeserializeCore(XElement node, + SyncSerializerOptions options) + { + var item = new List(); + + foreach (var element in node.Elements("PageNotFound")) + { + item.Add(new PageNotFound + { + ParentId = element.Element("ParentId").ValueOrDefault(Guid.Empty), + NotFoundPageId = element.Element("NotFoundPageId").ValueOrDefault(Guid.Empty) + }); + } + + return SyncAttempt>.Succeed("Configuration", item, ChangeType.Import, Array.Empty()); + } + + public override IEnumerable FindItem(int id) + { + throw new NotImplementedException(); + } + + public override IEnumerable FindItem(Guid key) => databaseService.LoadFromDb() ?? new List(); + + public override IEnumerable FindItem(string alias) + { + throw new NotImplementedException(); + } + + public override void SaveItem(IEnumerable items) + { + foreach (var item in items) + { + databaseService.InsertToDb(item.ParentId, item.NotFoundPageId); + } + + cacheService.RefreshCache(); + } + + public override void DeleteItem(IEnumerable item) + { + throw new NotImplementedException(); + } + + public override string ItemAlias(IEnumerable item) => "pageNotFoundManager"; + + public override Guid ItemKey(IEnumerable item) => Guid.Parse("cc346b54-54f8-46c0-9290-1214e9639a58"); + } +}