New: Bulk Manage Applications, Download Clients

Co-authored-by: Qstick <qstick@gmail.com>
This commit is contained in:
Bogdan
2023-07-11 00:42:51 +03:00
parent 834d334ca6
commit 77c1a42da1
109 changed files with 3588 additions and 189 deletions
@@ -0,0 +1,34 @@
using System.Collections.Generic;
using NzbDrone.Core.Download;
namespace Lidarr.Api.V1.DownloadClient
{
public class DownloadClientBulkResource : ProviderBulkResource<DownloadClientBulkResource>
{
public bool? Enable { get; set; }
public int? Priority { get; set; }
public bool? RemoveCompletedDownloads { get; set; }
public bool? RemoveFailedDownloads { get; set; }
}
public class DownloadClientBulkResourceMapper : ProviderBulkResourceMapper<DownloadClientBulkResource, DownloadClientDefinition>
{
public override List<DownloadClientDefinition> UpdateModel(DownloadClientBulkResource resource, List<DownloadClientDefinition> existingDefinitions)
{
if (resource == null)
{
return new List<DownloadClientDefinition>();
}
existingDefinitions.ForEach(existing =>
{
existing.Enable = resource.Enable ?? existing.Enable;
existing.Priority = resource.Priority ?? existing.Priority;
existing.RemoveCompletedDownloads = resource.RemoveCompletedDownloads ?? existing.RemoveCompletedDownloads;
existing.RemoveFailedDownloads = resource.RemoveFailedDownloads ?? existing.RemoveFailedDownloads;
});
return existingDefinitions;
}
}
}
@@ -4,12 +4,13 @@ using NzbDrone.Core.Download;
namespace Lidarr.Api.V1.DownloadClient
{
[V1ApiController]
public class DownloadClientController : ProviderControllerBase<DownloadClientResource, IDownloadClient, DownloadClientDefinition>
public class DownloadClientController : ProviderControllerBase<DownloadClientResource, DownloadClientBulkResource, IDownloadClient, DownloadClientDefinition>
{
public static readonly DownloadClientResourceMapper ResourceMapper = new DownloadClientResourceMapper();
public static readonly DownloadClientResourceMapper ResourceMapper = new ();
public static readonly DownloadClientBulkResourceMapper BulkResourceMapper = new ();
public DownloadClientController(IDownloadClientFactory downloadClientFactory)
: base(downloadClientFactory, "downloadclient", ResourceMapper)
: base(downloadClientFactory, "downloadclient", ResourceMapper, BulkResourceMapper)
{
}
}
@@ -0,0 +1,32 @@
using System.Collections.Generic;
using NzbDrone.Core.ImportLists;
namespace Lidarr.Api.V1.ImportLists
{
public class ImportListBulkResource : ProviderBulkResource<ImportListBulkResource>
{
public bool? EnableAutomaticAdd { get; set; }
public string RootFolderPath { get; set; }
public int? QualityProfileId { get; set; }
}
public class ImportListBulkResourceMapper : ProviderBulkResourceMapper<ImportListBulkResource, ImportListDefinition>
{
public override List<ImportListDefinition> UpdateModel(ImportListBulkResource resource, List<ImportListDefinition> existingDefinitions)
{
if (resource == null)
{
return new List<ImportListDefinition>();
}
existingDefinitions.ForEach(existing =>
{
existing.EnableAutomaticAdd = resource.EnableAutomaticAdd ?? existing.EnableAutomaticAdd;
existing.RootFolderPath = resource.RootFolderPath ?? existing.RootFolderPath;
existing.ProfileId = resource.QualityProfileId ?? existing.ProfileId;
});
return existingDefinitions;
}
}
}
@@ -6,14 +6,15 @@ using NzbDrone.Core.Validation.Paths;
namespace Lidarr.Api.V1.ImportLists
{
[V1ApiController]
public class ImportListController : ProviderControllerBase<ImportListResource, IImportList, ImportListDefinition>
public class ImportListController : ProviderControllerBase<ImportListResource, ImportListBulkResource, IImportList, ImportListDefinition>
{
public static readonly ImportListResourceMapper ResourceMapper = new ImportListResourceMapper();
public static readonly ImportListResourceMapper ResourceMapper = new ();
public static readonly ImportListBulkResourceMapper BulkResourceMapper = new ();
public ImportListController(IImportListFactory importListFactory,
QualityProfileExistsValidator qualityProfileExistsValidator,
MetadataProfileExistsValidator metadataProfileExistsValidator)
: base(importListFactory, "importlist", ResourceMapper)
: base(importListFactory, "importlist", ResourceMapper, BulkResourceMapper)
{
Http.Validation.RuleBuilderExtensions.ValidId(SharedValidator.RuleFor(s => s.QualityProfileId));
Http.Validation.RuleBuilderExtensions.ValidId(SharedValidator.RuleFor(s => s.MetadataProfileId));
@@ -0,0 +1,34 @@
using System.Collections.Generic;
using NzbDrone.Core.Indexers;
namespace Lidarr.Api.V1.Indexers
{
public class IndexerBulkResource : ProviderBulkResource<IndexerBulkResource>
{
public bool? EnableRss { get; set; }
public bool? EnableAutomaticSearch { get; set; }
public bool? EnableInteractiveSearch { get; set; }
public int? Priority { get; set; }
}
public class IndexerBulkResourceMapper : ProviderBulkResourceMapper<IndexerBulkResource, IndexerDefinition>
{
public override List<IndexerDefinition> UpdateModel(IndexerBulkResource resource, List<IndexerDefinition> existingDefinitions)
{
if (resource == null)
{
return new List<IndexerDefinition>();
}
existingDefinitions.ForEach(existing =>
{
existing.EnableRss = resource.EnableRss ?? existing.EnableRss;
existing.EnableAutomaticSearch = resource.EnableAutomaticSearch ?? existing.EnableAutomaticSearch;
existing.EnableInteractiveSearch = resource.EnableInteractiveSearch ?? existing.EnableInteractiveSearch;
existing.Priority = resource.Priority ?? existing.Priority;
});
return existingDefinitions;
}
}
}
@@ -4,12 +4,13 @@ using NzbDrone.Core.Indexers;
namespace Lidarr.Api.V1.Indexers
{
[V1ApiController]
public class IndexerController : ProviderControllerBase<IndexerResource, IIndexer, IndexerDefinition>
public class IndexerController : ProviderControllerBase<IndexerResource, IndexerBulkResource, IIndexer, IndexerDefinition>
{
public static readonly IndexerResourceMapper ResourceMapper = new IndexerResourceMapper();
public static readonly IndexerResourceMapper ResourceMapper = new ();
public static readonly IndexerBulkResourceMapper BulkResourceMapper = new ();
public IndexerController(IndexerFactory indexerFactory)
: base(indexerFactory, "indexer", ResourceMapper)
: base(indexerFactory, "indexer", ResourceMapper, BulkResourceMapper)
{
}
}
@@ -0,0 +1,12 @@
using NzbDrone.Core.Extras.Metadata;
namespace Lidarr.Api.V1.Metadata
{
public class MetadataBulkResource : ProviderBulkResource<MetadataBulkResource>
{
}
public class MetadataBulkResourceMapper : ProviderBulkResourceMapper<MetadataBulkResource, MetadataDefinition>
{
}
}
@@ -1,16 +1,31 @@
using System;
using Lidarr.Http;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Core.Extras.Metadata;
namespace Lidarr.Api.V1.Metadata
{
[V1ApiController]
public class MetadataController : ProviderControllerBase<MetadataResource, IMetadata, MetadataDefinition>
public class MetadataController : ProviderControllerBase<MetadataResource, MetadataBulkResource, IMetadata, MetadataDefinition>
{
public static readonly MetadataResourceMapper ResourceMapper = new MetadataResourceMapper();
public static readonly MetadataResourceMapper ResourceMapper = new ();
public static readonly MetadataBulkResourceMapper BulkResourceMapper = new ();
public MetadataController(IMetadataFactory metadataFactory)
: base(metadataFactory, "metadata", ResourceMapper)
: base(metadataFactory, "metadata", ResourceMapper, BulkResourceMapper)
{
}
[NonAction]
public override ActionResult<MetadataResource> UpdateProvider([FromBody] MetadataBulkResource providerResource)
{
throw new NotImplementedException();
}
[NonAction]
public override object DeleteProviders([FromBody] MetadataBulkResource resource)
{
throw new NotImplementedException();
}
}
}
@@ -0,0 +1,12 @@
using NzbDrone.Core.Notifications;
namespace Lidarr.Api.V1.Notifications
{
public class NotificationBulkResource : ProviderBulkResource<NotificationBulkResource>
{
}
public class NotificationBulkResourceMapper : ProviderBulkResourceMapper<NotificationBulkResource, NotificationDefinition>
{
}
}
@@ -1,16 +1,31 @@
using System;
using Lidarr.Http;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Core.Notifications;
namespace Lidarr.Api.V1.Notifications
{
[V1ApiController]
public class NotificationController : ProviderControllerBase<NotificationResource, INotification, NotificationDefinition>
public class NotificationController : ProviderControllerBase<NotificationResource, NotificationBulkResource, INotification, NotificationDefinition>
{
public static readonly NotificationResourceMapper ResourceMapper = new NotificationResourceMapper();
public static readonly NotificationResourceMapper ResourceMapper = new ();
public static readonly NotificationBulkResourceMapper BulkResourceMapper = new ();
public NotificationController(NotificationFactory notificationFactory)
: base(notificationFactory, "notification", ResourceMapper)
: base(notificationFactory, "notification", ResourceMapper, BulkResourceMapper)
{
}
[NonAction]
public override ActionResult<NotificationResource> UpdateProvider([FromBody] NotificationBulkResource providerResource)
{
throw new NotImplementedException();
}
[NonAction]
public override object DeleteProviders([FromBody] NotificationBulkResource resource)
{
throw new NotImplementedException();
}
}
}
+39
View File
@@ -0,0 +1,39 @@
using System.Collections.Generic;
using NzbDrone.Core.ThingiProvider;
namespace Lidarr.Api.V1
{
public class ProviderBulkResource<T>
{
public List<int> Ids { get; set; }
public List<int> Tags { get; set; }
public ApplyTags ApplyTags { get; set; }
public ProviderBulkResource()
{
Ids = new List<int>();
}
}
public enum ApplyTags
{
Add,
Remove,
Replace
}
public class ProviderBulkResourceMapper<TProviderBulkResource, TProviderDefinition>
where TProviderBulkResource : ProviderBulkResource<TProviderBulkResource>, new()
where TProviderDefinition : ProviderDefinition, new()
{
public virtual List<TProviderDefinition> UpdateModel(TProviderBulkResource resource, List<TProviderDefinition> existingDefinitions)
{
if (resource == null)
{
return new List<TProviderDefinition>();
}
return existingDefinitions;
}
}
}
+59 -2
View File
@@ -11,18 +11,25 @@ using NzbDrone.Core.Validation;
namespace Lidarr.Api.V1
{
public abstract class ProviderControllerBase<TProviderResource, TProvider, TProviderDefinition> : RestController<TProviderResource>
public abstract class ProviderControllerBase<TProviderResource, TBulkProviderResource, TProvider, TProviderDefinition> : RestController<TProviderResource>
where TProviderDefinition : ProviderDefinition, new()
where TProvider : IProvider
where TProviderResource : ProviderResource<TProviderResource>, new()
where TBulkProviderResource : ProviderBulkResource<TBulkProviderResource>, new()
{
private readonly IProviderFactory<TProvider, TProviderDefinition> _providerFactory;
private readonly ProviderResourceMapper<TProviderResource, TProviderDefinition> _resourceMapper;
private readonly ProviderBulkResourceMapper<TBulkProviderResource, TProviderDefinition> _bulkResourceMapper;
protected ProviderControllerBase(IProviderFactory<TProvider, TProviderDefinition> providerFactory, string resource, ProviderResourceMapper<TProviderResource, TProviderDefinition> resourceMapper)
protected ProviderControllerBase(IProviderFactory<TProvider,
TProviderDefinition> providerFactory,
string resource,
ProviderResourceMapper<TProviderResource, TProviderDefinition> resourceMapper,
ProviderBulkResourceMapper<TBulkProviderResource, TProviderDefinition> bulkResourceMapper)
{
_providerFactory = providerFactory;
_resourceMapper = resourceMapper;
_bulkResourceMapper = bulkResourceMapper;
SharedValidator.RuleFor(c => c.Name).NotEmpty();
SharedValidator.RuleFor(c => c.Name).Must((v, c) => !_providerFactory.All().Any(p => p.Name == c && p.Id != v.Id)).WithMessage("Should be unique");
@@ -93,6 +100,47 @@ namespace Lidarr.Api.V1
return Accepted(providerResource.Id);
}
[HttpPut("bulk")]
[Consumes("application/json")]
[Produces("application/json")]
public virtual ActionResult<TProviderResource> UpdateProvider([FromBody] TBulkProviderResource providerResource)
{
if (!providerResource.Ids.Any())
{
throw new BadRequestException("ids must be provided");
}
var definitionsToUpdate = _providerFactory.Get(providerResource.Ids).ToList();
foreach (var definition in definitionsToUpdate)
{
_providerFactory.SetProviderCharacteristics(definition);
if (providerResource.Tags != null)
{
var newTags = providerResource.Tags;
var applyTags = providerResource.ApplyTags;
switch (applyTags)
{
case ApplyTags.Add:
newTags.ForEach(t => definition.Tags.Add(t));
break;
case ApplyTags.Remove:
newTags.ForEach(t => definition.Tags.Remove(t));
break;
case ApplyTags.Replace:
definition.Tags = new HashSet<int>(newTags);
break;
}
}
}
_bulkResourceMapper.UpdateModel(providerResource, definitionsToUpdate);
return Accepted(_providerFactory.Update(definitionsToUpdate).Select(x => _resourceMapper.ToResource(x)));
}
private TProviderDefinition GetDefinition(TProviderResource providerResource, bool validate, bool includeWarnings, bool forceValidate)
{
var definition = _resourceMapper.ToModel(providerResource);
@@ -112,6 +160,15 @@ namespace Lidarr.Api.V1
return new { };
}
[HttpDelete("bulk")]
[Consumes("application/json")]
public virtual object DeleteProviders([FromBody] TBulkProviderResource resource)
{
_providerFactory.Delete(resource.Ids);
return new { };
}
[HttpGet("schema")]
[Produces("application/json")]
public List<TProviderResource> GetTemplates()
+34 -4
View File
@@ -72,11 +72,12 @@
"ApplicationURL": "Application URL",
"ApplicationUrlHelpText": "This application's external URL including http(s)://, port and URL base",
"Apply": "Apply",
"ApplyChanges": "Apply Changes",
"ApplyTags": "Apply Tags",
"ApplyTagsHelpTexts1": "ApplyTagsHelpTexts1",
"ApplyTagsHelpTexts2": "ApplyTagsHelpTexts2",
"ApplyTagsHelpTexts3": "ApplyTagsHelpTexts3",
"ApplyTagsHelpTexts4": "ApplyTagsHelpTexts4",
"ApplyTagsHelpTexts1": "How to apply tags to the selected indexers",
"ApplyTagsHelpTexts2": "Add: Add the tags the existing list of tags",
"ApplyTagsHelpTexts3": "Remove: Remove the entered tags",
"ApplyTagsHelpTexts4": "Replace: Replace the tags with the entered tags (enter no tags to clear all tags)",
"AreYouSure": "Are you sure?",
"Artist": "Artist",
"ArtistAlbumClickToChangeTrack": "Click to change track",
@@ -90,6 +91,8 @@
"AudioInfo": "Audio Info",
"Authentication": "Authentication",
"AuthenticationMethodHelpText": "Require Username and Password to access Lidarr",
"AutoAdd": "Auto Add",
"AutomaticAdd": "Automatic Add",
"AutoRedownloadFailedHelpText": "Automatically search for and attempt to download a different release",
"Automatic": "Automatic",
"AutomaticallySwitchRelease": "Automatically Switch Release",
@@ -159,6 +162,9 @@
"CopyUsingHardlinksHelpTextWarning": "Occasionally, file locks may prevent renaming files that are being seeded. You may temporarily disable seeding and use Lidarr's rename function as a work around.",
"CouldntFindAnyResultsForTerm": "Couldn't find any results for '{0}'",
"CountAlbums": "{0} albums",
"CountDownloadClientsSelected": "{0} download client(s) selected",
"CountImportListsSelected": "{0} import list(s) selected",
"CountIndexersSelected": "{0} indexer(s) selected",
"Country": "Country",
"CreateEmptyArtistFolders": "Create empty artist folders",
"CreateEmptyArtistFoldersHelpText": "Create missing artist folders during disk scan",
@@ -225,6 +231,12 @@
"DeleteRootFolder": "Delete Root Folder",
"DeleteRootFolderMessageText": "Are you sure you want to delete the root folder '{0}'?",
"DeleteSelected": "Delete Selected",
"DeleteSelectedDownloadClients": "Delete Download Client(s)",
"DeleteSelectedDownloadClientsMessageText": "Are you sure you want to delete {0} selected download client(s)?",
"DeleteSelectedImportLists": "Delete Import List(s)",
"DeleteSelectedImportListsMessageText": "Are you sure you want to delete {0} selected import list(s)?",
"DeleteSelectedIndexers": "Delete Indexer(s)",
"DeleteSelectedIndexersMessageText": "Are you sure you want to delete {0} selected indexer(s)?",
"DeleteSelectedTrackFiles": "Delete Selected Track Files",
"DeleteSelectedTrackFilesMessageText": "Are you sure you want to delete the selected track files?",
"DeleteTag": "Delete Tag",
@@ -283,6 +295,9 @@
"EditReleaseProfile": "Edit Release Profile",
"EditRemotePathMapping": "Edit Remote Path Mapping",
"EditRootFolder": "Edit Root Folder",
"EditSelectedDownloadClients": "Edit Selected Download Clients",
"EditSelectedImportLists": "Edit Selected Import Lists",
"EditSelectedIndexers": "Edit Selected Indexers",
"Enable": "Enable",
"EnableAutomaticAdd": "Enable Automatic Add",
"EnableAutomaticAddHelpText": "Add artist/albums to Lidarr when syncs are performed via the UI or by Lidarr",
@@ -313,6 +328,7 @@
"Exception": "Exception",
"ExistingAlbums": "Existing Albums",
"ExistingAlbumsData": "Monitor albums that have files or have not released yet",
"ExistingTag": "Existing tag",
"ExistingTagsScrubbed": "Existing tags scrubbed",
"ExpandAlbumByDefaultHelpText": "Albums",
"ExpandBroadcastByDefaultHelpText": "Broadcast",
@@ -387,6 +403,7 @@
"IgnoredHelpText": "The release will be rejected if it contains one or more of terms (case insensitive)",
"IgnoredPlaceHolder": "Add new restriction",
"IllRestartLater": "I'll restart later",
"Implementation": "Implementation",
"Import": "Import",
"ImportExtraFiles": "Import Extra Files",
"ImportExtraFilesHelpText": "Import matching extra files (subtitles, nfo, etc) after importing an track file",
@@ -478,6 +495,11 @@
"LongDateFormat": "Long Date Format",
"MIA": "MIA",
"MaintenanceRelease": "Maintenance Release: bug fixes and other improvements. See Github Commit History for more details",
"ManageClients": "Manage Clients",
"ManageDownloadClients": "Manage Download Clients",
"ManageImportLists": "Manage Import Lists",
"ManageIndexers": "Manage Indexers",
"ManageLists": "Manage Lists",
"ManageTracks": "Manage Tracks",
"Manual": "Manual",
"ManualDownload": "Manual Download",
@@ -554,12 +576,17 @@
"NewAlbums": "New Albums",
"NextAlbum": "Next Album",
"NextExecution": "Next Execution",
"No": "No",
"NoAlbums": "No albums",
"NoBackupsAreAvailable": "No backups are available",
"NoChange": "No Change",
"NoCutoffUnmetItems": "No cutoff unmet items",
"NoDownloadClientsFound": "No download clients found",
"NoEventsFound": "No events found",
"NoHistory": "No history.",
"NoHistoryBlocklist": "No history blocklist",
"NoImportListsFound": "No import lists found",
"NoIndexersFound": "No indexers found",
"NoLeaveIt": "No, Leave It",
"NoLimitForAnyRuntime": "No limit for any runtime",
"NoLogFiles": "No log files",
@@ -724,6 +751,7 @@
"RemoveTagExistingTag": "Existing tag",
"RemoveTagRemovingTag": "Removing tag",
"RemovedFromTaskQueue": "Removed from task queue",
"RemovingTag": "Removing tag",
"RenameTracks": "Rename Tracks",
"RenameTracksHelpText": "Lidarr will use the existing file name if renaming is disabled",
"Renamed": "Renamed",
@@ -809,6 +837,7 @@
"SetPermissions": "Set Permissions",
"SetPermissionsLinuxHelpText": "Should chmod be run when files are imported/renamed?",
"SetPermissionsLinuxHelpTextWarning": "If you're unsure what these settings do, do not alter them.",
"SetTags": "Set Tags",
"Settings": "Settings",
"ShortDateFormat": "Short Date Format",
"ShouldMonitorExisting": "Monitor existing albums",
@@ -1007,5 +1036,6 @@
"WriteMetadataTags": "Write Metadata Tags",
"WriteMetadataToAudioFiles": "Write Metadata to Audio Files",
"Year": "Year",
"Yes": "Yes",
"YesCancel": "Yes, Cancel"
}
@@ -12,9 +12,12 @@ namespace NzbDrone.Core.ThingiProvider
bool Exists(int id);
TProviderDefinition Find(int id);
TProviderDefinition Get(int id);
IEnumerable<TProviderDefinition> Get(IEnumerable<int> ids);
TProviderDefinition Create(TProviderDefinition definition);
void Update(TProviderDefinition definition);
IEnumerable<TProviderDefinition> Update(IEnumerable<TProviderDefinition> definitions);
void Delete(int id);
void Delete(IEnumerable<int> ids);
IEnumerable<TProviderDefinition> GetDefaultDefinitions();
IEnumerable<TProviderDefinition> GetPresetDefinitions(TProviderDefinition providerDefinition);
void SetProviderCharacteristics(TProviderDefinition definition);
@@ -101,6 +101,11 @@ namespace NzbDrone.Core.ThingiProvider
return _providerRepository.Get(id);
}
public IEnumerable<TProviderDefinition> Get(IEnumerable<int> ids)
{
return _providerRepository.Get(ids);
}
public TProviderDefinition Find(int id)
{
return _providerRepository.Find(id);
@@ -120,12 +125,34 @@ namespace NzbDrone.Core.ThingiProvider
_eventAggregator.PublishEvent(new ProviderUpdatedEvent<TProvider>(definition));
}
public virtual IEnumerable<TProviderDefinition> Update(IEnumerable<TProviderDefinition> definitions)
{
_providerRepository.UpdateMany(definitions.ToList());
foreach (var definition in definitions)
{
_eventAggregator.PublishEvent(new ProviderUpdatedEvent<TProvider>(definition));
}
return definitions;
}
public void Delete(int id)
{
_providerRepository.Delete(id);
_eventAggregator.PublishEvent(new ProviderDeletedEvent<TProvider>(id));
}
public void Delete(IEnumerable<int> ids)
{
_providerRepository.DeleteMany(ids);
foreach (var id in ids)
{
_eventAggregator.PublishEvent(new ProviderDeletedEvent<TProvider>(id));
}
}
public TProvider GetInstance(TProviderDefinition definition)
{
var type = GetImplementation(definition);