using System; using System.Collections.Generic; using System.IO; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.EnsureThat; using NzbDrone.Common.Extensions; using NzbDrone.Core.Books; using NzbDrone.Core.Configuration; using NzbDrone.Core.MediaFiles.BookImport; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.MediaFiles { public interface IMoveBookFiles { BookFile MoveBookFile(BookFile bookFile, Author author); BookFile MoveBookFile(BookFile bookFile, LocalBook localBook); BookFile CopyBookFile(BookFile bookFile, LocalBook localBook); } public class BookFileMovingService : IMoveBookFiles { private readonly IBookService _bookService; private readonly IUpdateBookFileService _updateBookFileService; private readonly IBuildFileNames _buildFileNames; private readonly IDiskTransferService _diskTransferService; private readonly IDiskProvider _diskProvider; private readonly IRootFolderWatchingService _rootFolderWatchingService; private readonly IMediaFileAttributeService _mediaFileAttributeService; private readonly IEventAggregator _eventAggregator; private readonly IConfigService _configService; private readonly Logger _logger; public BookFileMovingService(IBookService bookService, IUpdateBookFileService updateBookFileService, IBuildFileNames buildFileNames, IDiskTransferService diskTransferService, IDiskProvider diskProvider, IRootFolderWatchingService rootFolderWatchingService, IMediaFileAttributeService mediaFileAttributeService, IEventAggregator eventAggregator, IConfigService configService, Logger logger) { _bookService = bookService; _updateBookFileService = updateBookFileService; _buildFileNames = buildFileNames; _diskTransferService = diskTransferService; _diskProvider = diskProvider; _rootFolderWatchingService = rootFolderWatchingService; _mediaFileAttributeService = mediaFileAttributeService; _eventAggregator = eventAggregator; _configService = configService; _logger = logger; } public BookFile MoveBookFile(BookFile bookFile, Author author) { var book = _bookService.GetBook(bookFile.EditionId); var newFileName = _buildFileNames.BuildBookFileName(author, bookFile.Edition.Value, bookFile); var filePath = _buildFileNames.BuildBookFilePath(author, bookFile.Edition.Value, newFileName, Path.GetExtension(bookFile.Path)); EnsureBookFolder(bookFile, author, book, filePath); _logger.Debug("Renaming book file: {0} to {1}", bookFile, filePath); return TransferFile(bookFile, author, null, filePath, TransferMode.Move); } public BookFile MoveBookFile(BookFile bookFile, LocalBook localBook) { var newFileName = _buildFileNames.BuildBookFileName(localBook.Author, localBook.Edition, bookFile); var filePath = _buildFileNames.BuildBookFilePath(localBook.Author, localBook.Edition, newFileName, Path.GetExtension(localBook.Path)); EnsureTrackFolder(bookFile, localBook, filePath); _logger.Debug("Moving book file: {0} to {1}", bookFile.Path, filePath); return TransferFile(bookFile, localBook.Author, null, filePath, TransferMode.Move); } public BookFile CopyBookFile(BookFile bookFile, LocalBook localBook) { var newFileName = _buildFileNames.BuildBookFileName(localBook.Author, localBook.Edition, bookFile); var filePath = _buildFileNames.BuildBookFilePath(localBook.Author, localBook.Edition, newFileName, Path.GetExtension(localBook.Path)); EnsureTrackFolder(bookFile, localBook, filePath); if (_configService.CopyUsingHardlinks) { _logger.Debug("Hardlinking book file: {0} to {1}", bookFile.Path, filePath); return TransferFile(bookFile, localBook.Author, localBook.Book, filePath, TransferMode.HardLinkOrCopy); } _logger.Debug("Copying book file: {0} to {1}", bookFile.Path, filePath); return TransferFile(bookFile, localBook.Author, localBook.Book, filePath, TransferMode.Copy); } private BookFile TransferFile(BookFile bookFile, Author author, Book book, string destinationFilePath, TransferMode mode) { Ensure.That(bookFile, () => bookFile).IsNotNull(); Ensure.That(author, () => author).IsNotNull(); Ensure.That(destinationFilePath, () => destinationFilePath).IsValidPath(); var bookFilePath = bookFile.Path; if (!_diskProvider.FileExists(bookFilePath)) { throw new FileNotFoundException("Book file path does not exist", bookFilePath); } if (bookFilePath == destinationFilePath) { throw new SameFilenameException("File not moved, source and destination are the same", bookFilePath); } _rootFolderWatchingService.ReportFileSystemChangeBeginning(bookFilePath, destinationFilePath); _diskTransferService.TransferFile(bookFilePath, destinationFilePath, mode); bookFile.Path = destinationFilePath; _updateBookFileService.ChangeFileDateForFile(bookFile, author, book); try { _mediaFileAttributeService.SetFolderLastWriteTime(author.Path, bookFile.DateAdded); } catch (Exception ex) { _logger.Warn(ex, "Unable to set last write time"); } _mediaFileAttributeService.SetFilePermissions(destinationFilePath); return bookFile; } private void EnsureTrackFolder(BookFile bookFile, LocalBook localBook, string filePath) { EnsureBookFolder(bookFile, localBook.Author, localBook.Book, filePath); } private void EnsureBookFolder(BookFile bookFile, Author author, Book book, string filePath) { var trackFolder = Path.GetDirectoryName(filePath); var bookFolder = _buildFileNames.BuildBookPath(author); var authorFolder = author.Path; var rootFolder = new OsPath(authorFolder).Directory.FullPath; if (!_diskProvider.FolderExists(rootFolder)) { throw new RootFolderNotFoundException(string.Format("Root folder '{0}' was not found.", rootFolder)); } var changed = false; var newEvent = new TrackFolderCreatedEvent(author, bookFile); _rootFolderWatchingService.ReportFileSystemChangeBeginning(authorFolder, bookFolder, trackFolder); if (!_diskProvider.FolderExists(authorFolder)) { CreateFolder(authorFolder); newEvent.AuthorFolder = authorFolder; changed = true; } if (authorFolder != bookFolder && !_diskProvider.FolderExists(bookFolder)) { CreateFolder(bookFolder); newEvent.BookFolder = bookFolder; changed = true; } if (bookFolder != trackFolder && !_diskProvider.FolderExists(trackFolder)) { CreateFolder(trackFolder); newEvent.TrackFolder = trackFolder; changed = true; } if (changed) { _eventAggregator.PublishEvent(newEvent); } } private void CreateFolder(string directoryName) { Ensure.That(directoryName, () => directoryName).IsNotNullOrWhiteSpace(); var parentFolder = new OsPath(directoryName).Directory.FullPath; if (!_diskProvider.FolderExists(parentFolder)) { CreateFolder(parentFolder); } try { _diskProvider.CreateFolder(directoryName); } catch (IOException ex) { _logger.Error(ex, "Unable to create directory: {0}", directoryName); } _mediaFileAttributeService.SetFolderPermissions(directoryName); } } }