using System; using System.Linq; using NLog; using Marr.Data.QGen; using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore.Extensions; using System.Collections.Generic; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Qualities; using NzbDrone.Common.Extensions; namespace NzbDrone.Core.Music { public interface IAlbumRepository : IBasicRepository { List GetAlbums(int artistId); List GetAlbumsByArtistMetadataId(int artistMetadataId); List GetAlbumsForRefresh(int artistId, IEnumerable foreignIds); Album FindByTitle(int artistMetadataId, string title); Album FindById(string foreignId); PagingSpec AlbumsWithoutFiles(PagingSpec pagingSpec); PagingSpec AlbumsWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff); List AlbumsBetweenDates(DateTime startDate, DateTime endDate, bool includeUnmonitored); List ArtistAlbumsBetweenDates(Artist artist, DateTime startDate, DateTime endDate, bool includeUnmonitored); void SetMonitoredFlat(Album album, bool monitored); void SetMonitored(IEnumerable ids, bool monitored); Album FindAlbumByRelease(string albumReleaseId); Album FindAlbumByTrack(int trackId); List GetArtistAlbumsWithFiles(Artist artist); } public class AlbumRepository : BasicRepository, IAlbumRepository { private readonly IMainDatabase _database; private readonly Logger _logger; public AlbumRepository(IMainDatabase database, IEventAggregator eventAggregator, Logger logger) : base(database, eventAggregator) { _database = database; _logger = logger; } public List GetAlbums(int artistId) { return Query.Join(JoinType.Inner, album => album.Artist, (l, r) => l.ArtistMetadataId == r.ArtistMetadataId) .Where(a => a.Id == artistId).ToList(); } public List GetAlbumsByArtistMetadataId(int artistMetadataId) { return Query.Where(s => s.ArtistMetadataId == artistMetadataId); } public List GetAlbumsForRefresh(int artistMetadataId, IEnumerable foreignIds) { return Query .Where(a => a.ArtistMetadataId == artistMetadataId) .OrWhere($"[ForeignAlbumId] IN ('{string.Join("', '", foreignIds)}')") .ToList(); } public Album FindById(string foreignAlbumId) { return Query.Where(s => s.ForeignAlbumId == foreignAlbumId).SingleOrDefault(); } public PagingSpec AlbumsWithoutFiles(PagingSpec pagingSpec) { var currentTime = DateTime.UtcNow; //pagingSpec.TotalRecords = GetMissingAlbumsQuery(pagingSpec, currentTime).GetRowCount(); Cant Use GetRowCount with a Manual Query pagingSpec.TotalRecords = GetMissingAlbumsQueryCount(pagingSpec, currentTime); pagingSpec.Records = GetMissingAlbumsQuery(pagingSpec, currentTime).ToList(); return pagingSpec; } public PagingSpec AlbumsWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff) { pagingSpec.TotalRecords = GetCutOffAlbumsQueryCount(pagingSpec, qualitiesBelowCutoff); pagingSpec.Records = GetCutOffAlbumsQuery(pagingSpec, qualitiesBelowCutoff).ToList(); return pagingSpec; } public List AlbumsBetweenDates(DateTime startDate, DateTime endDate, bool includeUnmonitored) { var query = Query.Join(JoinType.Inner, rg => rg.Artist, (rg, a) => rg.ArtistMetadataId == a.ArtistMetadataId) .Where(rg => rg.ReleaseDate >= startDate) .AndWhere(rg => rg.ReleaseDate <= endDate); if (!includeUnmonitored) { query.AndWhere(e => e.Monitored) .AndWhere(e => e.Artist.Value.Monitored); } return query.ToList(); } public List ArtistAlbumsBetweenDates(Artist artist, DateTime startDate, DateTime endDate, bool includeUnmonitored) { var query = Query.Join(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistMetadataId == s.ArtistMetadataId) .Where(e => e.ReleaseDate >= startDate) .AndWhere(e => e.ReleaseDate <= endDate) .AndWhere(e => e.ArtistMetadataId == artist.ArtistMetadataId); if (!includeUnmonitored) { query.AndWhere(e => e.Monitored) .AndWhere(e => e.Artist.Value.Monitored); } return query.ToList(); } private QueryBuilder GetMissingAlbumsQuery(PagingSpec pagingSpec, DateTime currentTime) { string sortKey; string monitored = "(Albums.[Monitored] = 0) OR (Artists.[Monitored] = 0)"; if (pagingSpec.FilterExpressions.FirstOrDefault().ToString().Contains("True")) { monitored = "(Albums.[Monitored] = 1) AND (Artists.[Monitored] = 1)"; } if (pagingSpec.SortKey == "releaseDate") { sortKey = "Albums." + pagingSpec.SortKey; } else if (pagingSpec.SortKey == "artist.sortName") { sortKey = "Artists." + pagingSpec.SortKey.Split('.').Last(); } else if (pagingSpec.SortKey == "albumTitle") { sortKey = "Albums.title"; } else { sortKey = "Albums.releaseDate"; } string query = string.Format("SELECT Albums.* " + "FROM Albums " + "JOIN Artists ON Albums.ArtistMetadataId = Artists.ArtistMetadataId " + "JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " + "JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " + "LEFT OUTER JOIN TrackFiles ON TrackFiles.Id == Tracks.TrackFileId " + "WHERE TrackFiles.Id IS NULL " + "AND AlbumReleases.Monitored = 1 " + "AND ({0}) AND {1} " + "GROUP BY Albums.Id " + " ORDER BY {2} {3} LIMIT {4} OFFSET {5}", monitored, BuildReleaseDateCutoffWhereClause(currentTime), sortKey, pagingSpec.ToSortDirection(), pagingSpec.PageSize, pagingSpec.PagingOffset()); return Query.QueryText(query); } private int GetMissingAlbumsQueryCount(PagingSpec pagingSpec, DateTime currentTime) { var monitored = "(Albums.[Monitored] = 0) OR (Artists.[Monitored] = 0)"; if (pagingSpec.FilterExpressions.FirstOrDefault().ToString().Contains("True")) { monitored = "(Albums.[Monitored] = 1) AND (Artists.[Monitored] = 1)"; } string query = string.Format("SELECT Albums.* " + "FROM Albums " + "JOIN Artists ON Albums.ArtistMetadataId = Artists.ArtistMetadataId " + "JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " + "JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " + "LEFT OUTER JOIN TrackFiles ON TrackFiles.Id == Tracks.TrackFileId " + "WHERE TrackFiles.Id IS NULL " + "AND AlbumReleases.Monitored = 1 " + "AND ({0}) AND {1} " + "GROUP BY Albums.Id ", monitored, BuildReleaseDateCutoffWhereClause(currentTime)); return Query.QueryText(query).Count(); } private string BuildReleaseDateCutoffWhereClause(DateTime currentTime) { return string.Format("datetime(strftime('%s', Albums.[ReleaseDate]), 'unixepoch') <= '{0}'", currentTime.ToString("yyyy-MM-dd HH:mm:ss")); } private QueryBuilder GetCutOffAlbumsQuery(PagingSpec pagingSpec, List qualitiesBelowCutoff) { string sortKey; string monitored = "(Albums.[Monitored] = 0) OR (Artists.[Monitored] = 0)"; if (pagingSpec.FilterExpressions.FirstOrDefault().ToString().Contains("True")) { monitored = "(Albums.[Monitored] = 1) AND (Artists.[Monitored] = 1)"; } if (pagingSpec.SortKey == "releaseDate") { sortKey = "Albums." + pagingSpec.SortKey; } else if (pagingSpec.SortKey == "artist.sortName") { sortKey = "Artists." + pagingSpec.SortKey.Split('.').Last(); } else if (pagingSpec.SortKey == "albumTitle") { sortKey = "Albums.title"; } else { sortKey = "Albums.releaseDate"; } string query = string.Format("SELECT Albums.* " + "FROM Albums " + "JOIN Artists on Albums.ArtistMetadataId == Artists.ArtistMetadataId " + "JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " + "JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " + "JOIN TrackFiles ON TrackFiles.Id == Tracks.TrackFileId " + "WHERE {0} " + "AND AlbumReleases.Monitored = 1 " + "GROUP BY Albums.Id " + "HAVING {1} " + "ORDER BY {2} {3} LIMIT {4} OFFSET {5}", monitored, BuildQualityCutoffWhereClause(qualitiesBelowCutoff), sortKey, pagingSpec.ToSortDirection(), pagingSpec.PageSize, pagingSpec.PagingOffset()); return Query.QueryText(query); } private int GetCutOffAlbumsQueryCount(PagingSpec pagingSpec, List qualitiesBelowCutoff) { var monitored = "(Albums.[Monitored] = 0) OR (Artists.[Monitored] = 0)"; if (pagingSpec.FilterExpressions.FirstOrDefault().ToString().Contains("True")) { monitored = "(Albums.[Monitored] = 1) AND (Artists.[Monitored] = 1)"; } string query = string.Format("SELECT Albums.* " + "FROM Albums " + "JOIN Artists on Albums.ArtistMetadataId == Artists.ArtistMetadataId " + "JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " + "JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " + "JOIN TrackFiles ON TrackFiles.Id == Tracks.TrackFileId " + "WHERE {0} " + "AND AlbumReleases.Monitored = 1 " + "GROUP BY Albums.Id " + "HAVING {1}", monitored, BuildQualityCutoffWhereClause(qualitiesBelowCutoff)); return Query.QueryText(query).Count(); } private string BuildQualityCutoffWhereClause(List qualitiesBelowCutoff) { var clauses = new List(); foreach (var profile in qualitiesBelowCutoff) { foreach (var belowCutoff in profile.QualityIds) { clauses.Add(string.Format("(Artists.[QualityProfileId] = {0} AND MIN(TrackFiles.Quality) LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff)); } } return string.Format("({0})", string.Join(" OR ", clauses)); } public void SetMonitoredFlat(Album album, bool monitored) { album.Monitored = monitored; SetFields(album, p => p.Monitored); } public void SetMonitored(IEnumerable ids, bool monitored) { var mapper = _database.GetDataMapper(); mapper.AddParameter("monitored", monitored); var sql = "UPDATE Albums " + "SET Monitored = @monitored " + $"WHERE Id IN ({string.Join(", ", ids)})"; mapper.ExecuteNonQuery(sql); } public Album FindByTitle(int artistMetadataId, string title) { var cleanTitle = Parser.Parser.CleanArtistName(title); if (string.IsNullOrEmpty(cleanTitle)) cleanTitle = title; return Query.Where(s => s.CleanTitle == cleanTitle || s.Title == title) .AndWhere(s => s.ArtistMetadataId == artistMetadataId) .ExclusiveOrDefault(); } public Album FindAlbumByRelease(string albumReleaseId) { string query = string.Format("SELECT Albums.* " + "FROM Albums " + "JOIN AlbumReleases ON AlbumReleases.AlbumId = Albums.Id " + "WHERE AlbumReleases.ForeignReleaseId = '{0}'", albumReleaseId); return Query.QueryText(query).FirstOrDefault(); } public Album FindAlbumByTrack(int trackId) { string query = string.Format("SELECT Albums.* " + "FROM Albums " + "JOIN AlbumReleases ON AlbumReleases.AlbumId = Albums.Id " + "JOIN Tracks ON Tracks.AlbumReleaseId = AlbumReleases.Id " + "WHERE Tracks.Id = {0}", trackId); return Query.QueryText(query).FirstOrDefault(); } public List GetArtistAlbumsWithFiles(Artist artist) { string query = string.Format("SELECT Albums.* " + "FROM Albums " + "JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " + "JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " + "JOIN TrackFiles ON TrackFiles.Id == Tracks.TrackFileId " + "WHERE Albums.ArtistMetadataId == {0} " + "AND AlbumReleases.Monitored = 1 " + "GROUP BY Albums.Id ", artist.ArtistMetadataId); return Query.QueryText(query).ToList(); } } }