New: Update DB to store all releases for an album (#517)

* New: Store all releases for an album and track artists

* Add Overview, links and release date by release

* Tidy up

* Fix metadata refresh errors following musicbrainz edits
pull/573/head
ta264 5 years ago committed by Qstick
parent 24bdb5a891
commit c392569a63

@ -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
}

@ -106,6 +106,7 @@
.sizeOnDisk,
.qualityProfileName,
.links,
.tags {
margin-left: 8px;
font-weight: 300;

@ -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 {
</Label>
}
<Tooltip
anchor={
<Label
className={styles.detailsLabel}
size={sizes.LARGE}
>
<Icon
name={icons.EXTERNAL_LINK}
size={17}
/>
<span className={styles.links}>
Links
</span>
</Label>
}
tooltip={
<AlbumDetailsLinks
foreignAlbumId={foreignAlbumId}
links={links}
/>
}
kind={kinds.INVERSE}
position={tooltipPositions.BOTTOM}
/>
</div>
<div className={styles.overview}>
<TextTruncate
line={Math.floor(125 / (defaultFontSize * lineHeight))}
text={overview}
/>
</div>
</div>
</div>
@ -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,

@ -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();
}

@ -0,0 +1,13 @@
.links {
margin: 0;
}
.link {
white-space: nowrap;
}
.linkLabel {
composes: label from 'Components/Label.css';
cursor: pointer;
}

@ -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 (
<div className={styles.links}>
<Link
className={styles.link}
to={`https://musicbrainz.org/release-group/${foreignAlbumId}`}
>
<Label
className={styles.linkLabel}
kind={kinds.INFO}
size={sizes.LARGE}
>
Musicbrainz
</Label>
</Link>
{links.map((link, index) => {
return (
<span key={index}>
<Link className={styles.link}
to={link.url}
key={index}
>
<Label
className={styles.linkLabel}
kind={kinds.INFO}
size={sizes.LARGE}
>
{link.name}
</Label>
</Link>
{(index > 0 && index % 5 === 0) &&
<br />
}
</span>
);
})}
</div>
);
}
AlbumDetailsLinks.propTypes = {
foreignAlbumId: PropTypes.string.isRequired,
links: PropTypes.arrayOf(PropTypes.object).isRequired
};
export default AlbumDetailsLinks;

@ -19,7 +19,7 @@ function getMediumStatistics(tracks) {
if (track.trackFileId) {
trackCount++;
trackFileCount++;
} else if (track.monitored) {
} else {
trackCount++;
}

@ -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,

@ -43,7 +43,7 @@ class EditAlbumModalContent extends Component {
const {
monitored,
currentRelease,
anyReleaseOk,
releases
} = item;
@ -69,15 +69,26 @@ class EditAlbumModalContent extends Component {
/>
</FormGroup>
<FormGroup>
<FormLabel>Automatically Switch Release</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="anyReleaseOk"
helpText="Lidarr will automatically switch to the release best matching downloaded tracks"
{...anyReleaseOk}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel> Release</FormLabel>
<FormInputGroup
type={inputTypes.ALBUM_RELEASE_SELECT}
name="currentRelease"
name="releases"
helpText="Change release for this album"
albumReleases={releases}
selectedRelease={currentRelease}
onChange={onInputChange}
/>
</FormGroup>

@ -23,7 +23,7 @@ function createMapStateToProps() {
const albumSettings = _.pick(album, [
'monitored',
'currentRelease',
'anyReleaseOk',
'releases'
]);

@ -344,6 +344,14 @@ class ArtistDetails extends Component {
to={`/artist/${previousArtist.foreignArtistId}`}
/>
<IconButton
className={styles.artistNavigationButton}
name={icons.ARROW_UP}
size={30}
title={'Go to artist listing'}
to={'/'}
/>
<IconButton
className={styles.artistNavigationButton}
name={icons.ARROW_RIGHT}

@ -9,15 +9,14 @@ import SelectInput from './SelectInput';
function createMapStateToProps() {
return createSelector(
(state, { albumReleases }) => 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() {

@ -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,

@ -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()

@ -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();
}

@ -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<MediumResource> 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<string> Country { get; set; }
public List<string> 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<string> {count.ToString(), name});
}
public static List<AlbumReleaseResource> ToResource(this IEnumerable<AlbumRelease> models)
{
return models.Select(ToResource).ToList();

@ -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<string> 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<AlbumReleaseResource> Releases { get; set; }
public List<string> Genres { get; set; }
public List<MediumResource> Media { get; set; }
public ArtistResource Artist { get; set; }
public List<MediaCover> Images { get; set; }
public List<Links> 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;
}

@ -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;

@ -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)

@ -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
};
}

@ -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;
}

@ -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

@ -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<TrackResource> 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,

@ -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);
}
}
}

@ -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,
};
}

@ -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));

@ -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));

@ -36,6 +36,11 @@ namespace Marr.Data
}
}
public bool ShouldSerializeValue()
{
return IsLoaded;
}
public bool IsLoaded { get; protected set; }
public virtual void Prepare(Func<IDataMapper> dataMapperFactory, object parent)

@ -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();
}

@ -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<Artist>.CreateNew()
.BuildNew();
.With(a => a.ArtistMetadataId = 10)
.BuildNew();
Db.Insert(_artist);
_album = Builder<Album>.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<AlbumRelease>.CreateNew()
.With(e => e.AlbumId = _album.Id)
.With(e => e.Monitored = true)
.BuildNew();
Db.Insert(_release);
_track = Builder<Track>.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<TrackFile>.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();

@ -29,7 +29,7 @@ namespace NzbDrone.Core.Test.Datastore.Converters
public void should_return_enum_when_getting_int_from_db()
{
var mockMemberInfo = new Mock<MemberInfo>();
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;

@ -17,13 +17,15 @@ namespace NzbDrone.Core.Test.Datastore
public void one_to_one()
{
var trackFile = Builder<TrackFile>.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<Track>.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]

@ -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<ArtistMetadata>.CreateNew()
.With(v => v.Id = 0)
.Build();
Db.Insert(metadata);
var artist = Builder<Artist>.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<Album>.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<AlbumRelease>();
foreach (var album in albums)
{
releases.Add(
Builder<AlbumRelease>.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<TrackFile>.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<Track>.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<IDatabase>();
var DataMapper = db.GetDataMapper();
var albums = DataMapper.Query<Album>()
.Join<Album, Artist>(Marr.Data.QGen.JoinType.Inner, v => v.Artist, (l, r) => l.ArtistId == r.Id)
.Join<Album, Artist>(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<Track>()
.Join<Track, Artist>(Marr.Data.QGen.JoinType.Inner, v => v.Artist, (l, r) => l.ArtistId == r.Id)
.Join<Track, AlbumRelease>(JoinType.Inner, v => v.AlbumRelease, (l, r) => l.AlbumReleaseId == r.Id)
.Join<AlbumRelease, Album>(JoinType.Inner, v => v.Album, (l, r) => l.AlbumId == r.Id)
.Join<Album, Artist>(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<Track>()
.Join<Track, TrackFile>(Marr.Data.QGen.JoinType.Inner, v => v.TrackFile, (l, r) => l.TrackFileId == r.Id)
.Join<Track, TrackFile>(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<Track>()
.Join<Track, Artist>(Marr.Data.QGen.JoinType.Inner, v => v.Artist, (l, r) => l.ArtistId == r.Id)
.Join<Artist, Profile>(Marr.Data.QGen.JoinType.Inner, v => v.Profile, (l, r) => l.ProfileId == r.Id)
.Join<Track, AlbumRelease>(JoinType.Inner, v => v.AlbumRelease, (l, r) => l.AlbumReleaseId == r.Id)
.Join<AlbumRelease, Album>(JoinType.Inner, v => v.Album, (l, r) => l.AlbumId == r.Id)
.Join<Album, Artist>(JoinType.Inner, v => v.Artist, (l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
.Join<Artist, Profile>(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<Track>()
.Join<Track, Artist>(Marr.Data.QGen.JoinType.Inner, v => v.Artist, (l, r) => l.ArtistId == r.Id)
.Join<Artist, LanguageProfile>(Marr.Data.QGen.JoinType.Inner, v => v.LanguageProfile, (l, r) => l.ProfileId == r.Id)
.ToList();
.Join<Track, AlbumRelease>(JoinType.Inner, v => v.AlbumRelease, (l, r) => l.AlbumReleaseId == r.Id)
.Join<AlbumRelease, Album>(JoinType.Inner, v => v.Album, (l, r) => l.AlbumId == r.Id)
.Join<Album, Artist>(JoinType.Inner, v => v.Artist, (l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
.Join<Artist, LanguageProfile>(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);
}
}

@ -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<AlbumRelease> { 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<Album> { new Album(), new Album(), new Album(), new Album(), new Album(), new Album() }
Albums = new List<Album> { 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<Album> { new Album(), new Album() }
Albums = new List<Album> { 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<Album> { new Album { Id = 2 } }
Albums = new List<Album> { AlbumBuilder(2) }
};
@ -71,8 +85,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Mocker.GetMock<IAlbumService>().Setup(
s => s.GetAlbumsByArtist(It.IsAny<int>()))
.Returns(new List<Album>() {
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<IAlbumService>().Setup(
s => s.GetAlbumsByArtist(It.IsAny<int>()))
.Returns(new List<Album> {
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;

@ -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<Album>.CreateListOfSize(2)
.All()
.With(v => v.ArtistId, artist.Id)
.With(v => v.Artist, artist)
.With(v => v.Artist, new LazyLoaded<Artist>(artist))
.BuildList();
var criteria = new ArtistSearchCriteria { Albums = albums.Take(1).ToList()};

@ -84,7 +84,7 @@ namespace NzbDrone.Core.Test.Download
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>
{
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<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>
{
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<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>
{
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<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>
{
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<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>
{
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<IArtistService>()
@ -324,7 +324,7 @@ namespace NzbDrone.Core.Test.Download
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>
{
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<IHistoryService>()
@ -359,7 +359,7 @@ namespace NzbDrone.Core.Test.Download
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>
{
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);

@ -59,8 +59,10 @@ namespace NzbDrone.Core.Test.HistoryTests
var artist = Builder<Artist>.CreateNew().Build();
var tracks = Builder<Track>.CreateListOfSize(1).Build().ToList();
var trackFile = Builder<TrackFile>.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
{

@ -14,7 +14,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
public void should_delete_orphaned_albums()
{
var album = Builder<Album>.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<Artist>.CreateNew()
.BuildNew();
.With(e => e.Metadata = new ArtistMetadata {Id = 1})
.BuildNew();
Db.Insert(artist);
var albums = Builder<Album>.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);
}
}
}

@ -21,7 +21,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
.BuildNew();
_album = Builder<Album>.CreateNew()
.BuildNew();
.BuildNew();
}
private void GivenArtist()
@ -106,4 +106,4 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
AllStoredModels.Should().Contain(h => h.AlbumId == _album.Id);
}
}
}
}

@ -71,7 +71,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
.BuildNew();
var album = Builder<Album>.CreateNew()
.BuildNew();
.BuildNew();
Db.Insert(artist);
Db.Insert(album);

@ -24,20 +24,20 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
[Test]
public void should_not_delete_unorphaned_tracks()
{
var album = Builder<Album>.CreateNew()
var release = Builder<AlbumRelease>.CreateNew()
.BuildNew();
Db.Insert(album);
Db.Insert(release);
var tracks = Builder<Track>.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);
}
}
}

@ -49,7 +49,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
Mocker.GetMock<ISearchForNzb>()
.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)));
}
}
}

@ -29,7 +29,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests
_artist = Builder<Artist>.CreateNew()
.With(v => v.Id = 2)
.With(v => v.Images = new List<MediaCover.MediaCover> { new MediaCover.MediaCover(MediaCoverTypes.Poster, "") })
.With(v => v.Metadata.Value.Images = new List<MediaCover.MediaCover> { new MediaCover.MediaCover(MediaCoverTypes.Poster, "") })
.Build();
_album = Builder<Album>.CreateNew()

@ -49,7 +49,10 @@ namespace NzbDrone.Core.Test.MediaFiles
var album = Builder<Album>.CreateNew()
.With(e => e.Artist = artist)
.Build();
var release = Builder<AlbumRelease>.CreateNew()
.With(e => e.AlbumId = album.Id)
.Build();
var tracks = Builder<Track>.CreateListOfSize(5)
.Build();
@ -68,6 +71,7 @@ namespace NzbDrone.Core.Test.MediaFiles
{
Artist = artist,
Album = album,
Release = release,
Tracks = new List<Track> { track },
Path = Path.Combine(artist.Path, "Alien Ant Farm - 01 - Pilot.mp3"),
Quality = new QualityModel(Quality.MP3_256),

@ -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<TrackFile>.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<TrackFile>().Should().HaveCount(10);
var artist = Builder<Artist>.CreateNew()
.With(a => a.ArtistMetadataId = 11)
.With(a => a.Id = 0)
.Build();
Db.Insert(artist);
var album = Builder<Album>.CreateNew()
.With(a => a.Id = 0)
.With(a => a.ArtistMetadataId = artist.ArtistMetadataId)
.Build();
Db.Insert(album);
var release = Builder<AlbumRelease>.CreateNew()
.With(a => a.Id = 0)
.With(a => a.AlbumId = album.Id)
.With(a => a.Monitored = true)
.Build();
Db.Insert(release);
var track = Builder<Track>.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<Artist>().Should().HaveCount(1);
Db.All<Album>().Should().HaveCount(1);
Db.All<Track>().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);
}
}
}

@ -25,7 +25,7 @@ namespace NzbDrone.Core.Test.MediaFiles
_trackFiles = Builder<TrackFile>.CreateListOfSize(2)
.All()
.With(e => e.ArtistId = _artist.Id)
.With(e => e.Artist = _artist)
.Build()
.ToList();

@ -41,6 +41,14 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook
Allowed = true
}
},
ReleaseStatuses = new List<ProfileReleaseStatusItem>
{
new ProfileReleaseStatusItem
{
ReleaseStatus = ReleaseStatus.Official,
Allowed = true
}
}
};
Mocker.GetMock<IMetadataProfileService>()
@ -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<Album> {details.Item1});
ValidateAlbums(new List<Album> {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<Album> { details.Item1 });
var details = Subject.GetAlbumInfo(mbId);
details.Item1.Media.Count.Should().Be(mediaCount);
details.Item2.Count.Should().Be(trackCount);
ValidateAlbums(new List<Album> { 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<AlbumNotFoundException>(() => Subject.GetAlbumInfo("66c66aaa-6e2f-4930-8610-912e24c63ed1",null));
Assert.Throws<AlbumNotFoundException>(() => Subject.GetAlbumInfo("66c66aaa-6e2f-4930-8610-912e24c63ed1"));
}
[Test]
public void getting_details_of_invalid_guid_for_album()
{
Assert.Throws<BadRequestException>(() => Subject.GetAlbumInfo("66c66aaa-6e2f-4930-aaaaaa", null));
Assert.Throws<BadRequestException>(() => 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();
}

@ -40,6 +40,14 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook
Allowed = true
}
},
ReleaseStatuses = new List<ProfileReleaseStatusItem>
{
new ProfileReleaseStatusItem
{
ReleaseStatus = ReleaseStatus.Official,
Allowed = true
}
}
};
Mocker.GetMock<IMetadataProfileService>()

@ -19,6 +19,9 @@ namespace NzbDrone.Core.Test.MusicTests
public class AddAlbumFixture : CoreTest<AddAlbumService>
{
private Album _fakeAlbum;
private AlbumRelease _fakeRelease;
private readonly string _fakeArtistForeignId = "xxx-xxx-xxx";
private readonly List<ArtistMetadata> _fakeArtists = new List<ArtistMetadata> { new ArtistMetadata() };
[SetUp]
public void Setup()
@ -26,13 +29,18 @@ namespace NzbDrone.Core.Test.MusicTests
_fakeAlbum = Builder<Album>
.CreateNew()
.Build();
_fakeRelease = Builder<AlbumRelease>
.CreateNew()
.Build();
_fakeRelease.Tracks = new List<Track>();
_fakeAlbum.AlbumReleases = new List<AlbumRelease> {_fakeRelease};
}
private void GivenValidAlbum(string lidarrId)
{
Mocker.GetMock<IProvideAlbumInfo>()
.Setup(s => s.GetAlbumInfo(lidarrId, It.IsAny<string>()))
.Returns(new Tuple<Album, List<Track>>(_fakeAlbum, new List<Track>()));
.Setup(s => s.GetAlbumInfo(lidarrId))
.Returns(new Tuple<string, Album, List<ArtistMetadata>>(_fakeArtistForeignId, _fakeAlbum, _fakeArtists));
}
[Test]
@ -59,8 +67,8 @@ namespace NzbDrone.Core.Test.MusicTests
};
Mocker.GetMock<IProvideAlbumInfo>()
.Setup(s => s.GetAlbumInfo(newAlbum.ForeignAlbumId, It.IsAny<string>()))
.Throws(new AlbumNotFoundException(newAlbum.ForeignAlbumId));
.Setup(s => s.GetAlbumInfo(newAlbum.ForeignAlbumId))
.Throws(new AlbumNotFoundException(newAlbum.ForeignAlbumId));
Assert.Throws<ValidationException>(() => Subject.AddAlbum(newAlbum));

@ -28,13 +28,14 @@ namespace NzbDrone.Core.Test.MusicTests
.CreateNew()
.With(s => s.Path = null)
.Build();
_fakeArtist.Albums = new List<Album>();
}
private void GivenValidArtist(string lidarrId)
{
Mocker.GetMock<IProvideArtistInfo>()
.Setup(s => s.GetArtistInfo(lidarrId, It.IsAny<int>()))
.Returns(new Tuple<Artist, List<Album>>(_fakeArtist, new List<Album>()));
.Setup(s => s.GetArtistInfo(lidarrId, It.IsAny<int>()))
.Returns(_fakeArtist);
}
private void GivenValidPath()

@ -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<AlbumRepository>();
_releaseRepo = Mocker.Resolve<ReleaseRepository>();
_release = Builder<AlbumRelease>
.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<AlbumRelease>
{
new AlbumRelease
{
Id = "e00e40a3-5ed5-4ed3-9c22-0a8ff4119bdf"
}
}
AlbumReleases = new List<AlbumRelease> {_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<AlbumRelease>
AlbumReleases = new List<AlbumRelease>
{
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<AlbumRelease>
AlbumReleases = new List<AlbumRelease>
{
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();
}

@ -35,7 +35,7 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests
});
Mocker.GetMock<IAlbumRepository>()
.Setup(s => s.GetAlbums(It.IsAny<int>()))
.Setup(s => s.GetAlbumsByArtistMetadataId(It.IsAny<int>()))
.Returns(_albums);
}

@ -18,21 +18,32 @@ namespace NzbDrone.Core.Test.MusicTests.ArtistRepositoryTests
public class ArtistRepositoryFixture : DbTest<ArtistRepository, Artist>
{
private ArtistRepository _artistRepo;
private ArtistMetadataRepository _artistMetadataRepo;
private Artist CreateArtist(string name)
private void AddArtist(string name)
{
return Builder<Artist>.CreateNew()
var metadata = Builder<ArtistMetadata>.CreateNew()
.With(a => a.Id = 0)
.With(a => a.Name = name)
.BuildNew();
var artist = Builder<Artist>.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<ArtistRepository>();
_artistRepo.Insert(CreateArtist("The Black Eyed Peas"));
_artistRepo.Insert(CreateArtist("The Black Keys"));
_artistMetadataRepo = Mocker.Resolve<ArtistMetadataRepository>();
AddArtist("The Black Eyed Peas");
AddArtist("The Black Keys");
}
[Test]

@ -19,29 +19,46 @@ namespace NzbDrone.Core.Test.MusicTests
{
private Artist _artist;
private List<Album> _albums;
private List<AlbumRelease> _releases;
private readonly string _fakeArtistForeignId = "xxx-xxx-xxx";
private readonly List<ArtistMetadata> _fakeArtists = new List<ArtistMetadata> { new ArtistMetadata() };
[SetUp]
public void Setup()
{
var release = Builder<AlbumRelease>
.CreateNew()
.With(s => s.Media = new List<Medium> { 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<AlbumRelease> { release };
var album1 = Builder<Album>.CreateNew()
.With(s => s.Id = 1234)
.With(s => s.ForeignAlbumId = "1")
.With(s => s.AlbumReleases = _releases)
.Build();
_albums = new List<Album>{album1};
_albums = new List<Album>{ album1 };
_artist = Builder<Artist>.CreateNew()
.With(s => s.Albums = new List<Album>
{
album1
})
.Build();
.With(s => s.Albums = _albums)
.Build();
Mocker.GetMock<IArtistService>()
.Setup(s => s.GetArtist(_artist.Id))
.Returns(_artist);
Mocker.GetMock<IReleaseService>()
.Setup(s => s.GetReleasesByAlbum(album1.Id))
.Returns(new List<AlbumRelease> { release });
Mocker.GetMock<IProvideAlbumInfo>()
.Setup(s => s.GetAlbumInfo(It.IsAny<string>(), It.IsAny<string>()))
.Setup(s => s.GetAlbumInfo(It.IsAny<string>()))
.Callback(() => { throw new AlbumNotFoundException(album1.ForeignAlbumId); });
Mocker.GetMock<ICheckIfAlbumShouldBeRefreshed>()
@ -52,8 +69,8 @@ namespace NzbDrone.Core.Test.MusicTests
private void GivenNewAlbumInfo(Album album)
{
Mocker.GetMock<IProvideAlbumInfo>()
.Setup(s => s.GetAlbumInfo(_albums.First().ForeignAlbumId, It.IsAny<string>()))
.Returns(new Tuple<Album, List<Track>>(album, new List<Track>()));
.Setup(s => s.GetAlbumInfo(_albums.First().ForeignAlbumId))
.Returns(new Tuple<string, Album, List<ArtistMetadata>>(_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);

@ -35,8 +35,11 @@ namespace NzbDrone.Core.Test.MusicTests
_albums = new List<Album> {_album1, _album2};
var metadata = Builder<ArtistMetadata>.CreateNew().Build();
_artist = Builder<Artist>.CreateNew()
.Build();
.With(a => a.Metadata = metadata)
.Build();
Mocker.GetMock<IArtistService>()
.Setup(s => s.GetArtist(_artist.Id))
@ -55,7 +58,7 @@ namespace NzbDrone.Core.Test.MusicTests
{
Mocker.GetMock<IProvideArtistInfo>()
.Setup(s => s.GetArtistInfo(_artist.ForeignArtistId, _artist.MetadataProfileId))
.Returns(new Tuple<Artist, List<Album>>(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<Album>.CreateNew()
.With(s => s.ForeignAlbumId = "2")
.Build());
newArtistInfo.Albums.Value.Add(Builder<Album>.CreateNew()
.With(s => s.ForeignAlbumId = "2")
.Build());
_artist.Albums.Add(Builder<Album>.CreateNew()
.With(s => s.ForeignAlbumId = "2")
.Build());
_artist.Albums.Value.Add(Builder<Album>.CreateNew()
.With(s => s.ForeignAlbumId = "2")
.Build());
_artist.Albums.Add(Builder<Album>.CreateNew()
.With(s => s.ForeignAlbumId = "2")
.Build());
_artist.Albums.Value.Add(Builder<Album>.CreateNew()
.With(s => s.ForeignAlbumId = "2")
.Build());
GivenNewArtistInfo(newArtistInfo);
Subject.Execute(new RefreshArtistCommand(_artist.Id));
Mocker.GetMock<IArtistService>()
.Verify(v => v.UpdateArtist(It.Is<Artist>(s => s.Albums.Count == 2)));
.Verify(v => v.UpdateArtist(It.Is<Artist>(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<Album>.CreateNew()
.With(s => s.ForeignAlbumId = "2")
.Build());
newArtistInfo.Albums.Value.Add(Builder<Album>.CreateNew()
.With(s => s.ForeignAlbumId = "2")
.Build());
newArtistInfo.Albums.Add(Builder<Album>.CreateNew()
.With(s => s.ForeignAlbumId = "2")
.Build());
newArtistInfo.Albums.Value.Add(Builder<Album>.CreateNew()
.With(s => s.ForeignAlbumId = "2")
.Build());
GivenNewArtistInfo(newArtistInfo);
Subject.Execute(new RefreshArtistCommand(_artist.Id));
Mocker.GetMock<IArtistService>()
.Verify(v => v.UpdateArtist(It.Is<Artist>(s => s.Albums.Count == 2)));
.Verify(v => v.UpdateArtist(It.Is<Artist>(s => s.Albums.Value.Count == 2)));
}
}

@ -17,7 +17,7 @@ namespace NzbDrone.Core.Test.MusicTests
public void Setup()
{
_artist = Builder<Artist>.CreateNew()
.With(v => v.Status == ArtistStatusType.Continuing)
.With(v => v.Metadata.Value.Status == ArtistStatusType.Continuing)
.Build();
Mocker.GetMock<IAlbumService>()
@ -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()

@ -174,7 +174,5 @@ namespace NzbDrone.Core.Test.MusicTests.TitleMatchingTests
track.Should().BeNull();
}
}
}

@ -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<AlbumRelease>
.CreateNew()
.With(s => s.Media = new List<Medium> { new Medium { Number = 1 } })
.Build();
_track = Builder<Track>.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();

@ -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<AlbumRelease>
.CreateNew()
.With(s => s.Media = new List<Medium> { new Medium { Number = 1 } })
.Build();
_namingConfig = NamingConfig.Default;
_namingConfig.RenameTracks = true;
@ -48,6 +54,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
_track1 = Builder<Track>.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<Track>.CreateNew()
.With(e => e.Title = "Part 1")
.With(e => e.AbsoluteTrackNumber = 6)
.With(e => e.AlbumRelease = _release)
.Build();
Subject.BuildTrackFileName(new List<Track> { 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<Track>.CreateNew()
.With(e => e.Title = "Part 1")
.With(e => e.AbsoluteTrackNumber = 6)
.With(e => e.AlbumRelease = _release)
.Build();
Subject.BuildTrackFileName(new List<Track> { track }, new Artist { Name = "In The Woods..." }, new Album { Title = "30 Rock" }, _trackFile)

@ -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<AlbumRelease>
.CreateNew()
.With(s => s.Media = new List<Medium> { new Medium { Number = 1 } })
.Build();
_track = Builder<Track>.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" };

@ -33,18 +33,19 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
_fakeAlbum = Builder<Album>
.CreateNew()
.With(e => e.ArtistId = _fakeArtist.Id)
.With(e => e.Releases = new List<AlbumRelease>
.With(e => e.AlbumReleases = new List<AlbumRelease>
{
new AlbumRelease
{
Id = "5ecd552b-e54b-4c37-b62c-9d6234834bad"
ForeignReleaseId = "5ecd552b-e54b-4c37-b62c-9d6234834bad",
Monitored = true
}
})
.Build();
_fakeTrack = Builder<Track>
.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<IAlbumService>()
.Setup(s => s.FindAlbumByRelease(_fakeAlbum.Releases.First().Id))
.Setup(s => s.FindAlbumByRelease(_fakeAlbum.AlbumReleases.Value.First().ForeignReleaseId))
.Returns(_fakeAlbum);
Mocker.GetMock<ITrackService>()
@ -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()

@ -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<DiskScanService>
{
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<IDiskProvider>().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<IDiskProvider>().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<IDiskProvider>().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);

@ -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";
}
}
}

@ -48,7 +48,7 @@ namespace NzbDrone.Core.Datastore
_eventAggregator = eventAggregator;
}
protected QueryBuilder<TModel> Query => DataMapper.Query<TModel>();
protected virtual QueryBuilder<TModel> Query => DataMapper.Query<TModel>();
protected void Delete(Expression<Func<TModel, bool>> filter)
{
@ -80,7 +80,7 @@ namespace NzbDrone.Core.Datastore
public IEnumerable<TModel> Get(IEnumerable<int> 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())

@ -25,15 +25,7 @@ namespace NzbDrone.Core.Datastore.Extensions
public static RelationshipBuilder<TParent> Relationship<TParent>(this ColumnMapBuilder<TParent> mapBuilder)
{
return mapBuilder.Relationships.AutoMapComplexTypeProperties<ILazyLoaded>();
}
public static RelationshipBuilder<TParent> HasMany<TParent, TChild>(this RelationshipBuilder<TParent> relationshipBuilder, Expression<Func<TParent, LazyList<TChild>>> portalExpression, Func<TParent, int> childIdSelector)
where TParent : ModelBase
where TChild : ModelBase
{
return relationshipBuilder.For(portalExpression.GetMemberName())
.LazyLoad((db, parent) => db.Query<TChild>().Where(c => c.Id == childIdSelector(parent)).ToList());
return mapBuilder.Relationships.MapProperties<TParent>();
}
private static string GetMemberName<T, TMember>(this Expression<Func<T, TMember>> member)

@ -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<string> Country { get; set; }
public string Format { get; set; }
public List<string> Label { get; set; }
}
private List<AlbumRelease> ReadReleasesFromAlbums(IDbConnection conn, IDbTransaction tran)
{
// need to get all the old albums
var releases = new List<AlbumRelease>();
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<LegacyAlbumRelease>(releaseReader.GetString(1));
var media = new List<Medium>();
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<AlbumRelease> 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();
}
}
}
}
}

@ -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<Artist>().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<Album>().Where(rg => rg.ArtistMetadataId == a.Id).ToList());
Mapper.Entity<Album>().RegisterModel("Albums");
Mapper.Entity<ArtistMetadata>().RegisterModel("ArtistMetadata");
Mapper.Entity<TrackFile>().RegisterModel("TrackFiles")
.Ignore(f => f.Path)
.Relationships.AutoMapICollectionOrComplexProperties()
.For("Tracks")
.LazyLoad(condition: parent => parent.Id > 0,
query: (db, parent) => db.Query<Track>().Where(c => c.TrackFileId == parent.Id).ToList()) // TODO: Figure what the hell to do here
.HasOne(file => file.Artist, file => file.ArtistId);
Mapper.Entity<Album>().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<AlbumRelease>().Where(r => r.AlbumId == rg.Id).ToList())
.For(rg => rg.Artist)
.LazyLoad(condition: rg => rg.ArtistMetadataId > 0, query: (db, rg) => db.Query<Artist>().Where(a => a.ArtistMetadataId == rg.ArtistMetadataId).SingleOrDefault());
Mapper.Entity<AlbumRelease>().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<Track>().Where(t => t.AlbumReleaseId == r.Id).ToList());
Mapper.Entity<Track>().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<Artist>().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<TrackFile>().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<Track>().Where(c => c.TrackFileId == parent.Id).ToList())
.For(t => t.Artist)
.LazyLoad(condition: f => f.Id > 0, query: (db, f) => db.Query<Artist>().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<QualityDefinition>().RegisterModel("QualityDefinitions")
.Ignore(d => d.GroupName)

@ -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());

@ -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))
{

@ -158,7 +158,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
return new List<ImageFileResult>();
}
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);

@ -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

@ -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<ImageFileResult> 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);

@ -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

@ -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)");
}
}

@ -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)");
}
}
}

@ -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)");
}
}
}

@ -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)");
}
}
}

@ -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)");
}
}
}

@ -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);
}
}

@ -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<Func<Album, bool>> 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<Album>
{

@ -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;

@ -29,7 +29,17 @@ namespace NzbDrone.Core.MediaFiles
public List<TrackFile> 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<TrackFile> GetFilesByAlbum(int albumId)
@ -39,9 +49,20 @@ namespace NzbDrone.Core.MediaFiles
public List<TrackFile> 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<TrackFile>(query);
}
}

@ -28,7 +28,7 @@ namespace NzbDrone.Core.MediaFiles
}
public class MediaFileService : IMediaFileService, IHandleAsync<ArtistDeletedEvent>, IHandleAsync<AlbumDeletedEvent>
public class MediaFileService : IMediaFileService, IHandleAsync<AlbumDeletedEvent>
{
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);

@ -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<List<Track>> Episodes { get; set; }
public LazyLoaded<Artist> Artist { get; set; }
public LazyLoaded<List<Track>> Tracks { get; set; }
public Language Language { get; set; }
public int AlbumId { get; set; }
// These are queried from the database
public LazyLoaded<List<Track>> Tracks { get; set; }
public LazyLoaded<Artist> Artist { get; set; }
public LazyLoaded<Album> 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()
{

@ -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<TrackFile>();
var allOldTrackFiles = new List<TrackFile>();
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

@ -11,7 +11,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
public class ImportDecision
{
public LocalTrack LocalTrack { get; private set; }
public IEnumerable<Rejection> Rejections { get; private set; }
public IList<Rejection> 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);
}
}
}

@ -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<ImportDecision> GetImportDecisions(List<string> musicFiles, Artist artist);
List<ImportDecision> GetImportDecisions(List<string> musicFiles, Artist artist, ParsedTrackInfo folderInfo);
List<ImportDecision> GetImportDecisions(List<string> musicFiles, Artist artist, ParsedTrackInfo folderInfo, bool filterExistingFiles);
List<ImportDecision> GetImportDecisions(List<string> 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<IImportDecisionEngineSpecification> _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<IImportDecisionEngineSpecification> 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<ImportDecision> GetImportDecisions(List<string> musicFiles, Artist artist, ParsedTrackInfo folderInfo)
{
return GetImportDecisions(musicFiles, artist, folderInfo, false);
return GetImportDecisions(musicFiles, artist, folderInfo, false, false);
}
public List<ImportDecision> GetImportDecisions(List<string> 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<ImportDecision> GetImportDecisions(List<string> 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<ImportDecision> GetImportDecisionsForCurrentRelease(List<string> files, Artist artist, ParsedTrackInfo folderInfo, bool shouldUseFolderName)
{
var decisions = new List<ImportDecision>();
foreach (var file in files)
@ -78,6 +122,90 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
{
return GetDecision(file, artist, album, null, false);
}
public List<ImportDecision> GetImportDecisions(List<ImportDecision> 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<ImportDecision> GetImportDecisionsForBestRelease(List<ImportDecision> 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)
{

@ -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<int> TrackIds { get; set; }
public QualityModel Quality { get; set; }
public Language Language { get; set; }

@ -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<Track> Tracks { get; set; }
public QualityModel Quality { get; set; }
public Language Language { get; set; }

@ -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
};

@ -6,6 +6,6 @@ namespace NzbDrone.Core.MetadataSource
{
public interface IProvideAlbumInfo
{
Tuple<Album, List<Track>> GetAlbumInfo(string lidarrId, string releaseId);
Tuple<string, Album, List<ArtistMetadata>> GetAlbumInfo(string id);
}
}

@ -7,6 +7,6 @@ namespace NzbDrone.Core.MetadataSource
{
public interface IProvideArtistInfo
{
Tuple<Artist, List<Album>> GetArtistInfo(string lidarrId, int metadataProfileId);
Artist GetArtistInfo(string lidarrId, int metadataProfileId);
}
}

@ -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; }
}
}

@ -7,31 +7,18 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
{
public class AlbumResource
{
public AlbumResource()
{
Media = new List<MediumResource>();
Releases = new List<ReleaseResource>();
}
public List<ArtistResource> 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<ImageResource> Images { get; set; }
public string Title { get; set; }
public string ArtistId { get; set; }
public List<ArtistResource> Artists { get; set; }
public string Disambiguation { get; set; }
public string Overview { get; set; }
public List<string> Genres { get; set; }
public List<string> Labels { get; set; }
public string Type { get; set; }
public List<string> SecondaryTypes { get; set; }
public List<MediumResource> Media { get; set; }
public List<TrackResource> Tracks { get; set; }
public List<ReleaseResource> Releases { get; set; }
public string Id { get; set; }
public List<ImageResource> Images { get; set; }
public List<LinkResource> 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<ReleaseResource> Releases { get; set; }
public List<string> SecondaryTypes { get; set; }
public string Title { get; set; }
public string Type { get; set; }
}
}

@ -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; }
}
}

@ -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<string> Label {get; set;}
public List<string> Country { get; set; }
public DateTime ReleaseDate { get; set; }
public string Id { get; set; }
public List<string> Label { get; set; }
public List<MediumResource> 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<TrackResource> Tracks { get; set; }
}
}

@ -1,8 +0,0 @@
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
{
public class TimeOfDayResource
{
public int Hours { get; set; }
public int Minutes { get; set; }
}
}

@ -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<ArtistResource> Artists { get; set; }
}
}

@ -45,7 +45,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
_logger = logger;
}
public Tuple<Artist, List<Album>> 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, List<Album>>(artist, albums.ToList());
return artist;
}
public Tuple<Album, List<Track>> GetAlbumInfo(string foreignAlbumId, string releaseId)
public Tuple<string, Album, List<ArtistMetadata>> 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<AlbumResource>(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, List<Track>>(album, tracks.ToList());
return new Tuple<string, Album, List<ArtistMetadata>>(httpResponse.Resource.ArtistId, album, artists);
}
public List<Artist> 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<Artist> { GetArtistInfo(searchGuid.ToString(), metadataProfile).Item1 };
return new List<Artist> { GetArtistInfo(searchGuid.ToString(), metadataProfile) };
}
catch (ArtistNotFoundException)
{
@ -183,7 +185,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
var httpResponse = _httpClient.Get<List<ArtistResource>>(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<Album> {GetAlbumInfo(searchGuid.ToString(), null).Item1};
return new List<Album> { GetAlbumInfo(searchGuid.ToString()).Item2 };
}
existingAlbum.Artist = _artistService.GetArtist(existingAlbum.ArtistId);
@ -247,7 +249,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
var httpResponse = _httpClient.Get<List<AlbumResource>>(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<string, ArtistMetadata> 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<string, ArtistMetadata> 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<string, ArtistMetadata> 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<MediaCover.MediaCover>
{
new MediaCover.MediaCover(MediaCoverTypes.Headshot, arg.Image)
};
}
return newMember;
}
private static ArtistStatusType MapArtistStatus(string status)
{
if (status == null)

@ -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<Album, List<Track>> AddSkyhookData(Album newAlbum)
private Tuple<string, Album, List<ArtistMetadata>> AddSkyhookData(Album newAlbum)
{
Tuple<Album, List<Track>> tuple;
Tuple<string, Album, List<ArtistMetadata>> 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;
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save