mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-06 13:02:23 -07:00
New: Monitor and Process downloads separately
New: Queue remains up to date while importing file from remote file system Fixed: Failed downloads still in queue won't result in failed search
This commit is contained in:
parent
a104a2911e
commit
d83e20937d
40 changed files with 1119 additions and 509 deletions
|
@ -132,7 +132,7 @@ class Queue extends Component {
|
||||||
totalRecords,
|
totalRecords,
|
||||||
isGrabbing,
|
isGrabbing,
|
||||||
isRemoving,
|
isRemoving,
|
||||||
isCheckForFinishedDownloadExecuting,
|
isRefreshMonitoredDownloadsExecuting,
|
||||||
onRefreshPress,
|
onRefreshPress,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -145,7 +145,7 @@ class Queue extends Component {
|
||||||
isPendingSelected
|
isPendingSelected
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const isRefreshing = isFetching || isAlbumsFetching || isCheckForFinishedDownloadExecuting;
|
const isRefreshing = isFetching || isAlbumsFetching || isRefreshMonitoredDownloadsExecuting;
|
||||||
const isAllPopulated = isPopulated && (isAlbumsPopulated || !items.length || items.every((e) => !e.albumId));
|
const isAllPopulated = isPopulated && (isAlbumsPopulated || !items.length || items.every((e) => !e.albumId));
|
||||||
const hasError = error || albumsError;
|
const hasError = error || albumsError;
|
||||||
const selectedCount = this.getSelectedIds().length;
|
const selectedCount = this.getSelectedIds().length;
|
||||||
|
@ -279,7 +279,7 @@ Queue.propTypes = {
|
||||||
totalRecords: PropTypes.number,
|
totalRecords: PropTypes.number,
|
||||||
isGrabbing: PropTypes.bool.isRequired,
|
isGrabbing: PropTypes.bool.isRequired,
|
||||||
isRemoving: PropTypes.bool.isRequired,
|
isRemoving: PropTypes.bool.isRequired,
|
||||||
isCheckForFinishedDownloadExecuting: PropTypes.bool.isRequired,
|
isRefreshMonitoredDownloadsExecuting: PropTypes.bool.isRequired,
|
||||||
onRefreshPress: PropTypes.func.isRequired,
|
onRefreshPress: PropTypes.func.isRequired,
|
||||||
onGrabSelectedPress: PropTypes.func.isRequired,
|
onGrabSelectedPress: PropTypes.func.isRequired,
|
||||||
onRemoveSelectedPress: PropTypes.func.isRequired
|
onRemoveSelectedPress: PropTypes.func.isRequired
|
||||||
|
|
|
@ -18,13 +18,13 @@ function createMapStateToProps() {
|
||||||
(state) => state.albums,
|
(state) => state.albums,
|
||||||
(state) => state.queue.options,
|
(state) => state.queue.options,
|
||||||
(state) => state.queue.paged,
|
(state) => state.queue.paged,
|
||||||
createCommandExecutingSelector(commandNames.CHECK_FOR_FINISHED_DOWNLOAD),
|
createCommandExecutingSelector(commandNames.REFRESH_MONITORED_DOWNLOADS),
|
||||||
(albums, options, queue, isCheckForFinishedDownloadExecuting) => {
|
(albums, options, queue, isRefreshMonitoredDownloadsExecuting) => {
|
||||||
return {
|
return {
|
||||||
isAlbumsFetching: albums.isFetching,
|
isAlbumsFetching: albums.isFetching,
|
||||||
isAlbumsPopulated: albums.isPopulated,
|
isAlbumsPopulated: albums.isPopulated,
|
||||||
albumsError: albums.error,
|
albumsError: albums.error,
|
||||||
isCheckForFinishedDownloadExecuting,
|
isRefreshMonitoredDownloadsExecuting,
|
||||||
...options,
|
...options,
|
||||||
...queue
|
...queue
|
||||||
};
|
};
|
||||||
|
@ -129,7 +129,7 @@ class QueueConnector extends Component {
|
||||||
|
|
||||||
onRefreshPress = () => {
|
onRefreshPress = () => {
|
||||||
this.props.executeCommand({
|
this.props.executeCommand({
|
||||||
name: commandNames.CHECK_FOR_FINISHED_DOWNLOAD
|
name: commandNames.REFRESH_MONITORED_DOWNLOADS
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,7 @@ class QueueRow extends Component {
|
||||||
title,
|
title,
|
||||||
status,
|
status,
|
||||||
trackedDownloadStatus,
|
trackedDownloadStatus,
|
||||||
|
trackedDownloadState,
|
||||||
statusMessages,
|
statusMessages,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
artist,
|
artist,
|
||||||
|
@ -100,8 +101,8 @@ class QueueRow extends Component {
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const progress = 100 - (sizeleft / size * 100);
|
const progress = 100 - (sizeleft / size * 100);
|
||||||
const showInteractiveImport = status === 'Completed' && trackedDownloadStatus === 'Warning';
|
const showInteractiveImport = status === 'completed' && trackedDownloadStatus === 'warning';
|
||||||
const isPending = status === 'Delay' || status === 'DownloadClientUnavailable';
|
const isPending = status === 'delay' || status === 'downloadClientUnavailable';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
@ -129,6 +130,7 @@ class QueueRow extends Component {
|
||||||
sourceTitle={title}
|
sourceTitle={title}
|
||||||
status={status}
|
status={status}
|
||||||
trackedDownloadStatus={trackedDownloadStatus}
|
trackedDownloadStatus={trackedDownloadStatus}
|
||||||
|
trackedDownloadState={trackedDownloadState}
|
||||||
statusMessages={statusMessages}
|
statusMessages={statusMessages}
|
||||||
errorMessage={errorMessage}
|
errorMessage={errorMessage}
|
||||||
/>
|
/>
|
||||||
|
@ -348,6 +350,7 @@ QueueRow.propTypes = {
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
trackedDownloadStatus: PropTypes.string,
|
trackedDownloadStatus: PropTypes.string,
|
||||||
|
trackedDownloadState: PropTypes.string,
|
||||||
statusMessages: PropTypes.arrayOf(PropTypes.object),
|
statusMessages: PropTypes.arrayOf(PropTypes.object),
|
||||||
errorMessage: PropTypes.string,
|
errorMessage: PropTypes.string,
|
||||||
artist: PropTypes.object,
|
artist: PropTypes.object,
|
||||||
|
|
|
@ -37,13 +37,14 @@ function QueueStatusCell(props) {
|
||||||
const {
|
const {
|
||||||
sourceTitle,
|
sourceTitle,
|
||||||
status,
|
status,
|
||||||
trackedDownloadStatus = 'Ok',
|
trackedDownloadStatus,
|
||||||
|
trackedDownloadState,
|
||||||
statusMessages,
|
statusMessages,
|
||||||
errorMessage
|
errorMessage
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const hasWarning = trackedDownloadStatus === 'Warning';
|
const hasWarning = trackedDownloadStatus === 'warning';
|
||||||
const hasError = trackedDownloadStatus === 'Error';
|
const hasError = trackedDownloadStatus === 'error';
|
||||||
|
|
||||||
// status === 'downloading'
|
// status === 'downloading'
|
||||||
let iconName = icons.DOWNLOADING;
|
let iconName = icons.DOWNLOADING;
|
||||||
|
@ -54,46 +55,58 @@ function QueueStatusCell(props) {
|
||||||
iconKind = kinds.WARNING;
|
iconKind = kinds.WARNING;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'Paused') {
|
if (status === 'paused') {
|
||||||
iconName = icons.PAUSED;
|
iconName = icons.PAUSED;
|
||||||
title = 'Paused';
|
title = 'Paused';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'Queued') {
|
if (status === 'queued') {
|
||||||
iconName = icons.QUEUED;
|
iconName = icons.QUEUED;
|
||||||
title = 'Queued';
|
title = 'Queued';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'Completed') {
|
if (status === 'completed') {
|
||||||
iconName = icons.DOWNLOADED;
|
iconName = icons.DOWNLOADED;
|
||||||
title = 'Downloaded';
|
title = 'Downloaded';
|
||||||
|
|
||||||
|
if (trackedDownloadState === 'importPending') {
|
||||||
|
title += ' - Waiting to Import';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'Delay') {
|
if (trackedDownloadState === 'importing') {
|
||||||
|
title += ' - Importing';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trackedDownloadState === 'failedPending') {
|
||||||
|
title += ' - Waiting to Process';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === 'delay') {
|
||||||
iconName = icons.PENDING;
|
iconName = icons.PENDING;
|
||||||
title = 'Pending';
|
title = 'Pending';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'DownloadClientUnavailable') {
|
if (status === 'downloadClientUnavailable') {
|
||||||
iconName = icons.PENDING;
|
iconName = icons.PENDING;
|
||||||
iconKind = kinds.WARNING;
|
iconKind = kinds.WARNING;
|
||||||
title = 'Pending - Download client is unavailable';
|
title = 'Pending - Download client is unavailable';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'Failed') {
|
if (status === 'failed') {
|
||||||
iconName = icons.DOWNLOADING;
|
iconName = icons.DOWNLOADING;
|
||||||
iconKind = kinds.DANGER;
|
iconKind = kinds.DANGER;
|
||||||
title = 'Download failed';
|
title = 'Download failed';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'Warning') {
|
if (status === 'warning') {
|
||||||
iconName = icons.DOWNLOADING;
|
iconName = icons.DOWNLOADING;
|
||||||
iconKind = kinds.WARNING;
|
iconKind = kinds.WARNING;
|
||||||
title = `Download warning: ${errorMessage || 'check download client for more details'}`;
|
title = `Download warning: ${errorMessage || 'check download client for more details'}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasError) {
|
if (hasError) {
|
||||||
if (status === 'Completed') {
|
if (status === 'completed') {
|
||||||
iconName = icons.DOWNLOAD;
|
iconName = icons.DOWNLOAD;
|
||||||
iconKind = kinds.DANGER;
|
iconKind = kinds.DANGER;
|
||||||
title = `Import failed: ${sourceTitle}`;
|
title = `Import failed: ${sourceTitle}`;
|
||||||
|
@ -125,9 +138,15 @@ function QueueStatusCell(props) {
|
||||||
QueueStatusCell.propTypes = {
|
QueueStatusCell.propTypes = {
|
||||||
sourceTitle: PropTypes.string.isRequired,
|
sourceTitle: PropTypes.string.isRequired,
|
||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
trackedDownloadStatus: PropTypes.string,
|
trackedDownloadStatus: PropTypes.string.isRequired,
|
||||||
|
trackedDownloadState: PropTypes.string.isRequired,
|
||||||
statusMessages: PropTypes.arrayOf(PropTypes.object),
|
statusMessages: PropTypes.arrayOf(PropTypes.object),
|
||||||
errorMessage: PropTypes.string
|
errorMessage: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QueueStatusCell.defaultProps = {
|
||||||
|
trackedDownloadStatus: 'ok',
|
||||||
|
trackedDownloadState: 'downloading'
|
||||||
|
};
|
||||||
|
|
||||||
export default QueueStatusCell;
|
export default QueueStatusCell;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export const APPLICATION_UPDATE = 'ApplicationUpdate';
|
export const APPLICATION_UPDATE = 'ApplicationUpdate';
|
||||||
export const BACKUP = 'Backup';
|
export const BACKUP = 'Backup';
|
||||||
export const CHECK_FOR_FINISHED_DOWNLOAD = 'CheckForFinishedDownload';
|
export const REFRESH_MONITORED_DOWNLOADS = 'RefreshMonitoredDownloads';
|
||||||
export const CLEAR_BLACKLIST = 'ClearBlacklist';
|
export const CLEAR_BLACKLIST = 'ClearBlacklist';
|
||||||
export const CLEAR_LOGS = 'ClearLog';
|
export const CLEAR_LOGS = 'ClearLog';
|
||||||
export const CUTOFF_UNMET_ALBUM_SEARCH = 'CutoffUnmetAlbumSearch';
|
export const CUTOFF_UNMET_ALBUM_SEARCH = 'CutoffUnmetAlbumSearch';
|
||||||
|
|
|
@ -272,7 +272,7 @@ class SignalRConnector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSystemTask = () => {
|
handleSystemTask = () => {
|
||||||
// No-op for now, we may want this later
|
this.props.dispatchFetchCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRootfolder = (body) => {
|
handleRootfolder = (body) => {
|
||||||
|
|
|
@ -168,7 +168,7 @@ class QueuedTaskRow extends Component {
|
||||||
isCancelConfirmModalOpen
|
isCancelConfirmModalOpen
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
let triggerIcon = icons.UNKNOWN;
|
let triggerIcon = icons.QUICK;
|
||||||
|
|
||||||
if (trigger === 'manual') {
|
if (trigger === 'manual') {
|
||||||
triggerIcon = icons.INTERACTIVE;
|
triggerIcon = icons.INTERACTIVE;
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||||
using Lidarr.Api.V1.Albums;
|
using Lidarr.Api.V1.Albums;
|
||||||
using Lidarr.Api.V1.Artist;
|
using Lidarr.Api.V1.Artist;
|
||||||
using Lidarr.Http.REST;
|
using Lidarr.Http.REST;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Download.TrackedDownloads;
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
|
@ -23,7 +24,8 @@ namespace Lidarr.Api.V1.Queue
|
||||||
public TimeSpan? Timeleft { get; set; }
|
public TimeSpan? Timeleft { get; set; }
|
||||||
public DateTime? EstimatedCompletionTime { get; set; }
|
public DateTime? EstimatedCompletionTime { get; set; }
|
||||||
public string Status { get; set; }
|
public string Status { get; set; }
|
||||||
public string TrackedDownloadStatus { get; set; }
|
public TrackedDownloadStatus? TrackedDownloadStatus { get; set; }
|
||||||
|
public TrackedDownloadState? TrackedDownloadState { get; set; }
|
||||||
public List<TrackedDownloadStatusMessage> StatusMessages { get; set; }
|
public List<TrackedDownloadStatusMessage> StatusMessages { get; set; }
|
||||||
public string ErrorMessage { get; set; }
|
public string ErrorMessage { get; set; }
|
||||||
public string DownloadId { get; set; }
|
public string DownloadId { get; set; }
|
||||||
|
@ -56,8 +58,9 @@ namespace Lidarr.Api.V1.Queue
|
||||||
Sizeleft = model.Sizeleft,
|
Sizeleft = model.Sizeleft,
|
||||||
Timeleft = model.Timeleft,
|
Timeleft = model.Timeleft,
|
||||||
EstimatedCompletionTime = model.EstimatedCompletionTime,
|
EstimatedCompletionTime = model.EstimatedCompletionTime,
|
||||||
Status = model.Status,
|
Status = model.Status.FirstCharToLower(),
|
||||||
TrackedDownloadStatus = model.TrackedDownloadStatus,
|
TrackedDownloadStatus = model.TrackedDownloadStatus,
|
||||||
|
TrackedDownloadState = model.TrackedDownloadState,
|
||||||
StatusMessages = model.StatusMessages,
|
StatusMessages = model.StatusMessages,
|
||||||
ErrorMessage = model.ErrorMessage,
|
ErrorMessage = model.ErrorMessage,
|
||||||
DownloadId = model.DownloadId,
|
DownloadId = model.DownloadId,
|
||||||
|
|
|
@ -4,6 +4,7 @@ using Lidarr.Http;
|
||||||
using NzbDrone.Common.TPL;
|
using NzbDrone.Common.TPL;
|
||||||
using NzbDrone.Core.Datastore.Events;
|
using NzbDrone.Core.Datastore.Events;
|
||||||
using NzbDrone.Core.Download.Pending;
|
using NzbDrone.Core.Download.Pending;
|
||||||
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Queue;
|
using NzbDrone.Core.Queue;
|
||||||
using NzbDrone.SignalR;
|
using NzbDrone.SignalR;
|
||||||
|
@ -45,10 +46,10 @@ namespace Lidarr.Api.V1.Queue
|
||||||
TotalCount = queue.Count + pending.Count,
|
TotalCount = queue.Count + pending.Count,
|
||||||
Count = queue.Count(q => q.Artist != null) + pending.Count,
|
Count = queue.Count(q => q.Artist != null) + pending.Count,
|
||||||
UnknownCount = queue.Count(q => q.Artist == null),
|
UnknownCount = queue.Count(q => q.Artist == null),
|
||||||
Errors = queue.Any(q => q.Artist != null && q.TrackedDownloadStatus.Equals("Error", StringComparison.InvariantCultureIgnoreCase)),
|
Errors = queue.Any(q => q.Artist != null && q.TrackedDownloadStatus == TrackedDownloadStatus.Error),
|
||||||
Warnings = queue.Any(q => q.Artist != null && q.TrackedDownloadStatus.Equals("Warning", StringComparison.InvariantCultureIgnoreCase)),
|
Warnings = queue.Any(q => q.Artist != null && q.TrackedDownloadStatus == TrackedDownloadStatus.Warning),
|
||||||
UnknownErrors = queue.Any(q => q.Artist == null && q.TrackedDownloadStatus.Equals("Error", StringComparison.InvariantCultureIgnoreCase)),
|
UnknownErrors = queue.Any(q => q.Artist == null && q.TrackedDownloadStatus == TrackedDownloadStatus.Error),
|
||||||
UnknownWarnings = queue.Any(q => q.Artist == null && q.TrackedDownloadStatus.Equals("Warning", StringComparison.InvariantCultureIgnoreCase))
|
UnknownWarnings = queue.Any(q => q.Artist == null && q.TrackedDownloadStatus == TrackedDownloadStatus.Warning)
|
||||||
};
|
};
|
||||||
|
|
||||||
_broadcastDebounce.Resume();
|
_broadcastDebounce.Resume();
|
||||||
|
|
|
@ -4,6 +4,7 @@ using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.DecisionEngine.Specifications;
|
using NzbDrone.Core.DecisionEngine.Specifications;
|
||||||
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
using NzbDrone.Core.Music;
|
using NzbDrone.Core.Music;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Profiles.Qualities;
|
using NzbDrone.Core.Profiles.Qualities;
|
||||||
|
@ -69,11 +70,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
.Returns(new List<Queue.Queue>());
|
.Returns(new List<Queue.Queue>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenQueue(IEnumerable<RemoteAlbum> remoteAlbums)
|
private void GivenQueue(IEnumerable<RemoteAlbum> remoteAlbums, TrackedDownloadState trackedDownloadState = TrackedDownloadState.Downloading)
|
||||||
{
|
{
|
||||||
var queue = remoteAlbums.Select(remoteAlbum => new Queue.Queue
|
var queue = remoteAlbums.Select(remoteAlbum => new Queue.Queue
|
||||||
{
|
{
|
||||||
RemoteAlbum = remoteAlbum
|
RemoteAlbum = remoteAlbum,
|
||||||
|
TrackedDownloadState = trackedDownloadState
|
||||||
});
|
});
|
||||||
|
|
||||||
Mocker.GetMock<IQueueService>()
|
Mocker.GetMock<IQueueService>()
|
||||||
|
@ -308,5 +310,25 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
GivenQueue(new List<RemoteAlbum> { remoteAlbum });
|
GivenQueue(new List<RemoteAlbum> { remoteAlbum });
|
||||||
Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeFalse();
|
Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_true_if_everything_is_the_same_for_failed_pending()
|
||||||
|
{
|
||||||
|
_artist.QualityProfile.Value.Cutoff = Quality.FLAC.Id;
|
||||||
|
|
||||||
|
var remoteAlbum = Builder<RemoteAlbum>.CreateNew()
|
||||||
|
.With(r => r.Artist = _artist)
|
||||||
|
.With(r => r.Albums = new List<Album> { _album })
|
||||||
|
.With(r => r.ParsedAlbumInfo = new ParsedAlbumInfo
|
||||||
|
{
|
||||||
|
Quality = new QualityModel(Quality.MP3_008)
|
||||||
|
})
|
||||||
|
.With(r => r.Release = _releaseInfo)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
GivenQueue(new List<RemoteAlbum> { remoteAlbum }, TrackedDownloadState.DownloadFailedPending);
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeTrue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.Download.TrackedDownloads;
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
using NzbDrone.Core.History;
|
using NzbDrone.Core.History;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.MediaFiles.Events;
|
|
||||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Music;
|
using NzbDrone.Core.Music;
|
||||||
|
@ -18,10 +17,10 @@ using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.Download
|
namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class CompletedDownloadServiceFixture : CoreTest<CompletedDownloadService>
|
public class ImportFixture : CoreTest<CompletedDownloadService>
|
||||||
{
|
{
|
||||||
private TrackedDownload _trackedDownload;
|
private TrackedDownload _trackedDownload;
|
||||||
|
|
||||||
|
@ -37,7 +36,7 @@ namespace NzbDrone.Core.Test.Download
|
||||||
var remoteAlbum = BuildRemoteAlbum();
|
var remoteAlbum = BuildRemoteAlbum();
|
||||||
|
|
||||||
_trackedDownload = Builder<TrackedDownload>.CreateNew()
|
_trackedDownload = Builder<TrackedDownload>.CreateNew()
|
||||||
.With(c => c.State = TrackedDownloadStage.Downloading)
|
.With(c => c.State = TrackedDownloadState.Downloading)
|
||||||
.With(c => c.DownloadItem = completed)
|
.With(c => c.DownloadItem = completed)
|
||||||
.With(c => c.RemoteAlbum = remoteAlbum)
|
.With(c => c.RemoteAlbum = remoteAlbum)
|
||||||
.Build();
|
.Build();
|
||||||
|
@ -84,23 +83,6 @@ namespace NzbDrone.Core.Test.Download
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenNoGrabbedHistory()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IHistoryService>()
|
|
||||||
.Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId))
|
|
||||||
.Returns((History.History)null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenSuccessfulImport()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IDownloadedTracksImportService>()
|
|
||||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
|
||||||
.Returns(new List<ImportResult>
|
|
||||||
{
|
|
||||||
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenABadlyNamedDownload()
|
private void GivenABadlyNamedDownload()
|
||||||
{
|
{
|
||||||
_trackedDownload.RemoteAlbum.Artist = null;
|
_trackedDownload.RemoteAlbum.Artist = null;
|
||||||
|
@ -126,62 +108,184 @@ namespace NzbDrone.Core.Test.Download
|
||||||
.Returns(_trackedDownload.RemoteAlbum.Artist);
|
.Returns(_trackedDownload.RemoteAlbum.Artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(DownloadItemStatus.Downloading)]
|
[Test]
|
||||||
[TestCase(DownloadItemStatus.Failed)]
|
public void should_not_mark_as_imported_if_all_files_were_rejected()
|
||||||
[TestCase(DownloadItemStatus.Queued)]
|
|
||||||
[TestCase(DownloadItemStatus.Paused)]
|
|
||||||
[TestCase(DownloadItemStatus.Warning)]
|
|
||||||
public void should_not_process_if_download_status_isnt_completed(DownloadItemStatus status)
|
|
||||||
{
|
{
|
||||||
_trackedDownload.DownloadItem.Status = status;
|
Mocker.GetMock<IDownloadedTracksImportService>()
|
||||||
|
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
||||||
|
.Returns(new List<ImportResult>
|
||||||
|
{
|
||||||
|
new ImportResult(
|
||||||
|
new ImportDecision<LocalTrack>(
|
||||||
|
new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }, new Rejection("Rejected!")), "Test Failure"),
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
new ImportResult(
|
||||||
|
new ImportDecision<LocalTrack>(
|
||||||
|
new LocalTrack { Path = @"C:\TestPath\Droned.S01E02.mkv".AsOsAgnostic() }, new Rejection("Rejected!")), "Test Failure")
|
||||||
|
});
|
||||||
|
|
||||||
AssertNoAttemptedImport();
|
Subject.Import(_trackedDownload);
|
||||||
|
|
||||||
|
Mocker.GetMock<IEventAggregator>()
|
||||||
|
.Verify(v => v.PublishEvent<DownloadCompletedEvent>(It.IsAny<DownloadCompletedEvent>()), Times.Never());
|
||||||
|
|
||||||
|
AssertNotImported();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_process_if_matching_history_is_not_found_and_no_category_specified()
|
public void should_not_mark_as_imported_if_no_tracks_were_parsed()
|
||||||
{
|
{
|
||||||
_trackedDownload.DownloadItem.Category = null;
|
Mocker.GetMock<IDownloadedTracksImportService>()
|
||||||
GivenNoGrabbedHistory();
|
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
||||||
|
.Returns(new List<ImportResult>
|
||||||
|
{
|
||||||
|
new ImportResult(
|
||||||
|
new ImportDecision<LocalTrack>(
|
||||||
|
new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }, new Rejection("Rejected!")), "Test Failure"),
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
new ImportResult(
|
||||||
|
new ImportDecision<LocalTrack>(
|
||||||
|
new LocalTrack { Path = @"C:\TestPath\Droned.S01E02.mkv".AsOsAgnostic() }, new Rejection("Rejected!")), "Test Failure")
|
||||||
|
});
|
||||||
|
|
||||||
AssertNoAttemptedImport();
|
_trackedDownload.RemoteAlbum.Albums.Clear();
|
||||||
|
|
||||||
|
Subject.Import(_trackedDownload);
|
||||||
|
|
||||||
|
AssertNotImported();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_process_if_matching_history_is_not_found_but_category_specified()
|
public void should_not_mark_as_failed_if_nothing_found_to_import()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IDownloadedTracksImportService>()
|
||||||
|
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
||||||
|
.Returns(new List<ImportResult>());
|
||||||
|
|
||||||
|
Subject.Import(_trackedDownload);
|
||||||
|
|
||||||
|
_trackedDownload.State.Should().Be(TrackedDownloadState.Importing);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_mark_as_imported_if_all_files_were_skipped()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IDownloadedTracksImportService>()
|
||||||
|
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
||||||
|
.Returns(new List<ImportResult>
|
||||||
|
{
|
||||||
|
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }), "Test Failure"),
|
||||||
|
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }), "Test Failure")
|
||||||
|
});
|
||||||
|
|
||||||
|
Subject.Import(_trackedDownload);
|
||||||
|
|
||||||
|
AssertNotImported();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_mark_as_imported_if_all_tracks_were_imported_but_extra_files_were_not()
|
||||||
{
|
{
|
||||||
_trackedDownload.DownloadItem.Category = "tv";
|
|
||||||
GivenNoGrabbedHistory();
|
|
||||||
GivenArtistMatch();
|
GivenArtistMatch();
|
||||||
GivenSuccessfulImport();
|
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
_trackedDownload.RemoteAlbum.Albums = new List<Album>
|
||||||
|
{
|
||||||
|
CreateAlbum(1, 3)
|
||||||
|
};
|
||||||
|
|
||||||
AssertCompletedDownload();
|
Mocker.GetMock<IDownloadedTracksImportService>()
|
||||||
|
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
||||||
|
.Returns(new List<ImportResult>
|
||||||
|
{
|
||||||
|
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
|
||||||
|
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
|
||||||
|
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
|
||||||
|
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }), "Test Failure")
|
||||||
|
});
|
||||||
|
|
||||||
|
Subject.Import(_trackedDownload);
|
||||||
|
|
||||||
|
AssertImported();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_process_if_output_path_is_empty()
|
public void should_not_mark_as_imported_if_some_tracks_were_not_imported()
|
||||||
{
|
{
|
||||||
_trackedDownload.DownloadItem.OutputPath = default(OsPath);
|
_trackedDownload.RemoteAlbum.Albums = new List<Album>
|
||||||
|
{
|
||||||
|
CreateAlbum(1, 1),
|
||||||
|
CreateAlbum(1, 2),
|
||||||
|
CreateAlbum(1, 1)
|
||||||
|
};
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
Mocker.GetMock<IDownloadedTracksImportService>()
|
||||||
|
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
||||||
|
.Returns(new List<ImportResult>
|
||||||
|
{
|
||||||
|
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
|
||||||
|
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
|
||||||
|
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
|
||||||
|
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }), "Test Failure"),
|
||||||
|
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }), "Test Failure")
|
||||||
|
});
|
||||||
|
|
||||||
AssertNoAttemptedImport();
|
var history = Builder<History.History>.CreateListOfSize(2)
|
||||||
|
.BuildList();
|
||||||
|
|
||||||
|
Mocker.GetMock<IHistoryService>()
|
||||||
|
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
|
||||||
|
.Returns(history);
|
||||||
|
|
||||||
|
Mocker.GetMock<ITrackedDownloadAlreadyImported>()
|
||||||
|
.Setup(s => s.IsImported(_trackedDownload, history))
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
Subject.Import(_trackedDownload);
|
||||||
|
|
||||||
|
AssertNotImported();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_throw_if_remotealbum_is_null()
|
public void should_not_mark_as_imported_if_some_of_episodes_were_not_imported_including_history()
|
||||||
{
|
{
|
||||||
_trackedDownload.RemoteAlbum = null;
|
var tracks = Builder<Track>.CreateListOfSize(3).BuildList();
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
var releases = Builder<AlbumRelease>.CreateListOfSize(3).All().With(x => x.Monitored = true).With(x => x.TrackCount = 1).BuildList();
|
||||||
|
releases[0].Tracks = new List<Track> { tracks[0] };
|
||||||
|
releases[1].Tracks = new List<Track> { tracks[1] };
|
||||||
|
releases[2].Tracks = new List<Track> { tracks[2] };
|
||||||
|
|
||||||
AssertNoAttemptedImport();
|
var albums = Builder<Album>.CreateListOfSize(3).BuildList();
|
||||||
|
|
||||||
|
albums[0].AlbumReleases = new List<AlbumRelease> { releases[0] };
|
||||||
|
albums[1].AlbumReleases = new List<AlbumRelease> { releases[1] };
|
||||||
|
albums[2].AlbumReleases = new List<AlbumRelease> { releases[2] };
|
||||||
|
|
||||||
|
_trackedDownload.RemoteAlbum.Albums = albums;
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadedTracksImportService>()
|
||||||
|
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
||||||
|
.Returns(new List<ImportResult>
|
||||||
|
{
|
||||||
|
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv" })),
|
||||||
|
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv" }), "Test Failure"),
|
||||||
|
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv" }), "Test Failure")
|
||||||
|
});
|
||||||
|
|
||||||
|
var history = Builder<History.History>.CreateListOfSize(2)
|
||||||
|
.BuildList();
|
||||||
|
|
||||||
|
Mocker.GetMock<IHistoryService>()
|
||||||
|
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
|
||||||
|
.Returns(history);
|
||||||
|
|
||||||
|
Mocker.GetMock<ITrackedDownloadAlreadyImported>()
|
||||||
|
.Setup(s => s.IsImported(It.IsAny<TrackedDownload>(), It.IsAny<List<History.History>>()))
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
Subject.Import(_trackedDownload);
|
||||||
|
|
||||||
|
AssertNotImported();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -205,18 +309,27 @@ namespace NzbDrone.Core.Test.Download
|
||||||
new LocalTrack { Path = @"C:\TestPath\Droned.S01E02.mkv".AsOsAgnostic() }))
|
new LocalTrack { Path = @"C:\TestPath\Droned.S01E02.mkv".AsOsAgnostic() }))
|
||||||
});
|
});
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
Subject.Import(_trackedDownload);
|
||||||
|
|
||||||
AssertCompletedDownload();
|
AssertImported();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_mark_as_imported_if_all_tracks_were_imported_but_album_incomplete()
|
public void should_mark_as_imported_if_all_episodes_were_imported_including_history()
|
||||||
{
|
{
|
||||||
_trackedDownload.RemoteAlbum.Albums = new List<Album>
|
var track1 = new Track { Id = 1 };
|
||||||
{
|
var track2 = new Track { Id = 2 };
|
||||||
CreateAlbum(1, 3)
|
|
||||||
};
|
var releases = Builder<AlbumRelease>.CreateListOfSize(2).All().With(x => x.Monitored = true).With(x => x.TrackCount = 1).BuildList();
|
||||||
|
releases[0].Tracks = new List<Track> { track1 };
|
||||||
|
releases[1].Tracks = new List<Track> { track2 };
|
||||||
|
|
||||||
|
var albums = Builder<Album>.CreateListOfSize(2).BuildList();
|
||||||
|
|
||||||
|
albums[0].AlbumReleases = new List<AlbumRelease> { releases[0] };
|
||||||
|
albums[1].AlbumReleases = new List<AlbumRelease> { releases[1] };
|
||||||
|
|
||||||
|
_trackedDownload.RemoteAlbum.Albums = albums;
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadedTracksImportService>()
|
Mocker.GetMock<IDownloadedTracksImportService>()
|
||||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
||||||
|
@ -224,143 +337,27 @@ namespace NzbDrone.Core.Test.Download
|
||||||
{
|
{
|
||||||
new ImportResult(
|
new ImportResult(
|
||||||
new ImportDecision<LocalTrack>(
|
new ImportDecision<LocalTrack>(
|
||||||
new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
|
new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv", Tracks = new List<Track> { track1 } })),
|
||||||
|
|
||||||
new ImportResult(
|
new ImportResult(
|
||||||
new ImportDecision<LocalTrack>(
|
new ImportDecision<LocalTrack>(
|
||||||
new LocalTrack { Path = @"C:\TestPath\Droned.S01E02.mkv".AsOsAgnostic() }))
|
new LocalTrack { Path = @"C:\TestPath\Droned.S01E02.mkv", Tracks = new List<Track> { track2 } }), "Test Failure")
|
||||||
});
|
});
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
var history = Builder<History.History>.CreateListOfSize(2)
|
||||||
|
.BuildList();
|
||||||
|
|
||||||
AssertCompletedDownload();
|
Mocker.GetMock<IHistoryService>()
|
||||||
}
|
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
|
||||||
|
.Returns(history);
|
||||||
|
|
||||||
[Test]
|
Mocker.GetMock<ITrackedDownloadAlreadyImported>()
|
||||||
public void should_not_mark_as_imported_if_all_files_were_rejected()
|
.Setup(s => s.IsImported(It.IsAny<TrackedDownload>(), It.IsAny<List<History.History>>()))
|
||||||
{
|
.Returns(true);
|
||||||
Mocker.GetMock<IDownloadedTracksImportService>()
|
|
||||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
|
||||||
.Returns(new List<ImportResult>
|
|
||||||
{
|
|
||||||
new ImportResult(
|
|
||||||
new ImportDecision<LocalTrack>(
|
|
||||||
new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }, new Rejection("Rejected!")), "Test Failure"),
|
|
||||||
|
|
||||||
new ImportResult(
|
Subject.Import(_trackedDownload);
|
||||||
new ImportDecision<LocalTrack>(
|
|
||||||
new LocalTrack { Path = @"C:\TestPath\Droned.S01E02.mkv".AsOsAgnostic() }, new Rejection("Rejected!")), "Test Failure")
|
|
||||||
});
|
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
AssertImported();
|
||||||
|
|
||||||
Mocker.GetMock<IEventAggregator>()
|
|
||||||
.Verify(v => v.PublishEvent<DownloadCompletedEvent>(It.IsAny<DownloadCompletedEvent>()), Times.Never());
|
|
||||||
|
|
||||||
AssertImportIncomplete();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_mark_as_imported_if_no_tracks_were_parsed()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IDownloadedTracksImportService>()
|
|
||||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
|
||||||
.Returns(new List<ImportResult>
|
|
||||||
{
|
|
||||||
new ImportResult(
|
|
||||||
new ImportDecision<LocalTrack>(
|
|
||||||
new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }, new Rejection("Rejected!")), "Test Failure"),
|
|
||||||
|
|
||||||
new ImportResult(
|
|
||||||
new ImportDecision<LocalTrack>(
|
|
||||||
new LocalTrack { Path = @"C:\TestPath\Droned.S01E02.mkv".AsOsAgnostic() }, new Rejection("Rejected!")), "Test Failure")
|
|
||||||
});
|
|
||||||
|
|
||||||
_trackedDownload.RemoteAlbum.Albums.Clear();
|
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
|
||||||
|
|
||||||
AssertImportIncomplete();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_mark_as_failed_if_nothing_found_to_import()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IDownloadedTracksImportService>()
|
|
||||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
|
||||||
.Returns(new List<ImportResult>());
|
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
|
||||||
|
|
||||||
AssertNoCompletedDownload();
|
|
||||||
_trackedDownload.State.Should().NotBe(TrackedDownloadStage.ImportFailed);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_mark_as_imported_if_all_files_were_skipped()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IDownloadedTracksImportService>()
|
|
||||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
|
||||||
.Returns(new List<ImportResult>
|
|
||||||
{
|
|
||||||
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }), "Test Failure"),
|
|
||||||
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }), "Test Failure")
|
|
||||||
});
|
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
|
||||||
|
|
||||||
AssertImportIncomplete();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_mark_as_imported_if_all_tracks_were_imported_but_extra_files_were_not()
|
|
||||||
{
|
|
||||||
GivenArtistMatch();
|
|
||||||
|
|
||||||
_trackedDownload.RemoteAlbum.Albums = new List<Album>
|
|
||||||
{
|
|
||||||
CreateAlbum(1, 3)
|
|
||||||
};
|
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadedTracksImportService>()
|
|
||||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
|
||||||
.Returns(new List<ImportResult>
|
|
||||||
{
|
|
||||||
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
|
|
||||||
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
|
|
||||||
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
|
|
||||||
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }), "Test Failure")
|
|
||||||
});
|
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
|
||||||
|
|
||||||
AssertCompletedDownload();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_mark_as_failed_if_some_tracks_were_not_imported()
|
|
||||||
{
|
|
||||||
_trackedDownload.RemoteAlbum.Albums = new List<Album>
|
|
||||||
{
|
|
||||||
CreateAlbum(1, 1),
|
|
||||||
CreateAlbum(1, 2),
|
|
||||||
CreateAlbum(1, 1)
|
|
||||||
};
|
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadedTracksImportService>()
|
|
||||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
|
||||||
.Returns(new List<ImportResult>
|
|
||||||
{
|
|
||||||
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
|
|
||||||
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
|
|
||||||
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
|
|
||||||
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }), "Test Failure"),
|
|
||||||
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }), "Test Failure")
|
|
||||||
});
|
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
|
||||||
|
|
||||||
AssertImportIncomplete();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -375,117 +372,20 @@ namespace NzbDrone.Core.Test.Download
|
||||||
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }))
|
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }))
|
||||||
});
|
});
|
||||||
|
|
||||||
Mocker.GetMock<IArtistService>()
|
Subject.Import(_trackedDownload);
|
||||||
.Setup(v => v.GetArtist(It.IsAny<int>()))
|
|
||||||
.Returns(BuildRemoteAlbum().Artist);
|
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
AssertImported();
|
||||||
|
|
||||||
AssertCompletedDownload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
private void AssertNotImported()
|
||||||
public void should_not_mark_as_imported_if_the_download_cannot_be_tracked_using_the_source_title_as_it_was_initiated_externally()
|
|
||||||
{
|
|
||||||
GivenABadlyNamedDownload();
|
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadedTracksImportService>()
|
|
||||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
|
||||||
.Returns(new List<ImportResult>
|
|
||||||
{
|
|
||||||
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }))
|
|
||||||
});
|
|
||||||
|
|
||||||
Mocker.GetMock<IHistoryService>()
|
|
||||||
.Setup(s => s.MostRecentForDownloadId(It.Is<string>(i => i == "1234")));
|
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
|
||||||
|
|
||||||
AssertNoCompletedDownload();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_import_when_there_is_a_title_mismatch()
|
|
||||||
{
|
|
||||||
_trackedDownload.RemoteAlbum.Artist = null;
|
|
||||||
Mocker.GetMock<IParsingService>()
|
|
||||||
.Setup(s => s.GetArtist("Drone.S01E01.HDTV"))
|
|
||||||
.Returns((Artist)null);
|
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
|
||||||
|
|
||||||
AssertNoCompletedDownload();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_mark_as_import_title_mismatch_if_ignore_warnings_is_true()
|
|
||||||
{
|
|
||||||
_trackedDownload.RemoteAlbum.Albums = new List<Album>
|
|
||||||
{
|
|
||||||
CreateAlbum(0, 1)
|
|
||||||
};
|
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadedTracksImportService>()
|
|
||||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
|
|
||||||
.Returns(new List<ImportResult>
|
|
||||||
{
|
|
||||||
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }))
|
|
||||||
});
|
|
||||||
|
|
||||||
Subject.Process(_trackedDownload, true);
|
|
||||||
|
|
||||||
AssertCompletedDownload();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_warn_if_path_is_not_valid_for_windows()
|
|
||||||
{
|
|
||||||
WindowsOnly();
|
|
||||||
|
|
||||||
_trackedDownload.DownloadItem.OutputPath = new OsPath(@"/invalid/Windows/Path");
|
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
|
||||||
|
|
||||||
AssertNoAttemptedImport();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_warn_if_path_is_not_valid_for_linux()
|
|
||||||
{
|
|
||||||
PosixOnly();
|
|
||||||
|
|
||||||
_trackedDownload.DownloadItem.OutputPath = new OsPath(@"C:\Invalid\Mono\Path");
|
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
|
||||||
|
|
||||||
AssertNoAttemptedImport();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AssertNoAttemptedImport()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IDownloadedTracksImportService>()
|
|
||||||
.Verify(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()), Times.Never());
|
|
||||||
|
|
||||||
AssertNoCompletedDownload();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AssertImportIncomplete()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IEventAggregator>()
|
|
||||||
.Verify(v => v.PublishEvent(It.IsAny<AlbumImportIncompleteEvent>()), Times.Once());
|
|
||||||
|
|
||||||
AssertNoCompletedDownload();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AssertNoCompletedDownload()
|
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IEventAggregator>()
|
Mocker.GetMock<IEventAggregator>()
|
||||||
.Verify(v => v.PublishEvent(It.IsAny<DownloadCompletedEvent>()), Times.Never());
|
.Verify(v => v.PublishEvent(It.IsAny<DownloadCompletedEvent>()), Times.Never());
|
||||||
|
|
||||||
_trackedDownload.State.Should().NotBe(TrackedDownloadStage.Imported);
|
_trackedDownload.State.Should().Be(TrackedDownloadState.ImportFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AssertCompletedDownload()
|
private void AssertImported()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IDownloadedTracksImportService>()
|
Mocker.GetMock<IDownloadedTracksImportService>()
|
||||||
.Verify(v => v.ProcessPath(_trackedDownload.DownloadItem.OutputPath.FullPath, ImportMode.Auto, _trackedDownload.RemoteAlbum.Artist, _trackedDownload.DownloadItem), Times.Once());
|
.Verify(v => v.ProcessPath(_trackedDownload.DownloadItem.OutputPath.FullPath, ImportMode.Auto, _trackedDownload.RemoteAlbum.Artist, _trackedDownload.DownloadItem), Times.Once());
|
||||||
|
@ -493,7 +393,7 @@ namespace NzbDrone.Core.Test.Download
|
||||||
Mocker.GetMock<IEventAggregator>()
|
Mocker.GetMock<IEventAggregator>()
|
||||||
.Verify(v => v.PublishEvent(It.IsAny<DownloadCompletedEvent>()), Times.Once());
|
.Verify(v => v.PublishEvent(It.IsAny<DownloadCompletedEvent>()), Times.Once());
|
||||||
|
|
||||||
_trackedDownload.State.Should().Be(TrackedDownloadStage.Imported);
|
_trackedDownload.State.Should().Be(TrackedDownloadState.Imported);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
|
using NzbDrone.Core.History;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class ProcessFixture : CoreTest<CompletedDownloadService>
|
||||||
|
{
|
||||||
|
private TrackedDownload _trackedDownload;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
var completed = Builder<DownloadClientItem>.CreateNew()
|
||||||
|
.With(h => h.Status = DownloadItemStatus.Completed)
|
||||||
|
.With(h => h.OutputPath = new OsPath(@"C:\DropFolder\MyDownload".AsOsAgnostic()))
|
||||||
|
.With(h => h.Title = "Drone.S01E01.HDTV")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var remoteAlbum = BuildRemoteAlbum();
|
||||||
|
|
||||||
|
_trackedDownload = Builder<TrackedDownload>.CreateNew()
|
||||||
|
.With(c => c.State = TrackedDownloadState.Downloading)
|
||||||
|
.With(c => c.DownloadItem = completed)
|
||||||
|
.With(c => c.RemoteAlbum = remoteAlbum)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadClient>()
|
||||||
|
.SetupGet(c => c.Definition)
|
||||||
|
.Returns(new DownloadClientDefinition { Id = 1, Name = "testClient" });
|
||||||
|
|
||||||
|
Mocker.GetMock<IProvideDownloadClient>()
|
||||||
|
.Setup(c => c.Get(It.IsAny<int>()))
|
||||||
|
.Returns(Mocker.GetMock<IDownloadClient>().Object);
|
||||||
|
|
||||||
|
Mocker.GetMock<IHistoryService>()
|
||||||
|
.Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId))
|
||||||
|
.Returns(new History.History());
|
||||||
|
|
||||||
|
Mocker.GetMock<IParsingService>()
|
||||||
|
.Setup(s => s.GetArtist("Drone.S01E01.HDTV"))
|
||||||
|
.Returns(remoteAlbum.Artist);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RemoteAlbum BuildRemoteAlbum()
|
||||||
|
{
|
||||||
|
return new RemoteAlbum
|
||||||
|
{
|
||||||
|
Artist = new Artist(),
|
||||||
|
Albums = new List<Album> { new Album { Id = 1 } }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenNoGrabbedHistory()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IHistoryService>()
|
||||||
|
.Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId))
|
||||||
|
.Returns((History.History)null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenArtistMatch()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IParsingService>()
|
||||||
|
.Setup(s => s.GetArtist(It.IsAny<string>()))
|
||||||
|
.Returns(_trackedDownload.RemoteAlbum.Artist);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenABadlyNamedDownload()
|
||||||
|
{
|
||||||
|
_trackedDownload.DownloadItem.DownloadId = "1234";
|
||||||
|
_trackedDownload.DownloadItem.Title = "Droned Pilot"; // Set a badly named download
|
||||||
|
Mocker.GetMock<IHistoryService>()
|
||||||
|
.Setup(s => s.MostRecentForDownloadId(It.Is<string>(i => i == "1234")))
|
||||||
|
.Returns(new History.History() { SourceTitle = "Droned S01E01" });
|
||||||
|
|
||||||
|
Mocker.GetMock<IParsingService>()
|
||||||
|
.Setup(s => s.GetArtist(It.IsAny<string>()))
|
||||||
|
.Returns((Artist)null);
|
||||||
|
|
||||||
|
Mocker.GetMock<IParsingService>()
|
||||||
|
.Setup(s => s.GetArtist("Droned S01E01"))
|
||||||
|
.Returns(BuildRemoteAlbum().Artist);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(DownloadItemStatus.Downloading)]
|
||||||
|
[TestCase(DownloadItemStatus.Failed)]
|
||||||
|
[TestCase(DownloadItemStatus.Queued)]
|
||||||
|
[TestCase(DownloadItemStatus.Paused)]
|
||||||
|
[TestCase(DownloadItemStatus.Warning)]
|
||||||
|
public void should_not_process_if_download_status_isnt_completed(DownloadItemStatus status)
|
||||||
|
{
|
||||||
|
_trackedDownload.DownloadItem.Status = status;
|
||||||
|
|
||||||
|
Subject.Check(_trackedDownload);
|
||||||
|
|
||||||
|
AssertNotReadyToImport();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_process_if_matching_history_is_not_found_and_no_category_specified()
|
||||||
|
{
|
||||||
|
_trackedDownload.DownloadItem.Category = null;
|
||||||
|
GivenNoGrabbedHistory();
|
||||||
|
|
||||||
|
Subject.Check(_trackedDownload);
|
||||||
|
|
||||||
|
AssertNotReadyToImport();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_process_if_matching_history_is_not_found_but_category_specified()
|
||||||
|
{
|
||||||
|
_trackedDownload.DownloadItem.Category = "tv";
|
||||||
|
GivenNoGrabbedHistory();
|
||||||
|
GivenArtistMatch();
|
||||||
|
|
||||||
|
Subject.Check(_trackedDownload);
|
||||||
|
|
||||||
|
AssertReadyToImport();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_process_if_output_path_is_empty()
|
||||||
|
{
|
||||||
|
_trackedDownload.DownloadItem.OutputPath = default;
|
||||||
|
|
||||||
|
Subject.Check(_trackedDownload);
|
||||||
|
|
||||||
|
AssertNotReadyToImport();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_process_if_the_download_cannot_be_tracked_using_the_source_title_as_it_was_initiated_externally()
|
||||||
|
{
|
||||||
|
GivenABadlyNamedDownload();
|
||||||
|
_trackedDownload.RemoteAlbum.Artist = null;
|
||||||
|
|
||||||
|
Mocker.GetMock<IHistoryService>()
|
||||||
|
.Setup(s => s.MostRecentForDownloadId(It.Is<string>(i => i == "1234")));
|
||||||
|
|
||||||
|
Subject.Check(_trackedDownload);
|
||||||
|
|
||||||
|
AssertNotReadyToImport();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_process_when_there_is_a_title_mismatch()
|
||||||
|
{
|
||||||
|
_trackedDownload.RemoteAlbum.Artist = null;
|
||||||
|
Mocker.GetMock<IParsingService>()
|
||||||
|
.Setup(s => s.GetArtist("Drone.S01E01.HDTV"))
|
||||||
|
.Returns((Artist)null);
|
||||||
|
|
||||||
|
Subject.Check(_trackedDownload);
|
||||||
|
|
||||||
|
AssertNotReadyToImport();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AssertNotReadyToImport()
|
||||||
|
{
|
||||||
|
_trackedDownload.State.Should().NotBe(TrackedDownloadState.ImportPending);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AssertReadyToImport()
|
||||||
|
{
|
||||||
|
_trackedDownload.State.Should().Be(TrackedDownloadState.ImportPending);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
|
using NzbDrone.Core.History;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.Download.FailedDownloadServiceTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class ProcessFailedFixture : CoreTest<FailedDownloadService>
|
||||||
|
{
|
||||||
|
private TrackedDownload _trackedDownload;
|
||||||
|
private List<History.History> _grabHistory;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
var completed = Builder<DownloadClientItem>.CreateNew()
|
||||||
|
.With(h => h.Status = DownloadItemStatus.Completed)
|
||||||
|
.With(h => h.OutputPath = new OsPath(@"C:\DropFolder\MyDownload".AsOsAgnostic()))
|
||||||
|
.With(h => h.Title = "Drone.S01E01.HDTV")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_grabHistory = Builder<History.History>.CreateListOfSize(2).BuildList();
|
||||||
|
|
||||||
|
var remoteAlbum = new RemoteAlbum
|
||||||
|
{
|
||||||
|
Artist = new Artist(),
|
||||||
|
Albums = new List<Album> { new Album { Id = 1 } }
|
||||||
|
};
|
||||||
|
|
||||||
|
_trackedDownload = Builder<TrackedDownload>.CreateNew()
|
||||||
|
.With(c => c.State = TrackedDownloadState.DownloadFailedPending)
|
||||||
|
.With(c => c.DownloadItem = completed)
|
||||||
|
.With(c => c.RemoteAlbum = remoteAlbum)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
Mocker.GetMock<IHistoryService>()
|
||||||
|
.Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed))
|
||||||
|
.Returns(_grabHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_mark_failed_if_encrypted()
|
||||||
|
{
|
||||||
|
_trackedDownload.DownloadItem.IsEncrypted = true;
|
||||||
|
|
||||||
|
Subject.ProcessFailed(_trackedDownload);
|
||||||
|
|
||||||
|
AssertDownloadFailed();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_mark_failed_if_download_item_is_failed()
|
||||||
|
{
|
||||||
|
_trackedDownload.DownloadItem.Status = DownloadItemStatus.Failed;
|
||||||
|
|
||||||
|
Subject.ProcessFailed(_trackedDownload);
|
||||||
|
|
||||||
|
AssertDownloadFailed();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_include_tracked_download_in_message()
|
||||||
|
{
|
||||||
|
_trackedDownload.DownloadItem.Status = DownloadItemStatus.Failed;
|
||||||
|
|
||||||
|
Subject.ProcessFailed(_trackedDownload);
|
||||||
|
|
||||||
|
Mocker.GetMock<IEventAggregator>()
|
||||||
|
.Verify(v => v.PublishEvent(It.Is<DownloadFailedEvent>(c => c.TrackedDownload != null)), Times.Once());
|
||||||
|
|
||||||
|
AssertDownloadFailed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AssertDownloadNotFailed()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IEventAggregator>()
|
||||||
|
.Verify(v => v.PublishEvent(It.IsAny<DownloadFailedEvent>()), Times.Never());
|
||||||
|
|
||||||
|
_trackedDownload.State.Should().NotBe(TrackedDownloadState.DownloadFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AssertDownloadFailed()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IEventAggregator>()
|
||||||
|
.Verify(v => v.PublishEvent(It.IsAny<DownloadFailedEvent>()), Times.Once());
|
||||||
|
|
||||||
|
_trackedDownload.State.Should().Be(TrackedDownloadState.DownloadFailed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,10 +13,10 @@ using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.Download
|
namespace NzbDrone.Core.Test.Download.FailedDownloadServiceTests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class FailedDownloadServiceFixture : CoreTest<FailedDownloadService>
|
public class ProcessFixture : CoreTest<FailedDownloadService>
|
||||||
{
|
{
|
||||||
private TrackedDownload _trackedDownload;
|
private TrackedDownload _trackedDownload;
|
||||||
private List<History.History> _grabHistory;
|
private List<History.History> _grabHistory;
|
||||||
|
@ -39,7 +39,7 @@ namespace NzbDrone.Core.Test.Download
|
||||||
};
|
};
|
||||||
|
|
||||||
_trackedDownload = Builder<TrackedDownload>.CreateNew()
|
_trackedDownload = Builder<TrackedDownload>.CreateNew()
|
||||||
.With(c => c.State = TrackedDownloadStage.Downloading)
|
.With(c => c.State = TrackedDownloadState.Downloading)
|
||||||
.With(c => c.DownloadItem = completed)
|
.With(c => c.DownloadItem = completed)
|
||||||
.With(c => c.RemoteAlbum = remoteAlbum)
|
.With(c => c.RemoteAlbum = remoteAlbum)
|
||||||
.Build();
|
.Build();
|
||||||
|
@ -61,7 +61,7 @@ namespace NzbDrone.Core.Test.Download
|
||||||
{
|
{
|
||||||
GivenNoGrabbedHistory();
|
GivenNoGrabbedHistory();
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
Subject.Check(_trackedDownload);
|
||||||
|
|
||||||
AssertDownloadNotFailed();
|
AssertDownloadNotFailed();
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ namespace NzbDrone.Core.Test.Download
|
||||||
_trackedDownload.DownloadItem.Status = DownloadItemStatus.Failed;
|
_trackedDownload.DownloadItem.Status = DownloadItemStatus.Failed;
|
||||||
GivenNoGrabbedHistory();
|
GivenNoGrabbedHistory();
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
Subject.Check(_trackedDownload);
|
||||||
|
|
||||||
_trackedDownload.StatusMessages.Should().NotBeEmpty();
|
_trackedDownload.StatusMessages.Should().NotBeEmpty();
|
||||||
}
|
}
|
||||||
|
@ -83,50 +83,17 @@ namespace NzbDrone.Core.Test.Download
|
||||||
_trackedDownload.DownloadItem.Status = DownloadItemStatus.Failed;
|
_trackedDownload.DownloadItem.Status = DownloadItemStatus.Failed;
|
||||||
GivenNoGrabbedHistory();
|
GivenNoGrabbedHistory();
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
Subject.Check(_trackedDownload);
|
||||||
|
|
||||||
_trackedDownload.StatusMessages.Should().NotBeEmpty();
|
_trackedDownload.StatusMessages.Should().NotBeEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_mark_failed_if_encrypted()
|
|
||||||
{
|
|
||||||
_trackedDownload.DownloadItem.IsEncrypted = true;
|
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
|
||||||
|
|
||||||
AssertDownloadFailed();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_mark_failed_if_download_item_is_failed()
|
|
||||||
{
|
|
||||||
_trackedDownload.DownloadItem.Status = DownloadItemStatus.Failed;
|
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
|
||||||
|
|
||||||
AssertDownloadFailed();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_include_tracked_download_in_message()
|
|
||||||
{
|
|
||||||
_trackedDownload.DownloadItem.Status = DownloadItemStatus.Failed;
|
|
||||||
|
|
||||||
Subject.Process(_trackedDownload);
|
|
||||||
|
|
||||||
Mocker.GetMock<IEventAggregator>()
|
|
||||||
.Verify(v => v.PublishEvent(It.Is<DownloadFailedEvent>(c => c.TrackedDownload != null)), Times.Once());
|
|
||||||
|
|
||||||
AssertDownloadFailed();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AssertDownloadNotFailed()
|
private void AssertDownloadNotFailed()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IEventAggregator>()
|
Mocker.GetMock<IEventAggregator>()
|
||||||
.Verify(v => v.PublishEvent(It.IsAny<DownloadFailedEvent>()), Times.Never());
|
.Verify(v => v.PublishEvent(It.IsAny<DownloadFailedEvent>()), Times.Never());
|
||||||
|
|
||||||
_trackedDownload.State.Should().NotBe(TrackedDownloadStage.DownloadFailed);
|
_trackedDownload.State.Should().NotBe(TrackedDownloadState.DownloadFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AssertDownloadFailed()
|
private void AssertDownloadFailed()
|
||||||
|
@ -134,7 +101,7 @@ namespace NzbDrone.Core.Test.Download
|
||||||
Mocker.GetMock<IEventAggregator>()
|
Mocker.GetMock<IEventAggregator>()
|
||||||
.Verify(v => v.PublishEvent(It.IsAny<DownloadFailedEvent>()), Times.Once());
|
.Verify(v => v.PublishEvent(It.IsAny<DownloadFailedEvent>()), Times.Once());
|
||||||
|
|
||||||
_trackedDownload.State.Should().Be(TrackedDownloadStage.DownloadFailed);
|
_trackedDownload.State.Should().Be(TrackedDownloadState.DownloadFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -36,7 +36,7 @@ namespace NzbDrone.Core.Test.Download
|
||||||
SkipReDownload = true
|
SkipReDownload = true
|
||||||
};
|
};
|
||||||
|
|
||||||
Subject.HandleAsync(failedEvent);
|
Subject.Handle(failedEvent);
|
||||||
|
|
||||||
Mocker.GetMock<IManageCommandQueue>()
|
Mocker.GetMock<IManageCommandQueue>()
|
||||||
.Verify(x => x.Push(It.IsAny<Command>(), It.IsAny<CommandPriority>(), It.IsAny<CommandTrigger>()),
|
.Verify(x => x.Push(It.IsAny<Command>(), It.IsAny<CommandPriority>(), It.IsAny<CommandTrigger>()),
|
||||||
|
@ -56,7 +56,7 @@ namespace NzbDrone.Core.Test.Download
|
||||||
.Setup(x => x.AutoRedownloadFailed)
|
.Setup(x => x.AutoRedownloadFailed)
|
||||||
.Returns(false);
|
.Returns(false);
|
||||||
|
|
||||||
Subject.HandleAsync(failedEvent);
|
Subject.Handle(failedEvent);
|
||||||
|
|
||||||
Mocker.GetMock<IManageCommandQueue>()
|
Mocker.GetMock<IManageCommandQueue>()
|
||||||
.Verify(x => x.Push(It.IsAny<Command>(), It.IsAny<CommandPriority>(), It.IsAny<CommandTrigger>()),
|
.Verify(x => x.Push(It.IsAny<Command>(), It.IsAny<CommandPriority>(), It.IsAny<CommandTrigger>()),
|
||||||
|
@ -72,7 +72,7 @@ namespace NzbDrone.Core.Test.Download
|
||||||
AlbumIds = new List<int> { 2 }
|
AlbumIds = new List<int> { 2 }
|
||||||
};
|
};
|
||||||
|
|
||||||
Subject.HandleAsync(failedEvent);
|
Subject.Handle(failedEvent);
|
||||||
|
|
||||||
Mocker.GetMock<IManageCommandQueue>()
|
Mocker.GetMock<IManageCommandQueue>()
|
||||||
.Verify(x => x.Push(It.Is<AlbumSearchCommand>(c => c.AlbumIds.Count == 1 &&
|
.Verify(x => x.Push(It.Is<AlbumSearchCommand>(c => c.AlbumIds.Count == 1 &&
|
||||||
|
@ -95,7 +95,7 @@ namespace NzbDrone.Core.Test.Download
|
||||||
AlbumIds = new List<int> { 2, 3 }
|
AlbumIds = new List<int> { 2, 3 }
|
||||||
};
|
};
|
||||||
|
|
||||||
Subject.HandleAsync(failedEvent);
|
Subject.Handle(failedEvent);
|
||||||
|
|
||||||
Mocker.GetMock<IManageCommandQueue>()
|
Mocker.GetMock<IManageCommandQueue>()
|
||||||
.Verify(x => x.Push(It.Is<AlbumSearchCommand>(c => c.AlbumIds.Count == 2 &&
|
.Verify(x => x.Push(It.Is<AlbumSearchCommand>(c => c.AlbumIds.Count == 2 &&
|
||||||
|
@ -120,7 +120,7 @@ namespace NzbDrone.Core.Test.Download
|
||||||
AlbumIds = new List<int> { 1, 2, 3 }
|
AlbumIds = new List<int> { 1, 2, 3 }
|
||||||
};
|
};
|
||||||
|
|
||||||
Subject.HandleAsync(failedEvent);
|
Subject.Handle(failedEvent);
|
||||||
|
|
||||||
Mocker.GetMock<IManageCommandQueue>()
|
Mocker.GetMock<IManageCommandQueue>()
|
||||||
.Verify(x => x.Push(It.Is<ArtistSearchCommand>(c => c.ArtistId == failedEvent.ArtistId),
|
.Verify(x => x.Push(It.Is<ArtistSearchCommand>(c => c.ArtistId == failedEvent.ArtistId),
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
|
using NzbDrone.Core.History;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.Download.TrackedDownloads
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TrackedDownloadAlreadyImportedFixture : CoreTest<TrackedDownloadAlreadyImported>
|
||||||
|
{
|
||||||
|
private List<Album> _albums;
|
||||||
|
private TrackedDownload _trackedDownload;
|
||||||
|
private List<History.History> _historyItems;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_albums = new List<Album>();
|
||||||
|
|
||||||
|
var remoteAlbum = Builder<RemoteAlbum>.CreateNew()
|
||||||
|
.With(r => r.Albums = _albums)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_trackedDownload = Builder<TrackedDownload>.CreateNew()
|
||||||
|
.With(t => t.RemoteAlbum = remoteAlbum)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_historyItems = new List<History.History>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GivenEpisodes(int count)
|
||||||
|
{
|
||||||
|
_albums.AddRange(Builder<Album>.CreateListOfSize(count)
|
||||||
|
.BuildList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GivenHistoryForEpisode(Album episode, params HistoryEventType[] eventTypes)
|
||||||
|
{
|
||||||
|
foreach (var eventType in eventTypes)
|
||||||
|
{
|
||||||
|
_historyItems.Add(
|
||||||
|
Builder<History.History>.CreateNew()
|
||||||
|
.With(h => h.AlbumId = episode.Id)
|
||||||
|
.With(h => h.EventType = eventType)
|
||||||
|
.Build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_false_if_there_is_no_history()
|
||||||
|
{
|
||||||
|
GivenEpisodes(1);
|
||||||
|
|
||||||
|
Subject.IsImported(_trackedDownload, _historyItems)
|
||||||
|
.Should()
|
||||||
|
.BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_false_if_single_episode_download_is_not_imported()
|
||||||
|
{
|
||||||
|
GivenEpisodes(1);
|
||||||
|
|
||||||
|
GivenHistoryForEpisode(_albums[0], HistoryEventType.Grabbed);
|
||||||
|
|
||||||
|
Subject.IsImported(_trackedDownload, _historyItems)
|
||||||
|
.Should()
|
||||||
|
.BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_false_if_no_episode_in_multi_episode_download_is_imported()
|
||||||
|
{
|
||||||
|
GivenEpisodes(2);
|
||||||
|
|
||||||
|
GivenHistoryForEpisode(_albums[0], HistoryEventType.Grabbed);
|
||||||
|
GivenHistoryForEpisode(_albums[1], HistoryEventType.Grabbed);
|
||||||
|
|
||||||
|
Subject.IsImported(_trackedDownload, _historyItems)
|
||||||
|
.Should()
|
||||||
|
.BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_should_return_false_if_only_one_episode_in_multi_episode_download_is_imported()
|
||||||
|
{
|
||||||
|
GivenEpisodes(2);
|
||||||
|
|
||||||
|
GivenHistoryForEpisode(_albums[0], HistoryEventType.DownloadImported, HistoryEventType.Grabbed);
|
||||||
|
GivenHistoryForEpisode(_albums[1], HistoryEventType.Grabbed);
|
||||||
|
|
||||||
|
Subject.IsImported(_trackedDownload, _historyItems)
|
||||||
|
.Should()
|
||||||
|
.BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_true_if_single_episode_download_is_imported()
|
||||||
|
{
|
||||||
|
GivenEpisodes(1);
|
||||||
|
|
||||||
|
GivenHistoryForEpisode(_albums[0], HistoryEventType.DownloadImported, HistoryEventType.Grabbed);
|
||||||
|
|
||||||
|
Subject.IsImported(_trackedDownload, _historyItems)
|
||||||
|
.Should()
|
||||||
|
.BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_true_if_multi_episode_download_is_imported()
|
||||||
|
{
|
||||||
|
GivenEpisodes(2);
|
||||||
|
|
||||||
|
GivenHistoryForEpisode(_albums[0], HistoryEventType.DownloadImported, HistoryEventType.Grabbed);
|
||||||
|
GivenHistoryForEpisode(_albums[1], HistoryEventType.DownloadImported, HistoryEventType.Grabbed);
|
||||||
|
|
||||||
|
Subject.IsImported(_trackedDownload, _historyItems)
|
||||||
|
.Should()
|
||||||
|
.BeTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,7 +49,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
{
|
{
|
||||||
DownloadItem = downloadItem,
|
DownloadItem = downloadItem,
|
||||||
RemoteAlbum = remoteAlbum,
|
RemoteAlbum = remoteAlbum,
|
||||||
State = TrackedDownloadStage.Downloading
|
State = TrackedDownloadState.Downloading
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
{
|
{
|
||||||
DownloadItem = downloadItem,
|
DownloadItem = downloadItem,
|
||||||
RemoteAlbum = remoteAlbum,
|
RemoteAlbum = remoteAlbum,
|
||||||
State = TrackedDownloadStage.Downloading
|
State = TrackedDownloadState.Downloading
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,8 @@ namespace NzbDrone.Core.Test.Messaging.Commands
|
||||||
{
|
{
|
||||||
var commandModel = Builder<CommandModel>
|
var commandModel = Builder<CommandModel>
|
||||||
.CreateNew()
|
.CreateNew()
|
||||||
.With(c => c.Name = "CheckForFinishedDownload")
|
.With(c => c.Name = "ProcessMonitoredDownloads")
|
||||||
.With(c => c.Body = new CheckForFinishedDownloadCommand())
|
.With(c => c.Body = new ProcessMonitoredDownloadsCommand())
|
||||||
.With(c => c.Status = CommandStatus.Started)
|
.With(c => c.Status = CommandStatus.Started)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
@ -56,8 +56,8 @@ namespace NzbDrone.Core.Test.Messaging.Commands
|
||||||
|
|
||||||
var newCommandModel = Builder<CommandModel>
|
var newCommandModel = Builder<CommandModel>
|
||||||
.CreateNew()
|
.CreateNew()
|
||||||
.With(c => c.Name = "CheckForFinishedDownload")
|
.With(c => c.Name = "ProcessMonitoredDownloads")
|
||||||
.With(c => c.Body = new CheckForFinishedDownloadCommand())
|
.With(c => c.Body = new ProcessMonitoredDownloadsCommand())
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
Subject.Add(newCommandModel);
|
Subject.Add(newCommandModel);
|
||||||
|
|
|
@ -41,7 +41,7 @@ namespace NzbDrone.Core.Test.Messaging.Commands
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_remove_commands_for_five_minutes_after_they_end()
|
public void should_not_remove_commands_for_five_minutes_after_they_end()
|
||||||
{
|
{
|
||||||
var command = Subject.Push<CheckForFinishedDownloadCommand>(new CheckForFinishedDownloadCommand());
|
var command = Subject.Push<RefreshMonitoredDownloadsCommand>(new RefreshMonitoredDownloadsCommand());
|
||||||
|
|
||||||
// Start the command to mimic CommandQueue's behaviour
|
// Start the command to mimic CommandQueue's behaviour
|
||||||
command.StartedAt = DateTime.Now;
|
command.StartedAt = DateTime.Now;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Profiles.Releases;
|
using NzbDrone.Core.Profiles.Releases;
|
||||||
|
@ -43,7 +44,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||||
var remoteAlbum = queueItem.RemoteAlbum;
|
var remoteAlbum = queueItem.RemoteAlbum;
|
||||||
var qualityProfile = subject.Artist.QualityProfile.Value;
|
var qualityProfile = subject.Artist.QualityProfile.Value;
|
||||||
|
|
||||||
|
// To avoid a race make sure it's not FailedPending (failed awaiting removal/search).
|
||||||
|
// Failed items (already searching for a replacement) won't be part of the queue since
|
||||||
|
// it's a copy, of the tracked download, not a reference.
|
||||||
|
if (queueItem.TrackedDownloadState == TrackedDownloadState.DownloadFailedPending)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
_logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0}", remoteAlbum.ParsedAlbumInfo.Quality);
|
_logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0}", remoteAlbum.ParsedAlbumInfo.Quality);
|
||||||
|
|
||||||
var queuedItemPreferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Artist, queueItem.Title);
|
var queuedItemPreferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Artist, queueItem.Title);
|
||||||
|
|
||||||
if (!_upgradableSpecification.CutoffNotMet(qualityProfile,
|
if (!_upgradableSpecification.CutoffNotMet(qualityProfile,
|
||||||
|
|
|
@ -4,6 +4,5 @@ namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
public class CheckForFinishedDownloadCommand : Command
|
public class CheckForFinishedDownloadCommand : Command
|
||||||
{
|
{
|
||||||
public override bool RequiresDiskAccess => true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.Download.TrackedDownloads;
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
using NzbDrone.Core.History;
|
using NzbDrone.Core.History;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
@ -12,43 +10,37 @@ using NzbDrone.Core.MediaFiles.Events;
|
||||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Music;
|
using NzbDrone.Core.Music;
|
||||||
using NzbDrone.Core.Parser;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download
|
namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
public interface ICompletedDownloadService
|
public interface ICompletedDownloadService
|
||||||
{
|
{
|
||||||
void Process(TrackedDownload trackedDownload, bool ignoreWarnings = false);
|
void Check(TrackedDownload trackedDownload);
|
||||||
|
void Import(TrackedDownload trackedDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CompletedDownloadService : ICompletedDownloadService
|
public class CompletedDownloadService : ICompletedDownloadService
|
||||||
{
|
{
|
||||||
private readonly IConfigService _configService;
|
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly IHistoryService _historyService;
|
private readonly IHistoryService _historyService;
|
||||||
private readonly IDownloadedTracksImportService _downloadedTracksImportService;
|
private readonly IDownloadedTracksImportService _downloadedTracksImportService;
|
||||||
private readonly IParsingService _parsingService;
|
|
||||||
private readonly Logger _logger;
|
|
||||||
private readonly IArtistService _artistService;
|
private readonly IArtistService _artistService;
|
||||||
|
private readonly ITrackedDownloadAlreadyImported _trackedDownloadAlreadyImported;
|
||||||
|
|
||||||
public CompletedDownloadService(IConfigService configService,
|
public CompletedDownloadService(IEventAggregator eventAggregator,
|
||||||
IEventAggregator eventAggregator,
|
|
||||||
IHistoryService historyService,
|
IHistoryService historyService,
|
||||||
IDownloadedTracksImportService downloadedTracksImportService,
|
IDownloadedTracksImportService downloadedTracksImportService,
|
||||||
IParsingService parsingService,
|
|
||||||
IArtistService artistService,
|
IArtistService artistService,
|
||||||
Logger logger)
|
ITrackedDownloadAlreadyImported trackedDownloadAlreadyImported)
|
||||||
{
|
{
|
||||||
_configService = configService;
|
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_historyService = historyService;
|
_historyService = historyService;
|
||||||
_downloadedTracksImportService = downloadedTracksImportService;
|
_downloadedTracksImportService = downloadedTracksImportService;
|
||||||
_parsingService = parsingService;
|
|
||||||
_logger = logger;
|
|
||||||
_artistService = artistService;
|
_artistService = artistService;
|
||||||
|
_trackedDownloadAlreadyImported = trackedDownloadAlreadyImported;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Process(TrackedDownload trackedDownload, bool ignoreWarnings = false)
|
public void Check(TrackedDownload trackedDownload)
|
||||||
{
|
{
|
||||||
if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed ||
|
if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed ||
|
||||||
trackedDownload.RemoteAlbum == null)
|
trackedDownload.RemoteAlbum == null)
|
||||||
|
@ -56,8 +48,12 @@ namespace NzbDrone.Core.Download
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ignoreWarnings)
|
// Only process tracked downloads that are still downloading
|
||||||
|
if (trackedDownload.State != TrackedDownloadState.Downloading)
|
||||||
{
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId);
|
var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId);
|
||||||
|
|
||||||
if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
|
if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
|
||||||
|
@ -96,13 +92,14 @@ namespace NzbDrone.Core.Download
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trackedDownload.State = TrackedDownloadState.ImportPending;
|
||||||
}
|
}
|
||||||
|
|
||||||
Import(trackedDownload);
|
public void Import(TrackedDownload trackedDownload)
|
||||||
}
|
|
||||||
|
|
||||||
private void Import(TrackedDownload trackedDownload)
|
|
||||||
{
|
{
|
||||||
|
trackedDownload.State = TrackedDownloadState.Importing;
|
||||||
|
|
||||||
var outputPath = trackedDownload.DownloadItem.OutputPath.FullPath;
|
var outputPath = trackedDownload.DownloadItem.OutputPath.FullPath;
|
||||||
var importResults = _downloadedTracksImportService.ProcessPath(outputPath, ImportMode.Auto, trackedDownload.RemoteAlbum.Artist, trackedDownload.DownloadItem);
|
var importResults = _downloadedTracksImportService.ProcessPath(outputPath, ImportMode.Auto, trackedDownload.RemoteAlbum.Artist, trackedDownload.DownloadItem);
|
||||||
|
|
||||||
|
@ -112,17 +109,50 @@ namespace NzbDrone.Core.Download
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (importResults.All(c => c.Result == ImportResultType.Imported)
|
var allTracksImported = importResults.All(c => c.Result == ImportResultType.Imported) ||
|
||||||
|| importResults.Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteAlbum.Albums.Sum(x => x.AlbumReleases.Value.Where(y => y.Monitored).Sum(z => z.TrackCount))))
|
importResults.Count(c => c.Result == ImportResultType.Imported) >=
|
||||||
|
Math.Max(1, trackedDownload.RemoteAlbum.Albums.Sum(x => x.AlbumReleases.Value.Where(y => y.Monitored).Sum(z => z.TrackCount)));
|
||||||
|
|
||||||
|
Console.WriteLine($"allimported: {allTracksImported}");
|
||||||
|
Console.WriteLine($"count: {importResults.Count(c => c.Result == ImportResultType.Imported)}");
|
||||||
|
Console.WriteLine($"max: {Math.Max(1, trackedDownload.RemoteAlbum.Albums.Sum(x => x.AlbumReleases.Value.Where(y => y.Monitored).Sum(z => z.TrackCount)))}");
|
||||||
|
|
||||||
|
if (allTracksImported)
|
||||||
{
|
{
|
||||||
trackedDownload.State = TrackedDownloadStage.Imported;
|
trackedDownload.State = TrackedDownloadState.Imported;
|
||||||
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Double check if all albums were imported by checking the history if at least one
|
||||||
|
// file was imported. This will allow the decision engine to reject already imported
|
||||||
|
// albums and still mark the download complete when all files are imported.
|
||||||
|
if (importResults.Any(c => c.Result == ImportResultType.Imported))
|
||||||
|
{
|
||||||
|
var historyItems = _historyService.FindByDownloadId(trackedDownload.DownloadItem.DownloadId)
|
||||||
|
.OrderByDescending(h => h.Date)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var allTracksImportedInHistory = _trackedDownloadAlreadyImported.IsImported(trackedDownload, historyItems);
|
||||||
|
|
||||||
|
if (allTracksImportedInHistory)
|
||||||
|
{
|
||||||
|
trackedDownload.State = TrackedDownloadState.Imported;
|
||||||
|
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trackedDownload.State = TrackedDownloadState.ImportPending;
|
||||||
|
|
||||||
|
if (importResults.Empty())
|
||||||
|
{
|
||||||
|
trackedDownload.Warn("No files found are eligible for import in {0}", outputPath);
|
||||||
|
}
|
||||||
|
|
||||||
if (importResults.Any(c => c.Result != ImportResultType.Imported))
|
if (importResults.Any(c => c.Result != ImportResultType.Imported))
|
||||||
{
|
{
|
||||||
trackedDownload.State = TrackedDownloadStage.ImportFailed;
|
trackedDownload.State = TrackedDownloadState.ImportFailed;
|
||||||
var statusMessages = importResults
|
var statusMessages = importResults
|
||||||
.Where(v => v.Result != ImportResultType.Imported)
|
.Where(v => v.Result != ImportResultType.Imported)
|
||||||
.Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.Item.Path), v.Errors))
|
.Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.Item.Path), v.Errors))
|
||||||
|
@ -130,6 +160,7 @@ namespace NzbDrone.Core.Download
|
||||||
|
|
||||||
trackedDownload.Warn(statusMessages);
|
trackedDownload.Warn(statusMessages);
|
||||||
_eventAggregator.PublishEvent(new AlbumImportIncompleteEvent(trackedDownload));
|
_eventAggregator.PublishEvent(new AlbumImportIncompleteEvent(trackedDownload));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
66
src/NzbDrone.Core/Download/DownloadProcessingService.cs
Normal file
66
src/NzbDrone.Core/Download/DownloadProcessingService.cs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download
|
||||||
|
{
|
||||||
|
public class DownloadProcessingService : IExecute<ProcessMonitoredDownloadsCommand>
|
||||||
|
{
|
||||||
|
private readonly IConfigService _configService;
|
||||||
|
private readonly ICompletedDownloadService _completedDownloadService;
|
||||||
|
private readonly IFailedDownloadService _failedDownloadService;
|
||||||
|
private readonly ITrackedDownloadService _trackedDownloadService;
|
||||||
|
private readonly IEventAggregator _eventAggregator;
|
||||||
|
|
||||||
|
public DownloadProcessingService(IConfigService configService,
|
||||||
|
ICompletedDownloadService completedDownloadService,
|
||||||
|
IFailedDownloadService failedDownloadService,
|
||||||
|
ITrackedDownloadService trackedDownloadService,
|
||||||
|
IEventAggregator eventAggregator)
|
||||||
|
{
|
||||||
|
_configService = configService;
|
||||||
|
_completedDownloadService = completedDownloadService;
|
||||||
|
_failedDownloadService = failedDownloadService;
|
||||||
|
_trackedDownloadService = trackedDownloadService;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveCompletedDownloads(List<TrackedDownload> trackedDownloads)
|
||||||
|
{
|
||||||
|
foreach (var trackedDownload in trackedDownloads.Where(c => c.DownloadItem.CanBeRemoved && c.State == TrackedDownloadState.Imported))
|
||||||
|
{
|
||||||
|
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(ProcessMonitoredDownloadsCommand message)
|
||||||
|
{
|
||||||
|
var enableCompletedDownloadHandling = _configService.EnableCompletedDownloadHandling;
|
||||||
|
var trackedDownloads = _trackedDownloadService.GetTrackedDownloads();
|
||||||
|
|
||||||
|
foreach (var trackedDownload in trackedDownloads)
|
||||||
|
{
|
||||||
|
if (trackedDownload.State == TrackedDownloadState.DownloadFailedPending)
|
||||||
|
{
|
||||||
|
_failedDownloadService.ProcessFailed(trackedDownload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableCompletedDownloadHandling && trackedDownload.State == TrackedDownloadState.ImportPending)
|
||||||
|
{
|
||||||
|
_completedDownloadService.Import(trackedDownload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableCompletedDownloadHandling && _configService.RemoveCompletedDownloads)
|
||||||
|
{
|
||||||
|
// Remove tracked downloads that are now complete
|
||||||
|
RemoveCompletedDownloads(trackedDownloads);
|
||||||
|
}
|
||||||
|
|
||||||
|
_eventAggregator.PublishEvent(new DownloadsProcessedEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
src/NzbDrone.Core/Download/DownloadsProcessedEvent.cs
Normal file
11
src/NzbDrone.Core/Download/DownloadsProcessedEvent.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download
|
||||||
|
{
|
||||||
|
public class DownloadsProcessedEvent : IEvent
|
||||||
|
{
|
||||||
|
public DownloadsProcessedEvent()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,8 @@ namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
void MarkAsFailed(int historyId, bool skipReDownload = false);
|
void MarkAsFailed(int historyId, bool skipReDownload = false);
|
||||||
void MarkAsFailed(string downloadId, bool skipReDownload = false);
|
void MarkAsFailed(string downloadId, bool skipReDownload = false);
|
||||||
void Process(TrackedDownload trackedDownload);
|
void Check(TrackedDownload trackedDownload);
|
||||||
|
void ProcessFailed(TrackedDownload trackedDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FailedDownloadService : IFailedDownloadService
|
public class FailedDownloadService : IFailedDownloadService
|
||||||
|
@ -52,22 +53,19 @@ namespace NzbDrone.Core.Download
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Process(TrackedDownload trackedDownload)
|
public void Check(TrackedDownload trackedDownload)
|
||||||
{
|
{
|
||||||
string failure = null;
|
// Only process tracked downloads that are still downloading
|
||||||
|
if (trackedDownload.State != TrackedDownloadState.Downloading)
|
||||||
if (trackedDownload.DownloadItem.IsEncrypted)
|
|
||||||
{
|
{
|
||||||
failure = "Encrypted download detected";
|
return;
|
||||||
}
|
|
||||||
else if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed)
|
|
||||||
{
|
|
||||||
failure = trackedDownload.DownloadItem.Message ?? "Failed download detected";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (failure != null)
|
if (trackedDownload.DownloadItem.IsEncrypted ||
|
||||||
|
trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed)
|
||||||
{
|
{
|
||||||
var grabbedItems = _historyService.Find(trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed)
|
var grabbedItems = _historyService
|
||||||
|
.Find(trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (grabbedItems.Empty())
|
if (grabbedItems.Empty())
|
||||||
|
@ -76,11 +74,41 @@ namespace NzbDrone.Core.Download
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
trackedDownload.State = TrackedDownloadStage.DownloadFailed;
|
trackedDownload.State = TrackedDownloadState.DownloadFailedPending;
|
||||||
PublishDownloadFailedEvent(grabbedItems, failure, trackedDownload);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ProcessFailed(TrackedDownload trackedDownload)
|
||||||
|
{
|
||||||
|
if (trackedDownload.State != TrackedDownloadState.DownloadFailedPending)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var grabbedItems = _historyService
|
||||||
|
.Find(trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (grabbedItems.Empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var failure = "Failed download detected";
|
||||||
|
|
||||||
|
if (trackedDownload.DownloadItem.IsEncrypted)
|
||||||
|
{
|
||||||
|
failure = "Encrypted download detected";
|
||||||
|
}
|
||||||
|
else if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed && trackedDownload.DownloadItem.Message.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
failure = trackedDownload.DownloadItem.Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
trackedDownload.State = TrackedDownloadState.DownloadFailed;
|
||||||
|
PublishDownloadFailedEvent(grabbedItems, failure, trackedDownload);
|
||||||
|
}
|
||||||
|
|
||||||
private void PublishDownloadFailedEvent(List<History.History> historyItems, string message, TrackedDownload trackedDownload = null, bool skipReDownload = false)
|
private void PublishDownloadFailedEvent(List<History.History> historyItems, string message, TrackedDownload trackedDownload = null, bool skipReDownload = false)
|
||||||
{
|
{
|
||||||
var historyItem = historyItems.First();
|
var historyItem = historyItems.First();
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download
|
||||||
|
{
|
||||||
|
public class ProcessMonitoredDownloadsCommand : Command
|
||||||
|
{
|
||||||
|
public override bool RequiresDiskAccess => true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,14 @@
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.IndexerSearch;
|
using NzbDrone.Core.IndexerSearch;
|
||||||
|
using NzbDrone.Core.Messaging;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Music;
|
using NzbDrone.Core.Music;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download
|
namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
public class RedownloadFailedDownloadService : IHandleAsync<DownloadFailedEvent>
|
public class RedownloadFailedDownloadService : IHandle<DownloadFailedEvent>
|
||||||
{
|
{
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
private readonly IAlbumService _albumService;
|
private readonly IAlbumService _albumService;
|
||||||
|
@ -25,7 +26,8 @@ namespace NzbDrone.Core.Download
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleAsync(DownloadFailedEvent message)
|
[EventHandleOrder(EventHandleOrder.Last)]
|
||||||
|
public void Handle(DownloadFailedEvent message)
|
||||||
{
|
{
|
||||||
if (message.SkipReDownload)
|
if (message.SkipReDownload)
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download
|
||||||
|
{
|
||||||
|
public class RefreshMonitoredDownloadsCommand : Command
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,9 +11,10 @@ using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download.TrackedDownloads
|
namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
{
|
{
|
||||||
public class DownloadMonitoringService : IExecute<CheckForFinishedDownloadCommand>,
|
public class DownloadMonitoringService : IExecute<RefreshMonitoredDownloadsCommand>,
|
||||||
IHandle<AlbumGrabbedEvent>,
|
IHandle<AlbumGrabbedEvent>,
|
||||||
IHandle<TrackImportedEvent>,
|
IHandle<TrackImportedEvent>,
|
||||||
|
IHandle<DownloadsProcessedEvent>,
|
||||||
IHandle<TrackedDownloadsRemovedEvent>
|
IHandle<TrackedDownloadsRemovedEvent>
|
||||||
{
|
{
|
||||||
private readonly IDownloadClientStatusService _downloadClientStatusService;
|
private readonly IDownloadClientStatusService _downloadClientStatusService;
|
||||||
|
@ -52,7 +53,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
|
|
||||||
private void QueueRefresh()
|
private void QueueRefresh()
|
||||||
{
|
{
|
||||||
_manageCommandQueue.Push(new CheckForFinishedDownloadCommand());
|
_manageCommandQueue.Push(new RefreshMonitoredDownloadsCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Refresh()
|
private void Refresh()
|
||||||
|
@ -73,6 +74,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
|
|
||||||
_trackedDownloadService.UpdateTrackable(trackedDownloads);
|
_trackedDownloadService.UpdateTrackable(trackedDownloads);
|
||||||
_eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(trackedDownloads));
|
_eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(trackedDownloads));
|
||||||
|
_manageCommandQueue.Push(new ProcessMonitoredDownloadsCommand());
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -82,12 +84,12 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
|
|
||||||
private List<TrackedDownload> ProcessClientDownloads(IDownloadClient downloadClient)
|
private List<TrackedDownload> ProcessClientDownloads(IDownloadClient downloadClient)
|
||||||
{
|
{
|
||||||
List<DownloadClientItem> downloadClientHistory = new List<DownloadClientItem>();
|
var downloadClientItems = new List<DownloadClientItem>();
|
||||||
var trackedDownloads = new List<TrackedDownload>();
|
var trackedDownloads = new List<TrackedDownload>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
downloadClientHistory = downloadClient.GetItems().ToList();
|
downloadClientItems = downloadClient.GetItems().ToList();
|
||||||
|
|
||||||
_downloadClientStatusService.RecordSuccess(downloadClient.Definition.Id);
|
_downloadClientStatusService.RecordSuccess(downloadClient.Definition.Id);
|
||||||
}
|
}
|
||||||
|
@ -97,59 +99,40 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
_logger.Warn(ex, "Unable to retrieve queue and history items from " + downloadClient.Definition.Name);
|
_logger.Warn(ex, "Unable to retrieve queue and history items from " + downloadClient.Definition.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var downloadItem in downloadClientHistory)
|
foreach (var downloadItem in downloadClientItems)
|
||||||
{
|
{
|
||||||
var newItems = ProcessClientItems(downloadClient, downloadItem);
|
var item = ProcessClientItem(downloadClient, downloadItem);
|
||||||
trackedDownloads.AddRange(newItems);
|
trackedDownloads.AddIfNotNull(item);
|
||||||
}
|
|
||||||
|
|
||||||
if (_configService.EnableCompletedDownloadHandling && _configService.RemoveCompletedDownloads)
|
|
||||||
{
|
|
||||||
RemoveCompletedDownloads(trackedDownloads);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return trackedDownloads;
|
return trackedDownloads;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveCompletedDownloads(List<TrackedDownload> trackedDownloads)
|
private TrackedDownload ProcessClientItem(IDownloadClient downloadClient, DownloadClientItem downloadItem)
|
||||||
{
|
{
|
||||||
foreach (var trackedDownload in trackedDownloads.Where(c => c.DownloadItem.CanBeRemoved && c.State == TrackedDownloadStage.Imported))
|
|
||||||
{
|
|
||||||
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TrackedDownload> ProcessClientItems(IDownloadClient downloadClient, DownloadClientItem downloadItem)
|
|
||||||
{
|
|
||||||
var trackedDownloads = new List<TrackedDownload>();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var trackedDownload = _trackedDownloadService.TrackDownload((DownloadClientDefinition)downloadClient.Definition, downloadItem);
|
var trackedDownload = _trackedDownloadService.TrackDownload((DownloadClientDefinition)downloadClient.Definition, downloadItem);
|
||||||
if (trackedDownload != null && trackedDownload.State == TrackedDownloadStage.Downloading)
|
if (trackedDownload != null && trackedDownload.State == TrackedDownloadState.Downloading)
|
||||||
{
|
{
|
||||||
_failedDownloadService.Process(trackedDownload);
|
_failedDownloadService.Check(trackedDownload);
|
||||||
|
_completedDownloadService.Check(trackedDownload);
|
||||||
if (_configService.EnableCompletedDownloadHandling)
|
|
||||||
{
|
|
||||||
_completedDownloadService.Process(trackedDownload);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trackedDownloads.AddIfNotNull(trackedDownload);
|
return trackedDownload;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.Error(e, "Couldn't process tracked download {0}", downloadItem.Title);
|
_logger.Error(e, "Couldn't process tracked download {0}", downloadItem.Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
return trackedDownloads;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool DownloadIsTrackable(TrackedDownload trackedDownload)
|
private bool DownloadIsTrackable(TrackedDownload trackedDownload)
|
||||||
{
|
{
|
||||||
// If the download has already been imported or failed don't track it
|
// If the download has already been imported or failed don't track it
|
||||||
if (trackedDownload.State == TrackedDownloadStage.DownloadFailed
|
if (trackedDownload.State == TrackedDownloadState.Imported || trackedDownload.State == TrackedDownloadState.DownloadFailed)
|
||||||
|| trackedDownload.State == TrackedDownloadStage.Imported)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -163,8 +146,14 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Execute(RefreshMonitoredDownloadsCommand message)
|
||||||
|
{
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
public void Execute(CheckForFinishedDownloadCommand message)
|
public void Execute(CheckForFinishedDownloadCommand message)
|
||||||
{
|
{
|
||||||
|
_logger.Warn("A third party app used the deprecated CheckForFinishedDownload command, it should be updated RefreshMonitoredDownloads instead");
|
||||||
Refresh();
|
Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,6 +167,13 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
_refreshDebounce.Execute();
|
_refreshDebounce.Execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Handle(DownloadsProcessedEvent message)
|
||||||
|
{
|
||||||
|
var trackedDownloads = _trackedDownloadService.GetTrackedDownloads().Where(t => t.IsTrackable && DownloadIsTrackable(t)).ToList();
|
||||||
|
|
||||||
|
_eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(trackedDownloads));
|
||||||
|
}
|
||||||
|
|
||||||
public void Handle(TrackedDownloadsRemovedEvent message)
|
public void Handle(TrackedDownloadsRemovedEvent message)
|
||||||
{
|
{
|
||||||
var trackedDownloads = _trackedDownloadService.GetTrackedDownloads().Where(t => t.IsTrackable && DownloadIsTrackable(t)).ToList();
|
var trackedDownloads = _trackedDownloadService.GetTrackedDownloads().Where(t => t.IsTrackable && DownloadIsTrackable(t)).ToList();
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
{
|
{
|
||||||
public int DownloadClient { get; set; }
|
public int DownloadClient { get; set; }
|
||||||
public DownloadClientItem DownloadItem { get; set; }
|
public DownloadClientItem DownloadItem { get; set; }
|
||||||
public TrackedDownloadStage State { get; set; }
|
public TrackedDownloadState State { get; set; }
|
||||||
public TrackedDownloadStatus Status { get; private set; }
|
public TrackedDownloadStatus Status { get; private set; }
|
||||||
public RemoteAlbum RemoteAlbum { get; set; }
|
public RemoteAlbum RemoteAlbum { get; set; }
|
||||||
public TrackedDownloadStatusMessage[] StatusMessages { get; private set; }
|
public TrackedDownloadStatusMessage[] StatusMessages { get; private set; }
|
||||||
|
@ -33,10 +33,12 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TrackedDownloadStage
|
public enum TrackedDownloadState
|
||||||
{
|
{
|
||||||
Downloading,
|
Downloading,
|
||||||
DownloadFailed,
|
DownloadFailed,
|
||||||
|
DownloadFailedPending,
|
||||||
|
ImportPending,
|
||||||
Importing,
|
Importing,
|
||||||
ImportFailed,
|
ImportFailed,
|
||||||
Imported
|
Imported
|
||||||
|
@ -45,6 +47,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
public enum TrackedDownloadStatus
|
public enum TrackedDownloadStatus
|
||||||
{
|
{
|
||||||
Ok,
|
Ok,
|
||||||
Warning
|
Warning,
|
||||||
|
Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.History;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
|
{
|
||||||
|
public interface ITrackedDownloadAlreadyImported
|
||||||
|
{
|
||||||
|
bool IsImported(TrackedDownload trackedDownload, List<History.History> historyItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TrackedDownloadAlreadyImported : ITrackedDownloadAlreadyImported
|
||||||
|
{
|
||||||
|
public bool IsImported(TrackedDownload trackedDownload, List<History.History> historyItems)
|
||||||
|
{
|
||||||
|
if (historyItems.Empty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trackedDownload.RemoteAlbum == null || trackedDownload.RemoteAlbum.Albums == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var allAlbumsImportedInHistory = trackedDownload.RemoteAlbum.Albums.All(album =>
|
||||||
|
{
|
||||||
|
var lastHistoryItem = historyItems.FirstOrDefault(h => h.AlbumId == album.Id);
|
||||||
|
|
||||||
|
if (lastHistoryItem == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new[] { HistoryEventType.DownloadImported, HistoryEventType.TrackFileImported }.Contains(lastHistoryItem.EventType);
|
||||||
|
});
|
||||||
|
|
||||||
|
return allAlbumsImportedInHistory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
private readonly IParsingService _parsingService;
|
private readonly IParsingService _parsingService;
|
||||||
private readonly IHistoryService _historyService;
|
private readonly IHistoryService _historyService;
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
|
private readonly ITrackedDownloadAlreadyImported _trackedDownloadAlreadyImported;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
private readonly ICached<TrackedDownload> _cache;
|
private readonly ICached<TrackedDownload> _cache;
|
||||||
|
|
||||||
|
@ -35,11 +36,13 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
ICacheManager cacheManager,
|
ICacheManager cacheManager,
|
||||||
IHistoryService historyService,
|
IHistoryService historyService,
|
||||||
IEventAggregator eventAggregator,
|
IEventAggregator eventAggregator,
|
||||||
|
ITrackedDownloadAlreadyImported trackedDownloadAlreadyImported,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
_parsingService = parsingService;
|
_parsingService = parsingService;
|
||||||
_historyService = historyService;
|
_historyService = historyService;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
|
_trackedDownloadAlreadyImported = trackedDownloadAlreadyImported;
|
||||||
_cache = cacheManager.GetCache<TrackedDownload>(GetType());
|
_cache = cacheManager.GetCache<TrackedDownload>(GetType());
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
@ -93,7 +96,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
{
|
{
|
||||||
var existingItem = Find(downloadItem.DownloadId);
|
var existingItem = Find(downloadItem.DownloadId);
|
||||||
|
|
||||||
if (existingItem != null && existingItem.State != TrackedDownloadStage.Downloading)
|
if (existingItem != null && existingItem.State != TrackedDownloadState.Downloading)
|
||||||
{
|
{
|
||||||
LogItemChange(existingItem, existingItem.DownloadItem, downloadItem);
|
LogItemChange(existingItem, existingItem.DownloadItem, downloadItem);
|
||||||
|
|
||||||
|
@ -114,7 +117,9 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var parsedAlbumInfo = Parser.Parser.ParseAlbumTitle(trackedDownload.DownloadItem.Title);
|
var parsedAlbumInfo = Parser.Parser.ParseAlbumTitle(trackedDownload.DownloadItem.Title);
|
||||||
var historyItems = _historyService.FindByDownloadId(downloadItem.DownloadId);
|
var historyItems = _historyService.FindByDownloadId(downloadItem.DownloadId)
|
||||||
|
.OrderByDescending(h => h.Date)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
if (parsedAlbumInfo != null)
|
if (parsedAlbumInfo != null)
|
||||||
{
|
{
|
||||||
|
@ -123,8 +128,22 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
|
|
||||||
if (historyItems.Any())
|
if (historyItems.Any())
|
||||||
{
|
{
|
||||||
var firstHistoryItem = historyItems.OrderByDescending(h => h.Date).First();
|
var firstHistoryItem = historyItems.First();
|
||||||
trackedDownload.State = GetStateFromHistory(firstHistoryItem);
|
var state = GetStateFromHistory(firstHistoryItem);
|
||||||
|
|
||||||
|
// One potential issue here is if the latest is imported, but other episodes are ignored or never imported.
|
||||||
|
// It's unlikely that will happen, but could happen if additional episodes are added to season after it's already imported.
|
||||||
|
if (state == TrackedDownloadState.Imported)
|
||||||
|
{
|
||||||
|
var allImported = _trackedDownloadAlreadyImported.IsImported(trackedDownload, historyItems);
|
||||||
|
|
||||||
|
trackedDownload.State = allImported ? TrackedDownloadState.Imported : TrackedDownloadState.Downloading;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trackedDownload.State = state;
|
||||||
|
}
|
||||||
|
|
||||||
if (firstHistoryItem.EventType == HistoryEventType.AlbumImportIncomplete)
|
if (firstHistoryItem.EventType == HistoryEventType.AlbumImportIncomplete)
|
||||||
{
|
{
|
||||||
var messages = Json.Deserialize<List<TrackedDownloadStatusMessage>>(firstHistoryItem?.Data["statusMessages"]).ToArray();
|
var messages = Json.Deserialize<List<TrackedDownloadStatusMessage>>(firstHistoryItem?.Data["statusMessages"]).ToArray();
|
||||||
|
@ -223,25 +242,25 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TrackedDownloadStage GetStateFromHistory(NzbDrone.Core.History.History history)
|
private static TrackedDownloadState GetStateFromHistory(NzbDrone.Core.History.History history)
|
||||||
{
|
{
|
||||||
switch (history.EventType)
|
switch (history.EventType)
|
||||||
{
|
{
|
||||||
case HistoryEventType.AlbumImportIncomplete:
|
case HistoryEventType.AlbumImportIncomplete:
|
||||||
return TrackedDownloadStage.ImportFailed;
|
return TrackedDownloadState.ImportFailed;
|
||||||
case HistoryEventType.DownloadImported:
|
case HistoryEventType.DownloadImported:
|
||||||
return TrackedDownloadStage.Imported;
|
return TrackedDownloadState.Imported;
|
||||||
case HistoryEventType.DownloadFailed:
|
case HistoryEventType.DownloadFailed:
|
||||||
return TrackedDownloadStage.DownloadFailed;
|
return TrackedDownloadState.DownloadFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since DownloadComplete is a new event type, we can't assume it exists for old downloads
|
// Since DownloadComplete is a new event type, we can't assume it exists for old downloads
|
||||||
if (history.EventType == HistoryEventType.TrackFileImported)
|
if (history.EventType == HistoryEventType.TrackFileImported)
|
||||||
{
|
{
|
||||||
return DateTime.UtcNow.Subtract(history.Date).TotalSeconds < 60 ? TrackedDownloadStage.Importing : TrackedDownloadStage.Imported;
|
return DateTime.UtcNow.Subtract(history.Date).TotalSeconds < 60 ? TrackedDownloadState.Importing : TrackedDownloadState.Imported;
|
||||||
}
|
}
|
||||||
|
|
||||||
return TrackedDownloadStage.Downloading;
|
return TrackedDownloadState.Downloading;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Handle(AlbumDeletedEvent message)
|
public void Handle(AlbumDeletedEvent message)
|
||||||
|
|
|
@ -61,7 +61,7 @@ namespace NzbDrone.Core.Jobs
|
||||||
{
|
{
|
||||||
var defaultTasks = new[]
|
var defaultTasks = new[]
|
||||||
{
|
{
|
||||||
new ScheduledTask { Interval = 1, TypeName = typeof(CheckForFinishedDownloadCommand).FullName },
|
new ScheduledTask { Interval = 1, TypeName = typeof(RefreshMonitoredDownloadsCommand).FullName },
|
||||||
new ScheduledTask { Interval = 5, TypeName = typeof(MessagingCleanupCommand).FullName },
|
new ScheduledTask { Interval = 5, TypeName = typeof(MessagingCleanupCommand).FullName },
|
||||||
new ScheduledTask { Interval = 6 * 60, TypeName = typeof(ApplicationUpdateCheckCommand).FullName },
|
new ScheduledTask { Interval = 6 * 60, TypeName = typeof(ApplicationUpdateCheckCommand).FullName },
|
||||||
new ScheduledTask { Interval = 6 * 60, TypeName = typeof(CheckHealthCommand).FullName },
|
new ScheduledTask { Interval = 6 * 60, TypeName = typeof(CheckHealthCommand).FullName },
|
||||||
|
|
|
@ -375,7 +375,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
||||||
|
|
||||||
if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteAlbum.Albums.Count))
|
if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteAlbum.Albums.Count))
|
||||||
{
|
{
|
||||||
trackedDownload.State = TrackedDownloadStage.Imported;
|
trackedDownload.State = TrackedDownloadState.Imported;
|
||||||
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.History;
|
||||||
|
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
||||||
|
{
|
||||||
|
public class AlreadyImportedSpecification : IImportDecisionEngineSpecification<LocalAlbumRelease>
|
||||||
|
{
|
||||||
|
private readonly IHistoryService _historyService;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public AlreadyImportedSpecification(IHistoryService historyService,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_historyService = historyService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpecificationPriority Priority => SpecificationPriority.Database;
|
||||||
|
|
||||||
|
public Decision IsSatisfiedBy(LocalAlbumRelease localAlbumRelease, DownloadClientItem downloadClientItem)
|
||||||
|
{
|
||||||
|
if (downloadClientItem == null)
|
||||||
|
{
|
||||||
|
_logger.Debug("No download client information is available, skipping");
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
var albumRelease = localAlbumRelease.AlbumRelease;
|
||||||
|
|
||||||
|
if (!albumRelease.Tracks.Value.Any(x => x.HasFile))
|
||||||
|
{
|
||||||
|
_logger.Debug("Skipping already imported check for album without files");
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
var albumHistory = _historyService.GetByAlbum(albumRelease.AlbumId, null);
|
||||||
|
var lastImported = albumHistory.FirstOrDefault(h => h.EventType == HistoryEventType.DownloadImported);
|
||||||
|
var lastGrabbed = albumHistory.FirstOrDefault(h => h.EventType == HistoryEventType.Grabbed);
|
||||||
|
|
||||||
|
if (lastImported == null)
|
||||||
|
{
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastGrabbed != null && lastGrabbed.Date.After(lastImported.Date))
|
||||||
|
{
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastImported.DownloadId == downloadClientItem.DownloadId)
|
||||||
|
{
|
||||||
|
_logger.Debug("Album previously imported at {0}", lastImported.Date);
|
||||||
|
return Decision.Reject("Album already imported at {0}", lastImported.Date);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,16 +21,12 @@ namespace NzbDrone.Core.Messaging.Events
|
||||||
private class EventSubscribers<TEvent>
|
private class EventSubscribers<TEvent>
|
||||||
where TEvent : class, IEvent
|
where TEvent : class, IEvent
|
||||||
{
|
{
|
||||||
private IServiceFactory _serviceFactory;
|
|
||||||
|
|
||||||
public IHandle<TEvent>[] _syncHandlers;
|
public IHandle<TEvent>[] _syncHandlers;
|
||||||
public IHandleAsync<TEvent>[] _asyncHandlers;
|
public IHandleAsync<TEvent>[] _asyncHandlers;
|
||||||
public IHandleAsync<IEvent>[] _globalHandlers;
|
public IHandleAsync<IEvent>[] _globalHandlers;
|
||||||
|
|
||||||
public EventSubscribers(IServiceFactory serviceFactory)
|
public EventSubscribers(IServiceFactory serviceFactory)
|
||||||
{
|
{
|
||||||
_serviceFactory = serviceFactory;
|
|
||||||
|
|
||||||
_syncHandlers = serviceFactory.BuildAll<IHandle<TEvent>>()
|
_syncHandlers = serviceFactory.BuildAll<IHandle<TEvent>>()
|
||||||
.OrderBy(GetEventHandleOrder)
|
.OrderBy(GetEventHandleOrder)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
@ -144,8 +140,7 @@ namespace NzbDrone.Core.Messaging.Events
|
||||||
internal static int GetEventHandleOrder<TEvent>(IHandle<TEvent> eventHandler)
|
internal static int GetEventHandleOrder<TEvent>(IHandle<TEvent> eventHandler)
|
||||||
where TEvent : class, IEvent
|
where TEvent : class, IEvent
|
||||||
{
|
{
|
||||||
// TODO: Convert "Handle" to nameof(eventHandler.Handle) after .net 4.5
|
var method = eventHandler.GetType().GetMethod(nameof(eventHandler.Handle), new Type[] { typeof(TEvent) });
|
||||||
var method = eventHandler.GetType().GetMethod("Handle", new Type[] { typeof(TEvent) });
|
|
||||||
|
|
||||||
if (method == null)
|
if (method == null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,7 +20,8 @@ namespace NzbDrone.Core.Queue
|
||||||
public TimeSpan? Timeleft { get; set; }
|
public TimeSpan? Timeleft { get; set; }
|
||||||
public DateTime? EstimatedCompletionTime { get; set; }
|
public DateTime? EstimatedCompletionTime { get; set; }
|
||||||
public string Status { get; set; }
|
public string Status { get; set; }
|
||||||
public string TrackedDownloadStatus { get; set; }
|
public TrackedDownloadStatus? TrackedDownloadStatus { get; set; }
|
||||||
|
public TrackedDownloadState? TrackedDownloadState { get; set; }
|
||||||
public List<TrackedDownloadStatusMessage> StatusMessages { get; set; }
|
public List<TrackedDownloadStatusMessage> StatusMessages { get; set; }
|
||||||
public string DownloadId { get; set; }
|
public string DownloadId { get; set; }
|
||||||
public RemoteAlbum RemoteAlbum { get; set; }
|
public RemoteAlbum RemoteAlbum { get; set; }
|
||||||
|
|
|
@ -79,7 +79,8 @@ namespace NzbDrone.Core.Queue
|
||||||
Sizeleft = trackedDownload.DownloadItem.RemainingSize,
|
Sizeleft = trackedDownload.DownloadItem.RemainingSize,
|
||||||
Timeleft = trackedDownload.DownloadItem.RemainingTime,
|
Timeleft = trackedDownload.DownloadItem.RemainingTime,
|
||||||
Status = trackedDownload.DownloadItem.Status.ToString(),
|
Status = trackedDownload.DownloadItem.Status.ToString(),
|
||||||
TrackedDownloadStatus = trackedDownload.Status.ToString(),
|
TrackedDownloadStatus = trackedDownload.Status,
|
||||||
|
TrackedDownloadState = trackedDownload.State,
|
||||||
StatusMessages = trackedDownload.StatusMessages.ToList(),
|
StatusMessages = trackedDownload.StatusMessages.ToList(),
|
||||||
ErrorMessage = trackedDownload.DownloadItem.Message,
|
ErrorMessage = trackedDownload.DownloadItem.Message,
|
||||||
RemoteAlbum = trackedDownload.RemoteAlbum,
|
RemoteAlbum = trackedDownload.RemoteAlbum,
|
||||||
|
|
|
@ -89,8 +89,8 @@ namespace NzbDrone.App.Test
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_same_instance_of_singletons_by_different_interfaces()
|
public void should_return_same_instance_of_singletons_by_different_interfaces()
|
||||||
{
|
{
|
||||||
var first = _container.ResolveAll<IHandle<TrackedDownloadsRemovedEvent>>().OfType<DownloadMonitoringService>().Single();
|
var first = _container.ResolveAll<IHandle<AlbumGrabbedEvent>>().OfType<DownloadMonitoringService>().Single();
|
||||||
var second = (DownloadMonitoringService)_container.Resolve<IExecute<CheckForFinishedDownloadCommand>>();
|
var second = (DownloadMonitoringService)_container.Resolve<IExecute<RefreshMonitoredDownloadsCommand>>();
|
||||||
|
|
||||||
first.Should().BeSameAs(second);
|
first.Should().BeSameAs(second);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue