diff --git a/uSync8.BackOffice/App_Plugins/uSync8/dashboard.html b/uSync8.BackOffice/App_Plugins/uSync8/dashboard.html index 0ffac410..3952a705 100644 --- a/uSync8.BackOffice/App_Plugins/uSync8/dashboard.html +++ b/uSync8.BackOffice/App_Plugins/uSync8/dashboard.html @@ -11,145 +11,167 @@

uSync 8

+ action="vm.toggleSettings()" + ng-if="!vm.settingsView"> + + - - -
- - - - - - -
-
- -
-
- -
{{handler.Name}}
+
+ + +
+ + + + + + + +
-
-
-

{{vm.status.Message}}

-
-
-
-
- - - - -
-
-
{{vm.action}}
-
{{vm.countChanges(vm.results)}} changes across {{vm.results.length}} items
-
- -
- -
-
-
-
-
-
- Type -
-
- Name -
-
- Change -
-
- Message -
-
+ + + +
+
+ +
{{handler.Name}}
-
-
-
-
- - - -
+
+

{{vm.status.Message}}

+
+
+
+
+ + + + +
+
+
{{vm.action}}
+
{{vm.countChanges(vm.results)}} changes across {{vm.results.length}} items
+
+ +
+ +
+
+
- {{vm.getTypeName(result.ItemType)}}
- {{result.Name}} + Type
- {{result.Change}} + Name
- {{result.Message}} + Change
- - + Message
+
-
-
-
-
-
-
Name
-
Old Value
-
New Value
-
+
+
+
+
+
+ + + +
+
+ {{vm.getTypeName(result.ItemType)}} +
+
+ {{result.Name}} +
+
+ {{result.Change}} +
+
+ {{result.Message}} +
+
+ +
+ ({{result.Details.length}} items)
-
-
-
-
- {{detail.Name}} -
-
- {{detail.OldValue}} -
-
- {{detail.NewValue}} +
+
+
+
+
+
+
Action
+
Item
+
Old Value
+
New Value
-
- {{detail.Change}} +
+
+
+
+ + + + +
+
+ {{detail.Change}} +
+
+ {{detail.Name}} +
+
+ {{detail.OldValue}} +
+
+ {{detail.NewValue}} +
@@ -157,20 +179,113 @@

{{vm.status.Message}}

-
-
-

No Changes

-
- - +
+

No Changes

+
+ + +
+ +
+
+
+ + + + +
+ + +
+
Import at startup
+
Run an import of files from the disk when Umbraco starts
+
+
+ +
+ + +
+
Export at startup
+
Export the Umbraco settings when the site starts up
+
+
- +
+ + +
+
Export on Save
+
Generate uSync files when items are saved
+
+
+
+
+ + + + +
+ + +
+
Flat structure
+
All items of a type are stored in a flat folder strucure
+
+
+
+ + +
+
use Guids for filenames
+
use the guid of an item as the filename
+
+
+
+ + + +
+ +
+
+
+ + + +
+ + +
+
+ {{handler.Alias}} +
+
Enabled for : {{handler.Actions}}
+
+
+
+
+
+ +
diff --git a/uSync8.BackOffice/App_Plugins/uSync8/lang/en-US.xml b/uSync8.BackOffice/App_Plugins/uSync8/lang/en-US.xml index 83dc70be..829e9912 100644 --- a/uSync8.BackOffice/App_Plugins/uSync8/lang/en-US.xml +++ b/uSync8.BackOffice/App_Plugins/uSync8/lang/en-US.xml @@ -9,11 +9,13 @@ Database elements to and from disk Import + Full Import Report Export Details Item Handlers + Save Settings \ No newline at end of file diff --git a/uSync8.BackOffice/App_Plugins/uSync8/uSyncDashboardController.js b/uSync8.BackOffice/App_Plugins/uSync8/uSyncDashboardController.js index caba3f5e..5dbed382 100644 --- a/uSync8.BackOffice/App_Plugins/uSync8/uSyncDashboardController.js +++ b/uSync8.BackOffice/App_Plugins/uSync8/uSyncDashboardController.js @@ -31,6 +31,7 @@ vm.runmode = modes.NONE; vm.showAll = false; + vm.settingsView = false; vm.settings = {}; vm.handlers = []; @@ -38,17 +39,38 @@ vm.reportAction = ''; + // buttons + + vm.importButton = { + state: 'init', + defaultButton: { + labelKey: "usync_import", + handler: importItems + }, + subButtons: [{ + labelKey: "usync_importforce", + handler: importForce + }] + }; + + // functions vm.report = report; vm.exportItems = exportItems; + vm.importForce = importForce; vm.importItems = importItems; + vm.saveSettings = saveSettings; + vm.toggleDetails = toggleDetails; vm.getTypeName = getTypeName; vm.toggleAll = toggleAll; vm.countChanges = countChanges; vm.calcPercentage = calcPercentage; + vm.toggleSettings = toggleSettings; + vm.toggle = toggle; + vm.showChange = showChange; // kick it all off @@ -80,14 +102,35 @@ }); } + function importForce() { + importItems(true); + } + function importItems(force) { resetStatus(modes.IMPORT); + vm.importButton.state = 'busy'; uSync8DashboardService.importItems(force, getClientId()) .then(function (result) { vm.results = result.data; vm.working = false; vm.reported = true; + vm.importButton.state = 'success'; + }, function (error) { + vm.importButton.state = 'error'; + notificationsService.error('Failed', error.data.ExceptionMessage); + + vm.working = false; + vm.reported = true; + }); + } + + function saveSettings() { + vm.working = false; + uSync8DashboardService.saveSettings(vm.settings) + .then(function (result) { + vm.working = false; + notificationsService.success('Saved', 'Settings updated'); }); } @@ -124,6 +167,14 @@ return (100 * status.Processed) / status.TotalSteps; } + function toggle(item) { + item = !item; + } + + function toggleSettings() { + vm.settingsView = !vm.settingsView; + } + ////// private function init() { diff --git a/uSync8.BackOffice/App_Plugins/uSync8/uSyncService.js b/uSync8.BackOffice/App_Plugins/uSync8/uSyncService.js index 6e59d40f..2ae3cab6 100644 --- a/uSync8.BackOffice/App_Plugins/uSync8/uSyncService.js +++ b/uSync8.BackOffice/App_Plugins/uSync8/uSyncService.js @@ -20,7 +20,8 @@ report: report, exportItems: exportItems, - importItems: importItems + importItems: importItems, + saveSettings: saveSettings }; return service; @@ -46,6 +47,10 @@ function importItems(force, clientId) { return $http.put(serviceRoot + 'import', { force: force, clientId: clientId }); } + + function saveSettings(settings) { + return $http.post(serviceRoot + 'savesettings', settings); + } } angular.module('umbraco.services') diff --git a/uSync8.BackOffice/App_Plugins/uSync8/usync.css b/uSync8.BackOffice/App_Plugins/uSync8/usync.css index eac4ea82..7f7f05d6 100644 --- a/uSync8.BackOffice/App_Plugins/uSync8/usync.css +++ b/uSync8.BackOffice/App_Plugins/uSync8/usync.css @@ -29,14 +29,44 @@ font-weight: 700; } +.usync-detail-count { + padding: 6px 0; +} + .usync-item-details { border-left: 4px solid #aaa; } + .usync-item-details .umb-table-head .umb-table-row { + background-color: rgba(0,0,0,0.05); + border-bottom: 1px solid black; + } + .usync-item-details .umb-table { background-color: #f3f3f5; } + .usync-item-details .usync-detail-action-cell { + flex: 0 0 110px; + } + + .usync-item-details .usync-old-value { + text-decoration: line-through; + color: #C62828; + } + + .usync-item-details .usync-new-value { + color: #2e7d32 + } + +.usync-row-delete { + background-color: #ffebee; +} + +.usync-row-create { + background-color: #E8F5E9; +} + .usync-handler-icon { padding: 0.75em; margin-right: 14px; @@ -68,3 +98,27 @@ .usync-handler-icon.usync-complete { color: #35c786; } + + +.usync-settings { + display: flex; +} + +.usync-settings > div { + width: 50%; +} + + .usync-settings .usync-main-settings { + margin-right: 14px; + } + + .usync-control { + position: relative; + padding: 10px 0; + } + + .usync-control input[type="text"] { + position: absolute; + top: 10px; + width: 200px; + } \ No newline at end of file diff --git a/uSync8.BackOffice/Configuration/BackOfficeConfig.cs b/uSync8.BackOffice/Configuration/BackOfficeConfig.cs new file mode 100644 index 00000000..50f48f38 --- /dev/null +++ b/uSync8.BackOffice/Configuration/BackOfficeConfig.cs @@ -0,0 +1,240 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Umbraco.Core; +using uSync8.Core.Extensions; + +namespace uSync8.BackOffice.Configuration +{ + public class uSyncConfig + { + public uSyncSettings Settings { get; set; } + + private string settingsFile = Umbraco.Core.IO.SystemDirectories.Config + "/uSync8.config"; + + public uSyncConfig() + { + this.Settings = LoadSettings(); + } + + public uSyncSettings LoadSettings() + { + uSyncSettings settings = new uSyncSettings(); + + var node = GetSettingsFile(); + if (node == null) + { + return SaveSettings(settings); + } + + settings.RootFolder = node.Element("Folder").ValueOrDefault(settings.RootFolder); + settings.UseFlatStructure = node.Element("FlatFolders").ValueOrDefault(true); + settings.ImportAtStartup = node.Element("ImportAtStartup").ValueOrDefault(true); + settings.ExportAtStartup = node.Element("ExportAtStartup").ValueOrDefault(false); + settings.ExportOnSave = node.Element("ExportOnSave").ValueOrDefault(true); + settings.UseGuidNames = node.Element("UseGuidFilenames").ValueOrDefault(false); + + var handlerConfig = node.Element("Handlers"); + + if (handlerConfig != null && handlerConfig.HasElements) + { + settings.EnableMissingHandlers = handlerConfig.Attribute("EnableMissing").ValueOrDefault(true); + + foreach (var handlerNode in handlerConfig.Elements("Handler")) + { + var handlerSetting = LoadHandlerConfig(handlerNode, settings); + + if (handlerSetting != null) + settings.Handlers.Add(handlerSetting); + } + } + + return settings; + } + + + public uSyncSettings SaveSettings(uSyncSettings settings, bool fireReload = false) + { + var node = GetSettingsFile(true); + + node.CreateOrSetElement("Folder", settings.RootFolder); + node.CreateOrSetElement("FlatFolders", settings.UseFlatStructure); + node.CreateOrSetElement("ImportAtStartup", settings.ImportAtStartup); + node.CreateOrSetElement("ExportAtStartup", settings.ExportAtStartup); + node.CreateOrSetElement("ExportOnSave", settings.ExportOnSave); + node.CreateOrSetElement("UseGuidFilenames", settings.UseGuidNames); + + if (settings.Handlers != null && settings.Handlers.Any()) + { + var handlerConfig = node.Element("Handlers"); + if (handlerConfig == null) + { + handlerConfig = new XElement("Handlers", + new XAttribute("EnableMissing", true)); + node.Add(handlerConfig); + } + + + foreach (var handler in settings.Handlers) + { + if (!handler.GuidNames.IsOverridden) + handler.GuidNames.SetDefaultValue(settings.UseGuidNames); + + if (!handler.UseFlatStructure.IsOverridden) + handler.UseFlatStructure.SetDefaultValue(settings.UseFlatStructure); + + var handlerNode = handlerConfig.Elements("Handler").FirstOrDefault(x => x.Attribute("Alias").Value == handler.Alias); + if (handlerNode == null) + { + handlerNode = new XElement("Handler"); + handlerConfig.Add(handlerNode); + } + + SaveHandlerConfig(handlerNode, handler, settings); + } + } + + SaveSettingsFile(node); + + if (fireReload) + { + this.Settings = LoadSettings(); + Reloaded?.Invoke(settings); + } + + return settings; + } + + #region Default Handler Loading Stuff + public HandlerSettings LoadHandlerConfig(XElement node, uSyncSettings defaultSettings) + { + if (node == null) return null; + var alias = node.Attribute("Alias").ValueOrDefault(string.Empty); + + if (string.IsNullOrEmpty(alias)) return null; + + var enabled = node.Attribute("Enabled").ValueOrDefault(true); + + var settings = new HandlerSettings(alias, enabled); + + // these values can be set locally, but if they aren't + // we get them from the global setting. + settings.GuidNames = GetLocalValue(node.Attribute("GuidNames"), defaultSettings.UseGuidNames); + settings.UseFlatStructure = GetLocalValue(node.Attribute("UseFlatStructure"), defaultSettings.UseFlatStructure); + + settings.Actions = node.Attribute("Actions").ValueOrDefault("All").ToDelimitedList().ToArray(); + + var settingNode = node.Element("Settings"); + if (settingNode != null) + { + var perHandlerSettings = new Dictionary(); + + foreach (var settingItem in settingNode.Elements("Add")) + { + var key = settingItem.Attribute("Key").ValueOrDefault(string.Empty); + var value = settingItem.Attribute("Value").ValueOrDefault(string.Empty); + + if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(value)) + continue; + + perHandlerSettings.Add(key, value); + } + + settings.Settings = perHandlerSettings; + } + + return settings; + } + + private OverriddenValue GetLocalValue(XAttribute attribute, TObject defaultValue) + { + if (attribute == null) + return new OverriddenValue(defaultValue, false); + + return new OverriddenValue(attribute.ValueOrDefault(defaultValue), true); + } + + public void SaveHandlerConfig(XElement node, HandlerSettings config, uSyncSettings globalSettings) + { + if (node == null) return; + + node.SetAttributeValue("Alias", config.Alias); + node.SetAttributeValue("Enabled", config.Enabled); + + if (config.GuidNames.IsOverridden) + node.SetAttributeValue("GuidNames", config.GuidNames); + + if (config.UseFlatStructure.IsOverridden) + node.SetAttributeValue("UseFlatStructure", config.UseFlatStructure); + + if (config.Actions.Length > 0 && !(config.Actions.Length == 1 && config.Actions[0].InvariantEquals("all"))) + node.SetAttributeValue("Actions", string.Join(",", config.Actions)); + + if (config.Settings != null && config.Settings.Any()) + { + var settingNode = new XElement("Settings"); + + foreach (var setting in config.Settings) + { + settingNode.Add(new XElement("Add", + new XAttribute("Key", setting.Key), + new XAttribute("Value", setting.Value))); + } + + var existing = node.Element("Settings"); + if (existing != null) existing.Remove(); + + node.Add(settingNode); + } + } + + #endregion + + private XElement GetSettingsFile(bool loadIfBlank = false) + { + var filePath = Umbraco.Core.IO.IOHelper.MapPath(settingsFile); + if (File.Exists(filePath)) + { + var node = XElement.Load(filePath); + var settingsNode = node.Element("BackOffice"); + if (settingsNode != null) + return settingsNode; + + if (loadIfBlank) + { + node.Add(new XElement("BackOffice")); + return node.Element("BackOffice"); + } + else + { + return null; + } + } + + if (loadIfBlank) + { + var file = new XElement("uSync", + new XElement("BackOffice")); + return file.Element("BackOffice"); + } + + return null; + } + + private void SaveSettingsFile(XElement node) + { + var root = node.Parent; + if (root != null) + { + var filePath = Umbraco.Core.IO.IOHelper.MapPath(settingsFile); + root.Save(filePath); + } + } + + public static event uSyncSettingsEvent Reloaded; + + public delegate void uSyncSettingsEvent(uSyncSettings settings); + } + +} diff --git a/uSync8.BackOffice/Configuration/ConfigExtensions.cs b/uSync8.BackOffice/Configuration/ConfigExtensions.cs new file mode 100644 index 00000000..bc07e697 --- /dev/null +++ b/uSync8.BackOffice/Configuration/ConfigExtensions.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Configuration; + +namespace uSync8.BackOffice.Configuration +{ + public static class ConfigExtensions + { + public static uSyncSettings uSync(this Configs configs) + => configs.GetConfig().Settings; + } +} diff --git a/uSync8.BackOffice/Configuration/OverriddenValue.cs b/uSync8.BackOffice/Configuration/OverriddenValue.cs new file mode 100644 index 00000000..bc741f91 --- /dev/null +++ b/uSync8.BackOffice/Configuration/OverriddenValue.cs @@ -0,0 +1,41 @@ +namespace uSync8.BackOffice.Configuration +{ + /// + /// Overridden value - will let us use the value - but when + /// we load/save it we can work out if it's actually overriding the global + /// setting. + /// + public class OverriddenValue + { + public OverriddenValue() + : this(default(TObject), false) + { } + + public OverriddenValue(TObject value, bool overridden) + { + Value = value; + IsOverridden = overridden; + } + + public void Override(TObject value) + { + Value = value; + IsOverridden = true; + } + + public void SetDefaultValue(TObject defaultValue) + { + Value = defaultValue; + IsOverridden = false; + } + + public TObject Value { get; set; } + public bool IsOverridden { get; internal set; } + + public static implicit operator TObject(OverriddenValue value) + { + return value.Value; + } + } + +} diff --git a/uSync8.BackOffice/Configuration/uSyncHandlerSettings.cs b/uSync8.BackOffice/Configuration/uSyncHandlerSettings.cs new file mode 100644 index 00000000..8937e496 --- /dev/null +++ b/uSync8.BackOffice/Configuration/uSyncHandlerSettings.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace uSync8.BackOffice.Configuration +{ + public class HandlerSettings + { + public string Alias { get; } + public bool Enabled { get; set; } + + public string[] Actions { get; set; } + + public OverriddenValue UseFlatStructure { get; set; } = new OverriddenValue(); + public OverriddenValue GuidNames { get; set; } = new OverriddenValue(); + + public Dictionary Settings { get; set; } = new Dictionary(); + + public HandlerSettings(string alias, bool enabled) + { + Alias = alias; + Enabled = enabled; + } + } + +} diff --git a/uSync8.BackOffice/Configuration/uSyncSettings.cs b/uSync8.BackOffice/Configuration/uSyncSettings.cs new file mode 100644 index 00000000..a5a199a0 --- /dev/null +++ b/uSync8.BackOffice/Configuration/uSyncSettings.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Text; +using System.Threading.Tasks; +using System.Xml.XPath; +using uSync8.BackOffice.SyncHandlers; + +namespace uSync8.BackOffice.Configuration +{ + public class uSyncSettings + { + public string RootFolder { get; set; } = "~/uSync/v8/"; + + public bool UseFlatStructure { get; set; } = true; + public bool UseGuidNames { get; set; } = false; + + public bool ImportAtStartup { get; set; } = false; + public bool ExportAtStartup { get; set; } = false; + public bool ExportOnSave { get; set; } = true; + + public bool EnableMissingHandlers { get; set; } = true; + public List Handlers { get; set; } = new List(); + } + +} diff --git a/uSync8.BackOffice/Controllers/uSyncDashboardApiController.cs b/uSync8.BackOffice/Controllers/uSyncDashboardApiController.cs index 743bcd5d..6f716009 100644 --- a/uSync8.BackOffice/Controllers/uSyncDashboardApiController.cs +++ b/uSync8.BackOffice/Controllers/uSyncDashboardApiController.cs @@ -4,9 +4,11 @@ using System.Text; using System.Threading.Tasks; using System.Web.Http; +using Umbraco.Core.Composing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; +using uSync8.BackOffice.Configuration; using uSync8.BackOffice.Hubs; using uSync8.BackOffice.SyncHandlers; using Constants = Umbraco.Core.Constants; @@ -18,22 +20,32 @@ namespace uSync8.BackOffice.Controllers public class uSyncDashboardApiController : UmbracoAuthorizedApiController { private readonly uSyncService uSyncService; - - private readonly uSyncBackOfficeSettings settings; private readonly SyncHandlerCollection syncHandlers; + private uSyncSettings settings; + private uSyncConfig Config; + public uSyncDashboardApiController( uSyncService uSyncService, SyncHandlerCollection syncHandlers, - uSyncBackOfficeSettings settings) + uSyncConfig config) { + this.Config = config; this.uSyncService = uSyncService; - this.settings = settings; + + this.settings = Current.Configs.uSync(); this.syncHandlers = syncHandlers; + + uSyncConfig.Reloaded += BackOfficeConfig_Reloaded; + } + + private void BackOfficeConfig_Reloaded(uSyncSettings settings) + { + this.settings = settings; } [HttpGet] - public uSyncBackOfficeSettings GetSettings() + public uSyncSettings GetSettings() { return settings; } @@ -56,7 +68,7 @@ public IEnumerable Report(uSyncOptions options) var hubClient = new HubClientService(options.clientId); var summaryClient = new SummaryHandler(hubClient); - return uSyncService.Report(settings.rootFolder, summaryClient.PostSummary); + return uSyncService.Report(settings.RootFolder, summaryClient.PostSummary); } [HttpPost] @@ -65,7 +77,7 @@ public IEnumerable Export(uSyncOptions options) var hubClient = new HubClientService(options.clientId); var summaryClient = new SummaryHandler(hubClient); - return uSyncService.Export(settings.rootFolder, summaryClient.PostSummary); + return uSyncService.Export(settings.RootFolder, summaryClient.PostSummary); } [HttpPut] @@ -74,9 +86,18 @@ public IEnumerable Import(uSyncOptions options) var hubClient = new HubClientService(options.clientId); var summaryClient = new SummaryHandler(hubClient); - return uSyncService.Import(settings.rootFolder, options.force, summaryClient.PostSummary); + return uSyncService.Import(settings.RootFolder, options.force, summaryClient.PostSummary); } + [HttpPost] + public void SaveSettings(uSyncSettings settings) + { + Config.SaveSettings(settings, true); + } + + + + public class SummaryHandler { private readonly HubClientService hubClient; diff --git a/uSync8.BackOffice/Services/SyncFileService.cs b/uSync8.BackOffice/Services/SyncFileService.cs index 2394be9c..3ba4375b 100644 --- a/uSync8.BackOffice/Services/SyncFileService.cs +++ b/uSync8.BackOffice/Services/SyncFileService.cs @@ -6,7 +6,9 @@ using System.Threading.Tasks; using System.Xml.Linq; using System.Xml.Serialization; +using Umbraco.Core.Composing; using Umbraco.Core.IO; +using uSync8.BackOffice.Configuration; namespace uSync8.BackOffice.Services { @@ -16,15 +18,22 @@ namespace uSync8.BackOffice.Services /// public class SyncFileService { - private readonly uSyncBackOfficeSettings globalSettings; - private readonly string mappedRoot; + private uSyncSettings globalSettings; + private string mappedRoot; - public SyncFileService(uSyncBackOfficeSettings settings) + public SyncFileService() { - this.globalSettings = settings; - this.mappedRoot = IOHelper.MapPath(globalSettings.rootFolder); + this.globalSettings = Current.Configs.uSync(); + this.mappedRoot = IOHelper.MapPath(globalSettings.RootFolder); + + uSyncConfig.Reloaded += BackOfficeConfig_Reloaded; } + private void BackOfficeConfig_Reloaded(uSyncSettings settings) + { + this.globalSettings = Current.Configs.uSync(); + this.mappedRoot = IOHelper.MapPath(globalSettings.RootFolder); + } private string GetAbsPath(string path) { @@ -86,6 +95,13 @@ public void CreateFoldersForFile(string filePath) Directory.CreateDirectory(absPath); } + public void CleanFolder(string folder) + { + var absPath = GetAbsPath(folder); + + if (Directory.Exists(absPath)) + Directory.Delete(absPath, true); + } public IEnumerable GetFiles(string folder, string extensions) { diff --git a/uSync8.BackOffice/Services/uSyncService.cs b/uSync8.BackOffice/Services/uSyncService.cs index f7f84445..0804214b 100644 --- a/uSync8.BackOffice/Services/uSyncService.cs +++ b/uSync8.BackOffice/Services/uSyncService.cs @@ -3,7 +3,9 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; +using uSync8.BackOffice.Configuration; using uSync8.BackOffice.SyncHandlers; namespace uSync8.BackOffice @@ -16,15 +18,22 @@ namespace uSync8.BackOffice /// public class uSyncService { - private readonly uSyncBackOfficeSettings settings; + private uSyncSettings settings; private readonly SyncHandlerCollection syncHandlers; - public uSyncService( - SyncHandlerCollection syncHandlers, - uSyncBackOfficeSettings settings) + public uSyncService( + SyncHandlerCollection syncHandlers + ) { this.syncHandlers = syncHandlers; - this.settings = settings; + this.settings = Current.Configs.uSync(); + + uSyncConfig.Reloaded += BackOfficeConfig_Reloaded; + } + + private void BackOfficeConfig_Reloaded(uSyncSettings settings) + { + this.settings = Current.Configs.uSync(); } public delegate void SyncEventCallback(SyncProgressSummary summary); @@ -33,21 +42,22 @@ public IEnumerable Report(string folder, SyncEventCallback callback { var actions = new List(); - var configuredHandlers = settings.Handlers.Where(x => x.Config.Enabled == true).ToList(); + var configuredHandlers = syncHandlers.GetValidHandlers("report", settings).ToList(); var summary = new SyncProgressSummary(configuredHandlers.Select(x => x.Handler), "Reporting", configuredHandlers.Count); - + foreach (var configuredHandler in configuredHandlers) { var handler = configuredHandler.Handler; + var handlerSettings = configuredHandler.Settings; + summary.Processed++; - summary.UpdateHandler( - handler.Name, HandlerStatus.Processing, $"Reporting {handler.Name}"); + summary.UpdateHandler(handler.Name, HandlerStatus.Processing, $"Reporting {handler.Name}"); callback?.Invoke(summary); - actions.AddRange(handler.Report($"{folder}/{handler.DefaultFolder}", configuredHandler.Config)); + actions.AddRange(handler.Report($"{folder}/{handler.DefaultFolder}", handlerSettings)); summary.UpdateHandler(handler.Name, HandlerStatus.Complete); } @@ -70,7 +80,7 @@ public IEnumerable Import(string folder, bool force, SyncEventCallb var actions = new List(); - var configuredHandlers = settings.Handlers.Where(x => x.Config.Enabled == true).ToList(); + var configuredHandlers = syncHandlers.GetValidHandlers("import", settings).ToList(); var summary = new SyncProgressSummary(configuredHandlers.Select(x => x.Handler), "Importing", configuredHandlers.Count + 1); summary.Handlers.Add(new SyncHandlerSummary() @@ -83,6 +93,7 @@ public IEnumerable Import(string folder, bool force, SyncEventCallb foreach (var configuredHandler in configuredHandlers) { var handler = configuredHandler.Handler; + var handlerSettings = configuredHandler.Settings; summary.Processed++; @@ -91,7 +102,7 @@ public IEnumerable Import(string folder, bool force, SyncEventCallb callback?.Invoke(summary); - actions.AddRange(handler.ImportAll($"{folder}/{handler.DefaultFolder}", configuredHandler.Config, force)); + actions.AddRange(handler.ImportAll($"{folder}/{handler.DefaultFolder}", handlerSettings, force)); summary.UpdateHandler(handler.Name, HandlerStatus.Complete); } @@ -111,6 +122,7 @@ public IEnumerable Import(string folder, bool force, SyncEventCallb foreach (var configuredHandler in configuredHandlers) { var handler = configuredHandler.Handler; + var handlerSettings = configuredHandler.Settings; if (handler is ISyncPostImportHandler postHandler) { @@ -118,7 +130,7 @@ public IEnumerable Import(string folder, bool force, SyncEventCallb if (handlerActions.Any()) { - var postActions = postHandler.ProcessPostImport($"{folder}/{handler.DefaultFolder}", handlerActions, configuredHandler.Config); + var postActions = postHandler.ProcessPostImport($"{folder}/{handler.DefaultFolder}", handlerActions, handlerSettings); if (postActions != null) actions.AddRange(postActions); } @@ -130,7 +142,7 @@ public IEnumerable Import(string folder, bool force, SyncEventCallb return actions; } - catch(Exception ex) + catch (Exception ex) { throw ex; } @@ -145,7 +157,7 @@ public IEnumerable Export(string folder, SyncEventCallback callback { var actions = new List(); - var configuredHandlers = settings.Handlers.Where(x => x.Config.Enabled == true).ToList(); + var configuredHandlers = syncHandlers.GetValidHandlers("export", settings).ToList(); var summary = new SyncProgressSummary(configuredHandlers.Select(x => x.Handler), "Exporting", configuredHandlers.Count); @@ -159,7 +171,7 @@ public IEnumerable Export(string folder, SyncEventCallback callback callback?.Invoke(summary); - actions.AddRange(handler.ExportAll($"{folder}/{handler.DefaultFolder}", configuredHandler.Config)); + actions.AddRange(handler.ExportAll($"{folder}/{handler.DefaultFolder}", configuredHandler.Settings)); summary.UpdateHandler(handler.Name, HandlerStatus.Complete); } @@ -171,4 +183,5 @@ public IEnumerable Export(string folder, SyncEventCallback callback } } + } diff --git a/uSync8.BackOffice/SyncHandlers/Handlers/ContentTypeHandler.cs b/uSync8.BackOffice/SyncHandlers/Handlers/ContentTypeHandler.cs index 019e019b..035932c7 100644 --- a/uSync8.BackOffice/SyncHandlers/Handlers/ContentTypeHandler.cs +++ b/uSync8.BackOffice/SyncHandlers/Handlers/ContentTypeHandler.cs @@ -12,6 +12,7 @@ using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using uSync8.BackOffice.Configuration; using uSync8.BackOffice.Services; using uSync8.Core; using uSync8.Core.Serialization; @@ -32,9 +33,8 @@ public ContentTypeHandler( IContentTypeService contentTypeService, ISyncSerializer serializer, ISyncTracker tracker, - SyncFileService fileService, - uSyncBackOfficeSettings settings) - : base(entityService, logger, serializer, tracker, fileService, settings) + SyncFileService fileService) + : base(entityService, logger, serializer, tracker, fileService) { this.contentTypeService = contentTypeService; @@ -55,15 +55,24 @@ public ContentTypeHandler( #endregion - protected override void InitializeEvents() + protected override void InitializeEvents(HandlerSettings settings) { ContentTypeService.Saved += EventSavedItem; ContentTypeService.Deleted += EventDeletedItem; } - protected override string GetItemFileName(IUmbracoEntity item) - => item.Name; + protected override string GetItemFileName(IUmbracoEntity item, bool useGuid) + { + if (useGuid) return item.Key.ToString(); + + if (item is IContentType contentItem) + { + return contentItem.Alias.ToSafeFileName(); + } + + return item.Name.ToSafeFileName(); + } protected override IContentType GetFromService(int id) => contentTypeService.Get(id); diff --git a/uSync8.BackOffice/SyncHandlers/Handlers/DataTypeHandler.cs b/uSync8.BackOffice/SyncHandlers/Handlers/DataTypeHandler.cs index cbcbb350..d54423d3 100644 --- a/uSync8.BackOffice/SyncHandlers/Handlers/DataTypeHandler.cs +++ b/uSync8.BackOffice/SyncHandlers/Handlers/DataTypeHandler.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using uSync8.BackOffice.Configuration; using uSync8.BackOffice.Services; using uSync8.Core; using uSync8.Core.Serialization; @@ -27,30 +28,33 @@ public DataTypeHandler( IProfilingLogger logger, ISyncSerializer serializer, ISyncTracker tracker, - SyncFileService syncFileService, - uSyncBackOfficeSettings settings) - : base(entityService, logger, serializer, tracker, syncFileService, settings) + SyncFileService syncFileService) + : base(entityService, logger, serializer, tracker, syncFileService) { this.dataTypeService = dataTypeService; this.itemObjectType = UmbracoObjectTypes.DataType; + this.itemContainerType = UmbracoObjectTypes.DataTypeContainer; } protected override IDataType GetFromService(int id) => dataTypeService.GetDataType(id); - protected override void InitializeEvents() + protected override void InitializeEvents(HandlerSettings settings) { DataTypeService.Saved += EventSavedItem; DataTypeService.Deleted += EventDeletedItem; } - protected override string GetItemFileName(IUmbracoEntity item) - => item.Name.ToSafeFileName(); + protected override string GetItemFileName(IUmbracoEntity item, bool useGuid) + { + if (useGuid) return item.Key.ToString(); + return item.Name.ToSafeAlias(); + } protected override void DeleteFolder(int id) => dataTypeService.DeleteContainer(id); - public override IEnumerable ProcessPostImport(string folder, IEnumerable actions, uSyncHandlerSettings config) + public override IEnumerable ProcessPostImport(string folder, IEnumerable actions, HandlerSettings config) { if (actions == null || !actions.Any()) return null; diff --git a/uSync8.BackOffice/SyncHandlers/Handlers/LanguageHandler.cs b/uSync8.BackOffice/SyncHandlers/Handlers/LanguageHandler.cs index c1a086ad..92d0ae2a 100644 --- a/uSync8.BackOffice/SyncHandlers/Handlers/LanguageHandler.cs +++ b/uSync8.BackOffice/SyncHandlers/Handlers/LanguageHandler.cs @@ -6,8 +6,10 @@ using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using uSync8.BackOffice.Configuration; using uSync8.BackOffice.Services; using uSync8.Core.Serialization; using uSync8.Core.Tracking; @@ -25,9 +27,8 @@ public LanguageHandler( ILocalizationService localizationService, ISyncSerializer serializer, ISyncTracker tracker, - SyncFileService syncFileService, - uSyncBackOfficeSettings settings) - : base(entityService, logger, serializer, tracker, syncFileService, settings) + SyncFileService syncFileService) + : base(entityService, logger, serializer, tracker, syncFileService) { this.localizationService = localizationService; @@ -38,15 +39,24 @@ public LanguageHandler( protected override ILanguage GetFromService(int id) => localizationService.GetLanguageById(id); - protected override string GetItemPath(ILanguage item) + // language guids are not consistant (at least in alpha) + protected override string GetItemPath(ILanguage item, bool useGuid, bool isFlat) => item.IsoCode.ToSafeFileName(); - protected override void InitializeEvents() + protected override void InitializeEvents(HandlerSettings settings) { LocalizationService.SavedLanguage += EventSavedItem; LocalizationService.DeletedLanguage += EventDeletedItem; } + protected override IEnumerable GetExportItems(int parent, UmbracoObjectTypes objectType) + { + if (parent == -1) + return localizationService.GetAllLanguages(); + + return Enumerable.Empty(); + } + protected override ILanguage GetFromService(Guid key) => null; diff --git a/uSync8.BackOffice/SyncHandlers/Handlers/MacroHandler.cs b/uSync8.BackOffice/SyncHandlers/Handlers/MacroHandler.cs index a82765d0..57eb5c74 100644 --- a/uSync8.BackOffice/SyncHandlers/Handlers/MacroHandler.cs +++ b/uSync8.BackOffice/SyncHandlers/Handlers/MacroHandler.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using uSync8.BackOffice.Configuration; using uSync8.BackOffice.Services; using uSync8.Core.Serialization; using uSync8.Core.Tracking; @@ -24,9 +25,8 @@ public MacroHandler(IEntityService entityService, IMacroService macroService, ISyncSerializer serializer, ISyncTracker tracker, - SyncFileService syncFileService, - uSyncBackOfficeSettings settings) - : base(entityService, logger, serializer, tracker, syncFileService, settings) + SyncFileService syncFileService) + : base(entityService, logger, serializer, tracker, syncFileService) { this.macroService = macroService; } @@ -34,7 +34,7 @@ public MacroHandler(IEntityService entityService, /// /// overrider the default export, because macros, don't exist as an object type??? /// - public override IEnumerable ExportAll(int parent, string folder, uSyncHandlerSettings config = null) + public override IEnumerable ExportAll(int parent, string folder, HandlerSettings config = null) { var actions = new List(); @@ -50,10 +50,14 @@ public override IEnumerable ExportAll(int parent, string folder, uS protected override IMacro GetFromService(int id) => macroService.GetById(id); - protected override string GetItemPath(IMacro item) - => item.Alias.ToSafeFileName(); + // not sure we can trust macro guids in the path just yet. + protected override string GetItemPath(IMacro item, bool useGuid, bool isFlat) + { + if (useGuid) return item.Key.ToString(); + return item.Alias.ToSafeAlias(); + } - protected override void InitializeEvents() + protected override void InitializeEvents(HandlerSettings settings) { MacroService.Saved += EventSavedItem; MacroService.Deleted += EventDeletedItem; diff --git a/uSync8.BackOffice/SyncHandlers/Handlers/MediaTypeHandler.cs b/uSync8.BackOffice/SyncHandlers/Handlers/MediaTypeHandler.cs index 021f078b..f150a54b 100644 --- a/uSync8.BackOffice/SyncHandlers/Handlers/MediaTypeHandler.cs +++ b/uSync8.BackOffice/SyncHandlers/Handlers/MediaTypeHandler.cs @@ -3,11 +3,13 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using uSync8.BackOffice.Configuration; using uSync8.BackOffice.Services; using uSync8.Core; using uSync8.Core.Serialization; @@ -26,9 +28,8 @@ public MediaTypeHandler( IProfilingLogger logger, ISyncSerializer serializer, ISyncTracker tracker, - SyncFileService syncFileService, - uSyncBackOfficeSettings settings) - : base(entityService, logger, serializer, tracker, syncFileService, settings) + SyncFileService syncFileService) + : base(entityService, logger, serializer, tracker, syncFileService) { this.mediaTypeService = mediaTypeService; @@ -37,20 +38,29 @@ public MediaTypeHandler( } - protected override IMediaType GetFromService(int id) - => mediaTypeService.Get(id); - protected override string GetItemFileName(IUmbracoEntity item) - => item.Name; - - protected override void InitializeEvents() + protected override void InitializeEvents(HandlerSettings settings) { MediaTypeService.Saved += EventSavedItem; MediaTypeService.Deleted += EventDeletedItem; } - protected override void DeleteFolder(int id) - => mediaTypeService.DeleteContainer(id); + protected override string GetItemFileName(IUmbracoEntity item, bool useGuid) + { + if (useGuid) return item.Key.ToString(); + + if (item is IMediaType mediaType) + { + return mediaType.Alias.ToSafeFileName(); + } + + return item.Name.ToSafeFileName(); + } + + + + protected override IMediaType GetFromService(int id) + => mediaTypeService.Get(id); protected override IMediaType GetFromService(Guid key) => mediaTypeService.Get(key); @@ -60,5 +70,8 @@ protected override IMediaType GetFromService(string alias) protected override void DeleteViaService(IMediaType item) => mediaTypeService.Delete(item); + + protected override void DeleteFolder(int id) + => mediaTypeService.DeleteContainer(id); } } diff --git a/uSync8.BackOffice/SyncHandlers/Handlers/MemberTypeHandler.cs b/uSync8.BackOffice/SyncHandlers/Handlers/MemberTypeHandler.cs index 829ee23a..7658a3c5 100644 --- a/uSync8.BackOffice/SyncHandlers/Handlers/MemberTypeHandler.cs +++ b/uSync8.BackOffice/SyncHandlers/Handlers/MemberTypeHandler.cs @@ -3,11 +3,13 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using uSync8.BackOffice.Configuration; using uSync8.BackOffice.Services; using uSync8.Core.Serialization; using uSync8.Core.Tracking; @@ -25,9 +27,8 @@ public MemberTypeHandler( IMemberTypeService memberTypeService, ISyncSerializer serializer, ISyncTracker tracker, - SyncFileService syncFileService, - uSyncBackOfficeSettings settings) - : base(entityService, logger, serializer, tracker, syncFileService, settings) + SyncFileService syncFileService) + : base(entityService, logger, serializer, tracker, syncFileService) { this.memberTypeService = memberTypeService; @@ -40,8 +41,7 @@ public MemberTypeHandler( this.Enabled = false; // turn it off it appears to break things in current build } - - protected override void InitializeEvents() + protected override void InitializeEvents(HandlerSettings settings) { MemberTypeService.Saved += EventSavedItem; MemberTypeService.Deleted += EventDeletedItem; @@ -62,7 +62,16 @@ protected override IMemberType GetFromService(Guid key) protected override IMemberType GetFromService(string alias) => memberTypeService.Get(alias); - protected override string GetItemFileName(IUmbracoEntity item) - => item.Name; + protected override string GetItemFileName(IUmbracoEntity item, bool useGuid) + { + if (useGuid) return item.Key.ToString(); + + if (item is IMemberType memberType) + { + return memberType.Alias.ToSafeFileName(); + } + + return item.Name.ToSafeFileName(); + } } } diff --git a/uSync8.BackOffice/SyncHandlers/Handlers/TemplateHandler.cs b/uSync8.BackOffice/SyncHandlers/Handlers/TemplateHandler.cs index 0f6eb1a0..0f7b8f24 100644 --- a/uSync8.BackOffice/SyncHandlers/Handlers/TemplateHandler.cs +++ b/uSync8.BackOffice/SyncHandlers/Handlers/TemplateHandler.cs @@ -3,11 +3,13 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using uSync8.BackOffice.Configuration; using uSync8.BackOffice.Services; using uSync8.Core.Serialization; using uSync8.Core.Tracking; @@ -26,9 +28,8 @@ public TemplateHandler( IFileService fileService, ISyncSerializer serializer, ISyncTracker tracker, - SyncFileService syncFileService, - uSyncBackOfficeSettings settings) - : base(entityService, logger, serializer, tracker, syncFileService, settings) + SyncFileService syncFileService) + : base(entityService, logger, serializer, tracker, syncFileService) { this.fileService = fileService; @@ -41,14 +42,14 @@ public TemplateHandler( protected override ITemplate GetFromService(int id) => fileService.GetTemplate(id); - protected override void InitializeEvents() + protected override void InitializeEvents(HandlerSettings settings) { FileService.SavedTemplate += EventSavedItem; FileService.DeletedTemplate += EventDeletedItem; } - protected override string GetItemPath(ITemplate item) - => item.Name; + protected override string GetItemPath(ITemplate item, bool useGuid, bool isFlat) + => useGuid ? item.Key.ToString() : item.Alias.ToSafeFileName(); protected override ITemplate GetFromService(Guid key) => fileService.GetTemplate(key); diff --git a/uSync8.BackOffice/SyncHandlers/ISyncHandler.cs b/uSync8.BackOffice/SyncHandlers/ISyncHandler.cs index b2bd2132..2259531c 100644 --- a/uSync8.BackOffice/SyncHandlers/ISyncHandler.cs +++ b/uSync8.BackOffice/SyncHandlers/ISyncHandler.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using Umbraco.Core; +using uSync8.BackOffice.Configuration; namespace uSync8.BackOffice.SyncHandlers { @@ -17,13 +18,13 @@ public interface ISyncHandler Type ItemType { get; } bool Enabled { get; set; } - uSyncHandlerSettings DefaultConfig { get; set; } + HandlerSettings DefaultConfig { get; set; } - void Initialize(); + void Initialize(HandlerSettings settings); - IEnumerable ExportAll(string folder, uSyncHandlerSettings setting); - IEnumerable ImportAll(string folder, uSyncHandlerSettings setting, bool force); - IEnumerable Report(string folder, uSyncHandlerSettings setting); + IEnumerable ExportAll(string folder, HandlerSettings settings); + IEnumerable ImportAll(string folder, HandlerSettings settings, bool force); + IEnumerable Report(string folder, HandlerSettings settings); // uSyncAction Import(string file, bool force); diff --git a/uSync8.BackOffice/SyncHandlers/ISyncPostImportHanlder.cs b/uSync8.BackOffice/SyncHandlers/ISyncPostImportHanlder.cs index af5fcc81..2be3e874 100644 --- a/uSync8.BackOffice/SyncHandlers/ISyncPostImportHanlder.cs +++ b/uSync8.BackOffice/SyncHandlers/ISyncPostImportHanlder.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using uSync8.BackOffice.Configuration; namespace uSync8.BackOffice.SyncHandlers { @@ -13,6 +14,6 @@ namespace uSync8.BackOffice.SyncHandlers /// public interface ISyncPostImportHandler { - IEnumerable ProcessPostImport(string folder, IEnumerable actions, uSyncHandlerSettings config); + IEnumerable ProcessPostImport(string folder, IEnumerable actions, HandlerSettings config); } } diff --git a/uSync8.BackOffice/SyncHandlers/SyncHandlerBase.cs b/uSync8.BackOffice/SyncHandlers/SyncHandlerBase.cs index 2afffdcc..d9d6284e 100644 --- a/uSync8.BackOffice/SyncHandlers/SyncHandlerBase.cs +++ b/uSync8.BackOffice/SyncHandlers/SyncHandlerBase.cs @@ -4,10 +4,12 @@ using System.Linq; using System.Xml.Linq; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; +using uSync8.BackOffice.Configuration; using uSync8.BackOffice.Models; using uSync8.BackOffice.Services; using uSync8.Core; @@ -25,7 +27,6 @@ public abstract class SyncHandlerBase protected readonly IProfilingLogger logger; protected readonly IEntityService entityService; - protected readonly uSyncBackOfficeSettings globalSettings; protected readonly SyncFileService syncFileService; protected readonly ISyncSerializer serializer; @@ -44,7 +45,9 @@ public abstract class SyncHandlerBase /// settings can be loaded for these. public bool Enabled { get; set; } = true; - public uSyncHandlerSettings DefaultConfig { get; set; } + public HandlerSettings DefaultConfig { get; set; } + + protected string rootFolder { get; set; } protected UmbracoObjectTypes itemObjectType = UmbracoObjectTypes.Unknown; protected UmbracoObjectTypes itemContainerType = UmbracoObjectTypes.Unknown; @@ -56,15 +59,16 @@ public SyncHandlerBase( IProfilingLogger logger, ISyncSerializer serializer, ISyncTracker tracker, - SyncFileService syncFileService, - uSyncBackOfficeSettings settings) + SyncFileService syncFileService) { - this.entityService = entityService; this.logger = logger; - this.globalSettings = settings; + + this.entityService = entityService; + this.serializer = serializer; this.tracker = tracker; + this.syncFileService = syncFileService; var thisType = GetType(); @@ -79,10 +83,26 @@ public SyncHandlerBase( IsTwoPass = meta.IsTwoPass; Icon = string.IsNullOrWhiteSpace(meta.Icon) ? "icon-umb-content" : meta.Icon; + GetDefaultConfig(Current.Configs.uSync()); + uSyncConfig.Reloaded += BackOfficeConfig_Reloaded; + } + + private void GetDefaultConfig(uSyncSettings setting) + { + var config = setting.Handlers.FirstOrDefault(x => x.Alias == this.Alias); + if (config != null) + this.DefaultConfig = config; + + rootFolder = setting.RootFolder; + } + + private void BackOfficeConfig_Reloaded(uSyncSettings settings) + { + GetDefaultConfig(settings); } #region Importing - public IEnumerable ImportAll(string folder, uSyncHandlerSettings config = null, bool force = false) + public IEnumerable ImportAll(string folder, HandlerSettings config = null, bool force = false) { logger.Info("Running Import: {0}", Path.GetFileName(folder)); @@ -103,7 +123,7 @@ public IEnumerable ImportAll(string folder, uSyncHandlerSettings co return actions; } - protected virtual IEnumerable ImportFolder(string folder, uSyncHandlerSettings config, Dictionary updates, bool force) + protected virtual IEnumerable ImportFolder(string folder, HandlerSettings config, Dictionary updates, bool force) { List actions = new List(); @@ -117,7 +137,11 @@ protected virtual IEnumerable ImportFolder(string folder, uSyncHand updates.Add(file, attempt.Item); } - actions.Add(uSyncActionHelper.SetAction(attempt, file, IsTwoPass)); + var action = uSyncActionHelper.SetAction(attempt, file, IsTwoPass); + if (attempt.Details != null && attempt.Details.Any()) + action.Details = attempt.Details; + + actions.Add(action); } var folders = syncFileService.GetDirectories(folder); @@ -130,7 +154,7 @@ protected virtual IEnumerable ImportFolder(string folder, uSyncHand return actions; } - virtual public SyncAttempt Import(string filePath, uSyncHandlerSettings config, bool force = false) + virtual public SyncAttempt Import(string filePath, HandlerSettings config, bool force = false) { syncFileService.EnsureFileExists(filePath); @@ -142,7 +166,7 @@ virtual public SyncAttempt Import(string filePath, uSyncHandlerSettings } } - virtual public void ImportSecondPass(string file, TObject item, uSyncHandlerSettings config) + virtual public void ImportSecondPass(string file, TObject item, HandlerSettings config) { if (IsTwoPass) { @@ -160,12 +184,15 @@ virtual public void ImportSecondPass(string file, TObject item, uSyncHandlerSett #endregion #region Exporting - virtual public IEnumerable ExportAll(string folder, uSyncHandlerSettings config = null) + virtual public IEnumerable ExportAll(string folder, HandlerSettings config = null) { + // we clean the folder out on an export all. + syncFileService.CleanFolder(folder); + return ExportAll(-1, folder, config); } - virtual public IEnumerable ExportAll(int parent, string folder, uSyncHandlerSettings config) + virtual public IEnumerable ExportAll(int parent, string folder, HandlerSettings config) { var actions = new List(); @@ -178,11 +205,11 @@ virtual public IEnumerable ExportAll(int parent, string folder, uSy } } - var items = entityService.GetChildren(parent, this.itemObjectType); + var items = GetExportItems(parent, itemObjectType); foreach (var item in items) { - var contentType = GetFromService(item.Id); - actions.Add(Export(contentType, folder, config)); + var concreateType = GetFromService(item.Id); + actions.Add(Export(concreateType, folder, config)); actions.AddRange(ExportAll(item.Id, folder, config)); } @@ -190,12 +217,17 @@ virtual public IEnumerable ExportAll(int parent, string folder, uSy return actions; } - virtual public uSyncAction Export(TObject item, string folder, uSyncHandlerSettings config) + // almost everything does this - but languages can't so we need to + // let the language Handler override this. + virtual protected IEnumerable GetExportItems(int parent, UmbracoObjectTypes objectType) + => entityService.GetChildren(parent, objectType); + + virtual public uSyncAction Export(TObject item, string folder, HandlerSettings config) { if (item == null) return uSyncAction.Fail(nameof(item), typeof(TObject), ChangeType.Fail, "Item not set"); - var filename = GetPath(folder, item, config.GuidNames); + var filename = GetPath(folder, item, config.GuidNames, config.UseFlatStructure); var attempt = serializer.Serialize(item); if (attempt.Success) @@ -210,7 +242,7 @@ virtual public uSyncAction Export(TObject item, string folder, uSyncHandlerSetti #region reporting - public IEnumerable Report(string folder, uSyncHandlerSettings config = null) + public IEnumerable Report(string folder, HandlerSettings config = null) { var actions = new List(); actions.AddRange(ProcessActions(true)); @@ -218,7 +250,7 @@ public IEnumerable Report(string folder, uSyncHandlerSettings confi return actions; } - public IEnumerable ReportFolder(string folder, uSyncHandlerSettings config) + public IEnumerable ReportFolder(string folder, HandlerSettings config) { List actions = new List(); @@ -252,10 +284,18 @@ protected uSyncAction ReportItem(string file) var action = uSyncActionHelper .ReportAction(!current, node.GetAlias()); - action.Message = nameof(TObject); + action.Message = ""; if (action.Change > ChangeType.NoChange) + { action.Details = tracker.GetChanges(node); + if (action.Details == null || action.Details.Count() == 0) + { + action.Message = "Change details cannot be calculated"; + } + + action.Message = "Would update"; + } return action; } @@ -274,7 +314,7 @@ protected virtual void EventDeletedItem(IService sender, Umbraco.Core.Events.Del foreach (var item in e.DeletedEntities) { - ExportDeletedItem(item, Path.Combine(globalSettings.rootFolder, this.DefaultFolder), DefaultConfig); + ExportDeletedItem(item, Path.Combine(rootFolder, this.DefaultFolder), DefaultConfig); actionService.AddAction(item.Key, GetItemName(item), SyncActionType.Delete); } @@ -290,7 +330,7 @@ protected virtual void EventSavedItem(IService sender, Umbraco.Core.Events.SaveE foreach (var item in e.SavedEntities) { - var attempt = Export(item, Path.Combine(globalSettings.rootFolder, this.DefaultFolder), DefaultConfig); + var attempt = Export(item, Path.Combine(rootFolder, this.DefaultFolder), DefaultConfig); if (attempt.Success) { if (!DefaultConfig.GuidNames) @@ -302,10 +342,10 @@ protected virtual void EventSavedItem(IService sender, Umbraco.Core.Events.SaveE actionService.SaveActions(); } - protected virtual void ExportDeletedItem(TObject item, string folder, uSyncHandlerSettings config) + protected virtual void ExportDeletedItem(TObject item, string folder, HandlerSettings config) { if (item == null) return; - var filename = GetPath(folder, item, config.GuidNames); + var filename = GetPath(folder, item, config.GuidNames, config.UseFlatStructure); var attempt = serializer.SerializeEmpty(item, GetItemName(item)); if (attempt.Success) @@ -377,8 +417,16 @@ virtual public uSyncAction ProcessDelete(Guid key, string keyString, bool report if (item != null) { - if (!report) DeleteViaService(item); - return uSyncAction.SetAction(true, keyString, typeof(TObject), ChangeType.Delete); + var message = ""; + if (!report) + { + DeleteViaService(item); + } + else + { + message = "Would be deleted"; + } + return uSyncAction.SetAction(true, keyString, typeof(TObject), ChangeType.Delete, message); } return uSyncAction.SetAction(false, keyString, typeof(TObject), ChangeType.Removed); @@ -391,27 +439,27 @@ virtual public uSyncAction ProcessDelete(Guid key, string keyString, bool report abstract protected TObject GetFromService(string alias); abstract protected void DeleteViaService(TObject item); - abstract protected string GetItemPath(TObject item); + abstract protected string GetItemPath(TObject item, bool useGuid, bool isFlat); abstract protected string GetItemName(TObject item); - virtual protected string GetPath(string folder, TObject item, bool GuidNames) + virtual protected string GetPath(string folder, TObject item, bool GuidNames, bool isFlat) { - if (GuidNames) return $"{folder}/{item.Key}.config"; + if (isFlat && GuidNames) return $"{folder}/{item.Key}.config"; - return $"{folder}/{this.GetItemPath(item)}.config"; + return $"{folder}/{this.GetItemPath(item, GuidNames, isFlat)}.config"; } virtual public uSyncAction Rename(TObject item) => new uSyncAction(); - public void Initialize() + public void Initialize(HandlerSettings settings) { - actionFile = Path.Combine(globalSettings.rootFolder, $"_Actions/actions_{DefaultFolder}.config"); - InitializeEvents(); + actionFile = Path.Combine(rootFolder, $"_Actions/actions_{DefaultFolder}.config"); + InitializeEvents(settings); } - protected abstract void InitializeEvents(); + protected abstract void InitializeEvents(HandlerSettings settings); } } diff --git a/uSync8.BackOffice/SyncHandlers/SyncHandlerCollectionBuilder.cs b/uSync8.BackOffice/SyncHandlers/SyncHandlerCollectionBuilder.cs index e3e614dd..7689728a 100644 --- a/uSync8.BackOffice/SyncHandlers/SyncHandlerCollectionBuilder.cs +++ b/uSync8.BackOffice/SyncHandlers/SyncHandlerCollectionBuilder.cs @@ -3,7 +3,9 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Umbraco.Core; using Umbraco.Core.Composing; +using uSync8.BackOffice.Configuration; namespace uSync8.BackOffice.SyncHandlers { @@ -19,5 +21,40 @@ public SyncHandlerCollection(IEnumerable items) : base(items) { } + + public IEnumerable GetValidHandlers(string actionName, uSyncSettings settings) + { + var validHandlers = new List(); + + foreach (var syncHandler in this) + { + var config = settings.Handlers.FirstOrDefault(x => x.Alias.InvariantEquals(syncHandler.Alias)); + if (config == null) + { + config = new HandlerSettings(syncHandler.Alias, settings.EnableMissingHandlers) + { + GuidNames = new OverriddenValue(settings.UseGuidNames, false), + UseFlatStructure = new OverriddenValue(settings.UseFlatStructure, false) + }; + } + + if (config != null && config.Enabled) + { + validHandlers.Add(new HandlerConfigPair() + { + Handler = syncHandler, + Settings = config + }); + } + } + + return validHandlers; + } + } + + public class HandlerConfigPair + { + public ISyncHandler Handler { get; set; } + public HandlerSettings Settings { get; set; } } } diff --git a/uSync8.BackOffice/SyncHandlers/SyncHandlerTreeBase.cs b/uSync8.BackOffice/SyncHandlers/SyncHandlerTreeBase.cs index c1160615..4a406770 100644 --- a/uSync8.BackOffice/SyncHandlers/SyncHandlerTreeBase.cs +++ b/uSync8.BackOffice/SyncHandlers/SyncHandlerTreeBase.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; +using uSync8.BackOffice.Configuration; using uSync8.BackOffice.Services; using uSync8.Core; using uSync8.Core.Extensions; @@ -36,9 +37,8 @@ protected SyncHandlerTreeBase( IProfilingLogger logger, ISyncSerializer serializer, ISyncTracker tracker, - SyncFileService syncFileService, - uSyncBackOfficeSettings settings) - : base(entityService, logger, serializer, tracker, syncFileService, settings) + SyncFileService syncFileService) + : base(entityService, logger, serializer, tracker, syncFileService) { } @@ -53,11 +53,11 @@ protected SyncHandlerTreeBase( /// /// /// - protected override IEnumerable ImportFolder(string folder, uSyncHandlerSettings config, Dictionary updates, bool force) + protected override IEnumerable ImportFolder(string folder, HandlerSettings config, Dictionary updates, bool force) { // if not using flat, then directory structure is doing // this for us. - if (globalSettings.UseFlatStructure == false) + if (config.UseFlatStructure == false) return base.ImportFolder(folder, config, updates, force); List actions = new List(); @@ -103,7 +103,7 @@ protected override IEnumerable ImportFolder(string folder, uSyncHan } - public virtual IEnumerable ProcessPostImport(string folder, IEnumerable actions, uSyncHandlerSettings config) + public virtual IEnumerable ProcessPostImport(string folder, IEnumerable actions, HandlerSettings config) { if (actions == null || !actions.Any()) return null; @@ -150,28 +150,28 @@ private XElement LoadNode(string path) // path helpers - virtual protected string GetItemFileName(IUmbracoEntity item) + virtual protected string GetItemFileName(IUmbracoEntity item, bool useGuid) { if (item != null) { - if (globalSettings.UseFlatStructure) + if (useGuid) return item.Key.ToString(); - return item.Name.ToSafeFileName(); + return item.Name.ToSafeFileName(); } return Guid.NewGuid().ToString(); } - override protected string GetItemPath(TObject item) + override protected string GetItemPath(TObject item, bool useGuid, bool isFlat) { - if (globalSettings.UseFlatStructure) - return GetItemFileName((IUmbracoEntity)item); + if (isFlat) + return GetItemFileName((IUmbracoEntity)item, useGuid); - return GetEntityPath((IUmbracoEntity)item); + return GetEntityPath((IUmbracoEntity)item, useGuid, true); } - protected string GetEntityPath(IUmbracoEntity item) + protected string GetEntityPath(IUmbracoEntity item, bool useGuid, bool isTop) { var path = string.Empty; if (item != null) @@ -181,11 +181,12 @@ protected string GetEntityPath(IUmbracoEntity item) var parent = entityService.Get(item.ParentId); if (parent != null) { - path = GetEntityPath(parent); + path = GetEntityPath(parent, useGuid, false); } } - path = Path.Combine(path, GetItemFileName(item)); + // we only want the guid file name at the top of the tree + path = Path.Combine(path, GetItemFileName(item, useGuid && isTop)); } return path; diff --git a/uSync8.BackOffice/uSync8.BackOffice.csproj b/uSync8.BackOffice/uSync8.BackOffice.csproj index 25ce282e..53249317 100644 --- a/uSync8.BackOffice/uSync8.BackOffice.csproj +++ b/uSync8.BackOffice/uSync8.BackOffice.csproj @@ -219,6 +219,10 @@ + + + + @@ -246,7 +250,7 @@ - + diff --git a/uSync8.BackOffice/uSyncBackOfficeComposer.cs b/uSync8.BackOffice/uSyncBackOfficeComposer.cs index 04e66af0..ecf14aa1 100644 --- a/uSync8.BackOffice/uSyncBackOfficeComposer.cs +++ b/uSync8.BackOffice/uSyncBackOfficeComposer.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Umbraco.Core.Components; using Umbraco.Core.Composing; +using uSync8.BackOffice.Configuration; using uSync8.BackOffice.Services; using uSync8.BackOffice.SyncHandlers; @@ -14,7 +15,8 @@ public class uSyncBackOfficeComposer : IUserComposer { public void Compose(Composition composition) { - composition.RegisterUnique(); + composition.Configs.Add(() => new uSyncConfig()); + composition.RegisterUnique(); composition.WithCollectionBuilder() diff --git a/uSync8.BackOffice/uSyncBackOfficeSettings.cs b/uSync8.BackOffice/uSyncBackOfficeSettings.cs deleted file mode 100644 index 9e9073fa..00000000 --- a/uSync8.BackOffice/uSyncBackOfficeSettings.cs +++ /dev/null @@ -1,241 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Xml.Linq; -using System.Xml.XPath; -using Umbraco.Core; -using uSync8.BackOffice.SyncHandlers; -using uSync8.Core.Extensions; - -namespace uSync8.BackOffice -{ - public class uSyncBackOfficeSettings - { - public string rootFolder { get; set; } = "~/uSync/v8/"; - - public bool UseFlatStructure { get; set; } = true; - public bool ImportAtStartup { get; set; } = false; - - public bool ExportAtStartup { get; set; } = false; - public bool ExportOnSave { get; set; } = true; - public bool UseGuidNames { get; set; } = false; - - public List Handlers { get; set; } = new List(); - - private string settingsFile = Umbraco.Core.IO.SystemDirectories.Config + "/uSync8.config"; - - public void LoadSettings(SyncHandlerCollection syncHandlers) - { - this.Handlers = syncHandlers - .Select(x => new uSyncHandlerConfig(x, true)) - .ToList(); - - var node = GetSettingsFile(); - if (node == null) - { - SaveSettings(); - return; // everything will be default; - } - - this.rootFolder = node.Element("Folder").ValueOrDefault(rootFolder); - this.UseFlatStructure = node.Element("FlatFolders").ValueOrDefault(true); - this.ImportAtStartup = node.Element("ImportAtStartup").ValueOrDefault(true); - this.ExportAtStartup = node.Element("ExportAtStartup").ValueOrDefault(false); - this.ExportOnSave = node.Element("ExportOnSave").ValueOrDefault(true); - this.UseGuidNames = node.Element("UseGuidFilenames").ValueOrDefault(false); - - var handlerConfig = node.Element("Handlers"); - var defaultHandlerEnabled = handlerConfig.Attribute("EnableMissing").ValueOrDefault(false); - - if (this.Handlers != null && handlerConfig != null) - { - foreach (var handler in this.Handlers) - { - handler.Config.Enabled = defaultHandlerEnabled; - handler.Config.GuidNames = this.UseGuidNames; - - var handlerNode = handlerConfig.Elements("Handler").FirstOrDefault(x => x.Attribute("Alias").ValueOrDefault(string.Empty) == handler.Alias); - if (handlerNode != null) - { - LoadHandlerConfig(handlerNode, handler.Alias); - } - } - } - - } - public void SaveSettings() - { - var node = GetSettingsFile(true); - - node.CreateOrSetElement("Folder", rootFolder); - node.CreateOrSetElement("FlatFolders", UseFlatStructure); - node.CreateOrSetElement("ImportAtStartup", ImportAtStartup); - node.CreateOrSetElement("ExportAtStartup", ExportAtStartup); - node.CreateOrSetElement("ExportOnSave", ExportOnSave); - node.CreateOrSetElement("UseGuidFilenames", UseGuidNames); - - if (this.Handlers != null) - { - var handlerConfig = node.Element("Handlers"); - if (handlerConfig == null) - { - handlerConfig = new XElement("Handlers", - new XAttribute("EnableMissing", true)); - node.Add(handlerConfig); - } - - - foreach (var handler in Handlers) - { - var handlerNode = handlerConfig.Elements("Handler").FirstOrDefault(x => x.Attribute("Alias").Value == handler.Alias); - if (handlerNode == null) - { - handlerNode = new XElement("Handler"); - handlerConfig.Add(handlerNode); - } - - SaveHandlerConfig(handlerNode, handler); - } - } - - SaveSettingsFile(node); - } - - #region Default Handler Loading Stuff - public uSyncHandlerSettings LoadHandlerConfig(XElement node, string alias) - { - if (node == null) return null; - var nodeAlias = node.Attribute("Alias").ValueOrDefault("unknown"); - - if (!alias.InvariantEquals(nodeAlias)) return null; - - var settings = new uSyncHandlerSettings(); - - settings.Enabled = node.Attribute("Enabled").ValueOrDefault(true); - settings.GuidNames = node.Attribute("GuidNames").ValueOrDefault(this.UseGuidNames); - - var settingNode = node.Element("Settings"); - if (settingNode != null) - { - var handlerSettings = new Dictionary(); - - foreach (var setting in settingNode.Elements("Add")) - { - var key = setting.Attribute("Key").ValueOrDefault(string.Empty); - var value = setting.Attribute("Value").ValueOrDefault(string.Empty); - - if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(value)) - continue; - - handlerSettings.Add(key, value); - } - - settings.Settings = handlerSettings; - } - - return settings; - } - - public void SaveHandlerConfig(XElement node, uSyncHandlerConfig config) - { - if (node == null) return; - - node.SetAttributeValue("Alias", config.Alias); - node.SetAttributeValue("Enabled", config.Config.Enabled); - - if (config.Config.GuidNames != UseGuidNames) - node.SetAttributeValue("GuidNames", config.Config.GuidNames); - - if (config.Config.Settings != null && config.Config.Settings.Any()) - { - var settingNode = new XElement("Settings"); - - foreach (var setting in config.Config.Settings) - { - settingNode.Add(new XElement("Add", - new XAttribute("Key", setting.Key), - new XAttribute("Value", setting.Value))); - } - - var existing = node.Element("Settings"); - if (existing != null) existing.Remove(); - - node.Add(settingNode); - } - } - - #endregion - - private XElement GetSettingsFile(bool loadIfBlank = false) - { - var filePath = Umbraco.Core.IO.IOHelper.MapPath(settingsFile); - if (File.Exists(filePath)) - { - var node = XElement.Load(filePath); - var settingsNode = node.Element("BackOffice"); - if (settingsNode != null) - return settingsNode; - - if (loadIfBlank) - { - node.Add(new XElement("BackOffice")); - return node.Element("BackOffice"); - } - else - { - return null; - } - } - - if (loadIfBlank) - { - var file = new XElement("uSync", - new XElement("BackOffice")); - return file.Element("BackOffice"); - } - - return null; - } - - private void SaveSettingsFile(XElement node) - { - var root = node.Parent; - if (root != null) - { - var filePath = Umbraco.Core.IO.IOHelper.MapPath(settingsFile); - root.Save(filePath); - } - } - } - - public class uSyncHandlerConfig - { - public string Alias { get; } - public ISyncHandler Handler { get; private set; } - public uSyncHandlerSettings Config { get; set; } - - public uSyncHandlerConfig(ISyncHandler handler, bool enabled) - { - Alias = handler.Alias; - Handler = handler; - Config = new uSyncHandlerSettings() - { - Enabled = enabled - }; - } - } - - public class uSyncHandlerSettings - { - public bool Enabled { get; set; } - public string[] Actions { get; set; } - public bool GuidNames { get; set; } - - public Dictionary Settings { get; set; } = new Dictionary(); - - } -} diff --git a/uSync8.BackOffice/uSyncBackofficeComponent.cs b/uSync8.BackOffice/uSyncBackofficeComponent.cs index 9623ecd4..67a3ca4a 100644 --- a/uSync8.BackOffice/uSyncBackofficeComponent.cs +++ b/uSync8.BackOffice/uSyncBackofficeComponent.cs @@ -3,8 +3,11 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Umbraco.Core; using Umbraco.Core.Components; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; +using uSync8.BackOffice.Configuration; using uSync8.BackOffice.Services; using uSync8.BackOffice.SyncHandlers; @@ -16,22 +19,23 @@ public class uSyncBackofficeComponent : IComponent private readonly SyncHandlerCollection syncHandlers; private readonly SyncFileService syncFileService; - private readonly uSyncBackOfficeSettings globalSettings; + private readonly uSyncSettings globalSettings; private readonly uSyncService uSyncService; public uSyncBackofficeComponent( SyncHandlerCollection syncHandlers, IProfilingLogger logger, - SyncFileService fileService, - uSyncBackOfficeSettings settings, + SyncFileService fileService, uSyncService uSyncService) { + globalSettings = Current.Configs.uSync(); + this.syncHandlers = syncHandlers; this.logger = logger; this.syncFileService = fileService; - this.globalSettings = settings; this.uSyncService = uSyncService; + } public void Initialize() @@ -39,40 +43,29 @@ public void Initialize() using (logger.DebugDuration("uSync Starting")) { - InitSettings(); - InitBackOffice(); } } - private void InitSettings() - { - globalSettings.LoadSettings(syncHandlers); - foreach(var syncHandler in globalSettings.Handlers) - { - syncHandler.Handler.DefaultConfig = syncHandler.Config; - } - - } - private void InitBackOffice() { - if (globalSettings.ExportAtStartup || (globalSettings.ExportOnSave && !syncFileService.RootExists(globalSettings.rootFolder))) + if (globalSettings.ExportAtStartup || (globalSettings.ExportOnSave && !syncFileService.RootExists(globalSettings.RootFolder))) { - uSyncService.Export(globalSettings.rootFolder); + uSyncService.Export(globalSettings.RootFolder); } if (globalSettings.ImportAtStartup) { - uSyncService.Import(globalSettings.rootFolder,false); + uSyncService.Import(globalSettings.RootFolder,false); } if (globalSettings.ExportOnSave) { - foreach (var syncHandler in globalSettings.Handlers.Where(x => x.Config.Enabled)) + var handlers = syncHandlers.GetValidHandlers("Save", globalSettings); + foreach (var syncHandler in handlers) { logger.Debug($"Starting up Handler {syncHandler.Handler.Name}"); - syncHandler.Handler.Initialize(); + syncHandler.Handler.Initialize(syncHandler.Settings); } } diff --git a/uSync8.Core/Models/uSyncChange.cs b/uSync8.Core/Models/uSyncChange.cs index 34d508bb..61dbe80e 100644 --- a/uSync8.Core/Models/uSyncChange.cs +++ b/uSync8.Core/Models/uSyncChange.cs @@ -1,6 +1,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; @@ -18,6 +19,47 @@ public class uSyncChange [JsonConverter(typeof(StringEnumConverter))] public ChangeDetailType Change { get; set; } + + public static uSyncChange Create(string path, string name, string newValue, bool useNew = true) + { + return new uSyncChange() + { + Change = ChangeDetailType.Create, + Path = path, + Name = name, + OldValue = "", + NewValue = useNew ? newValue : "New Property" + }; + } + + public static uSyncChange Delete(string path, string name, string oldValue, bool useOld = true) + { + return new uSyncChange() + { + Change = ChangeDetailType.Delete, + Path = path, + Name = name, + OldValue = useOld ? oldValue : "Missing Property", + NewValue = "" + }; + } + + public static uSyncChange Update(string path, string name, string oldValue, string newValue) + { + return new uSyncChange() + { + Name = name, + Path = path, + Change = ChangeDetailType.Update, + NewValue = string.IsNullOrEmpty(newValue) ? "(Blank)" : newValue, + OldValue = string.IsNullOrEmpty(oldValue) ? "(Blank)" : oldValue + }; + } + + public static uSyncChange Update(string path, string name, TObject oldValue, TObject newValue) + { + return Update(path, name, oldValue.ToString(), newValue.ToString()); + } } public enum ChangeDetailType diff --git a/uSync8.Core/Serialization/Serializers/ContentTypeBaseSerializer.cs b/uSync8.Core/Serialization/Serializers/ContentTypeBaseSerializer.cs index b688283e..ab89e040 100644 --- a/uSync8.Core/Serialization/Serializers/ContentTypeBaseSerializer.cs +++ b/uSync8.Core/Serialization/Serializers/ContentTypeBaseSerializer.cs @@ -151,7 +151,7 @@ protected void DeserializeBase(TObject item, XElement node) var info = node.Element("Info"); if (info == null) return; - var alias = info.GetAlias(); + var alias = node.GetAlias(); if (item.Alias != alias) item.Alias = alias; diff --git a/uSync8.Core/Serialization/Serializers/MacroSerializer.cs b/uSync8.Core/Serialization/Serializers/MacroSerializer.cs index 474d0ae0..3d72371d 100644 --- a/uSync8.Core/Serialization/Serializers/MacroSerializer.cs +++ b/uSync8.Core/Serialization/Serializers/MacroSerializer.cs @@ -18,7 +18,7 @@ public class MacroSerializer : SyncSerializerBase, ISyncSerializer DeserializeCore(XElement node) { + var changes = new List(); + if (node.Element("Name") == null) throw new ArgumentNullException("XML missing Name parameter"); @@ -40,7 +42,6 @@ protected override SyncAttempt DeserializeCore(XElement node) item = macroService.GetById(key); - if (item == null) { item = macroService.GetByAlias(alias); @@ -49,6 +50,7 @@ protected override SyncAttempt DeserializeCore(XElement node) if (item == null) { item = new Macro(alias, name, macroSource, macroType); + changes.Add(uSyncChange.Create(alias, name, "New Macro")); } item.Name = name; @@ -56,8 +58,9 @@ protected override SyncAttempt DeserializeCore(XElement node) item.MacroSource = macroSource; item.MacroType = macroType; + item.UseInEditor = node.Element("UseInEditor").ValueOrDefault(false); - item.DontRender = node.Element("DontRender").ValueOrDefault(false); + item.DontRender = node.Element("DontRender").ValueOrDefault(false); item.CacheByMember = node.Element("CachedByMember").ValueOrDefault(false); item.CacheByPage = node.Element("CachedByPage").ValueOrDefault(false); item.CacheDuration = node.Element("CachedDuration").ValueOrDefault(0); @@ -65,29 +68,64 @@ protected override SyncAttempt DeserializeCore(XElement node) var properties = node.Element("Properties"); if (properties != null && properties.HasElements) { - foreach(var propNode in properties.Elements("Property")) + foreach (var propNode in properties.Elements("Property")) { var propertyAlias = propNode.Element("Alias").ValueOrDefault(string.Empty); var editorAlias = propNode.Element("EditorAlias").ValueOrDefault(string.Empty); var propertyName = propNode.Element("Name").ValueOrDefault(string.Empty); var sortOrder = propNode.Element("SortOrder").ValueOrDefault(0); + var propPath = $"{alias}: {propertyName}"; + if (item.Properties.ContainsKey(propertyAlias)) { item.Properties.UpdateProperty(propertyAlias, propertyName, sortOrder, editorAlias); } else { + changes.Add(uSyncChange.Create(propPath, "Property", propertyAlias)); item.Properties.Add(new MacroProperty(propertyAlias, propertyName, sortOrder, editorAlias)); } } + } + RemoveOrphanProperties(item, properties); + macroService.Save(item); - return SyncAttempt.Succeed(item.Name, item, ChangeType.Import); + var attempt = SyncAttempt.Succeed(item.Name, item, ChangeType.Import); + if (changes.Any()) + attempt.Details = changes; + + return attempt; } + private void RemoveOrphanProperties(IMacro item, XElement properties) + { + var removalKeys = new List(); + if (properties == null) + { + removalKeys = item.Properties.Keys.ToList(); + } + else + { + var aliases = properties.Elements("Property") + .Where(x => x.Element("Alias").ValueOrDefault(string.Empty) != string.Empty) + .Select(x => x.Element("Alias").ValueOrDefault(string.Empty)) + .ToList(); + + removalKeys = item.Properties.Values.Where(x => !aliases.Contains(x.Alias)) + .Select(x => x.Alias) + .ToList(); + } + + foreach (var propKey in removalKeys) + { + item.Properties.Remove(propKey); + } + + } protected override SyncAttempt SerializeCore(IMacro item) { @@ -103,14 +141,14 @@ protected override SyncAttempt SerializeCore(IMacro item) node.Add(new XElement("CachedDuration", item.CacheDuration)); var properties = new XElement("Properties"); - foreach(var property in item.Properties) + foreach (var property in item.Properties) { properties.Add(new XElement("Property", new XElement("Key", property.Key), - new XElement("Name", property.Name)), + new XElement("Name", property.Name), new XElement("Alias", property.Alias), new XElement("SortOrder", property.SortOrder), - new XElement("EditorAlias", property.EditorAlias)); + new XElement("EditorAlias", property.EditorAlias))); } node.Add(properties); diff --git a/uSync8.Core/Serialization/SyncSerializerBase.cs b/uSync8.Core/Serialization/SyncSerializerBase.cs index 7b9f9fe6..3e5c1f66 100644 --- a/uSync8.Core/Serialization/SyncSerializerBase.cs +++ b/uSync8.Core/Serialization/SyncSerializerBase.cs @@ -1,4 +1,6 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using System.Xml.Linq; @@ -200,5 +202,6 @@ private string MakeHash(XElement node) protected virtual XElement CleanseNode(XElement node) => node; + } } diff --git a/uSync8.Core/Tracking/Impliment/ContentTypeBaseTracker.cs b/uSync8.Core/Tracking/Impliment/ContentTypeBaseTracker.cs new file mode 100644 index 00000000..efeb72ce --- /dev/null +++ b/uSync8.Core/Tracking/Impliment/ContentTypeBaseTracker.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Models; +using uSync8.Core.Serialization; + +namespace uSync8.Core.Tracking.Impliment +{ + public class ContentTypeBaseTracker : SyncBaseTracker + where TObject : IContentTypeBase + { + public ContentTypeBaseTracker(ISyncSerializer serializer) : base(serializer) + { + } + + protected override TrackedItem TrackChanges() + { + return new TrackedItem("", true) + { + Children = new List() + { + new TrackedItem("", "/Info") + { + Children = new List() + { + new TrackedItem("Name", "/Name", true), + new TrackedItem("Icon", "/Icon", true), + new TrackedItem("Thumbnail", "/Thumbnail", true), + new TrackedItem("Description", "/Description", true), + new TrackedItem("AllowAtRoot", "/AllowAtRoot", true), + new TrackedItem("IsListView", "/IsListView", true), + new TrackedItem("Variations", "/Variations", true), + new TrackedItem("IsElement", "/IsElement", true), + } + }, + new TrackedItem("", "/GenericProperties", false) + { + Children = new List() + { + new TrackedItem("Property", "/GenericProperty") + { + Repeating = new RepeatingInfo("Key", "/GenericProperty", "Name"), + Children = new List() + { + new TrackedItem("Key", "/Key", true), + new TrackedItem("Name", "/Name", true), + new TrackedItem("Alias", "/Alias", true), + new TrackedItem("Definition", "/Definition", true), + new TrackedItem("Type", "/Type", true), + new TrackedItem("Mandatory", "/Mandatory", true), + new TrackedItem("Validation", "/Validation", true), + new TrackedItem("Description", "/Description", true), + new TrackedItem("SortOrder", "/SortOrder", true), + new TrackedItem("Tab", "/Tab", true) + } + } + } + }, + new TrackedItem("", "/Tabs", false) + { + Children = new List() + { + new TrackedItem("Tab", "/Tab") + { + Repeating = new RepeatingInfo("Key", "/Tab", "Caption"), + Children = new List() + { + new TrackedItem("Key", "/Key", true), + new TrackedItem("Caption", "/Caption", true), + new TrackedItem("SortOrder", "/SortOrder", true) + } + } + } + } + } + }; + } + } +} diff --git a/uSync8.Core/Tracking/Impliment/ContentTypeTracker.cs b/uSync8.Core/Tracking/Impliment/ContentTypeTracker.cs index c3793967..b4875cc5 100644 --- a/uSync8.Core/Tracking/Impliment/ContentTypeTracker.cs +++ b/uSync8.Core/Tracking/Impliment/ContentTypeTracker.cs @@ -8,7 +8,7 @@ namespace uSync8.Core.Tracking.Impliment { - public class ContentTypeTracker : SyncBaseTracker, ISyncTracker + public class ContentTypeTracker : ContentTypeBaseTracker, ISyncTracker { public ContentTypeTracker(ISyncSerializer serializer) : base(serializer) { @@ -16,7 +16,56 @@ public ContentTypeTracker(ISyncSerializer serializer) : base(seria protected override TrackedItem TrackChanges() { - return new TrackedItem(serializer.ItemType); + var tracker = base.TrackChanges(); + tracker.Children[0] + .Children.Add(new TrackedItem("DefaultTemplate", "/DefaultTemplate", true)); + + tracker.Children[0] + .Children.Add(new TrackedItem("Parent", "/Master", true)); + + tracker.Children[0] + .Children.Add(new TrackedItem("AllowedTemplates", "/AllowedTemplates") + { + Children = new List() + { + new TrackedItem("Template", "/Template") + { + Repeating = new RepeatingInfo("Key", string.Empty, "Template") + { + KeyIsAttribute = true + } + } + } + }); + + tracker.Children[0] + .Children.Add(new TrackedItem("Compositions", "/Compositions") + { + Children = new List() + { + new TrackedItem("Composition", "/Composition") + { + Repeating = new RepeatingInfo("Key", string.Empty, "Template") + { + KeyIsAttribute = true + } + } + } + }); + + tracker.Children.Add( + new TrackedItem("Structure", "/Structure") + { + Children = new List() + { + new TrackedItem("ContentType", "/ContentType") + { + Repeating = new RepeatingInfo(string.Empty, string.Empty, string.Empty) + } + } + }); + + return tracker; } } } diff --git a/uSync8.Core/Tracking/Impliment/DataTypeTracker.cs b/uSync8.Core/Tracking/Impliment/DataTypeTracker.cs index da7fb109..4a5377d0 100644 --- a/uSync8.Core/Tracking/Impliment/DataTypeTracker.cs +++ b/uSync8.Core/Tracking/Impliment/DataTypeTracker.cs @@ -17,7 +17,7 @@ public DataTypeTracker(ISyncSerializer serializer) protected override TrackedItem TrackChanges() { - return new TrackedItem(serializer.ItemType) + return new TrackedItem(serializer.ItemType, true) { Children = new List() { @@ -25,10 +25,10 @@ protected override TrackedItem TrackChanges() { Children = new List() { - new TrackedItem("Name", "/Info/Name", true), - new TrackedItem("EditorAlias", "/Info/EditorAlias", true), - new TrackedItem("DatabaseType", "/Info/DatabaseType", true), - new TrackedItem("SortOrder", "/Info/SortOrder") + new TrackedItem("Name", "/Name", true), + new TrackedItem("EditorAlias", "/EditorAlias", true), + new TrackedItem("DatabaseType", "/DatabaseType", true), + new TrackedItem("SortOrder", "/SortOrder") } }, new TrackedItem("Config", "/Config", true) diff --git a/uSync8.Core/Tracking/Impliment/LanguageTracker.cs b/uSync8.Core/Tracking/Impliment/LanguageTracker.cs index 692f501f..3d6273b4 100644 --- a/uSync8.Core/Tracking/Impliment/LanguageTracker.cs +++ b/uSync8.Core/Tracking/Impliment/LanguageTracker.cs @@ -16,7 +16,16 @@ public LanguageTracker(ISyncSerializer serializer) : base(serializer) protected override TrackedItem TrackChanges() { - return new TrackedItem(serializer.ItemType); + return new TrackedItem(serializer.ItemType, true) + { + Children = new List() + { + new TrackedItem("IsoCode", "/IsoCode", true), + new TrackedItem("CultureName", "/CultureName", true), + new TrackedItem("Mandatory", "/IsMandatory", true), + new TrackedItem("Default Lanaguage", "/IsDefault", true), + } + }; } } } diff --git a/uSync8.Core/Tracking/Impliment/MacroTracker.cs b/uSync8.Core/Tracking/Impliment/MacroTracker.cs index d6f39769..ec39f088 100644 --- a/uSync8.Core/Tracking/Impliment/MacroTracker.cs +++ b/uSync8.Core/Tracking/Impliment/MacroTracker.cs @@ -16,7 +16,40 @@ public MacroTracker(ISyncSerializer serializer) : base(serializer) protected override TrackedItem TrackChanges() { - return new TrackedItem(serializer.ItemType); + return new TrackedItem(serializer.ItemType, true) + { + Children = new List() + { + new TrackedItem("Name", "/Name", true), + new TrackedItem("Source", "/MacroSource", true), + new TrackedItem("Type", "/MacroType", true), + new TrackedItem("Use In Editor", "/UseInEditor", true), + new TrackedItem("Don't Render in Editor", "/DontRender", true), + new TrackedItem("Cache By Member", "/CachedByMember", true), + new TrackedItem("Cache By Page", "/CachedByPage", true), + new TrackedItem("Cache Duration", "/CachedDuration", true), + + new TrackedItem("", "/Properties", false) + { + Children = new List() + { + new TrackedItem("Property", "/Property") + { + Repeating = new RepeatingInfo("Key", "/Property", "Name"), + Children = new List() + { + new TrackedItem("Key", "/Key", true), + new TrackedItem("Name", "/Name", true), + new TrackedItem("Alias", "/Alias", true), + new TrackedItem("SortOrder", "/SortOrder", true), + new TrackedItem("EditorAlias", "/EditorAlias", true) + } + } + } + } + } + + }; } } } diff --git a/uSync8.Core/Tracking/Impliment/MediaTypeTracker.cs b/uSync8.Core/Tracking/Impliment/MediaTypeTracker.cs index c0eda398..482f232a 100644 --- a/uSync8.Core/Tracking/Impliment/MediaTypeTracker.cs +++ b/uSync8.Core/Tracking/Impliment/MediaTypeTracker.cs @@ -8,7 +8,7 @@ namespace uSync8.Core.Tracking.Impliment { - public class MediaTypeTracker : SyncBaseTracker, ISyncTracker + public class MediaTypeTracker : ContentTypeBaseTracker, ISyncTracker { public MediaTypeTracker(ISyncSerializer serializer) : base(serializer) { @@ -16,7 +16,21 @@ public MediaTypeTracker(ISyncSerializer serializer) : base(serialize protected override TrackedItem TrackChanges() { - return new TrackedItem(serializer.ItemType); + var tracker = base.TrackChanges(); + + tracker.Children.Add( + new TrackedItem("Structure", "/Structure") + { + Children = new List() + { + new TrackedItem("MediaType", "/MediaType") + { + Repeating = new RepeatingInfo(string.Empty, string.Empty, string.Empty) + } + } + }); + + return tracker; } } } diff --git a/uSync8.Core/Tracking/Impliment/MemberTypeTracker.cs b/uSync8.Core/Tracking/Impliment/MemberTypeTracker.cs index eed32ea4..c3880c8e 100644 --- a/uSync8.Core/Tracking/Impliment/MemberTypeTracker.cs +++ b/uSync8.Core/Tracking/Impliment/MemberTypeTracker.cs @@ -8,7 +8,7 @@ namespace uSync8.Core.Tracking.Impliment { - public class MemberTypeTracker : SyncBaseTracker, ISyncTracker + public class MemberTypeTracker : ContentTypeBaseTracker, ISyncTracker { public MemberTypeTracker(ISyncSerializer serializer) : base(serializer) { @@ -16,7 +16,25 @@ public MemberTypeTracker(ISyncSerializer serializer) : base(seriali protected override TrackedItem TrackChanges() { - return new TrackedItem(serializer.ItemType); + var tracker = base.TrackChanges(); + + // the membership one is a bit of a hack + // because we want to add new properties in + // the repeating properties, but it works. + var properties = tracker.Children.FirstOrDefault(x => x.Path == "/GenericProperties"); + if (properties != null) + { + properties.Children[0] + .Children.Add(new TrackedItem("CanEdit", "/CanEdit", true)); + properties.Children[0] + .Children.Add(new TrackedItem("CanView", "/CanView", true)); + properties.Children[0] + .Children.Add(new TrackedItem("IsSensitive", "/IsSensitive", true)); + } + + + + return tracker; } } } diff --git a/uSync8.Core/Tracking/Impliment/TemplateTracker.cs b/uSync8.Core/Tracking/Impliment/TemplateTracker.cs index 95f5ebb2..b986d660 100644 --- a/uSync8.Core/Tracking/Impliment/TemplateTracker.cs +++ b/uSync8.Core/Tracking/Impliment/TemplateTracker.cs @@ -20,7 +20,7 @@ public TemplateTracker(ISyncSerializer serializer) protected override TrackedItem TrackChanges() { - return new TrackedItem(serializer.ItemType) + return new TrackedItem(serializer.ItemType, true) { Children = new List { diff --git a/uSync8.Core/Tracking/SyncBaseTracker.cs b/uSync8.Core/Tracking/SyncBaseTracker.cs index b06bc0e1..4bff32f8 100644 --- a/uSync8.Core/Tracking/SyncBaseTracker.cs +++ b/uSync8.Core/Tracking/SyncBaseTracker.cs @@ -10,7 +10,7 @@ namespace uSync8.Core.Tracking { - public abstract class SyncBaseTracker + public abstract class SyncBaseTracker where TObject : IEntity { protected readonly ISyncSerializer serializer; @@ -39,11 +39,12 @@ public virtual IEnumerable GetChanges(XElement node) var changes = TrackChanges(); var item = serializer.GetItem(node); - if (item != null) { + if (item != null) + { var current = serializer.Serialize(item); if (current.Success) { - return CalculateChanges(changes, current.Item, node); + return CalculateChanges(changes, current.Item, node, "", ""); } } @@ -51,115 +52,252 @@ public virtual IEnumerable GetChanges(XElement node) } - private IEnumerable CalculateChanges(TrackedItem change, XElement current, XElement target) + private IEnumerable CalculateChanges(TrackedItem change, XElement current, XElement target, string name, string path) { - var updates = new List(); - if (change == null) return Enumerable.Empty(); - if (string.IsNullOrWhiteSpace(change.Name) || string.IsNullOrWhiteSpace(change.Path)) - return Enumerable.Empty(); + var changePath = GetChangePath(path, change.Path); + var changeName = GetChangeName(name, change.Name); + + if (change.Repeating == null) + return CalculateSingleChange(change, current, target, changeName, changePath); + - var currentNode = current.XPathSelectElement(change.Path); - var targetNode = target.XPathSelectElement(change.Path); + return CalculateRepeatingChanges(change, current, target, changeName, changePath); + } + + private IEnumerable CalculateSingleChange(TrackedItem change, XElement current, XElement target, string name, string path) + { + var updates = new List(); + + var currentNode = current; + var targetNode = target; - if (currentNode == null) + if (!string.IsNullOrEmpty(path)) { - updates.Add(new uSyncChange() + currentNode = current.XPathSelectElement(path); + targetNode = target.XPathSelectElement(path); + + if (currentNode == null) { - Change = ChangeDetailType.Create, - Path = change.Path, - Name = change.Name, - OldValue = change.CompareValue ? targetNode.ValueOrDefault(string.Empty) : "New Property", - NewValue = "" - }); - } + return uSyncChange.Create(path, name, targetNode.ValueOrDefault(string.Empty), change.CompareValue) + .AsEnumerableOfOne(); - if (targetNode == null) - { - // its a delete (not in target) - updates.Add(new uSyncChange() + } + + if (targetNode == null) + { + // its a delete (not in target) + return uSyncChange.Delete(path, name, currentNode.ValueOrDefault(string.Empty), change.CompareValue) + .AsEnumerableOfOne(); + } + + // this happens if both exist, we compare values in them. + + if (change.CompareValue) + { + // actual change + updates.AddNotNull(Compare(path, name, + currentNode.ValueOrDefault(string.Empty), + targetNode.ValueOrDefault(string.Empty))); + } + + if (change.Attributes != null && change.Attributes.Any()) { - Change = ChangeDetailType.Delete, - Path = change.Path, - Name = change.Name, - OldValue = change.CompareValue ? currentNode.ValueOrDefault(string.Empty) : "Missing Property", - NewValue = "" - }); + foreach (var attribute in change.Attributes) + { + var currentValue = currentNode.Attribute(attribute).ValueOrDefault(string.Empty); + var targetValue = targetNode.Attribute(attribute).ValueOrDefault(string.Empty); + updates.AddNotNull(Compare(path, $"{name} [{attribute}]", currentValue, targetValue)); + } + } } - if (change.CompareValue) + if (change.Children != null && change.Children.Any()) { - // actual change - updates.AddNotNull(Compare(change.Name, change.Path, - currentNode.ValueOrDefault(string.Empty), - targetNode.ValueOrDefault(string.Empty))); + foreach (var child in change.Children) + { + updates.AddRange(CalculateChanges(child, currentNode, targetNode, name, path)); + } } - if (change.Attributes != null && change.Attributes.Any()) + return updates; + } + + /// + /// works out changes when we have a repeating block (e.g all the properties on a content type) + /// + private IEnumerable CalculateRepeatingChanges(TrackedItem change, XElement current, XElement target, string name, string path) + { + var updates = new List(); + + var currentItems = current.XPathSelectElements(path); + var targetItems = target.XPathSelectElements(path); + + // loop through the nodes in the current item + foreach (var currentNode in currentItems) { - foreach(var attribute in change.Attributes) + var currentNodePath = path; + var currentNodeName = name; + + XElement targetNode = null; + + + // if the key is blank we just compare the values in the elements + if (string.IsNullOrWhiteSpace(change.Repeating.Key)) + { + targetNode = targetItems.FirstOrDefault(x => x.Value == currentNode.Value); + } + else + { + // we need to find the current key value + var currentKey = GetKeyValue(currentNode, change.Repeating.Key, change.Repeating.KeyIsAttribute); + if (currentKey == string.Empty) continue; + + // now we need to make the XPath for the children this will be [key = ''] or [@key =''] + // depending if its an attribute or element key + currentNodePath += MakeKeyPath(change.Repeating.Key, currentKey, change.Repeating.KeyIsAttribute); + if (!string.IsNullOrWhiteSpace(change.Repeating.Name)) + currentNodeName += $": {currentNode.Element(change.Repeating.Name).ValueOrDefault(change.Repeating.Value)}"; + + // now see if we can find that node in the target elements we have loaded + targetNode = GetTarget(targetItems, change.Repeating.Key, currentKey, change.Repeating.KeyIsAttribute); + } + + if (targetNode == null) + { + // no target, this element will get deleted + var oldValue = currentNode.Value; + if (!string.IsNullOrWhiteSpace(change.Repeating.Name)) + { + oldValue = $"{currentNode.Element(change.Repeating.Name).ValueOrDefault(string.Empty)}"; + } + + updates.Add(uSyncChange.Delete(path, name, oldValue)); + continue; + } + + // check all the children of the current and target for changes + if (change.Children != null && change.Children.Any()) { - var currentValue = currentNode.Attribute(attribute).ValueOrDefault(string.Empty); - var targetValue = targetNode.Attribute(attribute).ValueOrDefault(string.Empty); - updates.AddNotNull(Compare($"{change.Name} [{attribute}]", change.Path, currentValue, targetValue)); + foreach (var child in change.Children) + { + updates.AddRange(CalculateChanges(child, currentNode, targetNode, currentNodeName, currentNodePath)); + } + } + else + { + // if there are no children, they we are comparing the actual text of the nodes + updates.AddNotNull(Compare(path, name, + currentNode.ValueOrDefault(string.Empty), + targetNode.ValueOrDefault(string.Empty))); } } - if (change.Children != null && change.Children.Any()) + // look for things in target but not current (for they will be removed) + List missing = new List(); + + if (string.IsNullOrWhiteSpace(change.Repeating.Key)) + { + missing = targetItems.Where(x => !currentItems.Any(t => t.Value == x.Value)) + .ToList(); + } + else { - foreach(var child in change.Children) + missing = targetItems.Where(x => + !currentItems.Any(t => t.Element(change.Repeating.Key).ValueOrDefault(string.Empty) == x.Element(change.Repeating.Key).ValueOrDefault(string.Empty))) + .ToList(); + } + + if (missing.Any()) + { + foreach (var missingItem in missing) { - updates.AddRange(CalculateChanges(child, current, target)); + var oldValue = missingItem.Value; + if (!string.IsNullOrWhiteSpace(change.Repeating.Name)) + oldValue = $"{missingItem.Element(change.Repeating.Name).ValueOrDefault(string.Empty)}"; + + updates.Add(uSyncChange.Create(path, name, oldValue)); } } return updates; } - - protected uSyncChange Compare(string name, string path, string current, string target) + protected uSyncChange Compare(string path, string name, string current, string target) { if (current == target) return null; + return uSyncChange.Update(path, name, current, target); + } + + private uSyncChange Compare(string path, string name, TValue current, TValue target) + { + if (current.Equals(target)) return null; + return Compare(path, name, current.ToString(), target.ToString()); + } + + private string GetChangePath(string path, string child) + { + return path + child.Replace("//", "/"); + } - return new uSyncChange() + private string GetChangeName(string parent, string name) + { + if (!string.IsNullOrWhiteSpace(parent)) + return $"{parent}: " + name; + + return name; + } + + private string GetKeyValue(XElement node, string key, bool isAttribute) + { + if (isAttribute) { - Name = name, - Path = path, - Change = ChangeDetailType.Update, - NewValue = target, - OldValue = current - }; + return node.Attribute(key).ValueOrDefault(string.Empty); + } + + return node.Element(key).ValueOrDefault(string.Empty); } - private uSyncChange Compare(string name, string path, TValue current, TValue target) + private string MakeKeyPath(string key, string keyValue, bool isAttribute) { - if (current.Equals(target)) return null; + if (isAttribute) + { + return $"[@{key} = '{keyValue}']"; + } - return Compare(name, path, current.ToString(), target.ToString()); + return $"[{key} = '{keyValue}']"; } - protected virtual bool XmlMatches(XElement oldNode, XElement newNode) + private XElement GetTarget(IEnumerable items, string key, string value, bool isAttribute) { - return false; + if (isAttribute) + return items.FirstOrDefault(x => x.Attribute(key).ValueOrDefault(string.Empty) == value); + + return items.FirstOrDefault(x => x.Element(key).ValueOrDefault(string.Empty) == value); } } public class TrackedItem { - public TrackedItem(string name) + private TrackedItem(string name) { Name = name; Path = "/"; + } - Attributes = new List + public TrackedItem(string name, bool root) + : this(name) + { + if (root == true) { - "Key", - "Alias" - }; - - Children = new List(); + Attributes = new List + { + "Key", + "Alias" + }; + } } public TrackedItem(string name, string path) @@ -170,15 +308,50 @@ public TrackedItem(string name, string path) } public TrackedItem(string name, string path, bool compareValue) - :this(name, path) + : this(name, path) { CompareValue = compareValue; } + public RepeatingInfo Repeating { get; set; } + public bool CompareValue { get; set; } public string Path { get; set; } public string Name { get; set; } - public List Attributes { get; set; } - public List Children { get; set; } + public List Attributes { get; set; } = new List(); + public List Children { get; set; } = new List(); + } + + public class RepeatingInfo + { + public RepeatingInfo(string key, string value, string name) + { + Key = key; + Value = value; + Name = name; + } + + /// + /// Element used to match items in a collection of nodes + /// (e.g Key) + /// + public string Key { get; set; } + + /// + /// The repeating element name + /// (e.g GenericProperty) + /// + public string Value { get; set; } + + /// + /// the node we use to display the name of any item in the + /// repeater (e.g Alias) + /// + public string Name { get; set; } + + /// + /// indicates if the key is actually an attribute on the node. + /// + public bool KeyIsAttribute { get; set; } } } diff --git a/uSync8.Core/uSync8.Core.csproj b/uSync8.Core/uSync8.Core.csproj index 4964d6b6..6abdd9b7 100644 --- a/uSync8.Core/uSync8.Core.csproj +++ b/uSync8.Core/uSync8.Core.csproj @@ -147,6 +147,7 @@ + diff --git a/uSync8.Site/App_Plugins/uSync8/actions.html b/uSync8.Site/App_Plugins/uSync8/actions.html new file mode 100644 index 00000000..aa768c71 --- /dev/null +++ b/uSync8.Site/App_Plugins/uSync8/actions.html @@ -0,0 +1,147 @@ + + +
+ + + + + + +
+
+ + +
+
+ +
{{handler.Name}}
+
+
+
+

{{vm.status.Message}}

+
+
+
+
+
+
+ + +
+
+
{{vm.action}}
+
{{vm.countChanges(vm.results)}} changes across {{vm.results.length}} items
+
+ +
+ +
+
+
+
+
+
+ Type +
+
+ Name +
+
+ Change +
+
+ Message +
+
+
+
+
+
+
+
+ + + +
+
+ {{vm.getTypeName(result.ItemType)}} +
+
+ {{result.Name}} +
+
+ {{result.Change}} +
+
+ {{result.Message}} +
+
+ + +
+
+
+
+
+
+
+
Name
+
Old Value
+
New Value
+
+
+
+
+
+
+
+ {{detail.Name}} +
+
+ {{detail.OldValue}} +
+
+ {{detail.NewValue}} +
+
+ {{detail.Change}} +
+
+
+
+
+
+
+
+
+

No Changes

+
+
+
+ diff --git a/uSync8.Site/App_Plugins/uSync8/dashboard.html b/uSync8.Site/App_Plugins/uSync8/dashboard.html index eb4799dc..cda0fd01 100644 --- a/uSync8.Site/App_Plugins/uSync8/dashboard.html +++ b/uSync8.Site/App_Plugins/uSync8/dashboard.html @@ -11,145 +11,167 @@

uSync 8

+ action="vm.toggleSettings()" + ng-if="!vm.settingsView"> + +
- - -
- - - - - - -
-
- -
-
- -
{{handler.Name}}
+
+ + +
+ + + + + + + +
-
-
-

{{vm.status.Message}}

-
-
-
-
- - - - -
-
-
{{vm.action}}
-
{{vm.countChanges(vm.results)}} changes across {{vm.results.length}} items
-
- -
- -
-
-
-
-
-
- Type -
-
- Name -
-
- Change -
-
- Message -
-
+ + + +
+
+ +
{{handler.Name}}
-
-
-
-
- - - -
+
+

{{vm.status.Message}}

+
+
+
+
+ + + + +
+
+
{{vm.action}}
+
{{vm.countChanges(vm.results)}} changes across {{vm.results.length}} items
+
+ +
+ +
+
+
- {{vm.getTypeName(result.ItemType)}}
- {{result.Name}} + Type
- {{result.Change}} + Name
- {{result.Message}} + Change
- - + Message
+
-
-
-
-
-
-
Name
-
Old Value
-
New Value
-
+
+
+
+
+
+ + + +
+
+ {{vm.getTypeName(result.ItemType)}} +
+
+ {{result.Name}} +
+
+ {{result.Change}} +
+
+ {{result.Message}} +
+
+ +
+ ({{result.Details.length}} items)
-
-
-
-
- {{detail.Name}} -
-
- {{detail.OldValue}} -
-
- {{detail.NewValue}} +
+
+
+
+
+
+
Action
+
Item
+
Old Value
+
New Value
-
- {{detail.Change}} +
+
+
+
+ + + + +
+
+ {{detail.Change}} +
+
+ {{detail.Name}} +
+
+ {{detail.OldValue}} +
+
+ {{detail.NewValue}} +
@@ -157,20 +179,113 @@

{{vm.status.Message}}

-
-
-

No Changes

-
- - +
+

No Changes

+
+ + +
+ +
+
+
+ + + + +
+ + +
+
Import at startup
+
Run an import of files from the disk when Umbraco starts
+
+
+ +
+ + +
+
Export at startup
+
Export the Umbraco settings when the site starts up
+
+
- +
+ + +
+
Export on Save
+
Generate uSync files when items are saved
+
+
+
+
+ + + + +
+ + +
+
Flat structure
+
All items of a type are stored in a flat folder strucure
+
+
+
+ + +
+
use Guids for filenames
+
use the guid of an item as the filename
+
+
+
+ + + +
+ +
+
+
+ + + +
+ + +
+
+ {{handler.Alias}} +
+
Enabled for : {{handler.Actions}}
+
+
+
+
+
+ +
diff --git a/uSync8.Site/App_Plugins/uSync8/lang/en-US.xml b/uSync8.Site/App_Plugins/uSync8/lang/en-US.xml index 0b68dfc1..57229f69 100644 --- a/uSync8.Site/App_Plugins/uSync8/lang/en-US.xml +++ b/uSync8.Site/App_Plugins/uSync8/lang/en-US.xml @@ -9,11 +9,13 @@ Database elements to and from disk Import + Full Import Report Export Details Item Handlers + Save Settings \ No newline at end of file diff --git a/uSync8.Site/App_Plugins/uSync8/settings.html b/uSync8.Site/App_Plugins/uSync8/settings.html new file mode 100644 index 00000000..91449eb2 --- /dev/null +++ b/uSync8.Site/App_Plugins/uSync8/settings.html @@ -0,0 +1,3 @@ +
+ settings +
\ No newline at end of file diff --git a/uSync8.Site/App_Plugins/uSync8/uSyncDashboardController.js b/uSync8.Site/App_Plugins/uSync8/uSyncDashboardController.js index d32505e5..605d82d4 100644 --- a/uSync8.Site/App_Plugins/uSync8/uSyncDashboardController.js +++ b/uSync8.Site/App_Plugins/uSync8/uSyncDashboardController.js @@ -31,6 +31,7 @@ vm.runmode = modes.NONE; vm.showAll = false; + vm.settingsView = false; vm.settings = {}; vm.handlers = []; @@ -38,17 +39,38 @@ vm.reportAction = ''; + // buttons + + vm.importButton = { + state: 'init', + defaultButton: { + labelKey: "usync_import", + handler: importItems + }, + subButtons: [{ + labelKey: "usync_importforce", + handler: importForce + }] + }; + + // functions vm.report = report; vm.exportItems = exportItems; + vm.importForce = importForce; vm.importItems = importItems; + vm.saveSettings = saveSettings; + vm.toggleDetails = toggleDetails; vm.getTypeName = getTypeName; vm.toggleAll = toggleAll; vm.countChanges = countChanges; vm.calcPercentage = calcPercentage; + vm.toggleSettings = toggleSettings; + vm.toggle = toggle; + vm.showChange = showChange; // kick it all off @@ -80,14 +102,35 @@ }); } + function importForce() { + importItems(true); + } + function importItems(force) { resetStatus(modes.IMPORT); + vm.importButton.state = 'busy'; uSync8DashboardService.importItems(force, getClientId()) .then(function (result) { vm.results = result.data; vm.working = false; vm.reported = true; + vm.importButton.state = 'success'; + }, function (error) { + vm.importButton.state = 'error'; + notificationsService.error('Failed', error.data.ExceptionMessage); + + vm.working = false; + vm.reported = true; + }); + } + + function saveSettings() { + vm.working = false; + uSync8DashboardService.saveSettings(vm.settings) + .then(function (result) { + vm.working = false; + notificationsService.success('Saved', 'Settings updated'); }); } @@ -124,6 +167,14 @@ return (100 * status.Processed) / status.TotalSteps; } + function toggle(item) { + item = !item; + } + + function toggleSettings() { + vm.settingsView = !vm.settingsView; + } + ////// private function init() { diff --git a/uSync8.Site/App_Plugins/uSync8/uSyncService.js b/uSync8.Site/App_Plugins/uSync8/uSyncService.js index 6ce26f92..422e7ae9 100644 --- a/uSync8.Site/App_Plugins/uSync8/uSyncService.js +++ b/uSync8.Site/App_Plugins/uSync8/uSyncService.js @@ -20,7 +20,8 @@ report: report, exportItems: exportItems, - importItems: importItems + importItems: importItems, + saveSettings: saveSettings }; return service; @@ -46,6 +47,10 @@ function importItems(force, clientId) { return $http.put(serviceRoot + 'import', { force: force, clientId: clientId }); } + + function saveSettings(settings) { + return $http.post(serviceRoot + 'savesettings', settings); + } } angular.module('umbraco.services') diff --git a/uSync8.Site/App_Plugins/uSync8/usync.css b/uSync8.Site/App_Plugins/uSync8/usync.css index 08197cad..67985178 100644 --- a/uSync8.Site/App_Plugins/uSync8/usync.css +++ b/uSync8.Site/App_Plugins/uSync8/usync.css @@ -29,14 +29,44 @@ font-weight: 700; } +.usync-detail-count { + padding: 6px 0; +} + .usync-item-details { border-left: 4px solid #aaa; } + .usync-item-details .umb-table-head .umb-table-row { + background-color: rgba(0,0,0,0.05); + border-bottom: 1px solid black; + } + .usync-item-details .umb-table { background-color: #f3f3f5; } + .usync-item-details .usync-detail-action-cell { + flex: 0 0 110px; + } + + .usync-item-details .usync-old-value { + text-decoration: line-through; + color: #C62828; + } + + .usync-item-details .usync-new-value { + color: #2e7d32 + } + +.usync-row-delete { + background-color: #ffebee; +} + +.usync-row-create { + background-color: #E8F5E9; +} + .usync-handler-icon { padding: 0.75em; margin-right: 14px; @@ -68,3 +98,27 @@ .usync-handler-icon.usync-complete { color: #35c786; } + + +.usync-settings { + display: flex; +} + +.usync-settings > div { + width: 50%; +} + + .usync-settings .usync-main-settings { + margin-right: 14px; + } + + .usync-control { + position: relative; + padding: 10px 0; + } + + .usync-control input[type="text"] { + position: absolute; + top: 10px; + width: 200px; + } \ No newline at end of file diff --git a/uSync8.Site/Web.config b/uSync8.Site/Web.config index 84dd0239..81a471a2 100644 --- a/uSync8.Site/Web.config +++ b/uSync8.Site/Web.config @@ -1,5 +1,5 @@  - +