diff --git a/uSync.BackOffice/Hubs/uSyncCallbacks.cs b/uSync.BackOffice/Hubs/uSyncCallbacks.cs index 6f6fdfaf..77b0bf36 100644 --- a/uSync.BackOffice/Hubs/uSyncCallbacks.cs +++ b/uSync.BackOffice/Hubs/uSyncCallbacks.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using uSync.BackOffice.Models; @@ -11,26 +12,52 @@ namespace uSync.BackOffice; public delegate void SyncEventCallback(SyncProgressSummary summary); /// -/// callback delegate for SignalR messaging +/// async callback delegate for SignalR messaging +/// +public delegate Task SyncEventCallbackAsync(SyncProgressSummary summary); + +/// +/// callback delegate for SignalR messaging /// public delegate void SyncUpdateCallback(string message, int count, int total); +/// +/// async callback delegate for SignalR messaging +/// +public delegate Task SyncUpdateCallbackAsync(string message, int count, int total); + /// /// callback delegate to set the start and end range for the update counters. /// public delegate void SyncSetUpdateRange(int start, int end); +/// +/// async callback delegate to set the start and end range for the update counters. +/// +public delegate Task SyncSetUpdateRangeAsync(int start, int end); + /// /// callback to send a update message and increment the counter by one so moving the progress bar. /// /// public delegate void SyncIncrementalUpdateCallback(string message); +/// +/// async callback to send a update message and increment the counter by one so moving the progress bar. +/// +/// +public delegate Task SyncIncrementalUpdateCallbackAsync(string message); + /// /// callback to signal that the sync is complete /// public delegate void SyncCompleteCallBack(Guid id, string message, bool success, IEnumerable actions); +/// +/// async callback to signal that the sync is complete +/// +public delegate Task SyncCompleteCallBackAsync(Guid id, string message, bool success, IEnumerable actions); + /// /// Callback objects used to communicate via SignalR @@ -42,29 +69,56 @@ public class uSyncCallbacks /// public SyncEventCallback? Callback { get; private set; } + /// + /// Async add event callback. Set alongside (or instead of) when the + /// consumer needs to await the send (e.g. a SignalR hub push) rather than block on it. + /// + public SyncEventCallbackAsync? CallbackAsync { get; set; } + /// /// Update event callback /// public SyncUpdateCallback? Update { get; private set; } + /// + /// Async update event callback. Set alongside (or instead of) when the + /// consumer needs to await the send rather than block on it. + /// + public SyncUpdateCallbackAsync? UpdateAsync { get; set; } + /// /// set a start and end range for the counter. /// public SyncSetUpdateRange? SetRange { get; private set; } + /// + /// Async version of . + /// + public SyncSetUpdateRangeAsync? SetRangeAsync { get; set; } + /// /// update and increment callback. /// public SyncIncrementalUpdateCallback? IncrementalUpdate { get; private set; } + /// + /// Async version of . + /// + public SyncIncrementalUpdateCallbackAsync? IncrementalUpdateAsync { get; set; } + /// /// callback to signal that the sync is complete /// public SyncCompleteCallBack? Complete { get; private set; } + /// + /// Async version of . + /// + public SyncCompleteCallBackAsync? CompleteAsync { get; set; } + /// - /// generate a new callback object + /// generate a new callback object /// public uSyncCallbacks(SyncEventCallback? callback, SyncUpdateCallback? update) { @@ -75,9 +129,9 @@ public uSyncCallbacks(SyncEventCallback? callback, SyncUpdateCallback? update) /// /// generate a callback object with range and incremental update /// - public uSyncCallbacks(SyncEventCallback? callback, + public uSyncCallbacks(SyncEventCallback? callback, SyncUpdateCallback? update, - SyncSetUpdateRange? updateRange, + SyncSetUpdateRange? updateRange, SyncIncrementalUpdateCallback incrementalUpdate, SyncCompleteCallBack? complete) : this(callback, update) @@ -86,4 +140,59 @@ public uSyncCallbacks(SyncEventCallback? callback, this.IncrementalUpdate = incrementalUpdate; this.Complete = complete; } + + /// + /// raise the / event, awaiting the async + /// version (if set) so callers no longer need to block on it. + /// + public async Task RaiseCallbackAsync(SyncProgressSummary summary) + { + Callback?.Invoke(summary); + if (CallbackAsync is not null) + await CallbackAsync(summary).ConfigureAwait(false); + } + + /// + /// raise the / event, awaiting the async + /// version (if set) so callers no longer need to block on it. + /// + public async Task RaiseUpdateAsync(string message, int count, int total) + { + Update?.Invoke(message, count, total); + if (UpdateAsync is not null) + await UpdateAsync(message, count, total).ConfigureAwait(false); + } + + /// + /// raise the / event, awaiting the async + /// version (if set) so callers no longer need to block on it. + /// + public async Task RaiseSetRangeAsync(int start, int end) + { + SetRange?.Invoke(start, end); + if (SetRangeAsync is not null) + await SetRangeAsync(start, end).ConfigureAwait(false); + } + + /// + /// raise the / event, awaiting + /// the async version (if set) so callers no longer need to block on it. + /// + public async Task RaiseIncrementalUpdateAsync(string message) + { + IncrementalUpdate?.Invoke(message); + if (IncrementalUpdateAsync is not null) + await IncrementalUpdateAsync(message).ConfigureAwait(false); + } + + /// + /// raise the / event, awaiting the async + /// version (if set) so callers no longer need to block on it. + /// + public async Task RaiseCompleteAsync(Guid id, string message, bool success, IEnumerable actions) + { + Complete?.Invoke(id, message, success, actions); + if (CompleteAsync is not null) + await CompleteAsync(id, message, success, actions).ConfigureAwait(false); + } } diff --git a/uSync.BackOffice/Services/SyncActionService.cs b/uSync.BackOffice/Services/SyncActionService.cs index 91310959..afe499d6 100644 --- a/uSync.BackOffice/Services/SyncActionService.cs +++ b/uSync.BackOffice/Services/SyncActionService.cs @@ -124,7 +124,8 @@ public async Task ImportPostAsync(SyncFinalActionRequest reque request.ActionOptions.GetSetOrDefault(_uSyncConfig.Settings.DefaultSet), request.Actions); - request.Callbacks?.Update?.Invoke("Post Import Complete", 1, 1); + if (request.Callbacks is not null) + await request.Callbacks.RaiseUpdateAsync("Post Import Complete", 1, 1); return new SyncActionResult([.. actions.Where(x => x.Change > Core.ChangeType.NoChange)]); } @@ -218,7 +219,8 @@ public async Task FinishProcessAsync(SyncFinalActionRequest re request.HandlerAction, request.Actions.CountChanges(), request.Actions.Count(), elapsed); } - request.Callbacks?.Update?.Invoke($"{request.HandlerAction} completed ({elapsed:#,#}ms)", 1, 1); + if (request.Callbacks is not null) + await request.Callbacks.RaiseUpdateAsync($"{request.HandlerAction} completed ({elapsed:#,#}ms)", 1, 1); // for speed we return an empty list. the merge will just take // what we where passed in, and we avoid a whole copy and compare step diff --git a/uSync.BackOffice/Services/SyncService.cs b/uSync.BackOffice/Services/SyncService.cs index bd0d31a0..0a2db748 100644 --- a/uSync.BackOffice/Services/SyncService.cs +++ b/uSync.BackOffice/Services/SyncService.cs @@ -198,7 +198,8 @@ public async Task> ImportAsync(string[] folders, bool f summary.UpdateHandler( handler.Name, HandlerStatus.Processing, $"Importing {handler.Name}", 0); - callbacks?.Callback?.Invoke(summary); + if (callbacks is not null) + await callbacks.RaiseCallbackAsync(summary); var handlerActions = await this.ImportHandlerAsync(handler.Alias, importOptions); @@ -228,14 +229,18 @@ public async Task> ImportAsync(string[] folders, bool f if (actions.ContainsErrors()) _logger.LogWarning("uSync Import: Errors detected in import : {count}", actions.CountErrors()); - callbacks?.Update?.Invoke($"Processed {actions.Count} items in {sw.ElapsedMilliseconds}ms", 1, 1); + if (callbacks is not null) + await callbacks.RaiseUpdateAsync($"Processed {actions.Count} items in {sw.ElapsedMilliseconds}ms", 1, 1); summary.Message = "Completed"; summary.Total = handlers.Count() + 1; var finalActions = actions.Select(x => x.AsActionView()).ToList(); - callbacks?.Callback?.Invoke(summary); - callbacks?.Complete?.Invoke(requestId, "Sync complete", true, finalActions); + if (callbacks is not null) + { + await callbacks.RaiseCallbackAsync(summary); + await callbacks.RaiseCompleteAsync(requestId, "Sync complete", true, finalActions); + } return actions; } @@ -392,7 +397,8 @@ public async Task> ExportAsync(string folder, IEnumerab summary.UpdateHandler( handler.Name, HandlerStatus.Processing, $"Exporting {handler.Name}", 0); - callbacks?.Callback?.Invoke(summary); + if (callbacks is not null) + await callbacks.RaiseCallbackAsync(summary); var handlerActions = await handler.ExportAllAsync([$"{folder}/{handler.DefaultFolder}"], configuredHandler.Settings, callbacks?.Update); @@ -405,7 +411,8 @@ public async Task> ExportAsync(string folder, IEnumerab summary.UpdateMessage("Export Completed"); - callbacks?.Callback?.Invoke(summary); + if (callbacks is not null) + await callbacks.RaiseCallbackAsync(summary); await _mutexService.FireBulkCompleteAsync(new uSyncExportCompletedNotification(actions, null)); @@ -419,7 +426,8 @@ public async Task> ExportAsync(string folder, IEnumerab sw.ElapsedMilliseconds); } - callbacks?.Update?.Invoke($"Processed {actions.Count} items in {sw.ElapsedMilliseconds}ms", 1, 1); + if (callbacks is not null) + await callbacks.RaiseUpdateAsync($"Processed {actions.Count} items in {sw.ElapsedMilliseconds}ms", 1, 1); return actions; } diff --git a/uSync.BackOffice/Services/SyncService_Single.cs b/uSync.BackOffice/Services/SyncService_Single.cs index f9b0b341..6f018838 100644 --- a/uSync.BackOffice/Services/SyncService_Single.cs +++ b/uSync.BackOffice/Services/SyncService_Single.cs @@ -59,8 +59,9 @@ public async Task> ReportPartialAsync(IList> ImportPartialAsync(IList> ImportPartialSecondPassAsync(IEnumer continue; } - options.Callbacks?.Update?.Invoke($"Second Pass: {action.Name}", - CalculateProgress(index, total, options.ProgressMin, options.ProgressMax), 100); + if (options.Callbacks is not null) + await options.Callbacks.RaiseUpdateAsync($"Second Pass: {action.Name}", + CalculateProgress(index, total, options.ProgressMin, options.ProgressMax), 100); secondPassActions.AddRange(await handlerPair.Handler.ImportSecondPassAsync(action, handlerPair.Settings, options)); @@ -273,7 +276,8 @@ public async Task> ImportPartialPostImportAsync(IEnumer { if (handlerPair.Handler is ISyncPostImportHandler postImportHandler) { - options.Callbacks?.Update?.Invoke(actionItem.alias, index, folders.Count); + if (options.Callbacks is not null) + await options.Callbacks.RaiseUpdateAsync(actionItem.alias, index, folders.Count); var handlerActions = actions.Where(x => x.HandlerAlias.InvariantEquals(handlerPair.Handler.Alias)); results.AddRange(await postImportHandler.ProcessPostImportAsync(handlerActions, handlerPair.Settings)); @@ -326,7 +330,8 @@ public async Task> ImportPostCleanFilesAsync(IEnumerabl if (handlerPair.Handler is ISyncCleanEntryHandler cleanEntryHandler) { - options.Callbacks?.Update?.Invoke(actionItem.alias, index, cleans.Count); + if (options.Callbacks is not null) + await options.Callbacks.RaiseUpdateAsync(actionItem.alias, index, cleans.Count); var handlerActions = actions.Where(x => x.HandlerAlias.InvariantEquals(handlerPair.Handler.Alias)); results.AddRange(await cleanEntryHandler.ProcessCleanActionsAsync(actionItem.folder, handlerActions, handlerPair.Settings)); diff --git a/uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs b/uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs index d5b3f275..9be83d04 100644 --- a/uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs +++ b/uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs @@ -266,11 +266,13 @@ public async Task> ImportAllAsync(string[] folders, Han var cacheKey = PrepCaches(); try { - options.Callbacks?.Update?.Invoke("Calculating import order", 1, 9); + if (options.Callbacks is not null) + await options.Callbacks.RaiseUpdateAsync("Calculating import order", 1, 9); var items = await GetMergedItemsAsync(folders, new SyncMergeOptions(options.Callbacks?.Update)); - options.Callbacks?.Update?.Invoke($"Processing {items.Count} items", 2, 9); + if (options.Callbacks is not null) + await options.Callbacks.RaiseUpdateAsync($"Processing {items.Count} items", 2, 9); // create the update list with items.count space. this is the max size we need this list. List actions = new(items.Count); @@ -280,7 +282,8 @@ public async Task> ImportAllAsync(string[] folders, Han int count = 0; int total = items.Count; - options.Callbacks?.SetRange?.Invoke(count, total); + if (options.Callbacks is not null) + await options.Callbacks.RaiseSetRangeAsync(count, total); foreach (var item in items) { @@ -326,7 +329,8 @@ public async Task> ImportAllAsync(string[] folders, Han await PerformImportCleanAsync(cleanMarkers, actions, config, options.Callbacks?.Update); } - options.Callbacks?.Update?.Invoke("Done", 3, 3); + if (options.Callbacks is not null) + await options.Callbacks.RaiseUpdateAsync("Done", 3, 3); if (logger.IsEnabled(LogLevel.Debug)) logger.LogDebug("ImportAll: {count} items imported", actions.Count); @@ -458,7 +462,8 @@ virtual public async Task> ImportElementAsync(XElement { var actions = new List(); var elements = node.Elements().ToList(); - options.Callbacks?.SetRange?.Invoke(0, elements.Count); + if (options.Callbacks is not null) + await options.Callbacks.RaiseSetRangeAsync(0, elements.Count); foreach (var item in elements) { actions.AddRange(await ImportSingleElementAsync(new XElement(item), filename, settings, options)); @@ -492,7 +497,8 @@ virtual protected async Task> ImportSingleElementAsync( try { - options.Callbacks?.IncrementalUpdate?.Invoke(node.GetAlias()); + if (options.Callbacks is not null) + await options.Callbacks.RaiseIncrementalUpdateAsync(node.GetAlias()); // merge the options from the handler and any import options into our serializer options. var serializerOptions = new SyncSerializerOptions(options.Flags, settings.Settings, options.UserId); diff --git a/uSync.Backoffice.Management.Api/Services/uSyncManagementService.cs b/uSync.Backoffice.Management.Api/Services/uSyncManagementService.cs index 9ba186b7..c63dd893 100644 --- a/uSync.Backoffice.Management.Api/Services/uSyncManagementService.cs +++ b/uSync.Backoffice.Management.Api/Services/uSyncManagementService.cs @@ -269,7 +269,7 @@ await _syncActionService.StartProcessAsync(new SyncStartActionRequest if (actionRequest.StepNumber >= handlers.Count) { var actions = await PerformFinalSteps(requestId, action, handlerOptions, callbacks, user?.Username); - return SummerizeCompleteProcess(actionRequest, action, handlers, requestId, callbacks, actions); + return await SummerizeCompleteProcess(actionRequest, action, handlers, requestId, callbacks, actions); } @@ -282,7 +282,7 @@ await _syncActionService.StartProcessAsync(new SyncStartActionRequest var allActions = _syncManagementCache.GetCachedActions(requestId); var summaries = GetSummaries(action, handlers, actionRequest.StepNumber, allActions); - callbacks.Callback?.Invoke(new SyncProgressSummary(summaries, "Processing " + action.ToString(), handlers.Count)); + await callbacks.RaiseCallbackAsync(new SyncProgressSummary(summaries, "Processing " + action.ToString(), handlers.Count)); return new PerformActionResponse { @@ -293,15 +293,18 @@ await _syncActionService.StartProcessAsync(new SyncStartActionRequest }; } - private static PerformActionResponse SummerizeCompleteProcess(PerformActionRequest actionRequest, HandlerActions action, List handlers, Guid requestId, uSyncCallbacks callbacks, List actions) + private static async Task SummerizeCompleteProcess(PerformActionRequest actionRequest, HandlerActions action, List handlers, Guid requestId, uSyncCallbacks callbacks, List actions) { var finalSummary = GetSummaries(action, handlers, actionRequest.StepNumber + 1, actions); var actionViews = actions.Select(x => x.AsActionView()); - callbacks?.Callback?.Invoke(new SyncProgressSummary(finalSummary, "Completed", handlers.Count)); - callbacks?.Complete?.Invoke(requestId, "Sync complete", true, actionViews); + if (callbacks is not null) + { + await callbacks.RaiseCallbackAsync(new SyncProgressSummary(finalSummary, "Completed", handlers.Count)); + await callbacks.RaiseCompleteAsync(requestId, "Sync complete", true, actionViews); + } - // finished. + // finished. return new PerformActionResponse { RequestId = requestId.ToString(),