You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Lidarr/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs

266 lines
11 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Download.History;
using NzbDrone.Core.History;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Music;
using NzbDrone.Core.Music.Events;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.Download.TrackedDownloads
{
public interface ITrackedDownloadService : IHandle<AlbumDeletedEvent>
{
TrackedDownload Find(string downloadId);
void StopTracking(string downloadId);
void StopTracking(List<string> downloadIds);
TrackedDownload TrackDownload(DownloadClientDefinition downloadClient, DownloadClientItem downloadItem);
List<TrackedDownload> GetTrackedDownloads();
void UpdateTrackable(List<TrackedDownload> trackedDownloads);
}
public class TrackedDownloadService : ITrackedDownloadService
{
private readonly IParsingService _parsingService;
private readonly IHistoryService _historyService;
private readonly IEventAggregator _eventAggregator;
private readonly IDownloadHistoryService _downloadHistoryService;
private readonly ITrackedDownloadAlreadyImported _trackedDownloadAlreadyImported;
private readonly Logger _logger;
private readonly ICached<TrackedDownload> _cache;
public TrackedDownloadService(IParsingService parsingService,
ICacheManager cacheManager,
IHistoryService historyService,
IDownloadHistoryService downloadHistoryService,
IEventAggregator eventAggregator,
ITrackedDownloadAlreadyImported trackedDownloadAlreadyImported,
Logger logger)
{
_parsingService = parsingService;
_historyService = historyService;
_cache = cacheManager.GetCache<TrackedDownload>(GetType());
_eventAggregator = eventAggregator;
_trackedDownloadAlreadyImported = trackedDownloadAlreadyImported;
_downloadHistoryService = downloadHistoryService;
_logger = logger;
}
public TrackedDownload Find(string downloadId)
{
return _cache.Find(downloadId);
}
public void UpdateAlbumCache(int albumId)
{
var updateCacheItems = _cache.Values.Where(x => x.RemoteAlbum != null && x.RemoteAlbum.Albums.Any(a => a.Id == albumId)).ToList();
foreach (var item in updateCacheItems)
{
var parsedAlbumInfo = Parser.Parser.ParseAlbumTitle(item.DownloadItem.Title);
item.RemoteAlbum = null;
if (parsedAlbumInfo != null)
{
item.RemoteAlbum = _parsingService.Map(parsedAlbumInfo);
}
}
_eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(GetTrackedDownloads()));
}
public void StopTracking(string downloadId)
{
var trackedDownload = _cache.Find(downloadId);
_cache.Remove(downloadId);
_eventAggregator.PublishEvent(new TrackedDownloadsRemovedEvent(new List<TrackedDownload> { trackedDownload }));
}
public void StopTracking(List<string> downloadIds)
{
var trackedDownloads = new List<TrackedDownload>();
foreach (var downloadId in downloadIds)
{
var trackedDownload = _cache.Find(downloadId);
_cache.Remove(downloadId);
trackedDownloads.Add(trackedDownload);
}
_eventAggregator.PublishEvent(new TrackedDownloadsRemovedEvent(trackedDownloads));
}
public TrackedDownload TrackDownload(DownloadClientDefinition downloadClient, DownloadClientItem downloadItem)
{
var existingItem = Find(downloadItem.DownloadId);
if (existingItem != null && existingItem.State != TrackedDownloadState.Downloading)
{
LogItemChange(existingItem, existingItem.DownloadItem, downloadItem);
existingItem.DownloadItem = downloadItem;
existingItem.IsTrackable = true;
return existingItem;
}
var trackedDownload = new TrackedDownload
{
DownloadClient = downloadClient.Id,
DownloadItem = downloadItem,
Protocol = downloadClient.Protocol,
IsTrackable = true
};
try
{
var historyItems = _historyService.FindByDownloadId(downloadItem.DownloadId)
.OrderByDescending(h => h.Date)
.ToList();
// TODO: Create release info from history and use that here, so we don't loose indexer flags!
var parsedAlbumInfo = Parser.Parser.ParseAlbumTitle(trackedDownload.DownloadItem.Title);
if (parsedAlbumInfo != null)
{
trackedDownload.RemoteAlbum = _parsingService.Map(parsedAlbumInfo);
}
var downloadHistory = _downloadHistoryService.GetLatestDownloadHistoryItem(downloadItem.DownloadId);
if (downloadHistory != null)
{
var state = GetStateFromHistory(downloadHistory.EventType);
trackedDownload.State = state;
if (downloadHistory.EventType == DownloadHistoryEventType.DownloadImportIncomplete)
{
var messages = Json.Deserialize<List<TrackedDownloadStatusMessage>>(downloadHistory.Data["statusMessages"]).ToArray();
trackedDownload.Warn(messages);
}
}
if (historyItems.Any())
{
var firstHistoryItem = historyItems.First();
var grabbedEvent = historyItems.FirstOrDefault(v => v.EventType == EntityHistoryEventType.Grabbed);
trackedDownload.Indexer = grabbedEvent?.Data["indexer"];
if (parsedAlbumInfo == null ||
trackedDownload.RemoteAlbum == null ||
trackedDownload.RemoteAlbum.Artist == null ||
trackedDownload.RemoteAlbum.Albums.Empty())
{
// Try parsing the original source title and if that fails, try parsing it as a special
var historyArtist = firstHistoryItem.Artist;
var historyAlbums = new List<Album> { firstHistoryItem.Album };
parsedAlbumInfo = Parser.Parser.ParseAlbumTitle(firstHistoryItem.SourceTitle);
if (parsedAlbumInfo != null)
{
trackedDownload.RemoteAlbum = _parsingService.Map(parsedAlbumInfo,
firstHistoryItem.ArtistId,
historyItems.Where(v => v.EventType == EntityHistoryEventType.Grabbed).Select(h => h.AlbumId)
.Distinct());
}
else
{
parsedAlbumInfo =
Parser.Parser.ParseAlbumTitleWithSearchCriteria(firstHistoryItem.SourceTitle,
historyArtist,
historyAlbums);
if (parsedAlbumInfo != null)
{
trackedDownload.RemoteAlbum = _parsingService.Map(parsedAlbumInfo,
firstHistoryItem.ArtistId,
historyItems.Where(v => v.EventType == EntityHistoryEventType.Grabbed).Select(h => h.AlbumId)
.Distinct());
}
}
}
}
// Track it so it can be displayed in the queue even though we can't determine which artist it is for
if (trackedDownload.RemoteAlbum == null)
{
_logger.Trace("No Album found for download '{0}'", trackedDownload.DownloadItem.Title);
}
}
catch (Exception e)
{
_logger.Debug(e, "Failed to find album for " + downloadItem.Title);
return null;
}
LogItemChange(trackedDownload, existingItem?.DownloadItem, trackedDownload.DownloadItem);
_cache.Set(trackedDownload.DownloadItem.DownloadId, trackedDownload);
return trackedDownload;
}
public List<TrackedDownload> GetTrackedDownloads()
{
return _cache.Values.ToList();
}
public void UpdateTrackable(List<TrackedDownload> trackedDownloads)
{
var untrackable = GetTrackedDownloads().ExceptBy(t => t.DownloadItem.DownloadId, trackedDownloads, t => t.DownloadItem.DownloadId, StringComparer.CurrentCulture).ToList();
foreach (var trackedDownload in untrackable)
{
trackedDownload.IsTrackable = false;
}
}
private static TrackedDownloadState GetStateFromHistory(DownloadHistoryEventType eventType)
{
switch (eventType)
{
case DownloadHistoryEventType.DownloadImportIncomplete:
return TrackedDownloadState.ImportFailed;
case DownloadHistoryEventType.DownloadImported:
return TrackedDownloadState.Imported;
case DownloadHistoryEventType.DownloadFailed:
return TrackedDownloadState.DownloadFailed;
case DownloadHistoryEventType.DownloadIgnored:
return TrackedDownloadState.Ignored;
default:
return TrackedDownloadState.Downloading;
}
}
private void LogItemChange(TrackedDownload trackedDownload, DownloadClientItem existingItem, DownloadClientItem downloadItem)
{
if (existingItem == null ||
existingItem.Status != downloadItem.Status ||
existingItem.CanBeRemoved != downloadItem.CanBeRemoved ||
existingItem.CanMoveFiles != downloadItem.CanMoveFiles)
{
Whole album matching and fingerprinting (#592) * Cache result of GetAllArtists * Fixed: Manual import not respecting album import notifications * Fixed: partial album imports stay in queue, prompting manual import * Fixed: Allow release if tracks are missing * Fixed: Be tolerant of missing/extra "The" at start of artist name * Improve manual import UI * Omit video tracks from DB entirely * Revert "faster test packaging in build.sh" This reverts commit 2723e2a7b86bcbff9051fd2aced07dd807b4bcb7. -u and -T are not supported on macOS * Fix tests on linux and macOS * Actually lint on linux On linux yarn runs scripts with sh not bash so ** doesn't recursively glob * Match whole albums * Option to disable fingerprinting * Rip out MediaInfo * Don't split up things that have the same album selected in manual import * Try to speed up IndentificationService * More speedups * Some fixes and increase power of recording id * Fix NRE when no tags * Fix NRE when some (but not all) files in a directory have missing tags * Bump taglib, tidy up tag parsing * Add a health check * Remove media info setting * Tags -> audioTags * Add some tests where tags are null * Rename history events * Add missing method to interface * Reinstate MediaInfo tags and update info with artist scan Also adds migration to remove old format media info * This file no longer exists * Don't penalise year if missing from tags * Formatting improvements * Use correct system newline * Switch to the netstandard2.0 library to support net 461 * TagLib.File is IDisposable so should be in a using * Improve filename matching and add tests * Neater logging of parsed tags * Fix disk scan tests for new media info update * Fix quality detection source * Fix Inexact Artist/Album match * Add button to clear track mapping * Fix warning * Pacify eslint * Use \ not / * Fix UI updates * Fix media covers Prevent localizing URL propaging back to the metadata object * Reduce database overhead broadcasting UI updates * Relax timings a bit to make test pass * Remove irrelevant tests * Test framework for identification service * Fix PreferMissingToBadMatch test case * Make fingerprinting more robust * More logging * Penalize unknown media format and country * Prefer USA to UK * Allow Data CD * Fix exception if fingerprinting fails for all files * Fix tests * Fix NRE * Allow apostrophes and remove accents in filename aggregation * Address codacy issues * Cope with old versions of fpcalc and suggest upgrade * fpcalc health check passes if fingerprinting disabled * Get the Artist meta with the artist * Fix the mapper so that lazy loaded lists will be populated on Join And therefore we can join TrackFiles on Tracks by default and avoid an extra query * Rename subtitle -> lyric * Tidy up MediaInfoFormatter
5 years ago
_logger.Debug("Tracking '{0}:{1}': ClientState={2}{3} LidarrStage={4} Album='{5}' OutputPath={6}.",
downloadItem.DownloadClientInfo.Name,
downloadItem.Title,
downloadItem.Status,
downloadItem.CanBeRemoved ? "" : downloadItem.CanMoveFiles ? " (busy)" : " (readonly)",
trackedDownload.State,
trackedDownload.RemoteAlbum?.ParsedAlbumInfo,
downloadItem.OutputPath);
}
}
public void Handle(AlbumDeletedEvent message)
{
UpdateAlbumCache(message.Album.Id);
}
}
}