From 20ef22be94f4bdb5633ddfb080e91c8d5b0229da 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 --- frontend/src/Components/SignalRConnector.js | 50 ++++++++++++++++++- .../DownloadClientController.cs | 5 +- .../ImportLists/ImportListController.cs | 8 ++- .../Indexers/IndexerController.cs | 7 ++- .../Metadata/MetadataController.cs | 5 +- .../Notifications/NotificationController.cs | 5 +- src/Sonarr.Api.V3/ProviderControllerBase.cs | 31 +++++++++++- 7 files changed, 98 insertions(+), 13 deletions(-) diff --git a/frontend/src/Components/SignalRConnector.js b/frontend/src/Components/SignalRConnector.js index 918f53fa5..e59669883 100644 --- a/frontend/src/Components/SignalRConnector.js +++ b/frontend/src/Components/SignalRConnector.js @@ -168,7 +168,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); @@ -202,10 +202,58 @@ class SignalRConnector extends Component { } }; + 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 }); + } + }; + handleSeries = (body) => { const action = body.action; const section = 'series'; diff --git a/src/Sonarr.Api.V3/DownloadClient/DownloadClientController.cs b/src/Sonarr.Api.V3/DownloadClient/DownloadClientController.cs index 0f79a83c6..1c0c53605 100644 --- a/src/Sonarr.Api.V3/DownloadClient/DownloadClientController.cs +++ b/src/Sonarr.Api.V3/DownloadClient/DownloadClientController.cs @@ -1,4 +1,5 @@ using NzbDrone.Core.Download; +using NzbDrone.SignalR; using Sonarr.Http; namespace Sonarr.Api.V3.DownloadClient @@ -9,8 +10,8 @@ namespace Sonarr.Api.V3.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) { } } diff --git a/src/Sonarr.Api.V3/ImportLists/ImportListController.cs b/src/Sonarr.Api.V3/ImportLists/ImportListController.cs index 499043f33..02a55d364 100644 --- a/src/Sonarr.Api.V3/ImportLists/ImportListController.cs +++ b/src/Sonarr.Api.V3/ImportLists/ImportListController.cs @@ -2,6 +2,7 @@ using FluentValidation; using NzbDrone.Core.ImportLists; using NzbDrone.Core.Validation; using NzbDrone.Core.Validation.Paths; +using NzbDrone.SignalR; using Sonarr.Http; namespace Sonarr.Api.V3.ImportLists @@ -12,8 +13,11 @@ namespace Sonarr.Api.V3.ImportLists public static readonly ImportListResourceMapper ResourceMapper = new (); public static readonly ImportListBulkResourceMapper BulkResourceMapper = new (); - public ImportListController(IImportListFactory importListFactory, RootFolderExistsValidator rootFolderExistsValidator, QualityProfileExistsValidator qualityProfileExistsValidator) - : base(importListFactory, "importlist", ResourceMapper, BulkResourceMapper) + public ImportListController(IBroadcastSignalRMessage signalRBroadcaster, + IImportListFactory importListFactory, + RootFolderExistsValidator rootFolderExistsValidator, + QualityProfileExistsValidator qualityProfileExistsValidator) + : base(signalRBroadcaster, importListFactory, "importlist", ResourceMapper, BulkResourceMapper) { SharedValidator.RuleFor(c => c.RootFolderPath).Cascade(CascadeMode.Stop) .IsValidPath() diff --git a/src/Sonarr.Api.V3/Indexers/IndexerController.cs b/src/Sonarr.Api.V3/Indexers/IndexerController.cs index fffbc4d96..90d4d6b60 100644 --- a/src/Sonarr.Api.V3/Indexers/IndexerController.cs +++ b/src/Sonarr.Api.V3/Indexers/IndexerController.cs @@ -1,5 +1,6 @@ using NzbDrone.Core.Indexers; using NzbDrone.Core.Validation; +using NzbDrone.SignalR; using Sonarr.Http; namespace Sonarr.Api.V3.Indexers @@ -10,8 +11,10 @@ namespace Sonarr.Api.V3.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.DownloadClientId).SetValidator(downloadClientExistsValidator); } diff --git a/src/Sonarr.Api.V3/Metadata/MetadataController.cs b/src/Sonarr.Api.V3/Metadata/MetadataController.cs index 006cab8ba..08f248284 100644 --- a/src/Sonarr.Api.V3/Metadata/MetadataController.cs +++ b/src/Sonarr.Api.V3/Metadata/MetadataController.cs @@ -1,6 +1,7 @@ using System; using Microsoft.AspNetCore.Mvc; using NzbDrone.Core.Extras.Metadata; +using NzbDrone.SignalR; using Sonarr.Http; namespace Sonarr.Api.V3.Metadata @@ -11,8 +12,8 @@ namespace Sonarr.Api.V3.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/Sonarr.Api.V3/Notifications/NotificationController.cs b/src/Sonarr.Api.V3/Notifications/NotificationController.cs index b20c0fae7..ff69bf3d5 100644 --- a/src/Sonarr.Api.V3/Notifications/NotificationController.cs +++ b/src/Sonarr.Api.V3/Notifications/NotificationController.cs @@ -1,6 +1,7 @@ using System; using Microsoft.AspNetCore.Mvc; using NzbDrone.Core.Notifications; +using NzbDrone.SignalR; using Sonarr.Http; namespace Sonarr.Api.V3.Notifications @@ -11,8 +12,8 @@ namespace Sonarr.Api.V3.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/Sonarr.Api.V3/ProviderControllerBase.cs b/src/Sonarr.Api.V3/ProviderControllerBase.cs index 04b3c6b73..20f4a3f87 100644 --- a/src/Sonarr.Api.V3/ProviderControllerBase.cs +++ b/src/Sonarr.Api.V3/ProviderControllerBase.cs @@ -5,14 +5,21 @@ using FluentValidation.Results; 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; using Sonarr.Http.REST; using Sonarr.Http.REST.Attributes; namespace Sonarr.Api.V3 { - 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 Sonarr.Api.V3 private readonly ProviderResourceMapper _resourceMapper; private readonly ProviderBulkResourceMapper _bulkResourceMapper; - protected ProviderControllerBase(IProviderFactory providerFactory, string resource, ProviderResourceMapper resourceMapper, ProviderBulkResourceMapper bulkResourceMapper) + : base(signalRBroadcaster) { _providerFactory = providerFactory; _resourceMapper = resourceMapper; @@ -263,6 +272,24 @@ namespace Sonarr.Api.V3 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); + } + private void Validate(TProviderDefinition definition, bool includeWarnings) { var validationResult = definition.Settings.Validate();