public void Import(TrackedDownload trackedDownload)
        {
            trackedDownload.State = TrackedDownloadState.Importing;

            var outputPath = trackedDownload.DownloadItem.OutputPath.FullPath;
            var importResults = _downloadedTracksImportService.ProcessPath(outputPath, ImportMode.Auto, trackedDownload.RemoteBook.Author, trackedDownload.DownloadItem);

            if (importResults.Empty())
            {
                trackedDownload.State = TrackedDownloadState.ImportFailed;
                trackedDownload.Warn("No files found are eligible for import in {0}", outputPath);
                _eventAggregator.PublishEvent(new BookImportIncompleteEvent(trackedDownload));
                return;
            }

            if (VerifyImport(trackedDownload, importResults))
            {
                return;
            }

            trackedDownload.State = TrackedDownloadState.ImportPending;

            if (importResults.Any(c => c.Result != ImportResultType.Imported))
            {
                trackedDownload.State = TrackedDownloadState.ImportFailed;
                var statusMessages = importResults
                    .Where(v => v.Result != ImportResultType.Imported)
                    .Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.Item.Path), v.Errors))
                    .ToArray();

                trackedDownload.Warn(statusMessages);
                _eventAggregator.PublishEvent(new BookImportIncompleteEvent(trackedDownload));
                return;
            }
        }
        public bool IgnoreDownload(TrackedDownload trackedDownload)
        {
            var movie = trackedDownload.RemoteMovie.Movie;

            if (movie == null)
            {
                _logger.Warn("Unable to ignore download for unknown movie");
                return(false);
            }

            var downloadIgnoredEvent = new DownloadIgnoredEvent
            {
                MovieId            = movie.Id,
                Languages          = trackedDownload.RemoteMovie.ParsedMovieInfo.Languages,
                Quality            = trackedDownload.RemoteMovie.ParsedMovieInfo.Quality,
                SourceTitle        = trackedDownload.DownloadItem.Title,
                DownloadClientInfo = trackedDownload.DownloadItem.DownloadClientInfo,
                DownloadId         = trackedDownload.DownloadItem.DownloadId,
                TrackedDownload    = trackedDownload,
                Message            = "Manually ignored"
            };

            _eventAggregator.PublishEvent(downloadIgnoredEvent);
            return(true);
        }
        public void Process(TrackedDownload trackedDownload)
        {
            string failure = null;

            if (trackedDownload.DownloadItem.IsEncrypted)
            {
                failure = "Encrypted download detected";
            }
            else if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed)
            {
                failure = trackedDownload.DownloadItem.Message ?? "Failed download detected";
            }

            if (failure != null)
            {
                var grabbedItems = _historyService.Find(trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed)
                                   .ToList();

                if (grabbedItems.Empty())
                {
                    trackedDownload.Warn("Download wasn't grabbed by Bonarr, skipping");
                    return;
                }

                trackedDownload.State = TrackedDownloadStage.DownloadFailed;
                PublishDownloadFailedEvent(grabbedItems, failure, trackedDownload);
            }
        }
Exemple #4
0
        public void Import(TrackedDownload trackedDownload)
        {
            trackedDownload.State = TrackedDownloadState.Importing;

            var outputPath    = trackedDownload.DownloadItem.OutputPath.FullPath;
            var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, ImportMode.Auto,
                                                                             trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem);

            if (VerifyImport(trackedDownload, importResults))
            {
                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))
            {
                var statusMessages = importResults
                                     .Where(v => v.Result != ImportResultType.Imported && v.ImportDecision.LocalEpisode != null)
                                     .Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), v.Errors))
                                     .ToArray();

                trackedDownload.Warn(statusMessages);
            }
        }
Exemple #5
0
        public bool IgnoreDownload(TrackedDownload trackedDownload)
        {
            var artist = trackedDownload.RemoteAlbum.Artist;
            var albums = trackedDownload.RemoteAlbum.Albums;

            if (artist == null || albums.Empty())
            {
                _logger.Warn("Unable to ignore download for unknown artist/album");
                return(false);
            }

            var downloadIgnoredEvent = new DownloadIgnoredEvent
            {
                ArtistId           = artist.Id,
                AlbumIds           = albums.Select(e => e.Id).ToList(),
                Quality            = trackedDownload.RemoteAlbum.ParsedAlbumInfo.Quality,
                SourceTitle        = trackedDownload.DownloadItem.Title,
                DownloadClientInfo = trackedDownload.DownloadItem.DownloadClientInfo,
                DownloadId         = trackedDownload.DownloadItem.DownloadId,
                Message            = "Manually ignored"
            };

            _eventAggregator.PublishEvent(downloadIgnoredEvent);
            return(true);
        }
Exemple #6
0
        private void Import(TrackedDownload trackedDownload)
        {
            var outputPath    = trackedDownload.DownloadItem.OutputPath.FullPath;
            var importResults = _downloadedTracksImportService.ProcessPath(outputPath, ImportMode.Auto, trackedDownload.RemoteAlbum.Artist, trackedDownload.DownloadItem);

            if (importResults.Empty())
            {
                trackedDownload.State = TrackedDownloadStage.ImportFailed;
                trackedDownload.Warn("No files found are eligible for import in {0}", outputPath);
                _eventAggregator.PublishEvent(new AlbumImportIncompleteEvent(trackedDownload));
                return;
            }

            if (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))))
            {
                trackedDownload.State = TrackedDownloadStage.Imported;
                _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
                return;
            }

            if (importResults.Any(c => c.Result != ImportResultType.Imported))
            {
                trackedDownload.State = TrackedDownloadStage.ImportFailed;
                var statusMessages = importResults
                                     .Where(v => v.Result != ImportResultType.Imported)
                                     .Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.Item.Path), v.Errors))
                                     .ToArray();

                trackedDownload.Warn(statusMessages);
                _eventAggregator.PublishEvent(new AlbumImportIncompleteEvent(trackedDownload));
            }
        }
Exemple #7
0
        private void Import(TrackedDownload trackedDownload)
        {
            var outputPath    = trackedDownload.DownloadItem.OutputPath.FullPath;
            var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem);

            if (importResults.Empty())
            {
                trackedDownload.Warn("No files found are eligible for import in {0}", outputPath);
                return;
            }

            if (importResults.Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count))
            {
                trackedDownload.State = TrackedDownloadStage.Imported;
                _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
                return;
            }

            if (importResults.Any(c => c.Result != ImportResultType.Imported))
            {
                var statusMessages = importResults
                                     .Where(v => v.Result != ImportResultType.Imported)
                                     .Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), v.Errors))
                                     .ToArray();

                trackedDownload.Warn(statusMessages);
            }
        }
        public bool IgnoreDownload(TrackedDownload trackedDownload)
        {
            var series = trackedDownload.RemoteEpisode.Series;

            if (series == null)
            {
                _logger.Warn("Unable to ignore download for unknown series");
                return(false);
            }

            var episodes = trackedDownload.RemoteEpisode.Episodes;

            var downloadIgnoredEvent = new DownloadIgnoredEvent
            {
                SeriesId           = series.Id,
                EpisodeIds         = episodes.Select(e => e.Id).ToList(),
                Language           = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Language,
                Quality            = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Quality,
                SourceTitle        = trackedDownload.DownloadItem.Title,
                DownloadClientInfo = trackedDownload.DownloadItem.DownloadClientInfo,
                DownloadId         = trackedDownload.DownloadItem.DownloadId,
                TrackedDownload    = trackedDownload,
                Message            = "Manually ignored"
            };

            _eventAggregator.PublishEvent(downloadIgnoredEvent);
            return(true);
        }
Exemple #9
0
        private void RemoveCompleted(TrackedDownload trackedDownload, IDownloadClient downloadClient)
        {
            if (trackedDownload.State == TrackedDownloadState.Imported && !trackedDownload.DownloadItem.IsReadOnly)
            {
                try
                {
                    _logger.Debug("[{0}] Removing completed download from history.", trackedDownload.DownloadItem.Title);
                    downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId);

                    if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath))
                    {
                        _logger.Debug("Removing completed download directory: {0}",
                                      trackedDownload.DownloadItem.OutputPath);
                        _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath, true);
                    }
                    else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath))
                    {
                        _logger.Debug("Removing completed download file: {0}", trackedDownload.DownloadItem.OutputPath);
                        _diskProvider.DeleteFile(trackedDownload.DownloadItem.OutputPath);
                    }

                    trackedDownload.State = TrackedDownloadState.Removed;
                }
                catch (NotSupportedException)
                {
                    UpdateStatusMessage(trackedDownload, LogLevel.Debug,
                                        "Removing item not supported by your download client.");
                }
            }
        }
        private TrackedDownload GetTrackedDownload(String trackingId, Int32 downloadClient, DownloadClientItem downloadItem, List <History.History> grabbedHistory)
        {
            var trackedDownload = new TrackedDownload
            {
                TrackingId      = trackingId,
                DownloadClient  = downloadClient,
                DownloadItem    = downloadItem,
                StartedTracking = DateTime.UtcNow,
                State           = TrackedDownloadState.Unknown,
                Status          = TrackedDownloadStatus.Ok,
            };


            try
            {
                var historyItems = grabbedHistory.Where(h =>
                {
                    var downloadClientId = h.Data.GetValueOrDefault(DOWNLOAD_CLIENT_ID);

                    if (downloadClientId == null)
                    {
                        return(false);
                    }

                    return(downloadClientId.Equals(trackedDownload.DownloadItem.DownloadClientId));
                }).ToList();

                var parsedEpisodeInfo = Parser.Parser.ParseTitle(trackedDownload.DownloadItem.Title);
                if (parsedEpisodeInfo == null)
                {
                    return(null);
                }

                var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0);
                if (remoteEpisode.Series == null)
                {
                    if (historyItems.Empty())
                    {
                        return(null);
                    }

                    trackedDownload.Status = TrackedDownloadStatus.Warning;
                    trackedDownload.StatusMessages.Add(new TrackedDownloadStatusMessage(
                                                           trackedDownload.DownloadItem.Title,
                                                           "Series title mismatch, automatic import is not possible")
                                                       );

                    remoteEpisode = _parsingService.Map(parsedEpisodeInfo, historyItems.First().SeriesId, historyItems.Select(h => h.EpisodeId));
                }

                trackedDownload.RemoteEpisode = remoteEpisode;
            }
            catch (Exception e)
            {
                _logger.DebugException("Failed to find episode for " + downloadItem.Title, e);
                return(null);
            }

            return(trackedDownload);
        }
Exemple #11
0
        private void AssociateWithPreviouslyImported(TrackedDownload trackedDownload, List <History.History> grabbedItems, List <History.History> importedHistory)
        {
            if (grabbedItems.Any())
            {
                var episodeIds = trackedDownload.RemoteEpisode.Episodes.Select(v => v.Id).ToList();

                // Check if we can associate it with a previous drone factory import.
                var importedItems = importedHistory.Where(v => v.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID) == null &&
                                                          episodeIds.Contains(v.EpisodeId) &&
                                                          v.Data.GetValueOrDefault("droppedPath") != null &&
                                                          new FileInfo(v.Data["droppedPath"]).Directory.Name == grabbedItems.First().SourceTitle
                                                          ).ToList();
                if (importedItems.Count == 1)
                {
                    var importedFile = new FileInfo(importedItems.First().Data["droppedPath"]);

                    if (importedFile.Directory.Name == grabbedItems.First().SourceTitle)
                    {
                        trackedDownload.State = TrackedDownloadState.Imported;

                        importedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT]    = grabbedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT];
                        importedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT_ID] = grabbedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT_ID];
                        _historyService.UpdateHistoryData(importedItems.First().Id, importedItems.First().Data);

                        UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Intermediate Download path does not exist, but found probable drone factory ImportEvent.");
                        return;
                    }
                }
            }

            UpdateStatusMessage(trackedDownload, LogLevel.Error, "Intermediate Download path does not exist: {0}", trackedDownload.DownloadItem.OutputPath);
        }
Exemple #12
0
        private void ProcessImportResults(TrackedDownload trackedDownload, String outputPath, List <ImportResult> importResults)
        {
            if (importResults.Empty())
            {
                UpdateStatusMessage(trackedDownload, LogLevel.Error, "No files found are eligible for import in {0}", outputPath);
            }
            else if (importResults.Any(v => v.Result == ImportResultType.Imported) && importResults.All(v => v.Result == ImportResultType.Imported || v.Result == ImportResultType.Rejected))
            {
                UpdateStatusMessage(trackedDownload, LogLevel.Info, "Imported {0} files.", importResults.Count(v => v.Result == ImportResultType.Imported));

                trackedDownload.State = TrackedDownloadState.Imported;
            }
            else
            {
                var errors = importResults
                             .Where(v => v.Result == ImportResultType.Skipped || v.Result == ImportResultType.Rejected)
                             .Select(v => v.Errors.Aggregate(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), (a, r) => a + "\r\n- " + r))
                             .Aggregate("Failed to import:", (a, r) => a + "\r\n" + r);

                trackedDownload.StatusMessages = importResults.Where(v => v.Result == ImportResultType.Skipped || v.Result == ImportResultType.Rejected)
                                                 .Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), v.Errors)).ToList();

                UpdateStatusMessage(trackedDownload, LogLevel.Error, errors);
            }
        }
Exemple #13
0
        public void ProcessFailed(TrackedDownload trackedDownload)
        {
            if (trackedDownload.State != TrackedDownloadState.FailedPending)
            {
                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.Failed;
            PublishDownloadFailedEvent(grabbedItems, failure, trackedDownload);
        }
Exemple #14
0
        public void Check(TrackedDownload trackedDownload)
        {
            if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed)
            {
                return;
            }

            SetImportItem(trackedDownload);

            // Only process tracked downloads that are still downloading
            if (trackedDownload.State != TrackedDownloadState.Downloading)
            {
                return;
            }

            var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId);

            if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
            {
                trackedDownload.Warn("Download wasn't grabbed by Readarr and not in a category, Skipping.");
                return;
            }

            if (!ValidatePath(trackedDownload))
            {
                return;
            }

            trackedDownload.State = TrackedDownloadState.ImportPending;
        }
Exemple #15
0
        public bool IgnoreDownload(TrackedDownload trackedDownload)
        {
            var author = trackedDownload.RemoteBook.Author;
            var books  = trackedDownload.RemoteBook.Books;

            if (author == null || books.Empty())
            {
                _logger.Warn("Unable to ignore download for unknown author/book");
                return(false);
            }

            var downloadIgnoredEvent = new DownloadIgnoredEvent
            {
                AuthorId       = author.Id,
                BookIds        = books.Select(e => e.Id).ToList(),
                Quality        = trackedDownload.RemoteBook.ParsedBookInfo.Quality,
                SourceTitle    = trackedDownload.DownloadItem.Title,
                DownloadClient = trackedDownload.DownloadItem.DownloadClient,
                DownloadId     = trackedDownload.DownloadItem.DownloadId,
                Message        = "Manually ignored"
            };

            _eventAggregator.PublishEvent(downloadIgnoredEvent);
            return(true);
        }
Exemple #16
0
        private Boolean UpdateTrackedDownloads()
        {
            var downloadClients = _downloadClientProvider.GetDownloadClients();

            var oldTrackedDownloads = GetTrackedDownloads().ToDictionary(v => v.TrackingId);
            var newTrackedDownloads = new Dictionary <String, TrackedDownload>();

            var stateChanged = false;

            foreach (var downloadClient in downloadClients)
            {
                var downloadClientHistory = downloadClient.GetItems().ToList();
                foreach (var downloadItem in downloadClientHistory)
                {
                    var             trackingId = String.Format("{0}-{1}", downloadClient.Definition.Id, downloadItem.DownloadClientId);
                    TrackedDownload trackedDownload;

                    if (newTrackedDownloads.ContainsKey(trackingId))
                    {
                        continue;
                    }

                    if (!oldTrackedDownloads.TryGetValue(trackingId, out trackedDownload))
                    {
                        trackedDownload = new TrackedDownload
                        {
                            TrackingId      = trackingId,
                            DownloadClient  = downloadClient.Definition.Id,
                            StartedTracking = DateTime.UtcNow,
                            State           = TrackedDownloadState.Unknown
                        };

                        _logger.Debug("[{0}] Started tracking download with id {1}.", downloadItem.Title, trackingId);
                        stateChanged = true;
                    }

                    trackedDownload.DownloadItem = downloadItem;

                    newTrackedDownloads[trackingId] = trackedDownload;
                }
            }

            foreach (var trackedDownload in oldTrackedDownloads.Values.Where(v => !newTrackedDownloads.ContainsKey(v.TrackingId)))
            {
                if (trackedDownload.State != TrackedDownloadState.Removed)
                {
                    trackedDownload.State = TrackedDownloadState.Removed;
                    stateChanged          = true;

                    _logger.Debug("[{0}] Item with id {1} removed from download client directly (possibly by user).", trackedDownload.DownloadItem.Title, trackedDownload.TrackingId);
                }

                _logger.Debug("[{0}] Stopped tracking download with id {1}.", trackedDownload.DownloadItem.Title, trackedDownload.TrackingId);
            }

            _trackedDownloadCache.Set("tracked", newTrackedDownloads.Values.ToArray());

            return(stateChanged);
        }
Exemple #17
0
        public void Process(TrackedDownload trackedDownload, bool ignoreWarnings = false)
        {
            if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed)
            {
                return;
            }

            if (!ignoreWarnings)
            {
                var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId);

                if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
                {
                    trackedDownload.Warn("Download wasn't grabbed by Sonarr and not in a category, Skipping.");
                    return;
                }

                var downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath;

                if (downloadItemOutputPath.IsEmpty)
                {
                    trackedDownload.Warn("Download doesn't contain intermediate path, Skipping.");
                    return;
                }

                if ((OsInfo.IsWindows && !downloadItemOutputPath.IsWindowsPath) ||
                    (OsInfo.IsNotWindows && !downloadItemOutputPath.IsUnixPath))
                {
                    trackedDownload.Warn("[{0}] is not a valid local path. You may need a Remote Path Mapping.", downloadItemOutputPath);
                    return;
                }

                var downloadedEpisodesFolder = new OsPath(_configService.DownloadedEpisodesFolder);

                if (downloadedEpisodesFolder.Contains(downloadItemOutputPath))
                {
                    trackedDownload.Warn("Intermediate Download path inside drone factory, Skipping.");
                    return;
                }

                var series = _parsingService.GetSeries(trackedDownload.DownloadItem.Title);

                if (series == null)
                {
                    if (historyItem != null)
                    {
                        series = _seriesService.GetSeries(historyItem.SeriesId);
                    }

                    if (series == null)
                    {
                        trackedDownload.Warn("Series title mismatch, automatic import is not possible.");
                        return;
                    }
                }
            }

            Import(trackedDownload);
        }
Exemple #18
0
        public void Check(TrackedDownload trackedDownload)
        {
            if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed ||
                trackedDownload.RemoteBook == null)
            {
                return;
            }

            trackedDownload.ImportItem = _importItemService.ProvideImportItem(trackedDownload.DownloadItem, trackedDownload.ImportItem);

            // Only process tracked downloads that are still downloading
            if (trackedDownload.State != TrackedDownloadState.Downloading)
            {
                return;
            }

            var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId);

            if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
            {
                trackedDownload.Warn("Download wasn't grabbed by Readarr and not in a category, Skipping.");
                return;
            }

            var downloadItemOutputPath = trackedDownload.ImportItem.OutputPath;

            if (downloadItemOutputPath.IsEmpty)
            {
                trackedDownload.Warn("Download doesn't contain intermediate path, Skipping.");
                return;
            }

            if ((OsInfo.IsWindows && !downloadItemOutputPath.IsWindowsPath) ||
                (OsInfo.IsNotWindows && !downloadItemOutputPath.IsUnixPath))
            {
                trackedDownload.Warn("[{0}] is not a valid local path. You may need a Remote Path Mapping.", downloadItemOutputPath);
                return;
            }

            var author = trackedDownload.RemoteBook.Author;

            if (author == null)
            {
                if (historyItem != null)
                {
                    author = _authorService.GetAuthor(historyItem.AuthorId);
                }

                if (author == null)
                {
                    trackedDownload.Warn("Author name mismatch, automatic import is not possible.");
                    return;
                }
            }

            trackedDownload.State = TrackedDownloadState.ImportPending;
        }
Exemple #19
0
        public void Import(TrackedDownload trackedDownload)
        {
            SetImportItem(trackedDownload);

            if (!ValidatePath(trackedDownload))
            {
                return;
            }

            if (trackedDownload.RemoteEpisode == null)
            {
                trackedDownload.Warn("Unable to parse download, automatic import is not possible.");
                return;
            }

            trackedDownload.State = TrackedDownloadState.Importing;

            var outputPath    = trackedDownload.ImportItem.OutputPath.FullPath;
            var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, ImportMode.Auto,
                                                                             trackedDownload.RemoteEpisode.Series, trackedDownload.ImportItem);

            if (VerifyImport(trackedDownload, importResults))
            {
                return;
            }

            trackedDownload.State = TrackedDownloadState.ImportPending;

            if (importResults.Empty())
            {
                trackedDownload.Warn("No files found are eligible for import in {0}", outputPath);

                return;
            }

            var statusMessages = new List <TrackedDownloadStatusMessage>
            {
                new TrackedDownloadStatusMessage("One or more episodes expected in this release were not imported or missing", new List <string>())
            };

            if (importResults.Any(c => c.Result != ImportResultType.Imported))
            {
                statusMessages.AddRange(
                    importResults
                    .Where(v => v.Result != ImportResultType.Imported && v.ImportDecision.LocalEpisode != null)
                    .OrderBy(v => v.ImportDecision.LocalEpisode.Path)
                    .Select(v =>
                            new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path),
                                                             v.Errors))
                    );
            }

            if (statusMessages.Any())
            {
                trackedDownload.Warn(statusMessages.ToArray());
            }
        }
Exemple #20
0
        public void Import(TrackedDownload trackedDownload)
        {
            trackedDownload.State = TrackedDownloadState.Importing;

            var outputPath    = trackedDownload.DownloadItem.OutputPath.FullPath;
            var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, ImportMode.Auto,
                                                                             trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem);

            var allEpisodesImported = importResults.Where(c => c.Result == ImportResultType.Imported)
                                      .SelectMany(c => c.ImportDecision.LocalEpisode.Episodes)
                                      .Count() >= Math.Max(1,
                                                           trackedDownload.RemoteEpisode.Episodes.Count);

            if (allEpisodesImported)
            {
                trackedDownload.State = TrackedDownloadState.Imported;
                _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
                return;
            }

            // Double check if all episodes were imported by checking the history if at least one
            // file was imported. This will allow the decision engine to reject already imported
            // episode files 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 allEpisodesImportedInHistory = _trackedDownloadAlreadyImported.IsImported(trackedDownload, historyItems);

                if (allEpisodesImportedInHistory)
                {
                    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))
            {
                var statusMessages = importResults
                                     .Where(v => v.Result != ImportResultType.Imported)
                                     .Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), v.Errors))
                                     .ToArray();

                trackedDownload.Warn(statusMessages);
            }
        }
Exemple #21
0
        public bool VerifyImport(TrackedDownload trackedDownload, List <ImportResult> importResults)
        {
            var allEpisodesImported = importResults.Where(c => c.Result == ImportResultType.Imported)
                                      .SelectMany(c => c.ImportDecision.LocalEpisode.Episodes)
                                      .Count() >= Math.Max(1,
                                                           trackedDownload.RemoteEpisode.Episodes.Count);

            if (allEpisodesImported)
            {
                _logger.Debug("All episodes were imported for {0}", trackedDownload.DownloadItem.Title);
                trackedDownload.State = TrackedDownloadState.Imported;
                _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload, trackedDownload.RemoteEpisode.Series.Id));
                return(true);
            }

            // Double check if all episodes were imported by checking the history if at least one
            // file was imported. This will allow the decision engine to reject already imported
            // episode files and still mark the download complete when all files are imported.

            // EDGE CASE: This process relies on EpisodeIds being consistent between executions, if a series is updated
            // and an episode is removed, but later comes back with a different ID then Sonarr will treat it as incomplete.
            // Since imports should be relatively fast and these types of data changes are infrequent this should be quite
            // safe, but commenting for future benefit.

            var atLeastOneEpisodeImported = importResults.Any(c => c.Result == ImportResultType.Imported);

            var historyItems = _historyService.FindByDownloadId(trackedDownload.DownloadItem.DownloadId)
                               .OrderByDescending(h => h.Date)
                               .ToList();

            var allEpisodesImportedInHistory = _trackedDownloadAlreadyImported.IsImported(trackedDownload, historyItems);

            if (allEpisodesImportedInHistory)
            {
                if (atLeastOneEpisodeImported)
                {
                    _logger.Debug("All episodes were imported in history for {0}", trackedDownload.DownloadItem.Title);
                    trackedDownload.State = TrackedDownloadState.Imported;
                    _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload, trackedDownload.RemoteEpisode.Series.Id));

                    return(true);
                }

                _logger.Debug()
                .Message("No Episodes were just imported, but all episodes were previously imported, possible issue with download history.")
                .Property("SeriesId", trackedDownload.RemoteEpisode.Series.Id)
                .Property("DownloadId", trackedDownload.DownloadItem.DownloadId)
                .Property("Title", trackedDownload.DownloadItem.Title)
                .Property("Path", trackedDownload.DownloadItem.OutputPath.ToString())
                .WriteSentryWarn("DownloadHistoryIncomplete")
                .Write();
            }

            _logger.Debug("Not all episodes have been imported for {0}", trackedDownload.DownloadItem.Title);
            return(false);
        }
Exemple #22
0
        public bool VerifyImport(TrackedDownload trackedDownload, List <ImportResult> importResults)
        {
            var allMoviesImported = importResults.Where(c => c.Result == ImportResultType.Imported)
                                    .Select(c => c.ImportDecision.LocalMovie.Movie)
                                    .Any();

            if (allMoviesImported)
            {
                _logger.Debug("All movies were imported for {0}", trackedDownload.DownloadItem.Title);
                trackedDownload.State = TrackedDownloadState.Imported;
                _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload, trackedDownload.RemoteMovie.Movie.Id));
                return(true);
            }

            // Double check if all movies were imported by checking the history if at least one
            // file was imported. This will allow the decision engine to reject already imported
            // episode files and still mark the download complete when all files are imported.
            var atLeastOneMovieImported = importResults.Any(c => c.Result == ImportResultType.Imported);

            var historyItems = _historyService.FindByDownloadId(trackedDownload.DownloadItem.DownloadId)
                               .OrderByDescending(h => h.Date)
                               .ToList();

            var allMoviesImportedInHistory = _trackedDownloadAlreadyImported.IsImported(trackedDownload, historyItems);

            if (allMoviesImportedInHistory)
            {
                // Log different error messages depending on the circumstances, but treat both as fully imported, because that's the reality.
                // The second message shouldn't be logged in most cases, but continued reporting would indicate an ongoing issue.
                if (atLeastOneMovieImported)
                {
                    _logger.Debug("All movies were imported in history for {0}", trackedDownload.DownloadItem.Title);
                }
                else
                {
                    _logger.Debug()
                    .Message("No Movies were just imported, but all movies were previously imported, possible issue with download history.")
                    .Property("MovieId", trackedDownload.RemoteMovie.Movie.Id)
                    .Property("DownloadId", trackedDownload.DownloadItem.DownloadId)
                    .Property("Title", trackedDownload.DownloadItem.Title)
                    .Property("Path", trackedDownload.ImportItem.OutputPath.ToString())
                    .WriteSentryWarn("DownloadHistoryIncomplete")
                    .Write();
                }

                trackedDownload.State = TrackedDownloadState.Imported;
                _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload, trackedDownload.RemoteMovie.Movie.Id));

                return(true);
            }

            _logger.Debug("Not all movies have been imported for {0}", trackedDownload.DownloadItem.Title);
            return(false);
        }
Exemple #23
0
        public void Check(TrackedDownload trackedDownload)
        {
            if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed)
            {
                return;
            }

            // Only process tracked downloads that are still downloading
            if (trackedDownload.State != TrackedDownloadState.Downloading)
            {
                return;
            }

            var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId);

            if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
            {
                trackedDownload.Warn("Download wasn't grabbed by Sonarr and not in a category, Skipping.");
                return;
            }

            var downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath;

            if (downloadItemOutputPath.IsEmpty)
            {
                trackedDownload.Warn("Download doesn't contain intermediate path, Skipping.");
                return;
            }

            if ((OsInfo.IsWindows && !downloadItemOutputPath.IsWindowsPath) ||
                (OsInfo.IsNotWindows && !downloadItemOutputPath.IsUnixPath))
            {
                trackedDownload.Warn("[{0}] is not a valid local path. You may need a Remote Path Mapping.", downloadItemOutputPath);
                return;
            }

            var series = _parsingService.GetSeries(trackedDownload.DownloadItem.Title);

            if (series == null)
            {
                if (historyItem != null)
                {
                    series = _seriesService.GetSeries(historyItem.SeriesId);
                }

                if (series == null)
                {
                    trackedDownload.Warn("Series title mismatch, automatic import is not possible.");
                    return;
                }
            }

            trackedDownload.State = TrackedDownloadState.ImportPending;
        }
Exemple #24
0
        public void Check(TrackedDownload trackedDownload)
        {
            if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed)
            {
                return;
            }

            SetImportItem(trackedDownload);

            // Only process tracked downloads that are still downloading
            if (trackedDownload.State != TrackedDownloadState.Downloading)
            {
                return;
            }

            var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId);

            if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
            {
                trackedDownload.Warn("Download wasn't grabbed by Sonarr and not in a category, Skipping.");
                return;
            }

            if (!ValidatePath(trackedDownload))
            {
                return;
            }

            var series = _parsingService.GetSeries(trackedDownload.DownloadItem.Title);

            if (series == null)
            {
                if (historyItem != null)
                {
                    series = _seriesService.GetSeries(historyItem.SeriesId);
                }

                if (series == null)
                {
                    trackedDownload.Warn("Series title mismatch; automatic import is not possible.");
                    return;
                }

                Enum.TryParse(historyItem.Data.GetValueOrDefault(EpisodeHistory.SERIES_MATCH_TYPE, SeriesMatchType.Unknown.ToString()), out SeriesMatchType seriesMatchType);

                if (seriesMatchType == SeriesMatchType.Id)
                {
                    trackedDownload.Warn("Found matching series via grab history, but release was matched to series by ID. Automatic import is not possible.");
                    return;
                }
            }

            trackedDownload.State = TrackedDownloadState.ImportPending;
        }
Exemple #25
0
        public void Process(TrackedDownload trackedDownload, bool ignoreWarnings = false)
        {
            if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed ||
                trackedDownload.RemoteAlbum == null)
            {
                return;
            }

            if (!ignoreWarnings)
            {
                var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId);

                if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
                {
                    trackedDownload.Warn("Download wasn't grabbed by Lidarr and not in a category, Skipping.");
                    return;
                }

                var downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath;

                if (downloadItemOutputPath.IsEmpty)
                {
                    trackedDownload.Warn("Download doesn't contain intermediate path, Skipping.");
                    return;
                }

                if ((OsInfo.IsWindows && !downloadItemOutputPath.IsWindowsPath) ||
                    (OsInfo.IsNotWindows && !downloadItemOutputPath.IsUnixPath))
                {
                    trackedDownload.Warn("[{0}] is not a valid local path. You may need a Remote Path Mapping.", downloadItemOutputPath);
                    return;
                }

                var artist = trackedDownload.RemoteAlbum.Artist;

                if (artist == null)
                {
                    if (historyItem != null)
                    {
                        artist = _artistService.GetArtist(historyItem.ArtistId);
                    }

                    if (artist == null)
                    {
                        trackedDownload.Warn("Artist name mismatch, automatic import is not possible.");
                        return;
                    }
                }
            }

            Import(trackedDownload);
        }
 private void MarkItemAsImported(TrackedDownload trackedDownload, IDownloadClient downloadClient)
 {
     try
     {
         _logger.Debug("[{0}] Marking download as imported from {1}", trackedDownload.DownloadItem.Title, trackedDownload.DownloadItem.DownloadClientInfo.Name);
         downloadClient.MarkItemAsImported(trackedDownload.DownloadItem);
     }
     catch (NotSupportedException e)
     {
         _logger.Debug(e.Message);
     }
     catch (Exception e)
     {
         _logger.Error(e, "Couldn't mark item {0} as imported from client {1}", trackedDownload.DownloadItem.Title, downloadClient.Name);
     }
 }
        private void UpdateStatusMessage(TrackedDownload trackedDownload, LogLevel logLevel, String message, params object[] args)
        {
            var statusMessage = String.Format(message, args);
            var logMessage    = String.Format("[{0}] {1}", trackedDownload.DownloadItem.Title, statusMessage);

            if (trackedDownload.StatusMessage != statusMessage)
            {
                trackedDownload.HasError      = logLevel >= LogLevel.Warn;
                trackedDownload.StatusMessage = statusMessage;
                _logger.Log(logLevel, logMessage);
            }
            else
            {
                _logger.Debug(logMessage);
            }
        }
Exemple #28
0
        public void Check(TrackedDownload trackedDownload)
        {
            if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed ||
                trackedDownload.RemoteAlbum == null)
            {
                return;
            }

            SetImportItem(trackedDownload);

            // Only process tracked downloads that are still downloading
            if (trackedDownload.State != TrackedDownloadState.Downloading)
            {
                return;
            }

            var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId);

            if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
            {
                trackedDownload.Warn("Download wasn't grabbed by Lidarr and not in a category, Skipping.");
                return;
            }

            if (!ValidatePath(trackedDownload))
            {
                return;
            }

            var artist = trackedDownload.RemoteAlbum.Artist;

            if (artist == null)
            {
                if (historyItem != null)
                {
                    artist = _artistService.GetArtist(historyItem.ArtistId);
                }

                if (artist == null)
                {
                    trackedDownload.Warn("Artist name mismatch, automatic import is not possible.");
                    return;
                }
            }

            trackedDownload.State = TrackedDownloadState.ImportPending;
        }
Exemple #29
0
        public void Check(TrackedDownload trackedDownload)
        {
            if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed)
            {
                return;
            }

            SetImportItem(trackedDownload);

            // Only process tracked downloads that are still downloading
            if (trackedDownload.State != TrackedDownloadState.Downloading)
            {
                return;
            }

            var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId);

            if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
            {
                trackedDownload.Warn("Download wasn't grabbed by Sonarr and not in a category, Skipping.");
                return;
            }

            if (!ValidatePath(trackedDownload))
            {
                return;
            }

            var series = _parsingService.GetSeries(trackedDownload.DownloadItem.Title);

            if (series == null)
            {
                if (historyItem != null)
                {
                    series = _seriesService.GetSeries(historyItem.SeriesId);
                }

                if (series == null)
                {
                    trackedDownload.Warn("Series title mismatch, automatic import is not possible.");
                    return;
                }
            }

            trackedDownload.State = TrackedDownloadState.ImportPending;
        }
 private void RemoveFromDownloadClient(TrackedDownload trackedDownload, IDownloadClient downloadClient)
 {
     try
     {
         _logger.Debug("[{0}] Removing download from {1} history", trackedDownload.DownloadItem.Title, trackedDownload.DownloadItem.DownloadClientInfo.Name);
         downloadClient.RemoveItem(trackedDownload.DownloadItem, true);
         trackedDownload.DownloadItem.Removed = true;
     }
     catch (NotSupportedException)
     {
         _logger.Warn("Removing item not supported by your download client ({0}).", downloadClient.Definition.Name);
     }
     catch (Exception e)
     {
         _logger.Error(e, "Couldn't remove item {0} from client {1}", trackedDownload.DownloadItem.Title, downloadClient.Name);
     }
 }
        public void CheckForCompletedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> grabbedHistory, List<History.History> importedHistory)
        {
            if (!_configService.EnableCompletedDownloadHandling)
            {
                return;
            }

            if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Completed && trackedDownload.State == TrackedDownloadState.Downloading)
            {
                var grabbedItems = GetHistoryItems(grabbedHistory, trackedDownload.DownloadItem.DownloadClientId);

                if (!grabbedItems.Any() && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
                {
                    _logger.Trace("Ignoring download that wasn't grabbed by drone: " + trackedDownload.DownloadItem.Title);
                    return;
                }

                var importedItems = GetHistoryItems(importedHistory, trackedDownload.DownloadItem.DownloadClientId);

                if (importedItems.Any())
                {
                    trackedDownload.State = TrackedDownloadState.Imported;

                    _logger.Debug("Already added to history as imported: " + trackedDownload.DownloadItem.Title);
                }
                else
                {
                    string downloadedEpisodesFolder = _configService.DownloadedEpisodesFolder;
                    string downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath;
                    if (downloadItemOutputPath.IsNullOrWhiteSpace())
                    {
                        _logger.Trace("Storage path not specified: " + trackedDownload.DownloadItem.Title);
                        return;
                    }

                    if (!downloadedEpisodesFolder.IsNullOrWhiteSpace() && (downloadedEpisodesFolder.PathEquals(downloadItemOutputPath) || downloadedEpisodesFolder.IsParentPath(downloadItemOutputPath)))
                    {
                        _logger.Trace("Storage path inside drone factory, ignoring download: " + trackedDownload.DownloadItem.Title);
                        return;
                    }

                    if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath))
                    {
                        var decisions = _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem);

                        if (decisions.Any())
                        {
                            trackedDownload.State = TrackedDownloadState.Imported;
                        }
                    }
                    else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath))
                    {
                        var decisions = _downloadedEpisodesImportService.ProcessFile(new FileInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem);

                        if (decisions.Any())
                        {
                            trackedDownload.State = TrackedDownloadState.Imported;
                        }
                    }
                    else
                    {
                        if (grabbedItems.Any())
                        {
                            var episodeIds = trackedDownload.DownloadItem.RemoteEpisode.Episodes.Select(v => v.Id).ToList();

                            // Check if we can associate it with a previous drone factory import.
                            importedItems = importedHistory.Where(v => v.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID) == null &&
                                                                  episodeIds.Contains(v.EpisodeId) &&
                                                                  v.Data.GetValueOrDefault("droppedPath") != null &&
                                                                  new FileInfo(v.Data["droppedPath"]).Directory.Name == grabbedItems.First().SourceTitle
                                                                  ).ToList();
                            if (importedItems.Count == 1)
                            {
                                var importedFile = new FileInfo(importedItems.First().Data["droppedPath"]);

                                if (importedFile.Directory.Name == grabbedItems.First().SourceTitle)
                                {
                                    trackedDownload.State = TrackedDownloadState.Imported;

                                    importedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT] = grabbedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT];
                                    importedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT_ID] = grabbedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT_ID];
                                    _historyService.UpdateHistoryData(importedItems.First().Id, importedItems.First().Data);

                                    _logger.Debug("Storage path does not exist, but found probable drone factory ImportEvent: " + trackedDownload.DownloadItem.Title);
                                    return;
                                }
                            }
                        }

                        _logger.Debug("Storage path does not exist: " + trackedDownload.DownloadItem.Title);
                        return;
                    }
                }
            }

            if (_configService.RemoveCompletedDownloads && trackedDownload.State == TrackedDownloadState.Imported && !trackedDownload.DownloadItem.IsReadOnly)
            {
                try
                {
                    _logger.Info("Removing completed download from history: {0}", trackedDownload.DownloadItem.Title);
                    downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId);

                    if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath))
                    {
                        _logger.Info("Removing completed download directory: {0}", trackedDownload.DownloadItem.OutputPath);
                        _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath, true);
                    }
                    else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath))
                    {
                        _logger.Info("Removing completed download file: {0}", trackedDownload.DownloadItem.OutputPath);
                        _diskProvider.DeleteFile(trackedDownload.DownloadItem.OutputPath);
                    }

                    trackedDownload.State = TrackedDownloadState.Removed;
                }
                catch (NotSupportedException)
                {
                    _logger.Debug("Removing item not supported by your download client");
                }
            }
        }
        private void UpdateStatusMessage(TrackedDownload trackedDownload, LogLevel logLevel, String message, params object[] args)
        {
            var statusMessage = String.Format(message, args);
            var logMessage = String.Format("[{0}] {1}", trackedDownload.DownloadItem.Title, statusMessage);

            if (trackedDownload.StatusMessage != statusMessage)
            {
                trackedDownload.HasError = logLevel >= LogLevel.Warn;
                trackedDownload.StatusMessage = statusMessage;
                _logger.Log(logLevel, logMessage);
            }
            else
            {
                _logger.Debug(logMessage);
            }
        }
        public void CheckForFailedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> grabbedHistory, List<History.History> failedHistory)
        {
            if (!_configService.EnableFailedDownloadHandling)
            {
                return;
            }

            if (trackedDownload.DownloadItem.IsEncrypted && trackedDownload.State == TrackedDownloadState.Downloading)
            {
                var grabbedItems = GetHistoryItems(grabbedHistory, trackedDownload.DownloadItem.DownloadClientId);

                if (!grabbedItems.Any())
                {
                    _logger.Debug("Download was not grabbed by drone, ignoring.");
                    return;
                }

                trackedDownload.State = TrackedDownloadState.DownloadFailed;

                var failedItems = GetHistoryItems(failedHistory, trackedDownload.DownloadItem.DownloadClientId);

                if (failedItems.Any())
                {
                    _logger.Debug("Already added to history as failed");
                }
                else
                {
                    PublishDownloadFailedEvent(grabbedItems, "Encrypted download detected");
                }
            }

            if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed && trackedDownload.State == TrackedDownloadState.Downloading)
            {
                var grabbedItems = GetHistoryItems(grabbedHistory, trackedDownload.DownloadItem.DownloadClientId);

                if (!grabbedItems.Any())
                {
                    _logger.Debug("Download was not grabbed by drone, ignoring.");
                    return;
                }

                //TODO: Make this more configurable (ignore failure reasons) to support changes and other failures that should be ignored
                if (trackedDownload.DownloadItem.Message.Equals("Unpacking failed, write error or disk is full?",
                    StringComparison.InvariantCultureIgnoreCase))
                {
                    _logger.Debug("Failed due to lack of disk space, do not blacklist");
                    return;
                }

                if (FailedDownloadForRecentRelease(downloadClient, trackedDownload, grabbedItems))
                {
                    _logger.Debug("Recent release Failed, do not blacklist");
                    return;
                }

                trackedDownload.State = TrackedDownloadState.DownloadFailed;

                var failedItems = GetHistoryItems(failedHistory, trackedDownload.DownloadItem.DownloadClientId);

                if (failedItems.Any())
                {
                    _logger.Debug("Already added to history as failed");
                }
                else
                {
                    PublishDownloadFailedEvent(grabbedItems, trackedDownload.DownloadItem.Message);
                }
            }

            if (_configService.RemoveFailedDownloads && trackedDownload.State == TrackedDownloadState.DownloadFailed)
            {
                try
                {
                    _logger.Info("Removing failed download from client: {0}", trackedDownload.DownloadItem.Title);
                    downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId);

                    trackedDownload.State = TrackedDownloadState.Removed;
                }
                catch (NotSupportedException)
                {
                    _logger.Debug("Removing item not supported by your download client");
                }
            }
        }
        public void CheckForCompletedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> grabbedHistory, List<History.History> importedHistory)
        {
            if (!_configService.EnableCompletedDownloadHandling)
            {
                return;
            }

            if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Completed && trackedDownload.State == TrackedDownloadState.Downloading)
            {
                var grabbedItems = GetHistoryItems(grabbedHistory, trackedDownload.DownloadItem.DownloadClientId);

                if (!grabbedItems.Any() && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
                {
                    UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Download wasn't grabbed by drone or not in a category, ignoring download.");
                    return;
                }

                var importedItems = GetHistoryItems(importedHistory, trackedDownload.DownloadItem.DownloadClientId);

                if (importedItems.Any())
                {
                    trackedDownload.State = TrackedDownloadState.Imported;

                    UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Already added to history as imported.");
                }
                else
                {
                    string downloadedEpisodesFolder = _configService.DownloadedEpisodesFolder;
                    string downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath;
                    if (downloadItemOutputPath.IsNullOrWhiteSpace())
                    {
                        UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download doesn't contain intermediate path, ignoring download.");
                        return;
                    }

                    if (!downloadedEpisodesFolder.IsNullOrWhiteSpace() && (downloadedEpisodesFolder.PathEquals(downloadItemOutputPath) || downloadedEpisodesFolder.IsParentPath(downloadItemOutputPath)))
                    {
                        UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Intermediate Download path inside drone factory, ignoring download.");
                        return;
                    }

                    if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath))
                    {
                        var decisions = _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem);

                        if (!decisions.Any())
                        {
                            UpdateStatusMessage(trackedDownload, LogLevel.Error, "No files found eligible for import in {0}", trackedDownload.DownloadItem.OutputPath);
                        }
                        else if (decisions.Any(v => v.Approved))
                        {
                            UpdateStatusMessage(trackedDownload, LogLevel.Info, "Imported {0} files.", decisions.Count(v => v.Approved));

                            trackedDownload.State = TrackedDownloadState.Imported;
                        }
                        else
                        {
                            var rejections = decisions
                                .Where(v => !v.Approved)
                                .Select(v => v.Rejections.Aggregate(Path.GetFileName(v.LocalEpisode.Path), (a, r) => a + "\r\n- " + r))
                                .Aggregate("Failed to import:", (a, r) => a + "\r\n" + r);

                            UpdateStatusMessage(trackedDownload, LogLevel.Error, rejections);
                        }
                    }
                    else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath))
                    {
                        var decisions = _downloadedEpisodesImportService.ProcessFile(new FileInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem);

                        if (!decisions.Any())
                        {
                            UpdateStatusMessage(trackedDownload, LogLevel.Error, "No files found eligible for import in {0}", trackedDownload.DownloadItem.OutputPath);
                        }
                        else if (decisions.Any(v => v.Approved))
                        {
                            UpdateStatusMessage(trackedDownload, LogLevel.Info, "Imported {0} files.", decisions.Count(v => v.Approved));

                            trackedDownload.State = TrackedDownloadState.Imported;
                        }
                        else
                        {
                            var rejections = decisions
                                .Where(v => !v.Approved)
                                .Select(v => v.Rejections.Aggregate(Path.GetFileName(v.LocalEpisode.Path), (a, r) => a + "\r\n- " + r))
                                .Aggregate("Failed to import:", (a, r) => a + "\r\n" + r);

                            UpdateStatusMessage(trackedDownload, LogLevel.Error, rejections);
                        }
                    }
                    else
                    {
                        if (grabbedItems.Any())
                        {
                            var episodeIds = trackedDownload.DownloadItem.RemoteEpisode.Episodes.Select(v => v.Id).ToList();

                            // Check if we can associate it with a previous drone factory import.
                            importedItems = importedHistory.Where(v => v.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID) == null &&
                                                                  episodeIds.Contains(v.EpisodeId) &&
                                                                  v.Data.GetValueOrDefault("droppedPath") != null &&
                                                                  new FileInfo(v.Data["droppedPath"]).Directory.Name == grabbedItems.First().SourceTitle
                                                                  ).ToList();
                            if (importedItems.Count == 1)
                            {
                                var importedFile = new FileInfo(importedItems.First().Data["droppedPath"]);

                                if (importedFile.Directory.Name == grabbedItems.First().SourceTitle)
                                {
                                    trackedDownload.State = TrackedDownloadState.Imported;

                                    importedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT] = grabbedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT];
                                    importedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT_ID] = grabbedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT_ID];
                                    _historyService.UpdateHistoryData(importedItems.First().Id, importedItems.First().Data);

                                    UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Intermediate Download path does not exist, but found probable drone factory ImportEvent.");
                                    return;
                                }
                            }
                        }

                        UpdateStatusMessage(trackedDownload, LogLevel.Error, "Intermediate Download path does not exist: {0}", trackedDownload.DownloadItem.OutputPath);
                        return;
                    }
                }
            }

            if (_configService.RemoveCompletedDownloads && trackedDownload.State == TrackedDownloadState.Imported && !trackedDownload.DownloadItem.IsReadOnly)
            {
                try
                {
                    _logger.Debug("[{0}] Removing completed download from history.", trackedDownload.DownloadItem.Title);
                    downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId);

                    if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath))
                    {
                        _logger.Debug("Removing completed download directory: {0}", trackedDownload.DownloadItem.OutputPath);
                        _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath, true);
                    }
                    else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath))
                    {
                        _logger.Debug("Removing completed download file: {0}", trackedDownload.DownloadItem.OutputPath);
                        _diskProvider.DeleteFile(trackedDownload.DownloadItem.OutputPath);
                    }

                    trackedDownload.State = TrackedDownloadState.Removed;
                }
                catch (NotSupportedException)
                {
                    UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Removing item not supported by your download client.");
                }
            }
        }
        private bool FailedDownloadForRecentRelease(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> matchingHistoryItems)
        {
            double ageHours;

            if (!Double.TryParse(matchingHistoryItems.First().Data.GetValueOrDefault("ageHours"), out ageHours))
            {
                _logger.Debug("Unable to determine age of failed download");
                return false;
            }

            if (ageHours > _configService.BlacklistGracePeriod)
            {
                _logger.Debug("Failed download is older than the grace period");
                return false;
            }

            if (trackedDownload.RetryCount >= _configService.BlacklistRetryLimit)
            {
                _logger.Debug("Retry limit reached");
                return false;
            }

            if (trackedDownload.RetryCount == 0 || trackedDownload.LastRetry.AddMinutes(_configService.BlacklistRetryInterval) < DateTime.UtcNow)
            {
                _logger.Debug("Retrying failed release");
                trackedDownload.LastRetry = DateTime.UtcNow;
                trackedDownload.RetryCount++;

                try
                {
                    downloadClient.RetryDownload(trackedDownload.DownloadItem.DownloadClientId);
                }
                catch (NotSupportedException ex)
                {
                    _logger.Debug("Retrying failed downloads is not supported by your download client");
                    return false;
                }
            }

            return true;
        }