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 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 addedBooks = new List <Book>(); var bookDecisions = decisions.Where(e => e.Item.Book != null && e.Approved) .GroupBy(e => e.Item.Book.ForeignBookId).ToList(); var iDecision = 1; foreach (var bookDecision in bookDecisions) { _logger.ProgressInfo($"Importing book {iDecision++}/{bookDecisions.Count} {bookDecision.First().Item.Book}"); var decisionList = bookDecision.ToList(); var author = EnsureAuthorAdded(decisionList, addedAuthors); if (author == null) { // failed to add the author, carry on with next book continue; } var book = EnsureBookAdded(decisionList, addedBooks); if (book == null) { // failed to add the book, carry on with next one continue; } var edition = EnsureEditionAdded(decisionList); if (edition == null) { // failed to add the edition, carry on with next one continue; } // if (replaceExisting) // { // RemoveExistingTrackFiles(author, book); // } // make sure part numbers are populated for audio books // If all audio files and all part numbers are zero, set them by filename order if (decisionList.All(b => MediaFileExtensions.AudioExtensions.Contains(Path.GetExtension(b.Item.Path)) && b.Item.Part == 0)) { var part = 1; foreach (var d in decisionList.OrderBy(x => PadNumbers.Replace(x.Item.Path))) { d.Item.Part = part++; } } // set the correct release to be monitored before importing the new files var newRelease = bookDecision.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 AuthorScan. _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) { var localTrack = importDecision.Item; var oldFiles = new List <BookFile>(); try { //check if already imported if (importResults.Where(r => r.ImportDecision.Item.Book.Id == localTrack.Book.Id).Any(r => r.ImportDecision.Item.Part == localTrack.Part)) { importResults.Add(new ImportResult(importDecision, "Book has already been imported")); continue; } localTrack.Book.Author = localTrack.Author; var bookFile = new BookFile { Path = localTrack.Path.CleanFilePath(), CalibreId = localTrack.CalibreId, Part = localTrack.Part, PartCount = localTrack.PartCount, 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); if (bookFile.CalibreId == 0 && previousFile.CalibreId != 0) { bookFile.CalibreId = previousFile.CalibreId; } } _metadataTagService.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 (CalibreException e) { _logger.Warn(e, "Couldn't import book " + localTrack); importResults.Add(new ImportResult(importDecision, "Failed to import book, error communicating with Calibre. Check log for details.")); } catch (Exception e) { _logger.Warn(e, "Couldn't import book " + localTrack); importResults.Add(new ImportResult(importDecision, "Failed to import book.")); } } 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 bookImports = importResults.Where(e => e.ImportDecision.Item.Book != null) .GroupBy(e => e.ImportDecision.Item.Book.Id).ToList(); foreach (var bookImport in bookImports) { var book = bookImport.First().ImportDecision.Item.Book; var edition = book.Editions.Value.Single(x => x.Monitored); var author = bookImport.First().ImportDecision.Item.Author; if (bookImport.Where(e => e.Errors.Count == 0).ToList().Count > 0 && author != null && book != null) { _eventAggregator.PublishEvent(new BookImportedEvent( author, book, allImportedTrackFiles.Where(s => s.EditionId == edition.Id).ToList(), allOldTrackFiles.Where(s => s.EditionId == edition.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 authors we added if (addedAuthors.Any()) { _commandQueueManager.Push(new BulkRefreshAuthorCommand(addedAuthors.Select(x => x.Id).ToList(), true)); } var addedAuthorMetadataIds = addedAuthors.Select(x => x.AuthorMetadataId).ToHashSet(); var booksToRefresh = addedBooks.Where(x => !addedAuthorMetadataIds.Contains(x.AuthorMetadataId)).ToList(); if (booksToRefresh.Any()) { _logger.Debug($"Refreshing info for {booksToRefresh.Count} new books"); _commandQueueManager.Push(new BulkRefreshBookCommand(booksToRefresh.Select(x => x.Id).ToList())); } return(importResults); }