private void WriteTagsInternal(BookFile file, bool updateCover, bool embedMetadata) { if (file.CalibreId == 0) { _logger.Trace($"No calibre id for {file.Path}, skipping writing tags"); } var rootFolder = _rootFolderService.GetBestRootFolder(file.Path); _calibre.SetFields(file, rootFolder.CalibreSettings, updateCover, embedMetadata); }
public void WriteTags(BookFile bookFile, bool newDownload, bool force = false) { if (!force) { if (_configService.WriteBookTags == WriteBookTagsType.NewFiles && !newDownload) { return; } } _logger.Debug($"Writing tags for {bookFile}"); var rootFolder = _rootFolderService.GetBestRootFolder(bookFile.Path); _calibre.SetFields(bookFile, rootFolder.CalibreSettings, _configService.UpdateCovers, _configService.EmbedMetadata); }
private Artist EnsureArtistAdded(List <ImportDecision <LocalTrack> > decisions, List <Artist> addedArtists) { var artist = decisions.First().Item.Artist; if (artist.Id == 0) { var dbArtist = _artistService.FindById(artist.ForeignArtistId); if (dbArtist == null) { _logger.Debug($"Adding remote artist {artist}"); var rootFolder = _rootFolderService.GetBestRootFolder(decisions.First().Item.Path); artist.RootFolderPath = rootFolder.Path; artist.MetadataProfileId = rootFolder.DefaultMetadataProfileId; artist.QualityProfileId = rootFolder.DefaultQualityProfileId; artist.Monitored = rootFolder.DefaultMonitorOption != MonitorTypes.None; artist.Tags = rootFolder.DefaultTags; artist.AddOptions = new AddArtistOptions { SearchForMissingAlbums = false, Monitored = artist.Monitored, Monitor = rootFolder.DefaultMonitorOption }; try { dbArtist = _addArtistService.AddArtist(artist, false); addedArtists.Add(dbArtist); } catch (Exception e) { _logger.Error(e, "Failed to add artist {0}", artist); foreach (var decision in decisions) { decision.Reject(new Rejection("Failed to add missing artist", RejectionType.Temporary)); } return(null); } } // Put in the newly loaded artist foreach (var decision in decisions) { decision.Item.Artist = dbArtist; decision.Item.Album.Artist = dbArtist; decision.Item.Album.ArtistMetadataId = dbArtist.ArtistMetadataId; } artist = dbArtist; } return(artist); }
private void EnsureData(LocalAlbumRelease release) { if (release.AlbumRelease != null && release.AlbumRelease.Album.Value.Artist.Value.QualityProfileId == 0) { var rootFolder = _rootFolderService.GetBestRootFolder(release.LocalTracks.First().Path); var qualityProfile = _qualityProfileService.Get(rootFolder.DefaultQualityProfileId); var artist = release.AlbumRelease.Album.Value.Artist.Value; artist.QualityProfileId = qualityProfile.Id; artist.QualityProfile = qualityProfile; } }
private void EnsureData(LocalEdition edition) { if (edition.Edition != null && edition.Edition.Book.Value.Author.Value.QualityProfileId == 0) { var rootFolder = _rootFolderService.GetBestRootFolder(edition.LocalBooks.First().Path); var qualityProfile = _qualityProfileService.Get(rootFolder.DefaultQualityProfileId); var author = edition.Edition.Book.Value.Author.Value; author.QualityProfileId = qualityProfile.Id; author.QualityProfile = qualityProfile; } }
private void DeleteFile(BookFile bookFile, string subfolder = "") { var rootFolder = _rootFolderService.GetBestRootFolder(bookFile.Path); var isCalibre = rootFolder.IsCalibreLibrary && rootFolder.CalibreSettings != null; try { if (!isCalibre) { _recycleBinProvider.DeleteFile(bookFile.Path, subfolder); } else { _calibre.DeleteBook(bookFile, rootFolder.CalibreSettings); } } catch (Exception e) { _logger.Error(e, "Unable to delete book file"); throw new NzbDroneClientException(HttpStatusCode.InternalServerError, "Unable to delete book file"); } }
public Decision IsSatisfiedBy(LocalEdition item, DownloadClientItem downloadClientItem) { // Prevent imports to artists that are no longer inside a root folder Readarr manages var author = item.Edition.Book.Value.Author.Value; // a new author will have empty path, and will end up having path assinged based on file location var pathToCheck = author.Path.IsNotNullOrWhiteSpace() ? author.Path : item.LocalBooks.First().Path.GetParentPath(); if (_rootFolderService.GetBestRootFolder(pathToCheck) == null) { _logger.Warn($"Destination folder {pathToCheck} not in a Root Folder, skipping import"); return(Decision.Reject($"Destination folder {pathToCheck} is not in a Root Folder")); } return(Decision.Accept()); }
public List <ImportResult> Import(List <ImportDecision <LocalBook> > decisions, bool replaceExisting, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto) { var importResults = new List <ImportResult>(); var allImportedTrackFiles = new List <BookFile>(); var allOldTrackFiles = new List <BookFile>(); var addedAuthors = new List <Author>(); var bookDecisions = decisions.Where(e => e.Item.Book != null && e.Approved) .GroupBy(e => e.Item.Book.ForeignBookId).ToList(); var iDecision = 1; foreach (var albumDecision in bookDecisions) { _logger.ProgressInfo($"Importing book {iDecision++}/{bookDecisions.Count} {albumDecision.First().Item.Book}"); var decisionList = albumDecision.ToList(); var author = EnsureAuthorAdded(decisionList, addedAuthors); if (author == null) { // failed to add the author, carry on with next book continue; } var book = EnsureBookAdded(decisionList); if (book == null) { // failed to add the book, carry on with next one continue; } if (replaceExisting) { RemoveExistingTrackFiles(author, book); } // set the correct release to be monitored before importing the new files var newRelease = albumDecision.First().Item.Edition; _logger.Debug("Updating release to {0}", newRelease); book.Editions = _editionService.SetMonitored(newRelease); // Publish book edited event. // Deliberatly don't put in the old book since we don't want to trigger an ArtistScan. _eventAggregator.PublishEvent(new BookEditedEvent(book, book)); } var qualifiedImports = decisions.Where(c => c.Approved) .GroupBy(c => c.Item.Author.Id, (i, s) => s .OrderByDescending(c => c.Item.Quality, new QualityModelComparer(s.First().Item.Author.QualityProfile)) .ThenByDescending(c => c.Item.Size)) .SelectMany(c => c) .ToList(); _logger.ProgressInfo($"Importing {qualifiedImports.Count} files"); _logger.Debug($"Importing {qualifiedImports.Count} files. replaceExisting: {replaceExisting}"); var filesToAdd = new List <BookFile>(qualifiedImports.Count); var trackImportedEvents = new List <TrackImportedEvent>(qualifiedImports.Count); foreach (var importDecision in qualifiedImports.OrderByDescending(e => e.Item.Size)) { var localTrack = importDecision.Item; var oldFiles = new List <BookFile>(); try { //check if already imported if (importResults.Select(r => r.ImportDecision.Item.Book.Id).Contains(localTrack.Book.Id)) { if (!(localTrack.FileTrackInfo.TrackNumbers != null && localTrack.FileTrackInfo.TrackNumbers.Any())) { importResults.Add(new ImportResult(importDecision, "Book has already been imported")); continue; } else { var matchingImportResults = importResults.Where(r => r.ImportDecision.Item.Book.Id == localTrack.Book.Id); if (matchingImportResults.Select(r => r.ImportDecision.Item.FileTrackInfo.TrackNumbers).Contains(localTrack.FileTrackInfo.TrackNumbers)) { importResults.Add(new ImportResult(importDecision, "Audiobook track has already been imported")); continue; } } } localTrack.Book.Author = localTrack.Author; var bookFile = new BookFile { Path = localTrack.Path.CleanFilePath(), Size = localTrack.Size, Modified = localTrack.Modified, DateAdded = DateTime.UtcNow, ReleaseGroup = localTrack.ReleaseGroup, Quality = localTrack.Quality, MediaInfo = localTrack.FileTrackInfo.MediaInfo, EditionId = localTrack.Edition.Id, Author = localTrack.Author, Edition = localTrack.Edition }; bool copyOnly; switch (importMode) { default: case ImportMode.Auto: copyOnly = downloadClientItem != null && !downloadClientItem.CanMoveFiles; break; case ImportMode.Move: copyOnly = false; break; case ImportMode.Copy: copyOnly = true; break; } if (!localTrack.ExistingFile) { bookFile.SceneName = GetSceneReleaseName(downloadClientItem); var moveResult = _bookFileUpgrader.UpgradeBookFile(bookFile, localTrack, copyOnly); oldFiles = moveResult.OldFiles; } else { // Delete existing files from the DB mapped to this path var previousFile = _mediaFileService.GetFileWithPath(bookFile.Path); if (previousFile != null) { _mediaFileService.Delete(previousFile, DeleteMediaFileReason.ManualOverride); } var rootFolder = _rootFolderService.GetBestRootFolder(localTrack.Path); if (rootFolder.IsCalibreLibrary) { bookFile.CalibreId = bookFile.Path.ParseCalibreId(); } _audioTagService.WriteTags(bookFile, false); } filesToAdd.Add(bookFile); importResults.Add(new ImportResult(importDecision)); if (!localTrack.ExistingFile) { _extraService.ImportTrack(localTrack, bookFile, copyOnly); } allImportedTrackFiles.Add(bookFile); allOldTrackFiles.AddRange(oldFiles); // create all the import events here, but we can't publish until the trackfiles have been // inserted and ids created trackImportedEvents.Add(new TrackImportedEvent(localTrack, bookFile, oldFiles, !localTrack.ExistingFile, downloadClientItem)); } catch (RootFolderNotFoundException e) { _logger.Warn(e, "Couldn't import book " + localTrack); _eventAggregator.PublishEvent(new TrackImportFailedEvent(e, localTrack, !localTrack.ExistingFile, downloadClientItem)); importResults.Add(new ImportResult(importDecision, "Failed to import book, Root folder missing.")); } catch (DestinationAlreadyExistsException e) { _logger.Warn(e, "Couldn't import book " + localTrack); importResults.Add(new ImportResult(importDecision, "Failed to import book, Destination already exists.")); } catch (UnauthorizedAccessException e) { _logger.Warn(e, "Couldn't import book " + localTrack); _eventAggregator.PublishEvent(new TrackImportFailedEvent(e, localTrack, !localTrack.ExistingFile, downloadClientItem)); importResults.Add(new ImportResult(importDecision, "Failed to import book, Permissions error")); } catch (Exception) { throw; } } var watch = new System.Diagnostics.Stopwatch(); watch.Start(); _mediaFileService.AddMany(filesToAdd); _logger.Debug($"Inserted new trackfiles in {watch.ElapsedMilliseconds}ms"); // now that trackfiles have been inserted and ids generated, publish the import events foreach (var trackImportedEvent in trackImportedEvents) { _eventAggregator.PublishEvent(trackImportedEvent); } var albumImports = importResults.Where(e => e.ImportDecision.Item.Book != null) .GroupBy(e => e.ImportDecision.Item.Book.Id).ToList(); foreach (var albumImport in albumImports) { var book = albumImport.First().ImportDecision.Item.Book; var author = albumImport.First().ImportDecision.Item.Author; if (albumImport.Where(e => e.Errors.Count == 0).ToList().Count > 0 && author != null && book != null) { _eventAggregator.PublishEvent(new BookImportedEvent( author, book, allImportedTrackFiles.Where(s => s.EditionId == book.Id).ToList(), allOldTrackFiles.Where(s => s.EditionId == book.Id).ToList(), replaceExisting, downloadClientItem)); } } //Adding all the rejected decisions importResults.AddRange(decisions.Where(c => !c.Approved) .Select(d => new ImportResult(d, d.Rejections.Select(r => r.Reason).ToArray()))); // Refresh any artists we added if (addedAuthors.Any()) { _commandQueueManager.Push(new BulkRefreshAuthorCommand(addedAuthors.Select(x => x.Id).ToList(), true)); } return(importResults); }
public void Scan(List <string> folders = null, FilterFilesType filter = FilterFilesType.Known, bool addNewAuthors = false, List <int> authorIds = null) { if (folders == null) { folders = _rootFolderService.All().Select(x => x.Path).ToList(); } if (authorIds == null) { authorIds = new List <int>(); } var mediaFileList = new List <IFileInfo>(); var musicFilesStopwatch = Stopwatch.StartNew(); foreach (var folder in folders) { // We could be scanning a root folder or a subset of a root folder. If it's a subset, // check if the root folder exists before cleaning. var rootFolder = _rootFolderService.GetBestRootFolder(folder); if (rootFolder == null) { _logger.Error("Not scanning {0}, it's not a subdirectory of a defined root folder", folder); return; } var folderExists = _diskProvider.FolderExists(folder); if (!folderExists) { if (!_diskProvider.FolderExists(rootFolder.Path)) { _logger.Warn("Authors' root folder ({0}) doesn't exist.", rootFolder); var skippedAuthors = _authorService.GetAuthors(authorIds); skippedAuthors.ForEach(x => _eventAggregator.PublishEvent(new AuthorScanSkippedEvent(x, AuthorScanSkippedReason.RootFolderDoesNotExist))); return; } if (_diskProvider.FolderEmpty(rootFolder.Path)) { _logger.Warn("Authors' root folder ({0}) is empty.", rootFolder); var skippedAuthors = _authorService.GetAuthors(authorIds); skippedAuthors.ForEach(x => _eventAggregator.PublishEvent(new AuthorScanSkippedEvent(x, AuthorScanSkippedReason.RootFolderIsEmpty))); return; } } if (!folderExists) { _logger.Debug("Specified scan folder ({0}) doesn't exist.", folder); CleanMediaFiles(folder, new List <string>()); continue; } _logger.ProgressInfo("Scanning {0}", folder); var files = FilterFiles(folder, GetBookFiles(folder)); if (!files.Any()) { _logger.Warn("Scan folder {0} is empty.", folder); continue; } CleanMediaFiles(folder, files.Select(x => x.FullName).ToList()); mediaFileList.AddRange(files); } musicFilesStopwatch.Stop(); _logger.Trace("Finished getting track files for:\n{0} [{1}]", folders.ConcatToString("\n"), musicFilesStopwatch.Elapsed); var decisionsStopwatch = Stopwatch.StartNew(); var config = new ImportDecisionMakerConfig { Filter = filter, IncludeExisting = true, AddNewAuthors = addNewAuthors }; var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, null, null, config); decisionsStopwatch.Stop(); _logger.Debug("Import decisions complete [{0}]", decisionsStopwatch.Elapsed); var importStopwatch = Stopwatch.StartNew(); _importApprovedTracks.Import(decisions, false); // decisions may have been filtered to just new files. Anything new and approved will have been inserted. // Now we need to make sure anything new but not approved gets inserted // Note that knownFiles will include anything imported just now var knownFiles = new List <BookFile>(); folders.ForEach(x => knownFiles.AddRange(_mediaFileService.GetFilesWithBasePath(x))); var newFiles = decisions .ExceptBy(x => x.Item.Path, knownFiles, x => x.Path, PathEqualityComparer.Instance) .Select(decision => new BookFile { Path = decision.Item.Path, CalibreId = decision.Item.Path.ParseCalibreId(), Size = decision.Item.Size, Modified = decision.Item.Modified, DateAdded = DateTime.UtcNow, Quality = decision.Item.Quality, MediaInfo = decision.Item.FileTrackInfo.MediaInfo, Edition = decision.Item.Edition }) .ToList(); _mediaFileService.AddMany(newFiles); _logger.Debug($"Inserted {newFiles.Count} new unmatched trackfiles"); // finally update info on size/modified for existing files var updatedFiles = knownFiles .Join(decisions, x => x.Path, x => x.Item.Path, (file, decision) => new { File = file, Item = decision.Item }, PathEqualityComparer.Instance) .Where(x => x.File.Size != x.Item.Size || Math.Abs((x.File.Modified - x.Item.Modified).TotalSeconds) > 1) .Select(x => { x.File.Size = x.Item.Size; x.File.Modified = x.Item.Modified; x.File.MediaInfo = x.Item.FileTrackInfo.MediaInfo; x.File.Quality = x.Item.Quality; return(x.File); }) .ToList(); _mediaFileService.Update(updatedFiles); _logger.Debug($"Updated info for {updatedFiles.Count} known files"); var authors = _authorService.GetAuthors(authorIds); foreach (var author in authors) { CompletedScanning(author); } importStopwatch.Stop(); _logger.Debug("Book import complete for:\n{0} [{1}]", folders.ConcatToString("\n"), importStopwatch.Elapsed); }
public BookFileMoveResult UpgradeBookFile(BookFile bookFile, LocalBook localBook, bool copyOnly = false) { var moveFileResult = new BookFileMoveResult(); var existingFiles = localBook.Book.BookFiles.Value; var rootFolderPath = _diskProvider.GetParentFolder(localBook.Author.Path); var rootFolder = _rootFolderService.GetBestRootFolder(rootFolderPath); var isCalibre = rootFolder.IsCalibreLibrary && rootFolder.CalibreSettings != null; var settings = rootFolder.CalibreSettings; // If there are existing book files and the root folder is missing, throw, so the old file isn't left behind during the import process. if (existingFiles.Any() && !_diskProvider.FolderExists(rootFolderPath)) { throw new RootFolderNotFoundException($"Root folder '{rootFolderPath}' was not found."); } foreach (var file in existingFiles) { var bookFilePath = file.Path; var subfolder = rootFolderPath.GetRelativePath(_diskProvider.GetParentFolder(bookFilePath)); bookFile.CalibreId = file.CalibreId; if (_diskProvider.FileExists(bookFilePath)) { _logger.Debug("Removing existing book file: {0} CalibreId: {1}", file, file.CalibreId); if (!isCalibre) { _recycleBinProvider.DeleteFile(bookFilePath, subfolder); } else { var existing = _calibre.GetBook(file.CalibreId, settings); var existingFormats = existing.Formats.Keys; _logger.Debug($"Removing existing formats {existingFormats.ConcatToString()} from calibre"); _calibre.RemoveFormats(file.CalibreId, existingFormats, settings); } } moveFileResult.OldFiles.Add(file); _mediaFileService.Delete(file, DeleteMediaFileReason.Upgrade); } if (!isCalibre) { if (copyOnly) { moveFileResult.BookFile = _bookFileMover.CopyBookFile(bookFile, localBook); } else { moveFileResult.BookFile = _bookFileMover.MoveBookFile(bookFile, localBook); } _metadataTagService.WriteTags(bookFile, true); } else { var source = bookFile.Path; moveFileResult.BookFile = _calibre.AddAndConvert(bookFile, settings); if (!copyOnly) { _diskProvider.DeleteFile(source); } } return(moveFileResult); }
public void Execute(ManualImportCommand message) { _logger.ProgressTrace("Manually importing {0} files using mode {1}", message.Files.Count, message.ImportMode); var imported = new List <ImportResult>(); var importedTrackedDownload = new List <ManuallyImportedFile>(); var albumIds = message.Files.GroupBy(e => e.AlbumId).ToList(); var fileCount = 0; foreach (var importAlbumId in albumIds) { var albumImportDecisions = new List <ImportDecision <LocalTrack> >(); // turn off anyReleaseOk if specified if (importAlbumId.First().DisableReleaseSwitching) { var album = _albumService.GetAlbum(importAlbumId.First().AlbumId); album.AnyReleaseOk = false; _albumService.UpdateAlbum(album); } foreach (var file in importAlbumId) { _logger.ProgressTrace("Processing file {0} of {1}", fileCount + 1, message.Files.Count); var artist = _artistService.GetArtist(file.ArtistId); var album = _albumService.GetAlbum(file.AlbumId); var release = _releaseService.GetRelease(file.AlbumReleaseId); var tracks = _trackService.GetTracks(file.TrackIds); var fileTrackInfo = _audioTagService.ReadTags(file.Path) ?? new ParsedTrackInfo(); var fileInfo = _diskProvider.GetFileInfo(file.Path); var localTrack = new LocalTrack { ExistingFile = artist.Path.IsParentPath(file.Path), Tracks = tracks, FileTrackInfo = fileTrackInfo, Path = file.Path, Size = fileInfo.Length, Modified = fileInfo.LastWriteTimeUtc, Quality = file.Quality, Artist = artist, Album = album, Release = release }; var importDecision = new ImportDecision <LocalTrack>(localTrack); if (_rootFolderService.GetBestRootFolder(artist.Path) == null) { _logger.Warn($"Destination artist folder {artist.Path} not in a Root Folder, skipping import"); importDecision.Reject(new Rejection($"Destination artist folder {artist.Path} is not in a Root Folder")); } albumImportDecisions.Add(importDecision); fileCount += 1; } var downloadId = importAlbumId.Select(x => x.DownloadId).FirstOrDefault(x => x.IsNotNullOrWhiteSpace()); if (downloadId.IsNullOrWhiteSpace()) { imported.AddRange(_importApprovedTracks.Import(albumImportDecisions, message.ReplaceExistingFiles, null, message.ImportMode)); } else { var trackedDownload = _trackedDownloadService.Find(downloadId); var importResults = _importApprovedTracks.Import(albumImportDecisions, message.ReplaceExistingFiles, trackedDownload.DownloadItem, message.ImportMode); imported.AddRange(importResults); foreach (var importResult in importResults) { importedTrackedDownload.Add(new ManuallyImportedFile { TrackedDownload = trackedDownload, ImportResult = importResult }); } } } _logger.ProgressTrace("Manually imported {0} files", imported.Count); foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList()) { var trackedDownload = groupedTrackedDownload.First().TrackedDownload; var importArtist = groupedTrackedDownload.First().ImportResult.ImportDecision.Item.Artist; var outputPath = trackedDownload.ImportItem.OutputPath.FullPath; if (_diskProvider.FolderExists(outputPath)) { if (_downloadedTracksImportService.ShouldDeleteFolder( _diskProvider.GetDirectoryInfo(outputPath), importArtist) && trackedDownload.DownloadItem.CanMoveFiles) { _diskProvider.DeleteFolder(outputPath, true); } } if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteAlbum.Albums.Count)) { trackedDownload.State = TrackedDownloadState.Imported; _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload, importArtist.Id)); } } }
private Author EnsureAuthorAdded(List <ImportDecision <LocalBook> > decisions, List <Author> addedAuthors) { var author = decisions.First().Item.Author; if (author.Id == 0) { var dbAuthor = _authorService.FindById(author.ForeignAuthorId); if (dbAuthor == null) { _logger.Debug($"Adding remote author {author}"); var path = decisions.First().Item.Path; var rootFolder = _rootFolderService.GetBestRootFolder(path); author.RootFolderPath = rootFolder.Path; author.MetadataProfileId = rootFolder.DefaultMetadataProfileId; author.QualityProfileId = rootFolder.DefaultQualityProfileId; author.Monitored = rootFolder.DefaultMonitorOption != MonitorTypes.None; author.Tags = rootFolder.DefaultTags; author.AddOptions = new AddAuthorOptions { SearchForMissingBooks = false, Monitored = author.Monitored, Monitor = rootFolder.DefaultMonitorOption }; if (rootFolder.IsCalibreLibrary) { // calibre has author / book / files author.Path = path.GetParentPath().GetParentPath(); } try { dbAuthor = _addAuthorService.AddAuthor(author, false); addedAuthors.Add(dbAuthor); } catch (Exception e) { _logger.Error(e, "Failed to add author {0}", author); foreach (var decision in decisions) { decision.Reject(new Rejection("Failed to add missing author", RejectionType.Temporary)); } return(null); } } // Put in the newly loaded author foreach (var decision in decisions) { decision.Item.Author = dbAuthor; decision.Item.Book.Author = dbAuthor; decision.Item.Book.AuthorMetadataId = dbAuthor.AuthorMetadataId; } author = dbAuthor; } return(author); }
public void Execute(ManualImportCommand message) { _logger.ProgressTrace("Manually importing {0} files using mode {1}", message.Files.Count, message.ImportMode); var imported = new List <ImportResult>(); var importedTrackedDownload = new List <ManuallyImportedFile>(); var bookIds = message.Files.GroupBy(e => e.BookId).ToList(); var fileCount = 0; foreach (var importBookId in bookIds) { var bookImportDecisions = new List <ImportDecision <LocalBook> >(); // turn off anyReleaseOk if specified if (importBookId.First().DisableReleaseSwitching) { var book = _bookService.GetBook(importBookId.First().BookId); book.AnyEditionOk = false; _bookService.UpdateBook(book); } foreach (var file in importBookId) { _logger.ProgressTrace("Processing file {0} of {1}", fileCount + 1, message.Files.Count); var author = _authorService.GetAuthor(file.AuthorId); var book = _bookService.GetBook(file.BookId); var edition = _editionService.GetEditionByForeignEditionId(file.ForeignEditionId); if (edition == null) { var tuple = _bookInfo.GetBookInfo(book.ForeignBookId); edition = tuple.Item2.Editions.Value.SingleOrDefault(x => x.ForeignEditionId == file.ForeignEditionId); } var fileRootFolder = _rootFolderService.GetBestRootFolder(file.Path); var fileInfo = _diskProvider.GetFileInfo(file.Path); var fileTrackInfo = _metadataTagService.ReadTags(fileInfo) ?? new ParsedTrackInfo(); var localTrack = new LocalBook { ExistingFile = fileRootFolder != null, FileTrackInfo = fileTrackInfo, Path = file.Path, Part = fileTrackInfo.TrackNumbers.Any() ? fileTrackInfo.TrackNumbers.First() : 1, PartCount = importBookId.Count(), Size = fileInfo.Length, Modified = fileInfo.LastWriteTimeUtc, Quality = file.Quality, Author = author, Book = book, Edition = edition }; var importDecision = new ImportDecision <LocalBook>(localTrack); if (_rootFolderService.GetBestRootFolder(author.Path) == null) { _logger.Warn($"Destination author folder {author.Path} not in a Root Folder, skipping import"); importDecision.Reject(new Rejection($"Destination author folder {author.Path} is not in a Root Folder")); } bookImportDecisions.Add(importDecision); fileCount += 1; } var downloadId = importBookId.Select(x => x.DownloadId).FirstOrDefault(x => x.IsNotNullOrWhiteSpace()); if (downloadId.IsNullOrWhiteSpace()) { imported.AddRange(_importApprovedBooks.Import(bookImportDecisions, message.ReplaceExistingFiles, null, message.ImportMode)); } else { var trackedDownload = _trackedDownloadService.Find(downloadId); var importResults = _importApprovedBooks.Import(bookImportDecisions, message.ReplaceExistingFiles, trackedDownload.DownloadItem, message.ImportMode); imported.AddRange(importResults); foreach (var importResult in importResults) { importedTrackedDownload.Add(new ManuallyImportedFile { TrackedDownload = trackedDownload, ImportResult = importResult }); } } } _logger.ProgressTrace("Manually imported {0} files", imported.Count); foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList()) { var trackedDownload = groupedTrackedDownload.First().TrackedDownload; var outputPath = trackedDownload.ImportItem.OutputPath.FullPath; if (_diskProvider.FolderExists(outputPath)) { if (_downloadedTracksImportService.ShouldDeleteFolder(_diskProvider.GetDirectoryInfo(outputPath)) && trackedDownload.DownloadItem.CanMoveFiles) { _diskProvider.DeleteFolder(outputPath, true); } } var importedCount = groupedTrackedDownload.Select(c => c.ImportResult) .Count(c => c.Result == ImportResultType.Imported); var downloadItemCount = Math.Max(1, trackedDownload.RemoteBook?.Books.Count ?? 1); var allItemsImported = importedCount >= downloadItemCount; if (allItemsImported) { trackedDownload.State = TrackedDownloadState.Imported; _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload, imported.First().ImportDecision.Item.Author.Id)); } } }