2017-09-04 02:20:56 +00:00
|
|
|
using System;
|
2021-08-04 20:42:40 +00:00
|
|
|
using System.Collections.Generic;
|
2017-09-04 02:20:56 +00:00
|
|
|
using System.Linq;
|
2020-01-03 12:49:24 +00:00
|
|
|
using Lidarr.Http;
|
|
|
|
using Lidarr.Http.Extensions;
|
2021-08-04 20:42:40 +00:00
|
|
|
using Lidarr.Http.REST;
|
|
|
|
using Lidarr.Http.REST.Attributes;
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
2017-09-04 02:20:56 +00:00
|
|
|
using NzbDrone.Common.Extensions;
|
2023-10-21 01:36:51 +00:00
|
|
|
using NzbDrone.Core.Blocklisting;
|
2017-09-04 02:20:56 +00:00
|
|
|
using NzbDrone.Core.Datastore;
|
|
|
|
using NzbDrone.Core.Datastore.Events;
|
2021-08-04 20:42:40 +00:00
|
|
|
using NzbDrone.Core.Download;
|
2017-09-04 02:20:56 +00:00
|
|
|
using NzbDrone.Core.Download.Pending;
|
2021-08-04 20:42:40 +00:00
|
|
|
using NzbDrone.Core.Download.TrackedDownloads;
|
2023-05-22 00:51:36 +00:00
|
|
|
using NzbDrone.Core.Indexers;
|
2017-09-04 02:20:56 +00:00
|
|
|
using NzbDrone.Core.Messaging.Events;
|
2019-02-23 22:39:11 +00:00
|
|
|
using NzbDrone.Core.Profiles.Qualities;
|
|
|
|
using NzbDrone.Core.Qualities;
|
2017-09-04 02:20:56 +00:00
|
|
|
using NzbDrone.Core.Queue;
|
|
|
|
using NzbDrone.SignalR;
|
|
|
|
|
2017-10-31 01:28:29 +00:00
|
|
|
namespace Lidarr.Api.V1.Queue
|
2017-09-04 02:20:56 +00:00
|
|
|
{
|
2021-08-04 20:42:40 +00:00
|
|
|
[V1ApiController]
|
|
|
|
public class QueueController : RestControllerWithSignalR<QueueResource, NzbDrone.Core.Queue.Queue>,
|
2017-09-04 02:20:56 +00:00
|
|
|
IHandle<QueueUpdatedEvent>, IHandle<PendingReleasesUpdatedEvent>
|
|
|
|
{
|
|
|
|
private readonly IQueueService _queueService;
|
|
|
|
private readonly IPendingReleaseService _pendingReleaseService;
|
|
|
|
|
2020-01-03 12:49:24 +00:00
|
|
|
private readonly QualityModelComparer _qualityComparer;
|
2021-08-04 20:42:40 +00:00
|
|
|
private readonly ITrackedDownloadService _trackedDownloadService;
|
|
|
|
private readonly IFailedDownloadService _failedDownloadService;
|
|
|
|
private readonly IIgnoredDownloadService _ignoredDownloadService;
|
|
|
|
private readonly IProvideDownloadClient _downloadClientProvider;
|
2023-10-21 01:36:51 +00:00
|
|
|
private readonly IBlocklistService _blocklistService;
|
2019-02-23 22:39:11 +00:00
|
|
|
|
2021-08-04 20:42:40 +00:00
|
|
|
public QueueController(IBroadcastSignalRMessage broadcastSignalRMessage,
|
2019-02-23 22:39:11 +00:00
|
|
|
IQueueService queueService,
|
|
|
|
IPendingReleaseService pendingReleaseService,
|
2021-08-04 20:42:40 +00:00
|
|
|
QualityProfileService qualityProfileService,
|
|
|
|
ITrackedDownloadService trackedDownloadService,
|
|
|
|
IFailedDownloadService failedDownloadService,
|
|
|
|
IIgnoredDownloadService ignoredDownloadService,
|
2023-10-21 01:36:51 +00:00
|
|
|
IProvideDownloadClient downloadClientProvider,
|
|
|
|
IBlocklistService blocklistService)
|
2017-09-04 02:20:56 +00:00
|
|
|
: base(broadcastSignalRMessage)
|
|
|
|
{
|
|
|
|
_queueService = queueService;
|
|
|
|
_pendingReleaseService = pendingReleaseService;
|
2021-08-04 20:42:40 +00:00
|
|
|
_trackedDownloadService = trackedDownloadService;
|
|
|
|
_failedDownloadService = failedDownloadService;
|
|
|
|
_ignoredDownloadService = ignoredDownloadService;
|
|
|
|
_downloadClientProvider = downloadClientProvider;
|
2023-10-21 01:36:51 +00:00
|
|
|
_blocklistService = blocklistService;
|
2019-02-23 22:39:11 +00:00
|
|
|
|
2020-01-03 12:49:24 +00:00
|
|
|
_qualityComparer = new QualityModelComparer(qualityProfileService.GetDefaultProfile(string.Empty));
|
2017-09-04 02:20:56 +00:00
|
|
|
}
|
|
|
|
|
2023-06-21 02:57:27 +00:00
|
|
|
[NonAction]
|
2021-08-04 20:42:40 +00:00
|
|
|
public override QueueResource GetResourceById(int id)
|
2017-09-04 02:20:56 +00:00
|
|
|
{
|
2021-08-04 20:42:40 +00:00
|
|
|
throw new NotImplementedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
[RestDeleteById]
|
2024-01-23 04:56:35 +00:00
|
|
|
public void RemoveAction(int id, bool removeFromClient = true, bool blocklist = false, bool skipRedownload = false, bool changeCategory = false)
|
2021-08-04 20:42:40 +00:00
|
|
|
{
|
2023-10-21 01:36:51 +00:00
|
|
|
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id);
|
2021-08-04 20:42:40 +00:00
|
|
|
|
2023-10-21 01:36:51 +00:00
|
|
|
if (pendingRelease != null)
|
|
|
|
{
|
|
|
|
Remove(pendingRelease);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var trackedDownload = GetTrackedDownload(id);
|
|
|
|
|
|
|
|
if (trackedDownload == null)
|
2021-08-04 20:42:40 +00:00
|
|
|
{
|
2023-10-21 01:36:51 +00:00
|
|
|
throw new NotFoundException();
|
2021-08-04 20:42:40 +00:00
|
|
|
}
|
2023-10-21 01:36:51 +00:00
|
|
|
|
2024-01-23 04:56:35 +00:00
|
|
|
Remove(trackedDownload, removeFromClient, blocklist, skipRedownload, changeCategory);
|
2023-10-21 01:36:51 +00:00
|
|
|
_trackedDownloadService.StopTracking(trackedDownload.DownloadItem.DownloadId);
|
2021-08-04 20:42:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
[HttpDelete("bulk")]
|
2024-01-23 04:56:35 +00:00
|
|
|
public object RemoveMany([FromBody] QueueBulkResource resource, [FromQuery] bool removeFromClient = true, [FromQuery] bool blocklist = false, [FromQuery] bool skipRedownload = false, [FromQuery] bool changeCategory = false)
|
2021-08-04 20:42:40 +00:00
|
|
|
{
|
|
|
|
var trackedDownloadIds = new List<string>();
|
2023-10-21 01:36:51 +00:00
|
|
|
var pendingToRemove = new List<NzbDrone.Core.Queue.Queue>();
|
|
|
|
var trackedToRemove = new List<TrackedDownload>();
|
2021-08-04 20:42:40 +00:00
|
|
|
|
|
|
|
foreach (var id in resource.Ids)
|
|
|
|
{
|
2023-10-21 01:36:51 +00:00
|
|
|
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id);
|
|
|
|
|
|
|
|
if (pendingRelease != null)
|
|
|
|
{
|
|
|
|
pendingToRemove.Add(pendingRelease);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
var trackedDownload = GetTrackedDownload(id);
|
2021-08-04 20:42:40 +00:00
|
|
|
|
|
|
|
if (trackedDownload != null)
|
|
|
|
{
|
2023-10-21 01:36:51 +00:00
|
|
|
trackedToRemove.Add(trackedDownload);
|
2021-08-04 20:42:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-21 01:36:51 +00:00
|
|
|
foreach (var pendingRelease in pendingToRemove.DistinctBy(p => p.Id))
|
|
|
|
{
|
|
|
|
Remove(pendingRelease);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (var trackedDownload in trackedToRemove.DistinctBy(t => t.DownloadItem.DownloadId))
|
|
|
|
{
|
2024-01-23 04:56:35 +00:00
|
|
|
Remove(trackedDownload, removeFromClient, blocklist, skipRedownload, changeCategory);
|
2023-10-21 01:36:51 +00:00
|
|
|
trackedDownloadIds.Add(trackedDownload.DownloadItem.DownloadId);
|
|
|
|
}
|
|
|
|
|
2021-08-04 20:42:40 +00:00
|
|
|
_trackedDownloadService.StopTracking(trackedDownloadIds);
|
|
|
|
|
2021-12-24 18:31:18 +00:00
|
|
|
return new { };
|
2021-08-04 20:42:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
[HttpGet]
|
2023-05-30 02:52:20 +00:00
|
|
|
[Produces("application/json")]
|
2023-05-22 00:51:36 +00:00
|
|
|
public PagingResource<QueueResource> GetQueue([FromQuery] PagingRequestResource paging, bool includeUnknownArtistItems = false, bool includeArtist = false, bool includeAlbum = false, [FromQuery] int[] artistIds = null, DownloadProtocol? protocol = null, int? quality = null)
|
2021-08-04 20:42:40 +00:00
|
|
|
{
|
2023-10-10 03:37:31 +00:00
|
|
|
var pagingResource = new PagingResource<QueueResource>(paging);
|
2017-09-04 02:20:56 +00:00
|
|
|
var pagingSpec = pagingResource.MapToPagingSpec<QueueResource, NzbDrone.Core.Queue.Queue>("timeleft", SortDirection.Ascending);
|
|
|
|
|
2023-05-22 00:51:36 +00:00
|
|
|
return pagingSpec.ApplyToPage((spec) => GetQueue(spec, artistIds?.ToHashSet(), protocol, quality, includeUnknownArtistItems), (q) => MapToResource(q, includeArtist, includeAlbum));
|
2017-09-04 02:20:56 +00:00
|
|
|
}
|
|
|
|
|
2023-05-22 00:51:36 +00:00
|
|
|
private PagingSpec<NzbDrone.Core.Queue.Queue> GetQueue(PagingSpec<NzbDrone.Core.Queue.Queue> pagingSpec, HashSet<int> artistIds, DownloadProtocol? protocol, int? quality, bool includeUnknownArtistItems)
|
2017-09-04 02:20:56 +00:00
|
|
|
{
|
|
|
|
var ascending = pagingSpec.SortDirection == SortDirection.Ascending;
|
|
|
|
var orderByFunc = GetOrderByFunc(pagingSpec);
|
|
|
|
|
|
|
|
var queue = _queueService.GetQueue();
|
2019-02-23 22:39:11 +00:00
|
|
|
var filteredQueue = includeUnknownArtistItems ? queue : queue.Where(q => q.Artist != null);
|
2017-09-04 02:20:56 +00:00
|
|
|
var pending = _pendingReleaseService.GetPendingQueue();
|
2023-05-22 00:51:36 +00:00
|
|
|
|
|
|
|
var hasArtistIdFilter = artistIds.Any();
|
|
|
|
var fullQueue = filteredQueue.Concat(pending).Where(q =>
|
|
|
|
{
|
|
|
|
var include = true;
|
|
|
|
|
|
|
|
if (hasArtistIdFilter)
|
|
|
|
{
|
|
|
|
include &= q.Artist != null && artistIds.Contains(q.Artist.Id);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (include && protocol.HasValue)
|
|
|
|
{
|
|
|
|
include &= q.Protocol == protocol.Value;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (include && quality.HasValue)
|
|
|
|
{
|
|
|
|
include &= q.Quality.Quality.Id == quality.Value;
|
|
|
|
}
|
|
|
|
|
|
|
|
return include;
|
|
|
|
}).ToList();
|
|
|
|
|
2017-09-04 02:20:56 +00:00
|
|
|
IOrderedEnumerable<NzbDrone.Core.Queue.Queue> ordered;
|
|
|
|
|
|
|
|
if (pagingSpec.SortKey == "timeleft")
|
|
|
|
{
|
2019-02-23 22:39:11 +00:00
|
|
|
ordered = ascending
|
|
|
|
? fullQueue.OrderBy(q => q.Timeleft, new TimeleftComparer())
|
|
|
|
: fullQueue.OrderByDescending(q => q.Timeleft, new TimeleftComparer());
|
2017-09-04 02:20:56 +00:00
|
|
|
}
|
|
|
|
else if (pagingSpec.SortKey == "estimatedCompletionTime")
|
|
|
|
{
|
2019-02-23 22:39:11 +00:00
|
|
|
ordered = ascending
|
2024-01-16 05:28:28 +00:00
|
|
|
? fullQueue.OrderBy(q => q.EstimatedCompletionTime, new DatetimeComparer())
|
2019-02-23 22:39:11 +00:00
|
|
|
: fullQueue.OrderByDescending(q => q.EstimatedCompletionTime,
|
2024-01-16 05:28:28 +00:00
|
|
|
new DatetimeComparer());
|
|
|
|
}
|
|
|
|
else if (pagingSpec.SortKey == "added")
|
|
|
|
{
|
|
|
|
ordered = ascending
|
|
|
|
? fullQueue.OrderBy(q => q.Added, new DatetimeComparer())
|
|
|
|
: fullQueue.OrderByDescending(q => q.Added,
|
|
|
|
new DatetimeComparer());
|
2017-09-04 02:20:56 +00:00
|
|
|
}
|
2018-08-08 00:57:15 +00:00
|
|
|
else if (pagingSpec.SortKey == "protocol")
|
|
|
|
{
|
2019-02-23 22:39:11 +00:00
|
|
|
ordered = ascending
|
|
|
|
? fullQueue.OrderBy(q => q.Protocol)
|
|
|
|
: fullQueue.OrderByDescending(q => q.Protocol);
|
2018-08-08 00:57:15 +00:00
|
|
|
}
|
|
|
|
else if (pagingSpec.SortKey == "indexer")
|
|
|
|
{
|
2019-02-23 22:39:11 +00:00
|
|
|
ordered = ascending
|
|
|
|
? fullQueue.OrderBy(q => q.Indexer, StringComparer.InvariantCultureIgnoreCase)
|
|
|
|
: fullQueue.OrderByDescending(q => q.Indexer, StringComparer.InvariantCultureIgnoreCase);
|
2018-08-08 00:57:15 +00:00
|
|
|
}
|
|
|
|
else if (pagingSpec.SortKey == "downloadClient")
|
|
|
|
{
|
2019-02-23 22:39:11 +00:00
|
|
|
ordered = ascending
|
|
|
|
? fullQueue.OrderBy(q => q.DownloadClient, StringComparer.InvariantCultureIgnoreCase)
|
|
|
|
: fullQueue.OrderByDescending(q => q.DownloadClient, StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
}
|
|
|
|
else if (pagingSpec.SortKey == "quality")
|
|
|
|
{
|
|
|
|
ordered = ascending
|
2020-01-03 12:49:24 +00:00
|
|
|
? fullQueue.OrderBy(q => q.Quality, _qualityComparer)
|
|
|
|
: fullQueue.OrderByDescending(q => q.Quality, _qualityComparer);
|
2018-08-08 00:57:15 +00:00
|
|
|
}
|
2017-09-04 02:20:56 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
ordered = ascending ? fullQueue.OrderBy(orderByFunc) : fullQueue.OrderByDescending(orderByFunc);
|
|
|
|
}
|
|
|
|
|
2020-01-03 12:49:24 +00:00
|
|
|
ordered = ordered.ThenByDescending(q => q.Size == 0 ? 0 : 100 - (q.Sizeleft / q.Size * 100));
|
2017-09-04 02:20:56 +00:00
|
|
|
|
|
|
|
pagingSpec.Records = ordered.Skip((pagingSpec.Page - 1) * pagingSpec.PageSize).Take(pagingSpec.PageSize).ToList();
|
|
|
|
pagingSpec.TotalRecords = fullQueue.Count;
|
|
|
|
|
|
|
|
if (pagingSpec.Records.Empty() && pagingSpec.Page > 1)
|
|
|
|
{
|
|
|
|
pagingSpec.Page = (int)Math.Max(Math.Ceiling((decimal)(pagingSpec.TotalRecords / pagingSpec.PageSize)), 1);
|
|
|
|
pagingSpec.Records = ordered.Skip((pagingSpec.Page - 1) * pagingSpec.PageSize).Take(pagingSpec.PageSize).ToList();
|
|
|
|
}
|
|
|
|
|
|
|
|
return pagingSpec;
|
|
|
|
}
|
|
|
|
|
2020-01-03 12:49:24 +00:00
|
|
|
private Func<NzbDrone.Core.Queue.Queue, object> GetOrderByFunc(PagingSpec<NzbDrone.Core.Queue.Queue> pagingSpec)
|
2017-09-04 02:20:56 +00:00
|
|
|
{
|
|
|
|
switch (pagingSpec.SortKey)
|
|
|
|
{
|
2019-02-23 22:39:11 +00:00
|
|
|
case "status":
|
|
|
|
return q => q.Status;
|
2020-08-20 21:13:25 +00:00
|
|
|
case "artists.sortName":
|
2022-09-26 23:54:00 +00:00
|
|
|
return q => q.Artist?.SortName ?? q.Title;
|
2019-04-14 02:58:11 +00:00
|
|
|
case "title":
|
|
|
|
return q => q.Title;
|
2018-02-04 05:53:03 +00:00
|
|
|
case "album":
|
2017-09-04 02:20:56 +00:00
|
|
|
return q => q.Album;
|
2020-08-20 21:13:25 +00:00
|
|
|
case "albums.title":
|
2020-12-13 05:23:26 +00:00
|
|
|
return q => q.Album?.Title ?? string.Empty;
|
2021-04-18 02:12:45 +00:00
|
|
|
case "albums.releaseDate":
|
2020-12-13 05:23:26 +00:00
|
|
|
return q => q.Album?.ReleaseDate ?? DateTime.MinValue;
|
2017-09-04 02:20:56 +00:00
|
|
|
case "quality":
|
|
|
|
return q => q.Quality;
|
2023-06-18 11:56:08 +00:00
|
|
|
case "size":
|
|
|
|
return q => q.Size;
|
2017-09-04 02:20:56 +00:00
|
|
|
case "progress":
|
2019-02-23 22:39:11 +00:00
|
|
|
// Avoid exploding if a download's size is 0
|
2020-01-03 12:49:24 +00:00
|
|
|
return q => 100 - (q.Sizeleft / Math.Max(q.Size * 100, 1));
|
2017-09-04 02:20:56 +00:00
|
|
|
default:
|
|
|
|
return q => q.Timeleft;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-21 01:36:51 +00:00
|
|
|
private void Remove(NzbDrone.Core.Queue.Queue pendingRelease)
|
2021-08-04 20:42:40 +00:00
|
|
|
{
|
2023-10-21 01:36:51 +00:00
|
|
|
_blocklistService.Block(pendingRelease.RemoteAlbum, "Pending release manually blocklisted");
|
|
|
|
_pendingReleaseService.RemovePendingQueueItems(pendingRelease.Id);
|
|
|
|
}
|
2021-08-04 20:42:40 +00:00
|
|
|
|
2024-01-23 04:56:35 +00:00
|
|
|
private TrackedDownload Remove(TrackedDownload trackedDownload, bool removeFromClient, bool blocklist, bool skipRedownload, bool changeCategory)
|
2023-10-21 01:36:51 +00:00
|
|
|
{
|
2021-08-04 20:42:40 +00:00
|
|
|
if (removeFromClient)
|
|
|
|
{
|
|
|
|
var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient);
|
|
|
|
|
|
|
|
if (downloadClient == null)
|
|
|
|
{
|
|
|
|
throw new BadRequestException();
|
|
|
|
}
|
|
|
|
|
2021-12-24 22:25:17 +00:00
|
|
|
downloadClient.RemoveItem(trackedDownload.DownloadItem, true);
|
2021-08-04 20:42:40 +00:00
|
|
|
}
|
2024-01-23 04:56:35 +00:00
|
|
|
else if (changeCategory)
|
|
|
|
{
|
|
|
|
var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient);
|
|
|
|
|
|
|
|
if (downloadClient == null)
|
|
|
|
{
|
|
|
|
throw new BadRequestException();
|
|
|
|
}
|
|
|
|
|
|
|
|
downloadClient.MarkItemAsImported(trackedDownload.DownloadItem);
|
|
|
|
}
|
2021-08-04 20:42:40 +00:00
|
|
|
|
2021-12-08 14:43:10 +00:00
|
|
|
if (blocklist)
|
2021-08-04 20:42:40 +00:00
|
|
|
{
|
2023-07-13 20:40:04 +00:00
|
|
|
_failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId, skipRedownload);
|
2021-08-04 20:42:40 +00:00
|
|
|
}
|
|
|
|
|
2024-01-23 04:56:35 +00:00
|
|
|
if (!removeFromClient && !blocklist && !changeCategory)
|
2021-08-04 20:42:40 +00:00
|
|
|
{
|
|
|
|
if (!_ignoredDownloadService.IgnoreDownload(trackedDownload))
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return trackedDownload;
|
|
|
|
}
|
|
|
|
|
|
|
|
private TrackedDownload GetTrackedDownload(int queueId)
|
|
|
|
{
|
|
|
|
var queueItem = _queueService.Find(queueId);
|
|
|
|
|
|
|
|
if (queueItem == null)
|
|
|
|
{
|
|
|
|
throw new NotFoundException();
|
|
|
|
}
|
|
|
|
|
|
|
|
var trackedDownload = _trackedDownloadService.Find(queueItem.DownloadId);
|
|
|
|
|
|
|
|
if (trackedDownload == null)
|
|
|
|
{
|
|
|
|
throw new NotFoundException();
|
|
|
|
}
|
|
|
|
|
|
|
|
return trackedDownload;
|
|
|
|
}
|
|
|
|
|
2018-02-04 05:53:03 +00:00
|
|
|
private QueueResource MapToResource(NzbDrone.Core.Queue.Queue queueItem, bool includeArtist, bool includeAlbum)
|
2017-09-04 02:20:56 +00:00
|
|
|
{
|
2018-02-04 05:53:03 +00:00
|
|
|
return queueItem.ToResource(includeArtist, includeAlbum);
|
2017-09-04 02:20:56 +00:00
|
|
|
}
|
|
|
|
|
2021-08-04 20:42:40 +00:00
|
|
|
[NonAction]
|
2017-09-04 02:20:56 +00:00
|
|
|
public void Handle(QueueUpdatedEvent message)
|
|
|
|
{
|
|
|
|
BroadcastResourceChange(ModelAction.Sync);
|
|
|
|
}
|
|
|
|
|
2021-08-04 20:42:40 +00:00
|
|
|
[NonAction]
|
2017-09-04 02:20:56 +00:00
|
|
|
public void Handle(PendingReleasesUpdatedEvent message)
|
|
|
|
{
|
|
|
|
BroadcastResourceChange(ModelAction.Sync);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|