New: Lidarr to Readarr

This commit is contained in:
Qstick
2020-02-29 15:51:29 -05:00
parent 7359c2a9fa
commit 3b7eb01918
565 changed files with 1669 additions and 4272 deletions
@@ -0,0 +1,185 @@
using System.Collections.Generic;
using System.Linq;
using Nancy;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Queue;
using Readarr.Http;
using Readarr.Http.Extensions;
using Readarr.Http.REST;
namespace Readarr.Api.V1.Queue
{
public class QueueActionModule : ReadarrRestModule<QueueResource>
{
private readonly IQueueService _queueService;
private readonly ITrackedDownloadService _trackedDownloadService;
private readonly IFailedDownloadService _failedDownloadService;
private readonly IIgnoredDownloadService _ignoredDownloadService;
private readonly IProvideDownloadClient _downloadClientProvider;
private readonly IPendingReleaseService _pendingReleaseService;
private readonly IDownloadService _downloadService;
public QueueActionModule(IQueueService queueService,
ITrackedDownloadService trackedDownloadService,
IFailedDownloadService failedDownloadService,
IIgnoredDownloadService ignoredDownloadService,
IProvideDownloadClient downloadClientProvider,
IPendingReleaseService pendingReleaseService,
IDownloadService downloadService)
{
_queueService = queueService;
_trackedDownloadService = trackedDownloadService;
_failedDownloadService = failedDownloadService;
_ignoredDownloadService = ignoredDownloadService;
_downloadClientProvider = downloadClientProvider;
_pendingReleaseService = pendingReleaseService;
_downloadService = downloadService;
Post(@"/grab/(?<id>[\d]{1,10})", x => Grab((int)x.Id));
Post("/grab/bulk", x => Grab());
Delete(@"/(?<id>[\d]{1,10})", x => Remove((int)x.Id));
Delete("/bulk", x => Remove());
}
private object Grab(int id)
{
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id);
if (pendingRelease == null)
{
throw new NotFoundException();
}
_downloadService.DownloadReport(pendingRelease.RemoteAlbum);
return new object();
}
private object Grab()
{
var resource = Request.Body.FromJson<QueueBulkResource>();
foreach (var id in resource.Ids)
{
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id);
if (pendingRelease == null)
{
throw new NotFoundException();
}
_downloadService.DownloadReport(pendingRelease.RemoteAlbum);
}
return new object();
}
private object Remove(int id)
{
var removeFromClient = Request.GetBooleanQueryParameter("removeFromClient", true);
var blacklist = Request.GetBooleanQueryParameter("blacklist");
var skipReDownload = Request.GetBooleanQueryParameter("skipredownload");
var trackedDownload = Remove(id, removeFromClient, blacklist, skipReDownload);
if (trackedDownload != null)
{
_trackedDownloadService.StopTracking(trackedDownload.DownloadItem.DownloadId);
}
return new object();
}
private object Remove()
{
var removeFromClient = Request.GetBooleanQueryParameter("removeFromClient", true);
var blacklist = Request.GetBooleanQueryParameter("blacklist");
var skipReDownload = Request.GetBooleanQueryParameter("skipredownload");
var resource = Request.Body.FromJson<QueueBulkResource>();
var trackedDownloadIds = new List<string>();
foreach (var id in resource.Ids)
{
var trackedDownload = Remove(id, removeFromClient, blacklist, skipReDownload);
if (trackedDownload != null)
{
trackedDownloadIds.Add(trackedDownload.DownloadItem.DownloadId);
}
}
_trackedDownloadService.StopTracking(trackedDownloadIds);
return new object();
}
private TrackedDownload Remove(int id, bool removeFromClient, bool blacklist, bool skipReDownload)
{
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id);
if (pendingRelease != null)
{
_pendingReleaseService.RemovePendingQueueItems(pendingRelease.Id);
return null;
}
var trackedDownload = GetTrackedDownload(id);
if (trackedDownload == null)
{
throw new NotFoundException();
}
if (removeFromClient)
{
var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient);
if (downloadClient == null)
{
throw new BadRequestException();
}
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadId, true);
}
if (blacklist)
{
_failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId, skipReDownload);
}
if (!removeFromClient && !blacklist)
{
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;
}
}
}
@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Readarr.Api.V1.Queue
{
public class QueueBulkResource
{
public List<int> Ids { get; set; }
}
}
@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Queue;
using NzbDrone.SignalR;
using Readarr.Http;
using Readarr.Http.Extensions;
namespace Readarr.Api.V1.Queue
{
public class QueueDetailsModule : ReadarrRestModuleWithSignalR<QueueResource, NzbDrone.Core.Queue.Queue>,
IHandle<QueueUpdatedEvent>, IHandle<PendingReleasesUpdatedEvent>
{
private readonly IQueueService _queueService;
private readonly IPendingReleaseService _pendingReleaseService;
public QueueDetailsModule(IBroadcastSignalRMessage broadcastSignalRMessage, IQueueService queueService, IPendingReleaseService pendingReleaseService)
: base(broadcastSignalRMessage, "queue/details")
{
_queueService = queueService;
_pendingReleaseService = pendingReleaseService;
GetResourceAll = GetQueue;
}
private List<QueueResource> GetQueue()
{
var includeArtist = Request.GetBooleanQueryParameter("includeArtist");
var includeAlbum = Request.GetBooleanQueryParameter("includeAlbum", true);
var queue = _queueService.GetQueue();
var pending = _pendingReleaseService.GetPendingQueue();
var fullQueue = queue.Concat(pending);
var artistIdQuery = Request.Query.ArtistId;
var albumIdsQuery = Request.Query.AlbumIds;
if (artistIdQuery.HasValue)
{
return fullQueue.Where(q => q.Artist?.Id == (int)artistIdQuery).ToResource(includeArtist, includeAlbum);
}
if (albumIdsQuery.HasValue)
{
string albumIdsValue = albumIdsQuery.Value.ToString();
var albumIds = albumIdsValue.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(e => Convert.ToInt32(e))
.ToList();
return fullQueue.Where(q => q.Album != null && albumIds.Contains(q.Album.Id)).ToResource(includeArtist, includeAlbum);
}
return fullQueue.ToResource(includeArtist, includeAlbum);
}
public void Handle(QueueUpdatedEvent message)
{
BroadcastResourceChange(ModelAction.Sync);
}
public void Handle(PendingReleasesUpdatedEvent message)
{
BroadcastResourceChange(ModelAction.Sync);
}
}
}
+156
View File
@@ -0,0 +1,156 @@
using System;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Queue;
using NzbDrone.SignalR;
using Readarr.Http;
using Readarr.Http.Extensions;
namespace Readarr.Api.V1.Queue
{
public class QueueModule : ReadarrRestModuleWithSignalR<QueueResource, NzbDrone.Core.Queue.Queue>,
IHandle<QueueUpdatedEvent>, IHandle<PendingReleasesUpdatedEvent>
{
private readonly IQueueService _queueService;
private readonly IPendingReleaseService _pendingReleaseService;
private readonly QualityModelComparer _qualityComparer;
public QueueModule(IBroadcastSignalRMessage broadcastSignalRMessage,
IQueueService queueService,
IPendingReleaseService pendingReleaseService,
QualityProfileService qualityProfileService)
: base(broadcastSignalRMessage)
{
_queueService = queueService;
_pendingReleaseService = pendingReleaseService;
GetResourcePaged = GetQueue;
_qualityComparer = new QualityModelComparer(qualityProfileService.GetDefaultProfile(string.Empty));
}
private PagingResource<QueueResource> GetQueue(PagingResource<QueueResource> pagingResource)
{
var pagingSpec = pagingResource.MapToPagingSpec<QueueResource, NzbDrone.Core.Queue.Queue>("timeleft", SortDirection.Ascending);
var includeUnknownArtistItems = Request.GetBooleanQueryParameter("includeUnknownArtistItems");
var includeArtist = Request.GetBooleanQueryParameter("includeArtist");
var includeAlbum = Request.GetBooleanQueryParameter("includeAlbum");
return ApplyToPage((spec) => GetQueue(spec, includeUnknownArtistItems), pagingSpec, (q) => MapToResource(q, includeArtist, includeAlbum));
}
private PagingSpec<NzbDrone.Core.Queue.Queue> GetQueue(PagingSpec<NzbDrone.Core.Queue.Queue> pagingSpec, bool includeUnknownArtistItems)
{
var ascending = pagingSpec.SortDirection == SortDirection.Ascending;
var orderByFunc = GetOrderByFunc(pagingSpec);
var queue = _queueService.GetQueue();
var filteredQueue = includeUnknownArtistItems ? queue : queue.Where(q => q.Artist != null);
var pending = _pendingReleaseService.GetPendingQueue();
var fullQueue = filteredQueue.Concat(pending).ToList();
IOrderedEnumerable<NzbDrone.Core.Queue.Queue> ordered;
if (pagingSpec.SortKey == "timeleft")
{
ordered = ascending
? fullQueue.OrderBy(q => q.Timeleft, new TimeleftComparer())
: fullQueue.OrderByDescending(q => q.Timeleft, new TimeleftComparer());
}
else if (pagingSpec.SortKey == "estimatedCompletionTime")
{
ordered = ascending
? fullQueue.OrderBy(q => q.EstimatedCompletionTime, new EstimatedCompletionTimeComparer())
: fullQueue.OrderByDescending(q => q.EstimatedCompletionTime,
new EstimatedCompletionTimeComparer());
}
else if (pagingSpec.SortKey == "protocol")
{
ordered = ascending
? fullQueue.OrderBy(q => q.Protocol)
: fullQueue.OrderByDescending(q => q.Protocol);
}
else if (pagingSpec.SortKey == "indexer")
{
ordered = ascending
? fullQueue.OrderBy(q => q.Indexer, StringComparer.InvariantCultureIgnoreCase)
: fullQueue.OrderByDescending(q => q.Indexer, StringComparer.InvariantCultureIgnoreCase);
}
else if (pagingSpec.SortKey == "downloadClient")
{
ordered = ascending
? fullQueue.OrderBy(q => q.DownloadClient, StringComparer.InvariantCultureIgnoreCase)
: fullQueue.OrderByDescending(q => q.DownloadClient, StringComparer.InvariantCultureIgnoreCase);
}
else if (pagingSpec.SortKey == "quality")
{
ordered = ascending
? fullQueue.OrderBy(q => q.Quality, _qualityComparer)
: fullQueue.OrderByDescending(q => q.Quality, _qualityComparer);
}
else
{
ordered = ascending ? fullQueue.OrderBy(orderByFunc) : fullQueue.OrderByDescending(orderByFunc);
}
ordered = ordered.ThenByDescending(q => q.Size == 0 ? 0 : 100 - (q.Sizeleft / q.Size * 100));
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;
}
private Func<NzbDrone.Core.Queue.Queue, object> GetOrderByFunc(PagingSpec<NzbDrone.Core.Queue.Queue> pagingSpec)
{
switch (pagingSpec.SortKey)
{
case "status":
return q => q.Status;
case "artist.sortName":
return q => q.Artist?.SortName;
case "title":
return q => q.Title;
case "album":
return q => q.Album;
case "album.title":
return q => q.Album?.Title;
case "album.releaseDate":
return q => q.Album?.ReleaseDate;
case "quality":
return q => q.Quality;
case "progress":
// Avoid exploding if a download's size is 0
return q => 100 - (q.Sizeleft / Math.Max(q.Size * 100, 1));
default:
return q => q.Timeleft;
}
}
private QueueResource MapToResource(NzbDrone.Core.Queue.Queue queueItem, bool includeArtist, bool includeAlbum)
{
return queueItem.ToResource(includeArtist, includeAlbum);
}
public void Handle(QueueUpdatedEvent message)
{
BroadcastResourceChange(ModelAction.Sync);
}
public void Handle(PendingReleasesUpdatedEvent message)
{
BroadcastResourceChange(ModelAction.Sync);
}
}
}
+80
View File
@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Qualities;
using Readarr.Api.V1.Albums;
using Readarr.Api.V1.Artist;
using Readarr.Http.REST;
namespace Readarr.Api.V1.Queue
{
public class QueueResource : RestResource
{
public int? ArtistId { get; set; }
public int? AlbumId { get; set; }
public ArtistResource Artist { get; set; }
public AlbumResource Album { get; set; }
public QualityModel Quality { get; set; }
public decimal Size { get; set; }
public string Title { get; set; }
public decimal Sizeleft { get; set; }
public TimeSpan? Timeleft { get; set; }
public DateTime? EstimatedCompletionTime { get; set; }
public string Status { get; set; }
public TrackedDownloadStatus? TrackedDownloadStatus { get; set; }
public TrackedDownloadState? TrackedDownloadState { get; set; }
public List<TrackedDownloadStatusMessage> StatusMessages { get; set; }
public string ErrorMessage { get; set; }
public string DownloadId { get; set; }
public DownloadProtocol Protocol { get; set; }
public string DownloadClient { get; set; }
public string Indexer { get; set; }
public string OutputPath { get; set; }
public bool DownloadForced { get; set; }
}
public static class QueueResourceMapper
{
public static QueueResource ToResource(this NzbDrone.Core.Queue.Queue model, bool includeArtist, bool includeAlbum)
{
if (model == null)
{
return null;
}
return new QueueResource
{
Id = model.Id,
ArtistId = model.Artist?.Id,
AlbumId = model.Album?.Id,
Artist = includeArtist && model.Artist != null ? model.Artist.ToResource() : null,
Album = includeAlbum && model.Album != null ? model.Album.ToResource() : null,
Quality = model.Quality,
Size = model.Size,
Title = model.Title,
Sizeleft = model.Sizeleft,
Timeleft = model.Timeleft,
EstimatedCompletionTime = model.EstimatedCompletionTime,
Status = model.Status.FirstCharToLower(),
TrackedDownloadStatus = model.TrackedDownloadStatus,
TrackedDownloadState = model.TrackedDownloadState,
StatusMessages = model.StatusMessages,
ErrorMessage = model.ErrorMessage,
DownloadId = model.DownloadId,
Protocol = model.Protocol,
DownloadClient = model.DownloadClient,
Indexer = model.Indexer,
OutputPath = model.OutputPath,
DownloadForced = model.DownloadForced
};
}
public static List<QueueResource> ToResource(this IEnumerable<NzbDrone.Core.Queue.Queue> models, bool includeArtist, bool includeAlbum)
{
return models.Select((m) => ToResource(m, includeArtist, includeAlbum)).ToList();
}
}
}
@@ -0,0 +1,75 @@
using System;
using System.Linq;
using NzbDrone.Common.TPL;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Queue;
using NzbDrone.SignalR;
using Readarr.Http;
namespace Readarr.Api.V1.Queue
{
public class QueueStatusModule : ReadarrRestModuleWithSignalR<QueueStatusResource, NzbDrone.Core.Queue.Queue>,
IHandle<QueueUpdatedEvent>, IHandle<PendingReleasesUpdatedEvent>
{
private readonly IQueueService _queueService;
private readonly IPendingReleaseService _pendingReleaseService;
private readonly Debouncer _broadcastDebounce;
public QueueStatusModule(IBroadcastSignalRMessage broadcastSignalRMessage, IQueueService queueService, IPendingReleaseService pendingReleaseService)
: base(broadcastSignalRMessage, "queue/status")
{
_queueService = queueService;
_pendingReleaseService = pendingReleaseService;
_broadcastDebounce = new Debouncer(BroadcastChange, TimeSpan.FromSeconds(5));
Get("/", x => GetQueueStatusResponse());
}
private object GetQueueStatusResponse()
{
return GetQueueStatus();
}
private QueueStatusResource GetQueueStatus()
{
_broadcastDebounce.Pause();
var queue = _queueService.GetQueue();
var pending = _pendingReleaseService.GetPendingQueue();
var resource = new QueueStatusResource
{
TotalCount = queue.Count + pending.Count,
Count = queue.Count(q => q.Artist != null) + pending.Count,
UnknownCount = queue.Count(q => q.Artist == null),
Errors = queue.Any(q => q.Artist != null && q.TrackedDownloadStatus == TrackedDownloadStatus.Error),
Warnings = queue.Any(q => q.Artist != null && q.TrackedDownloadStatus == TrackedDownloadStatus.Warning),
UnknownErrors = queue.Any(q => q.Artist == null && q.TrackedDownloadStatus == TrackedDownloadStatus.Error),
UnknownWarnings = queue.Any(q => q.Artist == null && q.TrackedDownloadStatus == TrackedDownloadStatus.Warning)
};
_broadcastDebounce.Resume();
return resource;
}
private void BroadcastChange()
{
BroadcastResourceChange(ModelAction.Updated, GetQueueStatus());
}
public void Handle(QueueUpdatedEvent message)
{
_broadcastDebounce.Execute();
}
public void Handle(PendingReleasesUpdatedEvent message)
{
_broadcastDebounce.Execute();
}
}
}
@@ -0,0 +1,15 @@
using Readarr.Http.REST;
namespace Readarr.Api.V1.Queue
{
public class QueueStatusResource : RestResource
{
public int TotalCount { get; set; }
public int Count { get; set; }
public int UnknownCount { get; set; }
public bool Errors { get; set; }
public bool Warnings { get; set; }
public bool UnknownErrors { get; set; }
public bool UnknownWarnings { get; set; }
}
}