New: Real time UI updates for provider changes

(cherry picked from commit 20ef22be94f4bdb5633ddfb080e91c8d5b0229da)

Closes #5178
This commit is contained in:
Bogdan 2024-10-27 00:17:46 +03:00
parent 345bbcd992
commit 7217e891f7
7 changed files with 99 additions and 16 deletions

View file

@ -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';

View file

@ -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);
}

View file

@ -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()

View file

@ -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);

View file

@ -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)
{
}

View file

@ -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)
{
}

View file

@ -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<TProviderResource, TBulkProviderResource, TProvider, TProviderDefinition> : RestController<TProviderResource>
public abstract class ProviderControllerBase<TProviderResource, TBulkProviderResource, TProvider, TProviderDefinition> : RestControllerWithSignalR<TProviderResource, TProviderDefinition>,
IHandle<ProviderAddedEvent<TProvider>>,
IHandle<ProviderUpdatedEvent<TProvider>>,
IHandle<ProviderDeletedEvent<TProvider>>
where TProviderDefinition : ProviderDefinition, new()
where TProvider : IProvider
where TProviderResource : ProviderResource<TProviderResource>, new()
@ -22,11 +29,13 @@ namespace Lidarr.Api.V1
private readonly ProviderResourceMapper<TProviderResource, TProviderDefinition> _resourceMapper;
private readonly ProviderBulkResourceMapper<TBulkProviderResource, TProviderDefinition> _bulkResourceMapper;
protected ProviderControllerBase(IProviderFactory<TProvider,
protected ProviderControllerBase(IBroadcastSignalRMessage signalRBroadcaster,
IProviderFactory<TProvider,
TProviderDefinition> providerFactory,
string resource,
ProviderResourceMapper<TProviderResource, TProviderDefinition> resourceMapper,
ProviderBulkResourceMapper<TBulkProviderResource, TProviderDefinition> 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<TProvider> message)
{
BroadcastResourceChange(ModelAction.Created, message.Definition.Id);
}
[NonAction]
public virtual void Handle(ProviderUpdatedEvent<TProvider> message)
{
BroadcastResourceChange(ModelAction.Updated, message.Definition.Id);
}
[NonAction]
public virtual void Handle(ProviderDeletedEvent<TProvider> message)
{
BroadcastResourceChange(ModelAction.Deleted, message.ProviderId);
}
protected virtual void Validate(TProviderDefinition definition, bool includeWarnings)
{
var validationResult = definition.Settings.Validate();