diff --git a/build.sh b/build.sh index 92c1de871..0b823b594 100755 --- a/build.sh +++ b/build.sh @@ -84,7 +84,8 @@ BuildWithMSBuild() BuildWithXbuild() { export MONO_IOMAP=case - CheckExitCode msbuild /t:Clean $slnFile + CheckExitCode msbuild /p:Configuration=Debug /t:Clean $slnFile + CheckExitCode msbuild /p:Configuration=Release /t:Clean $slnFile mono $nuget restore $slnFile CheckExitCode msbuild /p:Configuration=Release /p:Platform=x86 /t:Build /p:AllowedReferenceRelatedFileExtensions=.pdb $slnFile } diff --git a/frontend/src/Album/Details/AlbumDetails.css b/frontend/src/Album/Details/AlbumDetails.css index bc6d6884d..2c0266f27 100644 --- a/frontend/src/Album/Details/AlbumDetails.css +++ b/frontend/src/Album/Details/AlbumDetails.css @@ -106,6 +106,7 @@ .sizeOnDisk, .qualityProfileName, +.links, .tags { margin-left: 8px; font-weight: 300; diff --git a/frontend/src/Album/Details/AlbumDetails.js b/frontend/src/Album/Details/AlbumDetails.js index d88b6ea8c..c75ced1ca 100644 --- a/frontend/src/Album/Details/AlbumDetails.js +++ b/frontend/src/Album/Details/AlbumDetails.js @@ -2,14 +2,17 @@ import _ from 'lodash'; import moment from 'moment'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import TextTruncate from 'react-text-truncate'; import formatBytes from 'Utilities/Number/formatBytes'; import selectAll from 'Utilities/Table/selectAll'; import toggleSelected from 'Utilities/Table/toggleSelected'; -import { align, icons, sizes } from 'Helpers/Props'; +import { align, icons, kinds, sizes, tooltipPositions } from 'Helpers/Props'; +import fonts from 'Styles/Variables/fonts'; import HeartRating from 'Components/HeartRating'; import Icon from 'Components/Icon'; import IconButton from 'Components/Link/IconButton'; import Label from 'Components/Label'; +import Tooltip from 'Components/Tooltip/Tooltip'; import AlbumCover from 'Album/AlbumCover'; import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector'; import EditAlbumModalConnector from 'Album/Edit/EditAlbumModalConnector'; @@ -24,9 +27,12 @@ import AlbumDetailsMediumConnector from './AlbumDetailsMediumConnector'; import ArtistHistoryModal from 'Artist/History/ArtistHistoryModal'; import InteractiveSearchModal from 'InteractiveSearch/InteractiveSearchModal'; import TrackFileEditorModal from 'TrackFile/Editor/TrackFileEditorModal'; - +import AlbumDetailsLinks from './AlbumDetailsLinks'; import styles from './AlbumDetails.css'; +const defaultFontSize = parseInt(fonts.defaultFontSize); +const lineHeight = parseFloat(fonts.lineHeight); + function getFanartUrl(images) { const fanartImage = _.find(images, { coverType: 'fanart' }); if (fanartImage) { @@ -135,14 +141,17 @@ class AlbumDetails extends Component { render() { const { id, + foreignAlbumId, title, disambiguation, + overview, albumType, statistics = {}, monitored, releaseDate, ratings, images, + links, media, isFetching, isPopulated, @@ -357,6 +366,38 @@ class AlbumDetails extends Component { } + + + + + Links + + + } + tooltip={ + + } + kind={kinds.INVERSE} + position={tooltipPositions.BOTTOM} + /> + + +
+
@@ -446,11 +487,13 @@ AlbumDetails.propTypes = { foreignAlbumId: PropTypes.string.isRequired, title: PropTypes.string.isRequired, disambiguation: PropTypes.string, + overview: PropTypes.string, albumType: PropTypes.string.isRequired, statistics: PropTypes.object.isRequired, releaseDate: PropTypes.string.isRequired, ratings: PropTypes.object.isRequired, images: PropTypes.arrayOf(PropTypes.object).isRequired, + links: PropTypes.arrayOf(PropTypes.object).isRequired, media: PropTypes.arrayOf(PropTypes.object).isRequired, monitored: PropTypes.bool.isRequired, shortDateFormat: PropTypes.string.isRequired, diff --git a/frontend/src/Album/Details/AlbumDetailsConnector.js b/frontend/src/Album/Details/AlbumDetailsConnector.js index e10450745..9d9585bab 100644 --- a/frontend/src/Album/Details/AlbumDetailsConnector.js +++ b/frontend/src/Album/Details/AlbumDetailsConnector.js @@ -67,6 +67,10 @@ const mapDispatchToProps = { clearTrackFiles }; +function getMonitoredReleases(props) { + return _.map(_.filter(props.releases, { monitored: true }), 'id').sort(); +} + class AlbumDetailsConnector extends Component { componentDidMount() { @@ -75,14 +79,8 @@ class AlbumDetailsConnector extends Component { } componentDidUpdate(prevProps) { - const { - id - } = this.props; - - // If the id has changed we need to clear the tracks/track - // files and fetch from the server. - - if (prevProps.id !== id) { + if (!_.isEqual(getMonitoredReleases(prevProps), getMonitoredReleases(this.props)) || + (prevProps.anyReleaseOk === false && this.props.anyReleaseOk === true)) { this.unpopulate(); this.populate(); } diff --git a/frontend/src/Album/Details/AlbumDetailsLinks.css b/frontend/src/Album/Details/AlbumDetailsLinks.css new file mode 100644 index 000000000..0f65b9154 --- /dev/null +++ b/frontend/src/Album/Details/AlbumDetailsLinks.css @@ -0,0 +1,13 @@ +.links { + margin: 0; +} + +.link { + white-space: nowrap; +} + +.linkLabel { + composes: label from 'Components/Label.css'; + + cursor: pointer; +} diff --git a/frontend/src/Album/Details/AlbumDetailsLinks.js b/frontend/src/Album/Details/AlbumDetailsLinks.js new file mode 100644 index 000000000..265a7c4ff --- /dev/null +++ b/frontend/src/Album/Details/AlbumDetailsLinks.js @@ -0,0 +1,63 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { kinds, sizes } from 'Helpers/Props'; +import Label from 'Components/Label'; +import Link from 'Components/Link/Link'; +import styles from './AlbumDetailsLinks.css'; + +function AlbumDetailsLinks(props) { + const { + foreignAlbumId, + links + } = props; + + return ( +
+ + + + + + {links.map((link, index) => { + return ( + + + + + {(index > 0 && index % 5 === 0) && +
+ } + +
+ ); + })} + +
+ + ); +} + +AlbumDetailsLinks.propTypes = { + foreignAlbumId: PropTypes.string.isRequired, + links: PropTypes.arrayOf(PropTypes.object).isRequired +}; + +export default AlbumDetailsLinks; diff --git a/frontend/src/Album/Details/AlbumDetailsMedium.js b/frontend/src/Album/Details/AlbumDetailsMedium.js index 675ac5954..33d6efb80 100644 --- a/frontend/src/Album/Details/AlbumDetailsMedium.js +++ b/frontend/src/Album/Details/AlbumDetailsMedium.js @@ -19,7 +19,7 @@ function getMediumStatistics(tracks) { if (track.trackFileId) { trackCount++; trackFileCount++; - } else if (track.monitored) { + } else { trackCount++; } diff --git a/frontend/src/Album/Details/TrackRow.js b/frontend/src/Album/Details/TrackRow.js index d7cda1fb4..059dc630d 100644 --- a/frontend/src/Album/Details/TrackRow.js +++ b/frontend/src/Album/Details/TrackRow.js @@ -178,7 +178,6 @@ TrackRow.propTypes = { id: PropTypes.number.isRequired, albumId: PropTypes.number.isRequired, trackFileId: PropTypes.number, - monitored: PropTypes.bool.isRequired, mediumNumber: PropTypes.number.isRequired, trackNumber: PropTypes.string.isRequired, absoluteTrackNumber: PropTypes.number, diff --git a/frontend/src/Album/Edit/EditAlbumModalContent.js b/frontend/src/Album/Edit/EditAlbumModalContent.js index 91a2a3539..58f5f1e46 100644 --- a/frontend/src/Album/Edit/EditAlbumModalContent.js +++ b/frontend/src/Album/Edit/EditAlbumModalContent.js @@ -43,7 +43,7 @@ class EditAlbumModalContent extends Component { const { monitored, - currentRelease, + anyReleaseOk, releases } = item; @@ -69,15 +69,26 @@ class EditAlbumModalContent extends Component { /> + + Automatically Switch Release + + + + Release diff --git a/frontend/src/Album/Edit/EditAlbumModalContentConnector.js b/frontend/src/Album/Edit/EditAlbumModalContentConnector.js index 867e9c0c0..31672d275 100644 --- a/frontend/src/Album/Edit/EditAlbumModalContentConnector.js +++ b/frontend/src/Album/Edit/EditAlbumModalContentConnector.js @@ -23,7 +23,7 @@ function createMapStateToProps() { const albumSettings = _.pick(album, [ 'monitored', - 'currentRelease', + 'anyReleaseOk', 'releases' ]); diff --git a/frontend/src/Artist/Details/ArtistDetails.js b/frontend/src/Artist/Details/ArtistDetails.js index 87f587a55..21bce5fbc 100644 --- a/frontend/src/Artist/Details/ArtistDetails.js +++ b/frontend/src/Artist/Details/ArtistDetails.js @@ -344,6 +344,14 @@ class ArtistDetails extends Component { to={`/artist/${previousArtist.foreignArtistId}`} /> + + albumReleases, - (state, { selectedRelease }) => selectedRelease, - (albumReleases, selectedRelease) => { + (albumReleases) => { const values = _.map(albumReleases.value, (albumRelease) => { return { - key: albumRelease.id, + key: albumRelease.foreignReleaseId, value: `${albumRelease.title}` + `${albumRelease.disambiguation ? ' (' : ''}${titleCase(albumRelease.disambiguation)}${albumRelease.disambiguation ? ')' : ''}` + - `, ${albumRelease.mediaCount} med, ${albumRelease.trackCount} tracks` + + `, ${albumRelease.mediumCount} med, ${albumRelease.trackCount} tracks` + `${albumRelease.country.length > 0 ? ', ' : ''}${albumRelease.country}` + `${albumRelease.format ? ', [' : ''}${albumRelease.format}${albumRelease.format ? ']' : ''}` }; @@ -25,7 +24,7 @@ function createMapStateToProps() { const sortedValues = _.orderBy(values, ['value']); - const value = selectedRelease.value.id; + const value = _.find(albumReleases.value, { monitored: true }).foreignReleaseId; return { values: sortedValues, @@ -45,7 +44,10 @@ class AlbumReleaseSelectInputConnector extends Component { albumReleases } = this.props; - this.props.onChange({ name, value: _.find(albumReleases.value, { id: value }) }); + let updatedReleases = _.map(albumReleases.value, (e) => ({ ...e, monitored: false })); + _.find(updatedReleases, { foreignReleaseId: value }).monitored = true; + + this.props.onChange({ name, value: updatedReleases }); } render() { diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js index c06a13f0d..ef0758e60 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js @@ -105,6 +105,7 @@ class InteractiveImportModalContentConnector extends Component { const { artist, album, + albumReleaseId, tracks, quality, language @@ -140,6 +141,7 @@ class InteractiveImportModalContentConnector extends Component { folderName: item.folderName, artistId: artist.id, albumId: album.id, + albumReleaseId, trackIds: _.map(tracks, 'id'), quality, language, diff --git a/src/Lidarr.Api.V1/Albums/AlbumModule.cs b/src/Lidarr.Api.V1/Albums/AlbumModule.cs index aaf352e2d..77ef981bf 100644 --- a/src/Lidarr.Api.V1/Albums/AlbumModule.cs +++ b/src/Lidarr.Api.V1/Albums/AlbumModule.cs @@ -14,13 +14,16 @@ namespace Lidarr.Api.V1.Albums { public class AlbumModule : AlbumModuleWithSignalR { - public AlbumModule(IArtistService artistService, - IAlbumService albumService, - IArtistStatisticsService artistStatisticsService, - IUpgradableSpecification upgradableSpecification, - IBroadcastSignalRMessage signalRBroadcaster) - : base(albumService, artistStatisticsService, artistService, upgradableSpecification, signalRBroadcaster) + protected readonly IReleaseService _releaseService; + + public AlbumModule(IAlbumService albumService, + IReleaseService releaseService, + IArtistStatisticsService artistStatisticsService, + IUpgradableSpecification upgradableSpecification, + IBroadcastSignalRMessage signalRBroadcaster) + : base(albumService, artistStatisticsService, upgradableSpecification, signalRBroadcaster) { + _releaseService = releaseService; GetResourceAll = GetAlbums; UpdateResource = UpdateAlbum; Put["/monitor"] = x => SetAlbumsMonitored(); @@ -67,8 +70,9 @@ namespace Lidarr.Api.V1.Albums var model = albumResource.ToModel(album); _albumService.UpdateAlbum(model); + _releaseService.UpdateMany(model.AlbumReleases.Value); - BroadcastResourceChange(ModelAction.Updated, albumResource); + BroadcastResourceChange(ModelAction.Updated, model.Id); } private Response SetAlbumsMonitored() diff --git a/src/Lidarr.Api.V1/Albums/AlbumModuleWithSignalR.cs b/src/Lidarr.Api.V1/Albums/AlbumModuleWithSignalR.cs index 7ecd7ae8a..e4a5f591b 100644 --- a/src/Lidarr.Api.V1/Albums/AlbumModuleWithSignalR.cs +++ b/src/Lidarr.Api.V1/Albums/AlbumModuleWithSignalR.cs @@ -24,19 +24,16 @@ namespace Lidarr.Api.V1.Albums { protected readonly IAlbumService _albumService; protected readonly IArtistStatisticsService _artistStatisticsService; - protected readonly IArtistService _artistService; protected readonly IUpgradableSpecification _qualityUpgradableSpecification; protected AlbumModuleWithSignalR(IAlbumService albumService, IArtistStatisticsService artistStatisticsService, - IArtistService artistService, IUpgradableSpecification qualityUpgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster) : base(signalRBroadcaster) { _albumService = albumService; _artistStatisticsService = artistStatisticsService; - _artistService = artistService; _qualityUpgradableSpecification = qualityUpgradableSpecification; GetResourceById = GetAlbum; @@ -44,7 +41,6 @@ namespace Lidarr.Api.V1.Albums protected AlbumModuleWithSignalR(IAlbumService albumService, IArtistStatisticsService artistStatisticsService, - IArtistService artistService, IUpgradableSpecification qualityUpgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster, string resource) @@ -52,7 +48,6 @@ namespace Lidarr.Api.V1.Albums { _albumService = albumService; _artistStatisticsService = artistStatisticsService; - _artistService = artistService; _qualityUpgradableSpecification = qualityUpgradableSpecification; GetResourceById = GetAlbum; @@ -71,7 +66,7 @@ namespace Lidarr.Api.V1.Albums if (includeArtist) { - var artist = album.Artist ?? _artistService.GetArtist(album.ArtistId); + var artist = album.Artist.Value; resource.Artist = artist.ToResource(); } @@ -92,9 +87,8 @@ namespace Lidarr.Api.V1.Albums { var album = albums[i]; var resource = result[i]; - - var artist = album.Artist ?? artistDict.GetValueOrDefault(albums[i].ArtistId) ?? _artistService.GetArtist(albums[i].ArtistId); - artistDict[artist.Id] = artist; + var artist = artistDict.GetValueOrDefault(albums[i].ArtistMetadataId) ?? album.Artist?.Value; + artistDict[artist.ArtistMetadataId] = artist; resource.Artist = artist.ToResource(); } diff --git a/src/Lidarr.Api.V1/Albums/AlbumReleaseResource.cs b/src/Lidarr.Api.V1/Albums/AlbumReleaseResource.cs index 6667cc30d..df2b4f769 100644 --- a/src/Lidarr.Api.V1/Albums/AlbumReleaseResource.cs +++ b/src/Lidarr.Api.V1/Albums/AlbumReleaseResource.cs @@ -7,15 +7,31 @@ namespace Lidarr.Api.V1.Albums { public class AlbumReleaseResource { - public string Id { get; set; } + public int Id { get; set; } + public int AlbumId { get; set; } + public string ForeignReleaseId { get; set; } public string Title { get; set; } - public DateTime? ReleaseDate { get; set; } + public string Status { get; set; } + public int Duration { get; set; } public int TrackCount { get; set; } - public int MediaCount { get; set; } + public List Media { get; set; } + public int MediumCount + { + get + { + if (Media == null) + { + return 0; + } + + return Media.Where(s => s.MediumNumber > 0).Count(); + } + } public string Disambiguation { get; set; } public List Country { get; set; } public List Label { get; set; } public string Format { get; set; } + public bool Monitored { get; set; } } public static class AlbumReleaseResourceMapper @@ -30,14 +46,23 @@ namespace Lidarr.Api.V1.Albums return new AlbumReleaseResource { Id = model.Id, + AlbumId = model.AlbumId, + ForeignReleaseId = model.ForeignReleaseId, Title = model.Title, - ReleaseDate = model.ReleaseDate, + Status = model.Status, + Duration = model.Duration, TrackCount = model.TrackCount, - MediaCount = model.MediaCount, + Media = model.Media.ToResource(), Disambiguation = model.Disambiguation, Country = model.Country, Label = model.Label, - Format = model.Format + Monitored = model.Monitored, + Format = string.Join(", ", + model.Media.OrderBy(x => x.Number) + .GroupBy(x => x.Format) + .Select(g => MediaFormatHelper(g.Key, g.Count())) + .ToList()) + }; } @@ -51,17 +76,25 @@ namespace Lidarr.Api.V1.Albums return new AlbumRelease { Id = resource.Id, + AlbumId = resource.AlbumId, + ForeignReleaseId = resource.ForeignReleaseId, Title = resource.Title, - ReleaseDate = resource.ReleaseDate, - TrackCount = resource.TrackCount, - MediaCount = resource.MediaCount, + Status = resource.Status, + Duration = resource.Duration, + Label = resource.Label, Disambiguation = resource.Disambiguation, Country = resource.Country, - Label = resource.Label, - Format = resource.Format + Media = resource.Media.ToModel(), + TrackCount = resource.TrackCount, + Monitored = resource.Monitored }; } + private static string MediaFormatHelper(string name, int count) + { + return count == 1 ? name : string.Join("x", new List {count.ToString(), name}); + } + public static List ToResource(this IEnumerable models) { return models.Select(ToResource).ToList(); diff --git a/src/Lidarr.Api.V1/Albums/AlbumResource.cs b/src/Lidarr.Api.V1/Albums/AlbumResource.cs index 758fc4c8a..2d172b014 100644 --- a/src/Lidarr.Api.V1/Albums/AlbumResource.cs +++ b/src/Lidarr.Api.V1/Albums/AlbumResource.cs @@ -13,10 +13,11 @@ namespace Lidarr.Api.V1.Albums { public string Title { get; set; } public string Disambiguation { get; set; } + public string Overview { get; set; } public int ArtistId { get; set; } - public List AlbumLabel { get; set; } public string ForeignAlbumId { get; set; } public bool Monitored { get; set; } + public bool AnyReleaseOk { get; set; } public int ProfileId { get; set; } public int Duration { get; set; } public string AlbumType { get; set; } @@ -35,12 +36,12 @@ namespace Lidarr.Api.V1.Albums } public Ratings Ratings { get; set; } public DateTime? ReleaseDate { get; set; } - public AlbumRelease CurrentRelease { get; set; } public List Releases { get; set; } public List Genres { get; set; } public List Media { get; set; } public ArtistResource Artist { get; set; } public List Images { get; set; } + public List Links { get; set; } public AlbumStatisticsResource Statistics { get; set; } public string RemoteCover { get; set; } @@ -56,27 +57,30 @@ namespace Lidarr.Api.V1.Albums { if (model == null) return null; + var selectedRelease = model.AlbumReleases.Value.Where(x => x.Monitored).SingleOrDefault(); + return new AlbumResource { Id = model.Id, ArtistId = model.ArtistId, - AlbumLabel = model.Label, ForeignAlbumId = model.ForeignAlbumId, ProfileId = model.ProfileId, Monitored = model.Monitored, + AnyReleaseOk = model.AnyReleaseOk, ReleaseDate = model.ReleaseDate, Genres = model.Genres, Title = model.Title, Disambiguation = model.Disambiguation, + Overview = model.Overview, Images = model.Images, + Links = model.Links, Ratings = model.Ratings, - Duration = model.Duration, + Duration = selectedRelease.Duration, AlbumType = model.AlbumType, SecondaryTypes = model.SecondaryTypes.Select(s => s.Name).ToList(), - Media = model.Media.ToResource(), - CurrentRelease = model.CurrentRelease, - Releases = model.Releases.ToResource(), - Artist = model.Artist.ToResource() + Releases = model.AlbumReleases.Value.ToResource(), + Media = selectedRelease.Media.ToResource(), + Artist = model.Artist.Value.ToResource() }; } @@ -90,9 +94,11 @@ namespace Lidarr.Api.V1.Albums ForeignAlbumId = resource.ForeignAlbumId, Title = resource.Title, Disambiguation = resource.Disambiguation, + Overview = resource.Overview, Images = resource.Images, Monitored = resource.Monitored, - CurrentRelease = resource.CurrentRelease + AnyReleaseOk = resource.AnyReleaseOk, + AlbumReleases = resource.Releases.ToModel() }; } @@ -101,6 +107,7 @@ namespace Lidarr.Api.V1.Albums var updatedAlbum = resource.ToModel(); album.ApplyChanges(updatedAlbum); + album.AlbumReleases = updatedAlbum.AlbumReleases; return album; } diff --git a/src/Lidarr.Api.V1/Artist/ArtistLookupModule.cs b/src/Lidarr.Api.V1/Artist/ArtistLookupModule.cs index b8677e7d7..4829b8b6b 100644 --- a/src/Lidarr.Api.V1/Artist/ArtistLookupModule.cs +++ b/src/Lidarr.Api.V1/Artist/ArtistLookupModule.cs @@ -32,7 +32,7 @@ namespace Lidarr.Api.V1.Artist foreach (var currentArtist in artist) { var resource = currentArtist.ToResource(); - var poster = currentArtist.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster); + var poster = currentArtist.Metadata.Value.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster); if (poster != null) { resource.RemotePoster = poster.Url; diff --git a/src/Lidarr.Api.V1/Artist/ArtistModule.cs b/src/Lidarr.Api.V1/Artist/ArtistModule.cs index 01d728464..5a0905703 100644 --- a/src/Lidarr.Api.V1/Artist/ArtistModule.cs +++ b/src/Lidarr.Api.V1/Artist/ArtistModule.cs @@ -238,7 +238,7 @@ namespace Lidarr.Api.V1.Artist public void Handle(TrackImportedEvent message) { - BroadcastResourceChange(ModelAction.Updated, message.ImportedTrack.ArtistId); + BroadcastResourceChange(ModelAction.Updated, message.TrackInfo.Artist.ToResource()); } public void Handle(TrackFileDeletedEvent message) diff --git a/src/Lidarr.Api.V1/Artist/ArtistResource.cs b/src/Lidarr.Api.V1/Artist/ArtistResource.cs index 324f1ee9c..88c5cb7fe 100644 --- a/src/Lidarr.Api.V1/Artist/ArtistResource.cs +++ b/src/Lidarr.Api.V1/Artist/ArtistResource.cs @@ -76,18 +76,18 @@ namespace Lidarr.Api.V1.Artist //AlternateTitles SortName = model.SortName, - Status = model.Status, - Overview = model.Overview, - ArtistType = model.ArtistType, - Disambiguation = model.Disambiguation, + Status = model.Metadata.Value.Status, + Overview = model.Metadata.Value.Overview, + ArtistType = model.Metadata.Value.Type, + Disambiguation = model.Metadata.Value.Disambiguation, - Images = model.Images, + Images = model.Metadata.Value.Images, Path = model.Path, QualityProfileId = model.ProfileId, LanguageProfileId = model.LanguageProfileId, MetadataProfileId = model.MetadataProfileId, - Links = model.Links, + Links = model.Metadata.Value.Links, AlbumFolder = model.AlbumFolder, Monitored = model.Monitored, @@ -95,14 +95,14 @@ namespace Lidarr.Api.V1.Artist LastInfoSync = model.LastInfoSync, CleanName = model.CleanName, - ForeignArtistId = model.ForeignArtistId, + ForeignArtistId = model.Metadata.Value.ForeignArtistId, // Root folder path is now calculated from the artist path // RootFolderPath = model.RootFolderPath, - Genres = model.Genres, + Genres = model.Metadata.Value.Genres, Tags = model.Tags, Added = model.Added, AddOptions = model.AddOptions, - Ratings = model.Ratings, + Ratings = model.Metadata.Value.Ratings, Statistics = new ArtistStatisticsResource() }; @@ -116,34 +116,38 @@ namespace Lidarr.Api.V1.Artist { Id = resource.Id, - Name = resource.ArtistName, + Metadata = new NzbDrone.Core.Music.ArtistMetadata + { + ForeignArtistId = resource.ForeignArtistId, + Name = resource.ArtistName, + Status = resource.Status, + Overview = resource.Overview, + Links = resource.Links, + Images = resource.Images, + Genres = resource.Genres, + Ratings = resource.Ratings, + Type = resource.ArtistType + }, + //AlternateTitles SortName = resource.SortName, - - Status = resource.Status, - Overview = resource.Overview, - - Images = resource.Images, - Path = resource.Path, ProfileId = resource.QualityProfileId, LanguageProfileId = resource.LanguageProfileId, MetadataProfileId = resource.MetadataProfileId, - Links = resource.Links, + AlbumFolder = resource.AlbumFolder, Monitored = resource.Monitored, LastInfoSync = resource.LastInfoSync, - ArtistType = resource.ArtistType, CleanName = resource.CleanName, - ForeignArtistId = resource.ForeignArtistId, RootFolderPath = resource.RootFolderPath, - Genres = resource.Genres, + Tags = resource.Tags, Added = resource.Added, AddOptions = resource.AddOptions, - Ratings = resource.Ratings + }; } diff --git a/src/Lidarr.Api.V1/Calendar/CalendarModule.cs b/src/Lidarr.Api.V1/Calendar/CalendarModule.cs index ccaaafd84..e20dc81d0 100644 --- a/src/Lidarr.Api.V1/Calendar/CalendarModule.cs +++ b/src/Lidarr.Api.V1/Calendar/CalendarModule.cs @@ -14,10 +14,9 @@ namespace Lidarr.Api.V1.Calendar { public CalendarModule(IAlbumService albumService, IArtistStatisticsService artistStatisticsService, - IArtistService artistService, - IUpgradableSpecification ugradableSpecification, + IUpgradableSpecification upgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster) - : base(albumService, artistStatisticsService, artistService, ugradableSpecification, signalRBroadcaster, "calendar") + : base(albumService, artistStatisticsService, upgradableSpecification, signalRBroadcaster, "calendar") { GetResourceAll = GetCalendar; } diff --git a/src/Lidarr.Api.V1/ManualImport/ManualImportModule.cs b/src/Lidarr.Api.V1/ManualImport/ManualImportModule.cs index faec43ecc..3fc11ce3a 100644 --- a/src/Lidarr.Api.V1/ManualImport/ManualImportModule.cs +++ b/src/Lidarr.Api.V1/ManualImport/ManualImportModule.cs @@ -15,16 +15,19 @@ namespace Lidarr.Api.V1.ManualImport { private readonly IArtistService _artistService; private readonly IAlbumService _albumService; + private readonly IReleaseService _releaseService; public ManualImportModule(IManualImportService manualImportService, IArtistService artistService, IAlbumService albumService, + IReleaseService releaseService, IBroadcastSignalRMessage signalRBroadcaster, Logger logger) : base(manualImportService, signalRBroadcaster, logger) { - _albumService = albumService; _artistService = artistService; + _albumService = albumService; + _releaseService = releaseService; GetResourceAll = GetMediaFiles; UpdateResource = UpdateImportItem; @@ -62,6 +65,7 @@ namespace Lidarr.Api.V1.ManualImport Size = resource.Size, Artist = resource.Artist == null ? null : _artistService.GetArtist(resource.Artist.Id), Album = resource.Album == null ? null : _albumService.GetAlbum(resource.Album.Id), + Release = resource.AlbumReleaseId == 0 ? null : _releaseService.GetRelease(resource.AlbumReleaseId), Quality = resource.Quality, Language = resource.Language, DownloadId = resource.DownloadId diff --git a/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs b/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs index f09656fc3..cd3c60b17 100644 --- a/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs +++ b/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs @@ -9,6 +9,7 @@ using Lidarr.Api.V1.Tracks; using Lidarr.Http.REST; using System.Collections.Generic; using System.Linq; +using NzbDrone.Core.Music; namespace Lidarr.Api.V1.ManualImport { @@ -21,6 +22,7 @@ namespace Lidarr.Api.V1.ManualImport public long Size { get; set; } public ArtistResource Artist { get; set; } public AlbumResource Album { get; set; } + public int AlbumReleaseId { get; set; } public List Tracks { get; set; } public QualityModel Quality { get; set; } public Language Language { get; set; } @@ -45,6 +47,7 @@ namespace Lidarr.Api.V1.ManualImport Size = model.Size, Artist = model.Artist.ToResource(), Album = model.Album.ToResource(), + AlbumReleaseId = model.Release?.Id ?? 0, Tracks = model.Tracks.ToResource(), Quality = model.Quality, Language = model.Language, diff --git a/src/Lidarr.Api.V1/Tracks/TrackModuleWithSignalR.cs b/src/Lidarr.Api.V1/Tracks/TrackModuleWithSignalR.cs index cf3a8faa6..eefd9f1f7 100644 --- a/src/Lidarr.Api.V1/Tracks/TrackModuleWithSignalR.cs +++ b/src/Lidarr.Api.V1/Tracks/TrackModuleWithSignalR.cs @@ -61,7 +61,7 @@ namespace Lidarr.Api.V1.Tracks if (includeArtist || includeTrackFile) { - var artist = track.Artist ?? _artistService.GetArtist(track.ArtistId); + var artist = track.Artist.Value; if (includeArtist) { @@ -87,17 +87,15 @@ namespace Lidarr.Api.V1.Tracks { var track = tracks[i]; var resource = result[i]; - - var series = track.Artist ?? artistDict.GetValueOrDefault(tracks[i].ArtistId) ?? _artistService.GetArtist(tracks[i].ArtistId); - artistDict[series.Id] = series; + var artist = track.Artist.Value; if (includeArtist) { - resource.Artist = series.ToResource(); + resource.Artist = artist.ToResource(); } if (includeTrackFile && tracks[i].TrackFileId != 0) { - resource.TrackFile = tracks[i].TrackFile.Value.ToResource(series, _upgradableSpecification); + resource.TrackFile = tracks[i].TrackFile.Value.ToResource(artist, _upgradableSpecification); } } } diff --git a/src/Lidarr.Api.V1/Tracks/TrackResource.cs b/src/Lidarr.Api.V1/Tracks/TrackResource.cs index a940d6fa6..e07652d43 100644 --- a/src/Lidarr.Api.V1/Tracks/TrackResource.cs +++ b/src/Lidarr.Api.V1/Tracks/TrackResource.cs @@ -22,7 +22,6 @@ namespace Lidarr.Api.V1.Tracks public TrackFileResource TrackFile { get; set; } public int MediumNumber { get; set; } public bool HasFile { get; set; } - public bool Monitored { get; set; } public ArtistResource Artist { get; set; } public Ratings Ratings { get; set; } @@ -52,7 +51,6 @@ namespace Lidarr.Api.V1.Tracks Duration = model.Duration, MediumNumber = model.MediumNumber, HasFile = model.HasFile, - Monitored = model.Monitored, Ratings = model.Ratings, }; } diff --git a/src/Lidarr.Api.V1/Wanted/CutoffModule.cs b/src/Lidarr.Api.V1/Wanted/CutoffModule.cs index b58bc564a..cf29509c2 100644 --- a/src/Lidarr.Api.V1/Wanted/CutoffModule.cs +++ b/src/Lidarr.Api.V1/Wanted/CutoffModule.cs @@ -17,10 +17,9 @@ namespace Lidarr.Api.V1.Wanted public CutoffModule(IAlbumCutoffService albumCutoffService, IAlbumService albumService, IArtistStatisticsService artistStatisticsService, - IArtistService artistService, IUpgradableSpecification upgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster) - : base(albumService, artistStatisticsService, artistService, upgradableSpecification, signalRBroadcaster, "wanted/cutoff") + : base(albumService, artistStatisticsService, upgradableSpecification, signalRBroadcaster, "wanted/cutoff") { _albumCutoffService = albumCutoffService; GetResourcePaged = GetCutoffUnmetAlbums; @@ -41,11 +40,11 @@ namespace Lidarr.Api.V1.Wanted if (filter != null && filter.Value == "false") { - pagingSpec.FilterExpressions.Add(v => v.Monitored == false || v.Artist.Monitored == false); + pagingSpec.FilterExpressions.Add(v => v.Monitored == false || v.Artist.Value.Monitored == false); } else { - pagingSpec.FilterExpressions.Add(v => v.Monitored == true && v.Artist.Monitored == true); + pagingSpec.FilterExpressions.Add(v => v.Monitored == true && v.Artist.Value.Monitored == true); } var resource = ApplyToPage(_albumCutoffService.AlbumsWhereCutoffUnmet, pagingSpec, v => MapToResource(v, includeArtist)); diff --git a/src/Lidarr.Api.V1/Wanted/MissingModule.cs b/src/Lidarr.Api.V1/Wanted/MissingModule.cs index d0ec478e8..8fb06e821 100644 --- a/src/Lidarr.Api.V1/Wanted/MissingModule.cs +++ b/src/Lidarr.Api.V1/Wanted/MissingModule.cs @@ -14,10 +14,9 @@ namespace Lidarr.Api.V1.Wanted { public MissingModule(IAlbumService albumService, IArtistStatisticsService artistStatisticsService, - IArtistService artistService, IUpgradableSpecification upgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster) - : base(albumService, artistStatisticsService, artistService, upgradableSpecification, signalRBroadcaster, "wanted/missing") + : base(albumService, artistStatisticsService, upgradableSpecification, signalRBroadcaster, "wanted/missing") { GetResourcePaged = GetMissingAlbums; } @@ -37,11 +36,11 @@ namespace Lidarr.Api.V1.Wanted if (monitoredFilter != null && monitoredFilter.Value == "false") { - pagingSpec.FilterExpressions.Add(v => v.Monitored == false || v.Artist.Monitored == false); + pagingSpec.FilterExpressions.Add(v => v.Monitored == false || v.Artist.Value.Monitored == false); } else { - pagingSpec.FilterExpressions.Add(v => v.Monitored == true && v.Artist.Monitored == true); + pagingSpec.FilterExpressions.Add(v => v.Monitored == true && v.Artist.Value.Monitored == true); } var resource = ApplyToPage(_albumService.AlbumsWithoutFiles, pagingSpec, v => MapToResource(v, includeArtist)); diff --git a/src/Marr.Data/LazyLoaded.cs b/src/Marr.Data/LazyLoaded.cs index 10d9c13d1..ae884a6ff 100644 --- a/src/Marr.Data/LazyLoaded.cs +++ b/src/Marr.Data/LazyLoaded.cs @@ -36,6 +36,11 @@ namespace Marr.Data } } + public bool ShouldSerializeValue() + { + return IsLoaded; + } + public bool IsLoaded { get; protected set; } public virtual void Prepare(Func dataMapperFactory, object parent) diff --git a/src/NzbDrone.Common/Cloud/LidarrCloudRequestBuilder.cs b/src/NzbDrone.Common/Cloud/LidarrCloudRequestBuilder.cs index a9e9d2069..e98910393 100644 --- a/src/NzbDrone.Common/Cloud/LidarrCloudRequestBuilder.cs +++ b/src/NzbDrone.Common/Cloud/LidarrCloudRequestBuilder.cs @@ -16,7 +16,7 @@ namespace NzbDrone.Common.Cloud Services = new HttpRequestBuilder("https://services.lidarr.audio/v1/") .CreateFactory(); - Search = new HttpRequestBuilder("https://api.lidarr.audio/api/v0.3/{route}") + Search = new HttpRequestBuilder("https://api.lidarr.audio/api/v0.4/{route}") .CreateFactory(); } diff --git a/src/NzbDrone.Core.Test/ArtistStatsTests/ArtistStatisticsFixture.cs b/src/NzbDrone.Core.Test/ArtistStatsTests/ArtistStatisticsFixture.cs index fd96a1f28..34a00f50f 100644 --- a/src/NzbDrone.Core.Test/ArtistStatsTests/ArtistStatisticsFixture.cs +++ b/src/NzbDrone.Core.Test/ArtistStatsTests/ArtistStatisticsFixture.cs @@ -16,6 +16,7 @@ namespace NzbDrone.Core.Test.ArtistStatsTests { private Artist _artist; private Album _album; + private AlbumRelease _release; private Track _track; private TrackFile _trackFile; @@ -23,25 +24,31 @@ namespace NzbDrone.Core.Test.ArtistStatsTests public void Setup() { _artist = Builder.CreateNew() - .BuildNew(); - + .With(a => a.ArtistMetadataId = 10) + .BuildNew(); + Db.Insert(_artist); + _album = Builder.CreateNew() - .With(e => e.ReleaseDate = DateTime.Today.AddDays(5)) - .BuildNew(); - - _artist.Id = Db.Insert(_artist).Id; - _artist.Id = Db.Insert(_album).Id; + .With(e => e.ReleaseDate = DateTime.Today.AddDays(-5)) + .With(e => e.ArtistMetadataId = 10) + .BuildNew(); + Db.Insert(_album); + + _release = Builder.CreateNew() + .With(e => e.AlbumId = _album.Id) + .With(e => e.Monitored = true) + .BuildNew(); + Db.Insert(_release); _track = Builder.CreateNew() .With(e => e.TrackFileId = 0) - .With(e => e.Monitored = false) - .With(e => e.ArtistId = _artist.Id) - .With(e => e.AlbumId = _album.Id) + .With(e => e.Artist = _artist) + .With(e => e.AlbumReleaseId = _release.Id) .BuildNew(); _trackFile = Builder.CreateNew() - .With(e => e.ArtistId = _artist.Id) - .With(e => e.AlbumId = _album.Id) + .With(e => e.Artist = _artist) + .With(e => e.Album = _album) .With(e => e.Quality = new QualityModel(Quality.MP3_256)) .BuildNew(); @@ -52,11 +59,6 @@ namespace NzbDrone.Core.Test.ArtistStatsTests _track.TrackFileId = 1; } - private void GivenMonitoredTrack() - { - _track.Monitored = true; - } - private void GivenTrack() { Db.Insert(_track); @@ -70,7 +72,6 @@ namespace NzbDrone.Core.Test.ArtistStatsTests [Test] public void should_get_stats_for_artist() { - GivenMonitoredTrack(); GivenTrack(); var stats = Subject.ArtistStatistics(); @@ -115,6 +116,7 @@ namespace NzbDrone.Core.Test.ArtistStatsTests [Test] public void should_have_size_on_disk_when_track_file_exists() { + GivenTrackWithFile(); GivenTrack(); GivenTrackFile(); diff --git a/src/NzbDrone.Core.Test/Datastore/Converters/EnumIntConverterFixture.cs b/src/NzbDrone.Core.Test/Datastore/Converters/EnumIntConverterFixture.cs index cc4144efb..49bd70074 100644 --- a/src/NzbDrone.Core.Test/Datastore/Converters/EnumIntConverterFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Converters/EnumIntConverterFixture.cs @@ -29,7 +29,7 @@ namespace NzbDrone.Core.Test.Datastore.Converters public void should_return_enum_when_getting_int_from_db() { var mockMemberInfo = new Mock(); - mockMemberInfo.SetupGet(s => s.DeclaringType).Returns(typeof(Artist)); + mockMemberInfo.SetupGet(s => s.DeclaringType).Returns(typeof(ArtistMetadata)); mockMemberInfo.SetupGet(s => s.Name).Returns("Status"); var expected = ArtistStatusType.Continuing; diff --git a/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs b/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs index fe9092fb1..dfdcb5e28 100644 --- a/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs @@ -17,13 +17,15 @@ namespace NzbDrone.Core.Test.Datastore public void one_to_one() { var trackFile = Builder.CreateNew() - .With(c => c.Quality = new QualityModel()) - .With(c => c.Language = Language.English) - .BuildNew(); + .With(c => c.Id = 0) + .With(c => c.Quality = new QualityModel()) + .With(c => c.Language = Language.English) + .BuildNew(); Db.Insert(trackFile); var track = Builder.CreateNew() + .With(c => c.Id = 0) .With(c => c.TrackFileId = trackFile.Id) .BuildNew(); @@ -38,7 +40,9 @@ namespace NzbDrone.Core.Test.Datastore .Excluding(c => c.DateAdded) .Excluding(c => c.Path) .Excluding(c => c.Artist) - .Excluding(c => c.Tracks)); + .Excluding(c => c.Tracks) + .Excluding(c => c.Album) + .Excluding(c => c.ArtistId)); } [Test] diff --git a/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs b/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs index da5b57720..106a16e45 100644 --- a/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs @@ -9,6 +9,8 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Languages; using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Test.Languages; +using Marr.Data.QGen; +using System.Collections.Generic; namespace NzbDrone.Core.Test.Datastore { @@ -38,24 +40,44 @@ namespace NzbDrone.Core.Test.Datastore profile = Db.Insert(profile); languageProfile = Db.Insert(languageProfile); + var metadata = Builder.CreateNew() + .With(v => v.Id = 0) + .Build(); + Db.Insert(metadata); + var artist = Builder.CreateListOfSize(1) .All() + .With(v => v.Id = 0) .With(v => v.ProfileId = profile.Id) .With(v => v.LanguageProfileId = languageProfile.Id) + .With(v => v.ArtistMetadataId = metadata.Id) .BuildListOfNew(); Db.InsertMany(artist); var albums = Builder.CreateListOfSize(3) .All() - .With(v => v.ArtistId = artist[0].Id) + .With(v => v.Id = 0) + .With(v => v.ArtistMetadataId = metadata.Id) .BuildListOfNew(); Db.InsertMany(albums); + var releases = new List(); + foreach (var album in albums) + { + releases.Add( + Builder.CreateNew() + .With(v => v.Id = 0) + .With(v => v.AlbumId = album.Id) + .With(v => v.ForeignReleaseId = "test" + album.Id) + .Build()); + } + Db.InsertMany(releases); + var trackFiles = Builder.CreateListOfSize(1) .All() - .With(v => v.ArtistId = artist[0].Id) + .With(v => v.Id = 0) .With(v => v.Quality = new QualityModel()) .BuildListOfNew(); @@ -63,23 +85,22 @@ namespace NzbDrone.Core.Test.Datastore var tracks = Builder.CreateListOfSize(10) .All() - .With(v => v.Monitored = true) + .With(v => v.Id = 0) .With(v => v.TrackFileId = trackFiles[0].Id) - .With(v => v.ArtistId = artist[0].Id) + .With(v => v.AlbumReleaseId = releases[0].Id) .BuildListOfNew(); Db.InsertMany(tracks); } [Test] - [Ignore("This does not currently join correctly, however we are not using the joined info")] public void should_join_artist_when_query_for_albums() { var db = Mocker.Resolve(); var DataMapper = db.GetDataMapper(); var albums = DataMapper.Query() - .Join(Marr.Data.QGen.JoinType.Inner, v => v.Artist, (l, r) => l.ArtistId == r.Id) + .Join(JoinType.Inner, v => v.Artist, (l, r) => l.ArtistMetadataId == r.ArtistMetadataId) .ToList(); foreach (var album in albums) @@ -95,14 +116,19 @@ namespace NzbDrone.Core.Test.Datastore var DataMapper = db.GetDataMapper(); var tracks = DataMapper.Query() - .Join(Marr.Data.QGen.JoinType.Inner, v => v.Artist, (l, r) => l.ArtistId == r.Id) + .Join(JoinType.Inner, v => v.AlbumRelease, (l, r) => l.AlbumReleaseId == r.Id) + .Join(JoinType.Inner, v => v.Album, (l, r) => l.AlbumId == r.Id) + .Join(JoinType.Inner, v => v.Artist, (l, r) => l.ArtistMetadataId == r.ArtistMetadataId) .ToList(); foreach (var track in tracks) { - Assert.IsNotNull(track.Artist); - Assert.IsFalse(track.Artist.Profile.IsLoaded); - Assert.IsFalse(track.Artist.LanguageProfile.IsLoaded); + Assert.IsTrue(track.AlbumRelease.IsLoaded); + Assert.IsTrue(track.AlbumRelease.Value.Album.IsLoaded); + Assert.IsTrue(track.AlbumRelease.Value.Album.Value.Artist.IsLoaded); + Assert.IsNotNull(track.AlbumRelease.Value.Album.Value.Artist.Value); + Assert.IsFalse(track.AlbumRelease.Value.Album.Value.Artist.Value.Profile.IsLoaded); + Assert.IsFalse(track.AlbumRelease.Value.Album.Value.Artist.Value.LanguageProfile.IsLoaded); } } @@ -113,12 +139,12 @@ namespace NzbDrone.Core.Test.Datastore var DataMapper = db.GetDataMapper(); var tracks = DataMapper.Query() - .Join(Marr.Data.QGen.JoinType.Inner, v => v.TrackFile, (l, r) => l.TrackFileId == r.Id) + .Join(JoinType.Inner, v => v.TrackFile, (l, r) => l.TrackFileId == r.Id) .ToList(); foreach (var track in tracks) { - Assert.IsNull(track.Artist); + Assert.IsFalse(track.Artist.IsLoaded); Assert.IsTrue(track.TrackFile.IsLoaded); } } @@ -130,15 +156,20 @@ namespace NzbDrone.Core.Test.Datastore var DataMapper = db.GetDataMapper(); var tracks = DataMapper.Query() - .Join(Marr.Data.QGen.JoinType.Inner, v => v.Artist, (l, r) => l.ArtistId == r.Id) - .Join(Marr.Data.QGen.JoinType.Inner, v => v.Profile, (l, r) => l.ProfileId == r.Id) + .Join(JoinType.Inner, v => v.AlbumRelease, (l, r) => l.AlbumReleaseId == r.Id) + .Join(JoinType.Inner, v => v.Album, (l, r) => l.AlbumId == r.Id) + .Join(JoinType.Inner, v => v.Artist, (l, r) => l.ArtistMetadataId == r.ArtistMetadataId) + .Join(JoinType.Inner, v => v.Profile, (l, r) => l.ProfileId == r.Id) .ToList(); foreach (var track in tracks) { - Assert.IsNotNull(track.Artist); - Assert.IsTrue(track.Artist.Profile.IsLoaded); - Assert.IsFalse(track.Artist.LanguageProfile.IsLoaded); + Assert.IsTrue(track.AlbumRelease.IsLoaded); + Assert.IsTrue(track.AlbumRelease.Value.Album.IsLoaded); + Assert.IsTrue(track.AlbumRelease.Value.Album.Value.Artist.IsLoaded); + Assert.IsNotNull(track.AlbumRelease.Value.Album.Value.Artist.Value); + Assert.IsTrue(track.AlbumRelease.Value.Album.Value.Artist.Value.Profile.IsLoaded); + Assert.IsFalse(track.AlbumRelease.Value.Album.Value.Artist.Value.LanguageProfile.IsLoaded); } } @@ -149,15 +180,20 @@ namespace NzbDrone.Core.Test.Datastore var DataMapper = db.GetDataMapper(); var tracks = DataMapper.Query() - .Join(Marr.Data.QGen.JoinType.Inner, v => v.Artist, (l, r) => l.ArtistId == r.Id) - .Join(Marr.Data.QGen.JoinType.Inner, v => v.LanguageProfile, (l, r) => l.ProfileId == r.Id) - .ToList(); + .Join(JoinType.Inner, v => v.AlbumRelease, (l, r) => l.AlbumReleaseId == r.Id) + .Join(JoinType.Inner, v => v.Album, (l, r) => l.AlbumId == r.Id) + .Join(JoinType.Inner, v => v.Artist, (l, r) => l.ArtistMetadataId == r.ArtistMetadataId) + .Join(JoinType.Inner, v => v.LanguageProfile, (l, r) => l.LanguageProfileId == r.Id) + .ToList(); foreach (var track in tracks) { - Assert.IsNotNull(track.Artist); - Assert.IsFalse(track.Artist.Profile.IsLoaded); - Assert.IsTrue(track.Artist.LanguageProfile.IsLoaded); + Assert.IsTrue(track.AlbumRelease.IsLoaded); + Assert.IsTrue(track.AlbumRelease.Value.Album.IsLoaded); + Assert.IsTrue(track.AlbumRelease.Value.Album.Value.Artist.IsLoaded); + Assert.IsNotNull(track.AlbumRelease.Value.Album.Value.Artist.Value); + Assert.IsFalse(track.AlbumRelease.Value.Album.Value.Artist.Value.Profile.IsLoaded); + Assert.IsTrue(track.AlbumRelease.Value.Album.Value.Artist.Value.LanguageProfile.IsLoaded); } } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs index 72106c4e8..c07b8d6df 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs @@ -25,6 +25,20 @@ namespace NzbDrone.Core.Test.DecisionEngineTests private Artist artist; private QualityDefinition qualityType; + private Album AlbumBuilder(int id = 0) + { + return new Album + { + Id = id, + AlbumReleases = new List { new AlbumRelease + { + Duration = 0, + Monitored = true + } + } + }; + } + [SetUp] public void Setup() { @@ -36,7 +50,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Artist = artist, Release = new ReleaseInfo(), ParsedAlbumInfo = new ParsedAlbumInfo { Quality = new QualityModel(Quality.MP3_192, new Revision(version: 2)) }, - Albums = new List { new Album(), new Album(), new Album(), new Album(), new Album(), new Album() } + Albums = new List { AlbumBuilder(), AlbumBuilder(), AlbumBuilder(), AlbumBuilder(), AlbumBuilder(), AlbumBuilder() } }; parseResultMulti = new RemoteAlbum @@ -44,7 +58,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Artist = artist, Release = new ReleaseInfo(), ParsedAlbumInfo = new ParsedAlbumInfo { Quality = new QualityModel(Quality.MP3_192, new Revision(version: 2)) }, - Albums = new List { new Album(), new Album() } + Albums = new List { AlbumBuilder(), AlbumBuilder() } }; parseResultSingle = new RemoteAlbum @@ -52,7 +66,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Artist = artist, Release = new ReleaseInfo(), ParsedAlbumInfo = new ParsedAlbumInfo { Quality = new QualityModel(Quality.MP3_192, new Revision(version: 2)) }, - Albums = new List { new Album { Id = 2 } } + Albums = new List { AlbumBuilder(2) } }; @@ -71,8 +85,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Mocker.GetMock().Setup( s => s.GetAlbumsByArtist(It.IsAny())) .Returns(new List() { - new Album(), new Album(), new Album(), new Album(), new Album(), - new Album(), new Album(), new Album(), new Album { Id = 2 }, new Album() }); + AlbumBuilder(), AlbumBuilder(), AlbumBuilder(), AlbumBuilder(), AlbumBuilder(), + AlbumBuilder(), AlbumBuilder(), AlbumBuilder(), AlbumBuilder(2), AlbumBuilder() }); } private void GivenLastAlbum() @@ -80,8 +94,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Mocker.GetMock().Setup( s => s.GetAlbumsByArtist(It.IsAny())) .Returns(new List { - new Album(), new Album(), new Album(), new Album(), new Album(), - new Album(), new Album(), new Album(), new Album(), new Album { Id = 2 } }); + AlbumBuilder(), AlbumBuilder(), AlbumBuilder(), AlbumBuilder(), AlbumBuilder(), + AlbumBuilder(), AlbumBuilder(), AlbumBuilder(), AlbumBuilder(), AlbumBuilder(2) }); } [TestCase(TWENTY_MINUTE_EP_MILLIS, 20, false)] @@ -92,7 +106,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [TestCase(FORTY_FIVE_MINUTE_LP_MILLIS, 75, false)] public void single_album(int runtime, int sizeInMegaBytes, bool expectedResult) { - parseResultSingle.Albums.Select(c => { c.Duration = runtime; return c; }).ToList(); + parseResultSingle.Albums.Select(c => { c.AlbumReleases.Value[0].Duration = runtime; return c; }).ToList(); parseResultSingle.Artist = artist; parseResultSingle.Release.Size = sizeInMegaBytes.Megabytes(); @@ -107,7 +121,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [TestCase(FORTY_FIVE_MINUTE_LP_MILLIS, 75 * 2, false)] public void multi_album(int runtime, int sizeInMegaBytes, bool expectedResult) { - parseResultMulti.Albums.Select(c => { c.Duration = runtime; return c; }).ToList(); + parseResultMulti.Albums.Select(c => { c.AlbumReleases.Value[0].Duration = runtime; return c; }).ToList(); parseResultMulti.Artist = artist; parseResultMulti.Release.Size = sizeInMegaBytes.Megabytes(); @@ -122,7 +136,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [TestCase(FORTY_FIVE_MINUTE_LP_MILLIS, 75 * 6, false)] public void multiset_album(int runtime, int sizeInMegaBytes, bool expectedResult) { - parseResultMultiSet.Albums.Select(c => { c.Duration = runtime; return c; }).ToList(); + parseResultMultiSet.Albums.Select(c => { c.AlbumReleases.Value[0].Duration = runtime; return c; }).ToList(); parseResultMultiSet.Artist = artist; parseResultMultiSet.Release.Size = sizeInMegaBytes.Megabytes(); @@ -133,7 +147,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void should_return_true_if_size_is_zero() { GivenLastAlbum(); - parseResultSingle.Albums.Select(c => { c.Duration = TWENTY_MINUTE_EP_MILLIS; return c; }).ToList(); + parseResultSingle.Albums.Select(c => { c.AlbumReleases.Value[0].Duration = TWENTY_MINUTE_EP_MILLIS; return c; }).ToList(); parseResultSingle.Artist = artist; parseResultSingle.Release.Size = 0; qualityType.MinSize = 150; @@ -146,7 +160,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void should_return_true_if_unlimited_20_minute() { GivenLastAlbum(); - parseResultSingle.Albums.Select(c => { c.Duration = TWENTY_MINUTE_EP_MILLIS; return c; }).ToList(); + parseResultSingle.Albums.Select(c => { c.AlbumReleases.Value[0].Duration = TWENTY_MINUTE_EP_MILLIS; return c; }).ToList(); parseResultSingle.Artist = artist; parseResultSingle.Release.Size = (HIGH_KBPS_BITRATE * 128) * (TWENTY_MINUTE_EP_MILLIS / 1000); qualityType.MaxSize = null; @@ -158,7 +172,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void should_return_true_if_unlimited_45_minute() { GivenLastAlbum(); - parseResultSingle.Albums.Select(c => { c.Duration = FORTY_FIVE_MINUTE_LP_MILLIS; return c; }).ToList(); + parseResultSingle.Albums.Select(c => { c.AlbumReleases.Value[0].Duration = FORTY_FIVE_MINUTE_LP_MILLIS; return c; }).ToList(); parseResultSingle.Artist = artist; parseResultSingle.Release.Size = (HIGH_KBPS_BITRATE * 128) * (FORTY_FIVE_MINUTE_LP_MILLIS / 1000); qualityType.MaxSize = null; diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs index 75c2af1aa..a3c4be868 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs @@ -11,6 +11,7 @@ using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Music; using NzbDrone.Test.Common; using FizzWare.NBuilder; +using Marr.Data; namespace NzbDrone.Core.Test.DecisionEngineTests { @@ -233,7 +234,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests var albums = Builder.CreateListOfSize(2) .All() .With(v => v.ArtistId, artist.Id) - .With(v => v.Artist, artist) + .With(v => v.Artist, new LazyLoaded(artist)) .BuildList(); var criteria = new ArtistSearchCriteria { Albums = albums.Take(1).ToList()}; diff --git a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs index b2fbc558f..c971cb115 100644 --- a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs @@ -84,7 +84,7 @@ namespace NzbDrone.Core.Test.Download .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { - new ImportResult(new ImportDecision(new LocalTrack() { Path = @"C:\TestPath\Droned.S01E01.mkv" })) + new ImportResult(new ImportDecision(new LocalTrack() { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })) }); } @@ -170,11 +170,11 @@ namespace NzbDrone.Core.Test.Download { new ImportResult( new ImportDecision( - new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv"})), + new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic()})), new ImportResult( new ImportDecision( - new LocalTrack {Path = @"C:\TestPath\Droned.S01E02.mkv"})) + new LocalTrack {Path = @"C:\TestPath\Droned.S01E02.mkv".AsOsAgnostic()})) }); Subject.Process(_trackedDownload); @@ -191,11 +191,11 @@ namespace NzbDrone.Core.Test.Download { new ImportResult( new ImportDecision( - new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv"}, new Rejection("Rejected!")), "Test Failure"), + new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic()}, new Rejection("Rejected!")), "Test Failure"), new ImportResult( new ImportDecision( - new LocalTrack {Path = @"C:\TestPath\Droned.S01E02.mkv"},new Rejection("Rejected!")), "Test Failure") + new LocalTrack {Path = @"C:\TestPath\Droned.S01E02.mkv".AsOsAgnostic()},new Rejection("Rejected!")), "Test Failure") }); Subject.Process(_trackedDownload); @@ -215,11 +215,11 @@ namespace NzbDrone.Core.Test.Download { new ImportResult( new ImportDecision( - new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv"}, new Rejection("Rejected!")), "Test Failure"), + new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic()}, new Rejection("Rejected!")), "Test Failure"), new ImportResult( new ImportDecision( - new LocalTrack {Path = @"C:\TestPath\Droned.S01E02.mkv"},new Rejection("Rejected!")), "Test Failure") + new LocalTrack {Path = @"C:\TestPath\Droned.S01E02.mkv".AsOsAgnostic()},new Rejection("Rejected!")), "Test Failure") }); _trackedDownload.RemoteAlbum.Albums.Clear(); @@ -236,8 +236,8 @@ namespace NzbDrone.Core.Test.Download .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { - new ImportResult(new ImportDecision(new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure"), - new ImportResult(new ImportDecision(new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure") + new ImportResult(new ImportDecision(new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic()}),"Test Failure"), + new ImportResult(new ImportDecision(new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic()}),"Test Failure") }); @@ -260,8 +260,8 @@ namespace NzbDrone.Core.Test.Download .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { - new ImportResult(new ImportDecision(new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv"})), - new ImportResult(new ImportDecision(new LocalTrack{Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure") + new ImportResult(new ImportDecision(new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic()})), + new ImportResult(new ImportDecision(new LocalTrack{Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic()}),"Test Failure") }); Subject.Process(_trackedDownload); @@ -283,9 +283,9 @@ namespace NzbDrone.Core.Test.Download .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { - new ImportResult(new ImportDecision(new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv"})), - new ImportResult(new ImportDecision(new LocalTrack{Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure"), - new ImportResult(new ImportDecision(new LocalTrack{Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure") + new ImportResult(new ImportDecision(new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic()})), + new ImportResult(new ImportDecision(new LocalTrack{Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic()}),"Test Failure"), + new ImportResult(new ImportDecision(new LocalTrack{Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic()}),"Test Failure") }); @@ -303,7 +303,7 @@ namespace NzbDrone.Core.Test.Download .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { - new ImportResult(new ImportDecision(new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv"})) + new ImportResult(new ImportDecision(new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic()})) }); Mocker.GetMock() @@ -324,7 +324,7 @@ namespace NzbDrone.Core.Test.Download .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { - new ImportResult(new ImportDecision(new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv"})) + new ImportResult(new ImportDecision(new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic()})) }); Mocker.GetMock() @@ -359,7 +359,7 @@ namespace NzbDrone.Core.Test.Download .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { - new ImportResult(new ImportDecision(new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv"})) + new ImportResult(new ImportDecision(new LocalTrack {Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic()})) }); Subject.Process(_trackedDownload, true); diff --git a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs index fa10cfb0f..439736688 100644 --- a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs +++ b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs @@ -59,8 +59,10 @@ namespace NzbDrone.Core.Test.HistoryTests var artist = Builder.CreateNew().Build(); var tracks = Builder.CreateListOfSize(1).Build().ToList(); var trackFile = Builder.CreateNew() - .With(f => f.SceneName = null) - .Build(); + .With(f => f.SceneName = null) + .With(f => f.Artist = artist) + .With(f => f.Album = new Album()) + .Build(); var localTrack = new LocalTrack { diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedAlbumsFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedAlbumsFixture.cs index cfe479c81..aeac11da6 100644 --- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedAlbumsFixture.cs +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedAlbumsFixture.cs @@ -14,7 +14,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers public void should_delete_orphaned_albums() { var album = Builder.CreateNew() - .BuildNew(); + .BuildNew(); Db.Insert(album); Subject.Clean(); @@ -25,19 +25,20 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers public void should_not_delete_unorphaned_albums() { var artist = Builder.CreateNew() - .BuildNew(); + .With(e => e.Metadata = new ArtistMetadata {Id = 1}) + .BuildNew(); Db.Insert(artist); var albums = Builder.CreateListOfSize(2) - .TheFirst(1) - .With(e => e.ArtistId = artist.Id) - .BuildListOfNew(); + .TheFirst(1) + .With(e => e.ArtistMetadataId = artist.Metadata.Value.Id) + .BuildListOfNew(); Db.InsertMany(albums); Subject.Clean(); AllStoredModels.Should().HaveCount(1); - AllStoredModels.Should().Contain(e => e.ArtistId == artist.Id); + AllStoredModels.Should().Contain(e => e.ArtistMetadataId == artist.Metadata.Value.Id); } } } diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs index fa1085cca..a118e4696 100644 --- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs @@ -21,7 +21,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers .BuildNew(); _album = Builder.CreateNew() - .BuildNew(); + .BuildNew(); } private void GivenArtist() @@ -106,4 +106,4 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers AllStoredModels.Should().Contain(h => h.AlbumId == _album.Id); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedMetadataFilesFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedMetadataFilesFixture.cs index ed8877812..78a0515f8 100644 --- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedMetadataFilesFixture.cs +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedMetadataFilesFixture.cs @@ -71,7 +71,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers .BuildNew(); var album = Builder.CreateNew() - .BuildNew(); + .BuildNew(); Db.Insert(artist); Db.Insert(album); diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedTracksFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedTracksFixture.cs index c46e86894..7c46f932b 100644 --- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedTracksFixture.cs +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedTracksFixture.cs @@ -24,20 +24,20 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers [Test] public void should_not_delete_unorphaned_tracks() { - var album = Builder.CreateNew() + var release = Builder.CreateNew() .BuildNew(); - Db.Insert(album); + Db.Insert(release); var tracks = Builder.CreateListOfSize(2) .TheFirst(1) - .With(e => e.AlbumId = album.Id) + .With(e => e.AlbumReleaseId = release.Id) .BuildListOfNew(); Db.InsertMany(tracks); Subject.Clean(); AllStoredModels.Should().HaveCount(1); - AllStoredModels.Should().Contain(e => e.AlbumId == album.Id); + AllStoredModels.Should().Contain(e => e.AlbumReleaseId == release.Id); } } } diff --git a/src/NzbDrone.Core.Test/IndexerSearchTests/ArtistSearchServiceFixture.cs b/src/NzbDrone.Core.Test/IndexerSearchTests/ArtistSearchServiceFixture.cs index 2a3723451..422ad17e3 100644 --- a/src/NzbDrone.Core.Test/IndexerSearchTests/ArtistSearchServiceFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerSearchTests/ArtistSearchServiceFixture.cs @@ -49,7 +49,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests Mocker.GetMock() .Verify(v => v.ArtistSearch(_artist.Id, false, true, false), - Times.Exactly(_artist.Albums.Count(s => s.Monitored))); + Times.Exactly(_artist.Albums.Value.Count(s => s.Monitored))); } } } diff --git a/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs b/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs index f604a9d83..4877cb48b 100644 --- a/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs @@ -29,7 +29,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests _artist = Builder.CreateNew() .With(v => v.Id = 2) - .With(v => v.Images = new List { new MediaCover.MediaCover(MediaCoverTypes.Poster, "") }) + .With(v => v.Metadata.Value.Images = new List { new MediaCover.MediaCover(MediaCoverTypes.Poster, "") }) .Build(); _album = Builder.CreateNew() diff --git a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedTracksFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedTracksFixture.cs index 4e1edf0f9..c0f52e789 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedTracksFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedTracksFixture.cs @@ -49,7 +49,10 @@ namespace NzbDrone.Core.Test.MediaFiles var album = Builder.CreateNew() .With(e => e.Artist = artist) .Build(); - + + var release = Builder.CreateNew() + .With(e => e.AlbumId = album.Id) + .Build(); var tracks = Builder.CreateListOfSize(5) .Build(); @@ -68,6 +71,7 @@ namespace NzbDrone.Core.Test.MediaFiles { Artist = artist, Album = album, + Release = release, Tracks = new List { track }, Path = Path.Combine(artist.Path, "Alien Ant Farm - 01 - Pilot.mp3"), Quality = new QualityModel(Quality.MP3_256), diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs index 03805aaa8..5f6e07b65 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs @@ -3,6 +3,7 @@ using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Qualities; +using NzbDrone.Core.Music; using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.MediaFiles @@ -13,22 +14,61 @@ namespace NzbDrone.Core.Test.MediaFiles [Test] public void get_files_by_artist() { + var files = Builder.CreateListOfSize(10) .All() .With(c => c.Id = 0) .With(c => c.Quality =new QualityModel(Quality.MP3_192)) - .Random(4) - .With(s => s.ArtistId = 12) .BuildListOfNew(); - Db.InsertMany(files); + Db.All().Should().HaveCount(10); + + var artist = Builder.CreateNew() + .With(a => a.ArtistMetadataId = 11) + .With(a => a.Id = 0) + .Build(); + Db.Insert(artist); + + var album = Builder.CreateNew() + .With(a => a.Id = 0) + .With(a => a.ArtistMetadataId = artist.ArtistMetadataId) + .Build(); + Db.Insert(album); + + var release = Builder.CreateNew() + .With(a => a.Id = 0) + .With(a => a.AlbumId = album.Id) + .With(a => a.Monitored = true) + .Build(); + Db.Insert(release); + + var track = Builder.CreateListOfSize(10) + .TheFirst(1) + .With(a => a.TrackFileId = files[1].Id) + .TheNext(1) + .With(a => a.TrackFileId = files[2].Id) + .TheNext(1) + .With(a => a.TrackFileId = files[3].Id) + .TheNext(1) + .With(a => a.TrackFileId = files[4].Id) + .TheNext(6) + .With(a => a.TrackFileId = 0) + .All() + .With(a => a.Id = 0) + .With(a => a.AlbumReleaseId = release.Id) + .Build(); + Db.InsertMany(track); + + Db.All().Should().HaveCount(1); + Db.All().Should().HaveCount(1); + Db.All().Should().HaveCount(10); - var artistFiles = Subject.GetFilesByArtist(12); + var artistFiles = Subject.GetFilesByArtist(artist.Id); artistFiles.Should().HaveCount(4); - artistFiles.Should().OnlyContain(c => c.ArtistId == 12); + artistFiles.Should().OnlyContain(c => c.ArtistId == artist.Id); } } } diff --git a/src/NzbDrone.Core.Test/MediaFiles/RenameTrackFileServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/RenameTrackFileServiceFixture.cs index 650d1f5a2..2e1f60544 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/RenameTrackFileServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/RenameTrackFileServiceFixture.cs @@ -25,7 +25,7 @@ namespace NzbDrone.Core.Test.MediaFiles _trackFiles = Builder.CreateListOfSize(2) .All() - .With(e => e.ArtistId = _artist.Id) + .With(e => e.Artist = _artist) .Build() .ToList(); diff --git a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs index 3be71bbb9..51c830d5b 100644 --- a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs +++ b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs @@ -41,6 +41,14 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook Allowed = true } }, + ReleaseStatuses = new List + { + new ProfileReleaseStatusItem + { + ReleaseStatus = ReleaseStatus.Official, + Allowed = true + } + } }; Mocker.GetMock() @@ -58,34 +66,33 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook { var details = Subject.GetArtistInfo(mbId, 1); - ValidateArtist(details.Item1); - ValidateAlbums(details.Item2); + ValidateArtist(details); + ValidateAlbums(details.Albums.Value); - details.Item1.Name.Should().Be(name); + details.Name.Should().Be(name); } - [TestCase("12fa3845-7c62-36e5-a8da-8be137155a72", null, "Hysteria")] - public void should_be_able_to_get_album_detail(string mbId, string release, string name) + [TestCase("12fa3845-7c62-36e5-a8da-8be137155a72", "Hysteria")] + public void should_be_able_to_get_album_detail(string mbId, string name) { - var details = Subject.GetAlbumInfo(mbId, release); + var details = Subject.GetAlbumInfo(mbId); - ValidateAlbums(new List {details.Item1}); + ValidateAlbums(new List {details.Item2}); - details.Item1.Title.Should().Be(name); + details.Item2.Title.Should().Be(name); } [TestCase("12fa3845-7c62-36e5-a8da-8be137155a72", "3c186b52-ca73-46a3-a8e6-04559bfbb581",1, 13, "Hysteria")] [TestCase("12fa3845-7c62-36e5-a8da-8be137155a72", "dee9ca6f-4f84-4359-82a9-b75a37ffc316",2, 27,"Hysteria")] public void should_be_able_to_get_album_detail_with_release(string mbId, string release, int mediaCount, int trackCount, string name) { - var details = Subject.GetAlbumInfo(mbId, release); - - ValidateAlbums(new List { details.Item1 }); + var details = Subject.GetAlbumInfo(mbId); - details.Item1.Media.Count.Should().Be(mediaCount); - details.Item2.Count.Should().Be(trackCount); + ValidateAlbums(new List { details.Item2 }); - details.Item1.Title.Should().Be(name); + details.Item2.AlbumReleases.Value.Single(r => r.ForeignReleaseId == release).Media.Count.Should().Be(mediaCount); + details.Item2.AlbumReleases.Value.Single(r => r.ForeignReleaseId == release).Tracks.Value.Count.Should().Be(trackCount); + details.Item2.Title.Should().Be(name); } [Test] @@ -103,13 +110,13 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook [Test] public void getting_details_of_invalid_album() { - Assert.Throws(() => Subject.GetAlbumInfo("66c66aaa-6e2f-4930-8610-912e24c63ed1",null)); + Assert.Throws(() => Subject.GetAlbumInfo("66c66aaa-6e2f-4930-8610-912e24c63ed1")); } [Test] public void getting_details_of_invalid_guid_for_album() { - Assert.Throws(() => Subject.GetAlbumInfo("66c66aaa-6e2f-4930-aaaaaa", null)); + Assert.Throws(() => Subject.GetAlbumInfo("66c66aaa-6e2f-4930-aaaaaa")); } private void ValidateArtist(Artist artist) @@ -118,8 +125,8 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook artist.Name.Should().NotBeNullOrWhiteSpace(); artist.CleanName.Should().Be(Parser.Parser.CleanArtistName(artist.Name)); artist.SortName.Should().Be(Parser.Parser.NormalizeTitle(artist.Name)); - artist.Overview.Should().NotBeNullOrWhiteSpace(); - artist.Images.Should().NotBeEmpty(); + artist.Metadata.Value.Overview.Should().NotBeNullOrWhiteSpace(); + artist.Metadata.Value.Images.Should().NotBeEmpty(); artist.ForeignArtistId.Should().NotBeNullOrWhiteSpace(); } diff --git a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs index a6863d59b..ae2c825b9 100644 --- a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs +++ b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs @@ -40,6 +40,14 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook Allowed = true } }, + ReleaseStatuses = new List + { + new ProfileReleaseStatusItem + { + ReleaseStatus = ReleaseStatus.Official, + Allowed = true + } + } }; Mocker.GetMock() diff --git a/src/NzbDrone.Core.Test/MusicTests/AddAlbumFixture.cs b/src/NzbDrone.Core.Test/MusicTests/AddAlbumFixture.cs index b19f1f9d2..b7cbe2ddf 100644 --- a/src/NzbDrone.Core.Test/MusicTests/AddAlbumFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/AddAlbumFixture.cs @@ -19,6 +19,9 @@ namespace NzbDrone.Core.Test.MusicTests public class AddAlbumFixture : CoreTest { private Album _fakeAlbum; + private AlbumRelease _fakeRelease; + private readonly string _fakeArtistForeignId = "xxx-xxx-xxx"; + private readonly List _fakeArtists = new List { new ArtistMetadata() }; [SetUp] public void Setup() @@ -26,13 +29,18 @@ namespace NzbDrone.Core.Test.MusicTests _fakeAlbum = Builder .CreateNew() .Build(); + _fakeRelease = Builder + .CreateNew() + .Build(); + _fakeRelease.Tracks = new List(); + _fakeAlbum.AlbumReleases = new List {_fakeRelease}; } private void GivenValidAlbum(string lidarrId) { Mocker.GetMock() - .Setup(s => s.GetAlbumInfo(lidarrId, It.IsAny())) - .Returns(new Tuple>(_fakeAlbum, new List())); + .Setup(s => s.GetAlbumInfo(lidarrId)) + .Returns(new Tuple>(_fakeArtistForeignId, _fakeAlbum, _fakeArtists)); } [Test] @@ -59,8 +67,8 @@ namespace NzbDrone.Core.Test.MusicTests }; Mocker.GetMock() - .Setup(s => s.GetAlbumInfo(newAlbum.ForeignAlbumId, It.IsAny())) - .Throws(new AlbumNotFoundException(newAlbum.ForeignAlbumId)); + .Setup(s => s.GetAlbumInfo(newAlbum.ForeignAlbumId)) + .Throws(new AlbumNotFoundException(newAlbum.ForeignAlbumId)); Assert.Throws(() => Subject.AddAlbum(newAlbum)); diff --git a/src/NzbDrone.Core.Test/MusicTests/AddArtistFixture.cs b/src/NzbDrone.Core.Test/MusicTests/AddArtistFixture.cs index 38f064433..1f04fa6a3 100644 --- a/src/NzbDrone.Core.Test/MusicTests/AddArtistFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/AddArtistFixture.cs @@ -28,13 +28,14 @@ namespace NzbDrone.Core.Test.MusicTests .CreateNew() .With(s => s.Path = null) .Build(); + _fakeArtist.Albums = new List(); } private void GivenValidArtist(string lidarrId) { Mocker.GetMock() - .Setup(s => s.GetArtistInfo(lidarrId, It.IsAny())) - .Returns(new Tuple>(_fakeArtist, new List())); + .Setup(s => s.GetArtistInfo(lidarrId, It.IsAny())) + .Returns(_fakeArtist); } private void GivenValidPath() diff --git a/src/NzbDrone.Core.Test/MusicTests/AlbumRepositoryTests/AlbumRepositoryFixture.cs b/src/NzbDrone.Core.Test/MusicTests/AlbumRepositoryTests/AlbumRepositoryFixture.cs index d7245756d..eff2146fe 100644 --- a/src/NzbDrone.Core.Test/MusicTests/AlbumRepositoryTests/AlbumRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/AlbumRepositoryTests/AlbumRepositoryFixture.cs @@ -3,12 +3,7 @@ using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Music; using NzbDrone.Core.Test.Framework; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using NLog; namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests { @@ -19,7 +14,9 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests private Album _album; private Album _albumSpecial; private Album _albumSimilar; + private AlbumRelease _release; private AlbumRepository _albumRepo; + private ReleaseRepository _releaseRepo; [SetUp] public void Setup() @@ -28,11 +25,22 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests { Name = "Alien Ant Farm", Monitored = true, - MBId = "this is a fake id", - Id = 1 + ForeignArtistId = "this is a fake id", + Id = 1, + Metadata = new ArtistMetadata { + Id = 1 + } }; _albumRepo = Mocker.Resolve(); + _releaseRepo = Mocker.Resolve(); + + _release = Builder + .CreateNew() + .With(e => e.Id = 0) + .With(e => e.ForeignReleaseId = "e00e40a3-5ed5-4ed3-9c22-0a8ff4119bdf" ) + .With(e => e.Monitored = true) + .Build(); _album = new Album { @@ -40,19 +48,14 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests ForeignAlbumId = "1", CleanTitle = "anthology", Artist = _artist, - ArtistId = _artist.Id, AlbumType = "", - Releases = new List - { - new AlbumRelease - { - Id = "e00e40a3-5ed5-4ed3-9c22-0a8ff4119bdf" - } - } - + AlbumReleases = new List {_release }, }; _albumRepo.Insert(_album); + _release.AlbumId = _album.Id; + _releaseRepo.Insert(_release); + _albumRepo.Update(_album); _albumSpecial = new Album { @@ -60,13 +63,13 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests ForeignAlbumId = "2", CleanTitle = "", Artist = _artist, - ArtistId = _artist.Id, + ArtistId = _artist.ArtistMetadataId, AlbumType = "", - Releases = new List + AlbumReleases = new List { new AlbumRelease { - Id = "fake id" + ForeignReleaseId = "fake id" } } @@ -80,13 +83,13 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests ForeignAlbumId = "3", CleanTitle = "anthology2", Artist = _artist, - ArtistId = _artist.Id, + ArtistId = _artist.ArtistMetadataId, AlbumType = "", - Releases = new List + AlbumReleases = new List { new AlbumRelease { - Id = "fake id 2" + ForeignReleaseId = "fake id 2" } } @@ -110,7 +113,7 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests [TestCase("anthology!")] public void should_find_album_in_db_by_title(string title) { - var album = _albumRepo.FindByTitle(_artist.Id, title); + var album = _albumRepo.FindByTitle(_artist.ArtistMetadataId, title); album.Should().NotBeNull(); album.Title.Should().Be(_album.Title); @@ -119,7 +122,7 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests [Test] public void should_find_album_in_db_by_title_all_special_characters() { - var album = _albumRepo.FindByTitle(_artist.Id, "+"); + var album = _albumRepo.FindByTitle(_artist.ArtistMetadataId, "+"); album.Should().NotBeNull(); album.Title.Should().Be(_albumSpecial.Title); @@ -131,7 +134,7 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests [TestCase("÷")] public void should_not_find_album_in_db_by_incorrect_title(string title) { - var album = _albumRepo.FindByTitle(_artist.Id, title); + var album = _albumRepo.FindByTitle(_artist.ArtistMetadataId, title); album.Should().BeNull(); } diff --git a/src/NzbDrone.Core.Test/MusicTests/AlbumServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/AlbumServiceFixture.cs index 1a44e97ec..ec4641eb6 100644 --- a/src/NzbDrone.Core.Test/MusicTests/AlbumServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/AlbumServiceFixture.cs @@ -35,7 +35,7 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests }); Mocker.GetMock() - .Setup(s => s.GetAlbums(It.IsAny())) + .Setup(s => s.GetAlbumsByArtistMetadataId(It.IsAny())) .Returns(_albums); } diff --git a/src/NzbDrone.Core.Test/MusicTests/ArtistRepositoryTests/ArtistRepositoryFixture.cs b/src/NzbDrone.Core.Test/MusicTests/ArtistRepositoryTests/ArtistRepositoryFixture.cs index 0142842a3..9cba40c8d 100644 --- a/src/NzbDrone.Core.Test/MusicTests/ArtistRepositoryTests/ArtistRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/ArtistRepositoryTests/ArtistRepositoryFixture.cs @@ -18,21 +18,32 @@ namespace NzbDrone.Core.Test.MusicTests.ArtistRepositoryTests public class ArtistRepositoryFixture : DbTest { private ArtistRepository _artistRepo; + private ArtistMetadataRepository _artistMetadataRepo; - private Artist CreateArtist(string name) + private void AddArtist(string name) { - return Builder.CreateNew() + var metadata = Builder.CreateNew() + .With(a => a.Id = 0) .With(a => a.Name = name) + .BuildNew(); + + var artist = Builder.CreateNew() + .With(a => a.Id = 0) + .With(a => a.Metadata = metadata) .With(a => a.CleanName = Parser.Parser.CleanArtistName(name)) .With(a => a.ForeignArtistId = name) .BuildNew(); + + _artistMetadataRepo.Insert(artist); + _artistRepo.Insert(artist); } private void GivenArtists() { _artistRepo = Mocker.Resolve(); - _artistRepo.Insert(CreateArtist("The Black Eyed Peas")); - _artistRepo.Insert(CreateArtist("The Black Keys")); + _artistMetadataRepo = Mocker.Resolve(); + AddArtist("The Black Eyed Peas"); + AddArtist("The Black Keys"); } [Test] diff --git a/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs index 32e74c7c1..8957256d1 100644 --- a/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs @@ -19,29 +19,46 @@ namespace NzbDrone.Core.Test.MusicTests { private Artist _artist; private List _albums; + private List _releases; + private readonly string _fakeArtistForeignId = "xxx-xxx-xxx"; + private readonly List _fakeArtists = new List { new ArtistMetadata() }; [SetUp] public void Setup() { + + var release = Builder + .CreateNew() + .With(s => s.Media = new List { new Medium { Number = 1 } }) + .With(s => s.ForeignReleaseId = "xxx-xxx-xxx-xxx") + .With(s => s.Monitored = true) + .With(s => s.TrackCount = 10) + .Build(); + + _releases = new List { release }; + var album1 = Builder.CreateNew() + .With(s => s.Id = 1234) .With(s => s.ForeignAlbumId = "1") + .With(s => s.AlbumReleases = _releases) .Build(); - _albums = new List{album1}; + _albums = new List{ album1 }; _artist = Builder.CreateNew() - .With(s => s.Albums = new List - { - album1 - }) - .Build(); + .With(s => s.Albums = _albums) + .Build(); Mocker.GetMock() .Setup(s => s.GetArtist(_artist.Id)) .Returns(_artist); + + Mocker.GetMock() + .Setup(s => s.GetReleasesByAlbum(album1.Id)) + .Returns(new List { release }); Mocker.GetMock() - .Setup(s => s.GetAlbumInfo(It.IsAny(), It.IsAny())) + .Setup(s => s.GetAlbumInfo(It.IsAny())) .Callback(() => { throw new AlbumNotFoundException(album1.ForeignAlbumId); }); Mocker.GetMock() @@ -52,8 +69,8 @@ namespace NzbDrone.Core.Test.MusicTests private void GivenNewAlbumInfo(Album album) { Mocker.GetMock() - .Setup(s => s.GetAlbumInfo(_albums.First().ForeignAlbumId, It.IsAny())) - .Returns(new Tuple>(album, new List())); + .Setup(s => s.GetAlbumInfo(_albums.First().ForeignAlbumId)) + .Returns(new Tuple>(_fakeArtistForeignId, album, _fakeArtists)); } [Test] @@ -72,6 +89,7 @@ namespace NzbDrone.Core.Test.MusicTests { var newAlbumInfo = _albums.FirstOrDefault().JsonClone(); newAlbumInfo.ForeignAlbumId = _albums.First().ForeignAlbumId + 1; + newAlbumInfo.AlbumReleases = _releases; GivenNewAlbumInfo(newAlbumInfo); diff --git a/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs index c456c83a4..cf036d6cb 100644 --- a/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs @@ -35,8 +35,11 @@ namespace NzbDrone.Core.Test.MusicTests _albums = new List {_album1, _album2}; + var metadata = Builder.CreateNew().Build(); + _artist = Builder.CreateNew() - .Build(); + .With(a => a.Metadata = metadata) + .Build(); Mocker.GetMock() .Setup(s => s.GetArtist(_artist.Id)) @@ -55,7 +58,7 @@ namespace NzbDrone.Core.Test.MusicTests { Mocker.GetMock() .Setup(s => s.GetArtistInfo(_artist.ForeignArtistId, _artist.MetadataProfileId)) - .Returns(new Tuple>(artist, _albums)); + .Returns(artist); } [Test] @@ -73,6 +76,8 @@ namespace NzbDrone.Core.Test.MusicTests public void should_update_if_musicbrainz_id_changed() { var newArtistInfo = _artist.JsonClone(); + newArtistInfo.Metadata = _artist.Metadata.Value.JsonClone(); + newArtistInfo.Albums = _albums; newArtistInfo.ForeignArtistId = _artist.ForeignArtistId + 1; GivenNewArtistInfo(newArtistInfo); @@ -90,24 +95,24 @@ namespace NzbDrone.Core.Test.MusicTests public void should_not_throw_if_duplicate_album_is_in_existing_info() { var newArtistInfo = _artist.JsonClone(); - newArtistInfo.Albums.Add(Builder.CreateNew() - .With(s => s.ForeignAlbumId = "2") - .Build()); + newArtistInfo.Albums.Value.Add(Builder.CreateNew() + .With(s => s.ForeignAlbumId = "2") + .Build()); - _artist.Albums.Add(Builder.CreateNew() - .With(s => s.ForeignAlbumId = "2") - .Build()); + _artist.Albums.Value.Add(Builder.CreateNew() + .With(s => s.ForeignAlbumId = "2") + .Build()); - _artist.Albums.Add(Builder.CreateNew() - .With(s => s.ForeignAlbumId = "2") - .Build()); + _artist.Albums.Value.Add(Builder.CreateNew() + .With(s => s.ForeignAlbumId = "2") + .Build()); GivenNewArtistInfo(newArtistInfo); Subject.Execute(new RefreshArtistCommand(_artist.Id)); Mocker.GetMock() - .Verify(v => v.UpdateArtist(It.Is(s => s.Albums.Count == 2))); + .Verify(v => v.UpdateArtist(It.Is(s => s.Albums.Value.Count == 2))); } [Test] @@ -115,20 +120,20 @@ namespace NzbDrone.Core.Test.MusicTests public void should_filter_duplicate_albums() { var newArtistInfo = _artist.JsonClone(); - newArtistInfo.Albums.Add(Builder.CreateNew() - .With(s => s.ForeignAlbumId = "2") - .Build()); + newArtistInfo.Albums.Value.Add(Builder.CreateNew() + .With(s => s.ForeignAlbumId = "2") + .Build()); - newArtistInfo.Albums.Add(Builder.CreateNew() - .With(s => s.ForeignAlbumId = "2") - .Build()); + newArtistInfo.Albums.Value.Add(Builder.CreateNew() + .With(s => s.ForeignAlbumId = "2") + .Build()); GivenNewArtistInfo(newArtistInfo); Subject.Execute(new RefreshArtistCommand(_artist.Id)); Mocker.GetMock() - .Verify(v => v.UpdateArtist(It.Is(s => s.Albums.Count == 2))); + .Verify(v => v.UpdateArtist(It.Is(s => s.Albums.Value.Count == 2))); } } diff --git a/src/NzbDrone.Core.Test/MusicTests/ShouldRefreshArtistFixture.cs b/src/NzbDrone.Core.Test/MusicTests/ShouldRefreshArtistFixture.cs index c8dc43559..fee0024f6 100644 --- a/src/NzbDrone.Core.Test/MusicTests/ShouldRefreshArtistFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/ShouldRefreshArtistFixture.cs @@ -17,7 +17,7 @@ namespace NzbDrone.Core.Test.MusicTests public void Setup() { _artist = Builder.CreateNew() - .With(v => v.Status == ArtistStatusType.Continuing) + .With(v => v.Metadata.Value.Status == ArtistStatusType.Continuing) .Build(); Mocker.GetMock() @@ -31,7 +31,7 @@ namespace NzbDrone.Core.Test.MusicTests private void GivenArtistIsEnded() { - _artist.Status = ArtistStatusType.Ended; + _artist.Metadata.Value.Status = ArtistStatusType.Ended; } private void GivenArtistLastRefreshedMonthsAgo() diff --git a/src/NzbDrone.Core.Test/MusicTests/TitleMatchingTests/TitleMatchingFixture.cs b/src/NzbDrone.Core.Test/MusicTests/TitleMatchingTests/TitleMatchingFixture.cs index 0594fdcf2..fba631707 100644 --- a/src/NzbDrone.Core.Test/MusicTests/TitleMatchingTests/TitleMatchingFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/TitleMatchingTests/TitleMatchingFixture.cs @@ -174,7 +174,5 @@ namespace NzbDrone.Core.Test.MusicTests.TitleMatchingTests track.Should().BeNull(); } - - } } diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleFixture.cs index 3350085e1..8d1389c18 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleFixture.cs @@ -16,6 +16,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests { private Artist _artist; private Album _album; + private AlbumRelease _release; private Track _track; private TrackFile _trackFile; private NamingConfig _namingConfig; @@ -33,9 +34,15 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests .With(s => s.Title = "Hail to the King") .Build(); + _release = Builder + .CreateNew() + .With(s => s.Media = new List { new Medium { Number = 1 } }) + .Build(); + _track = Builder.CreateNew() .With(e => e.Title = "Doing Time") .With(e => e.AbsoluteTrackNumber = 3) + .With(e => e.AlbumRelease = _release) .Build(); _trackFile = new TrackFile { Quality = new QualityModel(Quality.MP3_256), ReleaseGroup = "LidarrTest" }; @@ -86,6 +93,8 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests .With(e => e.Title = "Surrender Benson") .TheNext(1) .With(e => e.Title = "Imprisoned Lives") + .All() + .With(e => e.AlbumRelease = _release) .Build() .ToList(); diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs index 8b2cf3243..3d24aba3e 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs @@ -18,6 +18,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests { private Artist _artist; private Album _album; + private AlbumRelease _release; private Track _track1; private TrackFile _trackFile; private NamingConfig _namingConfig; @@ -37,6 +38,11 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests .With(s => s.Disambiguation = "The Best Album") .Build(); + _release = Builder + .CreateNew() + .With(s => s.Media = new List { new Medium { Number = 1 } }) + .Build(); + _namingConfig = NamingConfig.Default; _namingConfig.RenameTracks = true; @@ -48,6 +54,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests _track1 = Builder.CreateNew() .With(e => e.Title = "City Sushi") .With(e => e.AbsoluteTrackNumber = 6) + .With(e => e.AlbumRelease = _release) .Build(); _trackFile = new TrackFile { Quality = new QualityModel(Quality.MP3_256), ReleaseGroup = "LidarrTest" }; @@ -351,6 +358,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests var track = Builder.CreateNew() .With(e => e.Title = "Part 1") .With(e => e.AbsoluteTrackNumber = 6) + .With(e => e.AlbumRelease = _release) .Build(); Subject.BuildTrackFileName(new List { track }, new Artist { Name = "In The Woods." }, new Album { Title = "30 Rock" }, _trackFile) @@ -365,6 +373,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests var track = Builder.CreateNew() .With(e => e.Title = "Part 1") .With(e => e.AbsoluteTrackNumber = 6) + .With(e => e.AlbumRelease = _release) .Build(); Subject.BuildTrackFileName(new List { track }, new Artist { Name = "In The Woods..." }, new Album { Title = "30 Rock" }, _trackFile) diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/TitleTheFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/TitleTheFixture.cs index 8621f9f40..8b54bd11a 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/TitleTheFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/TitleTheFixture.cs @@ -16,6 +16,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests { private Artist _artist; private Album _album; + private AlbumRelease _release; private Track _track; private TrackFile _trackFile; private NamingConfig _namingConfig; @@ -32,10 +33,16 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests .CreateNew() .With(s => s.Title = "Anthology") .Build(); + + _release = Builder + .CreateNew() + .With(s => s.Media = new List { new Medium { Number = 1 } }) + .Build(); _track = Builder.CreateNew() .With(e => e.Title = "City Sushi") .With(e => e.AbsoluteTrackNumber = 6) + .With(e => e.AlbumRelease = _release) .Build(); _trackFile = new TrackFile { Quality = new QualityModel(Quality.MP3_320), ReleaseGroup = "LidarrTest" }; diff --git a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetLocalTrackFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetLocalTrackFixture.cs index 055c9d022..48c9202d8 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetLocalTrackFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetLocalTrackFixture.cs @@ -33,18 +33,19 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests _fakeAlbum = Builder .CreateNew() .With(e => e.ArtistId = _fakeArtist.Id) - .With(e => e.Releases = new List + .With(e => e.AlbumReleases = new List { new AlbumRelease { - Id = "5ecd552b-e54b-4c37-b62c-9d6234834bad" + ForeignReleaseId = "5ecd552b-e54b-4c37-b62c-9d6234834bad", + Monitored = true } }) .Build(); _fakeTrack = Builder .CreateNew() - .With(e => e.ArtistId = _fakeArtist.Id) + .With(e => e.Artist = _fakeArtist) .With(e => e.AlbumId = _fakeAlbum.Id) .With(e => e.Album = null) .Build(); @@ -61,7 +62,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests .Returns(_fakeAlbum); Mocker.GetMock() - .Setup(s => s.FindAlbumByRelease(_fakeAlbum.Releases.First().Id)) + .Setup(s => s.FindAlbumByRelease(_fakeAlbum.AlbumReleases.Value.First().ForeignReleaseId)) .Returns(_fakeAlbum); Mocker.GetMock() @@ -78,7 +79,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests private void HasReleaseMbIdNoTitle() { _parsedTrackInfo.AlbumTitle = ""; - _parsedTrackInfo.ReleaseMBId = _fakeAlbum.Releases.First().Id; + _parsedTrackInfo.ReleaseMBId = _fakeAlbum.AlbumReleases.Value.First().ForeignReleaseId; } private void HasNoReleaseIdOrTitle() diff --git a/src/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/GetAudioFilesFixture.cs b/src/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/GetAudioFilesFixture.cs index 4247b5b4d..ac6eaa253 100644 --- a/src/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/GetAudioFilesFixture.cs +++ b/src/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/GetAudioFilesFixture.cs @@ -1,21 +1,20 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using FizzWare.NBuilder; using FluentAssertions; using Moq; using NUnit.Framework; using NzbDrone.Common.Disk; +using NzbDrone.Test.Common; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Music; namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests { - public class GetAudioFilesFixture : CoreTest { private string[] _fileNames; + private readonly string path = @"C:\Test\".AsOsAgnostic(); [SetUp] public void Setup() @@ -48,8 +47,6 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests [Test] public void should_check_all_directories() { - var path = @"C:\Test\"; - Subject.GetAudioFiles(path); Mocker.GetMock().Verify(s => s.GetFiles(path, SearchOption.AllDirectories), Times.Once()); @@ -59,8 +56,6 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests [Test] public void should_check_all_directories_when_allDirectories_is_true() { - var path = @"C:\Test\"; - Subject.GetAudioFiles(path, true); Mocker.GetMock().Verify(s => s.GetFiles(path, SearchOption.AllDirectories), Times.Once()); @@ -70,8 +65,6 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests [Test] public void should_check_top_level_directory_only_when_allDirectories_is_false() { - var path = @"C:\Test\"; - Subject.GetAudioFiles(path, false); Mocker.GetMock().Verify(s => s.GetFiles(path, SearchOption.AllDirectories), Times.Never()); @@ -81,7 +74,6 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests [Test] public void should_return_audio_files_only() { - var path = @"C:\Test\"; GivenFiles(GetFiles(path)); Subject.GetAudioFiles(path).Should().HaveCount(4); @@ -96,7 +88,6 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests [TestCase(".unwanted")] public void should_filter_certain_sub_folders(string subFolder) { - var path = @"C:\Test\"; var files = GetFiles(path).ToList(); var specialFiles = GetFiles(path, subFolder).ToList(); var allFiles = files.Concat(specialFiles); diff --git a/src/NzbDrone.Core/ArtistStats/ArtistStatisticsRepository.cs b/src/NzbDrone.Core/ArtistStats/ArtistStatisticsRepository.cs index 298ee3bdc..84a13448e 100644 --- a/src/NzbDrone.Core/ArtistStats/ArtistStatisticsRepository.cs +++ b/src/NzbDrone.Core/ArtistStats/ArtistStatisticsRepository.cs @@ -28,7 +28,7 @@ namespace NzbDrone.Core.ArtistStats var sb = new StringBuilder(); sb.AppendLine(GetSelectClause()); - sb.AppendLine(GetTrackFilesJoin()); + sb.AppendLine("AND Albums.ReleaseDate < @currentDate"); sb.AppendLine(GetGroupByClause()); var queryText = sb.ToString(); @@ -44,8 +44,8 @@ namespace NzbDrone.Core.ArtistStats var sb = new StringBuilder(); sb.AppendLine(GetSelectClause()); - sb.AppendLine(GetTrackFilesJoin()); - sb.AppendLine("WHERE Tracks.ArtistId = @artistId"); + sb.AppendLine("AND Artists.Id = @artistId"); + sb.AppendLine("AND Albums.ReleaseDate < @currentDate"); sb.AppendLine(GetGroupByClause()); var queryText = sb.ToString(); @@ -54,28 +54,25 @@ namespace NzbDrone.Core.ArtistStats private string GetSelectClause() { - return @"SELECT Tracks.*, SUM(TrackFiles.Size) as SizeOnDisk FROM - (SELECT - Tracks.ArtistId, - Tracks.AlbumId, - COUNT(*) AS TotalTrackCount, - SUM(CASE WHEN TrackFileId > 0 THEN 1 ELSE 0 END) AS AvailableTrackCount, - SUM(CASE WHEN Monitored = 1 OR TrackFileId > 0 THEN 1 ELSE 0 END) AS TrackCount, - SUM(CASE WHEN TrackFileId > 0 THEN 1 ELSE 0 END) AS TrackFileCount + return @"SELECT + Artists.Id AS ArtistId, + Albums.Id AS AlbumId, + SUM(COALESCE(TrackFiles.Size, 0)) AS SizeOnDisk, + COUNT(Tracks.Id) AS TotalTrackCount, + SUM(CASE WHEN Tracks.TrackFileId > 0 THEN 1 ELSE 0 END) AS AvailableTrackCount, + SUM(CASE WHEN Albums.Monitored = 1 OR Tracks.TrackFileId > 0 THEN 1 ELSE 0 END) AS TrackCount, + SUM(CASE WHEN TrackFiles.Id IS NULL THEN 0 ELSE 1 END) AS TrackFileCount FROM Tracks - GROUP BY Tracks.ArtistId, Tracks.AlbumId) as Tracks"; + JOIN AlbumReleases ON Tracks.AlbumReleaseId = AlbumReleases.Id + JOIN Albums ON AlbumReleases.AlbumId = Albums.Id + JOIN Artists on Albums.ArtistMetadataId = Artists.ArtistMetadataId + LEFT OUTER JOIN TrackFiles ON Tracks.TrackFileId = TrackFiles.Id + WHERE AlbumReleases.Monitored = 1"; } private string GetGroupByClause() { - return "GROUP BY Tracks.ArtistId, Tracks.AlbumId"; - } - - private string GetTrackFilesJoin() - { - return @"LEFT OUTER JOIN TrackFiles - ON TrackFiles.ArtistId = Tracks.ArtistId - AND TrackFiles.AlbumId = Tracks.AlbumId"; + return "GROUP BY Artists.Id, Albums.Id"; } } } diff --git a/src/NzbDrone.Core/Datastore/BasicRepository.cs b/src/NzbDrone.Core/Datastore/BasicRepository.cs index 889416637..ae722d876 100644 --- a/src/NzbDrone.Core/Datastore/BasicRepository.cs +++ b/src/NzbDrone.Core/Datastore/BasicRepository.cs @@ -48,7 +48,7 @@ namespace NzbDrone.Core.Datastore _eventAggregator = eventAggregator; } - protected QueryBuilder Query => DataMapper.Query(); + protected virtual QueryBuilder Query => DataMapper.Query(); protected void Delete(Expression> filter) { @@ -80,7 +80,7 @@ namespace NzbDrone.Core.Datastore public IEnumerable Get(IEnumerable ids) { var idList = ids.ToList(); - var query = string.Format("Id IN ({0})", string.Join(",", idList)); + var query = string.Format("[t0].[Id] IN ({0})", string.Join(",", idList)); var result = Query.Where(query).ToList(); if (result.Count != idList.Count()) diff --git a/src/NzbDrone.Core/Datastore/Extensions/RelationshipExtensions.cs b/src/NzbDrone.Core/Datastore/Extensions/RelationshipExtensions.cs index 7c5669c99..4fdc76a2e 100644 --- a/src/NzbDrone.Core/Datastore/Extensions/RelationshipExtensions.cs +++ b/src/NzbDrone.Core/Datastore/Extensions/RelationshipExtensions.cs @@ -25,15 +25,7 @@ namespace NzbDrone.Core.Datastore.Extensions public static RelationshipBuilder Relationship(this ColumnMapBuilder mapBuilder) { - return mapBuilder.Relationships.AutoMapComplexTypeProperties(); - } - - public static RelationshipBuilder HasMany(this RelationshipBuilder relationshipBuilder, Expression>> portalExpression, Func childIdSelector) - where TParent : ModelBase - where TChild : ModelBase - { - return relationshipBuilder.For(portalExpression.GetMemberName()) - .LazyLoad((db, parent) => db.Query().Where(c => c.Id == childIdSelector(parent)).ToList()); + return mapBuilder.Relationships.MapProperties(); } private static string GetMemberName(this Expression> member) diff --git a/src/NzbDrone.Core/Datastore/Migration/023_add_release_groups_etc.cs b/src/NzbDrone.Core/Datastore/Migration/023_add_release_groups_etc.cs new file mode 100644 index 000000000..2e5d64571 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/023_add_release_groups_etc.cs @@ -0,0 +1,247 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; +using NzbDrone.Common.Serializer; +using System.Collections.Generic; +using NzbDrone.Core.Music; +using System.Data; +using System; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(023)] + public class add_release_groups_etc : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + // ARTISTS TABLE + + Create.TableForModel("ArtistMetadata") + .WithColumn("ForeignArtistId").AsString().Unique() + .WithColumn("Name").AsString() + .WithColumn("Overview").AsString().Nullable() + .WithColumn("Disambiguation").AsString().Nullable() + .WithColumn("Type").AsString().Nullable() + .WithColumn("Status").AsInt32() + .WithColumn("Images").AsString() + .WithColumn("Links").AsString().Nullable() + .WithColumn("Genres").AsString().Nullable() + .WithColumn("Ratings").AsString().Nullable() + .WithColumn("Members").AsString().Nullable(); + + // we want to preserve the artist ID. Shove all the metadata into the metadata table. + Execute.Sql(@"INSERT INTO ArtistMetadata (ForeignArtistId, Name, Overview, Disambiguation, Type, Status, Images, Links, Genres, Ratings, Members) + SELECT ForeignArtistId, Name, Overview, Disambiguation, ArtistType, Status, Images, Links, Genres, Ratings, Members + FROM Artists"); + + // Add an ArtistMetadataId column to Artists + Alter.Table("Artists").AddColumn("ArtistMetadataId").AsInt32().WithDefaultValue(0); + + // Update artistmetadataId + Execute.Sql(@"UPDATE Artists + SET ArtistMetadataId = (SELECT ArtistMetadata.Id + FROM ArtistMetadata + WHERE ArtistMetadata.ForeignArtistId = Artists.ForeignArtistId)"); + + // ALBUM RELEASES TABLE - Do this before we mess with the Albums table + + Create.TableForModel("AlbumReleases") + .WithColumn("ForeignReleaseId").AsString().Unique() + .WithColumn("AlbumId").AsInt32().Indexed() + .WithColumn("Title").AsString() + .WithColumn("Status").AsString() + .WithColumn("Duration").AsInt32().WithDefaultValue(0) + .WithColumn("Label").AsString().Nullable() + .WithColumn("Disambiguation").AsString().Nullable() + .WithColumn("Country").AsString().Nullable() + .WithColumn("ReleaseDate").AsDateTime().Nullable() + .WithColumn("Media").AsString().Nullable() + .WithColumn("TrackCount").AsInt32().Nullable() + .WithColumn("Monitored").AsBoolean(); + + Execute.WithConnection(PopulateReleases); + + // ALBUMS TABLE + + // Add in the extra columns and update artist metadata id + Alter.Table("Albums").AddColumn("ArtistMetadataId").AsInt32().WithDefaultValue(0); + Alter.Table("Albums").AddColumn("AnyReleaseOk").AsBoolean().WithDefaultValue(true); + Alter.Table("Albums").AddColumn("Links").AsString().Nullable(); + + // Set metadata ID + Execute.Sql(@"UPDATE Albums + SET ArtistMetadataId = (SELECT ArtistMetadata.Id + FROM ArtistMetadata + JOIN Artists ON ArtistMetadata.Id = Artists.ArtistMetadataId + WHERE Albums.ArtistId = Artists.Id)"); + + // TRACKS TABLE + Alter.Table("Tracks").AddColumn("ForeignRecordingId").AsString().WithDefaultValue("0"); + Alter.Table("Tracks").AddColumn("AlbumReleaseId").AsInt32().WithDefaultValue(0); + Alter.Table("Tracks").AddColumn("ArtistMetadataId").AsInt32().WithDefaultValue(0); + + // Set track release to the only release we've bothered populating + Execute.Sql(@"UPDATE Tracks + SET AlbumReleaseId = (SELECT AlbumReleases.Id + FROM AlbumReleases + JOIN Albums ON AlbumReleases.AlbumId = Albums.Id + WHERE Albums.Id = Tracks.AlbumId)"); + + // CLEAR OUT OLD COLUMNS + + // Remove the columns in Artists now in ArtistMetadata + Delete.Column("ForeignArtistId") + .Column("Name") + .Column("Overview") + .Column("Disambiguation") + .Column("ArtistType") + .Column("Status") + .Column("Images") + .Column("Links") + .Column("Genres") + .Column("Ratings") + .Column("Members") + // as well as the ones no longer used + .Column("MBId") + .Column("AMId") + .Column("TADBId") + .Column("DiscogsId") + .Column("NameSlug") + .Column("LastDiskSync") + .Column("DateFormed") + .FromTable("Artists"); + + // Remove old columns from Albums + Delete.Column("ArtistId") + .Column("MBId") + .Column("AMId") + .Column("TADBId") + .Column("DiscogsId") + .Column("TitleSlug") + .Column("Label") + .Column("SortTitle") + .Column("Tags") + .Column("Duration") + .Column("Media") + .Column("Releases") + .Column("CurrentRelease") + .Column("LastDiskSync") + .FromTable("Albums"); + + // Remove old columns from Tracks + Delete.Column("ArtistId") + .Column("AlbumId") + .Column("Compilation") + .Column("DiscNumber") + .Column("Monitored") + .FromTable("Tracks"); + + // Remove old columns from TrackFiles + Delete.Column("ArtistId").FromTable("TrackFiles"); + + // Add indices + Create.Index().OnTable("Artists").OnColumn("ArtistMetadataId").Ascending(); + Create.Index().OnTable("Artists").OnColumn("Monitored").Ascending(); + Create.Index().OnTable("Albums").OnColumn("ArtistMetadataId").Ascending(); + Create.Index().OnTable("Tracks").OnColumn("ArtistMetadataId").Ascending(); + Create.Index().OnTable("Tracks").OnColumn("AlbumReleaseId").Ascending(); + Create.Index().OnTable("Tracks").OnColumn("ForeignRecordingId").Ascending(); + + // Force a metadata refresh + Update.Table("Artists").Set(new { LastInfoSync = new System.DateTime(2018, 1, 1, 0, 0, 1)}).AllRows(); + Update.Table("Albums").Set(new { LastInfoSync = new System.DateTime(2018, 1, 1, 0, 0, 1)}).AllRows(); + Update.Table("ScheduledTasks") + .Set(new { LastExecution = new System.DateTime(2018, 1, 1, 0, 0, 1)}) + .Where(new { TypeName = "NzbDrone.Core.Music.Commands.RefreshArtistCommand" }); + + } + + private void PopulateReleases(IDbConnection conn, IDbTransaction tran) + { + var releases = ReadReleasesFromAlbums(conn, tran); + WriteReleasesToReleases(releases,conn, tran); + } + + public class LegacyAlbumRelease : IEmbeddedDocument + { + public string Id { get; set; } + public string Title { get; set; } + public DateTime? ReleaseDate { get; set; } + public int TrackCount { get; set; } + public int MediaCount { get; set; } + public string Disambiguation { get; set; } + public List Country { get; set; } + public string Format { get; set; } + public List Label { get; set; } + } + + private List ReadReleasesFromAlbums(IDbConnection conn, IDbTransaction tran) + { + + // need to get all the old albums + var releases = new List(); + + using (var getReleasesCmd = conn.CreateCommand()) + { + getReleasesCmd.Transaction = tran; + getReleasesCmd.CommandText = @"SELECT Id, CurrentRelease FROM Albums"; + + using (var releaseReader = getReleasesCmd.ExecuteReader()) + { + while (releaseReader.Read()) + { + int rgId = releaseReader.GetInt32(0); + var albumRelease = Json.Deserialize(releaseReader.GetString(1)); + var media = new List(); + for (var i = 1; i <= albumRelease.MediaCount; i++) + { + media.Add(new Medium { Number = i, Name = "", Format = albumRelease.Format } ); + } + + releases.Add(new AlbumRelease { + AlbumId = rgId, + ForeignReleaseId = albumRelease.Id, + Title = albumRelease.Title, + Status = "", + Duration = 0, + Label = albumRelease.Label, + Disambiguation = albumRelease.Disambiguation, + Country = albumRelease.Country, + Media=media, + TrackCount = albumRelease.TrackCount, + Monitored = true + }); + } + } + } + + return releases; + } + + private void WriteReleasesToReleases(List releases, IDbConnection conn, IDbTransaction tran) + { + foreach (var release in releases) + { + using (var writeReleaseCmd = conn.CreateCommand()) + { + writeReleaseCmd.Transaction = tran; + writeReleaseCmd.CommandText = + "INSERT INTO AlbumReleases (AlbumId, ForeignReleaseId, Title, Status, Duration, Label, Disambiguation, Country, Media, TrackCount, Monitored) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + writeReleaseCmd.AddParameter(release.AlbumId); + writeReleaseCmd.AddParameter(release.ForeignReleaseId); + writeReleaseCmd.AddParameter(release.Title); + writeReleaseCmd.AddParameter(release.Status); + writeReleaseCmd.AddParameter(release.Duration); + writeReleaseCmd.AddParameter(release.Label.ToJson()); + writeReleaseCmd.AddParameter(release.Disambiguation); + writeReleaseCmd.AddParameter(release.Country.ToJson()); + writeReleaseCmd.AddParameter(release.Media.ToJson()); + writeReleaseCmd.AddParameter(release.TrackCount); + writeReleaseCmd.AddParameter(release.Monitored); + + writeReleaseCmd.ExecuteNonQuery(); + } + } + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index 361c62967..9947ce3bc 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Marr.Data; using Marr.Data.Mapping; using NzbDrone.Common.Reflection; @@ -87,28 +88,71 @@ namespace NzbDrone.Core.Datastore Mapper.Entity().RegisterModel("Artists") .Ignore(s => s.RootFolderPath) + .Ignore(s => s.Name) + .Ignore(s => s.ForeignArtistId) .Relationship() + .HasOne(a => a.Metadata, a => a.ArtistMetadataId) .HasOne(a => a.Profile, a => a.ProfileId) .HasOne(s => s.LanguageProfile, s => s.LanguageProfileId) - .HasOne(s => s.MetadataProfile, s => s.MetadataProfileId); + .HasOne(s => s.MetadataProfile, s => s.MetadataProfileId) + .For(a => a.Albums) + .LazyLoad(condition: a => a.Id > 0, query: (db, a) => db.Query().Where(rg => rg.ArtistMetadataId == a.Id).ToList()); - Mapper.Entity().RegisterModel("Albums"); + Mapper.Entity().RegisterModel("ArtistMetadata"); - Mapper.Entity().RegisterModel("TrackFiles") - .Ignore(f => f.Path) - .Relationships.AutoMapICollectionOrComplexProperties() - .For("Tracks") - .LazyLoad(condition: parent => parent.Id > 0, - query: (db, parent) => db.Query().Where(c => c.TrackFileId == parent.Id).ToList()) // TODO: Figure what the hell to do here - .HasOne(file => file.Artist, file => file.ArtistId); + Mapper.Entity().RegisterModel("Albums") + .Ignore(r => r.ArtistId) + .Relationship() + .HasOne(r => r.ArtistMetadata, r => r.ArtistMetadataId) + .For(rg => rg.AlbumReleases) + .LazyLoad(condition: rg => rg.Id > 0, query: (db, rg) => db.Query().Where(r => r.AlbumId == rg.Id).ToList()) + .For(rg => rg.Artist) + .LazyLoad(condition: rg => rg.ArtistMetadataId > 0, query: (db, rg) => db.Query().Where(a => a.ArtistMetadataId == rg.ArtistMetadataId).SingleOrDefault()); + + + Mapper.Entity().RegisterModel("AlbumReleases") + .Relationship() + .HasOne(r => r.Album, r => r.AlbumId) + .For(r => r.Tracks) + .LazyLoad(condition: r => r.Id > 0, query: (db, r) => db.Query().Where(t => t.AlbumReleaseId == r.Id).ToList()); Mapper.Entity().RegisterModel("Tracks") - //.Ignore(e => e.SeriesTitle) - .Ignore(e => e.Album) - .Ignore(e => e.HasFile) - .Relationship() - // TODO: Need to implement ArtistId to Artist.Id here - .HasOne(track => track.TrackFile, track => track.TrackFileId); // TODO: Check lazy load for artists + .Ignore(t => t.HasFile) + .Ignore(t => t.AlbumId) + .Ignore(t => t.ArtistId) + .Ignore(t => t.Album) + .Relationship() + .HasOne(track => track.AlbumRelease, track => track.AlbumReleaseId) + .HasOne(track => track.ArtistMetadata, track => track.ArtistMetadataId) + .HasOne(track => track.TrackFile, track => track.TrackFileId) + .For(t => t.Artist) + .LazyLoad(condition: t => t.AlbumReleaseId > 0, query: (db, t) => db.Query().QueryText(string.Format( + "/* LazyLoading Artist for Track */\n" + + "SELECT Artists.* " + + "FROM Artists " + + "JOIN Albums ON Albums.ArtistMetadataId = Artists.ArtistMetadataId " + + "JOIN AlbumReleases ON AlbumReleases.AlbumId = Albums.Id " + + "WHERE AlbumReleases.Id = {0}", + t.AlbumReleaseId)).SingleOrDefault()); + + Mapper.Entity().RegisterModel("TrackFiles") + .Ignore(f => f.Path) + .Relationship() + .HasOne(f => f.Album, f => f.AlbumId) + .For("Tracks") + .LazyLoad(condition: parent => parent.Id > 0, + query: (db, parent) => db.Query().Where(c => c.TrackFileId == parent.Id).ToList()) + .For(t => t.Artist) + .LazyLoad(condition: f => f.Id > 0, query: (db, f) => db.Query().QueryText(string.Format( + "/* LazyLoading Artist for TrackFile */\n" + + "SELECT Artists.* " + + "FROM Artists " + + "JOIN Albums 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 TrackFiles.Id = {0}", + f.Id)).SingleOrDefault()); Mapper.Entity().RegisterModel("QualityDefinitions") .Ignore(d => d.GroupName) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs index bcdde336d..294ae477b 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs @@ -35,19 +35,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications } var qualityDefinition = _qualityDefinitionService.Get(quality); - var albumsDuration = subject.Albums.Sum(album => album.Duration) / 1000; if (qualityDefinition.MinSize.HasValue) { var minSize = qualityDefinition.MinSize.Value.Kilobits(); + var minReleaseDuration = subject.Albums.Select(a => a.AlbumReleases.Value.Where(r => r.Monitored || a.AnyReleaseOk).Select(r => r.Duration).Min()).Sum() / 1000; - //Multiply minSize by Album.Duration - minSize = minSize * albumsDuration; + //Multiply minSize by smallest release duration + minSize = minSize * minReleaseDuration; //If the parsed size is smaller than minSize we don't want it if (subject.Release.Size < minSize) { - var runtimeMessage = $"{albumsDuration}sec"; + var runtimeMessage = $"{minReleaseDuration}sec"; _logger.Debug("Item: {0}, Size: {1} is smaller than minimum allowed size ({2} bytes for {3}), rejecting.", subject, subject.Release.Size, minSize, runtimeMessage); return Decision.Reject("{0} is smaller than minimum allowed {1}", subject.Release.Size.SizeSuffix(), minSize.SizeSuffix()); @@ -60,14 +60,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications else { var maxSize = qualityDefinition.MaxSize.Value.Kilobits(); - + var maxReleaseDuration = subject.Albums.Select(a => a.AlbumReleases.Value.Where(r => r.Monitored || a.AnyReleaseOk).Select(r => r.Duration).Max()).Sum() / 1000; + //Multiply maxSize by Album.Duration - maxSize = maxSize * albumsDuration; + maxSize = maxSize * maxReleaseDuration; //If the parsed size is greater than maxSize we don't want it if (subject.Release.Size > maxSize) { - var runtimeMessage = $"{albumsDuration}sec"; + var runtimeMessage = $"{maxReleaseDuration}sec"; _logger.Debug("Item: {0}, Size: {1} is greater than maximum allowed size ({2} bytes for {3}), rejecting.", subject, subject.Release.Size, maxSize, runtimeMessage); return Decision.Reject("{0} is larger than maximum allowed {1}", subject.Release.Size.SizeSuffix(), maxSize.SizeSuffix()); diff --git a/src/NzbDrone.Core/Extras/Files/ExtraFileService.cs b/src/NzbDrone.Core/Extras/Files/ExtraFileService.cs index 1a74a1545..d5a61ee6e 100644 --- a/src/NzbDrone.Core/Extras/Files/ExtraFileService.cs +++ b/src/NzbDrone.Core/Extras/Files/ExtraFileService.cs @@ -112,7 +112,7 @@ namespace NzbDrone.Core.Extras.Files else { - var artist = _artistService.GetArtist(message.TrackFile.ArtistId); + var artist = trackFile.Artist.Value; foreach (var extra in _repository.GetFilesByTrackFile(trackFile.Id)) { diff --git a/src/NzbDrone.Core/Extras/Metadata/Consumers/Roksbox/RoksboxMetadata.cs b/src/NzbDrone.Core/Extras/Metadata/Consumers/Roksbox/RoksboxMetadata.cs index 419a14808..2366d1528 100644 --- a/src/NzbDrone.Core/Extras/Metadata/Consumers/Roksbox/RoksboxMetadata.cs +++ b/src/NzbDrone.Core/Extras/Metadata/Consumers/Roksbox/RoksboxMetadata.cs @@ -158,7 +158,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox return new List(); } - var image = artist.Images.SingleOrDefault(c => c.CoverType == MediaCoverTypes.Poster) ?? artist.Images.FirstOrDefault(); + var image = artist.Metadata.Value.Images.SingleOrDefault(c => c.CoverType == MediaCoverTypes.Poster) ?? artist.Metadata.Value.Images.FirstOrDefault(); if (image == null) { _logger.Trace("Failed to find suitable Artist image for artist {0}.", artist.Name); diff --git a/src/NzbDrone.Core/Extras/Metadata/Consumers/Wdtv/WdtvMetadata.cs b/src/NzbDrone.Core/Extras/Metadata/Consumers/Wdtv/WdtvMetadata.cs index fa2bcc602..baa0ddf83 100644 --- a/src/NzbDrone.Core/Extras/Metadata/Consumers/Wdtv/WdtvMetadata.cs +++ b/src/NzbDrone.Core/Extras/Metadata/Consumers/Wdtv/WdtvMetadata.cs @@ -111,10 +111,10 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Wdtv var details = new XElement("details"); details.Add(new XElement("id", artist.Id)); details.Add(new XElement("title", string.Format("{0} - {1} - {2}", artist.Name, track.TrackNumber, track.Title))); - details.Add(new XElement("artist_name", artist.Name)); + details.Add(new XElement("artist_name", artist.Metadata.Value.Name)); details.Add(new XElement("track_name", track.Title)); details.Add(new XElement("track_number", track.AbsoluteTrackNumber.ToString("00"))); - details.Add(new XElement("member", string.Join(" / ", artist.Members.ConvertAll(c => c.Name + " - " + c.Instrument)))); + details.Add(new XElement("member", string.Join(" / ", artist.Metadata.Value.Members.ConvertAll(c => c.Name + " - " + c.Instrument)))); //Todo: get guest stars, writer and director diff --git a/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs b/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs index 9cb23dba2..ed91bc47c 100644 --- a/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs +++ b/src/NzbDrone.Core/Extras/Metadata/Consumers/Xbmc/XbmcMetadata.cs @@ -113,19 +113,19 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc artistElement.Add(new XElement("title", artist.Name)); - if (artist.Ratings != null && artist.Ratings.Votes > 0) + if (artist.Metadata.Value.Ratings != null && artist.Metadata.Value.Ratings.Votes > 0) { - artistElement.Add(new XElement("rating", artist.Ratings.Value)); + artistElement.Add(new XElement("rating", artist.Metadata.Value.Ratings.Value)); } - artistElement.Add(new XElement("musicbrainzartistid", artist.ForeignArtistId)); - artistElement.Add(new XElement("biography", artist.Overview)); - artistElement.Add(new XElement("outline", artist.Overview)); + artistElement.Add(new XElement("musicbrainzartistid", artist.Metadata.Value.ForeignArtistId)); + artistElement.Add(new XElement("biography", artist.Metadata.Value.Overview)); + artistElement.Add(new XElement("outline", artist.Metadata.Value.Overview)); var doc = new XDocument(artistElement); doc.Save(xw); - _logger.Debug("Saving artist.nfo for {0}", artist.Name); + _logger.Debug("Saving artist.nfo for {0}", artist.Metadata.Value.Name); return new MetadataFileResult("artist.nfo", doc.ToString()); } @@ -156,7 +156,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc } albumElement.Add(new XElement("musicbrainzalbumid", album.ForeignAlbumId)); - albumElement.Add(new XElement("artistdesc", artist.Overview)); + albumElement.Add(new XElement("artistdesc", artist.Metadata.Value.Overview)); albumElement.Add(new XElement("releasedate", album.ReleaseDate.Value.ToShortDateString())); var doc = new XDocument(albumElement); @@ -203,7 +203,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc private IEnumerable ProcessArtistImages(Artist artist) { - foreach (var image in artist.Images) + foreach (var image in artist.Metadata.Value.Images) { var source = _mediaCoverService.GetCoverPath(artist.Id, image.CoverType); var destination = image.CoverType.ToString().ToLowerInvariant() + Path.GetExtension(image.Url); diff --git a/src/NzbDrone.Core/History/HistoryService.cs b/src/NzbDrone.Core/History/HistoryService.cs index 5f0bc45cb..14ff4a38d 100644 --- a/src/NzbDrone.Core/History/HistoryService.cs +++ b/src/NzbDrone.Core/History/HistoryService.cs @@ -202,8 +202,8 @@ namespace NzbDrone.Core.History Date = DateTime.UtcNow, Quality = message.TrackInfo.Quality, SourceTitle = message.ImportedTrack.SceneName ?? Path.GetFileNameWithoutExtension(message.TrackInfo.Path), - ArtistId = message.ImportedTrack.ArtistId, - AlbumId = message.ImportedTrack.AlbumId, + ArtistId = message.TrackInfo.Artist.Id, + AlbumId = message.TrackInfo.Album.Id, TrackId = track.Id, DownloadId = downloadId, Language = message.TrackInfo.Language diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedAlbums.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedAlbums.cs index f9f2d3e87..ce4893ff4 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedAlbums.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedAlbums.cs @@ -19,7 +19,7 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers WHERE Id IN ( SELECT Albums.Id FROM Albums LEFT OUTER JOIN Artists - ON Albums.ArtistId = Artists.Id + ON Albums.ArtistMetadataId = Artists.ArtistMetadataId WHERE Artists.Id IS NULL)"); } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedArtistMetadata.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedArtistMetadata.cs new file mode 100644 index 000000000..e667e3456 --- /dev/null +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedArtistMetadata.cs @@ -0,0 +1,26 @@ +using NzbDrone.Core.Datastore; + +namespace NzbDrone.Core.Housekeeping.Housekeepers +{ + public class CleanupOrphanedArtistMetadata : IHousekeepingTask + { + private readonly IMainDatabase _database; + + public CleanupOrphanedArtistMetadata(IMainDatabase database) + { + _database = database; + } + + public void Clean() + { + var mapper = _database.GetDataMapper(); + + mapper.ExecuteNonQuery(@"DELETE FROM ArtistMetadata + WHERE Id IN ( + SELECT ArtistMetadata.Id FROM ArtistMetadata + LEFT OUTER JOIN Albums ON Albums.ArtistMetadataId = ArtistMetadata.Id + LEFT OUTER JOIN Tracks ON Tracks.ArtistMetadataId = ArtistMetadata.Id + WHERE Albums.Id IS NULL AND Tracks.Id IS NULL)"); + } + } +} diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedReleases.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedReleases.cs new file mode 100644 index 000000000..5c7c3d1fe --- /dev/null +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedReleases.cs @@ -0,0 +1,26 @@ +using NzbDrone.Core.Datastore; + +namespace NzbDrone.Core.Housekeeping.Housekeepers +{ + public class CleanupOrphanedReleases : IHousekeepingTask + { + private readonly IMainDatabase _database; + + public CleanupOrphanedReleases(IMainDatabase database) + { + _database = database; + } + + public void Clean() + { + var mapper = _database.GetDataMapper(); + + mapper.ExecuteNonQuery(@"DELETE FROM AlbumReleases + WHERE Id IN ( + SELECT AlbumReleases.Id FROM AlbumReleases + LEFT OUTER JOIN Albums + ON AlbumReleases.AlbumId = Albums.Id + WHERE Albums.Id IS NULL)"); + } + } +} diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedTrackFiles.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedTrackFiles.cs index f9cc9594e..4d24b8269 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedTrackFiles.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedTrackFiles.cs @@ -15,12 +15,31 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { var mapper = _database.GetDataMapper(); + // Delete where track no longer exists mapper.ExecuteNonQuery(@"DELETE FROM TrackFiles WHERE Id IN ( SELECT TrackFiles.Id FROM TrackFiles LEFT OUTER JOIN Tracks ON TrackFiles.Id = Tracks.TrackFileId WHERE Tracks.Id IS NULL)"); + + // Delete trackfiles associated with releases that are not currently selected + mapper.ExecuteNonQuery(@"DELETE FROM TrackFiles + WHERE Id IN ( + SELECT TrackFiles.Id FROM TrackFiles + JOIN Tracks ON TrackFiles.Id = Tracks.TrackFileId + JOIN AlbumReleases ON Tracks.AlbumReleaseId = AlbumReleases.Id + JOIN Albums ON AlbumReleases.AlbumId = Albums.Id + WHERE AlbumReleases.Monitored = 0)"); + + // Unlink Tracks where the Trackfiles entry no longer exists + mapper.ExecuteNonQuery(@"UPDATE Tracks + SET TrackFileId = 0 + WHERE Id IN ( + SELECT Tracks.Id FROM Tracks + LEFT OUTER JOIN TrackFiles + ON Tracks.TrackFileId = TrackFiles.Id + WHERE TrackFiles.Id IS NULL)"); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedTracks.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedTracks.cs index dbdac6daf..94b8e22df 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedTracks.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedTracks.cs @@ -18,9 +18,9 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers mapper.ExecuteNonQuery(@"DELETE FROM Tracks WHERE Id IN ( SELECT Tracks.Id FROM Tracks - LEFT OUTER JOIN Albums - ON Tracks.AlbumId = Albums.Id - WHERE Albums.Id IS NULL)"); + LEFT OUTER JOIN AlbumReleases + ON Tracks.AlbumReleaseId = AlbumReleases.Id + WHERE AlbumReleases.Id IS NULL)"); } } } diff --git a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs index fa2234be8..ebe230759 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs @@ -96,8 +96,8 @@ namespace NzbDrone.Core.ImportLists report.AlbumMusicBrainzId = mappedAlbum.ForeignAlbumId; report.Album = mappedAlbum.Title; - report.Artist = mappedAlbum.Artist?.Name; - report.ArtistMusicBrainzId = mappedAlbum?.Artist?.ForeignArtistId; + report.Artist = mappedAlbum.ArtistMetadata?.Value?.Name; + report.ArtistMusicBrainzId = mappedAlbum?.ArtistMetadata?.Value?.ForeignArtistId; } @@ -106,20 +106,22 @@ namespace NzbDrone.Core.ImportLists { var mappedArtist = _artistSearchService.SearchForNewArtist(report.Artist) .FirstOrDefault(); - report.ArtistMusicBrainzId = mappedArtist?.ForeignArtistId; - report.Artist = mappedArtist?.Name; + report.ArtistMusicBrainzId = mappedArtist?.Metadata.Value?.ForeignArtistId; + report.Artist = mappedArtist?.Metadata.Value?.Name; } // Check to see if artist in DB var existingArtist = _artistService.FindById(report.ArtistMusicBrainzId); // Append Artist if not already in DB or already on add list - if (existingArtist == null && artistsToAdd.All(s => s.ForeignArtistId != report.ArtistMusicBrainzId)) + if (existingArtist == null && artistsToAdd.All(s => s.Metadata.Value.ForeignArtistId != report.ArtistMusicBrainzId)) { artistsToAdd.Add(new Artist { - ForeignArtistId = report.ArtistMusicBrainzId, - Name = report.Artist, + Metadata = new ArtistMetadata { + ForeignArtistId = report.ArtistMusicBrainzId, + Name = report.Artist + }, Monitored = importList.ShouldMonitor, RootFolderPath = importList.RootFolderPath, ProfileId = importList.ProfileId, @@ -132,9 +134,9 @@ namespace NzbDrone.Core.ImportLists } // Add Album so we know what to monitor - if (report.AlbumMusicBrainzId.IsNotNullOrWhiteSpace() && artistsToAdd.Any(s => s.ForeignArtistId == report.ArtistMusicBrainzId) && importList.ShouldMonitor) + if (report.AlbumMusicBrainzId.IsNotNullOrWhiteSpace() && artistsToAdd.Any(s => s.Metadata.Value.ForeignArtistId == report.ArtistMusicBrainzId) && importList.ShouldMonitor) { - artistsToAdd.Find(s => s.ForeignArtistId == report.ArtistMusicBrainzId).AddOptions.AlbumsToMonitor.Add(report.AlbumMusicBrainzId); + artistsToAdd.Find(s => s.Metadata.Value.ForeignArtistId == report.ArtistMusicBrainzId).AddOptions.AlbumsToMonitor.Add(report.AlbumMusicBrainzId); } } diff --git a/src/NzbDrone.Core/IndexerSearch/AlbumSearchService.cs b/src/NzbDrone.Core/IndexerSearch/AlbumSearchService.cs index 796120a2e..dd9052a3f 100644 --- a/src/NzbDrone.Core/IndexerSearch/AlbumSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/AlbumSearchService.cs @@ -86,7 +86,7 @@ namespace NzbDrone.Core.IndexerSearch SortKey = "Id" }; - pagingSpec.FilterExpressions.Add(v => v.Monitored == true && v.Artist.Monitored == true); + pagingSpec.FilterExpressions.Add(v => v.Monitored == true && v.Artist.Value.Monitored == true); albums = _albumService.AlbumsWithoutFiles(pagingSpec).Records.Where(e => e.ArtistId.Equals(artistId)).ToList(); @@ -102,7 +102,7 @@ namespace NzbDrone.Core.IndexerSearch SortKey = "Id" }; - pagingSpec.FilterExpressions.Add(v => v.Monitored == true && v.Artist.Monitored == true); + pagingSpec.FilterExpressions.Add(v => v.Monitored == true && v.Artist.Value.Monitored == true); albums = _albumService.AlbumsWithoutFiles(pagingSpec).Records.ToList(); @@ -118,20 +118,9 @@ namespace NzbDrone.Core.IndexerSearch { Expression> filterExpression; - if (message.ArtistId.HasValue) - { - filterExpression = v => - v.ArtistId == message.ArtistId.Value && - v.Monitored == true && - v.Artist.Monitored == true; - } - - else - { - filterExpression = v => - v.Monitored == true && - v.Artist.Monitored == true; - } + filterExpression = v => + v.Monitored == true && + v.Artist.Value.Monitored == true; var pagingSpec = new PagingSpec { diff --git a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs index 44aabd3e5..e64505893 100644 --- a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs +++ b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs @@ -105,7 +105,7 @@ namespace NzbDrone.Core.MediaCover private void EnsureCovers(Artist artist) { - foreach (var cover in artist.Images) + foreach (var cover in artist.Metadata.Value.Images) { var fileName = GetCoverPath(artist.Id, cover.CoverType); var alreadyExists = false; diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs index b66fa527b..3f5c70a3d 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs @@ -29,7 +29,17 @@ namespace NzbDrone.Core.MediaFiles public List GetFilesByArtist(int artistId) { - return Query.Where(c => c.ArtistId == artistId).ToList(); + string query = string.Format("SELECT TrackFiles.* " + + "FROM Artists " + + "JOIN Albums 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 Artists.Id == {0} " + + "AND AlbumReleases.Monitored = 1", + artistId); + + return Query.QueryText(query).ToList(); } public List GetFilesByAlbum(int albumId) @@ -39,9 +49,20 @@ namespace NzbDrone.Core.MediaFiles public List GetFilesWithRelativePath(int artistId, string relativePath) { - return Query.Where(c => c.ArtistId == artistId) - .AndWhere(c => c.RelativePath == relativePath) - .ToList(); + var mapper = DataMapper; + mapper.AddParameter("artistId", artistId); + mapper.AddParameter("relativePath", relativePath); + string query = "SELECT TrackFiles.* " + + "FROM Artists " + + "JOIN Albums 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 Artists.Id == @artistId " + + "AND AlbumReleases.Monitored = 1 " + + "AND TrackFiles.RelativePath == @relativePath"; + + return mapper.Query(query); } } diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs index 81b67eae3..d18fbecb4 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs @@ -28,7 +28,7 @@ namespace NzbDrone.Core.MediaFiles } - public class MediaFileService : IMediaFileService, IHandleAsync, IHandleAsync + public class MediaFileService : IMediaFileService, IHandleAsync { private readonly IEventAggregator _eventAggregator; private readonly IMediaFileRepository _mediaFileRepository; @@ -104,12 +104,6 @@ namespace NzbDrone.Core.MediaFiles return _mediaFileRepository.GetFilesWithRelativePath(artistId, relativePath); } - public void HandleAsync(ArtistDeletedEvent message) - { - var files = GetFilesByArtist(message.Artist.Id); - _mediaFileRepository.DeleteMany(files); - } - public void HandleAsync(AlbumDeletedEvent message) { var files = GetFilesByAlbum(message.Album.Id); diff --git a/src/NzbDrone.Core/MediaFiles/TrackFile.cs b/src/NzbDrone.Core/MediaFiles/TrackFile.cs index 7a861a932..e57146d55 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackFile.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackFile.cs @@ -13,10 +13,7 @@ namespace NzbDrone.Core.MediaFiles { public class TrackFile : ModelBase { - //public string ForeignTrackId { get; set; } - //public string ForeignArtistId { get; set; } - public int AlbumId { get; set; } - public int ArtistId { get; set; } + // these are model properties public string RelativePath { get; set; } public string Path { get; set; } public long Size { get; set; } @@ -25,10 +22,16 @@ namespace NzbDrone.Core.MediaFiles public string ReleaseGroup { get; set; } public QualityModel Quality { get; set; } public MediaInfoModel MediaInfo { get; set; } - //public LazyLoaded> Episodes { get; set; } - public LazyLoaded Artist { get; set; } - public LazyLoaded> Tracks { get; set; } public Language Language { get; set; } + public int AlbumId { get; set; } + + // These are queried from the database + public LazyLoaded> Tracks { get; set; } + public LazyLoaded Artist { get; set; } + public LazyLoaded Album { get; set; } + + // these are ignored by the database but retained/populated for compatibility + public int ArtistId { get { return Artist.Value?.Id ?? 0; } } public override string ToString() { diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs index a6612bb4d..692877d48 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs @@ -9,6 +9,8 @@ using NzbDrone.Core.Extras; using NzbDrone.Core.Languages; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Music; +using NzbDrone.Core.Music.Events; using NzbDrone.Core.Qualities; namespace NzbDrone.Core.MediaFiles.TrackImport @@ -24,20 +26,23 @@ namespace NzbDrone.Core.MediaFiles.TrackImport private readonly IMediaFileService _mediaFileService; private readonly IExtraService _extraService; private readonly IDiskProvider _diskProvider; + private readonly IReleaseService _releaseService; private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; public ImportApprovedTracks(IUpgradeMediaFiles trackFileUpgrader, - IMediaFileService mediaFileService, - IExtraService extraService, - IDiskProvider diskProvider, - IEventAggregator eventAggregator, - Logger logger) + IMediaFileService mediaFileService, + IExtraService extraService, + IDiskProvider diskProvider, + IReleaseService releaseService, + IEventAggregator eventAggregator, + Logger logger) { _trackFileUpgrader = trackFileUpgrader; _mediaFileService = mediaFileService; _extraService = extraService; _diskProvider = diskProvider; + _releaseService = releaseService; _eventAggregator = eventAggregator; _logger = logger; } @@ -56,6 +61,22 @@ namespace NzbDrone.Core.MediaFiles.TrackImport var allImportedTrackFiles = new List(); var allOldTrackFiles = new List(); + var albumDecisions = decisions.Where(e => e.LocalTrack.Album != null) + .GroupBy(e => e.LocalTrack.Album.Id).ToList(); + + foreach (var albumDecision in albumDecisions) + { + // set the correct release to be monitored after doing the import + var album = albumDecision.First().LocalTrack.Album; + var release = albumDecision.First().LocalTrack.Release; + _logger.Debug("Updating release to {0} [{1} tracks]", release, release.TrackCount); + _releaseService.SetMonitored(release); + + // Publish album edited event. + // Deliberatly don't put in the old album since we don't want to trigger an ArtistScan. + _eventAggregator.PublishEvent(new AlbumEditedEvent(album, album)); + } + foreach (var importDecision in qualifiedImports.OrderBy(e => e.LocalTrack.Tracks.Select(track => track.AbsoluteTrackNumber).MinOrDefault()) .ThenByDescending(e => e.LocalTrack.Size)) { @@ -74,17 +95,18 @@ namespace NzbDrone.Core.MediaFiles.TrackImport continue; } - var trackFile = new TrackFile(); - trackFile.DateAdded = DateTime.UtcNow; - trackFile.ArtistId = localTrack.Artist.Id; - trackFile.Path = localTrack.Path.CleanFilePath(); - trackFile.Size = _diskProvider.GetFileSize(localTrack.Path); - trackFile.Quality = localTrack.Quality; - trackFile.MediaInfo = localTrack.MediaInfo; - trackFile.AlbumId = localTrack.Album.Id; - trackFile.ReleaseGroup = localTrack.ParsedTrackInfo.ReleaseGroup; - trackFile.Tracks = localTrack.Tracks; - trackFile.Language = localTrack.Language; + + var trackFile = new TrackFile { + Path = localTrack.Path.CleanFilePath(), + Size = _diskProvider.GetFileSize(localTrack.Path), + DateAdded = DateTime.UtcNow, + ReleaseGroup = localTrack.ParsedTrackInfo.ReleaseGroup, + Quality = localTrack.Quality, + MediaInfo = localTrack.MediaInfo, + Language = localTrack.Language, + AlbumId = localTrack.Album.Id, + Tracks = localTrack.Tracks + }; bool copyOnly; switch (importMode) @@ -177,6 +199,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport allOldTrackFiles.Where(s => s.AlbumId == album.Id).ToList(), newDownload, downloadClientItem)); } + } //Adding all the rejected decisions diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecision.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecision.cs index be28f2541..f43b6fdce 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecision.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecision.cs @@ -11,7 +11,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport public class ImportDecision { public LocalTrack LocalTrack { get; private set; } - public IEnumerable Rejections { get; private set; } + public IList Rejections { get; private set; } public bool Approved => Rejections.Empty(); @@ -20,5 +20,10 @@ namespace NzbDrone.Core.MediaFiles.TrackImport LocalTrack = localTrack; Rejections = rejections.ToList(); } + + public void Reject(Rejection rejection) + { + Rejections.Add(rejection); + } } } diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecisionMaker.cs index 6cca5f5ab..d644ecb35 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecisionMaker.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecisionMaker.cs @@ -12,6 +12,10 @@ using NzbDrone.Core.Qualities; using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Core.Music; using NzbDrone.Core.Languages; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Music.Events; +using System.Diagnostics; +using NzbDrone.Common.EnsureThat; namespace NzbDrone.Core.MediaFiles.TrackImport { @@ -19,7 +23,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport { List GetImportDecisions(List musicFiles, Artist artist); List GetImportDecisions(List musicFiles, Artist artist, ParsedTrackInfo folderInfo); - List GetImportDecisions(List musicFiles, Artist artist, ParsedTrackInfo folderInfo, bool filterExistingFiles); + List GetImportDecisions(List musicFiles, Artist artist, ParsedTrackInfo folderInfo, bool filterExistingFiles, bool timidReleaseSwitching); ImportDecision GetImportDecision(string musicFile, Artist artist, Album album); } @@ -28,6 +32,9 @@ namespace NzbDrone.Core.MediaFiles.TrackImport private readonly IEnumerable _specifications; private readonly IParsingService _parsingService; private readonly IMediaFileService _mediaFileService; + private readonly IAlbumService _albumService; + private readonly IReleaseService _releaseService; + private readonly IEventAggregator _eventAggregator; private readonly IDiskProvider _diskProvider; private readonly IVideoFileInfoReader _videoFileInfoReader; private readonly Logger _logger; @@ -35,6 +42,9 @@ namespace NzbDrone.Core.MediaFiles.TrackImport public ImportDecisionMaker(IEnumerable specifications, IParsingService parsingService, IMediaFileService mediaFileService, + IAlbumService albumService, + IReleaseService releaseService, + IEventAggregator eventAggregator, IDiskProvider diskProvider, IVideoFileInfoReader videoFileInfoReader, Logger logger) @@ -42,6 +52,9 @@ namespace NzbDrone.Core.MediaFiles.TrackImport _specifications = specifications; _parsingService = parsingService; _mediaFileService = mediaFileService; + _albumService = albumService; + _releaseService = releaseService; + _eventAggregator = eventAggregator; _diskProvider = diskProvider; _videoFileInfoReader = videoFileInfoReader; _logger = logger; @@ -54,16 +67,47 @@ namespace NzbDrone.Core.MediaFiles.TrackImport public List GetImportDecisions(List musicFiles, Artist artist, ParsedTrackInfo folderInfo) { - return GetImportDecisions(musicFiles, artist, folderInfo, false); + return GetImportDecisions(musicFiles, artist, folderInfo, false, false); } - public List GetImportDecisions(List musicFiles, Artist artist, ParsedTrackInfo folderInfo, bool filterExistingFiles) + private bool MatchesCurrentRelease(ImportDecision decision) + { + return decision.Approved || decision.Rejections.Select(x => x.Reason).Contains("Has the same filesize as existing file"); + } + + public List GetImportDecisions(List musicFiles, Artist artist, ParsedTrackInfo folderInfo, bool filterExistingFiles, bool timidReleaseSwitching) { var files = filterExistingFiles ? _mediaFileService.FilterExistingFiles(musicFiles.ToList(), artist) : musicFiles.ToList(); _logger.Debug("Analyzing {0}/{1} files.", files.Count, musicFiles.Count); var shouldUseFolderName = ShouldUseFolderName(musicFiles, artist, folderInfo); + + // We have to do this once to match against albums + var decisions = GetImportDecisionsForCurrentRelease(files, artist, folderInfo, shouldUseFolderName); + + // Now we have matched the files against albums, we can group by album and check for the best release + var albums = decisions.Where(x => x.LocalTrack.Album != null) + .Select(x => x.LocalTrack.Album) + .GroupBy(x => x.Id) + .Select(x => x.First()) + .ToList(); + + var revisedDecisions = decisions.Where(x => x.LocalTrack.Album == null).ToList(); + + foreach (var album in albums) + { + var albumDecisions = decisions.Where(x => x.LocalTrack.Album != null && x.LocalTrack.Album.Id == album.Id).ToList(); + revisedDecisions.AddRange(GetImportDecisions(albumDecisions, artist, album, folderInfo, shouldUseFolderName, timidReleaseSwitching)); + } + + Ensure.That(decisions.Count == revisedDecisions.Count).IsTrue(); + + return revisedDecisions; + } + + private List GetImportDecisionsForCurrentRelease(List files, Artist artist, ParsedTrackInfo folderInfo, bool shouldUseFolderName) + { var decisions = new List(); foreach (var file in files) @@ -78,6 +122,90 @@ namespace NzbDrone.Core.MediaFiles.TrackImport { return GetDecision(file, artist, album, null, false); } + + public List GetImportDecisions(List decisions, Artist artist, Album album, ParsedTrackInfo folderInfo, bool shouldUseFolderName, bool timidReleaseSwitching) + { + _logger.Debug("Importing {0}", album); + var maxTrackCount = album.AlbumReleases.Value.Where(x => x.Monitored).Select(x => x.TrackCount).Max(); + var haveExistingFiles = _mediaFileService.GetFilesByAlbum(album.Id).Any(); + var releaseSwitchingAllowed = !(haveExistingFiles && timidReleaseSwitching); + + if (album.AnyReleaseOk && releaseSwitchingAllowed) + { + if (decisions.Any(x => !MatchesCurrentRelease(x)) || decisions.Count != maxTrackCount) + { + _logger.Debug("Importing {0}: {1}/{2} files approved for {3} track release", + album, + decisions.Count(x => MatchesCurrentRelease(x)), + decisions.Count, + maxTrackCount); + return GetImportDecisionsForBestRelease(decisions, artist, album, folderInfo, shouldUseFolderName); + } + else + { + _logger.Debug("Importing {0}: All files approved and all tracks have a file", album); + return decisions; + } + } + else + { + _logger.Debug("Importing {0}: {1}/{2} files approved for {3} track release. Release switching not allowed.", + album, + decisions.Count(x => MatchesCurrentRelease(x)), + decisions.Count, + maxTrackCount); + return decisions; + } + } + + private List GetImportDecisionsForBestRelease(List decisions, Artist artist, Album album, ParsedTrackInfo folderInfo, bool shouldUseFolderName) + { + var files = decisions.Select(x => x.LocalTrack.Path).ToList(); + + // At the moment we assume only one release can be monitored at a time + var originalRelease = album.AlbumReleases.Value.Where(x => x.Monitored).Single(); + var candidateReleases = album.AlbumReleases.Value.Where(x => x.TrackCount >= files.Count && x.Id != originalRelease.Id).ToList(); + var bestRelease = originalRelease; + var bestMatchCount = decisions.Count(x => MatchesCurrentRelease(x)); + var bestDecisions = decisions; + + foreach (var release in candidateReleases) + { + _logger.Debug("Trying Release {0} [{1} tracks]", release, release.TrackCount); + album.AlbumReleases = _releaseService.SetMonitored(release); + var newDecisions = GetImportDecisionsForCurrentRelease(files, artist, folderInfo, shouldUseFolderName); + + _logger.Debug("Importing {0}: {1}/{2} files approved for {3} track release {4}", + album, + newDecisions.Count(x => MatchesCurrentRelease(x)), + newDecisions.Count, + release.TrackCount, + release); + + // We want the release that matches the most tracks. If there's a tie, + // we want the release with the fewest entries (i.e. fewest missing) + var currentMatchCount = newDecisions.Count(x => MatchesCurrentRelease(x)); + if (currentMatchCount > bestMatchCount + || (currentMatchCount == bestMatchCount && release.TrackCount < bestRelease.TrackCount)) + { + bestMatchCount = currentMatchCount; + bestRelease = release; + bestDecisions = newDecisions; + + if (currentMatchCount == release.TrackCount && newDecisions.All(x => MatchesCurrentRelease(x))) + { + break; + } + } + } + + _logger.Debug("{0} Best release: {1}", album, bestRelease); + + // reinstate the original release in case the import isn't run (manual import) + album.AlbumReleases = _releaseService.SetMonitored(originalRelease); + + return bestDecisions; + } private ImportDecision GetDecision(string file, Artist artist, Album album, ParsedTrackInfo folderInfo, bool shouldUseFolderName) { diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportFile.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportFile.cs index ccddfc71e..42693f4f6 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportFile.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportFile.cs @@ -12,6 +12,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual public string FolderName { get; set; } public int ArtistId { get; set; } public int AlbumId { get; set; } + public int AlbumReleaseId { get; set; } public List TrackIds { get; set; } public QualityModel Quality { get; set; } public Language Language { get; set; } diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportItem.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportItem.cs index 7a171e71e..5e8fc38eb 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportItem.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportItem.cs @@ -16,6 +16,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual public long Size { get; set; } public Artist Artist { get; set; } public Album Album { get; set; } + public AlbumRelease Release { get; set; } public List Tracks { get; set; } public QualityModel Quality { get; set; } public Language Language { get; set; } diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportService.cs index ceb4d4960..47125fa41 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportService.cs @@ -35,6 +35,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual private readonly IMakeImportDecision _importDecisionMaker; private readonly IArtistService _artistService; private readonly IAlbumService _albumService; + private readonly IReleaseService _releaseService; private readonly ITrackService _trackService; private readonly IVideoFileInfoReader _videoFileInfoReader; private readonly IImportApprovedTracks _importApprovedTracks; @@ -50,6 +51,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual IMakeImportDecision importDecisionMaker, IArtistService artistService, IAlbumService albumService, + IReleaseService releaseService, ITrackService trackService, IVideoFileInfoReader videoFileInfoReader, IImportApprovedTracks importApprovedTracks, @@ -65,6 +67,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual _importDecisionMaker = importDecisionMaker; _artistService = artistService; _albumService = albumService; + _releaseService = releaseService; _trackService = trackService; _videoFileInfoReader = videoFileInfoReader; _importApprovedTracks = importApprovedTracks; @@ -138,7 +141,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual var folderInfo = Parser.Parser.ParseMusicTitle(directoryInfo.Name); var artistFiles = _diskScanService.GetAudioFiles(folder).ToList(); - var decisions = _importDecisionMaker.GetImportDecisions(artistFiles, artist, folderInfo, filterExistingFiles); + var decisions = _importDecisionMaker.GetImportDecisions(artistFiles, artist, folderInfo, filterExistingFiles, true); return decisions.Select(decision => MapItem(decision, folder, downloadId)).ToList(); } @@ -155,6 +158,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual if (decision.LocalTrack.Album != null) { item.Album = decision.LocalTrack.Album; + item.Release = decision.LocalTrack.Release; } if (decision.LocalTrack.Tracks.Any()) @@ -240,6 +244,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual if (decision.LocalTrack.Album != null) { item.Album = decision.LocalTrack.Album; + item.Release = decision.LocalTrack.Release; } if (decision.LocalTrack.Tracks.Any()) @@ -269,6 +274,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual var file = message.Files[i]; var artist = _artistService.GetArtist(file.ArtistId); var album = _albumService.GetAlbum(file.AlbumId); + var release = _releaseService.GetRelease(file.AlbumReleaseId); var tracks = _trackService.GetTracks(file.TrackIds); var parsedTrackInfo = Parser.Parser.ParseMusicPath(file.Path) ?? new ParsedTrackInfo(); var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path); @@ -285,6 +291,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual Language = file.Language, Artist = artist, Album = album, + Release = release, Size = 0 }; diff --git a/src/NzbDrone.Core/MetadataSource/IProvideAlbumInfo.cs b/src/NzbDrone.Core/MetadataSource/IProvideAlbumInfo.cs index 865c4096b..0aeb10200 100644 --- a/src/NzbDrone.Core/MetadataSource/IProvideAlbumInfo.cs +++ b/src/NzbDrone.Core/MetadataSource/IProvideAlbumInfo.cs @@ -6,6 +6,6 @@ namespace NzbDrone.Core.MetadataSource { public interface IProvideAlbumInfo { - Tuple> GetAlbumInfo(string lidarrId, string releaseId); + Tuple> GetAlbumInfo(string id); } } diff --git a/src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs b/src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs index a83b87b6d..bf49d2797 100644 --- a/src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs +++ b/src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs @@ -7,6 +7,6 @@ namespace NzbDrone.Core.MetadataSource { public interface IProvideArtistInfo { - Tuple> GetArtistInfo(string lidarrId, int metadataProfileId); + Artist GetArtistInfo(string lidarrId, int metadataProfileId); } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumArtistResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumArtistResource.cs deleted file mode 100644 index 11e25dc52..000000000 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumArtistResource.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace NzbDrone.Core.MetadataSource.SkyHook.Resource -{ - public class AlbumArtistResource - { - public string Id { get; set; } - public string Name { get; set; } - } -} diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs index 5f80f29d8..72e1c871e 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs @@ -7,31 +7,18 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource { public class AlbumResource { - public AlbumResource() - { - Media = new List(); - Releases = new List(); - } - - public List Artists { get; set; } // Will always be length of 1 unless a compilation - public string Url { get; set; } // Link to the endpoint api to give full info for this object - public string Id { get; set; } // This is a unique Album ID. Needed for all future API calls - public DateTime ReleaseDate { get; set; } - public List Images { get; set; } - public string Title { get; set; } + public string ArtistId { get; set; } + public List Artists { get; set; } public string Disambiguation { get; set; } public string Overview { get; set; } - public List Genres { get; set; } - public List Labels { get; set; } - public string Type { get; set; } - public List SecondaryTypes { get; set; } - public List Media { get; set; } - public List Tracks { get; set; } - public List Releases { get; set; } + public string Id { get; set; } + public List Images { get; set; } + public List Links { get; set; } public RatingResource Rating { get; set; } - public string SelectedRelease { get; set; } - public AlbumArtistResource Artist { get; set; } + public DateTime ReleaseDate { get; set; } + public List Releases { get; set; } + public List SecondaryTypes { get; set; } + public string Title { get; set; } + public string Type { get; set; } } - - } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/MemberResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/MemberResource.cs deleted file mode 100644 index 1f79b6221..000000000 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/MemberResource.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace NzbDrone.Core.MetadataSource.SkyHook.Resource -{ - public class MemberResource - { - public string Name { get; set; } - public string Instrument { get; set; } - public string Image { get; set; } - } -} diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ReleaseResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ReleaseResource.cs index bc9de7839..519848bab 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ReleaseResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ReleaseResource.cs @@ -5,14 +5,15 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource { public class ReleaseResource { - public string Id { get; set; } - public DateTime ReleaseDate { get; set; } - public int MediaCount { get; set; } - public int TrackCount { get; set; } public string Disambiguation { get; set; } - public List Label {get; set;} public List Country { get; set; } + public DateTime ReleaseDate { get; set; } + public string Id { get; set; } + public List Label { get; set; } + public List Media { get; set; } public string Title { get; set; } - public string Format { get; set; } + public string Status { get; set; } + public int TrackCount { get; set; } + public List Tracks { get; set; } } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TimeOfDayResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TimeOfDayResource.cs deleted file mode 100644 index 242f92a7c..000000000 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TimeOfDayResource.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace NzbDrone.Core.MetadataSource.SkyHook.Resource -{ - public class TimeOfDayResource - { - public int Hours { get; set; } - public int Minutes { get; set; } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackResource.cs index d2c7af36e..e79c637b9 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackResource.cs @@ -1,8 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - namespace NzbDrone.Core.MetadataSource.SkyHook.Resource { public class TrackResource @@ -12,16 +7,15 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource } - public int DiscNumber { get; set; } + public string ArtistId { get; set; } public int DurationMs { get; set; } - public string Href { get; set; } public string Id { get; set; } + public string RecordingId { get; set; } public string TrackName { get; set; } public string TrackNumber { get; set; } public int TrackPosition { get; set; } public bool Explicit { get; set; } public int MediumNumber { get; set; } - public List Artists { get; set; } } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index 9dcc437e2..7242e5cd2 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -45,7 +45,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook _logger = logger; } - public Tuple> GetArtistInfo(string foreignArtistId, int metadataProfileId) + public Artist GetArtistInfo(string foreignArtistId, int metadataProfileId) { _logger.Debug("Getting Artist with LidarrAPI.MetadataID of {0}", foreignArtistId); @@ -87,13 +87,16 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } } - var albums = httpResponse.Resource.Albums.Select(MapAlbum); - var artist = MapArtist(httpResponse.Resource); + var artist = new Artist(); + artist.Metadata = MapArtistMetadata(httpResponse.Resource); + artist.CleanName = Parser.Parser.CleanArtistName(artist.Metadata.Value.Name); + artist.SortName = Parser.Parser.NormalizeTitle(artist.Metadata.Value.Name); + artist.Albums = httpResponse.Resource.Albums.Select(x => MapAlbum(x, null)).ToList(); - return new Tuple>(artist, albums.ToList()); + return artist; } - public Tuple> GetAlbumInfo(string foreignAlbumId, string releaseId) + public Tuple> GetAlbumInfo(string foreignAlbumId) { _logger.Debug("Getting Album with LidarrAPI.MetadataID of {0}", foreignAlbumId); @@ -101,7 +104,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook var httpRequest = _customerRequestBuilder.Create() .SetSegment("route", "album/" + foreignAlbumId) - .AddQueryParam("release", releaseId ?? string.Empty) .Build(); httpRequest.AllowAutoRedirect = true; @@ -109,7 +111,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook var httpResponse = _httpClient.Get(httpRequest); - if (httpResponse.HasHttpError) { if (httpResponse.StatusCode == HttpStatusCode.NotFound) @@ -126,10 +127,11 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } } - var tracks = httpResponse.Resource.Tracks.Select(MapTrack); - var album = MapAlbum(httpResponse.Resource); + var artists = httpResponse.Resource.Artists.Select(MapArtistMetadata).ToList(); + var artistDict = artists.ToDictionary(x => x.ForeignArtistId, x => x); + var album = MapAlbum(httpResponse.Resource, artistDict); - return new Tuple>(album, tracks.ToList()); + return new Tuple>(httpResponse.Resource.ArtistId, album, artists); } public List SearchForNewArtist(string title) @@ -161,7 +163,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook var metadataProfile = _metadataProfileService.All().First().Id; //Change this to Use last Used profile? - return new List { GetArtistInfo(searchGuid.ToString(), metadataProfile).Item1 }; + return new List { GetArtistInfo(searchGuid.ToString(), metadataProfile) }; } catch (ArtistNotFoundException) { @@ -183,7 +185,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook var httpResponse = _httpClient.Get>(httpRequest); - return httpResponse.Resource.SelectList(MapSearhResult); + return httpResponse.Resource.SelectList(MapSearchResult); } catch (HttpException) { @@ -221,7 +223,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook if (existingAlbum == null) { - return new List {GetAlbumInfo(searchGuid.ToString(), null).Item1}; + return new List { GetAlbumInfo(searchGuid.ToString()).Item2 }; } existingAlbum.Artist = _artistService.GetArtist(existingAlbum.ArtistId); @@ -247,7 +249,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook var httpResponse = _httpClient.Get>(httpRequest); - return httpResponse.Resource.SelectList(MapSearhResult); + return httpResponse.Resource.SelectList(MapSearchResult); } catch (HttpException) { @@ -260,16 +262,21 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } } - private Artist MapSearhResult(ArtistResource resource) + private Artist MapSearchResult(ArtistResource resource) { - var artist = _artistService.FindById(resource.Id) ?? MapArtist(resource); + var artist = _artistService.FindById(resource.Id); + if (artist == null) + { + artist = new Artist(); + artist.Metadata = MapArtistMetadata(resource); + } return artist; } - private Album MapSearhResult(AlbumResource resource) + private Album MapSearchResult(AlbumResource resource) { - var album = _albumService.FindById(resource.Id) ?? MapAlbum(resource); + var album = _albumService.FindById(resource.Id) ?? MapAlbum(resource, null); if (album.Artist == null) { @@ -279,43 +286,60 @@ namespace NzbDrone.Core.MetadataSource.SkyHook return album; } - private static Album MapAlbum(AlbumResource resource) + private static Album MapAlbum(AlbumResource resource, Dictionary artistDict) { Album album = new Album(); + album.ForeignAlbumId = resource.Id; album.Title = resource.Title; + album.Overview = resource.Overview; album.Disambiguation = resource.Disambiguation; - album.ForeignAlbumId = resource.Id; album.ReleaseDate = resource.ReleaseDate; - album.CleanTitle = Parser.Parser.CleanArtistName(album.Title); - album.Ratings = MapRatings(resource.Rating); - album.AlbumType = resource.Type; if (resource.Images != null) { album.Images = resource.Images.Select(MapImage).ToList(); } - album.Label = resource.Labels; - album.Media = resource.Media.Select(MapMedium).ToList(); + album.AlbumType = resource.Type; album.SecondaryTypes = resource.SecondaryTypes.Select(MapSecondaryTypes).ToList(); + album.Ratings = MapRatings(resource.Rating); + album.Links = resource.Links?.Select(MapLink).ToList(); + album.CleanTitle = Parser.Parser.CleanArtistName(album.Title); if (resource.Releases != null) { - album.Releases = resource.Releases.Select(MapAlbumRelease).ToList(); - album.CurrentRelease = album.Releases.FirstOrDefault(s => s.Id == resource.SelectedRelease); + album.AlbumReleases = resource.Releases.Select(x => MapRelease(x, artistDict)).ToList(); + album.AlbumReleases.Value.OrderByDescending(x => x.TrackCount).First().Monitored = true; } - if (resource.Artist != null) + album.AnyReleaseOk = true; + + return album; + } + + private static AlbumRelease MapRelease(ReleaseResource resource, Dictionary artistDict) + { + AlbumRelease release = new AlbumRelease(); + release.ForeignReleaseId = resource.Id; + release.Title = resource.Title; + release.Status = resource.Status; + release.Label = resource.Label; + release.Disambiguation = resource.Disambiguation; + release.Country = resource.Country; + release.ReleaseDate = resource.ReleaseDate; + release.TrackCount = resource.TrackCount; + release.Tracks = resource.Tracks.Select(x => MapTrack(x, artistDict)).ToList(); + release.Media = resource.Media.Select(MapMedium).ToList(); + if (!release.Media.Any()) { - album.Artist = new Artist + foreach(int n in release.Tracks.Value.Select(x => x.MediumNumber).Distinct()) { - ForeignArtistId = resource.Artist.Id, - Name = resource.Artist.Name - }; + release.Media.Add(new Medium { Name = "Unknown", Number = n, Format = "Unknown" }); + } } - + release.Duration = release.Tracks.Value.Sum(x => x.Duration); - return album; + return release; } private static Medium MapMedium(MediumResource resource) @@ -330,30 +354,14 @@ namespace NzbDrone.Core.MetadataSource.SkyHook return medium; } - private static AlbumRelease MapAlbumRelease(ReleaseResource resource) - { - AlbumRelease albumRelease = new AlbumRelease - { - Id = resource.Id, - Title = resource.Title, - ReleaseDate = resource.ReleaseDate, - TrackCount = resource.TrackCount, - Format = resource.Format, - MediaCount = resource.MediaCount, - Country = resource.Country, - Disambiguation = resource.Disambiguation, - Label = resource.Label - }; - - return albumRelease; - } - - private static Track MapTrack(TrackResource resource) + private static Track MapTrack(TrackResource resource, Dictionary artistDict) { Track track = new Track { + ArtistMetadata = artistDict[resource.ArtistId], Title = resource.TrackName, ForeignTrackId = resource.Id, + ForeignRecordingId = resource.RecordingId, TrackNumber = resource.TrackNumber, AbsoluteTrackNumber = resource.TrackPosition, Duration = resource.DurationMs, @@ -363,45 +371,24 @@ namespace NzbDrone.Core.MetadataSource.SkyHook return track; } - private static Artist MapArtist(ArtistResource resource) + private static ArtistMetadata MapArtistMetadata(ArtistResource resource) { - Artist artist = new Artist(); + ArtistMetadata artist = new ArtistMetadata(); artist.Name = resource.ArtistName; artist.ForeignArtistId = resource.Id; artist.Genres = resource.Genres; artist.Overview = resource.Overview; - artist.CleanName = Parser.Parser.CleanArtistName(artist.Name); - artist.SortName = Parser.Parser.NormalizeTitle(artist.Name); artist.Disambiguation = resource.Disambiguation; - artist.ArtistType = resource.Type; - artist.Images = resource.Images.Select(MapImage).ToList(); + artist.Type = resource.Type; artist.Status = MapArtistStatus(resource.Status); artist.Ratings = MapRatings(resource.Rating); - artist.Links = resource.Links.Select(MapLink).ToList(); + artist.Images = resource.Images?.Select(MapImage).ToList(); + artist.Links = resource.Links?.Select(MapLink).ToList(); return artist; } - private static Member MapMembers(MemberResource arg) - { - var newMember = new Member - { - Name = arg.Name, - Instrument = arg.Instrument - }; - - if (arg.Image != null) - { - newMember.Images = new List - { - new MediaCover.MediaCover(MediaCoverTypes.Headshot, arg.Image) - }; - } - - return newMember; - } - private static ArtistStatusType MapArtistStatus(string status) { if (status == null) diff --git a/src/NzbDrone.Core/Music/AddAlbumService.cs b/src/NzbDrone.Core/Music/AddAlbumService.cs index bcd892f28..4d5052ead 100644 --- a/src/NzbDrone.Core/Music/AddAlbumService.cs +++ b/src/NzbDrone.Core/Music/AddAlbumService.cs @@ -21,16 +21,19 @@ namespace NzbDrone.Core.Music { private readonly IAlbumService _albumService; private readonly IProvideAlbumInfo _albumInfo; + private readonly IArtistMetadataRepository _artistMetadataRepository; private readonly IRefreshTrackService _refreshTrackService; private readonly Logger _logger; public AddAlbumService(IAlbumService albumService, - IProvideAlbumInfo albumInfo, - IRefreshTrackService refreshTrackService, - Logger logger) + IProvideAlbumInfo albumInfo, + IArtistMetadataRepository artistMetadataRepository, + IRefreshTrackService refreshTrackService, + Logger logger) { _albumService = albumService; _albumInfo = albumInfo; + _artistMetadataRepository = artistMetadataRepository; _refreshTrackService = refreshTrackService; _logger = logger; } @@ -40,10 +43,11 @@ namespace NzbDrone.Core.Music Ensure.That(newAlbum, () => newAlbum).IsNotNull(); var tuple = AddSkyhookData(newAlbum); - newAlbum = tuple.Item1; - _refreshTrackService.RefreshTrackInfo(newAlbum, tuple.Item2); + newAlbum = tuple.Item2; _logger.ProgressInfo("Adding Album {0}", newAlbum.Title); - _albumService.AddAlbum(newAlbum); + _artistMetadataRepository.UpsertMany(tuple.Item3); + _albumService.AddAlbum(newAlbum, tuple.Item1); + _refreshTrackService.RefreshTrackInfo(newAlbum); return newAlbum; } @@ -56,25 +60,26 @@ namespace NzbDrone.Core.Music foreach (var newAlbum in newAlbums) { var tuple = AddSkyhookData(newAlbum); - var album = tuple.Item1; + var album = tuple.Item2; album.Added = added; album.LastInfoSync = added; - album = _albumService.AddAlbum(album); - _refreshTrackService.RefreshTrackInfo(album,tuple.Item2); _logger.ProgressInfo("Adding Album {0}", newAlbum.Title); + _artistMetadataRepository.UpsertMany(tuple.Item3); + album = _albumService.AddAlbum(album, tuple.Item1); + _refreshTrackService.RefreshTrackInfo(album); albumsToAdd.Add(album); } return albumsToAdd; } - private Tuple> AddSkyhookData(Album newAlbum) + private Tuple> AddSkyhookData(Album newAlbum) { - Tuple> tuple; + Tuple> tuple; try { - tuple = _albumInfo.GetAlbumInfo(newAlbum.ForeignAlbumId, null); + tuple = _albumInfo.GetAlbumInfo(newAlbum.ForeignAlbumId); } catch (AlbumNotFoundException) { @@ -86,10 +91,8 @@ namespace NzbDrone.Core.Music }); } - tuple.Item1.ArtistId = newAlbum.ArtistId; - tuple.Item1.Monitored = newAlbum.Monitored; - tuple.Item1.ProfileId = newAlbum.ProfileId; - tuple.Item1.Duration = tuple.Item2.Sum(track => track.Duration); + tuple.Item2.Monitored = newAlbum.Monitored; + tuple.Item2.ProfileId = newAlbum.ProfileId; return tuple; } diff --git a/src/NzbDrone.Core/Music/AddArtistService.cs b/src/NzbDrone.Core/Music/AddArtistService.cs index afb081c68..0c0ae7e74 100644 --- a/src/NzbDrone.Core/Music/AddArtistService.cs +++ b/src/NzbDrone.Core/Music/AddArtistService.cs @@ -72,7 +72,7 @@ namespace NzbDrone.Core.Music catch (Exception ex) { // Catch Import Errors for now until we get things fixed up - _logger.Debug("Failed to import id: {1} - {2}", s.ForeignArtistId, s.Name); + _logger.Debug("Failed to import id: {0} - {1}", s.Metadata.Value.ForeignArtistId, s.Metadata.Value.Name); _logger.Error(ex, ex.Message); } @@ -83,26 +83,27 @@ namespace NzbDrone.Core.Music private Artist AddSkyhookData(Artist newArtist) { - Tuple> tuple; + Artist artist; try { - tuple = _artistInfo.GetArtistInfo(newArtist.ForeignArtistId, newArtist.MetadataProfileId); + artist = _artistInfo.GetArtistInfo(newArtist.Metadata.Value.ForeignArtistId, newArtist.MetadataProfileId); } catch (ArtistNotFoundException) { - _logger.Error("LidarrId {0} was not found, it may have been removed from Lidarr.", newArtist.ForeignArtistId); + _logger.Error("LidarrId {0} was not found, it may have been removed from Lidarr.", newArtist.Metadata.Value.ForeignArtistId); throw new ValidationException(new List { - new ValidationFailure("SpotifyId", "An artist with this ID was not found", newArtist.ForeignArtistId) + new ValidationFailure("SpotifyId", "An artist with this ID was not found", newArtist.Metadata.Value.ForeignArtistId) }); } - var artist = tuple.Item1; - // If albums were passed in on the new artist use them, otherwise use the albums from Skyhook - newArtist.Albums = newArtist.Albums != null && newArtist.Albums.Any() ? newArtist.Albums : artist.Albums; + if (newArtist.Albums == null || newArtist.Albums.Value == null || !newArtist.Albums.Value.Any()) + { + newArtist.Albums = artist.Albums.Value; + } artist.ApplyChanges(newArtist); @@ -117,8 +118,8 @@ namespace NzbDrone.Core.Music newArtist.Path = Path.Combine(newArtist.RootFolderPath, folderName); } - newArtist.CleanName = newArtist.Name.CleanArtistName(); - newArtist.SortName = ArtistNameNormalizer.Normalize(newArtist.Name, newArtist.ForeignArtistId); + newArtist.CleanName = newArtist.Metadata.Value.Name.CleanArtistName(); + newArtist.SortName = ArtistNameNormalizer.Normalize(newArtist.Metadata.Value.Name, newArtist.Metadata.Value.ForeignArtistId); newArtist.Added = DateTime.UtcNow; var validationResult = _addArtistValidator.Validate(newArtist); diff --git a/src/NzbDrone.Core/Music/Album.cs b/src/NzbDrone.Core/Music/Album.cs index 789ff8173..9379804f5 100644 --- a/src/NzbDrone.Core/Music/Album.cs +++ b/src/NzbDrone.Core/Music/Album.cs @@ -1,7 +1,9 @@ using NzbDrone.Common.Extensions; using NzbDrone.Core.Datastore; using System; +using System.Linq; using System.Collections.Generic; +using Marr.Data; namespace NzbDrone.Core.Music { @@ -11,40 +13,44 @@ namespace NzbDrone.Core.Music { Genres = new List(); Images = new List(); - Media = new List(); - Releases = new List(); - CurrentRelease = new AlbumRelease(); + Links = new List(); Ratings = new Ratings(); + Artist = new Artist(); } public const string RELEASE_DATE_FORMAT = "yyyy-MM-dd"; + // These correspond to columns in the Albums table + // These are metadata entries + public int ArtistMetadataId { get; set; } public string ForeignAlbumId { get; set; } - public int ArtistId { get; set; } public string Title { get; set; } + public string Overview { get; set; } public string Disambiguation { get; set; } - public string CleanTitle { get; set; } public DateTime? ReleaseDate { get; set; } - public List Label { get; set; } - public int ProfileId { get; set; } - public int Duration { get; set; } - public List Tracks { get; set; } - public bool Monitored { get; set; } public List Images { get; set; } + public List Links { get; set; } public List Genres { get; set; } - public List Media { get; set; } - public DateTime? LastInfoSync { get; set; } - public DateTime? LastDiskSync { get; set; } - public DateTime Added { get; set; } public String AlbumType { get; set; } public List SecondaryTypes { get; set; } - //public string ArtworkUrl { get; set; } - //public string Explicitness { get; set; } - public AddArtistOptions AddOptions { get; set; } - public Artist Artist { get; set; } public Ratings Ratings { get; set; } - public List Releases { get; set; } - public AlbumRelease CurrentRelease { get; set; } + + // These are Lidarr generated/config + public string CleanTitle { get; set; } + public int ProfileId { get; set; } + public bool Monitored { get; set; } + public bool AnyReleaseOk { get; set; } + public DateTime? LastInfoSync { get; set; } + public DateTime Added { get; set; } + public AddArtistOptions AddOptions { get; set; } + + // These are dynamically queried from other tables + public LazyLoaded ArtistMetadata { get; set; } + public LazyLoaded> AlbumReleases { get; set; } + public LazyLoaded Artist { get; set; } + + //compatibility properties with old version of Album + public int ArtistId { get { return Artist?.Value?.Id ?? 0; } set { Artist.Value.Id = value; } } public override string ToString() { @@ -53,16 +59,11 @@ namespace NzbDrone.Core.Music public void ApplyChanges(Album otherAlbum) { - ForeignAlbumId = otherAlbum.ForeignAlbumId; - - Tracks = otherAlbum.Tracks; - ProfileId = otherAlbum.ProfileId; AddOptions = otherAlbum.AddOptions; Monitored = otherAlbum.Monitored; - CurrentRelease = otherAlbum.CurrentRelease; - + AnyReleaseOk = otherAlbum.AnyReleaseOk; } } } diff --git a/src/NzbDrone.Core/Music/AlbumEditedService.cs b/src/NzbDrone.Core/Music/AlbumEditedService.cs index 89acbc5de..de4d63f28 100644 --- a/src/NzbDrone.Core/Music/AlbumEditedService.cs +++ b/src/NzbDrone.Core/Music/AlbumEditedService.cs @@ -1,7 +1,9 @@ using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.Music.Commands; +using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.Music.Events; +using System.Linq; +using System.Collections.Generic; namespace NzbDrone.Core.Music { @@ -16,9 +18,15 @@ namespace NzbDrone.Core.Music public void Handle(AlbumEditedEvent message) { - if (message.Album.CurrentRelease.Id != message.OldAlbum.CurrentRelease.Id) + if (message.Album.AlbumReleases.IsLoaded && message.OldAlbum.AlbumReleases.IsLoaded) { - _commandQueueManager.Push(new RefreshAlbumCommand(message.Album.Id)); + var new_monitored = new HashSet(message.Album.AlbumReleases.Value.Where(x => x.Monitored).Select(x => x.Id)); + var old_monitored = new HashSet(message.OldAlbum.AlbumReleases.Value.Where(x => x.Monitored).Select(x => x.Id)); + if (!new_monitored.SetEquals(old_monitored) || + (message.OldAlbum.AnyReleaseOk == false && message.Album.AnyReleaseOk == true)) + { + _commandQueueManager.Push(new RescanArtistCommand(message.Album.ArtistId)); + } } } } diff --git a/src/NzbDrone.Core/Music/AlbumMonitoredService.cs b/src/NzbDrone.Core/Music/AlbumMonitoredService.cs index 1eb0355d9..a5e188947 100644 --- a/src/NzbDrone.Core/Music/AlbumMonitoredService.cs +++ b/src/NzbDrone.Core/Music/AlbumMonitoredService.cs @@ -103,12 +103,6 @@ namespace NzbDrone.Core.Music foreach (var album in albums) { album.Monitored = monitored; - var tracks = _trackService.GetTracksByAlbum(album.Id); - foreach (var track in tracks) - { - track.Monitored = monitored; - } - _trackService.UpdateTracks(tracks); } } } diff --git a/src/NzbDrone.Core/Music/AlbumRelease.cs b/src/NzbDrone.Core/Music/AlbumRelease.cs deleted file mode 100644 index 93545c857..000000000 --- a/src/NzbDrone.Core/Music/AlbumRelease.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using NzbDrone.Core.Datastore; - -namespace NzbDrone.Core.Music -{ - public class AlbumRelease : IEmbeddedDocument - { - public string Id { get; set; } - public string Title { get; set; } - public DateTime? ReleaseDate { get; set; } - public int TrackCount { get; set; } - public int MediaCount { get; set; } - public string Disambiguation { get; set; } - public List Country { get; set; } - public string Format { get; set; } - public List Label { get; set; } - } -} diff --git a/src/NzbDrone.Core/Music/AlbumRepository.cs b/src/NzbDrone.Core/Music/AlbumRepository.cs index ff4162280..fba92541f 100644 --- a/src/NzbDrone.Core/Music/AlbumRepository.cs +++ b/src/NzbDrone.Core/Music/AlbumRepository.cs @@ -14,8 +14,9 @@ namespace NzbDrone.Core.Music public interface IAlbumRepository : IBasicRepository { List GetAlbums(int artistId); + List GetAlbumsByArtistMetadataId(int artistMetadataId); Album FindByName(string cleanTitle); - Album FindByTitle(int artistId, string title); + Album FindByTitle(int artistMetadataId, string title); Album FindByArtistAndName(string artistName, string cleanTitle); Album FindById(string spotifyId); PagingSpec AlbumsWithoutFiles(PagingSpec pagingSpec); @@ -24,7 +25,8 @@ namespace NzbDrone.Core.Music 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 releaseId); + Album FindAlbumByRelease(string albumReleaseId); + Album FindAlbumByTrack(int trackId); List GetArtistAlbumsWithFiles(Artist artist); } @@ -42,7 +44,13 @@ namespace NzbDrone.Core.Music public List GetAlbums(int artistId) { - return Query.Where(s => s.ArtistId == artistId).ToList(); + 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 Album FindById(string foreignAlbumId) @@ -73,15 +81,15 @@ namespace NzbDrone.Core.Music public List AlbumsBetweenDates(DateTime startDate, DateTime endDate, bool includeUnmonitored) { - var query = Query.Join(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistId == s.Id) - .Where(e => e.ReleaseDate >= startDate) - .AndWhere(e => e.ReleaseDate <= endDate); + 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.Monitored); + .AndWhere(e => e.Artist.Value.Monitored); } return query.ToList(); @@ -89,16 +97,16 @@ namespace NzbDrone.Core.Music public List ArtistAlbumsBetweenDates(Artist artist, DateTime startDate, DateTime endDate, bool includeUnmonitored) { - var query = Query.Join(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistId == s.Id) + 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.ArtistId == artist.Id); + .AndWhere(e => e.ArtistMetadataId == artist.ArtistMetadataId); if (!includeUnmonitored) { query.AndWhere(e => e.Monitored) - .AndWhere(e => e.Artist.Monitored); + .AndWhere(e => e.Artist.Value.Monitored); } return query.ToList(); @@ -131,14 +139,23 @@ namespace NzbDrone.Core.Music sortKey = "Albums.releaseDate"; } - string query = string.Format("SELECT Albums.* FROM (SELECT Tracks.AlbumId, COUNT(*) AS TotalTrackCount," + "" + - "SUM(CASE WHEN TrackFileId > 0 THEN 1 ELSE 0 END) AS AvailableTrackCount FROM Tracks GROUP BY Tracks.ArtistId, Tracks.AlbumId) as Tracks" + - " LEFT OUTER JOIN Albums ON Tracks.AlbumId == Albums.Id" + - " LEFT OUTER JOIN Artists ON Albums.ArtistId == Artists.Id" + - " WHERE Tracks.TotalTrackCount != Tracks.AvailableTrackCount AND ({0}) AND {1}" + - " GROUP BY Tracks.AlbumId" + - " ORDER BY {2} {3} LIMIT {4} OFFSET {5}", - monitored, BuildReleaseDateCutoffWhereClause(currentTime), sortKey, pagingSpec.ToSortDirection(), pagingSpec.PageSize, pagingSpec.PagingOffset()); + 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); } @@ -152,13 +169,18 @@ namespace NzbDrone.Core.Music monitored = "(Albums.[Monitored] = 1) AND (Artists.[Monitored] = 1)"; } - string query = string.Format("SELECT Albums.* FROM (SELECT Tracks.AlbumId, COUNT(*) AS TotalTrackCount," + - " SUM(CASE WHEN TrackFileId > 0 THEN 1 ELSE 0 END) AS AvailableTrackCount FROM Tracks GROUP BY Tracks.ArtistId, Tracks.AlbumId) as Tracks" + - " LEFT OUTER JOIN Albums ON Tracks.AlbumId == Albums.Id" + - " LEFT OUTER JOIN Artists ON Albums.ArtistId == Artists.Id" + - " WHERE Tracks.TotalTrackCount != Tracks.AvailableTrackCount AND ({0}) AND {1}" + - " GROUP BY Tracks.AlbumId", - monitored, BuildReleaseDateCutoffWhereClause(currentTime)); + 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(); } @@ -196,14 +218,24 @@ namespace NzbDrone.Core.Music sortKey = "Albums.releaseDate"; } - string query = string.Format("SELECT Albums.* FROM(SELECT TrackFiles.AlbumId, TrackFiles.Language, COUNT(*) AS FileCount, " + - " MIN(Quality) AS MinQuality FROM TrackFiles GROUP BY TrackFiles.ArtistId, TrackFiles.AlbumId) as TrackFiles" + - " LEFT OUTER JOIN Albums ON TrackFiles.AlbumId == Albums.Id" + - " LEFT OUTER JOIN Artists ON Albums.ArtistId == Artists.Id" + - " WHERE ({0}) AND ({1} OR {2})" + - " GROUP BY TrackFiles.AlbumId" + - " ORDER BY {3} {4} LIMIT {5} OFFSET {6}", - monitored, BuildQualityCutoffWhereClause(qualitiesBelowCutoff), BuildLanguageCutoffWhereClause(languagesBelowCutoff), sortKey, pagingSpec.ToSortDirection(), pagingSpec.PageSize, pagingSpec.PagingOffset()); + 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} OR {2}) " + + "ORDER BY {3} {4} LIMIT {5} OFFSET {6}", + monitored, + BuildQualityCutoffWhereClause(qualitiesBelowCutoff), + BuildLanguageCutoffWhereClause(languagesBelowCutoff), + sortKey, + pagingSpec.ToSortDirection(), + pagingSpec.PageSize, + pagingSpec.PagingOffset()); return Query.QueryText(query); @@ -218,13 +250,19 @@ namespace NzbDrone.Core.Music monitored = "(Albums.[Monitored] = 1) AND (Artists.[Monitored] = 1)"; } - string query = string.Format("SELECT Albums.* FROM (SELECT TrackFiles.AlbumId, TrackFiles.Language, COUNT(*) AS FileCount," + - " MIN(Quality) AS MinQuality FROM TrackFiles GROUP BY TrackFiles.ArtistId, TrackFiles.AlbumId) as TrackFiles" + - " LEFT OUTER JOIN Albums ON TrackFiles.AlbumId == Albums.Id" + - " LEFT OUTER JOIN Artists ON Albums.ArtistId == Artists.Id" + - " WHERE ({0}) AND ({1} OR {2})" + - " GROUP BY TrackFiles.AlbumId", - monitored, BuildQualityCutoffWhereClause(qualitiesBelowCutoff), BuildLanguageCutoffWhereClause(languagesBelowCutoff)); + 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} OR {2}) ", + monitored, + BuildQualityCutoffWhereClause(qualitiesBelowCutoff), + BuildLanguageCutoffWhereClause(languagesBelowCutoff)); return Query.QueryText(query).Count(); } @@ -253,7 +291,7 @@ namespace NzbDrone.Core.Music { foreach (var belowCutoff in profile.QualityIds) { - clauses.Add(string.Format("(Artists.[ProfileId] = {0} AND TrackFiles.MinQuality LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff)); + clauses.Add(string.Format("(Artists.[ProfileId] = {0} AND MIN(TrackFiles.Quality) LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff)); } } @@ -286,7 +324,7 @@ namespace NzbDrone.Core.Music return Query.Where(s => s.CleanTitle == cleanTitle).SingleOrDefault(); } - public Album FindByTitle(int artistId, string title) + public Album FindByTitle(int artistMetadataId, string title) { var cleanTitle = Parser.Parser.CleanArtistName(title); @@ -294,7 +332,7 @@ namespace NzbDrone.Core.Music cleanTitle = title; return Query.Where(s => s.CleanTitle == cleanTitle || s.Title == title) - .AndWhere(s => s.ArtistId == artistId) + .AndWhere(s => s.ArtistMetadataId == artistMetadataId) .FirstOrDefault(); } @@ -303,29 +341,46 @@ namespace NzbDrone.Core.Music var cleanArtistName = Parser.Parser.CleanArtistName(artistName); cleanTitle = cleanTitle.ToLowerInvariant(); - return Query.Join(JoinType.Inner, album => album.Artist, (album, artist) => album.ArtistId == artist.Id) + return Query.Join(JoinType.Inner, rg => rg.Artist, (rg, artist) => rg.ArtistMetadataId == artist.ArtistMetadataId) .Where(artist => artist.CleanName == cleanArtistName) .Where(album => album.CleanTitle == cleanTitle) .SingleOrDefault(); } - public Album FindAlbumByRelease(string releaseId) + public Album FindAlbumByRelease(string albumReleaseId) { - return Query.FirstOrDefault(e => e.Releases.Any(r => r.Id == releaseId)); + 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 List GetArtistAlbumsWithFiles(Artist artist) + public Album FindAlbumByTrack(int trackId) { - string query = string.Format("SELECT Albums.* FROM (SELECT Tracks.AlbumId, COUNT(*) AS TotalTrackCount," + "" + - "SUM(CASE WHEN TrackFileId > 0 THEN 1 ELSE 0 END) AS AvailableTrackCount FROM Tracks GROUP BY Tracks.ArtistId, Tracks.AlbumId) as Tracks" + - " LEFT OUTER JOIN Albums ON Tracks.AlbumId == Albums.Id" + - " LEFT OUTER JOIN Artists ON Albums.ArtistId == Artists.Id" + - " WHERE Tracks.AvailableTrackCount > 0" + - " AND Albums.ArtistId=" + artist.Id + - " GROUP BY Tracks.AlbumId"); - - return Query.QueryText(query); + 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(); } } } diff --git a/src/NzbDrone.Core/Music/AlbumService.cs b/src/NzbDrone.Core/Music/AlbumService.cs index 29a8d8ad2..12c39cc5c 100644 --- a/src/NzbDrone.Core/Music/AlbumService.cs +++ b/src/NzbDrone.Core/Music/AlbumService.cs @@ -15,8 +15,8 @@ namespace NzbDrone.Core.Music Album GetAlbum(int albumId); List GetAlbums(IEnumerable albumIds); List GetAlbumsByArtist(int artistId); - Album AddAlbum(Album newAlbum); - List AddAlbums(List newAlbums); + List GetAlbumsByArtistMetadataId(int artistMetadataId); + Album AddAlbum(Album newAlbum, string albumArtistId); Album FindById(string spotifyId); Album FindByTitle(int artistId, string title); Album FindByTitleInexact(int artistId, string title); @@ -33,7 +33,8 @@ namespace NzbDrone.Core.Music void UpdateMany(List albums); void DeleteMany(List albums); void RemoveAddOptions(Album album); - Album FindAlbumByRelease(string releaseId); + Album FindAlbumByRelease(string albumReleaseId); + Album FindAlbumByTrackId(int trackId); List GetArtistAlbumsWithFiles(Artist artist); } @@ -41,37 +42,46 @@ namespace NzbDrone.Core.Music IHandleAsync { private readonly IAlbumRepository _albumRepository; + private readonly IReleaseRepository _releaseRepository; + private readonly IArtistMetadataRepository _artistMetadataRepository; private readonly IEventAggregator _eventAggregator; private readonly ITrackService _trackService; private readonly Logger _logger; public AlbumService(IAlbumRepository albumRepository, + IReleaseRepository releaseRepository, + IArtistMetadataRepository artistMetadataRepository, IEventAggregator eventAggregator, ITrackService trackService, Logger logger) { _albumRepository = albumRepository; + _releaseRepository = releaseRepository; + _artistMetadataRepository = artistMetadataRepository; _eventAggregator = eventAggregator; _trackService = trackService; _logger = logger; } - public Album AddAlbum(Album newAlbum) + public Album AddAlbum(Album newAlbum, string albumArtistId) { _albumRepository.Insert(newAlbum); + + foreach (var release in newAlbum.AlbumReleases.Value) + { + release.AlbumId = newAlbum.Id; + } + _releaseRepository.InsertMany(newAlbum.AlbumReleases.Value); + + newAlbum.ArtistMetadata = _artistMetadataRepository.FindById(albumArtistId); + newAlbum.ArtistMetadataId = newAlbum.ArtistMetadata.Value.Id; + + _albumRepository.Update(newAlbum); //_eventAggregator.PublishEvent(new AlbumAddedEvent(GetAlbum(newAlbum.Id))); return newAlbum; } - public List AddAlbums(List newAlbums) - { - _albumRepository.InsertMany(newAlbums); - //_eventAggregator.PublishEvent(new AlbumsAddedEvent(newAlbums.Select(s => s.Id).ToList())); - - return newAlbums; - } - public void DeleteAlbum(int albumId, bool deleteFiles) { var album = _albumRepository.Get(albumId); @@ -93,7 +103,7 @@ namespace NzbDrone.Core.Music { var cleanTitle = title.CleanArtistName(); - var albums = GetAlbumsByArtist(artistId); + var albums = GetAlbumsByArtistMetadataId(artistId); Func< Func, string, Tuple, string>> tc = Tuple.Create; var scoringFunctions = new List, string>> { @@ -170,14 +180,25 @@ namespace NzbDrone.Core.Music return _albumRepository.GetAlbums(artistId).ToList(); } - public Album FindAlbumByRelease(string releaseId) + public List GetAlbumsByArtistMetadataId(int artistMetadataId) + { + return _albumRepository.GetAlbumsByArtistMetadataId(artistMetadataId).ToList(); + } + + public Album FindAlbumByRelease(string albumReleaseId) + { + return _albumRepository.FindAlbumByRelease(albumReleaseId); + } + + public Album FindAlbumByTrackId(int trackId) { - return _albumRepository.FindAlbumByRelease(releaseId); + return _albumRepository.FindAlbumByTrack(trackId); } public void RemoveAddOptions(Album album) { - _albumRepository.SetFields(album, s => s.AddOptions); + var rg = _albumRepository.Get(album.Id); + _albumRepository.SetFields(rg, s => s.AddOptions); } public PagingSpec AlbumsWithoutFiles(PagingSpec pagingSpec) @@ -229,8 +250,13 @@ namespace NzbDrone.Core.Music public Album UpdateAlbum(Album album) { var storedAlbum = GetAlbum(album.Id); - var updatedAlbum = _albumRepository.Update(album); + + // If updatedAlbum has populated the Releases, populate in the storedAlbum too + if (updatedAlbum.AlbumReleases.IsLoaded) + { + storedAlbum.AlbumReleases.LazyLoad(); + } _eventAggregator.PublishEvent(new AlbumEditedEvent(updatedAlbum, storedAlbum)); return updatedAlbum; @@ -241,28 +267,12 @@ namespace NzbDrone.Core.Music var album = _albumRepository.Get(albumId); _albumRepository.SetMonitoredFlat(album, monitored); - var tracks = _trackService.GetTracksByAlbum(albumId); - foreach (var track in tracks) - { - track.Monitored = monitored; - } - _trackService.UpdateTracks(tracks); - _logger.Debug("Monitored flag for Album:{0} was set to {1}", albumId, monitored); } public void SetMonitored(IEnumerable ids, bool monitored) { _albumRepository.SetMonitored(ids, monitored); - foreach (var id in ids) - { - var tracks = _trackService.GetTracksByAlbum(id); - foreach (var track in tracks) - { - track.Monitored = monitored; - } - _trackService.UpdateTracks(tracks); - } } public List UpdateAlbums(List albums) @@ -277,8 +287,8 @@ namespace NzbDrone.Core.Music public void HandleAsync(ArtistDeletedEvent message) { - var albums = GetAlbumsByArtist(message.Artist.Id); - _albumRepository.DeleteMany(albums); + var albums = GetAlbumsByArtistMetadataId(message.Artist.ArtistMetadataId); + DeleteMany(albums); } } } diff --git a/src/NzbDrone.Core/Music/Artist.cs b/src/NzbDrone.Core/Music/Artist.cs index 28b77ef69..c2c0b5197 100644 --- a/src/NzbDrone.Core/Music/Artist.cs +++ b/src/NzbDrone.Core/Music/Artist.cs @@ -15,68 +15,45 @@ namespace NzbDrone.Core.Music { public Artist() { - Images = new List(); - Genres = new List(); - Members = new List(); - Albums = new List(); Tags = new HashSet(); - Links = new List(); - + Metadata = new ArtistMetadata(); } - public string ForeignArtistId { get; set; } - public string MBId { get; set; } - public int TADBId { get; set; } - public int DiscogsId { get; set; } - public string AMId { get; set; } - public string Name { get; set; } + public int ArtistMetadataId { get; set; } + public LazyLoaded Metadata { get; set; } public string CleanName { get; set; } public string SortName { get; set; } - public string Overview { get; set; } - public string Disambiguation { get; set; } - public string ArtistType { get; set; } public bool Monitored { get; set; } public bool AlbumFolder { get; set; } public DateTime? LastInfoSync { get; set; } - public DateTime? LastDiskSync { get; set; } - public ArtistStatusType Status { get; set; } public string Path { get; set; } - public List Images { get; set; } - public List Links { get; set; } - public List Genres { get; set; } public string RootFolderPath { get; set; } public DateTime Added { get; set; } + public int ProfileId { get; set; } public LazyLoaded Profile { get; set; } + public int LanguageProfileId { get; set; } public LazyLoaded LanguageProfile { get; set; } + public int MetadataProfileId { get; set; } public LazyLoaded MetadataProfile { get; set; } - public int ProfileId { get; set; } - public int LanguageProfileId { get; set; } - public int MetadataProfileId { get; set; } - public List Albums { get; set; } + public LazyLoaded> Albums { get; set; } public HashSet Tags { get; set; } public AddArtistOptions AddOptions { get; set; } - public Ratings Ratings { get; set; } - public List Members { get; set; } public override string ToString() { - return string.Format("[{0}][{1}]", ForeignArtistId, Name.NullSafe()); + return string.Format("[{0}][{1}]", Metadata.Value.ForeignArtistId, Metadata.Value.Name.NullSafe()); } public void ApplyChanges(Artist otherArtist) { - ForeignArtistId = otherArtist.ForeignArtistId; - Path = otherArtist.Path; - + ProfileId = otherArtist.ProfileId; Profile = otherArtist.Profile; LanguageProfileId = otherArtist.LanguageProfileId; MetadataProfileId = otherArtist.MetadataProfileId; Albums = otherArtist.Albums; - - ProfileId = otherArtist.ProfileId; Tags = otherArtist.Tags; AddOptions = otherArtist.AddOptions; RootFolderPath = otherArtist.RootFolderPath; @@ -84,5 +61,10 @@ namespace NzbDrone.Core.Music AlbumFolder = otherArtist.AlbumFolder; } + + //compatibility properties + public string Name { get { return Metadata.Value.Name; } set { Metadata.Value.Name = value; } } + public string ForeignArtistId { get { return Metadata.Value.ForeignArtistId; } set { Metadata.Value.ForeignArtistId = value; } } + } } diff --git a/src/NzbDrone.Core/Music/ArtistMetadata.cs b/src/NzbDrone.Core/Music/ArtistMetadata.cs new file mode 100644 index 000000000..3d7555b33 --- /dev/null +++ b/src/NzbDrone.Core/Music/ArtistMetadata.cs @@ -0,0 +1,56 @@ +using Marr.Data; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Profiles.Qualities; +using NzbDrone.Core.Profiles.Languages; +using NzbDrone.Core.Profiles.Metadata; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music +{ + public class ArtistMetadata : ModelBase + { + public ArtistMetadata() + { + Images = new List(); + Genres = new List(); + Members = new List(); + Links = new List(); + } + + public string ForeignArtistId { get; set; } + public string Name { get; set; } + public string Overview { get; set; } + public string Disambiguation { get; set; } + public string Type { get; set; } + public ArtistStatusType Status { get; set; } + public List Images { get; set; } + public List Links { get; set; } + public List Genres { get; set; } + public Ratings Ratings { get; set; } + public List Members { get; set; } + + public override string ToString() + { + return string.Format("[{0}][{1}]", ForeignArtistId, Name.NullSafe()); + } + + public void ApplyChanges(ArtistMetadata otherArtist) + { + ForeignArtistId = otherArtist.ForeignArtistId; + Name = otherArtist.Name; + Overview = otherArtist.Overview; + Disambiguation = otherArtist.Disambiguation; + Type = otherArtist.Type; + Status = otherArtist.Status; + Images = otherArtist.Images; + Links = otherArtist.Links; + Genres = otherArtist.Genres; + Ratings = otherArtist.Ratings; + Members = otherArtist.Members; + } + } +} diff --git a/src/NzbDrone.Core/Music/ArtistMetadataRepository.cs b/src/NzbDrone.Core/Music/ArtistMetadataRepository.cs new file mode 100644 index 000000000..498d091bd --- /dev/null +++ b/src/NzbDrone.Core/Music/ArtistMetadataRepository.cs @@ -0,0 +1,108 @@ +using System.Linq; +using System.Collections.Generic; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Events; +using Marr.Data; +using NLog; + +namespace NzbDrone.Core.Music +{ + public interface IArtistMetadataRepository : IBasicRepository + { + Artist Insert(Artist artist); + List InsertMany(List artists); + Artist Update(Artist artist); + Artist Upsert(Artist artist); + void UpdateMany(List artists); + ArtistMetadata FindById(string ArtistId); + void UpsertMany(List artists); + void UpsertMany(List artists); + } + + public class ArtistMetadataRepository : BasicRepository, IArtistMetadataRepository + { + private readonly Logger _logger; + + public ArtistMetadataRepository(IMainDatabase database, IEventAggregator eventAggregator, Logger logger) + : base(database, eventAggregator) + { + _logger = logger; + } + + public Artist Insert(Artist artist) + { + Insert(artist.Metadata.Value); + artist.ArtistMetadataId = artist.Metadata.Value.Id; + return artist; + } + + public List InsertMany(List artists) + { + InsertMany(artists.Select(x => x.Metadata.Value).ToList()); + foreach (var artist in artists) + { + artist.ArtistMetadataId = artist.Metadata.Value.Id; + } + return artists; + } + + public Artist Update(Artist artist) + { + Update(artist.Metadata.Value); + return artist; + } + + public Artist Upsert(Artist artist) + { + var existing = FindById(artist.Metadata.Value.ForeignArtistId); + if (existing != null) + { + artist.ArtistMetadataId = existing.Id; + artist.Metadata.Value.Id = existing.Id; + Update(artist); + } + else + { + Insert(artist); + } + _logger.Debug("Upserted metadata with ID {0}", artist.Id); + return artist; + } + + public void UpsertMany(List artists) + { + foreach (var artist in artists) + { + Upsert(artist); + } + } + + public void UpdateMany(List artists) + { + UpdateMany(artists.Select(x => x.Metadata.Value).ToList()); + } + + public ArtistMetadata FindById(string artistId) + { + return Query.Where(a => a.ForeignArtistId == artistId).SingleOrDefault(); + } + + public void UpsertMany(List artists) + { + foreach (var artist in artists) + { + var existing = FindById(artist.ForeignArtistId); + if (existing != null) + { + artist.Id = existing.Id; + Update(artist); + } + else + { + Insert(artist); + } + _logger.Debug("Upserted metadata with ID {0}", artist.Id); + } + } + } +} diff --git a/src/NzbDrone.Core/Music/ArtistRepository.cs b/src/NzbDrone.Core/Music/ArtistRepository.cs index c5abc49ab..f860382a5 100644 --- a/src/NzbDrone.Core/Music/ArtistRepository.cs +++ b/src/NzbDrone.Core/Music/ArtistRepository.cs @@ -1,6 +1,7 @@ using System.Linq; using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; +using Marr.Data.QGen; namespace NzbDrone.Core.Music { @@ -10,6 +11,7 @@ namespace NzbDrone.Core.Music Artist FindByName(string cleanTitle); Artist FindById(int dbId); Artist FindById(string spotifyId); + Artist GetArtistByMetadataId(int artistMetadataId); } public class ArtistRepository : BasicRepository, IArtistRepository @@ -18,7 +20,9 @@ namespace NzbDrone.Core.Music : base(database, eventAggregator) { } - + + // Always explicitly join with ArtistMetadata to populate Metadata without repeated LazyLoading + protected override QueryBuilder Query => DataMapper.Query().Join(JoinType.Inner, a => a.Metadata, (l, r) => l.ArtistMetadataId == r.Id); public bool ArtistPathExists(string path) { @@ -27,7 +31,7 @@ namespace NzbDrone.Core.Music public Artist FindById(string foreignArtistId) { - return Query.Where(s => s.ForeignArtistId == foreignArtistId).SingleOrDefault(); + return Query.Where(m => m.ForeignArtistId == foreignArtistId).SingleOrDefault(); } public Artist FindById(int dbId) @@ -42,5 +46,10 @@ namespace NzbDrone.Core.Music return Query.Where(s => s.CleanName == cleanName) .SingleOrDefault(); } + + public Artist GetArtistByMetadataId(int artistMetadataId) + { + return Query.Where(s => s.ArtistMetadataId == artistMetadataId).SingleOrDefault(); + } } } diff --git a/src/NzbDrone.Core/Music/ArtistService.cs b/src/NzbDrone.Core/Music/ArtistService.cs index cba567d10..e07ff09c1 100644 --- a/src/NzbDrone.Core/Music/ArtistService.cs +++ b/src/NzbDrone.Core/Music/ArtistService.cs @@ -15,6 +15,7 @@ namespace NzbDrone.Core.Music public interface IArtistService { Artist GetArtist(int artistId); + Artist GetArtistByMetadataId(int artistMetadataId); List GetArtists(IEnumerable artistIds); Artist AddArtist(Artist newArtist); List AddArtists(List newArtists); @@ -33,18 +34,21 @@ namespace NzbDrone.Core.Music public class ArtistService : IArtistService { private readonly IArtistRepository _artistRepository; + private readonly IArtistMetadataRepository _artistMetadataRepository; private readonly IEventAggregator _eventAggregator; private readonly ITrackService _trackService; private readonly IBuildArtistPaths _artistPathBuilder; private readonly Logger _logger; public ArtistService(IArtistRepository artistRepository, - IEventAggregator eventAggregator, - ITrackService trackService, - IBuildArtistPaths artistPathBuilder, - Logger logger) + IArtistMetadataRepository artistMetadataRepository, + IEventAggregator eventAggregator, + ITrackService trackService, + IBuildArtistPaths artistPathBuilder, + Logger logger) { _artistRepository = artistRepository; + _artistMetadataRepository = artistMetadataRepository; _eventAggregator = eventAggregator; _trackService = trackService; _artistPathBuilder = artistPathBuilder; @@ -53,6 +57,7 @@ namespace NzbDrone.Core.Music public Artist AddArtist(Artist newArtist) { + _artistMetadataRepository.Upsert(newArtist); _artistRepository.Insert(newArtist); _eventAggregator.PublishEvent(new ArtistAddedEvent(GetArtist(newArtist.Id))); @@ -61,6 +66,7 @@ namespace NzbDrone.Core.Music public List AddArtists(List newArtists) { + _artistMetadataRepository.UpsertMany(newArtists); _artistRepository.InsertMany(newArtists); _eventAggregator.PublishEvent(new ArtistsImportedEvent(newArtists.Select(s => s.Id).ToList())); @@ -144,6 +150,11 @@ namespace NzbDrone.Core.Music return _artistRepository.Get(artistDBId); } + public Artist GetArtistByMetadataId(int artistMetadataId) + { + return _artistRepository.GetArtistByMetadataId(artistMetadataId); + } + public List GetArtists(IEnumerable artistIds) { return _artistRepository.Get(artistIds).ToList(); @@ -156,19 +167,9 @@ namespace NzbDrone.Core.Music public Artist UpdateArtist(Artist artist) { - var storedArtist = GetArtist(artist.Id); // Is it Id or iTunesId? - - foreach (var album in artist.Albums) - { - var storedAlbum = storedArtist.Albums.SingleOrDefault(s => s.ForeignAlbumId == album.ForeignAlbumId); - - if (storedAlbum != null && album.Monitored != storedAlbum.Monitored) - { - _trackService.SetTrackMonitoredByAlbum(artist.Id, album.Id, album.Monitored); - } - } - - var updatedArtist = _artistRepository.Update(artist); + var storedArtist = GetArtist(artist.Id); // Is it Id or iTunesId? + var updatedArtist = _artistMetadataRepository.Update(artist); + updatedArtist = _artistRepository.Update(updatedArtist); _eventAggregator.PublishEvent(new ArtistEditedEvent(updatedArtist, storedArtist)); return updatedArtist; @@ -196,6 +197,7 @@ namespace NzbDrone.Core.Music } } + _artistMetadataRepository.UpdateMany(artist); _artistRepository.UpdateMany(artist); _logger.Debug("{0} artists updated", artist.Count); diff --git a/src/NzbDrone.Core/Music/Events/ReleaseDeletedEvent.cs b/src/NzbDrone.Core/Music/Events/ReleaseDeletedEvent.cs new file mode 100644 index 000000000..a9cfa26bb --- /dev/null +++ b/src/NzbDrone.Core/Music/Events/ReleaseDeletedEvent.cs @@ -0,0 +1,18 @@ +using NzbDrone.Common.Messaging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music.Events +{ + public class ReleaseDeletedEvent : IEvent + { + public AlbumRelease Release { get; private set; } + + public ReleaseDeletedEvent(AlbumRelease release) + { + Release = release; + } + } +} diff --git a/src/NzbDrone.Core/Music/RefreshAlbumService.cs b/src/NzbDrone.Core/Music/RefreshAlbumService.cs index cc8f5fe54..b66b9e2ec 100644 --- a/src/NzbDrone.Core/Music/RefreshAlbumService.cs +++ b/src/NzbDrone.Core/Music/RefreshAlbumService.cs @@ -27,6 +27,8 @@ namespace NzbDrone.Core.Music { private readonly IAlbumService _albumService; private readonly IArtistService _artistService; + private readonly IArtistMetadataRepository _artistMetadataRepository; + private readonly IReleaseService _releaseService; private readonly IProvideAlbumInfo _albumInfo; private readonly IRefreshTrackService _refreshTrackService; private readonly IEventAggregator _eventAggregator; @@ -35,6 +37,8 @@ namespace NzbDrone.Core.Music public RefreshAlbumService(IAlbumService albumService, IArtistService artistService, + IArtistMetadataRepository artistMetadataRepository, + IReleaseService releaseService, IProvideAlbumInfo albumInfo, IRefreshTrackService refreshTrackService, IEventAggregator eventAggregator, @@ -43,6 +47,8 @@ namespace NzbDrone.Core.Music { _albumService = albumService; _artistService = artistService; + _artistMetadataRepository = artistMetadataRepository; + _releaseService = releaseService; _albumInfo = albumInfo; _refreshTrackService = refreshTrackService; _eventAggregator = eventAggregator; @@ -65,11 +71,11 @@ namespace NzbDrone.Core.Music { _logger.ProgressInfo("Updating Info for {0}", album.Title); - Tuple> tuple; + Tuple> tuple; try { - tuple = _albumInfo.GetAlbumInfo(album.ForeignAlbumId, album.CurrentRelease?.Id); + tuple = _albumInfo.GetAlbumInfo(album.ForeignAlbumId); } catch (AlbumNotFoundException) { @@ -79,7 +85,9 @@ namespace NzbDrone.Core.Music return; } - var albumInfo = tuple.Item1; + _artistMetadataRepository.UpsertMany(tuple.Item3); + + var albumInfo = tuple.Item2; if (album.ForeignAlbumId != albumInfo.ForeignAlbumId) { @@ -92,21 +100,55 @@ namespace NzbDrone.Core.Music album.LastInfoSync = DateTime.UtcNow; album.CleanTitle = albumInfo.CleanTitle; album.Title = albumInfo.Title ?? "Unknown"; + album.Overview = albumInfo.Overview; album.Disambiguation = albumInfo.Disambiguation; album.AlbumType = albumInfo.AlbumType; album.SecondaryTypes = albumInfo.SecondaryTypes; album.Genres = albumInfo.Genres; - album.Media = albumInfo.Media; - album.Label = albumInfo.Label; album.Images = albumInfo.Images; + album.Links = albumInfo.Links; album.ReleaseDate = albumInfo.ReleaseDate; - album.Duration = tuple.Item2.Sum(track => track.Duration); - album.Releases = albumInfo.Releases; album.Ratings = albumInfo.Ratings; - album.CurrentRelease = albumInfo.CurrentRelease; + album.AlbumReleases = new List(); + + var remoteReleases = albumInfo.AlbumReleases.Value.DistinctBy(m => m.ForeignReleaseId).ToList(); + var existingReleases = _releaseService.GetReleasesByForeignReleaseId(remoteReleases.Select(x => x.ForeignReleaseId).ToList()); + + var newReleaseList = new List(); + var updateReleaseList = new List(); + + foreach (var release in remoteReleases) + { + release.AlbumId = album.Id; + var releaseToRefresh = existingReleases.SingleOrDefault(r => r.ForeignReleaseId == release.ForeignReleaseId); + + if (releaseToRefresh != null) + { + existingReleases.Remove(releaseToRefresh); + release.Id = releaseToRefresh.Id; + release.Monitored = releaseToRefresh.Monitored; + updateReleaseList.Add(release); + } + else + { + release.Monitored = false; + newReleaseList.Add(release); + } + album.AlbumReleases.Value.Add(release); + } - _refreshTrackService.RefreshTrackInfo(album, tuple.Item2); + _releaseService.InsertMany(newReleaseList); + _releaseService.UpdateMany(updateReleaseList); + _releaseService.DeleteMany(existingReleases); + + if (album.AlbumReleases.Value.Count(x => x.Monitored) == 0) + { + var toMonitor = album.AlbumReleases.Value.OrderByDescending(x => x.TrackCount).First(); + toMonitor.Monitored = true; + _releaseService.UpdateMany(new List { toMonitor }); + } + _refreshTrackService.RefreshTrackInfo(album); _albumService.UpdateMany(new List{album}); _logger.Debug("Finished album refresh for {0}", album.Title); @@ -118,7 +160,7 @@ namespace NzbDrone.Core.Music if (message.AlbumId.HasValue) { var album = _albumService.GetAlbum(message.AlbumId.Value); - var artist = _artistService.GetArtist(album.ArtistId); + var artist = _artistService.GetArtistByMetadataId(album.ArtistMetadataId); RefreshAlbumInfo(album); _eventAggregator.PublishEvent(new ArtistUpdatedEvent(artist)); } diff --git a/src/NzbDrone.Core/Music/RefreshArtistService.cs b/src/NzbDrone.Core/Music/RefreshArtistService.cs index c79f25eac..51e7f0c8b 100644 --- a/src/NzbDrone.Core/Music/RefreshArtistService.cs +++ b/src/NzbDrone.Core/Music/RefreshArtistService.cs @@ -60,38 +60,28 @@ namespace NzbDrone.Core.Music { _logger.ProgressInfo("Updating Info for {0}", artist.Name); - Tuple> tuple; + Artist artistInfo; try { - tuple = _artistInfo.GetArtistInfo(artist.ForeignArtistId, artist.MetadataProfileId); + artistInfo = _artistInfo.GetArtistInfo(artist.Metadata.Value.ForeignArtistId, artist.MetadataProfileId); } catch (ArtistNotFoundException) { - _logger.Error("Artist '{0}' (LidarrAPI {1}) was not found, it may have been removed from Metadata sources.", artist.Name, artist.ForeignArtistId); + _logger.Error("Artist '{0}' (LidarrAPI {1}) was not found, it may have been removed from Metadata sources.", artist.Name, artist.Metadata.Value.ForeignArtistId); return; } - var artistInfo = tuple.Item1; - - if (artist.ForeignArtistId != artistInfo.ForeignArtistId) + if (artist.Metadata.Value.ForeignArtistId != artistInfo.Metadata.Value.ForeignArtistId) { - _logger.Warn("Artist '{0}' (Artist {1}) was replaced with '{2}' (LidarrAPI {3}), because the original was a duplicate.", artist.Name, artist.ForeignArtistId, artistInfo.Name, artistInfo.ForeignArtistId); - artist.ForeignArtistId = artistInfo.ForeignArtistId; + _logger.Warn("Artist '{0}' (Artist {1}) was replaced with '{2}' (LidarrAPI {3}), because the original was a duplicate.", artist.Name, artist.Metadata.Value.ForeignArtistId, artistInfo.Name, artistInfo.Metadata.Value.ForeignArtistId); + artist.Metadata.Value.ForeignArtistId = artistInfo.Metadata.Value.ForeignArtistId; } - artist.Name = artistInfo.Name; - artist.Overview = artistInfo.Overview; - artist.Status = artistInfo.Status; + artist.Metadata.Value.ApplyChanges(artistInfo.Metadata.Value); artist.CleanName = artistInfo.CleanName; artist.SortName = artistInfo.SortName; artist.LastInfoSync = DateTime.UtcNow; - artist.Images = artistInfo.Images; - artist.Genres = artistInfo.Genres; - artist.Links = artistInfo.Links; - artist.Ratings = artistInfo.Ratings; - artist.Disambiguation = artistInfo.Disambiguation; - artist.ArtistType = artistInfo.ArtistType; try { @@ -103,7 +93,7 @@ namespace NzbDrone.Core.Music _logger.Warn(e, "Couldn't update artist path for " + artist.Path); } - var remoteAlbums = tuple.Item2.DistinctBy(m => new { m.ForeignAlbumId, m.ReleaseDate }).ToList(); + var remoteAlbums = artistInfo.Albums.Value.DistinctBy(m => new { m.ForeignAlbumId, m.ReleaseDate }).ToList(); // Get list of DB current db albums for artist var existingAlbums = _albumService.GetAlbumsByArtist(artist.Id); @@ -128,17 +118,21 @@ namespace NzbDrone.Core.Music } } + // Delete old albums first - this avoids errors if albums have been merged and we'll + // end up trying to duplicate an existing release under a new album + _albumService.DeleteMany(existingAlbums); + // Update new albums with artist info and correct monitored status newAlbumsList = UpdateAlbums(artist, newAlbumsList); + _logger.Info("Artist {0}, MetadataId {1}, Metadata.Id {2}", artist, artist.ArtistMetadataId, artist.Metadata.Value.Id); + _artistService.UpdateArtist(artist); _addAlbumService.AddAlbums(newAlbumsList); _refreshAlbumService.RefreshAlbumInfo(updateAlbumsList, forceAlbumRefresh); - _albumService.DeleteMany(existingAlbums); - _eventAggregator.PublishEvent(new AlbumInfoRefreshedEvent(artist, newAlbumsList, updateAlbumsList)); _logger.Debug("Finished artist refresh for {0}", artist.Name); @@ -149,7 +143,6 @@ namespace NzbDrone.Core.Music { foreach (var album in albumsToUpdate) { - album.ArtistId = artist.Id; album.ProfileId = artist.ProfileId; album.Monitored = artist.Monitored; } diff --git a/src/NzbDrone.Core/Music/RefreshTrackService.cs b/src/NzbDrone.Core/Music/RefreshTrackService.cs index 6111d21f6..6adbec1ab 100644 --- a/src/NzbDrone.Core/Music/RefreshTrackService.cs +++ b/src/NzbDrone.Core/Music/RefreshTrackService.cs @@ -11,7 +11,7 @@ namespace NzbDrone.Core.Music { public interface IRefreshTrackService { - void RefreshTrackInfo(Album album, IEnumerable remoteTracks); + void RefreshTrackInfo(Album rg); } public class RefreshTrackService : IRefreshTrackService @@ -29,66 +29,64 @@ namespace NzbDrone.Core.Music _logger = logger; } - public void RefreshTrackInfo(Album album, IEnumerable remoteTracks) + public void RefreshTrackInfo(Album album) { _logger.Info("Starting track info refresh for: {0}", album); var successCount = 0; var failCount = 0; - album = _albumService.FindById(album.ForeignAlbumId); + foreach (var release in album.AlbumReleases.Value) + { - var existingTracks = _trackService.GetTracksByAlbum(album.Id); + var existingTracks = _trackService.GetTracksByForeignReleaseId(release.ForeignReleaseId); - var updateList = new List(); - var newList = new List(); - var dupeFreeRemoteTracks = remoteTracks.DistinctBy(m => new { m.ForeignTrackId, m.TrackNumber }).ToList(); + var updateList = new List(); + var newList = new List(); + var dupeFreeRemoteTracks = release.Tracks.Value.DistinctBy(m => new { m.ForeignTrackId, m.TrackNumber }).ToList(); - foreach (var track in OrderTracks(album, dupeFreeRemoteTracks)) - { - try + foreach (var track in OrderTracks(dupeFreeRemoteTracks)) { - var trackToUpdate = GetTrackToUpdate(album, track, existingTracks); - - if (trackToUpdate != null) + try { - existingTracks.Remove(trackToUpdate); - updateList.Add(trackToUpdate); + var trackToUpdate = GetTrackToUpdate(track, existingTracks); + + if (trackToUpdate != null) + { + existingTracks.Remove(trackToUpdate); + updateList.Add(trackToUpdate); + } + else + { + trackToUpdate = new Track(); + trackToUpdate.Id = track.Id; + newList.Add(trackToUpdate); + } + + // TODO: Use object mapper to automatically handle this + trackToUpdate.ForeignTrackId = track.ForeignTrackId; + trackToUpdate.ForeignRecordingId = track.ForeignRecordingId; + trackToUpdate.AlbumReleaseId = release.Id; + trackToUpdate.ArtistMetadataId = track.ArtistMetadata.Value.Id; + trackToUpdate.TrackNumber = track.TrackNumber; + trackToUpdate.AbsoluteTrackNumber = track.AbsoluteTrackNumber; + trackToUpdate.Title = track.Title ?? "Unknown"; + trackToUpdate.Explicit = track.Explicit; + trackToUpdate.Duration = track.Duration; + trackToUpdate.MediumNumber = track.MediumNumber; + + successCount++; } - else + catch (Exception e) { - trackToUpdate = new Track(); - trackToUpdate.Monitored = album.Monitored; - trackToUpdate.Id = track.Id; - newList.Add(trackToUpdate); + _logger.Fatal(e, "An error has occurred while updating track info for album {0}. {1}", album, track); + failCount++; } - - // TODO: Use object mapper to automatically handle this - trackToUpdate.ForeignTrackId = track.ForeignTrackId; - trackToUpdate.TrackNumber = track.TrackNumber; - trackToUpdate.AbsoluteTrackNumber = track.AbsoluteTrackNumber; - trackToUpdate.Title = track.Title ?? "Unknown"; - trackToUpdate.AlbumId = album.Id; - trackToUpdate.ArtistId = album.ArtistId; - trackToUpdate.Album = track.Album ?? album; - trackToUpdate.Explicit = track.Explicit; - trackToUpdate.ArtistId = album.ArtistId; - trackToUpdate.Compilation = track.Compilation; - trackToUpdate.Duration = track.Duration; - trackToUpdate.MediumNumber = track.MediumNumber; - - - successCount++; } - catch (Exception e) - { - _logger.Fatal(e, "An error has occurred while updating track info for album {0}. {1}", album, track); - failCount++; - } - } - _trackService.DeleteMany(existingTracks); - _trackService.UpdateMany(updateList); - _trackService.InsertMany(newList); + _trackService.DeleteMany(existingTracks); + _trackService.UpdateMany(updateList); + _trackService.InsertMany(newList); + } if (failCount != 0) { @@ -101,27 +99,15 @@ namespace NzbDrone.Core.Music } } - private bool GetMonitoredStatus(Track track, IEnumerable albums) - { - if (track.AbsoluteTrackNumber == 0 /*&& track.AlbumId != 1*/) - { - return false; - } - - var album = albums.SingleOrDefault(c => c.Id == track.AlbumId); - return album == null || album.Monitored; - } - - - private Track GetTrackToUpdate(Album album, Track track, List existingTracks) + private Track GetTrackToUpdate(Track track, List existingTracks) { var result = existingTracks.FirstOrDefault(e => e.ForeignTrackId == track.ForeignTrackId && e.TrackNumber == track.TrackNumber); return result; } - private IEnumerable OrderTracks(Album album, List tracks) + private IEnumerable OrderTracks(List tracks) { - return tracks.OrderBy(e => e.AlbumId).ThenBy(e => e.TrackNumber); + return tracks.OrderBy(e => e.AlbumReleaseId).ThenBy(e => e.TrackNumber); } } } diff --git a/src/NzbDrone.Core/Music/Release.cs b/src/NzbDrone.Core/Music/Release.cs new file mode 100644 index 000000000..ac2d2e6fe --- /dev/null +++ b/src/NzbDrone.Core/Music/Release.cs @@ -0,0 +1,35 @@ +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Datastore; +using System; +using System.Collections.Generic; +using System.Linq; +using Marr.Data; + +namespace NzbDrone.Core.Music +{ + public class AlbumRelease : ModelBase + { + // These correspond to columns in the AlbumReleases table + public int AlbumId { get; set; } + public string ForeignReleaseId { get; set; } + public string Title { get; set; } + public string Status { get; set; } + public int Duration { get; set; } + public List Label { get; set; } + public string Disambiguation { get; set; } + public List Country { get; set; } + public DateTime? ReleaseDate { get; set; } + public List Media { get; set; } + public int TrackCount { get; set; } + public bool Monitored { get; set; } + + // These are dynamically queried from other tables + public LazyLoaded Album { get; set; } + public LazyLoaded> Tracks { get; set; } + + public override string ToString() + { + return string.Format("[{0}][{1}]", ForeignReleaseId, Title.NullSafe()); + } + } +} diff --git a/src/NzbDrone.Core/Music/ReleaseRepository.cs b/src/NzbDrone.Core/Music/ReleaseRepository.cs new file mode 100644 index 000000000..ea99c2c5a --- /dev/null +++ b/src/NzbDrone.Core/Music/ReleaseRepository.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common.EnsureThat; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Events; + +namespace NzbDrone.Core.Music +{ + public interface IReleaseRepository : IBasicRepository + { + List FindByAlbum(int id); + List SetMonitored(AlbumRelease release); + List FindByForeignReleaseId(List foreignReleaseIds); + } + + public class ReleaseRepository : BasicRepository, IReleaseRepository + { + public ReleaseRepository(IMainDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) + { + } + + public List FindByAlbum(int id) + { + return Query.Where(r => r.AlbumId == id).ToList(); + } + + public List FindByForeignReleaseId(List foreignReleaseIds) + { + var query = "SELECT AlbumReleases.*" + + "FROM AlbumReleases " + + $"WHERE AlbumReleases.ForeignReleaseId IN ('{string.Join("', '", foreignReleaseIds)}')"; + + return Query.QueryText(query).ToList(); + } + + public List SetMonitored(AlbumRelease release) + { + var allReleases = FindByAlbum(release.AlbumId); + allReleases.ForEach(r => r.Monitored = r.Id == release.Id); + Ensure.That(allReleases.Count(x => x.Monitored) == 1).IsTrue(); + UpdateMany(allReleases); + return allReleases; + } + } +} diff --git a/src/NzbDrone.Core/Music/ReleaseService.cs b/src/NzbDrone.Core/Music/ReleaseService.cs new file mode 100644 index 000000000..fcd2f6c3b --- /dev/null +++ b/src/NzbDrone.Core/Music/ReleaseService.cs @@ -0,0 +1,77 @@ +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Music.Events; +using System.Collections.Generic; + +namespace NzbDrone.Core.Music +{ + public interface IReleaseService + { + AlbumRelease GetRelease(int id); + void InsertMany(List releases); + void UpdateMany(List releases); + void DeleteMany(List releases); + List GetReleasesByAlbum(int releaseGroupId); + List GetReleasesByForeignReleaseId(List foreignReleaseIds); + List SetMonitored(AlbumRelease release); + } + + public class ReleaseService : IReleaseService, + IHandleAsync + { + private readonly IReleaseRepository _releaseRepository; + private readonly IEventAggregator _eventAggregator; + + public ReleaseService(IReleaseRepository releaseRepository, + IEventAggregator eventAggregator) + { + _releaseRepository = releaseRepository; + _eventAggregator = eventAggregator; + } + + public AlbumRelease GetRelease(int id) + { + return _releaseRepository.Get(id); + } + + public void InsertMany(List releases) + { + _releaseRepository.InsertMany(releases); + } + + public void UpdateMany(List releases) + { + _releaseRepository.UpdateMany(releases); + } + + public void DeleteMany(List releases) + { + _releaseRepository.DeleteMany(releases); + foreach (var release in releases) + { + _eventAggregator.PublishEvent(new ReleaseDeletedEvent(release)); + } + } + + public List GetReleasesByAlbum(int releaseGroupId) + { + return _releaseRepository.FindByAlbum(releaseGroupId); + } + + public List GetReleasesByForeignReleaseId(List foreignReleaseIds) + { + return _releaseRepository.FindByForeignReleaseId(foreignReleaseIds); + } + + public List SetMonitored(AlbumRelease release) + { + return _releaseRepository.SetMonitored(release); + } + + public void HandleAsync(AlbumDeletedEvent message) + { + var releases = GetReleasesByAlbum(message.Album.Id); + DeleteMany(releases); + } + + } +} diff --git a/src/NzbDrone.Core/Music/ShouldRefreshArtist.cs b/src/NzbDrone.Core/Music/ShouldRefreshArtist.cs index 796e15206..82f4bb8d7 100644 --- a/src/NzbDrone.Core/Music/ShouldRefreshArtist.cs +++ b/src/NzbDrone.Core/Music/ShouldRefreshArtist.cs @@ -36,7 +36,7 @@ namespace NzbDrone.Core.Music return false; } - if (artist.Status == ArtistStatusType.Continuing && artist.LastInfoSync < DateTime.UtcNow.AddDays(-2)) + if (artist.Metadata.Value.Status == ArtistStatusType.Continuing && artist.LastInfoSync < DateTime.UtcNow.AddDays(-2)) { _logger.Trace("Artist {0} is continuing and has not been refreshed in 2 days, should refresh.", artist.Name); return true; diff --git a/src/NzbDrone.Core/Music/Track.cs b/src/NzbDrone.Core/Music/Track.cs index f7df68377..4d97544c6 100644 --- a/src/NzbDrone.Core/Music/Track.cs +++ b/src/NzbDrone.Core/Music/Track.cs @@ -1,10 +1,6 @@ using NzbDrone.Core.Datastore; using NzbDrone.Core.MediaFiles; using Marr.Data; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using NzbDrone.Common.Extensions; namespace NzbDrone.Core.Music @@ -15,33 +11,33 @@ namespace NzbDrone.Core.Music { } - public const string RELEASE_DATE_FORMAT = "yyyy-MM-dd"; - + // These are model fields public string ForeignTrackId { get; set; } - public int AlbumId { get; set; } - public Artist Artist { get; set; } - - public int ArtistId { get; set; } // This is the DB Id of the Artist, not the SpotifyId - //public int CompilationId { get; set; } - public bool Compilation { get; set; } + public string ForeignRecordingId { get; set; } + public int AlbumReleaseId { get; set; } + public int ArtistMetadataId { get; set; } public string TrackNumber { get; set; } public int AbsoluteTrackNumber { get; set; } public string Title { get; set; } public int Duration { get; set; } - //public bool Ignored { get; set; } public bool Explicit { get; set; } - public bool Monitored { get; set; } - public int TrackFileId { get; set; } public Ratings Ratings { get; set; } public int MediumNumber { get; set; } - //public DateTime? ReleaseDate { get; set; } + public int TrackFileId { get; set; } + public bool HasFile => TrackFileId > 0; + // These are dynamically queried from the DB + public LazyLoaded AlbumRelease { get; set; } + public LazyLoaded ArtistMetadata { get; set; } public LazyLoaded TrackFile { get; set; } + public LazyLoaded Artist { get; set; } + // These are retained for compatibility + // TODO: Remove set, bodged in because tests expect this to be writable + public int AlbumId { get { return AlbumRelease.Value?.Album.Value?.Id ?? 0; } set { /* empty */ } } + public int ArtistId { get { return Artist.Value?.Id ?? 0; } } public Album Album { get; set; } - public bool HasFile => TrackFileId > 0; - public override string ToString() { return string.Format("[{0}]{1}", ForeignTrackId, Title.NullSafe()); diff --git a/src/NzbDrone.Core/Music/TrackRepository.cs b/src/NzbDrone.Core/Music/TrackRepository.cs index a4563fc3c..f80348b6f 100644 --- a/src/NzbDrone.Core/Music/TrackRepository.cs +++ b/src/NzbDrone.Core/Music/TrackRepository.cs @@ -16,13 +16,11 @@ namespace NzbDrone.Core.Music Track Find(int artistId, int albumId, int mediumNumber, int trackNumber); List GetTracks(int artistId); List GetTracksByAlbum(int albumId); + List GetTracksByRelease(int albumReleaseId); + List GetTracksByForeignReleaseId(string foreignReleaseId); List GetTracksByMedium(int albumId, int mediumNumber); List GetTracksByFileId(int fileId); List TracksWithFiles(int artistId); - PagingSpec TracksWithoutFiles(PagingSpec pagingSpec); - PagingSpec TracksWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff); - void SetMonitoredFlat(Track episode, bool monitored); - void SetMonitoredByAlbum(int artistId, int albumId, bool monitored); void SetFileId(int trackId, int fileId); } @@ -40,23 +38,63 @@ namespace NzbDrone.Core.Music public Track Find(int artistId, int albumId, int mediumNumber, int trackNumber) { - return Query.Where(s => s.ArtistId == artistId) - .AndWhere(s => s.AlbumId == albumId) - .AndWhere(s => s.MediumNumber == mediumNumber) - .AndWhere(s => s.AbsoluteTrackNumber == trackNumber) - .SingleOrDefault(); + string query = string.Format("SELECT Tracks.* " + + "FROM Artists " + + "JOIN Albums ON Albums.ArtistMetadataId == Artists.ArtistMetadataId " + + "JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " + + "JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " + + "WHERE Artists.Id = {0} " + + "AND Albums.Id = {1} " + + "AND AlbumReleases.Monitored = 1 " + + "AND Tracks.MediumNumber = {2} " + + "AND Tracks.AbsoluteTrackNumber = {3}", + artistId, albumId, mediumNumber, trackNumber); + + return Query.QueryText(query).SingleOrDefault(); } public List GetTracks(int artistId) { - return Query.Where(s => s.ArtistId == artistId).ToList(); + string query = string.Format("SELECT Tracks.* " + + "FROM Artists " + + "JOIN Albums ON Albums.ArtistMetadataId == Artists.ArtistMetadataId " + + "JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " + + "JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " + + "WHERE Artists.Id = {0} " + + "AND AlbumReleases.Monitored = 1", + artistId); + + return Query.QueryText(query).ToList(); } public List GetTracksByAlbum(int albumId) { - return Query.Where(s => s.AlbumId == albumId) - .ToList(); + string query = string.Format("SELECT Tracks.* " + + "FROM Albums " + + "JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " + + "JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " + + "WHERE Albums.Id = {0} " + + "AND AlbumReleases.Monitored = 1", + albumId); + + return Query.QueryText(query).ToList(); + } + + public List GetTracksByRelease(int albumReleaseId) + { + return Query.Where(t => t.AlbumReleaseId == albumReleaseId).ToList(); + } + + public List GetTracksByForeignReleaseId(string foreignReleaseId) + { + string query = string.Format("SELECT Tracks.* " + + "FROM AlbumReleases " + + "JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " + + "WHERE AlbumReleases.ForeignReleaseId = '{0}'", + foreignReleaseId); + + return Query.QueryText(query).ToList(); } public List GetTracksByMedium(int albumId, int mediumNumber) @@ -66,9 +104,17 @@ namespace NzbDrone.Core.Music return GetTracksByAlbum(albumId); } - return Query.Where(s => s.AlbumId == albumId) - .AndWhere(s => s.MediumNumber == mediumNumber) - .ToList(); + string query = string.Format("SELECT Tracks.* " + + "FROM Albums " + + "JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " + + "JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " + + "WHERE Albums.Id = {0} " + + "AND AlbumReleases.Monitored = 1 " + + "AND Tracks.MediumNumber = {1}", + albumId, + mediumNumber); + + return Query.QueryText(query).ToList(); } public List GetTracksByFileId(int fileId) @@ -78,99 +124,22 @@ namespace NzbDrone.Core.Music public List TracksWithFiles(int artistId) { - return Query.Join(JoinType.Inner, e => e.TrackFile, (e, ef) => e.TrackFileId == ef.Id) - .Where(e => e.ArtistId == artistId); - } + string query = string.Format("SELECT Tracks.* " + + "FROM Artists " + + "JOIN Albums 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 Artists.Id == {0} " + + "AND AlbumReleases.Monitored = 1 ", + artistId); - public PagingSpec TracksWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff) - { - pagingSpec.TotalRecords = TracksWhereCutoffUnmetQuery(pagingSpec, qualitiesBelowCutoff).GetRowCount(); - pagingSpec.Records = TracksWhereCutoffUnmetQuery(pagingSpec, qualitiesBelowCutoff).ToList(); - - return pagingSpec; - } - - public void SetMonitoredFlat(Track track, bool monitored) - { - track.Monitored = monitored; - SetFields(track, p => p.Monitored); - } - - public void SetMonitoredByAlbum(int artistId, int albumId, bool monitored) - { - var mapper = _database.GetDataMapper(); - - mapper.AddParameter("artistId", artistId); - mapper.AddParameter("albumId", albumId); - mapper.AddParameter("monitored", monitored); - - const string sql = "UPDATE Tracks " + - "SET Monitored = @monitored " + - "WHERE ArtistId = @artistId " + - "AND AlbumId = @albumId"; - - mapper.ExecuteNonQuery(sql); + return Query.QueryText(query).ToList(); } public void SetFileId(int trackId, int fileId) { SetFields(new Track { Id = trackId, TrackFileId = fileId }, track => track.TrackFileId); } - - public PagingSpec TracksWithoutFiles(PagingSpec pagingSpec) - { - var currentTime = DateTime.UtcNow; - - pagingSpec.TotalRecords = GetMissingTracksQuery(pagingSpec, currentTime).GetRowCount(); - pagingSpec.Records = GetMissingTracksQuery(pagingSpec, currentTime).ToList(); - - return pagingSpec; - } - - private SortBuilder GetMissingTracksQuery(PagingSpec pagingSpec, DateTime currentTime) - { - return Query.Join(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistId == s.Id) - .Where(pagingSpec.FilterExpressions.FirstOrDefault()) - .AndWhere(e => e.TrackFileId == 0) - .AndWhere(BuildAirDateUtcCutoffWhereClause(currentTime)) - .OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection()) - .Skip(pagingSpec.PagingOffset()) - .Take(pagingSpec.PageSize); - } - - - private SortBuilder TracksWhereCutoffUnmetQuery(PagingSpec pagingSpec, List qualitiesBelowCutoff) - { - return Query.Join(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistId == s.Id) - .Join(JoinType.Left, e => e.TrackFile, (e, s) => e.TrackFileId == s.Id) - .Where(pagingSpec.FilterExpressions.FirstOrDefault()) - .AndWhere(e => e.TrackFileId != 0) - .AndWhere(BuildQualityCutoffWhereClause(qualitiesBelowCutoff)) - .OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection()) - .Skip(pagingSpec.PagingOffset()) - .Take(pagingSpec.PageSize); - } - - private string BuildAirDateUtcCutoffWhereClause(DateTime currentTime) - { - return string.Format("WHERE datetime(strftime('%s', [t0].[AirDateUtc]) + [t1].[RunTime] * 60, 'unixepoch') <= '{0}'", - currentTime.ToString("yyyy-MM-dd HH:mm:ss")); - } - - - private string BuildQualityCutoffWhereClause(List qualitiesBelowCutoff) - { - var clauses = new List(); - - foreach (var profile in qualitiesBelowCutoff) - { - foreach (var belowCutoff in profile.QualityIds) - { - clauses.Add(string.Format("([t1].[ProfileId] = {0} AND [t2].[Quality] LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff)); - } - } - - return string.Format("({0})", string.Join(" OR ", clauses)); - } } } diff --git a/src/NzbDrone.Core/Music/TrackService.cs b/src/NzbDrone.Core/Music/TrackService.cs index 3a47e4672..5d1eec687 100644 --- a/src/NzbDrone.Core/Music/TrackService.cs +++ b/src/NzbDrone.Core/Music/TrackService.cs @@ -23,22 +23,19 @@ namespace NzbDrone.Core.Music Track FindTrackByTitleInexact(int artistId, int albumId, int mediumNumber, int trackNumber, string releaseTitle); List GetTracksByArtist(int artistId); List GetTracksByAlbum(int albumId); - //List GetTracksByAlbumTitle(string artistId, string albumTitle); + List GetTracksByRelease(int albumReleaseId); + List GetTracksByForeignReleaseId(string foreignReleaseId); List TracksWithFiles(int artistId); - //PagingSpec TracksWithoutFiles(PagingSpec pagingSpec); List GetTracksByFileId(int trackFileId); void UpdateTrack(Track track); - void SetTrackMonitored(int trackId, bool monitored); void UpdateTracks(List tracks); void InsertMany(List tracks); void UpdateMany(List tracks); void DeleteMany(List tracks); - void SetTrackMonitoredByAlbum(int artistId, int albumId, bool monitored); } public class TrackService : ITrackService, - IHandleAsync, - IHandleAsync, + IHandleAsync, IHandle, IHandle { @@ -79,6 +76,16 @@ namespace NzbDrone.Core.Music return _trackRepository.GetTracksByAlbum(albumId); } + public List GetTracksByRelease(int albumReleaseId) + { + return _trackRepository.GetTracksByRelease(albumReleaseId); + } + + public List GetTracksByForeignReleaseId(string foreignReleaseId) + { + return _trackRepository.GetTracksByForeignReleaseId(foreignReleaseId); + } + public Track FindTrackByTitle(int artistId, int albumId, int mediumNumber, int trackNumber, string releaseTitle) { // TODO: can replace this search mechanism with something smarter/faster/better @@ -158,14 +165,6 @@ namespace NzbDrone.Core.Music return _trackRepository.TracksWithFiles(artistId); } - - public PagingSpec TracksWithoutFiles(PagingSpec pagingSpec) - { - var episodeResult = _trackRepository.TracksWithoutFiles(pagingSpec); - - return episodeResult; - } - public List GetTracksByFileId(int trackFileId) { return _trackRepository.GetTracksByFileId(trackFileId); @@ -176,19 +175,6 @@ namespace NzbDrone.Core.Music _trackRepository.Update(track); } - public void SetTrackMonitored(int trackId, bool monitored) - { - var track = _trackRepository.Get(trackId); - _trackRepository.SetMonitoredFlat(track, monitored); - - _logger.Debug("Monitored flag for Track:{0} was set to {1}", trackId, monitored); - } - - public void SetTrackMonitoredByAlbum(int artistId, int albumId, bool monitored) - { - _trackRepository.SetMonitoredByAlbum(artistId, albumId, monitored); - } - public void UpdateTracks(List tracks) { _trackRepository.UpdateMany(tracks); @@ -209,15 +195,9 @@ namespace NzbDrone.Core.Music _trackRepository.DeleteMany(tracks); } - public void HandleAsync(ArtistDeletedEvent message) + public void HandleAsync(ReleaseDeletedEvent message) { - var tracks = GetTracksByArtist(message.Artist.Id); - _trackRepository.DeleteMany(tracks); - } - - public void HandleAsync(AlbumDeletedEvent message) - { - var tracks = GetTracksByAlbum(message.Album.Id); + var tracks = GetTracksByRelease(message.Release.Id); _trackRepository.DeleteMany(tracks); } @@ -227,12 +207,6 @@ namespace NzbDrone.Core.Music { _logger.Debug("Detaching track {0} from file.", track.Id); track.TrackFileId = 0; - - if (message.Reason != DeleteMediaFileReason.Upgrade && _configService.AutoUnmonitorPreviouslyDownloadedTracks) - { - track.Monitored = false; - } - UpdateTrack(track); } } diff --git a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs index 73f39a56f..359ee1a1f 100644 --- a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs +++ b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs @@ -37,9 +37,9 @@ namespace NzbDrone.Core.Notifications.CustomScript environmentVariables.Add("Lidarr_EventType", "Grab"); environmentVariables.Add("Lidarr_Artist_Id", artist.Id.ToString()); - environmentVariables.Add("Lidarr_Artist_Name", artist.Name); - environmentVariables.Add("Lidarr_Artist_MBId", artist.ForeignArtistId); - environmentVariables.Add("Lidarr_Artist_Type", artist.ArtistType); + environmentVariables.Add("Lidarr_Artist_Name", artist.Metadata.Value.Name); + environmentVariables.Add("Lidarr_Artist_MBId", artist.Metadata.Value.ForeignArtistId); + environmentVariables.Add("Lidarr_Artist_Type", artist.Metadata.Value.Type); environmentVariables.Add("Lidarr_Release_AlbumCount", remoteAlbum.Albums.Count.ToString()); environmentVariables.Add("Lidarr_Release_AlbumReleaseDates", string.Join(",", remoteAlbum.Albums.Select(e => e.ReleaseDate))); environmentVariables.Add("Lidarr_Release_AlbumTitles", string.Join("|", remoteAlbum.Albums.Select(e => e.Title))); @@ -66,10 +66,10 @@ namespace NzbDrone.Core.Notifications.CustomScript environmentVariables.Add("Lidarr_EventType", "Download"); environmentVariables.Add("Lidarr_IsUpgrade", message.OldFiles.Any().ToString()); environmentVariables.Add("Lidarr_Artist_Id", artist.Id.ToString()); - environmentVariables.Add("Lidarr_Artist_Name", artist.Name); + environmentVariables.Add("Lidarr_Artist_Name", artist.Metadata.Value.Name); environmentVariables.Add("Lidarr_Artist_Path", artist.Path); - environmentVariables.Add("Lidarr_Artist_MBId", artist.ForeignArtistId); - environmentVariables.Add("Lidarr_Artist_Type", artist.ArtistType); + environmentVariables.Add("Lidarr_Artist_MBId", artist.Metadata.Value.ForeignArtistId); + environmentVariables.Add("Lidarr_Artist_Type", artist.Metadata.Value.Type); environmentVariables.Add("Lidarr_Album_Id", album.Id.ToString()); environmentVariables.Add("Lidarr_Album_Title", album.Title); environmentVariables.Add("Lidarr_Album_MBId", album.ForeignAlbumId); @@ -106,10 +106,10 @@ namespace NzbDrone.Core.Notifications.CustomScript environmentVariables.Add("Lidarr_EventType", "AlbumDownload"); environmentVariables.Add("Lidarr_Artist_Id", artist.Id.ToString()); - environmentVariables.Add("Lidarr_Artist_Name", artist.Name); + environmentVariables.Add("Lidarr_Artist_Name", artist.Metadata.Value.Name); environmentVariables.Add("Lidarr_Artist_Path", artist.Path); - environmentVariables.Add("Lidarr_Artist_MBId", artist.ForeignArtistId); - environmentVariables.Add("Lidarr_Artist_Type", artist.ArtistType); + environmentVariables.Add("Lidarr_Artist_MBId", artist.Metadata.Value.ForeignArtistId); + environmentVariables.Add("Lidarr_Artist_Type", artist.Metadata.Value.Type); environmentVariables.Add("Lidarr_Album_Id", album.Id.ToString()); environmentVariables.Add("Lidarr_Album_Title", album.Title); environmentVariables.Add("Lidarr_Album_MBId", album.ForeignAlbumId); @@ -138,10 +138,10 @@ namespace NzbDrone.Core.Notifications.CustomScript environmentVariables.Add("Lidarr_EventType", "Rename"); environmentVariables.Add("Lidarr_Artist_Id", artist.Id.ToString()); - environmentVariables.Add("Lidarr_Artist_Name", artist.Name); + environmentVariables.Add("Lidarr_Artist_Name", artist.Metadata.Value.Name); environmentVariables.Add("Lidarr_Artist_Path", artist.Path); - environmentVariables.Add("Lidarr_Artist_MBId", artist.ForeignArtistId); - environmentVariables.Add("Lidarr_Artist_Type", artist.ArtistType); + environmentVariables.Add("Lidarr_Artist_MBId", artist.Metadata.Value.ForeignArtistId); + environmentVariables.Add("Lidarr_Artist_Type", artist.Metadata.Value.Type); ExecuteScript(environmentVariables); } diff --git a/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerService.cs b/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerService.cs index 924978220..da6ac9e14 100644 --- a/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerService.cs +++ b/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerService.cs @@ -154,7 +154,7 @@ namespace NzbDrone.Core.Notifications.Plex.Server { _logger.Debug("Getting metadata from Plex host: {0} for series: {1}", settings.Host, artist); - return _plexServerProxy.GetMetadataId(sectionId, artist.ForeignArtistId, language, settings); + return _plexServerProxy.GetMetadataId(sectionId, artist.Metadata.Value.ForeignArtistId, language, settings); } public ValidationFailure Test(PlexServerSettings settings) diff --git a/src/NzbDrone.Core/Notifications/Webhook/WebhookArtist.cs b/src/NzbDrone.Core/Notifications/Webhook/WebhookArtist.cs index 5a699d26d..7e291dc9d 100644 --- a/src/NzbDrone.Core/Notifications/Webhook/WebhookArtist.cs +++ b/src/NzbDrone.Core/Notifications/Webhook/WebhookArtist.cs @@ -16,7 +16,7 @@ namespace NzbDrone.Core.Notifications.Webhook Id = artist.Id; Name = artist.Name; Path = artist.Path; - MBId = artist.ForeignArtistId; + MBId = artist.Metadata.Value.ForeignArtistId; } } } diff --git a/src/NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs b/src/NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs index 080aa29b5..94b331f60 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs @@ -85,7 +85,7 @@ namespace NzbDrone.Core.Notifications.Xbmc var query = string.Format( "select path.strPath from path, artist, artistlinkpath where artist.c12 = {0} and artistlinkpath.idArtist = artist.idArtist and artistlinkpath.idPath = path.idPath", - artist.ForeignArtistId); + artist.Metadata.Value.ForeignArtistId); var command = string.Format("QueryMusicDatabase({0})", query); const string setResponseCommand = diff --git a/src/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs b/src/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs index 22cc896d1..404877d7d 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs @@ -69,7 +69,7 @@ namespace NzbDrone.Core.Notifications.Xbmc { var musicBrainzId = s.MusicbrainzArtistId.FirstOrDefault(); - return musicBrainzId == artist.ForeignArtistId || s.Label == artist.Name; + return musicBrainzId == artist.Metadata.Value.ForeignArtistId || s.Label == artist.Name; }); return matchingArtist?.File; diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 8a28cddc1..5798ed566 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -1,4 +1,4 @@ - + Debug @@ -199,6 +199,7 @@ + @@ -510,7 +511,9 @@ + + @@ -798,16 +801,13 @@ - - - - + @@ -832,7 +832,10 @@ - + + + + @@ -858,15 +861,17 @@ + - + + @@ -1271,4 +1276,4 @@ --> - \ No newline at end of file + diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 5f84f4c00..4e6fb160b 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -101,14 +101,14 @@ namespace NzbDrone.Core.Organizer var pattern = namingConfig.StandardTrackFormat; var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); - tracks = tracks.OrderBy(e => e.AlbumId).ThenBy(e => e.TrackNumber).ToList(); + tracks = tracks.OrderBy(e => e.AlbumReleaseId).ThenBy(e => e.TrackNumber).ToList(); pattern = FormatTrackNumberTokens(pattern, "", tracks); pattern = FormatMediumNumberTokens(pattern, "", tracks); AddArtistTokens(tokenHandlers, artist); AddAlbumTokens(tokenHandlers, album); - AddMediumTokens(tokenHandlers, album.Media.SingleOrDefault(m => m.Number == tracks.First().MediumNumber)); + AddMediumTokens(tokenHandlers, tracks.First().AlbumRelease.Value.Media.SingleOrDefault(m => m.Number == tracks.First().MediumNumber)); AddTrackTokens(tokenHandlers, tracks); AddTrackFileTokens(tokenHandlers, trackFile); AddQualityTokens(tokenHandlers, artist, trackFile); diff --git a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs index 9f739e80b..370a5c157 100644 --- a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs +++ b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs @@ -30,7 +30,10 @@ namespace NzbDrone.Core.Organizer _standardArtist = new Artist { - Name = "The Artist Name" + Metadata = new ArtistMetadata + { + Name = "The Artist Name" + } }; _standardAlbum = new Album @@ -39,6 +42,11 @@ namespace NzbDrone.Core.Organizer ReleaseDate = System.DateTime.Today, AlbumType = "Album", Disambiguation = "The Best Album", + }; + + var _release = new AlbumRelease + { + Album = _standardAlbum, Media = new List { new Medium @@ -47,11 +55,13 @@ namespace NzbDrone.Core.Organizer Format = "CD", Number = 1 } - } + }, + Monitored = true }; _track1 = new Track { + AlbumRelease = _release, AbsoluteTrackNumber = 3, MediumNumber = 1, diff --git a/src/NzbDrone.Core/Parser/Model/LocalTrack.cs b/src/NzbDrone.Core/Parser/Model/LocalTrack.cs index eb45514c2..d0f8a53b9 100644 --- a/src/NzbDrone.Core/Parser/Model/LocalTrack.cs +++ b/src/NzbDrone.Core/Parser/Model/LocalTrack.cs @@ -21,6 +21,7 @@ namespace NzbDrone.Core.Parser.Model public ParsedTrackInfo ParsedTrackInfo { get; set; } public Artist Artist { get; set; } public Album Album { get; set; } + public AlbumRelease Release { get; set; } public List Tracks { get; set; } public QualityModel Quality { get; set; } public Language Language { get; set; } diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index 1823581b8..2f8e4fcfb 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -158,7 +158,7 @@ namespace NzbDrone.Core.Parser if (albumInfo == null) { // TODO: Search by Title and Year instead of just Title when matching - albumInfo = _albumService.FindByTitle(artist.Id, parsedAlbumInfo.AlbumTitle); + albumInfo = _albumService.FindByTitle(artist.ArtistMetadataId, parsedAlbumInfo.AlbumTitle); } if (albumInfo == null) @@ -288,6 +288,7 @@ namespace NzbDrone.Core.Parser { Artist = artist, Album = album, + Release = album?.AlbumReleases.Value.Single(r => r.Monitored), Quality = parsedTrackInfo.Quality, Language = parsedTrackInfo.Language, Tracks = tracks, @@ -322,13 +323,13 @@ namespace NzbDrone.Core.Parser if (album == null) { - album = _albumService.FindByTitle(artist.Id, cleanAlbumTitle); + album = _albumService.FindByTitle(artist.ArtistMetadataId, cleanAlbumTitle); } if (album == null) { _logger.Debug("Trying inexact album match for {0}", parsedTrackInfo); - album = _albumService.FindByTitleInexact(artist.Id, cleanAlbumTitle); + album = _albumService.FindByTitleInexact(artist.ArtistMetadataId, cleanAlbumTitle); } if (album == null) diff --git a/src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs b/src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs index 2e59d3a8d..6b8a12c26 100644 --- a/src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs +++ b/src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs @@ -21,7 +21,7 @@ namespace NzbDrone.Core.Validation.Paths { if (context.PropertyValue == null) return true; - return (!_artistService.GetAllArtists().Exists(s => s.ForeignArtistId == context.PropertyValue.ToString())); + return (!_artistService.GetAllArtists().Exists(s => s.Metadata.Value.ForeignArtistId == context.PropertyValue.ToString())); } } } diff --git a/src/NzbDrone.Integration.Test/ApiTests/TrackFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/TrackFixture.cs index 16283235b..78907d481 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/TrackFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/TrackFixture.cs @@ -46,17 +46,6 @@ namespace NzbDrone.Integration.Test.ApiTests Tracks.Get(tracks.First().Id).Should().NotBeNull(); } - [Test] - public void should_be_able_to_set_monitor_status() - { - var tracks = Tracks.GetTracksInArtist(_artist.Id); - var updatedTrack = tracks.First(); - updatedTrack.Monitored = false; - - Tracks.Put(updatedTrack).Monitored.Should().BeFalse(); - } - - [TearDown] public void TearDown() { diff --git a/test.sh b/test.sh old mode 100644 new mode 100755