From 7217e891f70f8ff779bb1ca3b789e09ba47088c5 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Sun, 27 Oct 2024 00:17:46 +0300 Subject: [PATCH] New: Real time UI updates for provider changes (cherry picked from commit 20ef22be94f4bdb5633ddfb080e91c8d5b0229da) Closes #5178 --- frontend/src/Components/SignalRConnector.js | 50 ++++++++++++++++++- .../DownloadClientController.cs | 5 +- .../ImportLists/ImportListController.cs | 12 +++-- .../Indexers/IndexerController.cs | 7 ++- .../Metadata/MetadataController.cs | 5 +- .../Notifications/NotificationController.cs | 5 +- src/Lidarr.Api.V1/ProviderControllerBase.cs | 31 +++++++++++- 7 files changed, 99 insertions(+), 16 deletions(-) diff --git a/frontend/src/Components/SignalRConnector.js b/frontend/src/Components/SignalRConnector.js index db631de6f..365827a2b 100644 --- a/frontend/src/Components/SignalRConnector.js +++ b/frontend/src/Components/SignalRConnector.js @@ -172,7 +172,7 @@ class SignalRConnector extends Component { const status = resource.status; // Both successful and failed commands need to be - // completed, otherwise they spin until they timeout. + // completed, otherwise they spin until they time out. if (status === 'completed' || status === 'failed') { this.props.dispatchFinishCommand(resource); @@ -224,10 +224,58 @@ class SignalRConnector extends Component { repopulatePage('trackFileUpdated'); }; + handleDownloadclient = ({ action, resource }) => { + const section = 'settings.downloadClients'; + + if (action === 'created' || action === 'updated') { + this.props.dispatchUpdateItem({ section, ...resource }); + } else if (action === 'deleted') { + this.props.dispatchRemoveItem({ section, id: resource.id }); + } + }; + handleHealth = () => { this.props.dispatchFetchHealth(); }; + handleImportlist = ({ action, resource }) => { + const section = 'settings.importLists'; + + if (action === 'created' || action === 'updated') { + this.props.dispatchUpdateItem({ section, ...resource }); + } else if (action === 'deleted') { + this.props.dispatchRemoveItem({ section, id: resource.id }); + } + }; + + handleIndexer = ({ action, resource }) => { + const section = 'settings.indexers'; + + if (action === 'created' || action === 'updated') { + this.props.dispatchUpdateItem({ section, ...resource }); + } else if (action === 'deleted') { + this.props.dispatchRemoveItem({ section, id: resource.id }); + } + }; + + handleMetadata = ({ action, resource }) => { + const section = 'settings.metadata'; + + if (action === 'updated') { + this.props.dispatchUpdateItem({ section, ...resource }); + } + }; + + handleNotification = ({ action, resource }) => { + const section = 'settings.notifications'; + + if (action === 'created' || action === 'updated') { + this.props.dispatchUpdateItem({ section, ...resource }); + } else if (action === 'deleted') { + this.props.dispatchRemoveItem({ section, id: resource.id }); + } + }; + handleArtist = (body) => { const action = body.action; const section = 'artist'; diff --git a/src/Lidarr.Api.V1/DownloadClient/DownloadClientController.cs b/src/Lidarr.Api.V1/DownloadClient/DownloadClientController.cs index bc54aa7e0..b1cbb3ab5 100644 --- a/src/Lidarr.Api.V1/DownloadClient/DownloadClientController.cs +++ b/src/Lidarr.Api.V1/DownloadClient/DownloadClientController.cs @@ -1,6 +1,7 @@ using FluentValidation; using Lidarr.Http; using NzbDrone.Core.Download; +using NzbDrone.SignalR; namespace Lidarr.Api.V1.DownloadClient { @@ -10,8 +11,8 @@ namespace Lidarr.Api.V1.DownloadClient public static readonly DownloadClientResourceMapper ResourceMapper = new (); public static readonly DownloadClientBulkResourceMapper BulkResourceMapper = new (); - public DownloadClientController(IDownloadClientFactory downloadClientFactory) - : base(downloadClientFactory, "downloadclient", ResourceMapper, BulkResourceMapper) + public DownloadClientController(IBroadcastSignalRMessage signalRBroadcaster, IDownloadClientFactory downloadClientFactory) + : base(signalRBroadcaster, downloadClientFactory, "downloadclient", ResourceMapper, BulkResourceMapper) { SharedValidator.RuleFor(c => c.Priority).InclusiveBetween(1, 50); } diff --git a/src/Lidarr.Api.V1/ImportLists/ImportListController.cs b/src/Lidarr.Api.V1/ImportLists/ImportListController.cs index ff2ed98db..24a823e58 100644 --- a/src/Lidarr.Api.V1/ImportLists/ImportListController.cs +++ b/src/Lidarr.Api.V1/ImportLists/ImportListController.cs @@ -3,6 +3,7 @@ using Lidarr.Http; using NzbDrone.Core.ImportLists; using NzbDrone.Core.Validation; using NzbDrone.Core.Validation.Paths; +using NzbDrone.SignalR; namespace Lidarr.Api.V1.ImportLists { @@ -12,11 +13,12 @@ namespace Lidarr.Api.V1.ImportLists public static readonly ImportListResourceMapper ResourceMapper = new (); public static readonly ImportListBulkResourceMapper BulkResourceMapper = new (); - public ImportListController(IImportListFactory importListFactory, - RootFolderExistsValidator rootFolderExistsValidator, - QualityProfileExistsValidator qualityProfileExistsValidator, - MetadataProfileExistsValidator metadataProfileExistsValidator) - : base(importListFactory, "importlist", ResourceMapper, BulkResourceMapper) + public ImportListController(IBroadcastSignalRMessage signalRBroadcaster, + IImportListFactory importListFactory, + RootFolderExistsValidator rootFolderExistsValidator, + QualityProfileExistsValidator qualityProfileExistsValidator, + MetadataProfileExistsValidator metadataProfileExistsValidator) + : base(signalRBroadcaster, importListFactory, "importlist", ResourceMapper, BulkResourceMapper) { SharedValidator.RuleFor(c => c.RootFolderPath).Cascade(CascadeMode.Stop) .IsValidPath() diff --git a/src/Lidarr.Api.V1/Indexers/IndexerController.cs b/src/Lidarr.Api.V1/Indexers/IndexerController.cs index 2187e2b28..462c68898 100644 --- a/src/Lidarr.Api.V1/Indexers/IndexerController.cs +++ b/src/Lidarr.Api.V1/Indexers/IndexerController.cs @@ -2,6 +2,7 @@ using FluentValidation; using Lidarr.Http; using NzbDrone.Core.Indexers; using NzbDrone.Core.Validation; +using NzbDrone.SignalR; namespace Lidarr.Api.V1.Indexers { @@ -11,8 +12,10 @@ namespace Lidarr.Api.V1.Indexers public static readonly IndexerResourceMapper ResourceMapper = new (); public static readonly IndexerBulkResourceMapper BulkResourceMapper = new (); - public IndexerController(IndexerFactory indexerFactory, DownloadClientExistsValidator downloadClientExistsValidator) - : base(indexerFactory, "indexer", ResourceMapper, BulkResourceMapper) + public IndexerController(IBroadcastSignalRMessage signalRBroadcaster, + IndexerFactory indexerFactory, + DownloadClientExistsValidator downloadClientExistsValidator) + : base(signalRBroadcaster, indexerFactory, "indexer", ResourceMapper, BulkResourceMapper) { SharedValidator.RuleFor(c => c.Priority).InclusiveBetween(1, 50); SharedValidator.RuleFor(c => c.DownloadClientId).SetValidator(downloadClientExistsValidator); diff --git a/src/Lidarr.Api.V1/Metadata/MetadataController.cs b/src/Lidarr.Api.V1/Metadata/MetadataController.cs index 01e82ad37..4349058b0 100644 --- a/src/Lidarr.Api.V1/Metadata/MetadataController.cs +++ b/src/Lidarr.Api.V1/Metadata/MetadataController.cs @@ -2,6 +2,7 @@ using System; using Lidarr.Http; using Microsoft.AspNetCore.Mvc; using NzbDrone.Core.Extras.Metadata; +using NzbDrone.SignalR; namespace Lidarr.Api.V1.Metadata { @@ -11,8 +12,8 @@ namespace Lidarr.Api.V1.Metadata public static readonly MetadataResourceMapper ResourceMapper = new (); public static readonly MetadataBulkResourceMapper BulkResourceMapper = new (); - public MetadataController(IMetadataFactory metadataFactory) - : base(metadataFactory, "metadata", ResourceMapper, BulkResourceMapper) + public MetadataController(IBroadcastSignalRMessage signalRBroadcaster, IMetadataFactory metadataFactory) + : base(signalRBroadcaster, metadataFactory, "metadata", ResourceMapper, BulkResourceMapper) { } diff --git a/src/Lidarr.Api.V1/Notifications/NotificationController.cs b/src/Lidarr.Api.V1/Notifications/NotificationController.cs index dc792fc1f..7e5f45064 100644 --- a/src/Lidarr.Api.V1/Notifications/NotificationController.cs +++ b/src/Lidarr.Api.V1/Notifications/NotificationController.cs @@ -2,6 +2,7 @@ using System; using Lidarr.Http; using Microsoft.AspNetCore.Mvc; using NzbDrone.Core.Notifications; +using NzbDrone.SignalR; namespace Lidarr.Api.V1.Notifications { @@ -11,8 +12,8 @@ namespace Lidarr.Api.V1.Notifications public static readonly NotificationResourceMapper ResourceMapper = new (); public static readonly NotificationBulkResourceMapper BulkResourceMapper = new (); - public NotificationController(NotificationFactory notificationFactory) - : base(notificationFactory, "notification", ResourceMapper, BulkResourceMapper) + public NotificationController(IBroadcastSignalRMessage signalRBroadcaster, NotificationFactory notificationFactory) + : base(signalRBroadcaster, notificationFactory, "notification", ResourceMapper, BulkResourceMapper) { } diff --git a/src/Lidarr.Api.V1/ProviderControllerBase.cs b/src/Lidarr.Api.V1/ProviderControllerBase.cs index 8d0b88c4a..c630dddd9 100644 --- a/src/Lidarr.Api.V1/ProviderControllerBase.cs +++ b/src/Lidarr.Api.V1/ProviderControllerBase.cs @@ -7,12 +7,19 @@ using Lidarr.Http.REST.Attributes; using Microsoft.AspNetCore.Mvc; using NzbDrone.Common.Extensions; using NzbDrone.Common.Serializer; +using NzbDrone.Core.Datastore.Events; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.ThingiProvider.Events; using NzbDrone.Core.Validation; +using NzbDrone.SignalR; namespace Lidarr.Api.V1 { - public abstract class ProviderControllerBase : RestController + public abstract class ProviderControllerBase : RestControllerWithSignalR, + IHandle>, + IHandle>, + IHandle> where TProviderDefinition : ProviderDefinition, new() where TProvider : IProvider where TProviderResource : ProviderResource, new() @@ -22,11 +29,13 @@ namespace Lidarr.Api.V1 private readonly ProviderResourceMapper _resourceMapper; private readonly ProviderBulkResourceMapper _bulkResourceMapper; - protected ProviderControllerBase(IProviderFactory providerFactory, string resource, ProviderResourceMapper resourceMapper, ProviderBulkResourceMapper bulkResourceMapper) + : base(signalRBroadcaster) { _providerFactory = providerFactory; _resourceMapper = resourceMapper; @@ -261,6 +270,24 @@ namespace Lidarr.Api.V1 return Content(data.ToJson(), "application/json"); } + [NonAction] + public virtual void Handle(ProviderAddedEvent message) + { + BroadcastResourceChange(ModelAction.Created, message.Definition.Id); + } + + [NonAction] + public virtual void Handle(ProviderUpdatedEvent message) + { + BroadcastResourceChange(ModelAction.Updated, message.Definition.Id); + } + + [NonAction] + public virtual void Handle(ProviderDeletedEvent message) + { + BroadcastResourceChange(ModelAction.Deleted, message.ProviderId); + } + protected virtual void Validate(TProviderDefinition definition, bool includeWarnings) { var validationResult = definition.Settings.Validate();