using System.Collections.Generic; using System.IO; using System.Linq; using Dapper; using NzbDrone.Common; using NzbDrone.Core.Books; using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.MediaFiles { public interface IMediaFileRepository : IBasicRepository { List GetFilesByAuthor(int authorId); List GetFilesByAuthorMetadataId(int authorMetadataId); List GetFilesByBook(int bookId); List GetFilesByEdition(int editionId); List GetUnmappedFiles(); List GetFilesWithBasePath(string path); List GetFileWithPath(List paths); BookFile GetFileWithPath(string path); void DeleteFilesByBook(int bookId); void UnlinkFilesByBook(int bookId); } public class MediaFileRepository : BasicRepository, IMediaFileRepository { public MediaFileRepository(IMainDatabase database, IEventAggregator eventAggregator) : base(database, eventAggregator) { } // always join with all the other good stuff // needed more often than not so better to load it all now protected override SqlBuilder Builder() => new SqlBuilder(_database.DatabaseType) .LeftJoin((b, e) => b.EditionId == e.Id) .LeftJoin((e, b) => e.BookId == b.Id) .LeftJoin((book, author) => book.AuthorMetadataId == author.AuthorMetadataId) .LeftJoin((a, m) => a.AuthorMetadataId == m.Id); protected override List Query(SqlBuilder builder) => Query(_database, builder).ToList(); public static IEnumerable Query(IDatabase database, SqlBuilder builder) { return database.QueryJoined(builder, (file, edition, book, author, metadata) => Map(file, edition, book, author, metadata)); } private static BookFile Map(BookFile file, Edition edition, Book book, Author author, AuthorMetadata metadata) { file.Edition = edition; if (edition != null) { edition.Book = book; } if (author != null) { author.Metadata = metadata; } file.Author = author; return file; } public List GetFilesByAuthor(int authorId) { return Query(Builder().Where(a => a.Id == authorId)); } public List GetFilesByAuthorMetadataId(int authorMetadataId) { return Query(Builder().Where(b => b.AuthorMetadataId == authorMetadataId)); } public List GetFilesByBook(int bookId) { return Query(Builder().Where(b => b.Id == bookId)); } public List GetFilesByEdition(int editionId) { return Query(Builder().Where(f => f.EditionId == editionId)); } public List GetUnmappedFiles() { return _database.Query(new SqlBuilder(_database.DatabaseType).Select(typeof(BookFile)) .Where(t => t.EditionId == 0)).ToList(); } public void DeleteFilesByBook(int bookId) { var fileIds = GetFilesByBook(bookId).Select(x => x.Id).ToList(); Delete(x => fileIds.Contains(x.Id)); } public void UnlinkFilesByBook(int bookId) { var files = GetFilesByBook(bookId); files.ForEach(x => x.EditionId = 0); SetFields(files, f => f.EditionId); } public List GetFilesWithBasePath(string path) { // ensure path ends with a single trailing path separator to avoid matching partial paths var safePath = path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; return _database.Query(new SqlBuilder(_database.DatabaseType).Where(x => x.Path.StartsWith(safePath))).ToList(); } public BookFile GetFileWithPath(string path) { return Query(x => x.Path == path).SingleOrDefault(); } public List GetFileWithPath(List paths) { // use more limited join for speed var builder = new SqlBuilder(_database.DatabaseType) .LeftJoin((f, t) => f.EditionId == t.Id); var all = _database.QueryJoined(builder, (file, book) => MapTrack(file, book)).ToList(); var joined = all.Join(paths, x => x.Path, x => x, (file, path) => file, PathEqualityComparer.Instance).ToList(); return joined; } private BookFile MapTrack(BookFile file, Edition book) { file.Edition = book; return file; } } }