using System.Collections.Generic; using System.IO; using System.Linq; using Dapper; using NzbDrone.Common; using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Music; namespace NzbDrone.Core.MediaFiles { public interface IMediaFileRepository : IBasicRepository { List GetFilesByArtist(int artistId); List GetFilesByAlbum(int albumId); List GetFilesByRelease(int releaseId); List GetUnmappedFiles(); List GetFilesWithBasePath(string path); List GetFileWithPath(List paths); TrackFile GetFileWithPath(string path); void DeleteFilesByAlbum(int albumId); void UnlinkFilesByAlbum(int albumId); } 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((t, x) => t.Id == x.TrackFileId) .LeftJoin((t, a) => t.AlbumId == a.Id) .LeftJoin((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId) .LeftJoin((a, m) => a.ArtistMetadataId == m.Id); protected override List Query(SqlBuilder builder) => Query(_database, builder).ToList(); public static IEnumerable Query(IDatabase database, SqlBuilder builder) { var fileDictionary = new Dictionary(); _ = database.QueryJoined(builder, (file, track, album, artist, metadata) => Map(fileDictionary, file, track, album, artist, metadata)); return fileDictionary.Values; } private static TrackFile Map(Dictionary dict, TrackFile file, Track track, Album album, Artist artist, ArtistMetadata metadata) { if (!dict.TryGetValue(file.Id, out var entry)) { if (artist != null) { artist.Metadata = metadata; } entry = file; entry.Tracks = new List(); entry.Album = album; entry.Artist = artist; dict.Add(entry.Id, entry); } if (track != null) { entry.Tracks.Value.Add(track); } return entry; } public List GetFilesByArtist(int artistId) { return Query(Builder().LeftJoin((t, r) => t.AlbumReleaseId == r.Id) .Where(r => r.Monitored == true) .Where(a => a.Id == artistId)); } public List GetFilesByAlbum(int albumId) { return Query(Builder().LeftJoin((t, r) => t.AlbumReleaseId == r.Id) .Where(r => r.Monitored == true) .Where(f => f.AlbumId == albumId)); } public List GetUnmappedFiles() { // x.Id == null is converted to SQL, so warning incorrect #pragma warning disable CS0472 return _database.Query(new SqlBuilder(_database.DatabaseType).Select(typeof(TrackFile)) .LeftJoin((f, t) => f.Id == t.TrackFileId) .Where(t => t.Id == null)).ToList(); #pragma warning restore CS0472 } public void DeleteFilesByAlbum(int albumId) { Delete(x => x.AlbumId == albumId); } public void UnlinkFilesByAlbum(int albumId) { var files = Query(x => x.AlbumId == albumId); files.ForEach(x => x.AlbumId = 0); SetFields(files, f => f.AlbumId); } public List GetFilesByRelease(int releaseId) { return Query(Builder().Where(x => x.AlbumReleaseId == releaseId)); } 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 TrackFile 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.Id == t.TrackFileId); var dict = new Dictionary(); _ = _database.QueryJoined(builder, (file, track) => MapTrack(dict, file, track)).ToList(); var all = dict.Values.ToList(); var joined = all.Join(paths, x => x.Path, x => x, (file, path) => file, PathEqualityComparer.Instance).ToList(); return joined; } private TrackFile MapTrack(Dictionary dict, TrackFile file, Track track) { if (!dict.TryGetValue(file.Id, out var entry)) { entry = file; entry.Tracks = new List(); dict.Add(entry.Id, entry); } if (track != null) { entry.Tracks.Value.Add(track); } return entry; } } }