2017-09-04 02:20:56 +00:00
using NLog ;
2014-12-26 13:55:35 +00:00
using NzbDrone.Common.Crypto ;
2014-12-02 06:26:25 +00:00
using NzbDrone.Common.Extensions ;
2014-12-15 23:40:53 +00:00
using NzbDrone.Core.Configuration ;
2014-06-08 08:22:55 +00:00
using NzbDrone.Core.DecisionEngine ;
2014-12-03 00:47:45 +00:00
using NzbDrone.Core.Indexers ;
2015-02-22 21:09:06 +00:00
using NzbDrone.Core.Jobs ;
2014-06-08 08:22:55 +00:00
using NzbDrone.Core.Messaging.Events ;
using NzbDrone.Core.Parser ;
using NzbDrone.Core.Parser.Model ;
2014-11-24 00:07:46 +00:00
using NzbDrone.Core.Profiles.Delay ;
2014-06-08 08:22:55 +00:00
using NzbDrone.Core.Qualities ;
2017-08-14 02:58:42 +00:00
using NzbDrone.Core.Music ;
using NzbDrone.Core.Music.Events ;
using System ;
using System.Collections.Generic ;
using System.Linq ;
2014-06-08 08:22:55 +00:00
namespace NzbDrone.Core.Download.Pending
{
public interface IPendingReleaseService
{
2017-10-27 03:21:06 +00:00
void Add ( DownloadDecision decision , PendingReleaseReason reason ) ;
2014-06-08 08:22:55 +00:00
List < ReleaseInfo > GetPending ( ) ;
2017-08-14 02:58:42 +00:00
List < RemoteAlbum > GetPendingRemoteAlbums ( int artistId ) ;
2014-06-08 08:22:55 +00:00
List < Queue . Queue > GetPendingQueue ( ) ;
2014-12-03 00:47:45 +00:00
Queue . Queue FindPendingQueueItem ( int queueId ) ;
2015-07-10 07:18:32 +00:00
void RemovePendingQueueItems ( int queueId ) ;
2017-08-14 02:58:42 +00:00
RemoteAlbum OldestPendingRelease ( int artistId , IEnumerable < int > albumIds ) ;
2014-06-08 08:22:55 +00:00
}
2014-12-03 00:47:45 +00:00
public class PendingReleaseService : IPendingReleaseService ,
2017-08-14 02:58:42 +00:00
IHandle < ArtistDeletedEvent > ,
IHandle < AlbumGrabbedEvent > ,
2014-12-03 00:47:45 +00:00
IHandle < RssSyncCompleteEvent >
2014-06-08 08:22:55 +00:00
{
2015-06-27 09:43:17 +00:00
private readonly IIndexerStatusService _indexerStatusService ;
2014-06-08 08:22:55 +00:00
private readonly IPendingReleaseRepository _repository ;
2017-08-14 02:58:42 +00:00
private readonly IArtistService _artistService ;
2014-06-08 08:22:55 +00:00
private readonly IParsingService _parsingService ;
2014-11-24 00:07:46 +00:00
private readonly IDelayProfileService _delayProfileService ;
2015-02-22 21:09:06 +00:00
private readonly ITaskManager _taskManager ;
2014-12-15 23:40:53 +00:00
private readonly IConfigService _configService ;
2014-06-08 08:22:55 +00:00
private readonly IEventAggregator _eventAggregator ;
private readonly Logger _logger ;
2015-06-27 09:43:17 +00:00
public PendingReleaseService ( IIndexerStatusService indexerStatusService ,
IPendingReleaseRepository repository ,
2017-08-14 02:58:42 +00:00
IArtistService artistService ,
2014-06-08 08:22:55 +00:00
IParsingService parsingService ,
2014-11-24 00:07:46 +00:00
IDelayProfileService delayProfileService ,
2015-02-22 21:09:06 +00:00
ITaskManager taskManager ,
2014-12-15 23:40:53 +00:00
IConfigService configService ,
2014-06-08 08:22:55 +00:00
IEventAggregator eventAggregator ,
Logger logger )
{
2015-06-27 09:43:17 +00:00
_indexerStatusService = indexerStatusService ;
2014-06-08 08:22:55 +00:00
_repository = repository ;
2017-08-14 02:58:42 +00:00
_artistService = artistService ;
2014-06-08 08:22:55 +00:00
_parsingService = parsingService ;
2014-11-24 00:07:46 +00:00
_delayProfileService = delayProfileService ;
2015-02-22 21:09:06 +00:00
_taskManager = taskManager ;
2014-12-15 23:40:53 +00:00
_configService = configService ;
2014-06-08 08:22:55 +00:00
_eventAggregator = eventAggregator ;
_logger = logger ;
}
2015-05-12 06:32:58 +00:00
2017-10-27 03:21:06 +00:00
public void Add ( DownloadDecision decision , PendingReleaseReason reason )
2014-06-08 08:22:55 +00:00
{
var alreadyPending = GetPendingReleases ( ) ;
2017-08-14 02:58:42 +00:00
var albumIds = decision . RemoteAlbum . Albums . Select ( e = > e . Id ) ;
2014-06-08 08:22:55 +00:00
2017-08-14 02:58:42 +00:00
var existingReports = alreadyPending . Where ( r = > r . RemoteAlbum . Albums . Select ( e = > e . Id )
. Intersect ( albumIds )
2014-06-08 08:22:55 +00:00
. Any ( ) ) ;
2017-10-27 03:21:06 +00:00
var matchingReports = existingReports . Where ( MatchingReleasePredicate ( decision . RemoteAlbum . Release ) ) . ToList ( ) ;
if ( matchingReports . Any ( ) )
2014-06-08 08:22:55 +00:00
{
2017-10-27 03:21:06 +00:00
var sameReason = true ;
foreach ( var matchingReport in matchingReports )
{
if ( matchingReport . Reason ! = reason )
{
2017-10-29 02:57:03 +00:00
_logger . Debug ( "The release {0} is already pending with reason {1}, changing to {2}" , decision . RemoteAlbum , matchingReport . Reason , reason ) ; matchingReport . Reason = reason ;
2017-10-27 03:21:06 +00:00
_repository . Update ( matchingReport ) ;
sameReason = false ;
}
}
if ( sameReason )
{
2017-10-29 02:57:03 +00:00
_logger . Debug ( "The release {0} is already pending with reason {1}, not adding again" , decision . RemoteAlbum , reason ) ; return ;
2017-10-27 03:21:06 +00:00
}
2014-06-08 08:22:55 +00:00
}
2017-10-29 02:57:03 +00:00
_logger . Debug ( "Adding release {0} to pending releases with reason {1}" , decision . RemoteAlbum , reason ) ; Insert ( decision , reason ) ;
2014-06-08 08:22:55 +00:00
}
public List < ReleaseInfo > GetPending ( )
{
2015-06-27 09:43:17 +00:00
var releases = _repository . All ( ) . Select ( p = > p . Release ) . ToList ( ) ;
if ( releases . Any ( ) )
{
releases = FilterBlockedIndexers ( releases ) ;
}
return releases ;
}
private List < ReleaseInfo > FilterBlockedIndexers ( List < ReleaseInfo > releases )
{
2017-10-27 03:21:06 +00:00
var blockedIndexers = new HashSet < int > ( _indexerStatusService . GetBlockedProviders ( ) . Select ( v = > v . ProviderId ) ) ;
2015-06-27 09:43:17 +00:00
return releases . Where ( release = > ! blockedIndexers . Contains ( release . IndexerId ) ) . ToList ( ) ;
2014-06-08 08:22:55 +00:00
}
2017-08-14 02:58:42 +00:00
public List < RemoteAlbum > GetPendingRemoteAlbums ( int artistId )
2014-06-08 08:22:55 +00:00
{
2017-08-14 02:58:42 +00:00
return _repository . AllByArtistId ( artistId ) . Select ( GetRemoteAlbum ) . ToList ( ) ;
2014-06-08 08:22:55 +00:00
}
public List < Queue . Queue > GetPendingQueue ( )
{
var queued = new List < Queue . Queue > ( ) ;
2015-02-22 21:09:06 +00:00
var nextRssSync = new Lazy < DateTime > ( ( ) = > _taskManager . GetNextExecution ( typeof ( RssSyncCommand ) ) ) ;
2017-10-27 03:21:06 +00:00
foreach ( var pendingRelease in GetPendingReleases ( ) . Where ( p = > p . Reason ! = PendingReleaseReason . Fallback ) )
2014-06-08 08:22:55 +00:00
{
2017-08-14 02:58:42 +00:00
foreach ( var album in pendingRelease . RemoteAlbum . Albums )
2014-06-08 08:22:55 +00:00
{
2017-08-14 02:58:42 +00:00
var ect = pendingRelease . Release . PublishDate . AddMinutes ( GetDelay ( pendingRelease . RemoteAlbum ) ) ;
2014-08-02 01:08:32 +00:00
2015-02-22 21:09:06 +00:00
if ( ect < nextRssSync . Value )
{
ect = nextRssSync . Value ;
}
else
{
ect = ect . AddMinutes ( _configService . RssSyncInterval ) ;
}
2017-10-27 03:21:06 +00:00
var timeleft = ect . Subtract ( DateTime . UtcNow ) ;
if ( timeleft . TotalSeconds < 0 )
{
timeleft = TimeSpan . Zero ;
}
2014-06-08 08:22:55 +00:00
var queue = new Queue . Queue
{
2017-08-14 02:58:42 +00:00
Id = GetQueueId ( pendingRelease , album ) ,
Artist = pendingRelease . RemoteAlbum . Artist ,
Album = album ,
Quality = pendingRelease . RemoteAlbum . ParsedAlbumInfo . Quality ,
2014-06-08 08:22:55 +00:00
Title = pendingRelease . Title ,
2017-08-14 02:58:42 +00:00
Size = pendingRelease . RemoteAlbum . Release . Size ,
Sizeleft = pendingRelease . RemoteAlbum . Release . Size ,
RemoteAlbum = pendingRelease . RemoteAlbum ,
2017-10-27 03:21:06 +00:00
Timeleft = timeleft ,
2014-08-02 01:08:32 +00:00
EstimatedCompletionTime = ect ,
2017-10-27 03:21:06 +00:00
Status = pendingRelease . Reason . ToString ( ) ,
2017-09-04 02:20:56 +00:00
Protocol = pendingRelease . RemoteAlbum . Release . DownloadProtocol ,
Indexer = pendingRelease . RemoteAlbum . Release . Indexer
} ;
2017-10-27 03:21:06 +00:00
2014-06-08 08:22:55 +00:00
queued . Add ( queue ) ;
}
}
2015-03-12 23:41:07 +00:00
//Return best quality release for each episode
2017-08-14 02:58:42 +00:00
var deduped = queued . GroupBy ( q = > q . Album . Id ) . Select ( g = >
2015-03-12 23:41:07 +00:00
{
2017-08-14 02:58:42 +00:00
var artist = g . First ( ) . Artist ;
2015-03-12 23:41:07 +00:00
2017-08-14 02:58:42 +00:00
return g . OrderByDescending ( e = > e . Quality , new QualityModelComparer ( artist . Profile ) )
. ThenBy ( q = > PrioritizeDownloadProtocol ( q . Artist , q . Protocol ) )
2015-03-12 23:41:07 +00:00
. First ( ) ;
} ) ;
return deduped . ToList ( ) ;
2014-06-08 08:22:55 +00:00
}
2014-12-03 00:47:45 +00:00
public Queue . Queue FindPendingQueueItem ( int queueId )
{
return GetPendingQueue ( ) . SingleOrDefault ( p = > p . Id = = queueId ) ;
}
2015-05-12 06:32:58 +00:00
public void RemovePendingQueueItems ( int queueId )
2014-12-03 00:47:45 +00:00
{
2015-07-10 07:18:32 +00:00
var targetItem = FindPendingRelease ( queueId ) ;
2017-08-14 02:58:42 +00:00
var artistReleases = _repository . AllByArtistId ( targetItem . ArtistId ) ;
2015-05-12 06:32:58 +00:00
2017-08-14 02:58:42 +00:00
var releasesToRemove = artistReleases . Where (
c = > c . ParsedAlbumInfo . AlbumTitle = = targetItem . ParsedAlbumInfo . AlbumTitle ) ;
2014-12-03 00:47:45 +00:00
2015-05-12 06:32:58 +00:00
_repository . DeleteMany ( releasesToRemove . Select ( c = > c . Id ) ) ;
2014-12-03 00:47:45 +00:00
}
2017-08-14 02:58:42 +00:00
public RemoteAlbum OldestPendingRelease ( int artistId , IEnumerable < int > albumIds )
2014-11-24 00:07:46 +00:00
{
2017-08-14 02:58:42 +00:00
return GetPendingRemoteAlbums ( artistId ) . Where ( r = > r . Albums . Select ( e = > e . Id ) . Intersect ( albumIds ) . Any ( ) )
2015-07-10 07:18:32 +00:00
. OrderByDescending ( p = > p . Release . AgeHours )
. FirstOrDefault ( ) ;
2014-11-24 00:07:46 +00:00
}
2014-06-08 08:22:55 +00:00
private List < PendingRelease > GetPendingReleases ( )
{
var result = new List < PendingRelease > ( ) ;
foreach ( var release in _repository . All ( ) )
{
2017-08-14 02:58:42 +00:00
var remoteAlbum = GetRemoteAlbum ( release ) ;
2014-06-08 08:22:55 +00:00
2017-08-14 02:58:42 +00:00
if ( remoteAlbum = = null )
{
continue ;
}
2014-06-08 08:22:55 +00:00
2017-08-14 02:58:42 +00:00
release . RemoteAlbum = remoteAlbum ;
2014-06-08 08:22:55 +00:00
result . Add ( release ) ;
}
return result ;
}
2017-08-14 02:58:42 +00:00
private RemoteAlbum GetRemoteAlbum ( PendingRelease release )
2014-06-08 08:22:55 +00:00
{
2017-08-14 02:58:42 +00:00
var artist = _artistService . GetArtist ( release . ArtistId ) ;
2014-06-08 08:22:55 +00:00
//Just in case the series was removed, but wasn't cleaned up yet (housekeeper will clean it up)
2017-08-14 02:58:42 +00:00
if ( artist = = null )
{
return null ;
}
2014-06-08 08:22:55 +00:00
2017-08-14 02:58:42 +00:00
var album = _parsingService . GetAlbums ( release . ParsedAlbumInfo , artist ) ;
2014-06-08 08:22:55 +00:00
2017-08-14 02:58:42 +00:00
return new RemoteAlbum
2014-06-08 08:22:55 +00:00
{
2017-08-14 02:58:42 +00:00
Artist = artist ,
Albums = album ,
ParsedAlbumInfo = release . ParsedAlbumInfo ,
2014-06-08 08:22:55 +00:00
Release = release . Release
} ;
}
2017-10-27 03:21:06 +00:00
private void Insert ( DownloadDecision decision , PendingReleaseReason reason )
2014-06-08 08:22:55 +00:00
{
_repository . Insert ( new PendingRelease
{
2017-08-14 02:58:42 +00:00
ArtistId = decision . RemoteAlbum . Artist . Id ,
ParsedAlbumInfo = decision . RemoteAlbum . ParsedAlbumInfo ,
Release = decision . RemoteAlbum . Release ,
Title = decision . RemoteAlbum . Release . Title ,
2017-10-27 03:21:06 +00:00
Added = DateTime . UtcNow ,
Reason = reason
2014-06-08 08:22:55 +00:00
} ) ;
_eventAggregator . PublishEvent ( new PendingReleasesUpdatedEvent ( ) ) ;
}
private void Delete ( PendingRelease pendingRelease )
{
_repository . Delete ( pendingRelease ) ;
_eventAggregator . PublishEvent ( new PendingReleasesUpdatedEvent ( ) ) ;
}
2015-05-12 06:32:58 +00:00
private static Func < PendingRelease , bool > MatchingReleasePredicate ( ReleaseInfo release )
2014-06-08 08:22:55 +00:00
{
2015-05-12 06:32:58 +00:00
return p = > p . Title = = release . Title & &
p . Release . PublishDate = = release . PublishDate & &
p . Release . Indexer = = release . Indexer ;
2014-06-08 08:22:55 +00:00
}
2017-08-14 02:58:42 +00:00
private int GetDelay ( RemoteAlbum remoteAlbum )
2014-11-24 00:07:46 +00:00
{
2017-08-14 02:58:42 +00:00
var delayProfile = _delayProfileService . AllForTags ( remoteAlbum . Artist . Tags ) . OrderBy ( d = > d . Order ) . First ( ) ;
var delay = delayProfile . GetProtocolDelay ( remoteAlbum . Release . DownloadProtocol ) ;
2014-12-15 23:40:53 +00:00
var minimumAge = _configService . MinimumAge ;
2014-11-24 00:07:46 +00:00
2015-05-12 06:32:58 +00:00
return new [ ] { delay , minimumAge } . Max ( ) ;
2014-11-24 00:07:46 +00:00
}
2017-08-14 02:58:42 +00:00
private void RemoveGrabbed ( RemoteAlbum remoteAlbum )
2014-12-03 00:47:45 +00:00
{
var pendingReleases = GetPendingReleases ( ) ;
2017-08-14 02:58:42 +00:00
var albumIds = remoteAlbum . Albums . Select ( e = > e . Id ) ;
2014-12-03 00:47:45 +00:00
2017-08-14 02:58:42 +00:00
var existingReports = pendingReleases . Where ( r = > r . RemoteAlbum . Albums . Select ( e = > e . Id )
. Intersect ( albumIds )
2014-12-03 00:47:45 +00:00
. Any ( ) )
. ToList ( ) ;
if ( existingReports . Empty ( ) )
{
return ;
}
2017-08-14 02:58:42 +00:00
var profile = remoteAlbum . Artist . Profile . Value ;
2014-12-03 00:47:45 +00:00
foreach ( var existingReport in existingReports )
{
2017-08-14 02:58:42 +00:00
var compare = new QualityModelComparer ( profile ) . Compare ( remoteAlbum . ParsedAlbumInfo . Quality ,
existingReport . RemoteAlbum . ParsedAlbumInfo . Quality ) ;
2014-12-03 00:47:45 +00:00
//Only remove lower/equal quality pending releases
//It is safer to retry these releases on the next round than remove it and try to re-add it (if its still in the feed)
if ( compare > = 0 )
{
_logger . Debug ( "Removing previously pending release, as it was grabbed." ) ;
Delete ( existingReport ) ;
}
}
}
private void RemoveRejected ( List < DownloadDecision > rejected )
{
_logger . Debug ( "Removing failed releases from pending" ) ;
var pending = GetPendingReleases ( ) ;
foreach ( var rejectedRelease in rejected )
{
2017-08-14 02:58:42 +00:00
var matching = pending . Where ( MatchingReleasePredicate ( rejectedRelease . RemoteAlbum . Release ) ) ;
2014-12-03 00:47:45 +00:00
2016-04-27 23:16:43 +00:00
foreach ( var pendingRelease in matching )
2014-12-03 00:47:45 +00:00
{
_logger . Debug ( "Removing previously pending release, as it has now been rejected." ) ;
2016-04-27 23:16:43 +00:00
Delete ( pendingRelease ) ;
2014-12-03 00:47:45 +00:00
}
}
}
2015-07-10 07:18:32 +00:00
private PendingRelease FindPendingRelease ( int queueId )
2014-12-03 00:47:45 +00:00
{
2017-08-14 02:58:42 +00:00
return GetPendingReleases ( ) . First ( p = > p . RemoteAlbum . Albums . Any ( e = > queueId = = GetQueueId ( p , e ) ) ) ;
2015-02-01 05:53:06 +00:00
}
2017-08-14 02:58:42 +00:00
private int GetQueueId ( PendingRelease pendingRelease , Album album )
2015-02-01 05:53:06 +00:00
{
2017-08-14 02:58:42 +00:00
return HashConverter . GetHashInt31 ( string . Format ( "pending-{0}-album{1}" , pendingRelease . Id , album . Id ) ) ;
2014-12-03 00:47:45 +00:00
}
2017-08-14 02:58:42 +00:00
private int PrioritizeDownloadProtocol ( Artist artist , DownloadProtocol downloadProtocol )
2015-03-12 23:41:07 +00:00
{
2017-08-14 02:58:42 +00:00
var delayProfile = _delayProfileService . BestForTags ( artist . Tags ) ;
2015-03-12 23:41:07 +00:00
if ( downloadProtocol = = delayProfile . PreferredProtocol )
{
return 0 ;
}
return 1 ;
}
2017-08-14 02:58:42 +00:00
public void Handle ( ArtistDeletedEvent message )
2014-06-08 08:22:55 +00:00
{
2017-08-14 02:58:42 +00:00
_repository . DeleteByArtistId ( message . Artist . Id ) ;
2014-06-08 08:22:55 +00:00
}
2014-12-03 00:47:45 +00:00
2017-08-14 02:58:42 +00:00
public void Handle ( AlbumGrabbedEvent message )
2014-12-03 00:47:45 +00:00
{
2017-08-14 02:58:42 +00:00
RemoveGrabbed ( message . Album ) ;
2014-12-03 00:47:45 +00:00
}
public void Handle ( RssSyncCompleteEvent message )
{
RemoveRejected ( message . ProcessedDecisions . Rejected ) ;
}
2014-06-08 08:22:55 +00:00
}
}