From 76db95947cb0616d23c646afc5a4c1f454e415c7 Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Thu, 11 May 2017 13:43:05 -0500 Subject: [PATCH 01/13] Refactored most code for track parsing. --- .../Definitions/SearchCriteriaBase.cs | 4 + .../Commands/RescanArtistCommand.cs | 26 ++++ .../MediaFiles/DiskScanService.cs | 124 +++++++++++++++++- .../EpisodeImport/ImportDecisionMaker.cs | 100 ++++++++------ .../Events/ArtistScanSkippedEvent.cs | 27 ++++ .../MediaFiles/Events/ArtistScannedEvent.cs | 19 +++ .../MediaFiles/MediaFileRepository.cs | 25 ++-- .../MediaFiles/MediaFileService.cs | 77 ++++++----- src/NzbDrone.Core/MediaFiles/TrackFile.cs | 2 +- src/NzbDrone.Core/Music/ArtistService.cs | 5 +- .../Music/RefreshArtistService.cs | 2 +- .../Parser/Model/ArtistTitleInfo.cs | 14 ++ .../Parser/Model/ParsedTrackInfo.cs | 73 ++--------- src/NzbDrone.Core/Parser/ParsingService.cs | 84 +++++++++++- src/UI/Controller.js | 6 + src/UI/Router.js | 1 + src/UI/Series/Details/SeriesDetailsLayout.js | 12 +- .../SeriesOverviewItemViewTemplate.hbs | 2 +- 18 files changed, 428 insertions(+), 175 deletions(-) create mode 100644 src/NzbDrone.Core/MediaFiles/Commands/RescanArtistCommand.cs create mode 100644 src/NzbDrone.Core/MediaFiles/Events/ArtistScanSkippedEvent.cs create mode 100644 src/NzbDrone.Core/MediaFiles/Events/ArtistScannedEvent.cs create mode 100644 src/NzbDrone.Core/Parser/Model/ArtistTitleInfo.cs diff --git a/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs b/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs index c5e602e59..630f5edc1 100644 --- a/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs +++ b/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs @@ -4,6 +4,7 @@ using System.Text.RegularExpressions; using NzbDrone.Common.EnsureThat; using NzbDrone.Common.Extensions; using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; namespace NzbDrone.Core.IndexerSearch.Definitions { @@ -19,6 +20,9 @@ namespace NzbDrone.Core.IndexerSearch.Definitions public virtual bool MonitoredEpisodesOnly { get; set; } public virtual bool UserInvokedSearch { get; set; } + public Artist Artist { get; set; } + public List Tracks { get; set; } + public List QueryTitles => SceneTitles.Select(GetQueryTitle).ToList(); public static string GetQueryTitle(string title) diff --git a/src/NzbDrone.Core/MediaFiles/Commands/RescanArtistCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/RescanArtistCommand.cs new file mode 100644 index 000000000..904350d5a --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/Commands/RescanArtistCommand.cs @@ -0,0 +1,26 @@ +using NzbDrone.Core.Messaging.Commands; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MediaFiles.Events +{ + public class RescanArtistCommand : Command + { + + public string ArtistId { get; set; } + + public override bool SendUpdatesToClient => true; + + public RescanArtistCommand() + { + ArtistId = ""; + } + + public RescanArtistCommand(string artistId) + { + ArtistId = artistId; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs index 2f6ba4b25..60c929956 100644 --- a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs +++ b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs @@ -16,12 +16,15 @@ using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv; using NzbDrone.Core.Tv.Events; +using NzbDrone.Core.Music; +using NzbDrone.Core.Music.Events; namespace NzbDrone.Core.MediaFiles { public interface IDiskScanService { void Scan(Series series); + void Scan(Artist artist); string[] GetVideoFiles(string path, bool allDirectories = true); string[] GetNonVideoFiles(string path, bool allDirectories = true); List FilterFiles(Series series, IEnumerable files); @@ -30,13 +33,16 @@ namespace NzbDrone.Core.MediaFiles public class DiskScanService : IDiskScanService, IHandle, - IExecute + IExecute, + IHandle, + IExecute { private readonly IDiskProvider _diskProvider; private readonly IMakeImportDecision _importDecisionMaker; private readonly IImportApprovedEpisodes _importApprovedEpisodes; private readonly IConfigService _configService; private readonly ISeriesService _seriesService; + private readonly IArtistService _artistService; private readonly IMediaFileTableCleanupService _mediaFileTableCleanupService; private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; @@ -46,6 +52,7 @@ namespace NzbDrone.Core.MediaFiles IImportApprovedEpisodes importApprovedEpisodes, IConfigService configService, ISeriesService seriesService, + IArtistService artistService, IMediaFileTableCleanupService mediaFileTableCleanupService, IEventAggregator eventAggregator, Logger logger) @@ -55,6 +62,7 @@ namespace NzbDrone.Core.MediaFiles _importApprovedEpisodes = importApprovedEpisodes; _configService = configService; _seriesService = seriesService; + _artistService = artistService; _mediaFileTableCleanupService = mediaFileTableCleanupService; _eventAggregator = eventAggregator; _logger = logger; @@ -63,6 +71,58 @@ namespace NzbDrone.Core.MediaFiles private static readonly Regex ExcludedSubFoldersRegex = new Regex(@"(?:\\|\/|^)(extras|@eadir|extrafanart|plex\sversions|\..+)(?:\\|\/)", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex ExcludedFilesRegex = new Regex(@"^\._|Thumbs\.db", RegexOptions.Compiled | RegexOptions.IgnoreCase); + public void Scan(Artist artist) + { + var rootFolder = _diskProvider.GetParentFolder(artist.Path); + + if (!_diskProvider.FolderExists(rootFolder)) + { + _logger.Warn("Artist' root folder ({0}) doesn't exist.", rootFolder); + _eventAggregator.PublishEvent(new ArtistScanSkippedEvent(artist, ArtistScanSkippedReason.RootFolderDoesNotExist)); + return; + } + + if (_diskProvider.GetDirectories(rootFolder).Empty()) + { + _logger.Warn("Artist' root folder ({0}) is empty.", rootFolder); + _eventAggregator.PublishEvent(new ArtistScanSkippedEvent(artist, ArtistScanSkippedReason.RootFolderIsEmpty)); + return; + } + + _logger.ProgressInfo("Scanning disk for {0}", artist.ArtistName); + + if (!_diskProvider.FolderExists(artist.Path)) + { + if (_configService.CreateEmptySeriesFolders) + { + _logger.Debug("Creating missing artist folder: {0}", artist.Path); + _diskProvider.CreateFolder(artist.Path); + SetPermissions(artist.Path); + } + else + { + _logger.Debug("Artist folder doesn't exist: {0}", artist.Path); + } + CleanMediaFiles(artist, new List()); + CompletedScanning(artist); + return; + } + + var musicFilesStopwatch = Stopwatch.StartNew(); + var mediaFileList = FilterFiles(artist, GetMusicFiles(artist.Path)).ToList(); + musicFilesStopwatch.Stop(); + _logger.Trace("Finished getting track files for: {0} [{1}]", artist, musicFilesStopwatch.Elapsed); + + CleanMediaFiles(artist, mediaFileList); + + var decisionsStopwatch = Stopwatch.StartNew(); + var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, artist); + decisionsStopwatch.Stop(); + _logger.Trace("Import decisions complete for: {0} [{1}]", artist, decisionsStopwatch.Elapsed); + _importApprovedTracks.Import(decisions, false); + + CompletedScanning(artist); + } public void Scan(Series series) { var rootFolder = _diskProvider.GetParentFolder(series.Path); @@ -122,12 +182,24 @@ namespace NzbDrone.Core.MediaFiles _mediaFileTableCleanupService.Clean(series, mediaFileList); } + private void CleanMediaFiles(Artist artist, List mediaFileList) + { + _logger.Debug("{0} Cleaning up media files in DB", artist); + _mediaFileTableCleanupService.Clean(artist, mediaFileList); + } + private void CompletedScanning(Series series) { _logger.Info("Completed scanning disk for {0}", series.Title); _eventAggregator.PublishEvent(new SeriesScannedEvent(series)); } + private void CompletedScanning(Artist artist) + { + _logger.Info("Completed scanning disk for {0}", artist.ArtistName); + _eventAggregator.PublishEvent(new ArtistScannedEvent(artist)); + } + public string[] GetVideoFiles(string path, bool allDirectories = true) { _logger.Debug("Scanning '{0}' for video files", path); @@ -143,9 +215,24 @@ namespace NzbDrone.Core.MediaFiles return mediaFileList.ToArray(); } + public string[] GetMusicFiles(string path, bool allDirectories = true) + { + _logger.Debug("Scanning '{0}' for music files", path); + + var searchOption = allDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; + var filesOnDisk = _diskProvider.GetFiles(path, searchOption).ToList(); + + var mediaFileList = filesOnDisk.Where(file => MediaFileExtensions.Extensions.Contains(Path.GetExtension(file).ToLower())) + .ToList(); + + _logger.Trace("{0} files were found in {1}", filesOnDisk.Count, path); + _logger.Debug("{0} video files were found in {1}", mediaFileList.Count, path); + return mediaFileList.ToArray(); + } + public string[] GetNonVideoFiles(string path, bool allDirectories = true) { - _logger.Debug("Scanning '{0}' for non-video files", path); + _logger.Debug("Scanning '{0}' for non-music files", path); var searchOption = allDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var filesOnDisk = _diskProvider.GetFiles(path, searchOption).ToList(); @@ -154,7 +241,7 @@ namespace NzbDrone.Core.MediaFiles .ToList(); _logger.Trace("{0} files were found in {1}", filesOnDisk.Count, path); - _logger.Debug("{0} non-video files were found in {1}", mediaFileList.Count, path); + _logger.Debug("{0} non-music files were found in {1}", mediaFileList.Count, path); return mediaFileList.ToArray(); } @@ -165,6 +252,13 @@ namespace NzbDrone.Core.MediaFiles .ToList(); } + public List FilterFiles(Artist artist, IEnumerable files) + { + return files.Where(file => !ExcludedSubFoldersRegex.IsMatch(artist.Path.GetRelativePath(file))) + .Where(file => !ExcludedFilesRegex.IsMatch(Path.GetFileName(file))) + .ToList(); + } + private void SetPermissions(string path) { if (!_configService.SetPermissionsLinux) @@ -191,6 +285,11 @@ namespace NzbDrone.Core.MediaFiles Scan(message.Series); } + public void Handle(ArtistUpdatedEvent message) + { + Scan(message.Artist); + } + public void Execute(RescanSeriesCommand message) { if (message.SeriesId.HasValue) @@ -209,5 +308,24 @@ namespace NzbDrone.Core.MediaFiles } } } + + public void Execute(RescanArtistCommand message) + { + if (message.ArtistId.IsNotNullOrWhiteSpace()) + { + var artist = _artistService.FindById(message.ArtistId); + Scan(artist); + } + + else + { + var allArtists = _artistService.GetAllArtists(); + + foreach (var artist in allArtists) + { + Scan(artist); + } + } + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs index 764e1b88f..23924686c 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs @@ -11,14 +11,16 @@ using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.MediaFiles.MediaInfo; - +using NzbDrone.Core.Music; namespace NzbDrone.Core.MediaFiles.EpisodeImport { public interface IMakeImportDecision { - List GetImportDecisions(List videoFiles, Series series); - List GetImportDecisions(List videoFiles, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource); + //List GetImportDecisions(List videoFiles, Series series); + List GetImportDecisions(List musicFiles, Artist artist); + //List GetImportDecisions(List videoFiles, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource); + List GetImportDecisions(List musicFiles, Artist artist, ParsedTrackInfo folderInfo); } public class ImportDecisionMaker : IMakeImportDecision @@ -48,65 +50,87 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport _logger = logger; } - public List GetImportDecisions(List videoFiles, Series series) + //public List GetImportDecisions(List videoFiles, Series series) + //{ + // return GetImportDecisions(videoFiles, series, null, false); + //} + + //public List GetImportDecisions(List videoFiles, Artist series, ParsedEpisodeInfo folderInfo, bool sceneSource) + //{ + // var newFiles = _mediaFileService.FilterExistingFiles(videoFiles.ToList(), series); + + // _logger.Debug("Analyzing {0}/{1} files.", newFiles.Count, videoFiles.Count()); + + // var shouldUseFolderName = ShouldUseFolderName(videoFiles, series, folderInfo); + // var decisions = new List(); + + // foreach (var file in newFiles) + // { + // decisions.AddIfNotNull(GetDecision(file, series, folderInfo, sceneSource, shouldUseFolderName)); + // } + + // return decisions; + //} + + public List GetImportDecisions(List musicFiles, Artist artist) { - return GetImportDecisions(videoFiles, series, null, false); + return GetImportDecisions(musicFiles, artist, null); } - public List GetImportDecisions(List videoFiles, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource) + public List GetImportDecisions(List musicFiles, Artist artist, ParsedTrackInfo folderInfo) { - var newFiles = _mediaFileService.FilterExistingFiles(videoFiles.ToList(), series); + var newFiles = _mediaFileService.FilterExistingFiles(musicFiles.ToList(), artist); - _logger.Debug("Analyzing {0}/{1} files.", newFiles.Count, videoFiles.Count()); + _logger.Debug("Analyzing {0}/{1} files.", newFiles.Count, musicFiles.Count()); - var shouldUseFolderName = ShouldUseFolderName(videoFiles, series, folderInfo); + var shouldUseFolderName = ShouldUseFolderName(musicFiles, artist, folderInfo); var decisions = new List(); foreach (var file in newFiles) { - decisions.AddIfNotNull(GetDecision(file, series, folderInfo, sceneSource, shouldUseFolderName)); + decisions.AddIfNotNull(GetDecision(file, artist, folderInfo, shouldUseFolderName)); } return decisions; } - private ImportDecision GetDecision(string file, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource, bool shouldUseFolderName) + private ImportDecision GetDecision(string file, Artist artist, ParsedTrackInfo folderInfo, bool sceneSource, bool shouldUseFolderName) { ImportDecision decision = null; try { - var localEpisode = _parsingService.GetLocalEpisode(file, series, shouldUseFolderName ? folderInfo : null, sceneSource); + var localTrack = _parsingService.GetLocalTrack(file, artist, shouldUseFolderName ? folderInfo : null); - if (localEpisode != null) + if (localTrack != null) { - localEpisode.Quality = GetQuality(folderInfo, localEpisode.Quality, series); - localEpisode.Size = _diskProvider.GetFileSize(file); + localTrack.Quality = GetQuality(folderInfo, localTrack.Quality, artist); + localTrack.Size = _diskProvider.GetFileSize(file); - _logger.Debug("Size: {0}", localEpisode.Size); + _logger.Debug("Size: {0}", localTrack.Size); //TODO: make it so media info doesn't ruin the import process of a new series if (sceneSource) { - localEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(file); + localTrack.MediaInfo = _videoFileInfoReader.GetMediaInfo(file); } - if (localEpisode.Episodes.Empty()) + if (localTrack.Tracks.Empty()) { - decision = new ImportDecision(localEpisode, new Rejection("Invalid season or episode")); + decision = new ImportDecision(localTrack, new Rejection("Invalid album or track")); } else { - decision = GetDecision(localEpisode); + decision = GetDecision(localTrack); } } else { - localEpisode = new LocalEpisode(); - localEpisode.Path = file; + localTrack = new LocalTrack(); + localTrack.Path = file; - decision = new ImportDecision(localEpisode, new Rejection("Unable to parse file")); + decision = new ImportDecision(localTrack, new Rejection("Unable to parse file")); } } catch (Exception e) @@ -150,28 +174,28 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport return null; } - private bool ShouldUseFolderName(List videoFiles, Series series, ParsedEpisodeInfo folderInfo) + private bool ShouldUseFolderName(List musicFiles, Artist artist, ParsedTrackInfo folderInfo) { if (folderInfo == null) { return false; } - if (folderInfo.FullSeason) - { - return false; - } + //if (folderInfo.FullSeason) + //{ + // return false; + //} - return videoFiles.Count(file => + return musicFiles.Count(file => { var size = _diskProvider.GetFileSize(file); var fileQuality = QualityParser.ParseQuality(file); - var sample = _detectSample.IsSample(series, GetQuality(folderInfo, fileQuality, series), file, size, folderInfo.IsPossibleSpecialEpisode); + //var sample = _detectSample.IsSample(artist, GetQuality(folderInfo, fileQuality, artist), file, size, folderInfo.IsPossibleSpecialEpisode); - if (sample) - { - return false; - } + //if (sample) + //{ + // return false; + //} if (SceneChecker.IsSceneTitle(Path.GetFileName(file))) { @@ -182,9 +206,9 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport }) == 1; } - private QualityModel GetQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQuality, Series series) + private QualityModel GetQuality(ParsedTrackInfo folderInfo, QualityModel fileQuality, Artist artist) { - if (UseFolderQuality(folderInfo, fileQuality, series)) + if (UseFolderQuality(folderInfo, fileQuality, artist)) { _logger.Debug("Using quality from folder: {0}", folderInfo.Quality); return folderInfo.Quality; @@ -193,7 +217,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport return fileQuality; } - private bool UseFolderQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQuality, Series series) + private bool UseFolderQuality(ParsedTrackInfo folderInfo, QualityModel fileQuality, Artist artist) { if (folderInfo == null) { @@ -210,7 +234,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport return true; } - if (new QualityModelComparer(series.Profile).Compare(folderInfo.Quality, fileQuality) > 0) + if (new QualityModelComparer(artist.Profile).Compare(folderInfo.Quality, fileQuality) > 0) { return true; } diff --git a/src/NzbDrone.Core/MediaFiles/Events/ArtistScanSkippedEvent.cs b/src/NzbDrone.Core/MediaFiles/Events/ArtistScanSkippedEvent.cs new file mode 100644 index 000000000..5188ffd07 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/Events/ArtistScanSkippedEvent.cs @@ -0,0 +1,27 @@ +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Music; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MediaFiles.Events +{ + public class ArtistScanSkippedEvent : IEvent + { + public Artist Artist { get; private set; } + public ArtistScanSkippedReason Reason { get; private set; } + + public ArtistScanSkippedEvent(Artist artist, ArtistScanSkippedReason reason) + { + Artist = artist; + Reason = reason; + } + } + + public enum ArtistScanSkippedReason + { + RootFolderDoesNotExist, + RootFolderIsEmpty + } +} diff --git a/src/NzbDrone.Core/MediaFiles/Events/ArtistScannedEvent.cs b/src/NzbDrone.Core/MediaFiles/Events/ArtistScannedEvent.cs new file mode 100644 index 000000000..63f5f4656 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/Events/ArtistScannedEvent.cs @@ -0,0 +1,19 @@ +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Music; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MediaFiles.Events +{ + public class ArtistScannedEvent : IEvent + { + public Artist Artist { get; private set; } + + public ArtistScannedEvent(Artist artist) + { + Artist = artist; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs index 206942356..9cd818dcb 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; @@ -5,36 +6,28 @@ using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.MediaFiles { - public interface IMediaFileRepository : IBasicRepository + public interface IMediaFileRepository : IBasicRepository { - List GetFilesBySeries(int seriesId); - List GetFilesBySeason(int seriesId, int seasonNumber); - List GetFilesWithoutMediaInfo(); + List GetFilesByArtist(string artistId); + List GetFilesWithoutMediaInfo(); } - public class MediaFileRepository : BasicRepository, IMediaFileRepository + public class MediaFileRepository : BasicRepository, IMediaFileRepository { public MediaFileRepository(IMainDatabase database, IEventAggregator eventAggregator) : base(database, eventAggregator) { } - public List GetFilesBySeries(int seriesId) + public List GetFilesWithoutMediaInfo() { - return Query.Where(c => c.SeriesId == seriesId).ToList(); - } - - public List GetFilesBySeason(int seriesId, int seasonNumber) - { - return Query.Where(c => c.SeriesId == seriesId) - .AndWhere(c => c.SeasonNumber == seasonNumber) - .ToList(); + return Query.Where(c => c.MediaInfo == null).ToList(); } - public List GetFilesWithoutMediaInfo() + public List GetFilesByArtist(string artistId) { - return Query.Where(c => c.MediaInfo == null).ToList(); + return Query.Where(c => c.SpotifyTrackId == artistId).ToList(); } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs index ca3f68ce2..5c61ce535 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs @@ -7,24 +7,26 @@ using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv; using NzbDrone.Core.Tv.Events; using NzbDrone.Common; +using NzbDrone.Core.Music; +using System; +using NzbDrone.Core.Music.Events; namespace NzbDrone.Core.MediaFiles { public interface IMediaFileService { - EpisodeFile Add(EpisodeFile episodeFile); - void Update(EpisodeFile episodeFile); - void Delete(EpisodeFile episodeFile, DeleteMediaFileReason reason); - List GetFilesBySeries(int seriesId); - List GetFilesBySeason(int seriesId, int seasonNumber); - List GetFilesWithoutMediaInfo(); - List FilterExistingFiles(List files, Series series); - EpisodeFile Get(int id); - List Get(IEnumerable ids); + TrackFile Add(TrackFile trackFile); + void Update(TrackFile trackFile); + void Delete(TrackFile trackFile, DeleteMediaFileReason reason); + List GetFilesByArtist(string artistId); + List GetFilesWithoutMediaInfo(); + List FilterExistingFiles(List files, Artist artist); + TrackFile Get(int id); + List Get(IEnumerable ids); } - public class MediaFileService : IMediaFileService, IHandleAsync + public class MediaFileService : IMediaFileService, IHandleAsync { private readonly IEventAggregator _eventAggregator; private readonly IMediaFileRepository _mediaFileRepository; @@ -37,66 +39,63 @@ namespace NzbDrone.Core.MediaFiles _logger = logger; } - public EpisodeFile Add(EpisodeFile episodeFile) + public TrackFile Add(TrackFile trackFile) { - var addedFile = _mediaFileRepository.Insert(episodeFile); - _eventAggregator.PublishEvent(new EpisodeFileAddedEvent(addedFile)); + var addedFile = _mediaFileRepository.Insert(trackFile); + _eventAggregator.PublishEvent(new TrackFileAddedEvent(addedFile)); return addedFile; } - public void Update(EpisodeFile episodeFile) + public void Update(TrackFile trackFile) { - _mediaFileRepository.Update(episodeFile); + _mediaFileRepository.Update(trackFile); } - public void Delete(EpisodeFile episodeFile, DeleteMediaFileReason reason) + public void Delete(TrackFile trackFile, DeleteMediaFileReason reason) { - //Little hack so we have the episodes and series attached for the event consumers - episodeFile.Episodes.LazyLoad(); - episodeFile.Path = Path.Combine(episodeFile.Series.Value.Path, episodeFile.RelativePath); + //Little hack so we have the tracks and artist attached for the event consumers + trackFile.Episodes.LazyLoad(); + trackFile.Path = Path.Combine(trackFile.Artist.Value.Path, trackFile.RelativePath); - _mediaFileRepository.Delete(episodeFile); - _eventAggregator.PublishEvent(new EpisodeFileDeletedEvent(episodeFile, reason)); + _mediaFileRepository.Delete(trackFile); + _eventAggregator.PublishEvent(new TrackFileDeletedEvent(trackFile, reason)); } - public List GetFilesBySeries(int seriesId) - { - return _mediaFileRepository.GetFilesBySeries(seriesId); - } - - public List GetFilesBySeason(int seriesId, int seasonNumber) - { - return _mediaFileRepository.GetFilesBySeason(seriesId, seasonNumber); - } - public List GetFilesWithoutMediaInfo() + public List GetFilesWithoutMediaInfo() { return _mediaFileRepository.GetFilesWithoutMediaInfo(); } - public List FilterExistingFiles(List files, Series series) + public List FilterExistingFiles(List files, Artist artist) { - var seriesFiles = GetFilesBySeries(series.Id).Select(f => Path.Combine(series.Path, f.RelativePath)).ToList(); + var artistFiles = GetFilesByArtist(artist.SpotifyId).Select(f => Path.Combine(artist.Path, f.RelativePath)).ToList(); - if (!seriesFiles.Any()) return files; + if (!artistFiles.Any()) return files; - return files.Except(seriesFiles, PathEqualityComparer.Instance).ToList(); + return files.Except(artistFiles, PathEqualityComparer.Instance).ToList(); } - public EpisodeFile Get(int id) + public TrackFile Get(int id) { + // TODO: Should this be spotifyID or DB Id? return _mediaFileRepository.Get(id); } - public List Get(IEnumerable ids) + public List Get(IEnumerable ids) { return _mediaFileRepository.Get(ids).ToList(); } - public void HandleAsync(SeriesDeletedEvent message) + public void HandleAsync(ArtistDeletedEvent message) { - var files = GetFilesBySeries(message.Series.Id); + var files = GetFilesByArtist(message.Artist.SpotifyId); _mediaFileRepository.DeleteMany(files); } + + public List GetFilesByArtist(string artistId) + { + return _mediaFileRepository.GetFilesByArtist(artistId); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaFiles/TrackFile.cs b/src/NzbDrone.Core/MediaFiles/TrackFile.cs index 9c2df9875..e08555b77 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackFile.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackFile.cs @@ -12,7 +12,7 @@ namespace NzbDrone.Core.MediaFiles { public class TrackFile : ModelBase { - public int ItunesTrackId { get; set; } + public string SpotifyTrackId { get; set; } public int AlbumId { get; set; } public string RelativePath { get; set; } public string Path { get; set; } diff --git a/src/NzbDrone.Core/Music/ArtistService.cs b/src/NzbDrone.Core/Music/ArtistService.cs index bedf41f74..f6f45cc8f 100644 --- a/src/NzbDrone.Core/Music/ArtistService.cs +++ b/src/NzbDrone.Core/Music/ArtistService.cs @@ -89,11 +89,12 @@ namespace NzbDrone.Core.Music return _artistRepository.All().ToList(); } - public Artist GetArtist(int artistId) + public Artist GetArtist(int artistDBId) { - return _artistRepository.Get(artistId); + return _artistRepository.Get(artistDBId); } + public List GetArtists(IEnumerable artistIds) { return _artistRepository.Get(artistIds).ToList(); diff --git a/src/NzbDrone.Core/Music/RefreshArtistService.cs b/src/NzbDrone.Core/Music/RefreshArtistService.cs index 314597c63..0eae8692f 100644 --- a/src/NzbDrone.Core/Music/RefreshArtistService.cs +++ b/src/NzbDrone.Core/Music/RefreshArtistService.cs @@ -157,7 +157,7 @@ namespace NzbDrone.Core.Music try { _logger.Info("Skipping refresh of artist: {0}", artist.ArtistName); - //TODO: _diskScanService.Scan(artist); + _diskScanService.Scan(artist); } catch (Exception e) { diff --git a/src/NzbDrone.Core/Parser/Model/ArtistTitleInfo.cs b/src/NzbDrone.Core/Parser/Model/ArtistTitleInfo.cs new file mode 100644 index 000000000..0d1938d24 --- /dev/null +++ b/src/NzbDrone.Core/Parser/Model/ArtistTitleInfo.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Parser.Model +{ + public class ArtistTitleInfo + { + public string Title { get; set; } + public string TitleWithoutYear { get; set; } + public int Year { get; set; } + } +} diff --git a/src/NzbDrone.Core/Parser/Model/ParsedTrackInfo.cs b/src/NzbDrone.Core/Parser/Model/ParsedTrackInfo.cs index 53cb470c9..f05090f9b 100644 --- a/src/NzbDrone.Core/Parser/Model/ParsedTrackInfo.cs +++ b/src/NzbDrone.Core/Parser/Model/ParsedTrackInfo.cs @@ -9,87 +9,34 @@ namespace NzbDrone.Core.Parser.Model { public class ParsedTrackInfo { - // [TODO]: Properly fill this out public string ArtistTitle { get; set; } public string AlbumTitle { get; set; } - public SeriesTitleInfo SeriesTitleInfo { get; set; } + public ArtistTitleInfo ArtistTitleInfo { get; set; } public QualityModel Quality { get; set; } - public int SeasonNumber { get; set; } - public int[] EpisodeNumbers { get; set; } - public int[] AbsoluteEpisodeNumbers { get; set; } - public string AirDate { get; set; } - public Language Language { get; set; } - public bool FullSeason { get; set; } - public bool Special { get; set; } + public string AlbumId { get; set; } // maybe + public int[] TrackNumbers { get; set; } + //public Language Language { get; set; } public string ReleaseGroup { get; set; } public string ReleaseHash { get; set; } public ParsedTrackInfo() { - EpisodeNumbers = new int[0]; - AbsoluteEpisodeNumbers = new int[0]; + TrackNumbers = new int[0]; } - public bool IsDaily - { - get - { - return !string.IsNullOrWhiteSpace(AirDate); - } - //This prevents manually downloading a release from blowing up in mono - //TODO: Is there a better way? - private set { } - } - public bool IsAbsoluteNumbering - { - get - { - return AbsoluteEpisodeNumbers.Any(); - } - - //This prevents manually downloading a release from blowing up in mono - //TODO: Is there a better way? - private set { } - } - - public bool IsPossibleSpecialEpisode - { - get - { - // if we don't have eny episode numbers we are likely a special episode and need to do a search by episode title - return (AirDate.IsNullOrWhiteSpace() && - ArtistTitle.IsNullOrWhiteSpace() && - (EpisodeNumbers.Length == 0 || SeasonNumber == 0) || - !ArtistTitle.IsNullOrWhiteSpace() && Special); - } - - //This prevents manually downloading a release from blowing up in mono - //TODO: Is there a better way? - private set { } - } public override string ToString() { - string episodeString = "[Unknown Episode]"; + string episodeString = "[Unknown Track]"; - if (IsDaily && EpisodeNumbers.Empty()) - { - episodeString = string.Format("{0}", AirDate); - } - else if (FullSeason) - { - episodeString = string.Format("Season {0:00}", SeasonNumber); - } - else if (EpisodeNumbers != null && EpisodeNumbers.Any()) - { - episodeString = string.Format("S{0:00}E{1}", SeasonNumber, string.Join("-", EpisodeNumbers.Select(c => c.ToString("00")))); - } - else if (AbsoluteEpisodeNumbers != null && AbsoluteEpisodeNumbers.Any()) + + if (TrackNumbers != null && TrackNumbers.Any()) { - episodeString = string.Format("{0}", string.Join("-", AbsoluteEpisodeNumbers.Select(c => c.ToString("000")))); + episodeString = string.Format("T{1}", string.Join("-", TrackNumbers.Select(c => c.ToString("00")))); } + return string.Format("{0} - {1} {2}", ArtistTitle, episodeString, Quality); } diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index 627bd6df1..333848512 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -25,7 +25,7 @@ namespace NzbDrone.Core.Parser // Music stuff here LocalTrack GetLocalTrack(string filename, Artist artist); - LocalTrack GetLocalTrack(string filename, Artist artist, ParsedTrackInfo folderInfo, bool sceneSource); + LocalTrack GetLocalTrack(string filename, Artist artist, ParsedTrackInfo folderInfo); } @@ -40,12 +40,14 @@ namespace NzbDrone.Core.Parser public ParsingService(IEpisodeService episodeService, ISeriesService seriesService, + ITrackService trackService, // ISceneMappingService sceneMappingService, Logger logger) { _episodeService = episodeService; _seriesService = seriesService; // _sceneMappingService = sceneMappingService; + _trackService = trackService; _logger = logger; } @@ -407,6 +409,73 @@ namespace NzbDrone.Core.Parser return result; } + private List GetStandardTracks(Artist artist, ParsedTrackInfo parsedTrackInfo, SearchCriteriaBase searchCriteria) + { + var result = new List(); + + if (parsedTrackInfo.TrackNumbers == null) + { + return result; + } + + foreach (var trackNumber in parsedTrackInfo.TrackNumbers) + { + //if (series.UseSceneNumbering && sceneSource) + //{ + // List episodes = new List(); + + // if (searchCriteria != null) + // { + // episodes = searchCriteria.Episodes.Where(e => e.SceneSeasonNumber == parsedTrackInfo.SeasonNumber && + // e.SceneEpisodeNumber == trackNumber).ToList(); + // } + + // if (!episodes.Any()) + // { + // episodes = _episodeService.FindEpisodesBySceneNumbering(series.Id, seasonNumber, trackNumber); + // } + + // if (episodes != null && episodes.Any()) + // { + // _logger.Debug("Using Scene to TVDB Mapping for: {0} - Scene: {1}x{2:00} - TVDB: {3}", + // series.Title, + // episodes.First().SceneSeasonNumber, + // episodes.First().SceneEpisodeNumber, + // string.Join(", ", episodes.Select(e => string.Format("{0}x{1:00}", e.SeasonNumber, e.EpisodeNumber)))); + + // result.AddRange(episodes); + // continue; + // } + //} + + Track trackInfo = null; + + if (searchCriteria != null) + { + trackInfo = searchCriteria.Tracks.SingleOrDefault(e => e.TrackNumber == trackNumber); //e => e.SeasonNumber == seasonNumber && e.TrackNumber == trackNumber + } + + if (trackInfo == null) + { + trackInfo = _trackService.FindTrack(artist.SpotifyId, trackNumber); + } + + if (trackInfo != null) + { + result.Add(trackInfo); + } + + else + { + _logger.Debug("Unable to find {0}", parsedEpisodeInfo); + } + } + + + + return result; + } + private List GetStandardEpisodes(Series series, ParsedEpisodeInfo parsedEpisodeInfo, bool sceneSource, SearchCriteriaBase searchCriteria) { var result = new List(); @@ -486,10 +555,10 @@ namespace NzbDrone.Core.Parser public LocalTrack GetLocalTrack(string filename, Artist artist) { - return GetLocalTrack(filename, artist, null, false); + return GetLocalTrack(filename, artist, null); } - public LocalTrack GetLocalTrack(string filename, Artist artist, ParsedTrackInfo folderInfo, bool sceneSource) + public LocalTrack GetLocalTrack(string filename, Artist artist, ParsedTrackInfo folderInfo) { ParsedTrackInfo parsedTrackInfo; @@ -504,7 +573,7 @@ namespace NzbDrone.Core.Parser parsedTrackInfo = Parser.ParseMusicPath(filename); } - if (parsedTrackInfo == null || parsedTrackInfo.IsPossibleSpecialEpisode) + if (parsedTrackInfo == null) { var title = Path.GetFileNameWithoutExtension(filename); //var specialEpisodeInfo = ParseSpecialEpisodeTitle(title, series); @@ -525,7 +594,7 @@ namespace NzbDrone.Core.Parser return null; } - var tracks = GetTracks(parsedTrackInfo, artist, sceneSource); + var tracks = GetTracks(parsedTrackInfo, artist); return new LocalTrack { @@ -538,10 +607,10 @@ namespace NzbDrone.Core.Parser }; } - private List GetTracks(ParsedTrackInfo parsedTrackInfo, Artist artist, bool sceneSource) + private List GetTracks(ParsedTrackInfo parsedTrackInfo, Artist artist) { - throw new NotImplementedException(); + // TODO: Ensure GetTracks(parsedTrackInfo, artist) doesn't need any checks /*if (parsedTrackInfo.FullSeason) // IF Album { return _trackService.GetTracksByAlbumTitle(artist.Id, parsedTrackInfo.AlbumTitle); @@ -566,6 +635,7 @@ namespace NzbDrone.Core.Parser } return GetStandardEpisodes(artist, parsedTrackInfo, sceneSource, searchCriteria);*/ + return GetStandardTracks(artist, parsedTrackInfo, searchCriteria); } } } \ No newline at end of file diff --git a/src/UI/Controller.js b/src/UI/Controller.js index f1e4032ab..a1e686a75 100644 --- a/src/UI/Controller.js +++ b/src/UI/Controller.js @@ -10,6 +10,7 @@ var ReleaseLayout = require('./Release/ReleaseLayout'); var SystemLayout = require('./System/SystemLayout'); var SeasonPassLayout = require('./SeasonPass/SeasonPassLayout'); var SeriesEditorLayout = require('./Series/Editor/SeriesEditorLayout'); +var SeriesDetailsLayout = require('./Series/Details/SeriesDetailsLayout'); module.exports = NzbDroneController.extend({ addSeries : function(action) { @@ -17,6 +18,11 @@ module.exports = NzbDroneController.extend({ this.showMainRegion(new AddSeriesLayout({ action : action })); }, + artistDetails: function(query) { + this.setTitle('Artist Detail'); + this.showMainRegion(new SeriesDetailsLayout()); + }, + calendar : function() { this.setTitle('Calendar'); this.showMainRegion(new CalendarLayout()); diff --git a/src/UI/Router.js b/src/UI/Router.js index 91b42a074..5aad3de5c 100644 --- a/src/UI/Router.js +++ b/src/UI/Router.js @@ -18,6 +18,7 @@ module.exports = Marionette.AppRouter.extend({ 'rss' : 'rss', 'system' : 'system', 'system/:action' : 'system', + 'artist/:query' : 'artistDetails', 'seasonpass' : 'seasonPass', 'serieseditor' : 'seriesEditor', ':whatever' : 'showNotFound' diff --git a/src/UI/Series/Details/SeriesDetailsLayout.js b/src/UI/Series/Details/SeriesDetailsLayout.js index b1b004f9b..4b3d34297 100644 --- a/src/UI/Series/Details/SeriesDetailsLayout.js +++ b/src/UI/Series/Details/SeriesDetailsLayout.js @@ -48,9 +48,10 @@ module.exports = Marionette.Layout.extend({ this.seriesCollection = ArtistCollection.clone(); this.seriesCollection.shadowCollection.bindSignalR(); + this.listenTo(this.model, 'change:monitored', this._setMonitoredState); this.listenTo(this.model, 'remove', this._seriesRemoved); - this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete); + //this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete); this.listenTo(this.model, 'change', function(model, options) { if (options && options.changeSource === 'signalr') { @@ -59,6 +60,7 @@ module.exports = Marionette.Layout.extend({ }); this.listenTo(this.model, 'change:images', this._updateImages); + }, onShow : function() { @@ -81,15 +83,16 @@ module.exports = Marionette.Layout.extend({ name : 'seriesSearch' } }); + console.log(this.model); - CommandController.bindToCommand({ + /*CommandController.bindToCommand({ element : this.ui.rename, command : { name : 'renameFiles', - seriesId : this.model.id, + seriesId : this.model.spotifyId, seasonNumber : -1 } - }); + });*/ }, onClose : function() { @@ -164,6 +167,7 @@ module.exports = Marionette.Layout.extend({ _showSeasons : function() { var self = this; + return; this.seasons.show(new LoadingView()); diff --git a/src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.hbs b/src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.hbs index 19da3e335..6fb80ec50 100644 --- a/src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.hbs +++ b/src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.hbs @@ -8,7 +8,7 @@
From 1024555f7530d7be6ca76ea18adb63007eb7f695 Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Mon, 29 May 2017 12:09:18 -0500 Subject: [PATCH 02/13] At a point where we can build. Many TODOs and existing Series-based APIs need to be removed. No track code actually works. --- .../EpisodeFiles/EpisodeFileModule.cs | 38 +-- src/NzbDrone.Api/Music/ArtistModule.cs | 4 +- .../Download/CompletedDownloadService.cs | 3 +- src/NzbDrone.Core/Extras/ExtraService.cs | 18 +- .../Commands/DownloadedEpisodesScanCommand.cs | 1 + .../MediaFiles/DiskScanService.cs | 104 +------ .../DownloadedEpisodesCommandService.cs | 1 + .../DownloadedEpisodesImportService.cs | 150 +++++----- .../IImportDecisionEngineSpecification.cs | 1 + .../EpisodeImport/ImportApprovedEpisodes.cs | 102 +------ .../EpisodeImport/ImportDecision.cs | 22 -- .../EpisodeImport/ImportDecisionMaker.cs | 41 ++- .../Manual/ManualImportCommand.cs | 1 + .../Manual/ManualImportService.cs | 281 +++++++++--------- .../Manual/ManuallyImportedFile.cs | 1 + .../Specifications/FreeSpaceSpecification.cs | 43 +++ .../Specifications/FullSeasonSpecification.cs | 8 +- .../MatchesFolderSpecification.cs | 50 +++- .../Specifications/NotSampleSpecification.cs | 8 +- .../NotUnpackingSpecification.cs | 5 + .../SameEpisodesImportSpecification.cs | 6 + .../UnverifiedSceneNumberingSpecification.cs | 8 +- .../Specifications/UpgradeSpecification.cs | 15 +- .../MediaFiles/Events/TrackDownloadedEvent.cs | 23 ++ .../MediaFileTableCleanupService.cs | 54 ++-- .../MediaInfo/UpdateMediaInfoService.cs | 13 +- .../MediaFiles/RenameEpisodeFileService.cs | 134 +++++---- .../MediaFiles/TrackFileMoveResult.cs | 18 ++ .../MediaFiles/TrackFileMovingService.cs | 226 ++++++++++++++ .../TrackImport/ImportApprovedTracks.cs | 172 +++++++++++ .../MediaFiles/TrackImport/ImportDecision.cs | 26 ++ .../ImportMode.cs | 2 +- .../ImportResult.cs | 8 +- .../ImportResultType.cs | 2 +- .../MediaFiles/UpgradeMediaFileService.cs | 26 +- .../MetadataSource/SkyHook/SkyHookProxy.cs | 2 +- src/NzbDrone.Core/Music/TrackService.cs | 16 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 16 +- src/NzbDrone.Core/Parser/Parser.cs | 39 +-- src/NzbDrone.Core/Parser/ParsingService.cs | 62 +++- src/NzbDrone.Core/Tv/RefreshSeriesService.cs | 2 +- 41 files changed, 1130 insertions(+), 622 deletions(-) delete mode 100644 src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecision.cs create mode 100644 src/NzbDrone.Core/MediaFiles/Events/TrackDownloadedEvent.cs create mode 100644 src/NzbDrone.Core/MediaFiles/TrackFileMoveResult.cs create mode 100644 src/NzbDrone.Core/MediaFiles/TrackFileMovingService.cs create mode 100644 src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs create mode 100644 src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecision.cs rename src/NzbDrone.Core/MediaFiles/{EpisodeImport => TrackImport}/ImportMode.cs (66%) rename src/NzbDrone.Core/MediaFiles/{EpisodeImport => TrackImport}/ImportResult.cs (85%) rename src/NzbDrone.Core/MediaFiles/{EpisodeImport => TrackImport}/ImportResultType.cs (66%) diff --git a/src/NzbDrone.Api/EpisodeFiles/EpisodeFileModule.cs b/src/NzbDrone.Api/EpisodeFiles/EpisodeFileModule.cs index c2044bda3..d89a0068c 100644 --- a/src/NzbDrone.Api/EpisodeFiles/EpisodeFileModule.cs +++ b/src/NzbDrone.Api/EpisodeFiles/EpisodeFileModule.cs @@ -11,6 +11,7 @@ using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv; using NzbDrone.Core.DecisionEngine; using NzbDrone.SignalR; +using System; namespace NzbDrone.Api.EpisodeFiles { @@ -47,24 +48,26 @@ namespace NzbDrone.Api.EpisodeFiles private EpisodeFileResource GetEpisodeFile(int id) { - var episodeFile = _mediaFileService.Get(id); - var series = _seriesService.GetSeries(episodeFile.SeriesId); + throw new NotImplementedException(); + //var episodeFile = _mediaFileService.Get(id); + //var series = _seriesService.GetSeries(episodeFile.SeriesId); - return episodeFile.ToResource(series, _qualityUpgradableSpecification); + //return episodeFile.ToResource(series, _qualityUpgradableSpecification); } private List GetEpisodeFiles() { - if (!Request.Query.SeriesId.HasValue) - { - throw new BadRequestException("seriesId is missing"); - } + throw new NotImplementedException(); + //if (!Request.Query.SeriesId.HasValue) + //{ + // throw new BadRequestException("seriesId is missing"); + //} - var seriesId = (int)Request.Query.SeriesId; + //var seriesId = (int)Request.Query.SeriesId; - var series = _seriesService.GetSeries(seriesId); + //var series = _seriesService.GetSeries(seriesId); - return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _qualityUpgradableSpecification)); + //return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _qualityUpgradableSpecification)); } private void SetQuality(EpisodeFileResource episodeFileResource) @@ -76,14 +79,15 @@ namespace NzbDrone.Api.EpisodeFiles private void DeleteEpisodeFile(int id) { - var episodeFile = _mediaFileService.Get(id); - var series = _seriesService.GetSeries(episodeFile.SeriesId); - var fullPath = Path.Combine(series.Path, episodeFile.RelativePath); - var subfolder = _diskProvider.GetParentFolder(series.Path).GetRelativePath(_diskProvider.GetParentFolder(fullPath)); + throw new NotImplementedException(); + //var episodeFile = _mediaFileService.Get(id); + //var series = _seriesService.GetSeries(episodeFile.SeriesId); + //var fullPath = Path.Combine(series.Path, episodeFile.RelativePath); + //var subfolder = _diskProvider.GetParentFolder(series.Path).GetRelativePath(_diskProvider.GetParentFolder(fullPath)); - _logger.Info("Deleting episode file: {0}", fullPath); - _recycleBinProvider.DeleteFile(fullPath, subfolder); - _mediaFileService.Delete(episodeFile, DeleteMediaFileReason.Manual); + //_logger.Info("Deleting episode file: {0}", fullPath); + //_recycleBinProvider.DeleteFile(fullPath, subfolder); + //_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.Manual); } public void Handle(EpisodeFileAddedEvent message) diff --git a/src/NzbDrone.Api/Music/ArtistModule.cs b/src/NzbDrone.Api/Music/ArtistModule.cs index d7ef5fed3..61acceb0e 100644 --- a/src/NzbDrone.Api/Music/ArtistModule.cs +++ b/src/NzbDrone.Api/Music/ArtistModule.cs @@ -144,14 +144,14 @@ namespace NzbDrone.Api.Music public void Handle(TrackImportedEvent message) { - BroadcastResourceChange(ModelAction.Updated, message.ImportedTrack.ItunesTrackId); + BroadcastResourceChange(ModelAction.Updated, message.ImportedTrack.Id); // TODO: Ensure we can pass DB ID instead of Metadata ID (SpotifyID) } public void Handle(TrackFileDeletedEvent message) { if (message.Reason == DeleteMediaFileReason.Upgrade) return; - BroadcastResourceChange(ModelAction.Updated, message.TrackFile.ItunesTrackId); + BroadcastResourceChange(ModelAction.Updated, message.TrackFile.Id); // TODO: Ensure we can pass DB ID instead of Metadata ID (SpotifyID) } public void Handle(ArtistUpdatedEvent message) diff --git a/src/NzbDrone.Core/Download/CompletedDownloadService.cs b/src/NzbDrone.Core/Download/CompletedDownloadService.cs index 4e46b3729..d9a48965c 100644 --- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs +++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs @@ -13,6 +13,7 @@ using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser; using NzbDrone.Core.Tv; +using NzbDrone.Core.MediaFiles.TrackImport; namespace NzbDrone.Core.Download { @@ -130,7 +131,7 @@ namespace NzbDrone.Core.Download { var statusMessages = importResults .Where(v => v.Result != ImportResultType.Imported) - .Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), v.Errors)) + .Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalTrack.Path), v.Errors)) .ToArray(); trackedDownload.Warn(statusMessages); diff --git a/src/NzbDrone.Core/Extras/ExtraService.cs b/src/NzbDrone.Core/Extras/ExtraService.cs index 811d3ebea..7447e162e 100644 --- a/src/NzbDrone.Core/Extras/ExtraService.cs +++ b/src/NzbDrone.Core/Extras/ExtraService.cs @@ -16,6 +16,7 @@ using NzbDrone.Core.Tv; namespace NzbDrone.Core.Extras { + // NOTE: Majora: ExtraService can be reserved for Music Videos, lyric files, etc for Plex. TODO: Implement Extras for Music public interface IExtraService { void ImportExtraFiles(LocalEpisode localEpisode, EpisodeFile episodeFile, bool isReadOnly); @@ -136,16 +137,17 @@ namespace NzbDrone.Core.Extras private List GetEpisodeFiles(int seriesId) { - var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId); - var episodes = _episodeService.GetEpisodeBySeries(seriesId); + //var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId); + //var episodes = _episodeService.GetEpisodeBySeries(seriesId); - foreach (var episodeFile in episodeFiles) - { - var localEpisodeFile = episodeFile; - episodeFile.Episodes = new LazyList(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id)); - } + //foreach (var episodeFile in episodeFiles) + //{ + // var localEpisodeFile = episodeFile; + // episodeFile.Episodes = new LazyList(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id)); + //} - return episodeFiles; + //return episodeFiles; + return new List(); } } } diff --git a/src/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs index ab2f80480..70befbb18 100644 --- a/src/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs +++ b/src/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs @@ -1,4 +1,5 @@ using NzbDrone.Core.MediaFiles.EpisodeImport; +using NzbDrone.Core.MediaFiles.TrackImport; using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.MediaFiles.Commands diff --git a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs index 60c929956..773b94519 100644 --- a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs +++ b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs @@ -18,12 +18,12 @@ using NzbDrone.Core.Tv; using NzbDrone.Core.Tv.Events; using NzbDrone.Core.Music; using NzbDrone.Core.Music.Events; +using NzbDrone.Core.MediaFiles.TrackImport; namespace NzbDrone.Core.MediaFiles { public interface IDiskScanService { - void Scan(Series series); void Scan(Artist artist); string[] GetVideoFiles(string path, bool allDirectories = true); string[] GetNonVideoFiles(string path, bool allDirectories = true); @@ -32,14 +32,12 @@ namespace NzbDrone.Core.MediaFiles public class DiskScanService : IDiskScanService, - IHandle, - IExecute, IHandle, IExecute { private readonly IDiskProvider _diskProvider; private readonly IMakeImportDecision _importDecisionMaker; - private readonly IImportApprovedEpisodes _importApprovedEpisodes; + private readonly IImportApprovedTracks _importApprovedTracks; private readonly IConfigService _configService; private readonly ISeriesService _seriesService; private readonly IArtistService _artistService; @@ -49,7 +47,7 @@ namespace NzbDrone.Core.MediaFiles public DiskScanService(IDiskProvider diskProvider, IMakeImportDecision importDecisionMaker, - IImportApprovedEpisodes importApprovedEpisodes, + IImportApprovedTracks importApprovedTracks, IConfigService configService, ISeriesService seriesService, IArtistService artistService, @@ -59,7 +57,7 @@ namespace NzbDrone.Core.MediaFiles { _diskProvider = diskProvider; _importDecisionMaker = importDecisionMaker; - _importApprovedEpisodes = importApprovedEpisodes; + _importApprovedTracks = importApprovedTracks; _configService = configService; _seriesService = seriesService; _artistService = artistService; @@ -123,76 +121,18 @@ namespace NzbDrone.Core.MediaFiles CompletedScanning(artist); } - public void Scan(Series series) - { - var rootFolder = _diskProvider.GetParentFolder(series.Path); - - if (!_diskProvider.FolderExists(rootFolder)) - { - _logger.Warn("Series' root folder ({0}) doesn't exist.", rootFolder); - _eventAggregator.PublishEvent(new SeriesScanSkippedEvent(series, SeriesScanSkippedReason.RootFolderDoesNotExist)); - return; - } - - if (_diskProvider.GetDirectories(rootFolder).Empty()) - { - _logger.Warn("Series' root folder ({0}) is empty.", rootFolder); - _eventAggregator.PublishEvent(new SeriesScanSkippedEvent(series, SeriesScanSkippedReason.RootFolderIsEmpty)); - return; - } - - _logger.ProgressInfo("Scanning disk for {0}", series.Title); - - if (!_diskProvider.FolderExists(series.Path)) - { - if (_configService.CreateEmptySeriesFolders) - { - _logger.Debug("Creating missing series folder: {0}", series.Path); - _diskProvider.CreateFolder(series.Path); - SetPermissions(series.Path); - } - else - { - _logger.Debug("Series folder doesn't exist: {0}", series.Path); - } - CleanMediaFiles(series, new List()); - CompletedScanning(series); - return; - } - - var videoFilesStopwatch = Stopwatch.StartNew(); - var mediaFileList = FilterFiles(series, GetVideoFiles(series.Path)).ToList(); - videoFilesStopwatch.Stop(); - _logger.Trace("Finished getting episode files for: {0} [{1}]", series, videoFilesStopwatch.Elapsed); - - CleanMediaFiles(series, mediaFileList); - - var decisionsStopwatch = Stopwatch.StartNew(); - var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, series); - decisionsStopwatch.Stop(); - _logger.Trace("Import decisions complete for: {0} [{1}]", series, decisionsStopwatch.Elapsed); - _importApprovedEpisodes.Import(decisions, false); - - CompletedScanning(series); - } - - private void CleanMediaFiles(Series series, List mediaFileList) - { - _logger.Debug("{0} Cleaning up media files in DB", series); - _mediaFileTableCleanupService.Clean(series, mediaFileList); - } - + private void CleanMediaFiles(Artist artist, List mediaFileList) { _logger.Debug("{0} Cleaning up media files in DB", artist); _mediaFileTableCleanupService.Clean(artist, mediaFileList); } - private void CompletedScanning(Series series) - { - _logger.Info("Completed scanning disk for {0}", series.Title); - _eventAggregator.PublishEvent(new SeriesScannedEvent(series)); - } + //private void CompletedScanning(Series series) + //{ + // _logger.Info("Completed scanning disk for {0}", series.Title); + // _eventAggregator.PublishEvent(new SeriesScannedEvent(series)); + //} private void CompletedScanning(Artist artist) { @@ -280,35 +220,11 @@ namespace NzbDrone.Core.MediaFiles } } - public void Handle(SeriesUpdatedEvent message) - { - Scan(message.Series); - } - public void Handle(ArtistUpdatedEvent message) { Scan(message.Artist); } - public void Execute(RescanSeriesCommand message) - { - if (message.SeriesId.HasValue) - { - var series = _seriesService.GetSeries(message.SeriesId.Value); - Scan(series); - } - - else - { - var allSeries = _seriesService.GetAllSeries(); - - foreach (var series in allSeries) - { - Scan(series); - } - } - } - public void Execute(RescanArtistCommand message) { if (message.ArtistId.IsNotNullOrWhiteSpace()) diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesCommandService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesCommandService.cs index 2f5e19a41..f3ded84b2 100644 --- a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesCommandService.cs +++ b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesCommandService.cs @@ -9,6 +9,7 @@ using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.MediaFiles.TrackImport; namespace NzbDrone.Core.MediaFiles { diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs index febc267a8..d5caf8fd2 100644 --- a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs @@ -9,6 +9,7 @@ using NzbDrone.Core.Parser; using NzbDrone.Core.Tv; using NzbDrone.Core.Download; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.MediaFiles.TrackImport; namespace NzbDrone.Core.MediaFiles { @@ -59,7 +60,7 @@ namespace NzbDrone.Core.MediaFiles results.AddRange(folderResults); } - foreach (var videoFile in _diskScanService.GetVideoFiles(directoryInfo.FullName, false)) + foreach (var videoFile in _diskScanService.GetNonVideoFiles(directoryInfo.FullName, false)) { var fileResults = ProcessFile(new FileInfo(videoFile), ImportMode.Auto, null); results.AddRange(fileResults); @@ -100,7 +101,7 @@ namespace NzbDrone.Core.MediaFiles public bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Series series) { - var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName); + var videoFiles = _diskScanService.GetNonVideoFiles(directoryInfo.FullName); var rarFiles = _diskProvider.GetFiles(directoryInfo.FullName, SearchOption.AllDirectories).Where(f => Path.GetExtension(f) == ".rar"); foreach (var videoFile in videoFiles) @@ -152,48 +153,50 @@ namespace NzbDrone.Core.MediaFiles private List ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, Series series, DownloadClientItem downloadClientItem) { - if (_seriesService.SeriesPathExists(directoryInfo.FullName)) - { - _logger.Warn("Unable to process folder that is mapped to an existing show"); - return new List(); - } - - var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name); - var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name); - - if (folderInfo != null) - { - _logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality); - } - - var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName); - - if (downloadClientItem == null) - { - foreach (var videoFile in videoFiles) - { - if (_diskProvider.IsFileLocked(videoFile)) - { - return new List - { - FileIsLockedResult(videoFile) - }; - } - } - } - - var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, folderInfo, true); - var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem, importMode); - - if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) && - importResults.Any(i => i.Result == ImportResultType.Imported) && - ShouldDeleteFolder(directoryInfo, series)) - { - _logger.Debug("Deleting folder after importing valid files"); - _diskProvider.DeleteFolder(directoryInfo.FullName, true); - } - - return importResults; + throw new System.NotImplementedException("Will be removed"); + + //if (_seriesService.SeriesPathExists(directoryInfo.FullName)) + //{ + // _logger.Warn("Unable to process folder that is mapped to an existing show"); + // return new List(); + //} + + //var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name); + //var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name); + + //if (folderInfo != null) + //{ + // _logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality); + //} + + //var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName); + + //if (downloadClientItem == null) + //{ + // foreach (var videoFile in videoFiles) + // { + // if (_diskProvider.IsFileLocked(videoFile)) + // { + // return new List + // { + // FileIsLockedResult(videoFile) + // }; + // } + // } + //} + + //var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, folderInfo, true); + //var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem, importMode); + + //if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) && + // importResults.Any(i => i.Result == ImportResultType.Imported) && + // ShouldDeleteFolder(directoryInfo, series)) + //{ + // _logger.Debug("Deleting folder after importing valid files"); + // _diskProvider.DeleteFolder(directoryInfo.FullName, true); + //} + + //return importResults; } private List ProcessFile(FileInfo fileInfo, ImportMode importMode, DownloadClientItem downloadClientItem) @@ -215,30 +218,31 @@ namespace NzbDrone.Core.MediaFiles private List ProcessFile(FileInfo fileInfo, ImportMode importMode, Series series, DownloadClientItem downloadClientItem) { - if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._")) - { - _logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName); - - return new List - { - new ImportResult(new ImportDecision(new LocalEpisode { Path = fileInfo.FullName }, new Rejection("Invalid video file, filename starts with '._'")), "Invalid video file, filename starts with '._'") - }; - } - - if (downloadClientItem == null) - { - if (_diskProvider.IsFileLocked(fileInfo.FullName)) - { - return new List - { - FileIsLockedResult(fileInfo.FullName) - }; - } - } - - var decisions = _importDecisionMaker.GetImportDecisions(new List() { fileInfo.FullName }, series, null, true); - - return _importApprovedEpisodes.Import(decisions, true, downloadClientItem, importMode); + throw new System.NotImplementedException("Will be removed"); + //if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._")) + //{ + // _logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName); + + // return new List + // { + // new ImportResult(new ImportDecision(new LocalTrack { Path = fileInfo.FullName }, new Rejection("Invalid music file, filename starts with '._'")), "Invalid music file, filename starts with '._'") + // }; + //} + + //if (downloadClientItem == null) + //{ + // if (_diskProvider.IsFileLocked(fileInfo.FullName)) + // { + // return new List + // { + // FileIsLockedResult(fileInfo.FullName) + // }; + // } + //} + + //var decisions = _importDecisionMaker.GetImportDecisions(new List() { fileInfo.FullName }, series, null, true); + + //return _importApprovedEpisodes.Import(decisions, true, downloadClientItem, importMode); } private string GetCleanedUpFolderName(string folder) @@ -251,15 +255,17 @@ namespace NzbDrone.Core.MediaFiles private ImportResult FileIsLockedResult(string videoFile) { - _logger.Debug("[{0}] is currently locked by another process, skipping", videoFile); - return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later"); + throw new System.NotImplementedException("Will be removed"); + //_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile); + //return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later"); } private ImportResult UnknownSeriesResult(string message, string videoFile = null) { - var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile }; + throw new System.NotImplementedException("Will be removed"); + //var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile }; - return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Series")), message); + //return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Series")), message); } } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/IImportDecisionEngineSpecification.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/IImportDecisionEngineSpecification.cs index 86abb87b7..50f491ca6 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/IImportDecisionEngineSpecification.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/IImportDecisionEngineSpecification.cs @@ -6,5 +6,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport public interface IImportDecisionEngineSpecification { Decision IsSatisfiedBy(LocalEpisode localEpisode); + Decision IsSatisfiedBy(LocalTrack localTrack); } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index cdfd289db..f3cc3404a 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -12,7 +12,7 @@ using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; using NzbDrone.Core.Download; using NzbDrone.Core.Extras; - +using NzbDrone.Core.MediaFiles.TrackImport; namespace NzbDrone.Core.MediaFiles.EpisodeImport { @@ -47,105 +47,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport public List Import(List decisions, bool newDownload, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto) { - var qualifiedImports = decisions.Where(c => c.Approved) - .GroupBy(c => c.LocalEpisode.Series.Id, (i, s) => s - .OrderByDescending(c => c.LocalEpisode.Quality, new QualityModelComparer(s.First().LocalEpisode.Series.Profile)) - .ThenByDescending(c => c.LocalEpisode.Size)) - .SelectMany(c => c) - .ToList(); - - var importResults = new List(); - - foreach (var importDecision in qualifiedImports.OrderBy(e => e.LocalEpisode.Episodes.Select(episode => episode.EpisodeNumber).MinOrDefault()) - .ThenByDescending(e => e.LocalEpisode.Size)) - { - var localEpisode = importDecision.LocalEpisode; - var oldFiles = new List(); - - try - { - //check if already imported - if (importResults.SelectMany(r => r.ImportDecision.LocalEpisode.Episodes) - .Select(e => e.Id) - .Intersect(localEpisode.Episodes.Select(e => e.Id)) - .Any()) - { - importResults.Add(new ImportResult(importDecision, "Episode has already been imported")); - continue; - } - - var episodeFile = new EpisodeFile(); - episodeFile.DateAdded = DateTime.UtcNow; - episodeFile.SeriesId = localEpisode.Series.Id; - episodeFile.Path = localEpisode.Path.CleanFilePath(); - episodeFile.Size = _diskProvider.GetFileSize(localEpisode.Path); - episodeFile.Quality = localEpisode.Quality; - episodeFile.MediaInfo = localEpisode.MediaInfo; - episodeFile.SeasonNumber = localEpisode.SeasonNumber; - episodeFile.Episodes = localEpisode.Episodes; - episodeFile.ReleaseGroup = localEpisode.ParsedEpisodeInfo.ReleaseGroup; - - bool copyOnly; - switch (importMode) - { - default: - case ImportMode.Auto: - copyOnly = downloadClientItem != null && downloadClientItem.IsReadOnly; - break; - case ImportMode.Move: - copyOnly = false; - break; - case ImportMode.Copy: - copyOnly = true; - break; - } - - if (newDownload) - { - episodeFile.SceneName = GetSceneName(downloadClientItem, localEpisode); - - var moveResult = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode, copyOnly); - oldFiles = moveResult.OldFiles; - } - else - { - episodeFile.RelativePath = localEpisode.Series.Path.GetRelativePath(episodeFile.Path); - } - - _mediaFileService.Add(episodeFile); - importResults.Add(new ImportResult(importDecision)); - - if (newDownload) - { - _extraService.ImportExtraFiles(localEpisode, episodeFile, copyOnly); - } - - if (downloadClientItem != null) - { - _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId, downloadClientItem.IsReadOnly)); - } - else - { - _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload)); - } - - if (newDownload) - { - _eventAggregator.PublishEvent(new EpisodeDownloadedEvent(localEpisode, episodeFile, oldFiles)); - } - } - catch (Exception e) - { - _logger.Warn(e, "Couldn't import episode " + localEpisode); - importResults.Add(new ImportResult(importDecision, "Failed to import episode")); - } - } - - //Adding all the rejected decisions - importResults.AddRange(decisions.Where(c => !c.Approved) - .Select(d => new ImportResult(d, d.Rejections.Select(r => r.Reason).ToArray()))); - - return importResults; + throw new NotImplementedException("This will be removed"); } private string GetSceneName(DownloadClientItem downloadClientItem, LocalEpisode localEpisode) diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecision.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecision.cs deleted file mode 100644 index 5e4e2ede2..000000000 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecision.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.DecisionEngine; -using NzbDrone.Core.Parser.Model; - -namespace NzbDrone.Core.MediaFiles.EpisodeImport -{ - public class ImportDecision - { - public LocalEpisode LocalEpisode { get; private set; } - public IEnumerable Rejections { get; private set; } - - public bool Approved => Rejections.Empty(); - - public ImportDecision(LocalEpisode localEpisode, params Rejection[] rejections) - { - LocalEpisode = localEpisode; - Rejections = rejections.ToList(); - } - } -} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs index 23924686c..4209e98c7 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs @@ -12,6 +12,7 @@ using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Core.Music; +using NzbDrone.Core.MediaFiles.TrackImport; namespace NzbDrone.Core.MediaFiles.EpisodeImport { @@ -94,7 +95,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport return decisions; } - private ImportDecision GetDecision(string file, Artist artist, ParsedTrackInfo folderInfo, bool sceneSource, bool shouldUseFolderName) + private ImportDecision GetDecision(string file, Artist artist, ParsedTrackInfo folderInfo, bool shouldUseFolderName) { ImportDecision decision = null; @@ -109,11 +110,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport _logger.Debug("Size: {0}", localTrack.Size); - //TODO: make it so media info doesn't ruin the import process of a new series - if (sceneSource) - { - localTrack.MediaInfo = _videoFileInfoReader.GetMediaInfo(file); - } + //TODO: make it so media info doesn't ruin the import process of a new artist if (localTrack.Tracks.Empty()) { @@ -137,19 +134,41 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport { _logger.Error(e, "Couldn't import file. {0}", file); - var localEpisode = new LocalEpisode { Path = file }; - decision = new ImportDecision(localEpisode, new Rejection("Unexpected error processing file")); + var localTrack = new LocalTrack { Path = file }; + decision = new ImportDecision(localTrack, new Rejection("Unexpected error processing file")); } return decision; } - private ImportDecision GetDecision(LocalEpisode localEpisode) + private ImportDecision GetDecision(LocalTrack localTrack) { - var reasons = _specifications.Select(c => EvaluateSpec(c, localEpisode)) + var reasons = _specifications.Select(c => EvaluateSpec(c, localTrack)) .Where(c => c != null); - return new ImportDecision(localEpisode, reasons.ToArray()); + return new ImportDecision(localTrack, reasons.ToArray()); + } + + private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalTrack localTrack) + { + try + { + var result = spec.IsSatisfiedBy(localTrack); + + if (!result.Accepted) + { + return new Rejection(result.Reason); + } + } + catch (Exception e) + { + //e.Data.Add("report", remoteEpisode.Report.ToJson()); + //e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson()); + _logger.Error(e, "Couldn't evaluate decision on {0}", localTrack.Path); + return new Rejection($"{spec.GetType().Name}: {e.Message}"); + } + + return null; } private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalEpisode localEpisode) diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportCommand.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportCommand.cs index 38ed485b7..28168385f 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportCommand.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportCommand.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.MediaFiles.TrackImport; namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs index d85a2e119..a12c841ea 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs @@ -15,6 +15,7 @@ using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Tv; +using NzbDrone.Core.MediaFiles.TrackImport; namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual { @@ -94,65 +95,67 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual private List ProcessFolder(string folder, string downloadId) { - var directoryInfo = new DirectoryInfo(folder); - var series = _parsingService.GetSeries(directoryInfo.Name); + throw new System.NotImplementedException("TODO: This will be rewritten for Music"); + //var directoryInfo = new DirectoryInfo(folder); + //var series = _parsingService.GetSeries(directoryInfo.Name); - if (series == null && downloadId.IsNotNullOrWhiteSpace()) - { - var trackedDownload = _trackedDownloadService.Find(downloadId); - series = trackedDownload.RemoteEpisode.Series; - } + //if (series == null && downloadId.IsNotNullOrWhiteSpace()) + //{ + // var trackedDownload = _trackedDownloadService.Find(downloadId); + // series = trackedDownload.RemoteEpisode.Series; + //} - if (series == null) - { - var files = _diskScanService.GetVideoFiles(folder); + //if (series == null) + //{ + // var files = _diskScanService.GetVideoFiles(folder); - return files.Select(file => ProcessFile(file, downloadId, folder)).Where(i => i != null).ToList(); - } + // return files.Select(file => ProcessFile(file, downloadId, folder)).Where(i => i != null).ToList(); + //} - var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name); - var seriesFiles = _diskScanService.GetVideoFiles(folder).ToList(); - var decisions = _importDecisionMaker.GetImportDecisions(seriesFiles, series, folderInfo, SceneSource(series, folder)); + //var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name); + //var seriesFiles = _diskScanService.GetVideoFiles(folder).ToList(); + //var decisions = _importDecisionMaker.GetImportDecisions(seriesFiles, series, folderInfo, SceneSource(series, folder)); - return decisions.Select(decision => MapItem(decision, folder, downloadId)).ToList(); + //return decisions.Select(decision => MapItem(decision, folder, downloadId)).ToList(); } private ManualImportItem ProcessFile(string file, string downloadId, string folder = null) { - if (folder.IsNullOrWhiteSpace()) - { - folder = new FileInfo(file).Directory.FullName; - } + throw new System.NotImplementedException("TODO: This will be rewritten for Music"); + //if (folder.IsNullOrWhiteSpace()) + //{ + // folder = new FileInfo(file).Directory.FullName; + //} - var relativeFile = folder.GetRelativePath(file); + //var relativeFile = folder.GetRelativePath(file); - var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]); + //var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]); - if (series == null) - { - series = _parsingService.GetSeries(relativeFile); - } + //if (series == null) + //{ + // series = _parsingService.GetSeries(relativeFile); + //} - if (series == null && downloadId.IsNotNullOrWhiteSpace()) - { - var trackedDownload = _trackedDownloadService.Find(downloadId); - series = trackedDownload.RemoteEpisode.Series; - } + //if (series == null && downloadId.IsNotNullOrWhiteSpace()) + //{ + // var trackedDownload = _trackedDownloadService.Find(downloadId); + // series = trackedDownload.RemoteEpisode.Series; + //} - if (series == null) - { - var localEpisode = new LocalEpisode(); - localEpisode.Path = file; - localEpisode.Quality = QualityParser.ParseQuality(file); - localEpisode.Size = _diskProvider.GetFileSize(file); + //if (series == null) + //{ + // var localEpisode = new LocalEpisode(); + // localEpisode.Path = file; + // localEpisode.Quality = QualityParser.ParseQuality(file); + // localEpisode.Size = _diskProvider.GetFileSize(file); - return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId); - } + // return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId); + //} - var importDecisions = _importDecisionMaker.GetImportDecisions(new List {file}, - series, null, SceneSource(series, folder)); + //var importDecisions = _importDecisionMaker.GetImportDecisions(new List {file}, + // series, null, SceneSource(series, folder)); - return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null; + //return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null; } private bool SceneSource(Series series, string folder) @@ -162,107 +165,109 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId) { - var item = new ManualImportItem(); - - item.Path = decision.LocalEpisode.Path; - item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path); - item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path); - item.DownloadId = downloadId; - - if (decision.LocalEpisode.Series != null) - { - item.Series = decision.LocalEpisode.Series; - } - - if (decision.LocalEpisode.Episodes.Any()) - { - item.SeasonNumber = decision.LocalEpisode.SeasonNumber; - item.Episodes = decision.LocalEpisode.Episodes; - } - - item.Quality = decision.LocalEpisode.Quality; - item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path); - item.Rejections = decision.Rejections; - - return item; + throw new System.NotImplementedException("TODO: This will be rewritten for Music"); + //var item = new ManualImportItem(); + + //item.Path = decision.LocalEpisode.Path; + //item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path); + //item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path); + //item.DownloadId = downloadId; + + //if (decision.LocalEpisode.Series != null) + //{ + // item.Series = decision.LocalEpisode.Series; + //} + + //if (decision.LocalEpisode.Episodes.Any()) + //{ + // item.SeasonNumber = decision.LocalEpisode.SeasonNumber; + // item.Episodes = decision.LocalEpisode.Episodes; + //} + + //item.Quality = decision.LocalEpisode.Quality; + //item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path); + //item.Rejections = decision.Rejections; + + //return item; } public void Execute(ManualImportCommand message) { _logger.ProgressTrace("Manually importing {0} files using mode {1}", message.Files.Count, message.ImportMode); - - var imported = new List(); - var importedTrackedDownload = new List(); - - for (int i = 0; i < message.Files.Count; i++) - { - _logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count); - - var file = message.Files[i]; - var series = _seriesService.GetSeries(file.SeriesId); - var episodes = _episodeService.GetEpisodes(file.EpisodeIds); - var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo(); - var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path); - var existingFile = series.Path.IsParentPath(file.Path); - - var localEpisode = new LocalEpisode - { - ExistingFile = false, - Episodes = episodes, - MediaInfo = mediaInfo, - ParsedEpisodeInfo = parsedEpisodeInfo, - Path = file.Path, - Quality = file.Quality, - Series = series, - Size = 0 - }; - - //TODO: Cleanup non-tracked downloads - - var importDecision = new ImportDecision(localEpisode); - - if (file.DownloadId.IsNullOrWhiteSpace()) - { - imported.AddRange(_importApprovedEpisodes.Import(new List { importDecision }, !existingFile, null, message.ImportMode)); - } - - else - { - var trackedDownload = _trackedDownloadService.Find(file.DownloadId); - var importResult = _importApprovedEpisodes.Import(new List { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First(); - - imported.Add(importResult); - - importedTrackedDownload.Add(new ManuallyImportedFile - { - TrackedDownload = trackedDownload, - ImportResult = importResult - }); - } - } - - _logger.ProgressTrace("Manually imported {0} files", imported.Count); - - foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList()) - { - var trackedDownload = groupedTrackedDownload.First().TrackedDownload; - - if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath)) - { - if (_downloadedEpisodesImportService.ShouldDeleteFolder( - new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath), - trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly) - { - _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true); - } - } - - if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count)) - { - trackedDownload.State = TrackedDownloadStage.Imported; - _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload)); - } - } + throw new System.NotImplementedException("TODO: This will be rewritten for Music"); + + //var imported = new List(); + //var importedTrackedDownload = new List(); + + //for (int i = 0; i < message.Files.Count; i++) + //{ + // _logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count); + + // var file = message.Files[i]; + // var series = _seriesService.GetSeries(file.SeriesId); + // var episodes = _episodeService.GetEpisodes(file.EpisodeIds); + // var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo(); + // var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path); + // var existingFile = series.Path.IsParentPath(file.Path); + + // var localEpisode = new LocalEpisode + // { + // ExistingFile = false, + // Episodes = episodes, + // MediaInfo = mediaInfo, + // ParsedEpisodeInfo = parsedEpisodeInfo, + // Path = file.Path, + // Quality = file.Quality, + // Series = series, + // Size = 0 + // }; + + // //TODO: Cleanup non-tracked downloads + + // var importDecision = new ImportDecision(localEpisode); + + // if (file.DownloadId.IsNullOrWhiteSpace()) + // { + // imported.AddRange(_importApprovedEpisodes.Import(new List { importDecision }, !existingFile, null, message.ImportMode)); + // } + + // else + // { + // var trackedDownload = _trackedDownloadService.Find(file.DownloadId); + // var importResult = _importApprovedEpisodes.Import(new List { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First(); + + // imported.Add(importResult); + + // importedTrackedDownload.Add(new ManuallyImportedFile + // { + // TrackedDownload = trackedDownload, + // ImportResult = importResult + // }); + // } + //} + + //_logger.ProgressTrace("Manually imported {0} files", imported.Count); + + //foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList()) + //{ + // var trackedDownload = groupedTrackedDownload.First().TrackedDownload; + + // if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath)) + // { + // if (_downloadedEpisodesImportService.ShouldDeleteFolder( + // new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath), + // trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly) + // { + // _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true); + // } + // } + + // if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count)) + // { + // trackedDownload.State = TrackedDownloadStage.Imported; + // _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload)); + // } + //} } } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManuallyImportedFile.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManuallyImportedFile.cs index 32f904e4d..4780240e2 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManuallyImportedFile.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManuallyImportedFile.cs @@ -1,4 +1,5 @@ using NzbDrone.Core.Download.TrackedDownloads; +using NzbDrone.Core.MediaFiles.TrackImport; namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecification.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecification.cs index 490bdb941..6e9695d34 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecification.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FreeSpaceSpecification.cs @@ -63,5 +63,48 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications return Decision.Accept(); } + + public Decision IsSatisfiedBy(LocalTrack localTrack) + { + if (_configService.SkipFreeSpaceCheckWhenImporting) + { + _logger.Debug("Skipping free space check when importing"); + return Decision.Accept(); + } + + try + { + if (localTrack.ExistingFile) + { + _logger.Debug("Skipping free space check for existing episode"); + return Decision.Accept(); + } + + var path = Directory.GetParent(localTrack.Artist.Path); + var freeSpace = _diskProvider.GetAvailableSpace(path.FullName); + + if (!freeSpace.HasValue) + { + _logger.Debug("Free space check returned an invalid result for: {0}", path); + return Decision.Accept(); + } + + if (freeSpace < localTrack.Size + 100.Megabytes()) + { + _logger.Warn("Not enough free space ({0}) to import: {1} ({2})", freeSpace, localTrack, localTrack.Size); + return Decision.Reject("Not enough free space"); + } + } + catch (DirectoryNotFoundException ex) + { + _logger.Error(ex, "Unable to check free disk space while importing."); + } + catch (Exception ex) + { + _logger.Error(ex, "Unable to check free disk space while importing. {0}", localTrack.Path); + } + + return Decision.Accept(); + } } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FullSeasonSpecification.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FullSeasonSpecification.cs index 7397c13e7..4357c07bc 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FullSeasonSpecification.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/FullSeasonSpecification.cs @@ -1,4 +1,5 @@ -using NLog; +using System; +using NLog; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Parser.Model; @@ -13,6 +14,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications _logger = logger; } + public Decision IsSatisfiedBy(LocalTrack localTrack) + { + throw new NotImplementedException("Interface will be removed"); + } + public Decision IsSatisfiedBy(LocalEpisode localEpisode) { if (localEpisode.ParsedEpisodeInfo.FullSeason) diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/MatchesFolderSpecification.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/MatchesFolderSpecification.cs index 79ef96f88..b97c42f35 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/MatchesFolderSpecification.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/MatchesFolderSpecification.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Linq; using NLog; using NzbDrone.Core.DecisionEngine; @@ -14,6 +15,53 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications { _logger = logger; } + + public Decision IsSatisfiedBy(LocalTrack localTrack) + { + if (localTrack.ExistingFile) + { + return Decision.Accept(); + } + + var dirInfo = new FileInfo(localTrack.Path).Directory; + + if (dirInfo == null) + { + return Decision.Accept(); + } + + throw new System.NotImplementedException("Needs to be implemented"); + + //var folderInfo = Parser.Parser.ParseTitle(dirInfo.Name); + + //if (folderInfo == null) + //{ + // return Decision.Accept(); + //} + + //if (!folderInfo.TrackNumbers.Any()) + //{ + // return Decision.Accept(); + //} + + + //var unexpected = localTrack.ParsedTrackInfo.TrackNumbers.Where(f => !folderInfo.TrackNumbers.Contains(f)).ToList(); + //// TODO: Implement MatchesFolderSpecification + //if (unexpected.Any()) + //{ + // _logger.Debug("Unexpected track number(s) in file: {0}", string.Join(", ", unexpected)); + + // if (unexpected.Count == 1) + // { + // return Decision.Reject("Track Number {0} was unexpected considering the {1} folder name", unexpected.First(), dirInfo.Name); + // } + + // return Decision.Reject("Episode Numbers {0} were unexpected considering the {1} folder name", string.Join(", ", unexpected), dirInfo.Name); + //} + + return Decision.Accept(); + } + public Decision IsSatisfiedBy(LocalEpisode localEpisode) { if (localEpisode.ExistingFile) diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotSampleSpecification.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotSampleSpecification.cs index c7b61d802..e8eb664ec 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotSampleSpecification.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotSampleSpecification.cs @@ -1,4 +1,5 @@ -using NLog; +using System; +using NLog; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Parser.Model; @@ -16,6 +17,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications _logger = logger; } + public Decision IsSatisfiedBy(LocalTrack localTrack) + { + throw new NotImplementedException(); + } + public Decision IsSatisfiedBy(LocalEpisode localEpisode) { if (localEpisode.ExistingFile) diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecification.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecification.cs index 2260ed71a..7fe4bef26 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecification.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/NotUnpackingSpecification.cs @@ -56,5 +56,10 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications return Decision.Accept(); } + + public Decision IsSatisfiedBy(LocalTrack localTrack) + { + throw new NotImplementedException(); + } } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/SameEpisodesImportSpecification.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/SameEpisodesImportSpecification.cs index ee6c02c53..8adb7416b 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/SameEpisodesImportSpecification.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/SameEpisodesImportSpecification.cs @@ -1,3 +1,4 @@ +using System; using NLog; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Parser.Model; @@ -27,5 +28,10 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications _logger.Debug("Episode file on disk contains more episodes than this file contains"); return Decision.Reject("Episode file on disk contains more episodes than this file contains"); } + + public Decision IsSatisfiedBy(LocalTrack localTrack) + { + throw new NotImplementedException(); + } } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UnverifiedSceneNumberingSpecification.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UnverifiedSceneNumberingSpecification.cs index ce65eb304..95c39b1d6 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UnverifiedSceneNumberingSpecification.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UnverifiedSceneNumberingSpecification.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using NLog; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Parser.Model; @@ -13,6 +14,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications _logger = logger; } + public Decision IsSatisfiedBy(LocalTrack localTrack) + { + throw new NotImplementedException("This is not needed, Interface will be removed"); + } + public Decision IsSatisfiedBy(LocalEpisode localEpisode) { if (localEpisode.ExistingFile) diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs index 3d07306af..c0eaa6a92 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using NLog; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Parser.Model; @@ -15,6 +16,18 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications _logger = logger; } + public Decision IsSatisfiedBy(LocalTrack localTrack) + { + var qualityComparer = new QualityModelComparer(localTrack.Artist.Profile); + if (localTrack.Tracks.Any(e => e.TrackFileId != 0 && qualityComparer.Compare(e.TrackFile.Value.Quality, localTrack.Quality) > 0)) + { + _logger.Debug("This file isn't an upgrade for all tracks. Skipping {0}", localTrack.Path); + return Decision.Reject("Not an upgrade for existing track file(s)"); + } + + return Decision.Accept(); + } + public Decision IsSatisfiedBy(LocalEpisode localEpisode) { var qualityComparer = new QualityModelComparer(localEpisode.Series.Profile); diff --git a/src/NzbDrone.Core/MediaFiles/Events/TrackDownloadedEvent.cs b/src/NzbDrone.Core/MediaFiles/Events/TrackDownloadedEvent.cs new file mode 100644 index 000000000..82ea9f788 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/Events/TrackDownloadedEvent.cs @@ -0,0 +1,23 @@ +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Parser.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MediaFiles.Events +{ + public class TrackDownloadedEvent : IEvent + { + public LocalTrack Track { get; private set; } + public TrackFile TrackFile { get; private set; } + public List OldFiles { get; private set; } + + public TrackDownloadedEvent(LocalTrack track, TrackFile trackFile, List oldFiles) + { + Track = track; + TrackFile = trackFile; + OldFiles = oldFiles; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs index 0b7b2cba3..a46844f54 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs @@ -5,81 +5,73 @@ using NLog; using NzbDrone.Common; using NzbDrone.Common.Extensions; using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; namespace NzbDrone.Core.MediaFiles { public interface IMediaFileTableCleanupService { - void Clean(Series series, List filesOnDisk); + void Clean(Artist artist, List filesOnDisk); } public class MediaFileTableCleanupService : IMediaFileTableCleanupService { private readonly IMediaFileService _mediaFileService; - private readonly IEpisodeService _episodeService; + private readonly ITrackService _trackService; private readonly Logger _logger; public MediaFileTableCleanupService(IMediaFileService mediaFileService, - IEpisodeService episodeService, + ITrackService trackService, Logger logger) { _mediaFileService = mediaFileService; - _episodeService = episodeService; + _trackService = trackService; _logger = logger; } - public void Clean(Series series, List filesOnDisk) + public void Clean(Artist artist, List filesOnDisk) { - var seriesFiles = _mediaFileService.GetFilesBySeries(series.Id); - var episodes = _episodeService.GetEpisodeBySeries(series.Id); + var artistFiles = _mediaFileService.GetFilesByArtist(artist.SpotifyId); + var tracks = _trackService.GetTracksByArtist(artist.SpotifyId); var filesOnDiskKeys = new HashSet(filesOnDisk, PathEqualityComparer.Instance); - foreach (var seriesFile in seriesFiles) + foreach (var artistFile in artistFiles) { - var episodeFile = seriesFile; - var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath); + var trackFile = artistFile; + var trackFilePath = Path.Combine(artist.Path, trackFile.RelativePath); try { - if (!filesOnDiskKeys.Contains(episodeFilePath)) + if (!filesOnDiskKeys.Contains(trackFilePath)) { - _logger.Debug("File [{0}] no longer exists on disk, removing from db", episodeFilePath); - _mediaFileService.Delete(seriesFile, DeleteMediaFileReason.MissingFromDisk); + _logger.Debug("File [{0}] no longer exists on disk, removing from db", trackFilePath); + _mediaFileService.Delete(artistFile, DeleteMediaFileReason.MissingFromDisk); continue; } - if (episodes.None(e => e.EpisodeFileId == episodeFile.Id)) + if (tracks.None(e => e.TrackFileId == trackFile.Id)) { - _logger.Debug("File [{0}] is not assigned to any episodes, removing from db", episodeFilePath); - _mediaFileService.Delete(episodeFile, DeleteMediaFileReason.NoLinkedEpisodes); + _logger.Debug("File [{0}] is not assigned to any artist, removing from db", trackFilePath); + _mediaFileService.Delete(trackFile, DeleteMediaFileReason.NoLinkedEpisodes); continue; } - -// var localEpsiode = _parsingService.GetLocalEpisode(episodeFile.Path, series); -// -// if (localEpsiode == null || episodes.Count != localEpsiode.Episodes.Count) -// { -// _logger.Debug("File [{0}] parsed episodes has changed, removing from db", episodeFile.Path); -// _mediaFileService.Delete(episodeFile); -// continue; -// } } catch (Exception ex) { - _logger.Error(ex, "Unable to cleanup EpisodeFile in DB: {0}", episodeFile.Id); + _logger.Error(ex, "Unable to cleanup EpisodeFile in DB: {0}", trackFile.Id); } } - foreach (var e in episodes) + foreach (var t in tracks) { - var episode = e; + var track = t; - if (episode.EpisodeFileId > 0 && seriesFiles.None(f => f.Id == episode.EpisodeFileId)) + if (track.TrackFileId > 0 && artistFiles.None(f => f.Id == track.TrackFileId)) { - episode.EpisodeFileId = 0; - _episodeService.UpdateEpisode(episode); + track.TrackFileId = 0; + _trackService.UpdateTrack(track); } } } diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs index fb232f2f9..60cf5bf01 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs @@ -7,10 +7,11 @@ using NzbDrone.Core.Tv; using System.Collections.Generic; using System.Linq; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Music; namespace NzbDrone.Core.MediaFiles.MediaInfo { - public class UpdateMediaInfoService : IHandle + public class UpdateMediaInfoService : IHandle { private readonly IDiskProvider _diskProvider; private readonly IMediaFileService _mediaFileService; @@ -33,11 +34,11 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo _logger = logger; } - private void UpdateMediaInfo(Series series, List mediaFiles) + private void UpdateMediaInfo(Artist artist, List mediaFiles) { foreach (var mediaFile in mediaFiles) { - var path = Path.Combine(series.Path, mediaFile.RelativePath); + var path = Path.Combine(artist.Path, mediaFile.RelativePath); if (!_diskProvider.FileExists(path)) { @@ -56,7 +57,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo } } - public void Handle(SeriesScannedEvent message) + public void Handle(ArtistScannedEvent message) { if (!_configService.EnableMediaInfo) { @@ -64,10 +65,10 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo return; } - var allMediaFiles = _mediaFileService.GetFilesBySeries(message.Series.Id); + var allMediaFiles = _mediaFileService.GetFilesByArtist(message.Artist.SpotifyId); var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < CURRENT_MEDIA_INFO_SCHEMA_REVISION).ToList(); - UpdateMediaInfo(message.Series, filteredMediaFiles); + UpdateMediaInfo(message.Artist, filteredMediaFiles); } } } diff --git a/src/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs b/src/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs index 4cfe84b37..d279b7289 100644 --- a/src/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs @@ -55,24 +55,28 @@ namespace NzbDrone.Core.MediaFiles public List GetRenamePreviews(int seriesId) { - var series = _seriesService.GetSeries(seriesId); - var episodes = _episodeService.GetEpisodeBySeries(seriesId); - var files = _mediaFileService.GetFilesBySeries(seriesId); - - return GetPreviews(series, episodes, files) - .OrderByDescending(e => e.SeasonNumber) - .ThenByDescending(e => e.EpisodeNumbers.First()) - .ToList(); + // TODO + throw new NotImplementedException(); + //var series = _seriesService.GetSeries(seriesId); + //var episodes = _episodeService.GetEpisodeBySeries(seriesId); + //var files = _mediaFileService.GetFilesBySeries(seriesId); + + //return GetPreviews(series, episodes, files) + // .OrderByDescending(e => e.SeasonNumber) + // .ThenByDescending(e => e.EpisodeNumbers.First()) + // .ToList(); } public List GetRenamePreviews(int seriesId, int seasonNumber) { - var series = _seriesService.GetSeries(seriesId); - var episodes = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber); - var files = _mediaFileService.GetFilesBySeason(seriesId, seasonNumber); - - return GetPreviews(series, episodes, files) - .OrderByDescending(e => e.EpisodeNumbers.First()).ToList(); + // TODO + throw new NotImplementedException(); + //var series = _seriesService.GetSeries(seriesId); + //var episodes = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber); + //var files = _mediaFileService.GetFilesBySeason(seriesId, seasonNumber); + + //return GetPreviews(series, episodes, files) + // .OrderByDescending(e => e.EpisodeNumbers.First()).ToList(); } private IEnumerable GetPreviews(Series series, List episodes, List files) @@ -110,62 +114,68 @@ namespace NzbDrone.Core.MediaFiles private void RenameFiles(List episodeFiles, Series series) { - var renamed = new List(); - - foreach (var episodeFile in episodeFiles) - { - var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath); - - try - { - _logger.Debug("Renaming episode file: {0}", episodeFile); - _episodeFileMover.MoveEpisodeFile(episodeFile, series); - - _mediaFileService.Update(episodeFile); - renamed.Add(episodeFile); - - _logger.Debug("Renamed episode file: {0}", episodeFile); - } - catch (SameFilenameException ex) - { - _logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename); - } - catch (Exception ex) - { - _logger.Error(ex, "Failed to rename file {0}", episodeFilePath); - } - } - - if (renamed.Any()) - { - _diskProvider.RemoveEmptySubfolders(series.Path); - - _eventAggregator.PublishEvent(new SeriesRenamedEvent(series)); - } + // TODO + throw new NotImplementedException(); + //var renamed = new List(); + + //foreach (var episodeFile in episodeFiles) + //{ + // var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath); + + // try + // { + // _logger.Debug("Renaming episode file: {0}", episodeFile); + // _episodeFileMover.MoveEpisodeFile(episodeFile, series); + + // _mediaFileService.Update(episodeFile); + // renamed.Add(episodeFile); + + // _logger.Debug("Renamed episode file: {0}", episodeFile); + // } + // catch (SameFilenameException ex) + // { + // _logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename); + // } + // catch (Exception ex) + // { + // _logger.Error(ex, "Failed to rename file {0}", episodeFilePath); + // } + //} + + //if (renamed.Any()) + //{ + // _diskProvider.RemoveEmptySubfolders(series.Path); + + // _eventAggregator.PublishEvent(new SeriesRenamedEvent(series)); + //} } public void Execute(RenameFilesCommand message) { - var series = _seriesService.GetSeries(message.SeriesId); - var episodeFiles = _mediaFileService.Get(message.Files); - - _logger.ProgressInfo("Renaming {0} files for {1}", episodeFiles.Count, series.Title); - RenameFiles(episodeFiles, series); - _logger.ProgressInfo("Selected episode files renamed for {0}", series.Title); + // TODO + throw new NotImplementedException(); + //var series = _seriesService.GetSeries(message.SeriesId); + //var episodeFiles = _mediaFileService.Get(message.Files); + + //_logger.ProgressInfo("Renaming {0} files for {1}", episodeFiles.Count, series.Title); + //RenameFiles(episodeFiles, series); + //_logger.ProgressInfo("Selected episode files renamed for {0}", series.Title); } public void Execute(RenameSeriesCommand message) { - _logger.Debug("Renaming all files for selected series"); - var seriesToRename = _seriesService.GetSeries(message.SeriesIds); - - foreach (var series in seriesToRename) - { - var episodeFiles = _mediaFileService.GetFilesBySeries(series.Id); - _logger.ProgressInfo("Renaming all files in series: {0}", series.Title); - RenameFiles(episodeFiles, series); - _logger.ProgressInfo("All episode files renamed for {0}", series.Title); - } + // TODO + throw new NotImplementedException(); + //_logger.Debug("Renaming all files for selected series"); + //var seriesToRename = _seriesService.GetSeries(message.SeriesIds); + + //foreach (var series in seriesToRename) + //{ + // var episodeFiles = _mediaFileService.GetFilesBySeries(series.Id); + // _logger.ProgressInfo("Renaming all files in series: {0}", series.Title); + // RenameFiles(episodeFiles, series); + // _logger.ProgressInfo("All episode files renamed for {0}", series.Title); + //} } } } diff --git a/src/NzbDrone.Core/MediaFiles/TrackFileMoveResult.cs b/src/NzbDrone.Core/MediaFiles/TrackFileMoveResult.cs new file mode 100644 index 000000000..8b4b3d854 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/TrackFileMoveResult.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MediaFiles +{ + public class TrackFileMoveResult + { + public TrackFileMoveResult() + { + OldFiles = new List(); + } + + public TrackFile TrackFile { get; set; } + public List OldFiles { get; set; } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/TrackFileMovingService.cs b/src/NzbDrone.Core/MediaFiles/TrackFileMovingService.cs new file mode 100644 index 000000000..5500f6007 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/TrackFileMovingService.cs @@ -0,0 +1,226 @@ +using NLog; +using NzbDrone.Common.Disk; +using NzbDrone.Common.EnsureThat; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Music; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Tv; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MediaFiles +{ + public interface IMoveTrackFiles + { + TrackFile MoveTrackFile(TrackFile trackFile, Artist artist); + TrackFile MoveTrackFile(TrackFile trackFile, LocalTrack localTrack); + TrackFile CopyTrackFile(TrackFile trackFile, LocalTrack localTrack); + } + + public class TrackFileMovingService : IMoveTrackFiles + { + private readonly ITrackService _trackService; + //private readonly IUpdateTrackFileService _updateTrackFileService; + private readonly IBuildFileNames _buildFileNames; + private readonly IDiskTransferService _diskTransferService; + private readonly IDiskProvider _diskProvider; + private readonly IMediaFileAttributeService _mediaFileAttributeService; + private readonly IEventAggregator _eventAggregator; + private readonly IConfigService _configService; + private readonly Logger _logger; + + public TrackFileMovingService(ITrackService episodeService, + //IUpdateEpisodeFileService updateEpisodeFileService, + IBuildFileNames buildFileNames, + IDiskTransferService diskTransferService, + IDiskProvider diskProvider, + IMediaFileAttributeService mediaFileAttributeService, + IEventAggregator eventAggregator, + IConfigService configService, + Logger logger) + { + _trackService = episodeService; + //_updateTrackFileService = updateEpisodeFileService; + _buildFileNames = buildFileNames; + _diskTransferService = diskTransferService; + _diskProvider = diskProvider; + _mediaFileAttributeService = mediaFileAttributeService; + _eventAggregator = eventAggregator; + _configService = configService; + _logger = logger; + } + + public TrackFile MoveTrackFile(TrackFile trackFile, Artist artist) + { + throw new System.NotImplementedException(); + // TODO + //var tracks = _trackService.GetTracksByFileId(trackFile.Id); + //var newFileName = _buildFileNames.BuildFileName(tracks, artist, trackFile); + //var filePath = _buildFileNames.BuildFilePath(artist, tracks.First(), trackFile.AlbumId, newFileName, Path.GetExtension(trackFile.RelativePath)); + + //EnsureAlbumFolder(trackFile, artist, tracks.Select(v => v.Album).First(), filePath); + + //_logger.Debug("Renaming track file: {0} to {1}", trackFile, filePath); + + //return TransferFile(trackFile, artist, tracks, filePath, TransferMode.Move); + } + + public TrackFile MoveTrackFile(TrackFile trackFile, LocalTrack localTrack) + { + // TODO + throw new System.NotImplementedException(); + //var newFileName = _buildFileNames.BuildFileName(localEpisode.Episodes, localEpisode.Series, episodeFile); + //var filePath = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(localEpisode.Path)); + + //EnsureEpisodeFolder(episodeFile, localEpisode, filePath); + + //_logger.Debug("Moving episode file: {0} to {1}", episodeFile.Path, filePath); + + //return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Move); + } + + public TrackFile CopyTrackFile(TrackFile trackFile, LocalTrack localTrack) + { + // TODO + throw new System.NotImplementedException(); + //var newFileName = _buildFileNames.BuildFileName(localEpisode.Episodes, localEpisode.Series, episodeFile); + //var filePath = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(localEpisode.Path)); + + //EnsureEpisodeFolder(episodeFile, localEpisode, filePath); + + //if (_configService.CopyUsingHardlinks) + //{ + // _logger.Debug("Hardlinking episode file: {0} to {1}", episodeFile.Path, filePath); + // return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.HardLinkOrCopy); + //} + + //_logger.Debug("Copying episode file: {0} to {1}", episodeFile.Path, filePath); + //return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Copy); + } + + private EpisodeFile TransferFile(EpisodeFile episodeFile, Series series, List episodes, string destinationFilePath, TransferMode mode) + { + // TODO + throw new System.NotImplementedException(); + + //Ensure.That(episodeFile, () => episodeFile).IsNotNull(); + //Ensure.That(series, () => series).IsNotNull(); + //Ensure.That(destinationFilePath, () => destinationFilePath).IsValidPath(); + + //var episodeFilePath = episodeFile.Path ?? Path.Combine(series.Path, episodeFile.RelativePath); + + //if (!_diskProvider.FileExists(episodeFilePath)) + //{ + // throw new FileNotFoundException("Episode file path does not exist", episodeFilePath); + //} + + //if (episodeFilePath == destinationFilePath) + //{ + // throw new SameFilenameException("File not moved, source and destination are the same", episodeFilePath); + //} + + //_diskTransferService.TransferFile(episodeFilePath, destinationFilePath, mode); + + //episodeFile.RelativePath = series.Path.GetRelativePath(destinationFilePath); + + //_updateTrackFileService.ChangeFileDateForFile(episodeFile, series, episodes); + + //try + //{ + // _mediaFileAttributeService.SetFolderLastWriteTime(series.Path, episodeFile.DateAdded); + + // if (series.SeasonFolder) + // { + // var seasonFolder = Path.GetDirectoryName(destinationFilePath); + + // _mediaFileAttributeService.SetFolderLastWriteTime(seasonFolder, episodeFile.DateAdded); + // } + //} + + //catch (Exception ex) + //{ + // _logger.Warn(ex, "Unable to set last write time"); + //} + + //_mediaFileAttributeService.SetFilePermissions(destinationFilePath); + + //return episodeFile; + } + + private void EnsureEpisodeFolder(EpisodeFile episodeFile, LocalEpisode localEpisode, string filePath) + { + EnsureEpisodeFolder(episodeFile, localEpisode.Series, localEpisode.SeasonNumber, filePath); + } + + private void EnsureEpisodeFolder(EpisodeFile episodeFile, Series series, int seasonNumber, string filePath) + { + var episodeFolder = Path.GetDirectoryName(filePath); + var seasonFolder = _buildFileNames.BuildSeasonPath(series, seasonNumber); + var seriesFolder = series.Path; + var rootFolder = new OsPath(seriesFolder).Directory.FullPath; + + if (!_diskProvider.FolderExists(rootFolder)) + { + throw new DirectoryNotFoundException(string.Format("Root folder '{0}' was not found.", rootFolder)); + } + + var changed = false; + var newEvent = new EpisodeFolderCreatedEvent(series, episodeFile); + + if (!_diskProvider.FolderExists(seriesFolder)) + { + CreateFolder(seriesFolder); + newEvent.SeriesFolder = seriesFolder; + changed = true; + } + + if (seriesFolder != seasonFolder && !_diskProvider.FolderExists(seasonFolder)) + { + CreateFolder(seasonFolder); + newEvent.SeasonFolder = seasonFolder; + changed = true; + } + + if (seasonFolder != episodeFolder && !_diskProvider.FolderExists(episodeFolder)) + { + CreateFolder(episodeFolder); + newEvent.EpisodeFolder = episodeFolder; + changed = true; + } + + if (changed) + { + _eventAggregator.PublishEvent(newEvent); + } + } + + private void CreateFolder(string directoryName) + { + Ensure.That(directoryName, () => directoryName).IsNotNullOrWhiteSpace(); + + var parentFolder = new OsPath(directoryName).Directory.FullPath; + if (!_diskProvider.FolderExists(parentFolder)) + { + CreateFolder(parentFolder); + } + + try + { + _diskProvider.CreateFolder(directoryName); + } + catch (IOException ex) + { + _logger.Error(ex, "Unable to create directory: {0}", directoryName); + } + + _mediaFileAttributeService.SetFolderPermissions(directoryName); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs new file mode 100644 index 000000000..09abac393 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs @@ -0,0 +1,172 @@ +using NLog; +using NzbDrone.Common.Disk; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Download; +using NzbDrone.Core.MediaFiles.EpisodeImport; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Qualities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MediaFiles.TrackImport +{ + public interface IImportApprovedTracks + { + List Import(List decisions, bool newDownload, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto); + } + + public class ImportApprovedTracks : IImportApprovedTracks + { + private readonly IUpgradeMediaFiles _trackFileUpgrader; + private readonly IMediaFileService _mediaFileService; + //private readonly IExtraService _extraService; + private readonly IDiskProvider _diskProvider; + private readonly IEventAggregator _eventAggregator; + private readonly Logger _logger; + + public ImportApprovedTracks(IUpgradeMediaFiles episodeFileUpgrader, + IMediaFileService mediaFileService, + //IExtraService extraService, + IDiskProvider diskProvider, + IEventAggregator eventAggregator, + Logger logger) + { + _trackFileUpgrader = episodeFileUpgrader; + _mediaFileService = mediaFileService; + // _extraService = extraService; + _diskProvider = diskProvider; + _eventAggregator = eventAggregator; + _logger = logger; + } + + public List Import(List decisions, bool newDownload, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto) + { + var qualifiedImports = decisions.Where(c => c.Approved) + .GroupBy(c => c.LocalTrack.Artist.Id, (i, s) => s + .OrderByDescending(c => c.LocalTrack.Quality, new QualityModelComparer(s.First().LocalTrack.Artist.Profile)) + .ThenByDescending(c => c.LocalTrack.Size)) + .SelectMany(c => c) + .ToList(); + + var importResults = new List(); + + foreach (var importDecision in qualifiedImports.OrderBy(e => e.LocalTrack.Tracks.Select(track => track.TrackNumber).MinOrDefault()) + .ThenByDescending(e => e.LocalTrack.Size)) + { + var localTrack = importDecision.LocalTrack; + var oldFiles = new List(); + + try + { + //check if already imported + if (importResults.SelectMany(r => r.ImportDecision.LocalTrack.Tracks) + .Select(e => e.Id) + .Intersect(localTrack.Tracks.Select(e => e.Id)) + .Any()) + { + importResults.Add(new ImportResult(importDecision, "Episode has already been imported")); + continue; + } + + var trackFile = new TrackFile(); + trackFile.DateAdded = DateTime.UtcNow; + trackFile.SpotifyTrackId = localTrack.Artist.SpotifyId; + trackFile.Path = localTrack.Path.CleanFilePath(); + trackFile.Size = _diskProvider.GetFileSize(localTrack.Path); + trackFile.Quality = localTrack.Quality; + trackFile.MediaInfo = localTrack.MediaInfo; + //trackFile.AlbumId = localTrack.Album.ElementAt(0); // TODO: Implement ImportApprovedTracks Album Id + trackFile.Tracks = localTrack.Tracks; + trackFile.ReleaseGroup = localTrack.ParsedTrackInfo.ReleaseGroup; + + bool copyOnly; + switch (importMode) + { + default: + case ImportMode.Auto: + copyOnly = downloadClientItem != null && downloadClientItem.IsReadOnly; + break; + case ImportMode.Move: + copyOnly = false; + break; + case ImportMode.Copy: + copyOnly = true; + break; + } + + if (newDownload) + { + //trackFile.SceneName = GetSceneName(downloadClientItem, localTrack); + + var moveResult = _trackFileUpgrader.UpgradeTrackFile(trackFile, localTrack, copyOnly); + oldFiles = moveResult.OldFiles; + } + else + { + trackFile.RelativePath = localTrack.Artist.Path.GetRelativePath(trackFile.Path); + } + + _mediaFileService.Add(trackFile); + importResults.Add(new ImportResult(importDecision)); + + //if (newDownload) + //{ + // _extraService.ImportExtraFiles(localTrack, trackFile, copyOnly); // TODO: Import Music Extras + //} + + if (downloadClientItem != null) + { + _eventAggregator.PublishEvent(new TrackImportedEvent(localTrack, trackFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId, downloadClientItem.IsReadOnly)); + } + else + { + _eventAggregator.PublishEvent(new TrackImportedEvent(localTrack, trackFile, newDownload)); + } + + if (newDownload) + { + _eventAggregator.PublishEvent(new TrackDownloadedEvent(localTrack, trackFile, oldFiles)); + } + } + catch (Exception e) + { + _logger.Warn(e, "Couldn't import track " + localTrack); + importResults.Add(new ImportResult(importDecision, "Failed to import episode")); + } + } + + //Adding all the rejected decisions + importResults.AddRange(decisions.Where(c => !c.Approved) + .Select(d => new ImportResult(d, d.Rejections.Select(r => r.Reason).ToArray()))); + + return importResults; + } + + //private string GetSceneName(DownloadClientItem downloadClientItem, LocalEpisode localEpisode) + //{ + // if (downloadClientItem != null) + // { + // var title = Parser.Parser.RemoveFileExtension(downloadClientItem.Title); + + // var parsedTitle = Parser.Parser.ParseTitle(title); + + // if (parsedTitle != null && !parsedTitle.FullSeason) + // { + // return title; + // } + // } + + // var fileName = Path.GetFileNameWithoutExtension(localEpisode.Path.CleanFilePath()); + + // if (SceneChecker.IsSceneTitle(fileName)) + // { + // return fileName; + // } + + // return null; + //} + } +} diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecision.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecision.cs new file mode 100644 index 000000000..885ec60b0 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecision.cs @@ -0,0 +1,26 @@ +using NzbDrone.Common.Extensions; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Parser.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MediaFiles.TrackImport +{ + public class ImportDecision + { + public LocalTrack LocalTrack { get; private set; } + public IEnumerable Rejections { get; private set; } + + public bool Approved => Rejections.Empty(); + + public object LocalEpisode { get; internal set; } + + public ImportDecision(LocalTrack localTrack, params Rejection[] rejections) + { + LocalTrack = localTrack; + Rejections = rejections.ToList(); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportMode.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportMode.cs similarity index 66% rename from src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportMode.cs rename to src/NzbDrone.Core/MediaFiles/TrackImport/ImportMode.cs index ffdf7eed8..43236bc86 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportMode.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportMode.cs @@ -1,4 +1,4 @@ -namespace NzbDrone.Core.MediaFiles.EpisodeImport +namespace NzbDrone.Core.MediaFiles.TrackImport { public enum ImportMode { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportResult.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportResult.cs similarity index 85% rename from src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportResult.cs rename to src/NzbDrone.Core/MediaFiles/TrackImport/ImportResult.cs index a0d989335..64f60869b 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportResult.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportResult.cs @@ -1,8 +1,10 @@ -using System.Collections.Generic; +using NzbDrone.Common.EnsureThat; +using System; +using System.Collections.Generic; using System.Linq; -using NzbDrone.Common.EnsureThat; +using System.Text; -namespace NzbDrone.Core.MediaFiles.EpisodeImport +namespace NzbDrone.Core.MediaFiles.TrackImport { public class ImportResult { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportResultType.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportResultType.cs similarity index 66% rename from src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportResultType.cs rename to src/NzbDrone.Core/MediaFiles/TrackImport/ImportResultType.cs index 7c43332de..e4e3eaef8 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportResultType.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportResultType.cs @@ -1,4 +1,4 @@ -namespace NzbDrone.Core.MediaFiles.EpisodeImport +namespace NzbDrone.Core.MediaFiles.TrackImport { public enum ImportResultType { diff --git a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs index d6c270d2c..0e952a676 100644 --- a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs @@ -9,7 +9,8 @@ namespace NzbDrone.Core.MediaFiles { public interface IUpgradeMediaFiles { - EpisodeFileMoveResult UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode, bool copyOnly = false); + //EpisodeFileMoveResult UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode, bool copyOnly = false); + TrackFileMoveResult UpgradeTrackFile(TrackFile trackFile, LocalTrack localTrack, bool copyOnly = false); } public class UpgradeMediaFileService : IUpgradeMediaFiles @@ -17,35 +18,36 @@ namespace NzbDrone.Core.MediaFiles private readonly IRecycleBinProvider _recycleBinProvider; private readonly IMediaFileService _mediaFileService; private readonly IMoveEpisodeFiles _episodeFileMover; + private readonly IMoveTrackFiles _trackFileMover; private readonly IDiskProvider _diskProvider; private readonly Logger _logger; public UpgradeMediaFileService(IRecycleBinProvider recycleBinProvider, IMediaFileService mediaFileService, - IMoveEpisodeFiles episodeFileMover, + IMoveTrackFiles trackFileMover, IDiskProvider diskProvider, Logger logger) { _recycleBinProvider = recycleBinProvider; _mediaFileService = mediaFileService; - _episodeFileMover = episodeFileMover; + _trackFileMover = trackFileMover; _diskProvider = diskProvider; _logger = logger; } - public EpisodeFileMoveResult UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode, bool copyOnly = false) + public TrackFileMoveResult UpgradeTrackFile(TrackFile trackFile, LocalTrack localTrack, bool copyOnly = false) { - var moveFileResult = new EpisodeFileMoveResult(); - var existingFiles = localEpisode.Episodes - .Where(e => e.EpisodeFileId > 0) - .Select(e => e.EpisodeFile.Value) + var moveFileResult = new TrackFileMoveResult(); + var existingFiles = localTrack.Tracks + .Where(e => e.TrackFileId > 0) + .Select(e => e.TrackFile.Value) .GroupBy(e => e.Id); foreach (var existingFile in existingFiles) { var file = existingFile.First(); - var episodeFilePath = Path.Combine(localEpisode.Series.Path, file.RelativePath); - var subfolder = _diskProvider.GetParentFolder(localEpisode.Series.Path).GetRelativePath(_diskProvider.GetParentFolder(episodeFilePath)); + var episodeFilePath = Path.Combine(localTrack.Artist.Path, file.RelativePath); + var subfolder = _diskProvider.GetParentFolder(localTrack.Artist.Path).GetRelativePath(_diskProvider.GetParentFolder(episodeFilePath)); if (_diskProvider.FileExists(episodeFilePath)) { @@ -59,11 +61,11 @@ namespace NzbDrone.Core.MediaFiles if (copyOnly) { - moveFileResult.EpisodeFile = _episodeFileMover.CopyEpisodeFile(episodeFile, localEpisode); + moveFileResult.TrackFile = _trackFileMover.CopyTrackFile(trackFile, localTrack); } else { - moveFileResult.EpisodeFile = _episodeFileMover.MoveEpisodeFile(episodeFile, localEpisode); + moveFileResult.TrackFile = _trackFileMover.MoveTrackFile(trackFile, localTrack); } return moveFileResult; diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index 5a76a4c8e..d2db40331 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -205,7 +205,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook var lowerTitle = title.ToLowerInvariant(); Console.WriteLine("Searching for " + lowerTitle); - if (lowerTitle.StartsWith("itunes:") || lowerTitle.StartsWith("itunesid:")) + if (lowerTitle.StartsWith("spotify:") || lowerTitle.StartsWith("spotifyid:")) { var slug = lowerTitle.Split(':')[1].Trim(); diff --git a/src/NzbDrone.Core/Music/TrackService.cs b/src/NzbDrone.Core/Music/TrackService.cs index cf09139e5..7b57b7749 100644 --- a/src/NzbDrone.Core/Music/TrackService.cs +++ b/src/NzbDrone.Core/Music/TrackService.cs @@ -55,11 +55,16 @@ namespace NzbDrone.Core.Music return _trackRepository.Get(ids).ToList(); } - public Track FindTrack(string artistId, string albumId, int episodeNumber) + public Track FindTrack(string artistId, string albumId, int trackNumber) { - return _trackRepository.Find(artistId, albumId, episodeNumber); + return _trackRepository.Find(artistId, albumId, trackNumber); } + //public Track FindTrack(string artistId, int trackNumber) + //{ + // return _trackRepository.Find(artistId, trackNumber); + //} + public List GetTracksByArtist(string artistId) { return _trackRepository.GetTracks(artistId).ToList(); @@ -132,7 +137,7 @@ namespace NzbDrone.Core.Music _trackRepository.SetMonitoredByAlbum(artistId, albumId, monitored); } - public void UpdateEpisodes(List tracks) + public void UpdateTracks(List tracks) { _trackRepository.UpdateMany(tracks); } @@ -182,10 +187,5 @@ namespace NzbDrone.Core.Music _logger.Debug("Linking [{0}] > [{1}]", message.TrackFile.RelativePath, track); } } - - public void UpdateTracks(List tracks) - { - _trackRepository.UpdateMany(tracks); - } } } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 65494030b..e0793e352 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -725,7 +725,10 @@ - + + + + @@ -740,12 +743,10 @@ - - - + @@ -761,11 +762,14 @@ + + + @@ -789,6 +793,9 @@ + + + @@ -928,6 +935,7 @@ + diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index be6ed1da3..22e0e07be 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -380,33 +380,34 @@ namespace NzbDrone.Core.Parser if (result != null) { - if (result.FullSeason && title.ContainsIgnoreCase("Special")) - { - result.FullSeason = false; - result.Special = true; - } + //if (result.FullSeason && title.ContainsIgnoreCase("Special")) + //{ + // result.FullSeason = false; + // result.Special = true; + //} - result.Language = LanguageParser.ParseLanguage(title); - Logger.Debug("Language parsed: {0}", result.Language); + //result.Language = LanguageParser.ParseLanguage(title); + //Logger.Debug("Language parsed: {0}", result.Language); result.Quality = QualityParser.ParseQuality(title); Logger.Debug("Quality parsed: {0}", result.Quality); - result.ReleaseGroup = ParseReleaseGroup(title); + // Majora: We don't currently need Release Group for Music. + //result.ReleaseGroup = ParseReleaseGroup(title); - var subGroup = GetSubGroup(match); - if (!subGroup.IsNullOrWhiteSpace()) - { - result.ReleaseGroup = subGroup; - } + //var subGroup = GetSubGroup(match); + //if (!subGroup.IsNullOrWhiteSpace()) + //{ + // result.ReleaseGroup = subGroup; + //} - Logger.Debug("Release Group parsed: {0}", result.ReleaseGroup); + //Logger.Debug("Release Group parsed: {0}", result.ReleaseGroup); - result.ReleaseHash = GetReleaseHash(match); - if (!result.ReleaseHash.IsNullOrWhiteSpace()) - { - Logger.Debug("Release Hash parsed: {0}", result.ReleaseHash); - } + //result.ReleaseHash = GetReleaseHash(match); + //if (!result.ReleaseHash.IsNullOrWhiteSpace()) + //{ + // Logger.Debug("Release Hash parsed: {0}", result.ReleaseHash); + //} return result; } diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index 333848512..1c4ff67d0 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -457,7 +457,9 @@ namespace NzbDrone.Core.Parser if (trackInfo == null) { - trackInfo = _trackService.FindTrack(artist.SpotifyId, trackNumber); + // TODO: [ParsingService]: FindTrack by artistID and trackNumber (or albumID and trackNumber if we change db schema to album as base) + _logger.Debug("TrackInfo is null, we will not add as FindTrack(artistId, trackNumber) is not implemented"); + //trackInfo = _trackService.FindTrack(artist.SpotifyId, trackNumber); } if (trackInfo != null) @@ -467,7 +469,7 @@ namespace NzbDrone.Core.Parser else { - _logger.Debug("Unable to find {0}", parsedEpisodeInfo); + _logger.Debug("Unable to find {0}", parsedTrackInfo); } } @@ -553,6 +555,7 @@ namespace NzbDrone.Core.Parser return result; } + public LocalTrack GetLocalTrack(string filename, Artist artist) { return GetLocalTrack(filename, artist, null); @@ -635,7 +638,60 @@ namespace NzbDrone.Core.Parser } return GetStandardEpisodes(artist, parsedTrackInfo, sceneSource, searchCriteria);*/ - return GetStandardTracks(artist, parsedTrackInfo, searchCriteria); + return GetStandardTracks(artist, parsedTrackInfo); + } + + private List GetStandardTracks(Artist artist, ParsedTrackInfo parsedTrackInfo) + { + var result = new List(); + //var seasonNumber = parsedEpisodeInfo.SeasonNumber; + + //if (sceneSource) + //{ + // var sceneMapping = _sceneMappingService.FindSceneMapping(parsedEpisodeInfo.SeriesTitle); + + // if (sceneMapping != null && sceneMapping.SeasonNumber.HasValue && sceneMapping.SeasonNumber.Value >= 0 && + // sceneMapping.SceneSeasonNumber == seasonNumber) + // { + // seasonNumber = sceneMapping.SeasonNumber.Value; + // } + //} + + if (parsedTrackInfo.TrackNumbers == null) + { + return new List(); + } + + foreach (var trackNumber in parsedTrackInfo.TrackNumbers) + { + + + Track trackInfo = null; + + //if (searchCriteria != null) + //{ + // trackInfo = searchCriteria.Episodes.SingleOrDefault(e => e.SeasonNumber == seasonNumber && e.EpisodeNumber == trackNumber); + //} + + if (trackInfo == null) + { + // TODO: [ParsingService]: FindTrack by artistID and trackNumber (or albumID and trackNumber if we change db schema to album as base) + _logger.Debug("TrackInfo is null, we will not add as FindTrack(artistId, trackNumber) is not implemented"); + //trackInfo = _trackService.FindTrack(artist.SpotifyId, trackNumber); //series.Id, seasonNumber, trackNumber + } + + if (trackInfo != null) + { + result.Add(trackInfo); + } + + else + { + _logger.Debug("Unable to find {0}", parsedTrackInfo); + } + } + + return result; } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Tv/RefreshSeriesService.cs b/src/NzbDrone.Core/Tv/RefreshSeriesService.cs index f177b5857..39e32dbe7 100644 --- a/src/NzbDrone.Core/Tv/RefreshSeriesService.cs +++ b/src/NzbDrone.Core/Tv/RefreshSeriesService.cs @@ -176,7 +176,7 @@ namespace NzbDrone.Core.Tv try { _logger.Info("Skipping refresh of series: {0}", series.Title); - _diskScanService.Scan(series); + //_diskScanService.Scan(series); } catch (Exception e) { From 7dcacffec72e7d765febe98bb4d1c48c770019da Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Mon, 29 May 2017 13:05:18 -0500 Subject: [PATCH 03/13] Added some extra code around erroneous Qualities in ProfileService --- src/NzbDrone.Core/Profiles/ProfileService.cs | 9 +++++---- src/NzbDrone.Core/Qualities/Quality.cs | 4 ++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/NzbDrone.Core/Profiles/ProfileService.cs b/src/NzbDrone.Core/Profiles/ProfileService.cs index 37014191c..1317b10b9 100644 --- a/src/NzbDrone.Core/Profiles/ProfileService.cs +++ b/src/NzbDrone.Core/Profiles/ProfileService.cs @@ -6,6 +6,7 @@ using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; namespace NzbDrone.Core.Profiles { @@ -22,13 +23,13 @@ namespace NzbDrone.Core.Profiles public class ProfileService : IProfileService, IHandle { private readonly IProfileRepository _profileRepository; - private readonly ISeriesService _seriesService; + private readonly IArtistService _artistService; private readonly Logger _logger; - public ProfileService(IProfileRepository profileRepository, ISeriesService seriesService, Logger logger) + public ProfileService(IProfileRepository profileRepository, IArtistService artistService, Logger logger) { _profileRepository = profileRepository; - _seriesService = seriesService; + _artistService = artistService; _logger = logger; } @@ -44,7 +45,7 @@ namespace NzbDrone.Core.Profiles public void Delete(int id) { - if (_seriesService.GetAllSeries().Any(c => c.ProfileId == id)) + if (_artistService.GetAllArtists().Any(c => c.ProfileId == id)) { throw new ProfileInUseException(id); } diff --git a/src/NzbDrone.Core/Qualities/Quality.cs b/src/NzbDrone.Core/Qualities/Quality.cs index acb949cc2..4a8b45026 100644 --- a/src/NzbDrone.Core/Qualities/Quality.cs +++ b/src/NzbDrone.Core/Qualities/Quality.cs @@ -103,6 +103,10 @@ namespace NzbDrone.Core.Qualities public static Quality FindById(int id) { if (id == 0) return Unknown; + else if (id > AllLookup.Length) + { + throw new ArgumentException("ID does not match a known quality", nameof(id)); + } var quality = AllLookup[id]; From 0f3c35538142842f4d5de319f7ccb027251acdc7 Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 12 Jun 2017 22:02:17 -0400 Subject: [PATCH 04/13] New DB Schema Rearrange DB Schema for Album Centric Plan --- src/NzbDrone.Api/Music/ArtistModule.cs | 2 +- src/NzbDrone.Api/Music/ArtistResource.cs | 20 +- .../Datastore/Migration/111_setup_music.cs | 101 +++++--- .../112_Add_music_fields_to_NamingConfig.cs | 15 -- src/NzbDrone.Core/Datastore/TableMapping.cs | 4 +- .../Download/AlbumGrabbedEvent.cs | 17 ++ ...{AlbumInfoResource.cs => AlbumResource.cs} | 4 +- .../SkyHook/Resource/ArtistResource.cs | 49 +--- ...{TrackInfoResource.cs => TrackResource.cs} | 4 +- .../MetadataSource/SkyHook/SkyHookProxy.cs | 216 ++++++++++-------- src/NzbDrone.Core/Music/AddArtistService.cs | 10 +- src/NzbDrone.Core/Music/AddArtistValidator.cs | 2 +- src/NzbDrone.Core/Music/Album.cs | 2 +- src/NzbDrone.Core/Music/Artist.cs | 21 +- src/NzbDrone.Core/Music/ArtistRepository.cs | 6 +- src/NzbDrone.Core/Music/ArtistService.cs | 8 +- .../Music/ArtistSlugValidator.cs | 2 +- .../Music/RefreshArtistService.cs | 26 +-- .../Music/RefreshTrackService.cs | 6 +- .../Music/ShouldRefreshArtist.cs | 4 +- src/NzbDrone.Core/Music/TrackRepository.cs | 4 +- src/NzbDrone.Core/Music/TrackService.cs | 2 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 5 +- .../Organizer/FileNameBuilder.cs | 4 +- src/NzbDrone.Core/Parser/Model/RemoteAlbum.cs | 26 +++ .../Validation/Paths/ArtistExistsValidator.cs | 2 +- src/UI/AddArtist/EmptyViewTemplate.hbs | 2 +- src/UI/AddArtist/SearchResultView.js | 2 +- src/UI/AddArtist/SearchResultViewTemplate.hbs | 8 +- src/UI/Artist/ArtistCollection.js | 2 +- src/UI/Artist/ArtistController.js | 4 +- src/UI/Artist/Details/ArtistDetailsLayout.js | 2 +- .../Artist/Details/ArtistDetailsTemplate.hbs | 20 +- src/UI/Artist/Details/InfoView.js | 8 +- src/UI/Artist/Details/InfoViewTemplate.hbs | 2 +- src/UI/Artist/Editor/ArtistEditorLayout.js | 2 +- .../ArtistOverviewItemViewTemplate.hbs | 2 +- src/UI/Handlebars/Helpers/Artist.js | 2 +- 38 files changed, 335 insertions(+), 283 deletions(-) delete mode 100644 src/NzbDrone.Core/Datastore/Migration/112_Add_music_fields_to_NamingConfig.cs create mode 100644 src/NzbDrone.Core/Download/AlbumGrabbedEvent.cs rename src/NzbDrone.Core/MetadataSource/SkyHook/Resource/{AlbumInfoResource.cs => AlbumResource.cs} (92%) rename src/NzbDrone.Core/MetadataSource/SkyHook/Resource/{TrackInfoResource.cs => TrackResource.cs} (88%) create mode 100644 src/NzbDrone.Core/Parser/Model/RemoteAlbum.cs diff --git a/src/NzbDrone.Api/Music/ArtistModule.cs b/src/NzbDrone.Api/Music/ArtistModule.cs index d7ef5fed3..7f0065d64 100644 --- a/src/NzbDrone.Api/Music/ArtistModule.cs +++ b/src/NzbDrone.Api/Music/ArtistModule.cs @@ -71,7 +71,7 @@ namespace NzbDrone.Api.Music PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace()); PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace()); - PostValidator.RuleFor(s => s.SpotifyId).NotEqual("").SetValidator(artistExistsValidator); + PostValidator.RuleFor(s => s.ForeignArtistId).NotEqual("").SetValidator(artistExistsValidator); PutValidator.RuleFor(s => s.Path).IsValidPath(); } diff --git a/src/NzbDrone.Api/Music/ArtistResource.cs b/src/NzbDrone.Api/Music/ArtistResource.cs index 71cc14b85..d75fbebdb 100644 --- a/src/NzbDrone.Api/Music/ArtistResource.cs +++ b/src/NzbDrone.Api/Music/ArtistResource.cs @@ -18,8 +18,8 @@ namespace NzbDrone.Api.Music //View Only - public string ArtistName { get; set; } - public string SpotifyId { get; set; } + public string Name { get; set; } + public string ForeignArtistId { get; set; } public string Overview { get; set; } public int AlbumCount @@ -59,7 +59,7 @@ namespace NzbDrone.Api.Music public DateTime Added { get; set; } public AddSeriesOptions AddOptions { get; set; } public Ratings Ratings { get; set; } - public string ArtistSlug { get; internal set; } + public string NameSlug { get; set; } } public static class ArtistResourceMapper @@ -72,7 +72,7 @@ namespace NzbDrone.Api.Music { Id = model.Id, - ArtistName = model.ArtistName, + Name = model.Name, //AlternateTitles //SortTitle = resource.SortTitle, @@ -94,7 +94,6 @@ namespace NzbDrone.Api.Music Path = model.Path, ProfileId = model.ProfileId, - ArtistFolder = model.ArtistFolder, Monitored = model.Monitored, //UseSceneNumbering = resource.UseSceneNumbering, @@ -105,8 +104,8 @@ namespace NzbDrone.Api.Music //FirstAired = resource.FirstAired, //LastInfoSync = resource.LastInfoSync, //SeriesType = resource.SeriesType, - SpotifyId = model.SpotifyId, - ArtistSlug = model.ArtistSlug, + ForeignArtistId = model.ForeignArtistId, + NameSlug = model.NameSlug, RootFolderPath = model.RootFolderPath, Genres = model.Genres, @@ -125,7 +124,7 @@ namespace NzbDrone.Api.Music { Id = resource.Id, - ArtistName = resource.ArtistName, + Name = resource.Name, //AlternateTitles //SortTitle = resource.SortTitle, @@ -147,11 +146,10 @@ namespace NzbDrone.Api.Music Path = resource.Path, ProfileId = resource.ProfileId, - ArtistFolder = resource.ArtistFolder, Monitored = resource.Monitored, //LastInfoSync = resource.LastInfoSync, - SpotifyId = resource.SpotifyId, - ArtistSlug = resource.ArtistSlug, + ForeignArtistId = resource.ForeignArtistId, + NameSlug = resource.NameSlug, RootFolderPath = resource.RootFolderPath, Genres = resource.Genres, diff --git a/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs b/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs index 12b0c2d0b..57decb35d 100644 --- a/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs +++ b/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs @@ -12,64 +12,91 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - Create.TableForModel("Artist") - .WithColumn("SpotifyId").AsString().Nullable().Unique() - .WithColumn("ArtistName").AsString().Unique() - .WithColumn("ArtistSlug").AsString().Nullable() //.Unique() - .WithColumn("CleanTitle").AsString().Nullable() // Do we need this? - .WithColumn("Monitored").AsBoolean() + Create.TableForModel("Artists") + .WithColumn("ForeignArtistId").AsString().Unique() + .WithColumn("MBId").AsString().Nullable() + .WithColumn("AMId").AsString().Nullable() + .WithColumn("TADBId").AsInt32().Nullable() + .WithColumn("DiscogsId").AsInt32().Nullable() + .WithColumn("Name").AsString() + .WithColumn("NameSlug").AsString().Nullable().Unique() + .WithColumn("CleanName").AsString().Indexed() + .WithColumn("Status").AsInt32() .WithColumn("Overview").AsString().Nullable() - .WithColumn("AlbumFolder").AsBoolean().Nullable() - .WithColumn("ArtistFolder").AsBoolean().Nullable() + .WithColumn("Images").AsString() + .WithColumn("Path").AsString().Indexed() + .WithColumn("Monitored").AsBoolean() + .WithColumn("AlbumFolder").AsBoolean() .WithColumn("LastInfoSync").AsDateTime().Nullable() .WithColumn("LastDiskSync").AsDateTime().Nullable() - .WithColumn("Status").AsInt32().Nullable() - .WithColumn("Path").AsString() - .WithColumn("Images").AsString().Nullable() - .WithColumn("QualityProfileId").AsInt32().Nullable() - .WithColumn("RootFolderPath").AsString().Nullable() - .WithColumn("Added").AsDateTime().Nullable() - .WithColumn("ProfileId").AsInt32().Nullable() // This is either ProfileId or Profile + .WithColumn("DateFormed").AsDateTime().Nullable() + .WithColumn("Members").AsString().Nullable() + .WithColumn("Ratings").AsString().Nullable() .WithColumn("Genres").AsString().Nullable() - .WithColumn("Albums").AsString().Nullable() + .WithColumn("SortName").AsString().Nullable() + .WithColumn("ProfileId").AsInt32().Nullable() .WithColumn("Tags").AsString().Nullable() - .WithColumn("AddOptions").AsString().Nullable() - ; + .WithColumn("Added").AsDateTime().Nullable() + .WithColumn("AddOptions").AsString().Nullable(); Create.TableForModel("Albums") - .WithColumn("AlbumId").AsString().Unique() - .WithColumn("ArtistId").AsInt32() // Should this be artistId (string) + .WithColumn("ForeignArtistId").AsString().Unique() + .WithColumn("ArtistId").AsInt32() + .WithColumn("MBId").AsString().Indexed() + .WithColumn("AMId").AsString().Nullable() + .WithColumn("TADBId").AsInt32().Indexed() + .WithColumn("DiscogsId").AsInt32().Nullable() .WithColumn("Title").AsString() - .WithColumn("Year").AsInt32() - .WithColumn("Image").AsInt32() - .WithColumn("TrackCount").AsInt32() - .WithColumn("DiscCount").AsInt32() + .WithColumn("TitleSlug").AsString().Nullable().Unique() + .WithColumn("CleanTitle").AsString().Indexed() + .WithColumn("Status").AsInt32() + .WithColumn("Overview").AsString().Nullable() + .WithColumn("Images").AsString() + .WithColumn("Path").AsString().Indexed() .WithColumn("Monitored").AsBoolean() - .WithColumn("Overview").AsString(); + .WithColumn("LastInfoSync").AsDateTime().Nullable() + .WithColumn("LastDiskSync").AsDateTime().Nullable() + .WithColumn("ReleaseDate").AsDateTime().Nullable() + .WithColumn("Ratings").AsString().Nullable() + .WithColumn("Genres").AsString().Nullable() + .WithColumn("Label").AsString().Nullable() + .WithColumn("SortTitle").AsString().Nullable() + .WithColumn("ProfileId").AsInt32().Nullable() + .WithColumn("Tags").AsString().Nullable() + .WithColumn("Added").AsDateTime().Nullable() + .WithColumn("AlbumType").AsString() + .WithColumn("AddOptions").AsString().Nullable(); Create.TableForModel("Tracks") - .WithColumn("SpotifyTrackId").AsString().Nullable() // This shouldn't be nullable, but TrackRepository won't behave. Someone please fix this. - .WithColumn("AlbumId").AsString() - .WithColumn("ArtistId").AsString() // This may be a list of Ids in future for compilations - .WithColumn("ArtistSpotifyId").AsString() - .WithColumn("Compilation").AsBoolean() + .WithColumn("ArtistId").AsInt32().Indexed() + .WithColumn("AlbumId").AsInt32() + .WithColumn("MBId").AsString().Indexed() .WithColumn("TrackNumber").AsInt32() .WithColumn("Title").AsString().Nullable() - .WithColumn("Ignored").AsBoolean().Nullable() - .WithColumn("Explict").AsBoolean() + .WithColumn("Explicit").AsBoolean() + .WithColumn("DiscNumber").AsInt32().Nullable() + .WithColumn("TrackFileId").AsInt32().Nullable().Indexed() .WithColumn("Monitored").AsBoolean() - .WithColumn("TrackFileId").AsInt32().Nullable() - .WithColumn("ReleaseDate").AsDateTime().Nullable(); + .WithColumn("Ratings").AsString().Nullable(); + Create.Index().OnTable("Tracks").OnColumn("ArtistId").Ascending() + .OnColumn("AlbumId").Ascending() + .OnColumn("TrackNumber").Ascending(); Create.TableForModel("TrackFiles") - .WithColumn("ArtistId").AsInt32() - .WithColumn("Path").AsString().Unique() + .WithColumn("ArtistId").AsInt32().Indexed() + .WithColumn("AlbumId").AsInt32().Indexed() .WithColumn("Quality").AsString() .WithColumn("Size").AsInt64() .WithColumn("DateAdded").AsDateTime() - .WithColumn("AlbumId").AsInt32(); // How does this impact stand alone tracks? + .WithColumn("SceneName").AsString().Nullable() + .WithColumn("ReleaseGroup").AsString().Nullable() + .WithColumn("MediaInfo").AsString().Nullable() + .WithColumn("RelativePath").AsString().Nullable(); + Alter.Table("NamingConfig") + .AddColumn("ArtistFolderFormat").AsString().Nullable() + .AddColumn("AlbumFolderFormat").AsString().Nullable(); } } diff --git a/src/NzbDrone.Core/Datastore/Migration/112_Add_music_fields_to_NamingConfig.cs b/src/NzbDrone.Core/Datastore/Migration/112_Add_music_fields_to_NamingConfig.cs deleted file mode 100644 index b855e1e28..000000000 --- a/src/NzbDrone.Core/Datastore/Migration/112_Add_music_fields_to_NamingConfig.cs +++ /dev/null @@ -1,15 +0,0 @@ -using FluentMigrator; -using NzbDrone.Core.Datastore.Migration.Framework; - -namespace NzbDrone.Core.Datastore.Migration -{ - [Migration(112)] - public class add_music_fields_to_namingconfig : NzbDroneMigrationBase - { - protected override void MainDbUpgrade() - { - Alter.Table("NamingConfig").AddColumn("ArtistFolderFormat").AsAnsiString().Nullable(); - Alter.Table("NamingConfig").AddColumn("AlbumFolderFormat").AsAnsiString().Nullable(); - } - } -} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index 9b9905adc..db5eaa1ac 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -92,11 +92,13 @@ namespace NzbDrone.Core.Datastore .Relationship() .HasOne(episode => episode.EpisodeFile, episode => episode.EpisodeFileId); - Mapper.Entity().RegisterModel("Artist") + Mapper.Entity().RegisterModel("Artists") .Ignore(s => s.RootFolderPath) .Relationship() .HasOne(a => a.Profile, a => a.ProfileId); + Mapper.Entity().RegisterModel("Album"); + Mapper.Entity().RegisterModel("TrackFiles") .Ignore(f => f.Path) .Relationships.AutoMapICollectionOrComplexProperties() diff --git a/src/NzbDrone.Core/Download/AlbumGrabbedEvent.cs b/src/NzbDrone.Core/Download/AlbumGrabbedEvent.cs new file mode 100644 index 000000000..5b7972802 --- /dev/null +++ b/src/NzbDrone.Core/Download/AlbumGrabbedEvent.cs @@ -0,0 +1,17 @@ +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Download +{ + public class AlbumGrabbedEvent : IEvent + { + public RemoteAlbum Album { get; private set; } + public string DownloadClient { get; set; } + public string DownloadId { get; set; } + + public AlbumGrabbedEvent(RemoteAlbum album) + { + Album = album; + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumInfoResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs similarity index 92% rename from src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumInfoResource.cs rename to src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs index 7f478ea3c..7c1a918f0 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumInfoResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs @@ -5,9 +5,9 @@ using System.Text; namespace NzbDrone.Core.MetadataSource.SkyHook.Resource { - public class AlbumInfoResource + public class AlbumResource { - public AlbumInfoResource() + public AlbumResource() { } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs index d85d675a4..c2c922d1b 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs @@ -5,47 +5,20 @@ using System.Text; namespace NzbDrone.Core.MetadataSource.SkyHook.Resource { - - public class AristResultResource - { - public AristResultResource() - { - - } - - public List Items { get; set; } - public int Count { get; set; } - } - - public class AlbumResultResource - { - public AlbumResultResource() - { - - } - - public List Items { get; set; } - public int Count { get; set; } - } - - public class TrackResultResource - { - public TrackResultResource() - { - - } - - public List Items { get; set; } - public int Count { get; set; } - } public class ArtistResource { - public ArtistResource() - { - + public ArtistResource() { + Albums = new List(); + Tracks = new List(); } - public AristResultResource Artists { get; set; } - public AristResultResource Albums { get; set; } + public List Genres { get; set; } + public string AristUrl { get; set; } + public string Overview { get; set; } + public string Id { get; set; } + public List Images { get; set; } + public string ArtistName { get; set; } + public List Albums { get; set; } + public List Tracks { get; set; } } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackInfoResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackResource.cs similarity index 88% rename from src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackInfoResource.cs rename to src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackResource.cs index 3bdf71977..c2d4eddd2 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackInfoResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackResource.cs @@ -5,9 +5,9 @@ using System.Text; namespace NzbDrone.Core.MetadataSource.SkyHook.Resource { - public class TrackInfoResource + public class TrackResource { - public TrackInfoResource() + public TrackResource() { } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index 093fb88e5..f4ae40a16 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -108,92 +108,96 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } // It is safe to assume an id will only return one Artist back - Artist artist = new Artist(); - artist.ArtistName = httpResponse.Resource.Artists.Items[0].ArtistName; - artist.SpotifyId = httpResponse.Resource.Artists.Items[0].Id; - artist.Genres = httpResponse.Resource.Artists.Items[0].Genres; - - var albumRet = MapAlbums(artist); - artist = albumRet.Item1; - - - - return new Tuple>(albumRet.Item1, albumRet.Item2); - } - - private Tuple> MapAlbums(Artist artist) - { - - // Find all albums for the artist and all tracks for said album - ///v1/artists/{id}/albums - var httpRequest = _requestBuilder.Create() - .SetSegment("route", "artists/" + artist.SpotifyId + "/albums") - .Build(); - httpRequest.AllowAutoRedirect = true; - httpRequest.SuppressHttpError = true; + var albums = httpResponse.Resource.Albums.Select(MapAlbum); + var tracks = httpResponse.Resource.Tracks.Select(MapTrack); + var artist = MapArtist(httpResponse.Resource); - var httpResponse = _httpClient.Get(httpRequest); - - if (httpResponse.HasHttpError) - { - throw new HttpException(httpRequest, httpResponse); - } - - List masterTracks = new List(); - List albums = new List(); - foreach(var albumResource in httpResponse.Resource.Items) - { - Album album = new Album(); - album.AlbumId = albumResource.Id; - album.Title = albumResource.AlbumName; - album.ArtworkUrl = albumResource.Images.Count > 0 ? albumResource.Images[0].Url : ""; - album.Tracks = MapTracksToAlbum(album); - masterTracks.InsertRange(masterTracks.Count, album.Tracks); - albums.Add(album); - } + //artist.Name = httpResponse.Resource.Artists.Items[0].ArtistName; + + //artist.ForeignArtistId = httpResponse.Resource.Artists.Items[0].Id; + //artist.Genres = httpResponse.Resource.Artists.Items[0].Genres; - // TODO: We now need to get all tracks for each album + //var albumRet = MapAlbums(artist); + //artist = albumRet.Item1; - artist.Albums = albums; - return new Tuple>(artist, masterTracks); + return new Tuple>(artist, tracks.ToList()); } - private List MapTracksToAlbum(Album album) - { - var httpRequest = _requestBuilder.Create() - .SetSegment("route", "albums/" + album.AlbumId + "/tracks") - .Build(); - - httpRequest.AllowAutoRedirect = true; - httpRequest.SuppressHttpError = true; - var httpResponse = _httpClient.Get(httpRequest); - if (httpResponse.HasHttpError) - { - throw new HttpException(httpRequest, httpResponse); - } + //private Tuple> MapAlbums(Artist artist) + //{ - List tracks = new List(); - foreach(var trackResource in httpResponse.Resource.Items) - { - Track track = new Track(); - track.AlbumId = album.AlbumId; - //track.Album = album; // This will cause infinite loop when trying to serialize. - // TODO: Implement more track mapping - //track.Artist = trackResource.Artists - //track.ArtistId = album. - track.SpotifyTrackId = trackResource.Id; - track.ArtistSpotifyId = trackResource.Artists.Count > 0 ? trackResource.Artists[0].Id : null; - track.Explict = trackResource.Explicit; - track.Compilation = trackResource.Artists.Count > 1; - track.TrackNumber = trackResource.TrackNumber; - track.Title = trackResource.TrackName; - tracks.Add(track); - } + // // Find all albums for the artist and all tracks for said album + // ///v1/artists/{id}/albums + // var httpRequest = _requestBuilder.Create() + // .SetSegment("route", "artists/" + artist.ForeignArtistId + "/albums") + // .Build(); + // httpRequest.AllowAutoRedirect = true; + // httpRequest.SuppressHttpError = true; + + // var httpResponse = _httpClient.Get(httpRequest); + + // if (httpResponse.HasHttpError) + // { + // throw new HttpException(httpRequest, httpResponse); + // } + + // List masterTracks = new List(); + // List albums = new List(); + // foreach(var albumResource in httpResponse.Resource.Items) + // { + // Album album = new Album(); + // album.AlbumId = albumResource.Id; + // album.Title = albumResource.AlbumName; + // album.ArtworkUrl = albumResource.Images.Count > 0 ? albumResource.Images[0].Url : ""; + // album.Tracks = MapTracksToAlbum(album); + // masterTracks.InsertRange(masterTracks.Count, album.Tracks); + // albums.Add(album); + // } + + // // TODO: We now need to get all tracks for each album + + // artist.Albums = albums; + // return new Tuple>(artist, masterTracks); + //} - return tracks; - } + //private List MapTracksToAlbum(Album album) + //{ + // var httpRequest = _requestBuilder.Create() + // .SetSegment("route", "albums/" + album.AlbumId + "/tracks") + // .Build(); + + // httpRequest.AllowAutoRedirect = true; + // httpRequest.SuppressHttpError = true; + + // var httpResponse = _httpClient.Get(httpRequest); + + // if (httpResponse.HasHttpError) + // { + // throw new HttpException(httpRequest, httpResponse); + // } + + // List tracks = new List(); + // foreach(var trackResource in httpResponse.Resource.Items) + // { + // Track track = new Track(); + // track.AlbumId = album.AlbumId; + // //track.Album = album; // This will cause infinite loop when trying to serialize. + // // TODO: Implement more track mapping + // //track.Artist = trackResource.Artists + // //track.ArtistId = album. + // track.SpotifyTrackId = trackResource.Id; + // track.ArtistSpotifyId = trackResource.Artists.Count > 0 ? trackResource.Artists[0].Id : null; + // track.Explict = trackResource.Explicit; + // track.Compilation = trackResource.Artists.Count > 1; + // track.TrackNumber = trackResource.TrackNumber; + // track.Title = trackResource.TrackName; + // tracks.Add(track); + // } + + // return tracks; + //} public List SearchForNewArtist(string title) @@ -230,12 +234,26 @@ namespace NzbDrone.Core.MetadataSource.SkyHook - var httpResponse = _httpClient.Get(httpRequest); + var httpResponse = _httpClient.Get>(httpRequest); + + return httpResponse.Resource.SelectList(MapArtist); + //List artists = MapArtists(httpResponse.Resource); + //List artists = new List(); + //foreach (var artistResource in httpResponse.Resource.Artists.Items) + //{ + // Artist artist = new Artist(); + // artist.Name = artistResource.ArtistName; + // artist.ForeignArtistId = artistResource.Id; // TODO: Rename spotifyId to LidarrId + // artist.Genres = artistResource.Genres; + // artist.NameSlug = Parser.Parser.CleanArtistTitle(artist.Name); + // artist.CleanName = Parser.Parser.CleanArtistTitle(artist.Name); + //artist.Images = artistResource.Images; + // artists.Add(artist); + //} - List artists = MapArtists(httpResponse.Resource); - return artists; + //return artists; } catch (HttpException) { @@ -248,27 +266,37 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } } - private List MapArtists(ArtistResource resource) + private static Album MapAlbum(AlbumResource resource) + { + Album album = new Album(); + return album; + } + + private static Track MapTrack(TrackResource resource) + { + Track track = new Track(); + return track; + } + + private static Artist MapArtist(ArtistResource resource) { + Artist artist = new Artist(); + + artist.Name = resource.ArtistName; + artist.ForeignArtistId = resource.Id; // TODO: Rename spotifyId to LidarrId + artist.Genres = resource.Genres; + artist.Overview = resource.Overview; + artist.NameSlug = Parser.Parser.CleanArtistTitle(artist.Name); + artist.CleanName = Parser.Parser.CleanArtistTitle(artist.Name); + //artist.Images = resource.Artists.Items[0].Images; + - List artists = new List(); - foreach(var artistResource in resource.Artists.Items) - { - Artist artist = new Artist(); - artist.ArtistName = artistResource.ArtistName; - artist.SpotifyId = artistResource.Id; // TODO: Rename spotifyId to LidarrId - artist.Genres = artistResource.Genres; - artist.ArtistSlug = Parser.Parser.CleanArtistTitle(artist.ArtistName); - //artist.Images = artistResource.Images; - - artists.Add(artist); - } // Maybe? Get all the albums for said artist - return artists; + return artist; } //private Album MapAlbum(AlbumResource albumQuery) diff --git a/src/NzbDrone.Core/Music/AddArtistService.cs b/src/NzbDrone.Core/Music/AddArtistService.cs index 2a1358b5b..aab228341 100644 --- a/src/NzbDrone.Core/Music/AddArtistService.cs +++ b/src/NzbDrone.Core/Music/AddArtistService.cs @@ -48,11 +48,11 @@ namespace NzbDrone.Core.Music if (string.IsNullOrWhiteSpace(newArtist.Path)) { - var folderName = newArtist.ArtistName;// TODO: _fileNameBuilder.GetArtistFolder(newArtist); + var folderName = newArtist.Name;// TODO: _fileNameBuilder.GetArtistFolder(newArtist); newArtist.Path = Path.Combine(newArtist.RootFolderPath, folderName); } - newArtist.CleanTitle = newArtist.ArtistName.CleanSeriesTitle(); + newArtist.CleanName = newArtist.Name.CleanSeriesTitle(); //newArtist.SortTitle = ArtistNameNormalizer.Normalize(newArtist.ArtistName, newArtist.ItunesId); // There is no Sort Title newArtist.Added = DateTime.UtcNow; @@ -75,15 +75,15 @@ namespace NzbDrone.Core.Music try { - tuple = _artistInfo.GetArtistInfo(newArtist.SpotifyId); + tuple = _artistInfo.GetArtistInfo(newArtist.ForeignArtistId); } catch (ArtistNotFoundException) { - _logger.Error("SpotifyId {1} was not found, it may have been removed from Spotify.", newArtist.SpotifyId); + _logger.Error("SpotifyId {1} was not found, it may have been removed from Spotify.", newArtist.ForeignArtistId); throw new ValidationException(new List { - new ValidationFailure("SpotifyId", "An artist with this ID was not found", newArtist.SpotifyId) + new ValidationFailure("SpotifyId", "An artist with this ID was not found", newArtist.ForeignArtistId) }); } diff --git a/src/NzbDrone.Core/Music/AddArtistValidator.cs b/src/NzbDrone.Core/Music/AddArtistValidator.cs index ab789c2fc..bc860f09e 100644 --- a/src/NzbDrone.Core/Music/AddArtistValidator.cs +++ b/src/NzbDrone.Core/Music/AddArtistValidator.cs @@ -28,7 +28,7 @@ namespace NzbDrone.Core.Music .SetValidator(droneFactoryValidator) .SetValidator(seriesAncestorValidator); - RuleFor(c => c.ArtistSlug).SetValidator(artistTitleSlugValidator);// TODO: Check if we are going to use a slug or artistName + RuleFor(c => c.NameSlug).SetValidator(artistTitleSlugValidator);// TODO: Check if we are going to use a slug or artistName } } } diff --git a/src/NzbDrone.Core/Music/Album.cs b/src/NzbDrone.Core/Music/Album.cs index 6d18a766b..76027c0c3 100644 --- a/src/NzbDrone.Core/Music/Album.cs +++ b/src/NzbDrone.Core/Music/Album.cs @@ -7,7 +7,7 @@ using System.Text; namespace NzbDrone.Core.Music { - public class Album : IEmbeddedDocument + public class Album : ModelBase { public Album() { diff --git a/src/NzbDrone.Core/Music/Artist.cs b/src/NzbDrone.Core/Music/Artist.cs index 000aaf928..cdce64860 100644 --- a/src/NzbDrone.Core/Music/Artist.cs +++ b/src/NzbDrone.Core/Music/Artist.cs @@ -22,21 +22,19 @@ namespace NzbDrone.Core.Music } - public string SpotifyId { get; set; } - public string ArtistName { get; set; } - public string ArtistSlug { get; set; } - public string CleanTitle { get; set; } + public string ForeignArtistId { get; set; } + public string Name { get; set; } + public string NameSlug { get; set; } + public string CleanName { get; set; } public string Overview { get; set; } public bool Monitored { get; set; } public bool AlbumFolder { get; set; } - public bool ArtistFolder { get; set; } public DateTime? LastInfoSync { get; set; } public DateTime? LastDiskSync { get; set; } public int Status { get; set; } // TODO: Figure out what this is, do we need it? public string Path { get; set; } public List Images { get; set; } public List Genres { get; set; } - public int QualityProfileId { get; set; } public string RootFolderPath { get; set; } public DateTime Added { get; set; } public LazyLoaded Profile { get; set; } @@ -47,16 +45,16 @@ namespace NzbDrone.Core.Music public override string ToString() { - return string.Format("[{0}][{1}]", SpotifyId, ArtistName.NullSafe()); + return string.Format("[{0}][{1}]", ForeignArtistId, Name.NullSafe()); } public void ApplyChanges(Artist otherArtist) { - SpotifyId = otherArtist.SpotifyId; - ArtistName = otherArtist.ArtistName; - ArtistSlug = otherArtist.ArtistSlug; - CleanTitle = otherArtist.CleanTitle; + ForeignArtistId = otherArtist.ForeignArtistId; + Name = otherArtist.Name; + NameSlug = otherArtist.NameSlug; + CleanName = otherArtist.CleanName; Monitored = otherArtist.Monitored; AlbumFolder = otherArtist.AlbumFolder; LastInfoSync = otherArtist.LastInfoSync; @@ -69,7 +67,6 @@ namespace NzbDrone.Core.Music ProfileId = otherArtist.ProfileId; Albums = otherArtist.Albums; Tags = otherArtist.Tags; - ArtistFolder = otherArtist.ArtistFolder; AddOptions = otherArtist.AddOptions; Albums = otherArtist.Albums; diff --git a/src/NzbDrone.Core/Music/ArtistRepository.cs b/src/NzbDrone.Core/Music/ArtistRepository.cs index 0da04ad0d..907af1662 100644 --- a/src/NzbDrone.Core/Music/ArtistRepository.cs +++ b/src/NzbDrone.Core/Music/ArtistRepository.cs @@ -24,16 +24,16 @@ namespace NzbDrone.Core.Music return Query.Where(c => c.Path == path).Any(); } - public Artist FindById(string spotifyId) + public Artist FindById(string foreignArtistId) { - return Query.Where(s => s.SpotifyId == spotifyId).SingleOrDefault(); + return Query.Where(s => s.ForeignArtistId == foreignArtistId).SingleOrDefault(); } public Artist FindByName(string cleanName) { cleanName = cleanName.ToLowerInvariant(); - return Query.Where(s => s.CleanTitle == cleanName) + return Query.Where(s => s.CleanName == cleanName) .SingleOrDefault(); } } diff --git a/src/NzbDrone.Core/Music/ArtistService.cs b/src/NzbDrone.Core/Music/ArtistService.cs index bedf41f74..12387b3c0 100644 --- a/src/NzbDrone.Core/Music/ArtistService.cs +++ b/src/NzbDrone.Core/Music/ArtistService.cs @@ -114,7 +114,7 @@ namespace NzbDrone.Core.Music if (storedAlbum != null && album.Monitored != storedAlbum.Monitored) { - _trackService.SetTrackMonitoredByAlbum(artist.SpotifyId, album.AlbumId, album.Monitored); + _trackService.SetTrackMonitoredByAlbum(artist.ForeignArtistId, album.AlbumId, album.Monitored); } } @@ -129,17 +129,17 @@ namespace NzbDrone.Core.Music _logger.Debug("Updating {0} artist", artist.Count); foreach (var s in artist) { - _logger.Trace("Updating: {0}", s.ArtistName); + _logger.Trace("Updating: {0}", s.Name); if (!s.RootFolderPath.IsNullOrWhiteSpace()) { var folderName = new DirectoryInfo(s.Path).Name; s.Path = Path.Combine(s.RootFolderPath, folderName); - _logger.Trace("Changing path for {0} to {1}", s.ArtistName, s.Path); + _logger.Trace("Changing path for {0} to {1}", s.Name, s.Path); } else { - _logger.Trace("Not changing path for: {0}", s.ArtistName); + _logger.Trace("Not changing path for: {0}", s.Name); } } diff --git a/src/NzbDrone.Core/Music/ArtistSlugValidator.cs b/src/NzbDrone.Core/Music/ArtistSlugValidator.cs index 4d5626c89..1d894fee2 100644 --- a/src/NzbDrone.Core/Music/ArtistSlugValidator.cs +++ b/src/NzbDrone.Core/Music/ArtistSlugValidator.cs @@ -23,7 +23,7 @@ namespace NzbDrone.Core.Music dynamic instance = context.ParentContext.InstanceToValidate; var instanceId = (int)instance.Id; - return !_artistService.GetAllArtists().Exists(s => s.ArtistSlug.Equals(context.PropertyValue.ToString()) && s.Id != instanceId); + return !_artistService.GetAllArtists().Exists(s => s.NameSlug.Equals(context.PropertyValue.ToString()) && s.Id != instanceId); } } } diff --git a/src/NzbDrone.Core/Music/RefreshArtistService.cs b/src/NzbDrone.Core/Music/RefreshArtistService.cs index 314597c63..cdac3ac30 100644 --- a/src/NzbDrone.Core/Music/RefreshArtistService.cs +++ b/src/NzbDrone.Core/Music/RefreshArtistService.cs @@ -45,33 +45,33 @@ namespace NzbDrone.Core.Music private void RefreshArtistInfo(Artist artist) { - _logger.ProgressInfo("Updating Info for {0}", artist.ArtistName); + _logger.ProgressInfo("Updating Info for {0}", artist.Name); Tuple> tuple; try { - tuple = _artistInfo.GetArtistInfo(artist.SpotifyId); + tuple = _artistInfo.GetArtistInfo(artist.ForeignArtistId); } catch (ArtistNotFoundException) { - _logger.Error("Artist '{0}' (SpotifyId {1}) was not found, it may have been removed from Spotify.", artist.ArtistName, artist.SpotifyId); + _logger.Error("Artist '{0}' (SpotifyId {1}) was not found, it may have been removed from Spotify.", artist.Name, artist.ForeignArtistId); return; } var artistInfo = tuple.Item1; - if (artist.SpotifyId != artistInfo.SpotifyId) + if (artist.ForeignArtistId != artistInfo.ForeignArtistId) { - _logger.Warn("Artist '{0}' (SpotifyId {1}) was replaced with '{2}' (SpotifyId {3}), because the original was a duplicate.", artist.ArtistName, artist.SpotifyId, artistInfo.ArtistName, artistInfo.SpotifyId); - artist.SpotifyId = artistInfo.SpotifyId; + _logger.Warn("Artist '{0}' (SpotifyId {1}) was replaced with '{2}' (SpotifyId {3}), because the original was a duplicate.", artist.Name, artist.ForeignArtistId, artistInfo.Name, artistInfo.ForeignArtistId); + artist.ForeignArtistId = artistInfo.ForeignArtistId; } - artist.ArtistName = artistInfo.ArtistName; - artist.ArtistSlug = artistInfo.ArtistSlug; + artist.Name = artistInfo.Name; + artist.NameSlug = artistInfo.NameSlug; artist.Overview = artistInfo.Overview; artist.Status = artistInfo.Status; - artist.CleanTitle = artistInfo.CleanTitle; + artist.CleanName = artistInfo.CleanName; artist.LastInfoSync = DateTime.UtcNow; artist.Images = artistInfo.Images; artist.Genres = artistInfo.Genres; @@ -91,7 +91,7 @@ namespace NzbDrone.Core.Music _artistService.UpdateArtist(artist); _refreshTrackService.RefreshTrackInfo(artist, tuple.Item2); - _logger.Debug("Finished artist refresh for {0}", artist.ArtistName); + _logger.Debug("Finished artist refresh for {0}", artist.Name); _eventAggregator.PublishEvent(new ArtistUpdatedEvent(artist)); } @@ -112,7 +112,7 @@ namespace NzbDrone.Core.Music // continue; //} - _logger.Debug("New album ({0}) for artist: [{1}] {2}, setting monitored to true", album.Title, artist.SpotifyId, artist.ArtistName); + _logger.Debug("New album ({0}) for artist: [{1}] {2}, setting monitored to true", album.Title, artist.ForeignArtistId, artist.Name); album.Monitored = true; } @@ -136,7 +136,7 @@ namespace NzbDrone.Core.Music } else { - var allArtists = _artistService.GetAllArtists().OrderBy(c => c.ArtistName).ToList(); + var allArtists = _artistService.GetAllArtists().OrderBy(c => c.Name).ToList(); foreach (var artist in allArtists) { @@ -156,7 +156,7 @@ namespace NzbDrone.Core.Music { try { - _logger.Info("Skipping refresh of artist: {0}", artist.ArtistName); + _logger.Info("Skipping refresh of artist: {0}", artist.Name); //TODO: _diskScanService.Scan(artist); } catch (Exception e) diff --git a/src/NzbDrone.Core/Music/RefreshTrackService.cs b/src/NzbDrone.Core/Music/RefreshTrackService.cs index 169582e3d..4459ee503 100644 --- a/src/NzbDrone.Core/Music/RefreshTrackService.cs +++ b/src/NzbDrone.Core/Music/RefreshTrackService.cs @@ -33,7 +33,7 @@ namespace NzbDrone.Core.Music var successCount = 0; var failCount = 0; - var existingTracks = _trackService.GetTracksByArtist(artist.SpotifyId); + var existingTracks = _trackService.GetTracksByArtist(artist.ForeignArtistId); var albums = artist.Albums; var updateList = new List(); @@ -66,7 +66,7 @@ namespace NzbDrone.Core.Music trackToUpdate.Explict = track.Explict; if (track.ArtistSpotifyId.IsNullOrWhiteSpace()) { - trackToUpdate.ArtistSpotifyId = artist.SpotifyId; + trackToUpdate.ArtistSpotifyId = artist.ForeignArtistId; } else { trackToUpdate.ArtistSpotifyId = track.ArtistSpotifyId; @@ -104,7 +104,7 @@ namespace NzbDrone.Core.Music if (failCount != 0) { _logger.Info("Finished track refresh for artist: {0}. Successful: {1} - Failed: {2} ", - artist.ArtistName, successCount, failCount); + artist.Name, successCount, failCount); } else { diff --git a/src/NzbDrone.Core/Music/ShouldRefreshArtist.cs b/src/NzbDrone.Core/Music/ShouldRefreshArtist.cs index 669a1db3e..f38ccaaf5 100644 --- a/src/NzbDrone.Core/Music/ShouldRefreshArtist.cs +++ b/src/NzbDrone.Core/Music/ShouldRefreshArtist.cs @@ -26,13 +26,13 @@ namespace NzbDrone.Core.Music { if (artist.LastInfoSync < DateTime.UtcNow.AddDays(-30)) { - _logger.Trace("Artist {0} last updated more than 30 days ago, should refresh.", artist.ArtistName); + _logger.Trace("Artist {0} last updated more than 30 days ago, should refresh.", artist.Name); return true; } if (artist.LastInfoSync >= DateTime.UtcNow.AddHours(-6)) { - _logger.Trace("Artist {0} last updated less than 6 hours ago, should not be refreshed.", artist.ArtistName); + _logger.Trace("Artist {0} last updated less than 6 hours ago, should not be refreshed.", artist.Name); return false; } diff --git a/src/NzbDrone.Core/Music/TrackRepository.cs b/src/NzbDrone.Core/Music/TrackRepository.cs index 354d09afc..8d707ec09 100644 --- a/src/NzbDrone.Core/Music/TrackRepository.cs +++ b/src/NzbDrone.Core/Music/TrackRepository.cs @@ -118,7 +118,7 @@ namespace NzbDrone.Core.Music private SortBuilder GetMissingEpisodesQuery(PagingSpec pagingSpec, DateTime currentTime) { - return Query.Join(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistSpotifyId == s.SpotifyId) + return Query.Join(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistSpotifyId == s.ForeignArtistId) .Where(pagingSpec.FilterExpression) .AndWhere(e => e.TrackFileId == 0) .AndWhere(BuildAirDateUtcCutoffWhereClause(currentTime)) @@ -130,7 +130,7 @@ namespace NzbDrone.Core.Music private SortBuilder EpisodesWhereCutoffUnmetQuery(PagingSpec pagingSpec, List qualitiesBelowCutoff) { - return Query.Join(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistSpotifyId == s.SpotifyId) + return Query.Join(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistSpotifyId == s.ForeignArtistId) .Join(JoinType.Left, e => e.TrackFile, (e, s) => e.TrackFileId == s.Id) .Where(pagingSpec.FilterExpression) .AndWhere(e => e.TrackFileId != 0) diff --git a/src/NzbDrone.Core/Music/TrackService.cs b/src/NzbDrone.Core/Music/TrackService.cs index cf09139e5..54a415404 100644 --- a/src/NzbDrone.Core/Music/TrackService.cs +++ b/src/NzbDrone.Core/Music/TrackService.cs @@ -154,7 +154,7 @@ namespace NzbDrone.Core.Music public void HandleAsync(ArtistDeletedEvent message) { - var tracks = GetTracksByArtist(message.Artist.SpotifyId); + var tracks = GetTracksByArtist(message.Artist.ForeignArtistId); _trackRepository.DeleteMany(tracks); } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 65494030b..dc7c2c9b9 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -288,7 +288,6 @@ - @@ -816,7 +815,7 @@ - + @@ -825,7 +824,7 @@ - + diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index c85e72927..b9b53a0dc 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -284,8 +284,8 @@ namespace NzbDrone.Core.Organizer private void AddArtistTokens(Dictionary> tokenHandlers, Artist artist) { - tokenHandlers["{Artist Name}"] = m => artist.ArtistName; - tokenHandlers["{Artist CleanTitle}"] = m => CleanTitle(artist.ArtistName); + tokenHandlers["{Artist Name}"] = m => artist.Name; + tokenHandlers["{Artist CleanTitle}"] = m => CleanTitle(artist.Name); } private string AddSeasonEpisodeNumberingTokens(string pattern, Dictionary> tokenHandlers, List episodes, NamingConfig namingConfig) diff --git a/src/NzbDrone.Core/Parser/Model/RemoteAlbum.cs b/src/NzbDrone.Core/Parser/Model/RemoteAlbum.cs new file mode 100644 index 000000000..2b1f0ceb0 --- /dev/null +++ b/src/NzbDrone.Core/Parser/Model/RemoteAlbum.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.Music; + +namespace NzbDrone.Core.Parser.Model +{ + public class RemoteAlbum + { + public ReleaseInfo Release { get; set; } + public ParsedTrackInfo ParsedTrackInfo { get; set; } + public Artist Artist { get; set; } + public List Albums { get; set; } + public bool DownloadAllowed { get; set; } + + public bool IsRecentAlbum() + { + return Albums.Any(e => e.ReleaseDate >= DateTime.UtcNow.Date.AddDays(-14)); + } + + public override string ToString() + { + return Release.Title; + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs b/src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs index 3260895c5..2e59d3a8d 100644 --- a/src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs +++ b/src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs @@ -21,7 +21,7 @@ namespace NzbDrone.Core.Validation.Paths { if (context.PropertyValue == null) return true; - return (!_artistService.GetAllArtists().Exists(s => s.SpotifyId == context.PropertyValue.ToString())); + return (!_artistService.GetAllArtists().Exists(s => s.ForeignArtistId == context.PropertyValue.ToString())); } } } diff --git a/src/UI/AddArtist/EmptyViewTemplate.hbs b/src/UI/AddArtist/EmptyViewTemplate.hbs index 26c712271..e2a20efdc 100644 --- a/src/UI/AddArtist/EmptyViewTemplate.hbs +++ b/src/UI/AddArtist/EmptyViewTemplate.hbs @@ -1,3 +1,3 @@
- You can also search by Spotify using the spotify: prefixes. + You can also search by MusicBrianzID using the MBID: prefixes.
diff --git a/src/UI/AddArtist/SearchResultView.js b/src/UI/AddArtist/SearchResultView.js index cc53f6b15..47dd29944 100644 --- a/src/UI/AddArtist/SearchResultView.js +++ b/src/UI/AddArtist/SearchResultView.js @@ -97,7 +97,7 @@ var view = Marionette.ItemView.extend({ }, _configureTemplateHelpers : function() { - var existingArtist = ArtistCollection.where({ spotifyId : this.model.get('spotifyId') }); + var existingArtist = ArtistCollection.where({ foreignArtistId : this.model.get('foreignArtistId') }); if (existingArtist.length > 0) { this.templateHelpers.existing = existingArtist[0].toJSON(); diff --git a/src/UI/AddArtist/SearchResultViewTemplate.hbs b/src/UI/AddArtist/SearchResultViewTemplate.hbs index 0377ccbc6..686bad6b3 100644 --- a/src/UI/AddArtist/SearchResultViewTemplate.hbs +++ b/src/UI/AddArtist/SearchResultViewTemplate.hbs @@ -5,7 +5,7 @@

- {{artistName}} + {{name}}
- -
diff --git a/src/UI/Artist/ArtistCollection.js b/src/UI/Artist/ArtistCollection.js index 4bffc07e2..8e700d7bf 100644 --- a/src/UI/Artist/ArtistCollection.js +++ b/src/UI/Artist/ArtistCollection.js @@ -77,7 +77,7 @@ var Collection = PageableCollection.extend({ }, artistName: { - sortKey : 'artistName' + sortKey : 'name' }, nextAiring : { diff --git a/src/UI/Artist/ArtistController.js b/src/UI/Artist/ArtistController.js index c6e24a771..2f54bc2cf 100644 --- a/src/UI/Artist/ArtistController.js +++ b/src/UI/Artist/ArtistController.js @@ -21,12 +21,12 @@ module.exports = NzbDroneController.extend({ }, artistDetails : function(query) { - var artists = ArtistCollection.where({ artistSlug : query }); + var artists = ArtistCollection.where({ nameSlug : query }); console.log('artistDetails, artists: ', artists); if (artists.length !== 0) { var targetArtist = artists[0]; console.log("[ArtistController] targetArtist: ", targetArtist); - this.setTitle(targetArtist.get('artistName')); // TODO: Update NzbDroneController + this.setTitle(targetArtist.get('name')); // TODO: Update NzbDroneController //this.setArtistName(targetSeries.get('artistName')); this.showMainRegion(new ArtistDetailsLayout({ model : targetArtist })); } else { diff --git a/src/UI/Artist/Details/ArtistDetailsLayout.js b/src/UI/Artist/Details/ArtistDetailsLayout.js index 67d55c306..fc068d291 100644 --- a/src/UI/Artist/Details/ArtistDetailsLayout.js +++ b/src/UI/Artist/Details/ArtistDetailsLayout.js @@ -63,7 +63,7 @@ module.exports = Marionette.Layout.extend({ onShow : function() { this._showBackdrop(); - this._showSeasons(); + this._showAlbums(); this._setMonitoredState(); this._showInfo(); }, diff --git a/src/UI/Artist/Details/ArtistDetailsTemplate.hbs b/src/UI/Artist/Details/ArtistDetailsTemplate.hbs index 605ead424..8d5b1f5b1 100644 --- a/src/UI/Artist/Details/ArtistDetailsTemplate.hbs +++ b/src/UI/Artist/Details/ArtistDetailsTemplate.hbs @@ -1,35 +1,35 @@ -
+
{{poster}}

- - {{title}} -
+ + {{name}} +
- +
- +
- +

-
+
{{overview}}
-
+
diff --git a/src/UI/Artist/Details/InfoView.js b/src/UI/Artist/Details/InfoView.js index c7fab9fc4..74ddb2bf4 100644 --- a/src/UI/Artist/Details/InfoView.js +++ b/src/UI/Artist/Details/InfoView.js @@ -1,18 +1,18 @@ var Marionette = require('marionette'); module.exports = Marionette.ItemView.extend({ - template : 'Series/Details/InfoViewTemplate', + template : 'Artist/Details/InfoViewTemplate', initialize : function(options) { - this.episodeFileCollection = options.episodeFileCollection; + this.trackFileCollection = options.trackFileCollection; this.listenTo(this.model, 'change', this.render); - this.listenTo(this.episodeFileCollection, 'sync', this.render); + this.listenTo(this.trackFileCollection, 'sync', this.render); }, templateHelpers : function() { return { - fileCount : this.episodeFileCollection.length + fileCount : this.trackFileCollection.length }; } }); \ No newline at end of file diff --git a/src/UI/Artist/Details/InfoViewTemplate.hbs b/src/UI/Artist/Details/InfoViewTemplate.hbs index b52130246..c6e689630 100644 --- a/src/UI/Artist/Details/InfoViewTemplate.hbs +++ b/src/UI/Artist/Details/InfoViewTemplate.hbs @@ -28,7 +28,7 @@ {{/if_eq}}
- + Trakt The TVDB diff --git a/src/UI/Artist/Editor/ArtistEditorLayout.js b/src/UI/Artist/Editor/ArtistEditorLayout.js index 51f0ec52e..6d8520154 100644 --- a/src/UI/Artist/Editor/ArtistEditorLayout.js +++ b/src/UI/Artist/Editor/ArtistEditorLayout.js @@ -45,7 +45,7 @@ module.exports = Marionette.Layout.extend({ cell : ArtistStatusCell }, { - name : 'artistName', + name : 'name', label : 'Artist', cell : ArtistTitleCell, cellValue : 'this' diff --git a/src/UI/Artist/Index/Overview/ArtistOverviewItemViewTemplate.hbs b/src/UI/Artist/Index/Overview/ArtistOverviewItemViewTemplate.hbs index 222287a2e..de34cc5cd 100644 --- a/src/UI/Artist/Index/Overview/ArtistOverviewItemViewTemplate.hbs +++ b/src/UI/Artist/Index/Overview/ArtistOverviewItemViewTemplate.hbs @@ -9,7 +9,7 @@
diff --git a/src/UI/Handlebars/Helpers/Artist.js b/src/UI/Handlebars/Helpers/Artist.js index a44f24802..13990530c 100644 --- a/src/UI/Handlebars/Helpers/Artist.js +++ b/src/UI/Handlebars/Helpers/Artist.js @@ -40,7 +40,7 @@ Handlebars.registerHelper('tvMazeUrl', function() { }); Handlebars.registerHelper('route', function() { - return StatusModel.get('urlBase') + '/artist/' + this.artistSlug; + return StatusModel.get('urlBase') + '/artist/' + this.nameSlug; }); Handlebars.registerHelper('percentOfEpisodes', function() { From 15b70ede7cd3f9411f39df471b8b29b6a69065ec Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 12 Jun 2017 23:03:36 -0400 Subject: [PATCH 05/13] Track Mapping Fixes Fix Some Issues with Track Mapping --- .../Datastore/Migration/111_setup_music.cs | 4 ++- .../SkyHook/Resource/TrackResource.cs | 2 +- .../Music/RefreshTrackService.cs | 17 ++++------- src/NzbDrone.Core/Music/Track.cs | 16 +++++------ src/NzbDrone.Core/Music/TrackRepository.cs | 28 +++++++++---------- src/NzbDrone.Core/Music/TrackService.cs | 20 ++++++------- src/NzbDrone.Core/Parser/Model/LocalTrack.cs | 10 +------ 7 files changed, 42 insertions(+), 55 deletions(-) diff --git a/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs b/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs index 57decb35d..6b4010417 100644 --- a/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs +++ b/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs @@ -40,7 +40,7 @@ namespace NzbDrone.Core.Datastore.Migration .WithColumn("AddOptions").AsString().Nullable(); Create.TableForModel("Albums") - .WithColumn("ForeignArtistId").AsString().Unique() + .WithColumn("ForeignAlbumId").AsString().Unique() .WithColumn("ArtistId").AsInt32() .WithColumn("MBId").AsString().Indexed() .WithColumn("AMId").AsString().Nullable() @@ -68,12 +68,14 @@ namespace NzbDrone.Core.Datastore.Migration .WithColumn("AddOptions").AsString().Nullable(); Create.TableForModel("Tracks") + .WithColumn("ForeignTrackId").AsString().Unique() .WithColumn("ArtistId").AsInt32().Indexed() .WithColumn("AlbumId").AsInt32() .WithColumn("MBId").AsString().Indexed() .WithColumn("TrackNumber").AsInt32() .WithColumn("Title").AsString().Nullable() .WithColumn("Explicit").AsBoolean() + .WithColumn("Compilation").AsBoolean() .WithColumn("DiscNumber").AsInt32().Nullable() .WithColumn("TrackFileId").AsInt32().Nullable().Indexed() .WithColumn("Monitored").AsBoolean() diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackResource.cs index c2d4eddd2..eeb86ba48 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackResource.cs @@ -19,7 +19,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource public string TrackName { get; set; } public int TrackNumber { get; set; } public bool Explicit { get; set; } - public List Artists { get; set; } + public List Artists { get; set; } } } diff --git a/src/NzbDrone.Core/Music/RefreshTrackService.cs b/src/NzbDrone.Core/Music/RefreshTrackService.cs index 4459ee503..3bd5925a5 100644 --- a/src/NzbDrone.Core/Music/RefreshTrackService.cs +++ b/src/NzbDrone.Core/Music/RefreshTrackService.cs @@ -33,7 +33,7 @@ namespace NzbDrone.Core.Music var successCount = 0; var failCount = 0; - var existingTracks = _trackService.GetTracksByArtist(artist.ForeignArtistId); + var existingTracks = _trackService.GetTracksByArtist(artist.Id); var albums = artist.Albums; var updateList = new List(); @@ -58,20 +58,13 @@ namespace NzbDrone.Core.Music newList.Add(trackToUpdate); } - trackToUpdate.SpotifyTrackId = track.SpotifyTrackId; + trackToUpdate.ForeignTrackId = track.ForeignTrackId; trackToUpdate.TrackNumber = track.TrackNumber; trackToUpdate.Title = track.Title ?? "Unknown"; trackToUpdate.AlbumId = track.AlbumId; trackToUpdate.Album = track.Album; - trackToUpdate.Explict = track.Explict; - if (track.ArtistSpotifyId.IsNullOrWhiteSpace()) - { - trackToUpdate.ArtistSpotifyId = artist.ForeignArtistId; - } else - { - trackToUpdate.ArtistSpotifyId = track.ArtistSpotifyId; - } - trackToUpdate.ArtistId = track.ArtistId; + trackToUpdate.Explicit = track.Explicit; + trackToUpdate.ArtistId = artist.Id; trackToUpdate.Compilation = track.Compilation; // TODO: Implement rest of [RefreshTrackService] fields @@ -119,7 +112,7 @@ namespace NzbDrone.Core.Music return false; } - var album = albums.SingleOrDefault(c => c.AlbumId == track.AlbumId); + var album = albums.SingleOrDefault(c => c.Id == track.AlbumId); return album == null || album.Monitored; } diff --git a/src/NzbDrone.Core/Music/Track.cs b/src/NzbDrone.Core/Music/Track.cs index 599328c58..8e281260a 100644 --- a/src/NzbDrone.Core/Music/Track.cs +++ b/src/NzbDrone.Core/Music/Track.cs @@ -17,20 +17,20 @@ namespace NzbDrone.Core.Music public const string RELEASE_DATE_FORMAT = "yyyy-MM-dd"; - public string SpotifyTrackId { get; set; } - public string AlbumId { get; set; } + public string ForeignTrackId { get; set; } + public int AlbumId { get; set; } public LazyLoaded Artist { get; set; } - public string ArtistSpotifyId { get; set; } - public long ArtistId { get; set; } // This is the DB Id of the Artist, not the SpotifyId + + public int ArtistId { get; set; } // This is the DB Id of the Artist, not the SpotifyId //public int CompilationId { get; set; } public bool Compilation { get; set; } public int TrackNumber { get; set; } public string Title { get; set; } - public bool Ignored { get; set; } - public bool Explict { get; set; } + //public bool Ignored { get; set; } + public bool Explicit { get; set; } public bool Monitored { get; set; } public int TrackFileId { get; set; } - public DateTime? ReleaseDate { get; set; } + //public DateTime? ReleaseDate { get; set; } public LazyLoaded TrackFile { get; set; } @@ -40,7 +40,7 @@ namespace NzbDrone.Core.Music public override string ToString() { - return string.Format("[{0}]{1}", SpotifyTrackId, Title.NullSafe()); + return string.Format("[{0}]{1}", ForeignTrackId, Title.NullSafe()); } } } diff --git a/src/NzbDrone.Core/Music/TrackRepository.cs b/src/NzbDrone.Core/Music/TrackRepository.cs index 8d707ec09..948ce5936 100644 --- a/src/NzbDrone.Core/Music/TrackRepository.cs +++ b/src/NzbDrone.Core/Music/TrackRepository.cs @@ -13,11 +13,11 @@ namespace NzbDrone.Core.Music { public interface ITrackRepository : IBasicRepository { - Track Find(string artistId, string albumId, int trackNumber); - List GetTracks(string artistId); - List GetTracks(string artistId, string albumId); + Track Find(int artistId, int albumId, int trackNumber); + List GetTracks(int artistId); + List GetTracks(int artistId, int albumId); List GetTracksByFileId(int fileId); - List TracksWithFiles(string artistId); + List TracksWithFiles(int artistId); PagingSpec TracksWithoutFiles(PagingSpec pagingSpec); PagingSpec TracksWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff); void SetMonitoredFlat(Track episode, bool monitored); @@ -37,23 +37,23 @@ namespace NzbDrone.Core.Music _logger = logger; } - public Track Find(string artistId, string albumId, int trackNumber) + public Track Find(int artistId, int albumId, int trackNumber) { - return Query.Where(s => s.ArtistSpotifyId == artistId) + return Query.Where(s => s.ArtistId == artistId) .AndWhere(s => s.AlbumId == albumId) .AndWhere(s => s.TrackNumber == trackNumber) .SingleOrDefault(); } - public List GetTracks(string artistId) + public List GetTracks(int artistId) { - return Query.Where(s => s.ArtistSpotifyId == artistId).ToList(); + return Query.Where(s => s.ArtistId == artistId).ToList(); } - public List GetTracks(string artistId, string albumId) + public List GetTracks(int artistId, int albumId) { - return Query.Where(s => s.ArtistSpotifyId == artistId) + return Query.Where(s => s.ArtistId == artistId) .AndWhere(s => s.AlbumId == albumId) .ToList(); } @@ -63,10 +63,10 @@ namespace NzbDrone.Core.Music return Query.Where(e => e.TrackFileId == fileId).ToList(); } - public List TracksWithFiles(string artistId) + public List TracksWithFiles(int artistId) { return Query.Join(JoinType.Inner, e => e.TrackFile, (e, ef) => e.TrackFileId == ef.Id) - .Where(e => e.ArtistSpotifyId == artistId); + .Where(e => e.ArtistId == artistId); } public PagingSpec TracksWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff) @@ -118,7 +118,7 @@ namespace NzbDrone.Core.Music private SortBuilder GetMissingEpisodesQuery(PagingSpec pagingSpec, DateTime currentTime) { - return Query.Join(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistSpotifyId == s.ForeignArtistId) + return Query.Join(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistId == s.Id) .Where(pagingSpec.FilterExpression) .AndWhere(e => e.TrackFileId == 0) .AndWhere(BuildAirDateUtcCutoffWhereClause(currentTime)) @@ -130,7 +130,7 @@ namespace NzbDrone.Core.Music private SortBuilder EpisodesWhereCutoffUnmetQuery(PagingSpec pagingSpec, List qualitiesBelowCutoff) { - return Query.Join(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistSpotifyId == s.ForeignArtistId) + return Query.Join(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistId == s.Id) .Join(JoinType.Left, e => e.TrackFile, (e, s) => e.TrackFileId == s.Id) .Where(pagingSpec.FilterExpression) .AndWhere(e => e.TrackFileId != 0) diff --git a/src/NzbDrone.Core/Music/TrackService.cs b/src/NzbDrone.Core/Music/TrackService.cs index 54a415404..834d3b054 100644 --- a/src/NzbDrone.Core/Music/TrackService.cs +++ b/src/NzbDrone.Core/Music/TrackService.cs @@ -15,12 +15,12 @@ namespace NzbDrone.Core.Music { Track GetTrack(int id); List GetTracks(IEnumerable ids); - Track FindTrack(string artistId, string albumId, int trackNumber); - Track FindTrackByTitle(string artistId, string albumId, string releaseTitle); - List GetTracksByArtist(string artistId); + Track FindTrack(int artistId, int albumId, int trackNumber); + Track FindTrackByTitle(int artistId, int albumId, string releaseTitle); + List GetTracksByArtist(int artistId); //List GetTracksByAlbum(string artistId, string albumId); //List GetTracksByAlbumTitle(string artistId, string albumTitle); - List TracksWithFiles(string artistId); + List TracksWithFiles(int artistId); //PagingSpec TracksWithoutFiles(PagingSpec pagingSpec); List GetTracksByFileId(int trackFileId); void UpdateTrack(Track track); @@ -55,22 +55,22 @@ namespace NzbDrone.Core.Music return _trackRepository.Get(ids).ToList(); } - public Track FindTrack(string artistId, string albumId, int episodeNumber) + public Track FindTrack(int artistId, int albumId, int episodeNumber) { return _trackRepository.Find(artistId, albumId, episodeNumber); } - public List GetTracksByArtist(string artistId) + public List GetTracksByArtist(int artistId) { return _trackRepository.GetTracks(artistId).ToList(); } - public List GetTracksByAlbum(string artistId, string albumId) + public List GetTracksByAlbum(int artistId, int albumId) { return _trackRepository.GetTracks(artistId, albumId); } - public Track FindTrackByTitle(string artistId, string albumId, string releaseTitle) + public Track FindTrackByTitle(int artistId, int albumId, string releaseTitle) { // TODO: can replace this search mechanism with something smarter/faster/better var normalizedReleaseTitle = Parser.Parser.NormalizeEpisodeTitle(releaseTitle).Replace(".", " "); @@ -96,7 +96,7 @@ namespace NzbDrone.Core.Music return null; } - public List TracksWithFiles(string artistId) + public List TracksWithFiles(int artistId) { return _trackRepository.TracksWithFiles(artistId); } @@ -154,7 +154,7 @@ namespace NzbDrone.Core.Music public void HandleAsync(ArtistDeletedEvent message) { - var tracks = GetTracksByArtist(message.Artist.ForeignArtistId); + var tracks = GetTracksByArtist(message.Artist.Id); _trackRepository.DeleteMany(tracks); } diff --git a/src/NzbDrone.Core/Parser/Model/LocalTrack.cs b/src/NzbDrone.Core/Parser/Model/LocalTrack.cs index 2f8b35588..359ce6285 100644 --- a/src/NzbDrone.Core/Parser/Model/LocalTrack.cs +++ b/src/NzbDrone.Core/Parser/Model/LocalTrack.cs @@ -19,20 +19,12 @@ namespace NzbDrone.Core.Parser.Model public long Size { get; set; } public ParsedTrackInfo ParsedTrackInfo { get; set; } public Artist Artist { get; set; } + public Album Album { get; set; } public List Tracks { get; set; } public QualityModel Quality { get; set; } public MediaInfoModel MediaInfo { get; set; } public bool ExistingFile { get; set; } - public string Album - { - get - { - return Tracks.Select(c => c.AlbumId).Distinct().Single(); - } - } - - public bool IsSpecial => Album != ""; public override string ToString() { From a8ac1f3adc486ec83ef18c72e5092299a48f353a Mon Sep 17 00:00:00 2001 From: Qstick Date: Sat, 17 Jun 2017 22:27:01 -0400 Subject: [PATCH 06/13] Album Repo Work Adds Album Repo (Need to Update Album API), Gets Tracks working with new Schema, Fixes Albums Saving to DB --- src/NzbDrone.Api/Music/AlbumResource.cs | 14 +- .../Datastore/Migration/111_setup_music.cs | 7 +- src/NzbDrone.Core/Datastore/TableMapping.cs | 2 +- .../MetadataSource/IProvideArtistInfo.cs | 2 +- .../SkyHook/Resource/AlbumResource.cs | 10 +- .../SkyHook/Resource/ArtistResource.cs | 3 +- .../MetadataSource/SkyHook/SkyHookProxy.cs | 28 +++- src/NzbDrone.Core/Music/AddArtistService.cs | 8 +- src/NzbDrone.Core/Music/Album.cs | 27 +++- src/NzbDrone.Core/Music/AlbumRepository.cs | 46 ++++++ src/NzbDrone.Core/Music/AlbumService.cs | 147 ++++++++++++++++++ src/NzbDrone.Core/Music/ArtistService.cs | 4 +- .../Music/Events/AlbumAddedEvent.cs | 18 +++ .../Music/Events/AlbumDeletedEvent.cs | 20 +++ .../Music/Events/AlbumEditedEvent.cs | 20 +++ .../Music/Events/AlbumInfoRefreshedEvent.cs | 23 +++ .../Music/Events/TrackInfoRefreshedEvent.cs | 6 +- .../Music/RefreshAlbumService.cs | 133 ++++++++++++++++ .../Music/RefreshArtistService.cs | 15 +- .../Music/RefreshTrackService.cs | 34 ++-- src/NzbDrone.Core/Music/TrackService.cs | 2 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 7 + .../Organizer/FileNameBuilder.cs | 39 ++++- src/UI/AddArtist/SearchResultView.js | 4 +- src/UI/AddArtist/SearchResultViewTemplate.hbs | 4 +- 25 files changed, 549 insertions(+), 74 deletions(-) create mode 100644 src/NzbDrone.Core/Music/AlbumRepository.cs create mode 100644 src/NzbDrone.Core/Music/AlbumService.cs create mode 100644 src/NzbDrone.Core/Music/Events/AlbumAddedEvent.cs create mode 100644 src/NzbDrone.Core/Music/Events/AlbumDeletedEvent.cs create mode 100644 src/NzbDrone.Core/Music/Events/AlbumEditedEvent.cs create mode 100644 src/NzbDrone.Core/Music/Events/AlbumInfoRefreshedEvent.cs create mode 100644 src/NzbDrone.Core/Music/RefreshAlbumService.cs diff --git a/src/NzbDrone.Api/Music/AlbumResource.cs b/src/NzbDrone.Api/Music/AlbumResource.cs index d3e243c66..5ae4b2efa 100644 --- a/src/NzbDrone.Api/Music/AlbumResource.cs +++ b/src/NzbDrone.Api/Music/AlbumResource.cs @@ -11,7 +11,7 @@ namespace NzbDrone.Api.Music public string AlbumId { get; set; } public string AlbumName { get; set; } public bool Monitored { get; set; } - public int Year { get; set; } + public DateTime ReleaseDate { get; set; } public List Genres { get; set; } public string ArtworkUrl { get; set; } @@ -25,12 +25,12 @@ namespace NzbDrone.Api.Music return new AlbumResource { - AlbumId = model.AlbumId, + AlbumId = model.ForeignAlbumId, Monitored = model.Monitored, - Year = model.Year, + ReleaseDate = model.ReleaseDate, AlbumName = model.Title, Genres = model.Genres, - ArtworkUrl = model.ArtworkUrl + //ArtworkUrl = model.ArtworkUrl }; } @@ -40,12 +40,12 @@ namespace NzbDrone.Api.Music return new Album { - AlbumId = resource.AlbumId, + ForeignAlbumId = resource.AlbumId, Monitored = resource.Monitored, - Year = resource.Year, + ReleaseDate = resource.ReleaseDate, Title = resource.AlbumName, Genres = resource.Genres, - ArtworkUrl = resource.ArtworkUrl + //ArtworkUrl = resource.ArtworkUrl }; } diff --git a/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs b/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs index 6b4010417..d845291d8 100644 --- a/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs +++ b/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs @@ -42,14 +42,13 @@ namespace NzbDrone.Core.Datastore.Migration Create.TableForModel("Albums") .WithColumn("ForeignAlbumId").AsString().Unique() .WithColumn("ArtistId").AsInt32() - .WithColumn("MBId").AsString().Indexed() + .WithColumn("MBId").AsString().Nullable().Indexed() .WithColumn("AMId").AsString().Nullable() - .WithColumn("TADBId").AsInt32().Indexed() + .WithColumn("TADBId").AsInt32().Nullable().Indexed() .WithColumn("DiscogsId").AsInt32().Nullable() .WithColumn("Title").AsString() .WithColumn("TitleSlug").AsString().Nullable().Unique() .WithColumn("CleanTitle").AsString().Indexed() - .WithColumn("Status").AsInt32() .WithColumn("Overview").AsString().Nullable() .WithColumn("Images").AsString() .WithColumn("Path").AsString().Indexed() @@ -71,7 +70,7 @@ namespace NzbDrone.Core.Datastore.Migration .WithColumn("ForeignTrackId").AsString().Unique() .WithColumn("ArtistId").AsInt32().Indexed() .WithColumn("AlbumId").AsInt32() - .WithColumn("MBId").AsString().Indexed() + .WithColumn("MBId").AsString().Nullable().Indexed() .WithColumn("TrackNumber").AsInt32() .WithColumn("Title").AsString().Nullable() .WithColumn("Explicit").AsBoolean() diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index db5eaa1ac..c5945047b 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -97,7 +97,7 @@ namespace NzbDrone.Core.Datastore .Relationship() .HasOne(a => a.Profile, a => a.ProfileId); - Mapper.Entity().RegisterModel("Album"); + Mapper.Entity().RegisterModel("Albums"); Mapper.Entity().RegisterModel("TrackFiles") .Ignore(f => f.Path) diff --git a/src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs b/src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs index 76810655a..139d70a69 100644 --- a/src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs +++ b/src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs @@ -6,6 +6,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook { public interface IProvideArtistInfo { - Tuple> GetArtistInfo(string spotifyId); + Tuple> GetArtistInfo(string lidarrId); } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs index 7c1a918f0..5e5a1f13d 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs @@ -9,18 +9,20 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource { public AlbumResource() { - + Tracks = new List(); } //public string AlbumType { get; set; } // Might need to make this a separate class - public List Artists { get; set; } // Will always be length of 1 unless a compilation + public List Artists { get; set; } // Will always be length of 1 unless a compilation public string Url { get; set; } // Link to the endpoint api to give full info for this object public string Id { get; set; } // This is a unique Album ID. Needed for all future API calls - public int Year { get; set; } + public DateTime ReleaseDate { get; set; } public List Images { get; set; } - public string AlbumName { get; set; } // In case of a takedown, this may be empty + public string Title { get; set; } // In case of a takedown, this may be empty public string Overview { get; set; } public List Genres { get; set; } public string Label { get; set; } + public string Type { get; set; } + public List Tracks { get; set; } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs index c2c922d1b..f2ab72f94 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs @@ -9,7 +9,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource { public ArtistResource() { Albums = new List(); - Tracks = new List(); } public List Genres { get; set; } @@ -19,6 +18,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource public List Images { get; set; } public string ArtistName { get; set; } public List Albums { get; set; } - public List Tracks { get; set; } + } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index f4ae40a16..b411d7490 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -74,14 +74,14 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } - public Tuple> GetArtistInfo(string spotifyId) + public Tuple> GetArtistInfo(string foreignArtistId) { - _logger.Debug("Getting Artist with SpotifyId of {0}", spotifyId); + _logger.Debug("Getting Artist with SpotifyId of {0}", foreignArtistId); // We need to perform a direct lookup of the artist var httpRequest = _requestBuilder.Create() - .SetSegment("route", "artists/" + spotifyId) + .SetSegment("route", "artists/" + foreignArtistId) //.SetSegment("route", "search") //.AddQueryParam("type", "artist,album") //.AddQueryParam("q", spotifyId.ToString()) @@ -99,7 +99,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook { if (httpResponse.StatusCode == HttpStatusCode.NotFound) { - throw new ArtistNotFoundException(spotifyId); + throw new ArtistNotFoundException(foreignArtistId); } else { @@ -109,18 +109,17 @@ namespace NzbDrone.Core.MetadataSource.SkyHook // It is safe to assume an id will only return one Artist back var albums = httpResponse.Resource.Albums.Select(MapAlbum); - var tracks = httpResponse.Resource.Tracks.Select(MapTrack); var artist = MapArtist(httpResponse.Resource); //artist.Name = httpResponse.Resource.Artists.Items[0].ArtistName; - + //artist.ForeignArtistId = httpResponse.Resource.Artists.Items[0].Id; //artist.Genres = httpResponse.Resource.Artists.Items[0].Genres; //var albumRet = MapAlbums(artist); //artist = albumRet.Item1; - return new Tuple>(artist, tracks.ToList()); + return new Tuple>(artist, albums.ToList()); } @@ -179,7 +178,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook // } // List tracks = new List(); - // foreach(var trackResource in httpResponse.Resource.Items) + // foreach (var trackResource in httpResponse.Resource.Items) // { // Track track = new Track(); // track.AlbumId = album.AlbumId; @@ -269,12 +268,25 @@ namespace NzbDrone.Core.MetadataSource.SkyHook private static Album MapAlbum(AlbumResource resource) { Album album = new Album(); + album.Title = resource.Title; + album.ForeignAlbumId = resource.Id; + album.ReleaseDate = resource.ReleaseDate; + album.CleanTitle = Parser.Parser.CleanArtistTitle(album.Title); + album.AlbumType = resource.Type; + + var tracks = resource.Tracks.Select(MapTrack); + album.Tracks = tracks.ToList(); + + return album; } private static Track MapTrack(TrackResource resource) { Track track = new Track(); + track.Title = resource.TrackName; + track.ForeignTrackId = resource.Id; + track.TrackNumber = resource.TrackNumber; return track; } diff --git a/src/NzbDrone.Core/Music/AddArtistService.cs b/src/NzbDrone.Core/Music/AddArtistService.cs index aab228341..3811764f0 100644 --- a/src/NzbDrone.Core/Music/AddArtistService.cs +++ b/src/NzbDrone.Core/Music/AddArtistService.cs @@ -48,11 +48,11 @@ namespace NzbDrone.Core.Music if (string.IsNullOrWhiteSpace(newArtist.Path)) { - var folderName = newArtist.Name;// TODO: _fileNameBuilder.GetArtistFolder(newArtist); + var folderName = _fileNameBuilder.GetArtistFolder(newArtist); newArtist.Path = Path.Combine(newArtist.RootFolderPath, folderName); } - newArtist.CleanName = newArtist.Name.CleanSeriesTitle(); + newArtist.CleanName = newArtist.Name.CleanArtistTitle(); //newArtist.SortTitle = ArtistNameNormalizer.Normalize(newArtist.ArtistName, newArtist.ItunesId); // There is no Sort Title newArtist.Added = DateTime.UtcNow; @@ -71,7 +71,7 @@ namespace NzbDrone.Core.Music private Artist AddSkyhookData(Artist newArtist) { - Tuple> tuple; + Tuple> tuple; try { @@ -79,7 +79,7 @@ namespace NzbDrone.Core.Music } catch (ArtistNotFoundException) { - _logger.Error("SpotifyId {1} was not found, it may have been removed from Spotify.", newArtist.ForeignArtistId); + _logger.Error("LidarrId {1} was not found, it may have been removed from Lidarr.", newArtist.ForeignArtistId); throw new ValidationException(new List { diff --git a/src/NzbDrone.Core/Music/Album.cs b/src/NzbDrone.Core/Music/Album.cs index 76027c0c3..ee88b2e02 100644 --- a/src/NzbDrone.Core/Music/Album.cs +++ b/src/NzbDrone.Core/Music/Album.cs @@ -1,4 +1,5 @@ -using NzbDrone.Core.Datastore; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Datastore; using NzbDrone.Core.Tv; using System; using System.Collections.Generic; @@ -14,17 +15,27 @@ namespace NzbDrone.Core.Music Images = new List(); } - public string AlbumId { get; set; } + public string ForeignAlbumId { get; set; } + public int ArtistId { get; set; } public string Title { get; set; } // NOTE: This should be CollectionName in API - public int Year { get; set; } - public int TrackCount { get; set; } + public string CleanTitle { get; set; } + public DateTime ReleaseDate { get; set; } + //public int TrackCount { get; set; } + public string Path { get; set; } public List Tracks { get; set; } - public int DiscCount { get; set; } + //public int DiscCount { get; set; } public bool Monitored { get; set; } public List Images { get; set; } - public List Actors { get; set; } // These are band members. TODO: Refactor + //public List Actors { get; set; } // These are band members. TODO: Refactor public List Genres { get; set; } - public string ArtworkUrl { get; set; } - public string Explicitness { get; set; } + public String AlbumType { get; set; } //Turn this into a type similar to Series Type in TV + //public string ArtworkUrl { get; set; } + //public string Explicitness { get; set; } + public AddSeriesOptions AddOptions { get; set; } + + public override string ToString() + { + return string.Format("[{0}][{1}]", ForeignAlbumId, Title.NullSafe()); + } } } diff --git a/src/NzbDrone.Core/Music/AlbumRepository.cs b/src/NzbDrone.Core/Music/AlbumRepository.cs new file mode 100644 index 000000000..1b072e19a --- /dev/null +++ b/src/NzbDrone.Core/Music/AlbumRepository.cs @@ -0,0 +1,46 @@ +using System.Linq; +using NzbDrone.Core.Datastore; +using System.Collections.Generic; +using NzbDrone.Core.Messaging.Events; + +namespace NzbDrone.Core.Music +{ + public interface IAlbumRepository : IBasicRepository + { + bool AlbumPathExists(string path); + List GetAlbums(int artistId); + Album FindByName(string cleanTitle); + Album FindById(string spotifyId); + } + + public class AlbumRepository : BasicRepository, IAlbumRepository + { + public AlbumRepository(IMainDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) + { + } + + + public bool AlbumPathExists(string path) + { + return Query.Where(c => c.Path == path).Any(); + } + public List GetAlbums(int artistId) + { + return Query.Where(s => s.ArtistId == artistId).ToList(); + } + + public Album FindById(string foreignAlbumId) + { + return Query.Where(s => s.ForeignAlbumId == foreignAlbumId).SingleOrDefault(); + } + + public Album FindByName(string cleanTitle) + { + cleanTitle = cleanTitle.ToLowerInvariant(); + + return Query.Where(s => s.CleanTitle == cleanTitle) + .SingleOrDefault(); + } + } +} diff --git a/src/NzbDrone.Core/Music/AlbumService.cs b/src/NzbDrone.Core/Music/AlbumService.cs new file mode 100644 index 000000000..8d470830c --- /dev/null +++ b/src/NzbDrone.Core/Music/AlbumService.cs @@ -0,0 +1,147 @@ +using NLog; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Music.Events; +using NzbDrone.Core.Organizer; +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.Parser; +using System.Text; +using System.IO; +using NzbDrone.Common.Extensions; + +namespace NzbDrone.Core.Music +{ + public interface IAlbumService + { + Album GetAlbum(int albumid); + List GetAlbums(IEnumerable albumIds); + List GetAlbumsByArtist(int artistId); + Album AddAlbum(Album newAlbum); + Album FindById(string spotifyId); + Album FindByTitleInexact(string title); + void DeleteAlbum(int albumId, bool deleteFiles); + List GetAllAlbums(); + Album UpdateAlbum(Album album); + List UpdateAlbums(List album); + void InsertMany(List albums); + void UpdateMany(List albums); + void DeleteMany(List albums); + bool AlbumPathExists(string folder); + void RemoveAddOptions(Album album); + } + + public class AlbumService : IAlbumService + { + private readonly IAlbumRepository _albumRepository; + private readonly IEventAggregator _eventAggregator; + private readonly ITrackService _trackService; + private readonly IBuildFileNames _fileNameBuilder; + private readonly Logger _logger; + + public AlbumService(IAlbumRepository albumRepository, + IEventAggregator eventAggregator, + ITrackService trackService, + IBuildFileNames fileNameBuilder, + Logger logger) + { + _albumRepository = albumRepository; + _eventAggregator = eventAggregator; + _trackService = trackService; + _fileNameBuilder = fileNameBuilder; + _logger = logger; + } + + public Album AddAlbum(Album newAlbum) + { + _albumRepository.Insert(newAlbum); + _eventAggregator.PublishEvent(new AlbumAddedEvent(GetAlbum(newAlbum.Id))); + + return newAlbum; + } + + public bool AlbumPathExists(string folder) + { + return _albumRepository.AlbumPathExists(folder); + } + + public void DeleteAlbum(int albumId, bool deleteFiles) + { + var album = _albumRepository.Get(albumId); + _albumRepository.Delete(albumId); + _eventAggregator.PublishEvent(new AlbumDeletedEvent(album, deleteFiles)); + } + + public Album FindById(string spotifyId) + { + return _albumRepository.FindById(spotifyId); + } + + + + public Album FindByTitleInexact(string title) + { + throw new NotImplementedException(); + } + + public List GetAllAlbums() + { + return _albumRepository.All().ToList(); + } + + public Album GetAlbum(int albumId) + { + return _albumRepository.Get(albumId); + } + + public List GetAlbums(IEnumerable albumIds) + { + return _albumRepository.Get(albumIds).ToList(); + } + + public List GetAlbumsByArtist(int artistId) + { + return _albumRepository.GetAlbums(artistId).ToList(); + } + + public void RemoveAddOptions(Album album) + { + _albumRepository.SetFields(album, s => s.AddOptions); + } + + public void InsertMany(List albums) + { + _albumRepository.InsertMany(albums); + } + + public void UpdateMany(List albums) + { + _albumRepository.UpdateMany(albums); + } + + public void DeleteMany(List albums) + { + _albumRepository.DeleteMany(albums); + } + + public Album UpdateAlbum(Album album) + { + var storedAlbum = GetAlbum(album.Id); // Is it Id or iTunesId? + + var updatedAlbum = _albumRepository.Update(album); + _eventAggregator.PublishEvent(new AlbumEditedEvent(updatedAlbum, storedAlbum)); + + return updatedAlbum; + } + + public List UpdateAlbums(List album) + { + _logger.Debug("Updating {0} album", album.Count); + + _albumRepository.UpdateMany(album); + _logger.Debug("{0} albums updated", album.Count); + + return album; + } + } +} diff --git a/src/NzbDrone.Core/Music/ArtistService.cs b/src/NzbDrone.Core/Music/ArtistService.cs index 12387b3c0..b504538d7 100644 --- a/src/NzbDrone.Core/Music/ArtistService.cs +++ b/src/NzbDrone.Core/Music/ArtistService.cs @@ -110,11 +110,11 @@ namespace NzbDrone.Core.Music foreach (var album in artist.Albums) { - var storedAlbum = storedArtist.Albums.SingleOrDefault(s => s.AlbumId == album.AlbumId); + var storedAlbum = storedArtist.Albums.SingleOrDefault(s => s.ForeignAlbumId == album.ForeignAlbumId); if (storedAlbum != null && album.Monitored != storedAlbum.Monitored) { - _trackService.SetTrackMonitoredByAlbum(artist.ForeignArtistId, album.AlbumId, album.Monitored); + _trackService.SetTrackMonitoredByAlbum(artist.ForeignArtistId, album.ForeignAlbumId, album.Monitored); } } diff --git a/src/NzbDrone.Core/Music/Events/AlbumAddedEvent.cs b/src/NzbDrone.Core/Music/Events/AlbumAddedEvent.cs new file mode 100644 index 000000000..985cc419a --- /dev/null +++ b/src/NzbDrone.Core/Music/Events/AlbumAddedEvent.cs @@ -0,0 +1,18 @@ +using NzbDrone.Common.Messaging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music.Events +{ + public class AlbumAddedEvent : IEvent + { + public Album Album { get; private set; } + + public AlbumAddedEvent(Album album) + { + Album = album; + } + } +} diff --git a/src/NzbDrone.Core/Music/Events/AlbumDeletedEvent.cs b/src/NzbDrone.Core/Music/Events/AlbumDeletedEvent.cs new file mode 100644 index 000000000..28548116c --- /dev/null +++ b/src/NzbDrone.Core/Music/Events/AlbumDeletedEvent.cs @@ -0,0 +1,20 @@ +using NzbDrone.Common.Messaging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music.Events +{ + public class AlbumDeletedEvent : IEvent + { + public Album Album { get; private set; } + public bool DeleteFiles { get; private set; } + + public AlbumDeletedEvent(Album album, bool deleteFiles) + { + Album = album; + DeleteFiles = deleteFiles; + } + } +} diff --git a/src/NzbDrone.Core/Music/Events/AlbumEditedEvent.cs b/src/NzbDrone.Core/Music/Events/AlbumEditedEvent.cs new file mode 100644 index 000000000..aed8f155e --- /dev/null +++ b/src/NzbDrone.Core/Music/Events/AlbumEditedEvent.cs @@ -0,0 +1,20 @@ +using NzbDrone.Common.Messaging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music.Events +{ + public class AlbumEditedEvent : IEvent + { + public Album Album { get; private set; } + public Album OldAlbum { get; private set; } + + public AlbumEditedEvent(Album album, Album oldAlbum) + { + Album = album; + OldAlbum = oldAlbum; + } + } +} diff --git a/src/NzbDrone.Core/Music/Events/AlbumInfoRefreshedEvent.cs b/src/NzbDrone.Core/Music/Events/AlbumInfoRefreshedEvent.cs new file mode 100644 index 000000000..5fdb3f539 --- /dev/null +++ b/src/NzbDrone.Core/Music/Events/AlbumInfoRefreshedEvent.cs @@ -0,0 +1,23 @@ +using NzbDrone.Common.Messaging; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music.Events +{ + public class AlbumInfoRefreshedEvent : IEvent + { + public Artist Artist { get; set; } + public ReadOnlyCollection Added { get; private set; } + public ReadOnlyCollection Updated { get; private set; } + + public AlbumInfoRefreshedEvent(Artist artist, IList added, IList updated) + { + Artist = artist; + Added = new ReadOnlyCollection(added); + Updated = new ReadOnlyCollection(updated); + } + } +} diff --git a/src/NzbDrone.Core/Music/Events/TrackInfoRefreshedEvent.cs b/src/NzbDrone.Core/Music/Events/TrackInfoRefreshedEvent.cs index 99661c480..ef6eb1a8b 100644 --- a/src/NzbDrone.Core/Music/Events/TrackInfoRefreshedEvent.cs +++ b/src/NzbDrone.Core/Music/Events/TrackInfoRefreshedEvent.cs @@ -9,13 +9,13 @@ namespace NzbDrone.Core.Music.Events { public class TrackInfoRefreshedEvent : IEvent { - public Artist Artist { get; set; } + public Album Album { get; set; } public ReadOnlyCollection Added { get; private set; } public ReadOnlyCollection Updated { get; private set; } - public TrackInfoRefreshedEvent(Artist artist, IList added, IList updated) + public TrackInfoRefreshedEvent(Album album, IList added, IList updated) { - Artist = artist; + Album = album; Added = new ReadOnlyCollection(added); Updated = new ReadOnlyCollection(updated); } diff --git a/src/NzbDrone.Core/Music/RefreshAlbumService.cs b/src/NzbDrone.Core/Music/RefreshAlbumService.cs new file mode 100644 index 000000000..1fbb8f6a8 --- /dev/null +++ b/src/NzbDrone.Core/Music/RefreshAlbumService.cs @@ -0,0 +1,133 @@ +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Music.Events; +using System; +using System.Collections.Generic; +using NzbDrone.Core.Organizer; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music +{ + public interface IRefreshAlbumService + { + void RefreshAlbumInfo(Artist artist, IEnumerable remoteAlbums); + } + + public class RefreshAlbumService : IRefreshAlbumService + { + private readonly IAlbumService _albumService; + private readonly IRefreshTrackService _refreshTrackService; + private readonly IEventAggregator _eventAggregator; + private readonly Logger _logger; + + public RefreshAlbumService(IAlbumService albumService, IRefreshTrackService refreshTrackService, IEventAggregator eventAggregator, Logger logger) + { + _albumService = albumService; + _refreshTrackService = refreshTrackService; + _eventAggregator = eventAggregator; + _logger = logger; + } + + public void RefreshAlbumInfo(Artist artist, IEnumerable remoteAlbums) + { + _logger.Info("Starting album info refresh for: {0}", artist); + var successCount = 0; + var failCount = 0; + + var existingAlbums = _albumService.GetAlbumsByArtist(artist.Id); + var albums = artist.Albums; + + var updateList = new List(); + var newList = new List(); + var dupeFreeRemoteAlbums = remoteAlbums.DistinctBy(m => new { m.ForeignAlbumId, m.ReleaseDate }).ToList(); + + foreach (var album in OrderAlbums(artist, dupeFreeRemoteAlbums)) + { + try + { + var albumToUpdate = GetAlbumToUpdate(artist, album, existingAlbums); + + if (albumToUpdate != null) + { + existingAlbums.Remove(albumToUpdate); + updateList.Add(albumToUpdate); + } + else + { + albumToUpdate = new Album(); + albumToUpdate.Monitored = artist.Monitored; + newList.Add(albumToUpdate); + //var folderName = _fileNameBuilder.GetAlbumFolder(albumToUpdate); //This likely does not belong here, need to create AddAlbumService + //albumToUpdate.Path = Path.Combine(newArtist.RootFolderPath, folderName); + } + + albumToUpdate.ForeignAlbumId = album.ForeignAlbumId; + albumToUpdate.CleanTitle = album.CleanTitle; + //albumToUpdate.TrackNumber = album.TrackNumber; + albumToUpdate.Title = album.Title ?? "Unknown"; + //albumToUpdate.AlbumId = album.AlbumId; + //albumToUpdate.Album = album.Album; + //albumToUpdate.Explicit = album.Explicit; + albumToUpdate.ArtistId = artist.Id; + albumToUpdate.Path = artist.Path + album.Title; + albumToUpdate.AlbumType = album.AlbumType; + //albumToUpdate.Compilation = album.Compilation; + + _refreshTrackService.RefreshTrackInfo(album, album.Tracks); + + + successCount++; + } + catch (Exception e) + { + _logger.Fatal(e, "An error has occurred while updating track info for artist {0}. {1}", artist, album); + failCount++; + } + } + + var allAlbums = new List(); + allAlbums.AddRange(newList); + allAlbums.AddRange(updateList); + + // TODO: See if anything needs to be done here + //AdjustMultiEpisodeAirTime(artist, allTracks); + //AdjustDirectToDvdAirDate(artist, allTracks); + + _albumService.DeleteMany(existingAlbums); + _albumService.UpdateMany(updateList); + _albumService.InsertMany(newList); + + _eventAggregator.PublishEvent(new AlbumInfoRefreshedEvent(artist, newList, updateList)); + + if (failCount != 0) + { + _logger.Info("Finished album refresh for artist: {0}. Successful: {1} - Failed: {2} ", + artist.Name, successCount, failCount); + } + else + { + _logger.Info("Finished album refresh for artist: {0}.", artist); + } + } + + private bool GetMonitoredStatus(Album album, IEnumerable artists) + { + var artist = artists.SingleOrDefault(c => c.Id == album.ArtistId); + return album == null || album.Monitored; + } + + + private Album GetAlbumToUpdate(Artist artist, Album album, List existingAlbums) + { + return existingAlbums.FirstOrDefault(e => e.ForeignAlbumId == album.ForeignAlbumId && e.ReleaseDate == album.ReleaseDate); + } + + private IEnumerable OrderAlbums(Artist artist, List albums) + { + return albums.OrderBy(e => e.ForeignAlbumId).ThenBy(e => e.ReleaseDate); + } + } +} + diff --git a/src/NzbDrone.Core/Music/RefreshArtistService.cs b/src/NzbDrone.Core/Music/RefreshArtistService.cs index cdac3ac30..62febf26c 100644 --- a/src/NzbDrone.Core/Music/RefreshArtistService.cs +++ b/src/NzbDrone.Core/Music/RefreshArtistService.cs @@ -20,7 +20,7 @@ namespace NzbDrone.Core.Music { private readonly IProvideArtistInfo _artistInfo; private readonly IArtistService _artistService; - private readonly IRefreshTrackService _refreshTrackService; + private readonly IRefreshAlbumService _refreshAlbumService; private readonly IEventAggregator _eventAggregator; private readonly IDiskScanService _diskScanService; private readonly ICheckIfArtistShouldBeRefreshed _checkIfArtistShouldBeRefreshed; @@ -28,7 +28,7 @@ namespace NzbDrone.Core.Music public RefreshArtistService(IProvideArtistInfo artistInfo, IArtistService artistService, - IRefreshTrackService refreshTrackService, + IRefreshAlbumService refreshAlbumService, IEventAggregator eventAggregator, IDiskScanService diskScanService, ICheckIfArtistShouldBeRefreshed checkIfArtistShouldBeRefreshed, @@ -36,7 +36,7 @@ namespace NzbDrone.Core.Music { _artistInfo = artistInfo; _artistService = artistService; - _refreshTrackService = refreshTrackService; + _refreshAlbumService = refreshAlbumService; _eventAggregator = eventAggregator; _diskScanService = diskScanService; _checkIfArtistShouldBeRefreshed = checkIfArtistShouldBeRefreshed; @@ -47,7 +47,7 @@ namespace NzbDrone.Core.Music { _logger.ProgressInfo("Updating Info for {0}", artist.Name); - Tuple> tuple; + Tuple> tuple; try { @@ -89,7 +89,8 @@ namespace NzbDrone.Core.Music artist.Albums = UpdateAlbums(artist, artistInfo); _artistService.UpdateArtist(artist); - _refreshTrackService.RefreshTrackInfo(artist, tuple.Item2); + _refreshAlbumService.RefreshAlbumInfo(artist, tuple.Item2); + //_refreshTrackService.RefreshTrackInfo(artist, tuple.Item2); _logger.Debug("Finished artist refresh for {0}", artist.Name); _eventAggregator.PublishEvent(new ArtistUpdatedEvent(artist)); @@ -97,11 +98,11 @@ namespace NzbDrone.Core.Music private List UpdateAlbums(Artist artist, Artist artistInfo) { - var albums = artistInfo.Albums.DistinctBy(s => s.AlbumId).ToList(); + var albums = artistInfo.Albums.DistinctBy(s => s.ForeignAlbumId).ToList(); foreach (var album in albums) { - var existingAlbum = artist.Albums.FirstOrDefault(s => s.AlbumId == album.AlbumId); + var existingAlbum = artist.Albums.FirstOrDefault(s => s.ForeignAlbumId == album.ForeignAlbumId); //Todo: Should this should use the previous season's monitored state? if (existingAlbum == null) diff --git a/src/NzbDrone.Core/Music/RefreshTrackService.cs b/src/NzbDrone.Core/Music/RefreshTrackService.cs index 3bd5925a5..77592e4f3 100644 --- a/src/NzbDrone.Core/Music/RefreshTrackService.cs +++ b/src/NzbDrone.Core/Music/RefreshTrackService.cs @@ -11,7 +11,7 @@ namespace NzbDrone.Core.Music { public interface IRefreshTrackService { - void RefreshTrackInfo(Artist artist, IEnumerable remoteTracks); + void RefreshTrackInfo(Album album, IEnumerable remoteTracks); } public class RefreshTrackService : IRefreshTrackService @@ -27,24 +27,24 @@ namespace NzbDrone.Core.Music _logger = logger; } - public void RefreshTrackInfo(Artist artist, IEnumerable remoteTracks) + public void RefreshTrackInfo(Album album, IEnumerable remoteTracks) { - _logger.Info("Starting track info refresh for: {0}", artist); + _logger.Info("Starting track info refresh for: {0}", album); var successCount = 0; var failCount = 0; - var existingTracks = _trackService.GetTracksByArtist(artist.Id); - var albums = artist.Albums; + var existingTracks = _trackService.GetTracksByAlbum(album.ArtistId, album.Id); + //var albums = artist.Albums; var updateList = new List(); var newList = new List(); var dupeFreeRemoteTracks = remoteTracks.DistinctBy(m => new { m.AlbumId, m.TrackNumber }).ToList(); - foreach (var track in OrderTracks(artist, dupeFreeRemoteTracks)) + foreach (var track in OrderTracks(album, dupeFreeRemoteTracks)) { try { - var trackToUpdate = GetTrackToUpdate(artist, track, existingTracks); + var trackToUpdate = GetTrackToUpdate(album, track, existingTracks); if (trackToUpdate != null) { @@ -54,17 +54,17 @@ namespace NzbDrone.Core.Music else { trackToUpdate = new Track(); - trackToUpdate.Monitored = GetMonitoredStatus(track, albums); + trackToUpdate.Monitored = album.Monitored; newList.Add(trackToUpdate); } trackToUpdate.ForeignTrackId = track.ForeignTrackId; trackToUpdate.TrackNumber = track.TrackNumber; trackToUpdate.Title = track.Title ?? "Unknown"; - trackToUpdate.AlbumId = track.AlbumId; + trackToUpdate.AlbumId = album.Id; trackToUpdate.Album = track.Album; trackToUpdate.Explicit = track.Explicit; - trackToUpdate.ArtistId = artist.Id; + trackToUpdate.ArtistId = album.ArtistId; trackToUpdate.Compilation = track.Compilation; // TODO: Implement rest of [RefreshTrackService] fields @@ -75,7 +75,7 @@ namespace NzbDrone.Core.Music } catch (Exception e) { - _logger.Fatal(e, "An error has occurred while updating track info for artist {0}. {1}", artist, track); + _logger.Fatal(e, "An error has occurred while updating track info for album {0}. {1}", album, track); failCount++; } } @@ -92,16 +92,16 @@ namespace NzbDrone.Core.Music _trackService.UpdateMany(updateList); _trackService.InsertMany(newList); - _eventAggregator.PublishEvent(new TrackInfoRefreshedEvent(artist, newList, updateList)); + _eventAggregator.PublishEvent(new TrackInfoRefreshedEvent(album, newList, updateList)); if (failCount != 0) { - _logger.Info("Finished track refresh for artist: {0}. Successful: {1} - Failed: {2} ", - artist.Name, successCount, failCount); + _logger.Info("Finished track refresh for album: {0}. Successful: {1} - Failed: {2} ", + album.Title, successCount, failCount); } else { - _logger.Info("Finished track refresh for artist: {0}.", artist); + _logger.Info("Finished track refresh for album: {0}.", album); } } @@ -117,12 +117,12 @@ namespace NzbDrone.Core.Music } - private Track GetTrackToUpdate(Artist artist, Track track, List existingTracks) + private Track GetTrackToUpdate(Album album, Track track, List existingTracks) { return existingTracks.FirstOrDefault(e => e.AlbumId == track.AlbumId && e.TrackNumber == track.TrackNumber); } - private IEnumerable OrderTracks(Artist artist, List tracks) + private IEnumerable OrderTracks(Album album, List tracks) { return tracks.OrderBy(e => e.AlbumId).ThenBy(e => e.TrackNumber); } diff --git a/src/NzbDrone.Core/Music/TrackService.cs b/src/NzbDrone.Core/Music/TrackService.cs index 834d3b054..bbb4e74c9 100644 --- a/src/NzbDrone.Core/Music/TrackService.cs +++ b/src/NzbDrone.Core/Music/TrackService.cs @@ -18,7 +18,7 @@ namespace NzbDrone.Core.Music Track FindTrack(int artistId, int albumId, int trackNumber); Track FindTrackByTitle(int artistId, int albumId, string releaseTitle); List GetTracksByArtist(int artistId); - //List GetTracksByAlbum(string artistId, string albumId); + List GetTracksByAlbum(int artistId, int albumId); //List GetTracksByAlbumTitle(string artistId, string albumTitle); List TracksWithFiles(int artistId); //PagingSpec TracksWithoutFiles(PagingSpec pagingSpec); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index dc7c2c9b9..db0659a86 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -856,17 +856,24 @@ + + + + + + + diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index b9b53a0dc..d485da076 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -22,6 +22,8 @@ namespace NzbDrone.Core.Organizer string BuildSeasonPath(Series series, int seasonNumber); BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); string GetSeriesFolder(Series series, NamingConfig namingConfig = null); + string GetArtistFolder(Artist artist, NamingConfig namingConfig = null); + string GetAlbumFolder(Album album, NamingConfig namingConfig = null); string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null); // TODO: Implement Music functions @@ -232,6 +234,20 @@ namespace NzbDrone.Core.Organizer return CleanFolderName(ReplaceTokens(namingConfig.SeriesFolderFormat, tokenHandlers, namingConfig)); } + public string GetArtistFolder(Artist artist, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + AddArtistTokens(tokenHandlers, artist); + + return CleanFolderName(ReplaceTokens(namingConfig.ArtistFolderFormat, tokenHandlers, namingConfig)); + } + public string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null) { if (namingConfig == null) @@ -247,6 +263,20 @@ namespace NzbDrone.Core.Organizer return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig)); } + public string GetAlbumFolder(Album album, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + AddAlbumTokens(tokenHandlers, album); + + return CleanFolderName(ReplaceTokens(namingConfig.AlbumFolderFormat, tokenHandlers, namingConfig)); + } + public static string CleanTitle(string title) { title = title.Replace("&", "and"); @@ -285,7 +315,14 @@ namespace NzbDrone.Core.Organizer private void AddArtistTokens(Dictionary> tokenHandlers, Artist artist) { tokenHandlers["{Artist Name}"] = m => artist.Name; - tokenHandlers["{Artist CleanTitle}"] = m => CleanTitle(artist.Name); + tokenHandlers["{Artist CleanName}"] = m => CleanTitle(artist.Name); + } + + private void AddAlbumTokens(Dictionary> tokenHandlers, Album album) + { + tokenHandlers["{Album Title}"] = m => album.Title; + tokenHandlers["{Album CleanTitle}"] = m => CleanTitle(album.Title); + tokenHandlers["{Album Year}"] = m => album.ReleaseDate.Year.ToString(); } private string AddSeasonEpisodeNumberingTokens(string pattern, Dictionary> tokenHandlers, List episodes, NamingConfig namingConfig) diff --git a/src/UI/AddArtist/SearchResultView.js b/src/UI/AddArtist/SearchResultView.js index 47dd29944..9c0529c60 100644 --- a/src/UI/AddArtist/SearchResultView.js +++ b/src/UI/AddArtist/SearchResultView.js @@ -223,12 +223,12 @@ var view = Marionette.ItemView.extend({ self.close(); Messenger.show({ - message : 'Added: ' + self.model.get('artistName'), + message : 'Added: ' + self.model.get('name'), actions : { goToArtist : { label : 'Go to Artist', action : function() { - Backbone.history.navigate('/artist/' + self.model.get('artistSlug'), { trigger : true }); + Backbone.history.navigate('/artist/' + self.model.get('nameSlug'), { trigger : true }); } } }, diff --git a/src/UI/AddArtist/SearchResultViewTemplate.hbs b/src/UI/AddArtist/SearchResultViewTemplate.hbs index 686bad6b3..3c27e1b97 100644 --- a/src/UI/AddArtist/SearchResultViewTemplate.hbs +++ b/src/UI/AddArtist/SearchResultViewTemplate.hbs @@ -16,11 +16,11 @@

- +
{{#unless existing}} {{#unless path}} From fe58f54ad4387bab9578731ecd5176513c7efe76 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 18 Jun 2017 22:16:39 -0400 Subject: [PATCH 07/13] NamingConfig Refactor Adds track NamingConfig, Gets naming section in settings working. Adds Release Year token and track number token --- src/NzbDrone.Api/Config/NamingConfigModule.cs | 19 ++ .../Config/NamingConfigResource.cs | 16 +- .../Config/NamingSampleResource.cs | 3 + .../Datastore/Migration/111_setup_music.cs | 2 + .../Organizer/FileNameBuilder.cs | 166 ++++++++++++++---- .../Organizer/FileNameSampleService.cs | 74 ++++++++ .../Organizer/FileNameValidation.cs | 32 ++++ .../Organizer/FileNameValidationService.cs | 22 +++ src/NzbDrone.Core/Organizer/NamingConfig.cs | 8 +- src/NzbDrone.Core/Organizer/SampleResult.cs | 5 + .../MediaManagement/Naming/NamingView.js | 35 ++-- .../Naming/NamingViewTemplate.hbs | 159 +++-------------- .../Partials/AlbumTitleNamingPartial.hbs | 11 ++ .../Partials/ArtistNameNamingPartial.hbs | 11 ++ .../Partials/ReleaseYearNamingPartial.hbs | 1 + .../Naming/Partials/TrackNumNamingPartial.hbs | 7 + .../Partials/TrackTitleNamingPartial.hbs | 11 ++ 17 files changed, 390 insertions(+), 192 deletions(-) create mode 100644 src/UI/Settings/MediaManagement/Naming/Partials/AlbumTitleNamingPartial.hbs create mode 100644 src/UI/Settings/MediaManagement/Naming/Partials/ArtistNameNamingPartial.hbs create mode 100644 src/UI/Settings/MediaManagement/Naming/Partials/ReleaseYearNamingPartial.hbs create mode 100644 src/UI/Settings/MediaManagement/Naming/Partials/TrackNumNamingPartial.hbs create mode 100644 src/UI/Settings/MediaManagement/Naming/Partials/TrackTitleNamingPartial.hbs diff --git a/src/NzbDrone.Api/Config/NamingConfigModule.cs b/src/NzbDrone.Api/Config/NamingConfigModule.cs index 0b72e0b0c..d2f05539a 100644 --- a/src/NzbDrone.Api/Config/NamingConfigModule.cs +++ b/src/NzbDrone.Api/Config/NamingConfigModule.cs @@ -35,10 +35,13 @@ namespace NzbDrone.Api.Config SharedValidator.RuleFor(c => c.MultiEpisodeStyle).InclusiveBetween(0, 5); SharedValidator.RuleFor(c => c.StandardEpisodeFormat).ValidEpisodeFormat(); + SharedValidator.RuleFor(c => c.StandardTrackFormat).ValidTrackFormat(); SharedValidator.RuleFor(c => c.DailyEpisodeFormat).ValidDailyEpisodeFormat(); SharedValidator.RuleFor(c => c.AnimeEpisodeFormat).ValidAnimeEpisodeFormat(); SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat(); SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat(); + SharedValidator.RuleFor(c => c.ArtistFolderFormat).ValidArtistFolderFormat(); + SharedValidator.RuleFor(c => c.AlbumFolderFormat).ValidAlbumFolderFormat(); } private void UpdateNamingConfig(NamingConfigResource resource) @@ -74,6 +77,7 @@ namespace NzbDrone.Api.Config var sampleResource = new NamingSampleResource(); var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec); + var singleTrackSampleResult = _filenameSampleService.GetStandardTrackSample(nameSpec); var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec); var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec); var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec); @@ -83,6 +87,10 @@ namespace NzbDrone.Api.Config ? "Invalid format" : singleEpisodeSampleResult.FileName; + sampleResource.SingleTrackExample = _filenameValidationService.ValidateTrackFilename(singleTrackSampleResult) != null + ? "Invalid format" + : singleTrackSampleResult.FileName; + sampleResource.MultiEpisodeExample = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult) != null ? "Invalid format" : multiEpisodeSampleResult.FileName; @@ -107,18 +115,28 @@ namespace NzbDrone.Api.Config ? "Invalid format" : _filenameSampleService.GetSeasonFolderSample(nameSpec); + sampleResource.ArtistFolderExample = nameSpec.ArtistFolderFormat.IsNullOrWhiteSpace() + ? "Invalid format" + : _filenameSampleService.GetArtistFolderSample(nameSpec); + + sampleResource.AlbumFolderExample = nameSpec.AlbumFolderFormat.IsNullOrWhiteSpace() + ? "Invalid format" + : _filenameSampleService.GetAlbumFolderSample(nameSpec); + return sampleResource.AsResponse(); } private void ValidateFormatResult(NamingConfig nameSpec) { var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec); + var singleTrackSampleResult = _filenameSampleService.GetStandardTrackSample(nameSpec); var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec); var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec); var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec); var animeMultiEpisodeSampleResult = _filenameSampleService.GetAnimeMultiEpisodeSample(nameSpec); var singleEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult); + var singleTrackValidationResult = _filenameValidationService.ValidateTrackFilename(singleTrackSampleResult); var multiEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult); var dailyEpisodeValidationResult = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult); var animeEpisodeValidationResult = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult); @@ -127,6 +145,7 @@ namespace NzbDrone.Api.Config var validationFailures = new List(); validationFailures.AddIfNotNull(singleEpisodeValidationResult); + validationFailures.AddIfNotNull(singleTrackValidationResult); validationFailures.AddIfNotNull(multiEpisodeValidationResult); validationFailures.AddIfNotNull(dailyEpisodeValidationResult); validationFailures.AddIfNotNull(animeEpisodeValidationResult); diff --git a/src/NzbDrone.Api/Config/NamingConfigResource.cs b/src/NzbDrone.Api/Config/NamingConfigResource.cs index 39147b993..d49c39936 100644 --- a/src/NzbDrone.Api/Config/NamingConfigResource.cs +++ b/src/NzbDrone.Api/Config/NamingConfigResource.cs @@ -6,13 +6,17 @@ namespace NzbDrone.Api.Config public class NamingConfigResource : RestResource { public bool RenameEpisodes { get; set; } + public bool RenameTracks { get; set; } public bool ReplaceIllegalCharacters { get; set; } public int MultiEpisodeStyle { get; set; } public string StandardEpisodeFormat { get; set; } + public string StandardTrackFormat { get; set; } public string DailyEpisodeFormat { get; set; } public string AnimeEpisodeFormat { get; set; } public string SeriesFolderFormat { get; set; } public string SeasonFolderFormat { get; set; } + public string ArtistFolderFormat { get; set; } + public string AlbumFolderFormat { get; set; } public bool IncludeSeriesTitle { get; set; } public bool IncludeEpisodeTitle { get; set; } public bool IncludeQuality { get; set; } @@ -30,13 +34,17 @@ namespace NzbDrone.Api.Config Id = model.Id, RenameEpisodes = model.RenameEpisodes, + RenameTracks = model.RenameTracks, ReplaceIllegalCharacters = model.ReplaceIllegalCharacters, MultiEpisodeStyle = model.MultiEpisodeStyle, StandardEpisodeFormat = model.StandardEpisodeFormat, + StandardTrackFormat = model.StandardTrackFormat, DailyEpisodeFormat = model.DailyEpisodeFormat, AnimeEpisodeFormat = model.AnimeEpisodeFormat, SeriesFolderFormat = model.SeriesFolderFormat, - SeasonFolderFormat = model.SeasonFolderFormat + SeasonFolderFormat = model.SeasonFolderFormat, + ArtistFolderFormat = model.ArtistFolderFormat, + AlbumFolderFormat = model.AlbumFolderFormat //IncludeSeriesTitle //IncludeEpisodeTitle //IncludeQuality @@ -63,13 +71,17 @@ namespace NzbDrone.Api.Config Id = resource.Id, RenameEpisodes = resource.RenameEpisodes, + RenameTracks = resource.RenameTracks, ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters, MultiEpisodeStyle = resource.MultiEpisodeStyle, StandardEpisodeFormat = resource.StandardEpisodeFormat, + StandardTrackFormat = resource.StandardTrackFormat, DailyEpisodeFormat = resource.DailyEpisodeFormat, AnimeEpisodeFormat = resource.AnimeEpisodeFormat, SeriesFolderFormat = resource.SeriesFolderFormat, - SeasonFolderFormat = resource.SeasonFolderFormat + SeasonFolderFormat = resource.SeasonFolderFormat, + ArtistFolderFormat = resource.ArtistFolderFormat, + AlbumFolderFormat = resource.AlbumFolderFormat }; } } diff --git a/src/NzbDrone.Api/Config/NamingSampleResource.cs b/src/NzbDrone.Api/Config/NamingSampleResource.cs index 1f9c7f066..f6d6d15b3 100644 --- a/src/NzbDrone.Api/Config/NamingSampleResource.cs +++ b/src/NzbDrone.Api/Config/NamingSampleResource.cs @@ -3,11 +3,14 @@ public class NamingSampleResource { public string SingleEpisodeExample { get; set; } + public string SingleTrackExample { get; set; } public string MultiEpisodeExample { get; set; } public string DailyEpisodeExample { get; set; } public string AnimeEpisodeExample { get; set; } public string AnimeMultiEpisodeExample { get; set; } public string SeriesFolderExample { get; set; } public string SeasonFolderExample { get; set; } + public string ArtistFolderExample { get; set; } + public string AlbumFolderExample { get; set; } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs b/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs index d845291d8..117412062 100644 --- a/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs +++ b/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs @@ -97,6 +97,8 @@ namespace NzbDrone.Core.Datastore.Migration Alter.Table("NamingConfig") .AddColumn("ArtistFolderFormat").AsString().Nullable() + .AddColumn("RenameTracks").AsBoolean().Nullable() + .AddColumn("StandardTrackFormat").AsString().Nullable() .AddColumn("AlbumFolderFormat").AsString().Nullable(); } diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index d485da076..f5d3c3c24 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -18,12 +18,13 @@ namespace NzbDrone.Core.Organizer public interface IBuildFileNames { string BuildFileName(List episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null); + string BuildTrackFileName(List tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig namingConfig = null); string BuildFilePath(Series series, int seasonNumber, string fileName, string extension); string BuildSeasonPath(Series series, int seasonNumber); BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); string GetSeriesFolder(Series series, NamingConfig namingConfig = null); string GetArtistFolder(Artist artist, NamingConfig namingConfig = null); - string GetAlbumFolder(Album album, NamingConfig namingConfig = null); + string GetAlbumFolder(Artist artist, Album album, NamingConfig namingConfig = null); string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null); // TODO: Implement Music functions @@ -44,6 +45,9 @@ namespace NzbDrone.Core.Organizer private static readonly Regex EpisodeRegex = new Regex(@"(?\{episode(?:\:0+)?})", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex TrackRegex = new Regex(@"(?\{track(?:\:0+)?})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex SeasonRegex = new Regex(@"(?\{season(?:\:0+)?})", RegexOptions.Compiled | RegexOptions.IgnoreCase); @@ -61,6 +65,12 @@ namespace NzbDrone.Core.Organizer public static readonly Regex SeriesTitleRegex = new Regex(@"(?\{(?:Series)(?[- ._])(Clean)?Title\})", RegexOptions.Compiled | RegexOptions.IgnoreCase); + public static readonly Regex ArtistNameRegex = new Regex(@"(?\{(?:Artist)(?[- ._])(Clean)?Name\})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static readonly Regex AlbumTitleRegex = new Regex(@"(?\{(?:Album)(?[- ._])(Clean)?Title\})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled); private static readonly Regex TrimSeparatorsRegex = new Regex(@"[- ._]$", RegexOptions.Compiled); @@ -142,6 +152,47 @@ namespace NzbDrone.Core.Organizer return fileName; } + public string BuildTrackFileName(List tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + if (!namingConfig.RenameTracks) + { + return GetOriginalTitle(trackFile); + } + + if (namingConfig.StandardTrackFormat.IsNullOrWhiteSpace()) + { + throw new NamingFormatException("Standard track format cannot be empty"); + } + + var pattern = namingConfig.StandardTrackFormat; + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + tracks = tracks.OrderBy(e => e.AlbumId).ThenBy(e => e.TrackNumber).ToList(); + + //pattern = AddSeasonEpisodeNumberingTokens(pattern, tokenHandlers, episodes, namingConfig); + + pattern = FormatTrackNumberTokens(pattern, "", tracks); + //pattern = AddAbsoluteNumberingTokens(pattern, tokenHandlers, series, episodes, namingConfig); + + AddArtistTokens(tokenHandlers, artist); + AddAlbumTokens(tokenHandlers, album); + AddTrackTokens(tokenHandlers, tracks); + AddTrackFileTokens(tokenHandlers, trackFile); + AddQualityTokens(tokenHandlers, artist, trackFile); + //AddMediaInfoTokens(tokenHandlers, trackFile); TODO ReWork MediaInfo for Tracks + + var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); + fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString()); + fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty); + + return fileName; + } + public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension) { Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); @@ -263,7 +314,7 @@ namespace NzbDrone.Core.Organizer return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig)); } - public string GetAlbumFolder(Album album, NamingConfig namingConfig = null) + public string GetAlbumFolder(Artist artist, Album album, NamingConfig namingConfig = null) { if (namingConfig == null) { @@ -273,6 +324,7 @@ namespace NzbDrone.Core.Organizer var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); AddAlbumTokens(tokenHandlers, album); + AddArtistTokens(tokenHandlers, artist); return CleanFolderName(ReplaceTokens(namingConfig.AlbumFolderFormat, tokenHandlers, namingConfig)); } @@ -322,7 +374,7 @@ namespace NzbDrone.Core.Organizer { tokenHandlers["{Album Title}"] = m => album.Title; tokenHandlers["{Album CleanTitle}"] = m => CleanTitle(album.Title); - tokenHandlers["{Album Year}"] = m => album.ReleaseDate.Year.ToString(); + tokenHandlers["{Release Year}"] = m => album.ReleaseDate.Year.ToString(); } private string AddSeasonEpisodeNumberingTokens(string pattern, Dictionary> tokenHandlers, List episodes, NamingConfig namingConfig) @@ -469,6 +521,12 @@ namespace NzbDrone.Core.Organizer tokenHandlers["{Episode CleanTitle}"] = m => CleanTitle(GetEpisodeTitle(episodes, "and")); } + private void AddTrackTokens(Dictionary> tokenHandlers, List tracks) + { + tokenHandlers["{Track Title}"] = m => GetTrackTitle(tracks, "+"); + tokenHandlers["{Track CleanTitle}"] = m => CleanTitle(GetTrackTitle(tracks, "and")); + } + private void AddEpisodeFileTokens(Dictionary> tokenHandlers, EpisodeFile episodeFile) { tokenHandlers["{Original Title}"] = m => GetOriginalTitle(episodeFile); @@ -476,6 +534,13 @@ namespace NzbDrone.Core.Organizer tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? m.DefaultValue("Lidarr"); } + private void AddTrackFileTokens(Dictionary> tokenHandlers, TrackFile trackFile) + { + tokenHandlers["{Original Title}"] = m => GetOriginalTitle(trackFile); + tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(trackFile); + tokenHandlers["{Release Group}"] = m => trackFile.ReleaseGroup ?? m.DefaultValue("Lidarr"); + } + private void AddQualityTokens(Dictionary> tokenHandlers, Series series, EpisodeFile episodeFile) { var qualityTitle = _qualityDefinitionService.Get(episodeFile.Quality.Quality).Title; @@ -488,6 +553,18 @@ namespace NzbDrone.Core.Organizer tokenHandlers["{Quality Real}"] = m => qualityReal; } + private void AddQualityTokens(Dictionary> tokenHandlers, Artist artist, TrackFile trackFile) + { + var qualityTitle = _qualityDefinitionService.Get(trackFile.Quality.Quality).Title; + //var qualityProper = GetQualityProper(artist, trackFile.Quality); + //var qualityReal = GetQualityReal(artist, trackFile.Quality); + + tokenHandlers["{Quality Full}"] = m => String.Format("{0}", qualityTitle); + tokenHandlers["{Quality Title}"] = m => qualityTitle; + //tokenHandlers["{Quality Proper}"] = m => qualityProper; + //tokenHandlers["{Quality Real}"] = m => qualityReal; + } + private void AddMediaInfoTokens(Dictionary> tokenHandlers, EpisodeFile episodeFile) { if (episodeFile.MediaInfo == null) return; @@ -683,6 +760,20 @@ namespace NzbDrone.Core.Organizer return ReplaceSeasonTokens(pattern, episodes.First().SeasonNumber); } + private string FormatTrackNumberTokens(string basePattern, string formatPattern, List tracks) + { + var pattern = string.Empty; + + for (int i = 0; i < tracks.Count; i++) + { + var patternToReplace = i == 0 ? basePattern : formatPattern; + + pattern += TrackRegex.Replace(patternToReplace, match => ReplaceNumberToken(match.Groups["track"].Value, tracks[i].TrackNumber)); + } + + return pattern; + } + private string FormatAbsoluteNumberTokens(string basePattern, string formatPattern, List episodes) { var pattern = string.Empty; @@ -765,6 +856,30 @@ namespace NzbDrone.Core.Organizer return string.Join(separator, titles); } + private string GetTrackTitle(List tracks, string separator) + { + separator = string.Format(" {0} ", separator.Trim()); + + if (tracks.Count == 1) + { + return tracks.First().Title.TrimEnd(EpisodeTitleTrimCharacters); + } + + var titles = tracks.Select(c => c.Title.TrimEnd(EpisodeTitleTrimCharacters)) + .Select(CleanupEpisodeTitle) + .Distinct() + .ToList(); + + if (titles.All(t => t.IsNullOrWhiteSpace())) + { + titles = tracks.Select(c => c.Title.TrimEnd(EpisodeTitleTrimCharacters)) + .Distinct() + .ToList(); + } + + return string.Join(separator, titles); + } + private string CleanupEpisodeTitle(string title) { //this will remove (1),(2) from the end of multi part episodes. @@ -806,6 +921,16 @@ namespace NzbDrone.Core.Organizer return episodeFile.SceneName; } + private string GetOriginalTitle(TrackFile trackFile) + { + if (trackFile.SceneName.IsNullOrWhiteSpace()) + { + return GetOriginalFileName(trackFile); + } + + return trackFile.SceneName; + } + private string GetOriginalFileName(EpisodeFile episodeFile) { if (episodeFile.RelativePath.IsNullOrWhiteSpace()) @@ -816,35 +941,16 @@ namespace NzbDrone.Core.Organizer return Path.GetFileNameWithoutExtension(episodeFile.RelativePath); } - //public string GetArtistFolder(Artist artist, NamingConfig namingConfig = null) - //{ - // if (namingConfig == null) - // { - // namingConfig = _namingConfigService.GetConfig(); - // } - - // var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); - - // AddArtistTokens(tokenHandlers, artist); - - // return CleanFolderName(ReplaceTokens("{Artist Name}", tokenHandlers, namingConfig)); //namingConfig.ArtistFolderFormat, - //} - - //public string GetAlbumFolder(Artist artist, string albumName, NamingConfig namingConfig = null) - //{ - // throw new NotImplementedException(); - // //if (namingConfig == null) - // //{ - // // namingConfig = _namingConfigService.GetConfig(); - // //} - - // //var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + private string GetOriginalFileName(TrackFile trackFile) + { + if (trackFile.RelativePath.IsNullOrWhiteSpace()) + { + return Path.GetFileNameWithoutExtension(trackFile.Path); + } - // //AddSeriesTokens(tokenHandlers, artist); - // //AddSeasonTokens(tokenHandlers, seasonNumber); + return Path.GetFileNameWithoutExtension(trackFile.RelativePath); + } - // //return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig)); - //} } internal sealed class TokenMatch diff --git a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs index 7f92fe180..324e6e524 100644 --- a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs +++ b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs @@ -2,6 +2,7 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; using NzbDrone.Core.MediaFiles.MediaInfo; namespace NzbDrone.Core.Organizer @@ -9,26 +10,34 @@ namespace NzbDrone.Core.Organizer public interface IFilenameSampleService { SampleResult GetStandardSample(NamingConfig nameSpec); + SampleResult GetStandardTrackSample(NamingConfig nameSpec); SampleResult GetMultiEpisodeSample(NamingConfig nameSpec); SampleResult GetDailySample(NamingConfig nameSpec); SampleResult GetAnimeSample(NamingConfig nameSpec); SampleResult GetAnimeMultiEpisodeSample(NamingConfig nameSpec); string GetSeriesFolderSample(NamingConfig nameSpec); string GetSeasonFolderSample(NamingConfig nameSpec); + string GetArtistFolderSample(NamingConfig nameSpec); + string GetAlbumFolderSample(NamingConfig nameSpec); } public class FileNameSampleService : IFilenameSampleService { private readonly IBuildFileNames _buildFileNames; private static Series _standardSeries; + private static Artist _standardArtist; + private static Album _standardAlbum; + private static Track _track1; private static Series _dailySeries; private static Series _animeSeries; private static Episode _episode1; private static Episode _episode2; private static Episode _episode3; private static List _singleEpisode; + private static List _singleTrack; private static List _multiEpisodes; private static EpisodeFile _singleEpisodeFile; + private static TrackFile _singleTrackFile; private static EpisodeFile _multiEpisodeFile; private static EpisodeFile _dailyEpisodeFile; private static EpisodeFile _animeEpisodeFile; @@ -44,6 +53,17 @@ namespace NzbDrone.Core.Organizer Title = "Series Title (2010)" }; + _standardArtist = new Artist + { + Name = "Artist Name" + }; + + _standardAlbum = new Album + { + Title = "Album Title", + ReleaseDate = System.DateTime.Today + }; + _dailySeries = new Series { SeriesType = SeriesTypes.Daily, @@ -56,6 +76,14 @@ namespace NzbDrone.Core.Organizer Title = "Series Title (2010)" }; + _track1 = new Track + { + TrackNumber = 3, + + Title = "Track Title (1)", + + }; + _episode1 = new Episode { SeasonNumber = 1, @@ -82,6 +110,7 @@ namespace NzbDrone.Core.Organizer }; _singleEpisode = new List { _episode1 }; + _singleTrack = new List { _track1 }; _multiEpisodes = new List { _episode1, _episode2, _episode3 }; var mediaInfo = new MediaInfoModel() @@ -115,6 +144,15 @@ namespace NzbDrone.Core.Organizer MediaInfo = mediaInfo }; + _singleTrackFile = new TrackFile + { + Quality = new QualityModel(Quality.MP3256, new Revision(2)), + RelativePath = "Artist.Name.Album.Name.TrackNum.Track.Title.MP3256.mp3", + SceneName = "Artist.Name.Album.Name.TrackNum.Track.Title.MP3256", + ReleaseGroup = "RlsGrp", + MediaInfo = mediaInfo + }; + _multiEpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.MP3256, new Revision(2)), @@ -165,6 +203,20 @@ namespace NzbDrone.Core.Organizer return result; } + public SampleResult GetStandardTrackSample(NamingConfig nameSpec) + { + var result = new SampleResult + { + FileName = BuildTrackSample(_singleTrack, _standardArtist, _standardAlbum, _singleTrackFile, nameSpec), + Artist = _standardArtist, + Album = _standardAlbum, + Tracks = _singleTrack, + TrackFile = _singleTrackFile + }; + + return result; + } + public SampleResult GetMultiEpisodeSample(NamingConfig nameSpec) { var result = new SampleResult @@ -227,6 +279,16 @@ namespace NzbDrone.Core.Organizer return _buildFileNames.GetSeasonFolder(_standardSeries, _episode1.SeasonNumber, nameSpec); } + public string GetArtistFolderSample(NamingConfig nameSpec) + { + return _buildFileNames.GetArtistFolder(_standardArtist, nameSpec); + } + + public string GetAlbumFolderSample(NamingConfig nameSpec) + { + return _buildFileNames.GetAlbumFolder(_standardArtist, _standardAlbum, nameSpec); + } + private string BuildSample(List episodes, Series series, EpisodeFile episodeFile, NamingConfig nameSpec) { try @@ -238,5 +300,17 @@ namespace NzbDrone.Core.Organizer return string.Empty; } } + + private string BuildTrackSample(List tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig nameSpec) + { + try + { + return _buildFileNames.BuildTrackFileName(tracks, artist, album, trackFile, nameSpec); + } + catch (NamingFormatException) + { + return string.Empty; + } + } } } diff --git a/src/NzbDrone.Core/Organizer/FileNameValidation.cs b/src/NzbDrone.Core/Organizer/FileNameValidation.cs index 930b8a044..5231f1fe6 100644 --- a/src/NzbDrone.Core/Organizer/FileNameValidation.cs +++ b/src/NzbDrone.Core/Organizer/FileNameValidation.cs @@ -18,6 +18,12 @@ namespace NzbDrone.Core.Organizer return ruleBuilder.SetValidator(new ValidStandardEpisodeFormatValidator()); } + public static IRuleBuilderOptions ValidTrackFormat(this IRuleBuilder ruleBuilder) + { + ruleBuilder.SetValidator(new NotEmptyValidator(null)); + return ruleBuilder.SetValidator(new ValidStandardTrackFormatValidator()); + } + public static IRuleBuilderOptions ValidDailyEpisodeFormat(this IRuleBuilder ruleBuilder) { ruleBuilder.SetValidator(new NotEmptyValidator(null)); @@ -41,6 +47,17 @@ namespace NzbDrone.Core.Organizer ruleBuilder.SetValidator(new NotEmptyValidator(null)); return ruleBuilder.SetValidator(new RegularExpressionValidator(SeasonFolderRegex)).WithMessage("Must contain season number"); } + public static IRuleBuilderOptions ValidArtistFolderFormat(this IRuleBuilder ruleBuilder) + { + ruleBuilder.SetValidator(new NotEmptyValidator(null)); + return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.ArtistNameRegex)).WithMessage("Must contain Artist name"); + } + + public static IRuleBuilderOptions ValidAlbumFolderFormat(this IRuleBuilder ruleBuilder) + { + ruleBuilder.SetValidator(new NotEmptyValidator(null)); + return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.AlbumTitleRegex)).WithMessage("Must contain Album name"); + } } public class ValidStandardEpisodeFormatValidator : PropertyValidator @@ -65,6 +82,21 @@ namespace NzbDrone.Core.Organizer } } + public class ValidStandardTrackFormatValidator : PropertyValidator + { + public ValidStandardTrackFormatValidator() + : base("Must contain Album Title and Track numbers OR Original Title") + { + + } + + protected override bool IsValid(PropertyValidatorContext context) + { + + return true; //TODO Add Logic here + } + } + public class ValidDailyEpisodeFormatValidator : PropertyValidator { public ValidDailyEpisodeFormatValidator() diff --git a/src/NzbDrone.Core/Organizer/FileNameValidationService.cs b/src/NzbDrone.Core/Organizer/FileNameValidationService.cs index 9367c11d8..a26b619c8 100644 --- a/src/NzbDrone.Core/Organizer/FileNameValidationService.cs +++ b/src/NzbDrone.Core/Organizer/FileNameValidationService.cs @@ -9,6 +9,7 @@ namespace NzbDrone.Core.Organizer public interface IFilenameValidationService { ValidationFailure ValidateStandardFilename(SampleResult sampleResult); + ValidationFailure ValidateTrackFilename(SampleResult sampleResult); ValidationFailure ValidateDailyFilename(SampleResult sampleResult); ValidationFailure ValidateAnimeFilename(SampleResult sampleResult); } @@ -35,6 +36,27 @@ namespace NzbDrone.Core.Organizer return null; } + public ValidationFailure ValidateTrackFilename(SampleResult sampleResult) + { + var validationFailure = new ValidationFailure("StandardTrackFormat", ERROR_MESSAGE); + + //TODO Add Validation for TrackFilename + //var parsedEpisodeInfo = Parser.Parser.ParseTitle(sampleResult.FileName); + + + //if (parsedEpisodeInfo == null) + //{ + // return validationFailure; + //} + + //if (!ValidateSeasonAndEpisodeNumbers(sampleResult.Episodes, parsedEpisodeInfo)) + //{ + // return validationFailure; + //} + + return null; + } + public ValidationFailure ValidateDailyFilename(SampleResult sampleResult) { var validationFailure = new ValidationFailure("DailyEpisodeFormat", ERROR_MESSAGE); diff --git a/src/NzbDrone.Core/Organizer/NamingConfig.cs b/src/NzbDrone.Core/Organizer/NamingConfig.cs index 637cd15cd..b0a4d811a 100644 --- a/src/NzbDrone.Core/Organizer/NamingConfig.cs +++ b/src/NzbDrone.Core/Organizer/NamingConfig.cs @@ -1,4 +1,4 @@ -using NzbDrone.Core.Datastore; +using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Organizer { @@ -7,21 +7,25 @@ namespace NzbDrone.Core.Organizer public static NamingConfig Default => new NamingConfig { RenameEpisodes = false, + RenameTracks = false, ReplaceIllegalCharacters = true, MultiEpisodeStyle = 0, StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}", + StandardTrackFormat = "{Artist Name} - {track:00} - {Album Title} - {Track Title}", DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title} {Quality Full}", AnimeEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}", SeriesFolderFormat = "{Series Title}", SeasonFolderFormat = "Season {season}", ArtistFolderFormat = "{Artist Name}", - AlbumFolderFormat = "{Album Name} ({Year})" + AlbumFolderFormat = "{Album Title} ({Release Year})" }; public bool RenameEpisodes { get; set; } + public bool RenameTracks { get; set; } public bool ReplaceIllegalCharacters { get; set; } public int MultiEpisodeStyle { get; set; } public string StandardEpisodeFormat { get; set; } + public string StandardTrackFormat { get; set; } public string DailyEpisodeFormat { get; set; } public string AnimeEpisodeFormat { get; set; } public string SeriesFolderFormat { get; set; } diff --git a/src/NzbDrone.Core/Organizer/SampleResult.cs b/src/NzbDrone.Core/Organizer/SampleResult.cs index 0f3885a1b..3075032ce 100644 --- a/src/NzbDrone.Core/Organizer/SampleResult.cs +++ b/src/NzbDrone.Core/Organizer/SampleResult.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; namespace NzbDrone.Core.Organizer { @@ -8,7 +9,11 @@ namespace NzbDrone.Core.Organizer { public string FileName { get; set; } public Series Series { get; set; } + public Artist Artist { get; set; } + public Album Album { get; set; } public List Episodes { get; set; } public EpisodeFile EpisodeFile { get; set; } + public List Tracks { get; set; } + public TrackFile TrackFile { get; set; } } } diff --git a/src/UI/Settings/MediaManagement/Naming/NamingView.js b/src/UI/Settings/MediaManagement/Naming/NamingView.js index 71e4df4f8..55dbb860f 100644 --- a/src/UI/Settings/MediaManagement/Naming/NamingView.js +++ b/src/UI/Settings/MediaManagement/Naming/NamingView.js @@ -10,26 +10,20 @@ module.exports = (function() { template : 'Settings/MediaManagement/Naming/NamingViewTemplate', ui : { namingOptions : '.x-naming-options', - renameEpisodesCheckbox : '.x-rename-episodes', - singleEpisodeExample : '.x-single-episode-example', - multiEpisodeExample : '.x-multi-episode-example', - dailyEpisodeExample : '.x-daily-episode-example', - animeEpisodeExample : '.x-anime-episode-example', - animeMultiEpisodeExample : '.x-anime-multi-episode-example', + renameTracksCheckbox : '.x-rename-tracks', + singleTrackExample : '.x-single-track-example', namingTokenHelper : '.x-naming-token-helper', - multiEpisodeStyle : '.x-multi-episode-style', - seriesFolderExample : '.x-series-folder-example', - seasonFolderExample : '.x-season-folder-example' + artistFolderExample : '.x-artist-folder-example', + albumFolderExample : '.x-album-folder-example' }, events : { - "change .x-rename-episodes" : '_setFailedDownloadOptionsVisibility', + "change .x-rename-tracks" : '_setFailedDownloadOptionsVisibility', "click .x-show-wizard" : '_showWizard', - "click .x-naming-token-helper a" : '_addToken', - "change .x-multi-episode-style" : '_multiEpisodeFomatChanged' + "click .x-naming-token-helper a" : '_addToken' }, regions : { basicNamingRegion : '.x-basic-naming' }, onRender : function() { - if (!this.model.get('renameEpisodes')) { + if (!this.model.get('renameTracks')) { this.ui.namingOptions.hide(); } var basicNamingView = new BasicNamingView({ model : this.model }); @@ -40,7 +34,7 @@ module.exports = (function() { this._updateSamples(); }, _setFailedDownloadOptionsVisibility : function() { - var checked = this.ui.renameEpisodesCheckbox.prop('checked'); + var checked = this.ui.renameTracksCheckbox.prop('checked'); if (checked) { this.ui.namingOptions.slideDown(); } else { @@ -51,13 +45,9 @@ module.exports = (function() { this.namingSampleModel.fetch({ data : this.model.toJSON() }); }, _showSamples : function() { - this.ui.singleEpisodeExample.html(this.namingSampleModel.get('singleEpisodeExample')); - this.ui.multiEpisodeExample.html(this.namingSampleModel.get('multiEpisodeExample')); - this.ui.dailyEpisodeExample.html(this.namingSampleModel.get('dailyEpisodeExample')); - this.ui.animeEpisodeExample.html(this.namingSampleModel.get('animeEpisodeExample')); - this.ui.animeMultiEpisodeExample.html(this.namingSampleModel.get('animeMultiEpisodeExample')); - this.ui.seriesFolderExample.html(this.namingSampleModel.get('seriesFolderExample')); - this.ui.seasonFolderExample.html(this.namingSampleModel.get('seasonFolderExample')); + this.ui.singleTrackExample.html(this.namingSampleModel.get('singleTrackExample')); + this.ui.artistFolderExample.html(this.namingSampleModel.get('artistFolderExample')); + this.ui.albumFolderExample.html(this.namingSampleModel.get('albumFolderExample')); }, _addToken : function(e) { e.preventDefault(); @@ -75,9 +65,6 @@ module.exports = (function() { this.ui.namingTokenHelper.removeClass('open'); input.focus(); }, - multiEpisodeFormatChanged : function() { - this.model.set('multiEpisodeStyle', this.ui.multiEpisodeStyle.val()); - } }); AsModelBoundView.call(view); AsValidatedView.call(view); diff --git a/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.hbs b/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.hbs index 39ddcf63f..6b46ace90 100644 --- a/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.hbs +++ b/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.hbs @@ -1,13 +1,13 @@
- Episode Naming + Track Naming
- +
diff --git a/src/UI/Settings/MediaManagement/Naming/Partials/AlbumTitleNamingPartial.hbs b/src/UI/Settings/MediaManagement/Naming/Partials/AlbumTitleNamingPartial.hbs new file mode 100644 index 000000000..9e3da7a54 --- /dev/null +++ b/src/UI/Settings/MediaManagement/Naming/Partials/AlbumTitleNamingPartial.hbs @@ -0,0 +1,11 @@ + diff --git a/src/UI/Settings/MediaManagement/Naming/Partials/ArtistNameNamingPartial.hbs b/src/UI/Settings/MediaManagement/Naming/Partials/ArtistNameNamingPartial.hbs new file mode 100644 index 000000000..c951bd123 --- /dev/null +++ b/src/UI/Settings/MediaManagement/Naming/Partials/ArtistNameNamingPartial.hbs @@ -0,0 +1,11 @@ + diff --git a/src/UI/Settings/MediaManagement/Naming/Partials/ReleaseYearNamingPartial.hbs b/src/UI/Settings/MediaManagement/Naming/Partials/ReleaseYearNamingPartial.hbs new file mode 100644 index 000000000..0a4153d66 --- /dev/null +++ b/src/UI/Settings/MediaManagement/Naming/Partials/ReleaseYearNamingPartial.hbs @@ -0,0 +1 @@ +
  • Release Year
  • \ No newline at end of file diff --git a/src/UI/Settings/MediaManagement/Naming/Partials/TrackNumNamingPartial.hbs b/src/UI/Settings/MediaManagement/Naming/Partials/TrackNumNamingPartial.hbs new file mode 100644 index 000000000..68ddc8ff5 --- /dev/null +++ b/src/UI/Settings/MediaManagement/Naming/Partials/TrackNumNamingPartial.hbs @@ -0,0 +1,7 @@ + diff --git a/src/UI/Settings/MediaManagement/Naming/Partials/TrackTitleNamingPartial.hbs b/src/UI/Settings/MediaManagement/Naming/Partials/TrackTitleNamingPartial.hbs new file mode 100644 index 000000000..591c57098 --- /dev/null +++ b/src/UI/Settings/MediaManagement/Naming/Partials/TrackTitleNamingPartial.hbs @@ -0,0 +1,11 @@ + From b4279a455ebc15c1eedcf44775943eea00d74990 Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Mon, 19 Jun 2017 08:06:37 -0500 Subject: [PATCH 08/13] Fixed all issues from merging --- .../MetadataSource/SkyHook/SkyHookProxy.cs | 4 +-- src/NzbDrone.Core/Music/TrackRepository.cs | 28 +++++++++---------- src/NzbDrone.Core/Music/TrackService.cs | 18 ++++++------ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index bafc84ecc..03e5f2bdb 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -77,7 +77,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook public Tuple> GetArtistInfo(string foreignArtistId) { - _logger.Debug("Getting Artist with SpotifyId of {0}", foreignArtistId); + _logger.Debug("Getting Artist with LidarrAPI.MetadataID of {0}", foreignArtistId); // We need to perform a direct lookup of the artist var httpRequest = _requestBuilder.Create() @@ -296,7 +296,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook Artist artist = new Artist(); artist.Name = resource.ArtistName; - artist.ForeignArtistId = resource.Id; // TODO: Rename spotifyId to LidarrId + artist.ForeignArtistId = resource.Id; artist.Genres = resource.Genres; artist.Overview = resource.Overview; artist.NameSlug = Parser.Parser.CleanArtistTitle(artist.Name); diff --git a/src/NzbDrone.Core/Music/TrackRepository.cs b/src/NzbDrone.Core/Music/TrackRepository.cs index 948ce5936..4657b3dad 100644 --- a/src/NzbDrone.Core/Music/TrackRepository.cs +++ b/src/NzbDrone.Core/Music/TrackRepository.cs @@ -13,11 +13,11 @@ namespace NzbDrone.Core.Music { public interface ITrackRepository : IBasicRepository { - Track Find(int artistId, int albumId, int trackNumber); - List GetTracks(int artistId); - List GetTracks(int artistId, int albumId); + Track Find(string artistId, string albumId, int trackNumber); + List GetTracks(string artistId); + List GetTracks(string artistId, string albumId); List GetTracksByFileId(int fileId); - List TracksWithFiles(int artistId); + List TracksWithFiles(string artistId); PagingSpec TracksWithoutFiles(PagingSpec pagingSpec); PagingSpec TracksWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff); void SetMonitoredFlat(Track episode, bool monitored); @@ -37,24 +37,24 @@ namespace NzbDrone.Core.Music _logger = logger; } - public Track Find(int artistId, int albumId, int trackNumber) + public Track Find(string artistId, string albumId, int trackNumber) { - return Query.Where(s => s.ArtistId == artistId) - .AndWhere(s => s.AlbumId == albumId) + return Query.Where(s => s.ForeignTrackId == artistId) + .AndWhere(s => s.Album.ForeignAlbumId == albumId) .AndWhere(s => s.TrackNumber == trackNumber) .SingleOrDefault(); } - public List GetTracks(int artistId) + public List GetTracks(string artistId) { - return Query.Where(s => s.ArtistId == artistId).ToList(); + return Query.Where(s => s.ForeignTrackId == artistId).ToList(); } - public List GetTracks(int artistId, int albumId) + public List GetTracks(string artistId, string albumId) { - return Query.Where(s => s.ArtistId == artistId) - .AndWhere(s => s.AlbumId == albumId) + return Query.Where(s => s.ForeignTrackId == artistId) + .AndWhere(s => s.Album.ForeignAlbumId == albumId) .ToList(); } @@ -63,10 +63,10 @@ namespace NzbDrone.Core.Music return Query.Where(e => e.TrackFileId == fileId).ToList(); } - public List TracksWithFiles(int artistId) + public List TracksWithFiles(string artistId) { return Query.Join(JoinType.Inner, e => e.TrackFile, (e, ef) => e.TrackFileId == ef.Id) - .Where(e => e.ArtistId == artistId); + .Where(e => e.ForeignTrackId == artistId); } public PagingSpec TracksWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff) diff --git a/src/NzbDrone.Core/Music/TrackService.cs b/src/NzbDrone.Core/Music/TrackService.cs index bf19f76b3..6219b515e 100644 --- a/src/NzbDrone.Core/Music/TrackService.cs +++ b/src/NzbDrone.Core/Music/TrackService.cs @@ -15,12 +15,12 @@ namespace NzbDrone.Core.Music { Track GetTrack(int id); List GetTracks(IEnumerable ids); - Track FindTrack(int artistId, int albumId, int trackNumber); - Track FindTrackByTitle(int artistId, int albumId, string releaseTitle); - List GetTracksByArtist(int artistId); - List GetTracksByAlbum(int artistId, int albumId); + Track FindTrack(string artistId, string albumId, int trackNumber); + Track FindTrackByTitle(string artistId, string albumId, string releaseTitle); + List GetTracksByArtist(string artistId); + List GetTracksByAlbum(string artistId, string albumId); //List GetTracksByAlbumTitle(string artistId, string albumTitle); - List TracksWithFiles(int artistId); + List TracksWithFiles(string artistId); //PagingSpec TracksWithoutFiles(PagingSpec pagingSpec); List GetTracksByFileId(int trackFileId); void UpdateTrack(Track track); @@ -65,12 +65,12 @@ namespace NzbDrone.Core.Music return _trackRepository.GetTracks(artistId).ToList(); } - public List GetTracksByAlbum(int artistId, int albumId) + public List GetTracksByAlbum(string artistId, string albumId) { return _trackRepository.GetTracks(artistId, albumId); } - public Track FindTrackByTitle(int artistId, int albumId, string releaseTitle) + public Track FindTrackByTitle(string artistId, string albumId, string releaseTitle) { // TODO: can replace this search mechanism with something smarter/faster/better var normalizedReleaseTitle = Parser.Parser.NormalizeEpisodeTitle(releaseTitle).Replace(".", " "); @@ -96,7 +96,7 @@ namespace NzbDrone.Core.Music return null; } - public List TracksWithFiles(int artistId) + public List TracksWithFiles(string artistId) { return _trackRepository.TracksWithFiles(artistId); } @@ -154,7 +154,7 @@ namespace NzbDrone.Core.Music public void HandleAsync(ArtistDeletedEvent message) { - var tracks = GetTracksByArtist(message.Artist.Id); + var tracks = GetTracksByArtist(message.Artist.ForeignArtistId); _trackRepository.DeleteMany(tracks); } From fafe4e93f34fea769c9a64d9a98015821738b1e4 Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 19 Jun 2017 20:57:07 -0400 Subject: [PATCH 09/13] Clean up and back to build state Clean up and back to build state --- .../MediaFiles/DiskScanService.cs | 4 +-- .../MediaFiles/MediaFileRepository.cs | 8 ++--- .../MediaFiles/MediaFileService.cs | 10 +++--- .../MediaFileTableCleanupService.cs | 6 ++-- .../MediaInfo/UpdateMediaInfoService.cs | 2 +- src/NzbDrone.Core/MediaFiles/TrackFile.cs | 1 + .../TrackImport/ImportApprovedTracks.cs | 6 ++-- src/NzbDrone.Core/Music/ArtistService.cs | 2 +- src/NzbDrone.Core/Music/TrackRepository.cs | 32 +++++++++---------- src/NzbDrone.Core/Music/TrackService.cs | 26 +++++++-------- 10 files changed, 49 insertions(+), 48 deletions(-) diff --git a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs index 773b94519..db2f389f4 100644 --- a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs +++ b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs @@ -87,7 +87,7 @@ namespace NzbDrone.Core.MediaFiles return; } - _logger.ProgressInfo("Scanning disk for {0}", artist.ArtistName); + _logger.ProgressInfo("Scanning disk for {0}", artist.Name); if (!_diskProvider.FolderExists(artist.Path)) { @@ -136,7 +136,7 @@ namespace NzbDrone.Core.MediaFiles private void CompletedScanning(Artist artist) { - _logger.Info("Completed scanning disk for {0}", artist.ArtistName); + _logger.Info("Completed scanning disk for {0}", artist.Name); _eventAggregator.PublishEvent(new ArtistScannedEvent(artist)); } diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs index 9cd818dcb..4b93d8591 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; @@ -8,7 +8,7 @@ namespace NzbDrone.Core.MediaFiles { public interface IMediaFileRepository : IBasicRepository { - List GetFilesByArtist(string artistId); + List GetFilesByArtist(int artistId); List GetFilesWithoutMediaInfo(); } @@ -25,9 +25,9 @@ namespace NzbDrone.Core.MediaFiles return Query.Where(c => c.MediaInfo == null).ToList(); } - public List GetFilesByArtist(string artistId) + public List GetFilesByArtist(int artistId) { - return Query.Where(c => c.SpotifyTrackId == artistId).ToList(); + return Query.Where(c => c.ArtistId == artistId).ToList(); } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs index 5c61ce535..eb20ae076 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using NLog; @@ -18,7 +18,7 @@ namespace NzbDrone.Core.MediaFiles TrackFile Add(TrackFile trackFile); void Update(TrackFile trackFile); void Delete(TrackFile trackFile, DeleteMediaFileReason reason); - List GetFilesByArtist(string artistId); + List GetFilesByArtist(int artistId); List GetFilesWithoutMediaInfo(); List FilterExistingFiles(List files, Artist artist); TrackFile Get(int id); @@ -69,7 +69,7 @@ namespace NzbDrone.Core.MediaFiles public List FilterExistingFiles(List files, Artist artist) { - var artistFiles = GetFilesByArtist(artist.SpotifyId).Select(f => Path.Combine(artist.Path, f.RelativePath)).ToList(); + var artistFiles = GetFilesByArtist(artist.Id).Select(f => Path.Combine(artist.Path, f.RelativePath)).ToList(); if (!artistFiles.Any()) return files; @@ -89,11 +89,11 @@ namespace NzbDrone.Core.MediaFiles public void HandleAsync(ArtistDeletedEvent message) { - var files = GetFilesByArtist(message.Artist.SpotifyId); + var files = GetFilesByArtist(message.Artist.Id); _mediaFileRepository.DeleteMany(files); } - public List GetFilesByArtist(string artistId) + public List GetFilesByArtist(int artistId) { return _mediaFileRepository.GetFilesByArtist(artistId); } diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs index a46844f54..772d766a4 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using NLog; @@ -31,8 +31,8 @@ namespace NzbDrone.Core.MediaFiles public void Clean(Artist artist, List filesOnDisk) { - var artistFiles = _mediaFileService.GetFilesByArtist(artist.SpotifyId); - var tracks = _trackService.GetTracksByArtist(artist.SpotifyId); + var artistFiles = _mediaFileService.GetFilesByArtist(artist.Id); + var tracks = _trackService.GetTracksByArtist(artist.Id); var filesOnDiskKeys = new HashSet(filesOnDisk, PathEqualityComparer.Instance); diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs index 60cf5bf01..593cdcee6 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs @@ -65,7 +65,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo return; } - var allMediaFiles = _mediaFileService.GetFilesByArtist(message.Artist.SpotifyId); + var allMediaFiles = _mediaFileService.GetFilesByArtist(message.Artist.Id); var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < CURRENT_MEDIA_INFO_SCHEMA_REVISION).ToList(); UpdateMediaInfo(message.Artist, filteredMediaFiles); diff --git a/src/NzbDrone.Core/MediaFiles/TrackFile.cs b/src/NzbDrone.Core/MediaFiles/TrackFile.cs index e08555b77..e20b3b5dc 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackFile.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackFile.cs @@ -14,6 +14,7 @@ namespace NzbDrone.Core.MediaFiles { public string SpotifyTrackId { get; set; } public int AlbumId { get; set; } + public int ArtistId { get; set; } public string RelativePath { get; set; } public string Path { get; set; } public long Size { get; set; } diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs index 09abac393..e38c27ca8 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs @@ -67,18 +67,18 @@ namespace NzbDrone.Core.MediaFiles.TrackImport .Intersect(localTrack.Tracks.Select(e => e.Id)) .Any()) { - importResults.Add(new ImportResult(importDecision, "Episode has already been imported")); + importResults.Add(new ImportResult(importDecision, "Track has already been imported")); continue; } var trackFile = new TrackFile(); trackFile.DateAdded = DateTime.UtcNow; - trackFile.SpotifyTrackId = localTrack.Artist.SpotifyId; + 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.ElementAt(0); // TODO: Implement ImportApprovedTracks Album Id + trackFile.AlbumId = localTrack.Album.Id; trackFile.Tracks = localTrack.Tracks; trackFile.ReleaseGroup = localTrack.ParsedTrackInfo.ReleaseGroup; diff --git a/src/NzbDrone.Core/Music/ArtistService.cs b/src/NzbDrone.Core/Music/ArtistService.cs index 211f0c978..1297a42e9 100644 --- a/src/NzbDrone.Core/Music/ArtistService.cs +++ b/src/NzbDrone.Core/Music/ArtistService.cs @@ -115,7 +115,7 @@ namespace NzbDrone.Core.Music if (storedAlbum != null && album.Monitored != storedAlbum.Monitored) { - _trackService.SetTrackMonitoredByAlbum(artist.ForeignArtistId, album.ForeignAlbumId, album.Monitored); + _trackService.SetTrackMonitoredByAlbum(artist.Id, album.Id, album.Monitored); } } diff --git a/src/NzbDrone.Core/Music/TrackRepository.cs b/src/NzbDrone.Core/Music/TrackRepository.cs index 4657b3dad..b260056fe 100644 --- a/src/NzbDrone.Core/Music/TrackRepository.cs +++ b/src/NzbDrone.Core/Music/TrackRepository.cs @@ -13,15 +13,15 @@ namespace NzbDrone.Core.Music { public interface ITrackRepository : IBasicRepository { - Track Find(string artistId, string albumId, int trackNumber); - List GetTracks(string artistId); - List GetTracks(string artistId, string albumId); + Track Find(int artistId, int albumId, int trackNumber); + List GetTracks(int artistId); + List GetTracks(int artistId, int albumId); List GetTracksByFileId(int fileId); - List TracksWithFiles(string artistId); + List TracksWithFiles(int artistId); PagingSpec TracksWithoutFiles(PagingSpec pagingSpec); PagingSpec TracksWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff); void SetMonitoredFlat(Track episode, bool monitored); - void SetMonitoredByAlbum(string artistId, string albumId, bool monitored); + void SetMonitoredByAlbum(int artistId, int albumId, bool monitored); void SetFileId(int trackId, int fileId); } @@ -37,24 +37,24 @@ namespace NzbDrone.Core.Music _logger = logger; } - public Track Find(string artistId, string albumId, int trackNumber) + public Track Find(int artistId, int albumId, int trackNumber) { - return Query.Where(s => s.ForeignTrackId == artistId) - .AndWhere(s => s.Album.ForeignAlbumId == albumId) + return Query.Where(s => s.ArtistId == artistId) + .AndWhere(s => s.AlbumId == albumId) .AndWhere(s => s.TrackNumber == trackNumber) .SingleOrDefault(); } - public List GetTracks(string artistId) + public List GetTracks(int artistId) { - return Query.Where(s => s.ForeignTrackId == artistId).ToList(); + return Query.Where(s => s.ArtistId == artistId).ToList(); } - public List GetTracks(string artistId, string albumId) + public List GetTracks(int artistId, int albumId) { - return Query.Where(s => s.ForeignTrackId == artistId) - .AndWhere(s => s.Album.ForeignAlbumId == albumId) + return Query.Where(s => s.ArtistId == artistId) + .AndWhere(s => s.AlbumId == albumId) .ToList(); } @@ -63,10 +63,10 @@ namespace NzbDrone.Core.Music return Query.Where(e => e.TrackFileId == fileId).ToList(); } - public List TracksWithFiles(string artistId) + public List TracksWithFiles(int artistId) { return Query.Join(JoinType.Inner, e => e.TrackFile, (e, ef) => e.TrackFileId == ef.Id) - .Where(e => e.ForeignTrackId == artistId); + .Where(e => e.ArtistId == artistId); } public PagingSpec TracksWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff) @@ -85,7 +85,7 @@ namespace NzbDrone.Core.Music SetFields(track, p => p.Monitored); } - public void SetMonitoredByAlbum(string artistId, string albumId, bool monitored) + public void SetMonitoredByAlbum(int artistId, int albumId, bool monitored) { var mapper = _database.GetDataMapper(); diff --git a/src/NzbDrone.Core/Music/TrackService.cs b/src/NzbDrone.Core/Music/TrackService.cs index 6219b515e..39c065b0e 100644 --- a/src/NzbDrone.Core/Music/TrackService.cs +++ b/src/NzbDrone.Core/Music/TrackService.cs @@ -15,12 +15,12 @@ namespace NzbDrone.Core.Music { Track GetTrack(int id); List GetTracks(IEnumerable ids); - Track FindTrack(string artistId, string albumId, int trackNumber); - Track FindTrackByTitle(string artistId, string albumId, string releaseTitle); - List GetTracksByArtist(string artistId); - List GetTracksByAlbum(string artistId, string albumId); + Track FindTrack(int artistId, int albumId, int trackNumber); + Track FindTrackByTitle(int artistId, int albumId, string releaseTitle); + List GetTracksByArtist(int artistId); + List GetTracksByAlbum(int artistId, int albumId); //List GetTracksByAlbumTitle(string artistId, string albumTitle); - List TracksWithFiles(string artistId); + List TracksWithFiles(int artistId); //PagingSpec TracksWithoutFiles(PagingSpec pagingSpec); List GetTracksByFileId(int trackFileId); void UpdateTrack(Track track); @@ -29,7 +29,7 @@ namespace NzbDrone.Core.Music void InsertMany(List tracks); void UpdateMany(List tracks); void DeleteMany(List tracks); - void SetTrackMonitoredByAlbum(string artistId, string albumId, bool monitored); + void SetTrackMonitoredByAlbum(int artistId, int albumId, bool monitored); } public class TrackService : ITrackService @@ -55,22 +55,22 @@ namespace NzbDrone.Core.Music return _trackRepository.Get(ids).ToList(); } - public Track FindTrack(string artistId, string albumId, int trackNumber) + public Track FindTrack(int artistId, int albumId, int trackNumber) { return _trackRepository.Find(artistId, albumId, trackNumber); } - public List GetTracksByArtist(string artistId) + public List GetTracksByArtist(int artistId) { return _trackRepository.GetTracks(artistId).ToList(); } - public List GetTracksByAlbum(string artistId, string albumId) + public List GetTracksByAlbum(int artistId, int albumId) { return _trackRepository.GetTracks(artistId, albumId); } - public Track FindTrackByTitle(string artistId, string albumId, string releaseTitle) + public Track FindTrackByTitle(int artistId, int albumId, string releaseTitle) { // TODO: can replace this search mechanism with something smarter/faster/better var normalizedReleaseTitle = Parser.Parser.NormalizeEpisodeTitle(releaseTitle).Replace(".", " "); @@ -96,7 +96,7 @@ namespace NzbDrone.Core.Music return null; } - public List TracksWithFiles(string artistId) + public List TracksWithFiles(int artistId) { return _trackRepository.TracksWithFiles(artistId); } @@ -127,7 +127,7 @@ namespace NzbDrone.Core.Music _logger.Debug("Monitored flag for Track:{0} was set to {1}", trackId, monitored); } - public void SetTrackMonitoredByAlbum(string artistId, string albumId, bool monitored) + public void SetTrackMonitoredByAlbum(int artistId, int albumId, bool monitored) { _trackRepository.SetMonitoredByAlbum(artistId, albumId, monitored); } @@ -154,7 +154,7 @@ namespace NzbDrone.Core.Music public void HandleAsync(ArtistDeletedEvent message) { - var tracks = GetTracksByArtist(message.Artist.ForeignArtistId); + var tracks = GetTracksByArtist(message.Artist.Id); _trackRepository.DeleteMany(tracks); } From e96cf084093abc0487265ed91a4cbfa8aaf2f42e Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Tue, 20 Jun 2017 08:03:13 -0500 Subject: [PATCH 10/13] Back to a stable build. Fixedtrackrepo.findtracks --- src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs | 2 +- src/NzbDrone.Core/MediaFiles/DiskScanService.cs | 4 ++-- src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs | 2 +- src/NzbDrone.Core/MediaFiles/MediaFileService.cs | 4 ++-- src/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs | 4 ++-- .../MediaFiles/MediaInfo/UpdateMediaInfoService.cs | 2 +- src/NzbDrone.Core/MediaFiles/TrackFile.cs | 3 ++- .../MediaFiles/TrackImport/ImportApprovedTracks.cs | 2 +- src/NzbDrone.Core/Music/RefreshTrackService.cs | 2 +- src/NzbDrone.Core/Music/TrackRepository.cs | 2 +- 10 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs b/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs index ca8385242..698d3b4a4 100644 --- a/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs +++ b/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs @@ -20,7 +20,7 @@ namespace NzbDrone.Common.Cloud //Search = new HttpRequestBuilder("https://api.spotify.com/{version}/{route}/") // TODO: maybe use {version} // .SetSegment("version", "v1") // .CreateFactory(); - Search = new HttpRequestBuilder("http://localhost:5000/{route}/") // TODO: maybe use {version} + Search = new HttpRequestBuilder("http://localhost:3000/{route}/") // TODO: maybe use {version} .CreateFactory(); InternalSearch = new HttpRequestBuilder("https://itunes.apple.com/WebObjects/MZStore.woa/wa/{route}") //viewArtist or search diff --git a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs index 773b94519..db2f389f4 100644 --- a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs +++ b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs @@ -87,7 +87,7 @@ namespace NzbDrone.Core.MediaFiles return; } - _logger.ProgressInfo("Scanning disk for {0}", artist.ArtistName); + _logger.ProgressInfo("Scanning disk for {0}", artist.Name); if (!_diskProvider.FolderExists(artist.Path)) { @@ -136,7 +136,7 @@ namespace NzbDrone.Core.MediaFiles private void CompletedScanning(Artist artist) { - _logger.Info("Completed scanning disk for {0}", artist.ArtistName); + _logger.Info("Completed scanning disk for {0}", artist.Name); _eventAggregator.PublishEvent(new ArtistScannedEvent(artist)); } diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs index 9cd818dcb..4337b0073 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs @@ -27,7 +27,7 @@ namespace NzbDrone.Core.MediaFiles public List GetFilesByArtist(string artistId) { - return Query.Where(c => c.SpotifyTrackId == artistId).ToList(); + return Query.Where(c => c.ForeignTrackId == artistId).ToList(); } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs index 5c61ce535..a03d3b0a4 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs @@ -69,7 +69,7 @@ namespace NzbDrone.Core.MediaFiles public List FilterExistingFiles(List files, Artist artist) { - var artistFiles = GetFilesByArtist(artist.SpotifyId).Select(f => Path.Combine(artist.Path, f.RelativePath)).ToList(); + var artistFiles = GetFilesByArtist(artist.ForeignArtistId).Select(f => Path.Combine(artist.Path, f.RelativePath)).ToList(); if (!artistFiles.Any()) return files; @@ -89,7 +89,7 @@ namespace NzbDrone.Core.MediaFiles public void HandleAsync(ArtistDeletedEvent message) { - var files = GetFilesByArtist(message.Artist.SpotifyId); + var files = GetFilesByArtist(message.Artist.ForeignArtistId); _mediaFileRepository.DeleteMany(files); } diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs index a46844f54..69b3b9f0d 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileTableCleanupService.cs @@ -31,8 +31,8 @@ namespace NzbDrone.Core.MediaFiles public void Clean(Artist artist, List filesOnDisk) { - var artistFiles = _mediaFileService.GetFilesByArtist(artist.SpotifyId); - var tracks = _trackService.GetTracksByArtist(artist.SpotifyId); + var artistFiles = _mediaFileService.GetFilesByArtist(artist.ForeignArtistId); + var tracks = _trackService.GetTracksByArtist(artist.ForeignArtistId); var filesOnDiskKeys = new HashSet(filesOnDisk, PathEqualityComparer.Instance); diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs index 60cf5bf01..a45ec726b 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs @@ -65,7 +65,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo return; } - var allMediaFiles = _mediaFileService.GetFilesByArtist(message.Artist.SpotifyId); + var allMediaFiles = _mediaFileService.GetFilesByArtist(message.Artist.ForeignArtistId); var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < CURRENT_MEDIA_INFO_SCHEMA_REVISION).ToList(); UpdateMediaInfo(message.Artist, filteredMediaFiles); diff --git a/src/NzbDrone.Core/MediaFiles/TrackFile.cs b/src/NzbDrone.Core/MediaFiles/TrackFile.cs index e08555b77..ecc51c7be 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackFile.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackFile.cs @@ -12,7 +12,8 @@ namespace NzbDrone.Core.MediaFiles { public class TrackFile : ModelBase { - public string SpotifyTrackId { get; set; } + public string ForeignTrackId { get; set; } + public string ForeignArtistId { get; set; } public int AlbumId { get; set; } public string RelativePath { get; set; } public string Path { get; set; } diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs index 09abac393..a6ff0b9be 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs @@ -73,7 +73,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport var trackFile = new TrackFile(); trackFile.DateAdded = DateTime.UtcNow; - trackFile.SpotifyTrackId = localTrack.Artist.SpotifyId; + trackFile.ForeignArtistId = localTrack.Artist.ForeignArtistId; trackFile.Path = localTrack.Path.CleanFilePath(); trackFile.Size = _diskProvider.GetFileSize(localTrack.Path); trackFile.Quality = localTrack.Quality; diff --git a/src/NzbDrone.Core/Music/RefreshTrackService.cs b/src/NzbDrone.Core/Music/RefreshTrackService.cs index 77592e4f3..58754cf44 100644 --- a/src/NzbDrone.Core/Music/RefreshTrackService.cs +++ b/src/NzbDrone.Core/Music/RefreshTrackService.cs @@ -33,7 +33,7 @@ namespace NzbDrone.Core.Music var successCount = 0; var failCount = 0; - var existingTracks = _trackService.GetTracksByAlbum(album.ArtistId, album.Id); + var existingTracks = _trackService.GetTracksByAlbum(album.ForeignAlbumId, album.ForeignAlbumId); //var albums = artist.Albums; var updateList = new List(); diff --git a/src/NzbDrone.Core/Music/TrackRepository.cs b/src/NzbDrone.Core/Music/TrackRepository.cs index 4657b3dad..0572ec800 100644 --- a/src/NzbDrone.Core/Music/TrackRepository.cs +++ b/src/NzbDrone.Core/Music/TrackRepository.cs @@ -48,7 +48,7 @@ namespace NzbDrone.Core.Music public List GetTracks(string artistId) { - return Query.Where(s => s.ForeignTrackId == artistId).ToList(); + return Query.Join(JoinType.Inner, s => s.Artist, (track, artist) => track.ArtistId == artist.Id).ToList(); } public List GetTracks(string artistId, string albumId) From 916db8d35614a484f3c42886306c387f26f1201e Mon Sep 17 00:00:00 2001 From: Qstick Date: Tue, 20 Jun 2017 23:06:28 -0400 Subject: [PATCH 11/13] Add Track and TrackFile API Resources Add Track and TrackFile API Resources, Add Rename Track Resource, Add GetFilesByAlbum function to Media File Service, Add Links to Artist Detail Page, Misc other UI work --- src/NzbDrone.Api/Music/ArtistResource.cs | 16 +- src/NzbDrone.Api/NzbDrone.Api.csproj | 7 + .../TrackFiles/TrackFileModule.cs | 101 ++++++++++ .../TrackFiles/TrackFileResource.cs | 64 ++++++ src/NzbDrone.Api/Tracks/RenameTrackModule.cs | 37 ++++ .../Tracks/RenameTrackResource.cs | 39 ++++ src/NzbDrone.Api/Tracks/TrackModule.cs | 40 ++++ .../Tracks/TrackModuleWithSignalR.cs | 126 ++++++++++++ src/NzbDrone.Api/Tracks/TrackResource.cs | 78 ++++++++ .../Commands/RenameArtistCommand.cs | 16 ++ .../MediaFiles/MediaFileService.cs | 6 + .../MediaFiles/RenameTrackFilePreview.cs | 14 ++ .../MediaFiles/RenameTrackFileService.cs | 185 ++++++++++++++++++ src/NzbDrone.Core/MediaFiles/TrackFile.cs | 1 - src/NzbDrone.Core/Music/Artist.cs | 8 + src/NzbDrone.Core/Music/Track.cs | 2 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 3 + .../Organizer/FileNameBuilder.cs | 29 +++ src/UI/AddArtist/SearchResultViewTemplate.hbs | 4 +- src/UI/Artist/Details/InfoViewTemplate.hbs | 16 +- src/UI/Artist/Index/ArtistIndexLayout.js | 15 +- src/UI/Artist/Index/FooterViewTemplate.hbs | 7 +- src/UI/Artist/Index/TrackProgressPartial.hbs | 6 +- src/UI/Artist/TrackCollection.js | 30 +-- src/UI/Artist/TrackFileCollection.js | 4 +- src/UI/Artist/TrackModel.js | 4 +- src/UI/Controller.js | 5 - src/UI/Handlebars/Helpers/Artist.js | 35 ++-- src/UI/Router.js | 1 - 29 files changed, 834 insertions(+), 65 deletions(-) create mode 100644 src/NzbDrone.Api/TrackFiles/TrackFileModule.cs create mode 100644 src/NzbDrone.Api/TrackFiles/TrackFileResource.cs create mode 100644 src/NzbDrone.Api/Tracks/RenameTrackModule.cs create mode 100644 src/NzbDrone.Api/Tracks/RenameTrackResource.cs create mode 100644 src/NzbDrone.Api/Tracks/TrackModule.cs create mode 100644 src/NzbDrone.Api/Tracks/TrackModuleWithSignalR.cs create mode 100644 src/NzbDrone.Api/Tracks/TrackResource.cs create mode 100644 src/NzbDrone.Core/MediaFiles/Commands/RenameArtistCommand.cs create mode 100644 src/NzbDrone.Core/MediaFiles/RenameTrackFilePreview.cs create mode 100644 src/NzbDrone.Core/MediaFiles/RenameTrackFileService.cs diff --git a/src/NzbDrone.Api/Music/ArtistResource.cs b/src/NzbDrone.Api/Music/ArtistResource.cs index d75fbebdb..4405c0d38 100644 --- a/src/NzbDrone.Api/Music/ArtistResource.cs +++ b/src/NzbDrone.Api/Music/ArtistResource.cs @@ -20,6 +20,10 @@ namespace NzbDrone.Api.Music //View Only public string Name { get; set; } public string ForeignArtistId { get; set; } + public string MBId { get; set; } + public int TADBId { get; set; } + public int DiscogsId { get; set; } + public string AMId { get; set; } public string Overview { get; set; } public int AlbumCount @@ -53,7 +57,7 @@ namespace NzbDrone.Api.Music public bool Monitored { get; set; } public string RootFolderPath { get; set; } - public string Certification { get; set; } + //public string Certification { get; set; } public List Genres { get; set; } public HashSet Tags { get; set; } public DateTime Added { get; set; } @@ -71,7 +75,10 @@ namespace NzbDrone.Api.Music return new ArtistResource { Id = model.Id, - + MBId = model.MBId, + TADBId = model.TADBId, + DiscogsId = model.DiscogsId, + AMId = model.AMId, Name = model.Name, //AlternateTitles //SortTitle = resource.SortTitle, @@ -127,7 +134,10 @@ namespace NzbDrone.Api.Music Name = resource.Name, //AlternateTitles //SortTitle = resource.SortTitle, - + MBId = resource.MBId, + TADBId = resource.TADBId, + DiscogsId = resource.DiscogsId, + AMId = resource.AMId, //TotalEpisodeCount //EpisodeCount //EpisodeFileCount diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 80572e068..877563c2a 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -104,6 +104,13 @@ + + + + + + + diff --git a/src/NzbDrone.Api/TrackFiles/TrackFileModule.cs b/src/NzbDrone.Api/TrackFiles/TrackFileModule.cs new file mode 100644 index 000000000..1f679ac5d --- /dev/null +++ b/src/NzbDrone.Api/TrackFiles/TrackFileModule.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.IO; +using NLog; +using NzbDrone.Api.REST; +using NzbDrone.Common.Disk; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Datastore.Events; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.SignalR; +using System; + +namespace NzbDrone.Api.TrackFiles +{ + public class TrackFileModule : NzbDroneRestModuleWithSignalR, + IHandle + { + private readonly IMediaFileService _mediaFileService; + private readonly IDiskProvider _diskProvider; + private readonly IRecycleBinProvider _recycleBinProvider; + private readonly ISeriesService _seriesService; + private readonly IArtistService _artistService; + private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification; + private readonly Logger _logger; + + public TrackFileModule(IBroadcastSignalRMessage signalRBroadcaster, + IMediaFileService mediaFileService, + IDiskProvider diskProvider, + IRecycleBinProvider recycleBinProvider, + ISeriesService seriesService, + IArtistService artistService, + IQualityUpgradableSpecification qualityUpgradableSpecification, + Logger logger) + : base(signalRBroadcaster) + { + _mediaFileService = mediaFileService; + _diskProvider = diskProvider; + _recycleBinProvider = recycleBinProvider; + _seriesService = seriesService; + _artistService = artistService; + _qualityUpgradableSpecification = qualityUpgradableSpecification; + _logger = logger; + GetResourceById = GetTrackFile; + GetResourceAll = GetTrackFiles; + UpdateResource = SetQuality; + DeleteResource = DeleteTrackFile; + } + + private TrackFileResource GetTrackFile(int id) + { + throw new NotImplementedException(); + //var episodeFile = _mediaFileService.Get(id); + //var series = _seriesService.GetSeries(episodeFile.SeriesId); + + //return episodeFile.ToResource(series, _qualityUpgradableSpecification); + } + + private List GetTrackFiles() + { + if (!Request.Query.ArtistId.HasValue) + { + throw new BadRequestException("artistId is missing"); + } + + var artistId = (int)Request.Query.ArtistId; + + var artist = _artistService.GetArtist(artistId); + + return _mediaFileService.GetFilesByArtist(artistId).ConvertAll(f => f.ToResource(artist, _qualityUpgradableSpecification)); + } + + private void SetQuality(TrackFileResource trackFileResource) + { + var trackFile = _mediaFileService.Get(trackFileResource.Id); + trackFile.Quality = trackFileResource.Quality; + _mediaFileService.Update(trackFile); + } + + private void DeleteTrackFile(int id) + { + throw new NotImplementedException(); + //var episodeFile = _mediaFileService.Get(id); + //var series = _seriesService.GetSeries(episodeFile.SeriesId); + //var fullPath = Path.Combine(series.Path, episodeFile.RelativePath); + //var subfolder = _diskProvider.GetParentFolder(series.Path).GetRelativePath(_diskProvider.GetParentFolder(fullPath)); + + //_logger.Info("Deleting episode file: {0}", fullPath); + //_recycleBinProvider.DeleteFile(fullPath, subfolder); + //_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.Manual); + } + + public void Handle(TrackFileAddedEvent message) + { + BroadcastResourceChange(ModelAction.Updated, message.TrackFile.Id); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/TrackFiles/TrackFileResource.cs b/src/NzbDrone.Api/TrackFiles/TrackFileResource.cs new file mode 100644 index 000000000..4c75dc429 --- /dev/null +++ b/src/NzbDrone.Api/TrackFiles/TrackFileResource.cs @@ -0,0 +1,64 @@ +using System; +using System.IO; +using NzbDrone.Api.REST; +using NzbDrone.Core.Qualities; + +namespace NzbDrone.Api.TrackFiles +{ + public class TrackFileResource : RestResource + { + public int ArtistId { get; set; } + public int AlbumId { get; set; } + public string RelativePath { get; set; } + public string Path { get; set; } + public long Size { get; set; } + public DateTime DateAdded { get; set; } + //public string SceneName { get; set; } + public QualityModel Quality { get; set; } + + public bool QualityCutoffNotMet { get; set; } + } + + public static class TrackFileResourceMapper + { + private static TrackFileResource ToResource(this Core.MediaFiles.TrackFile model) + { + if (model == null) return null; + + return new TrackFileResource + { + Id = model.Id, + + ArtistId = model.ArtistId, + AlbumId = model.AlbumId, + RelativePath = model.RelativePath, + //Path + Size = model.Size, + DateAdded = model.DateAdded, + //SceneName = model.SceneName, + Quality = model.Quality, + //QualityCutoffNotMet + }; + } + + public static TrackFileResource ToResource(this Core.MediaFiles.TrackFile model, Core.Music.Artist artist, Core.DecisionEngine.IQualityUpgradableSpecification qualityUpgradableSpecification) + { + if (model == null) return null; + + return new TrackFileResource + { + Id = model.Id, + + ArtistId = model.ArtistId, + AlbumId = model.AlbumId, + RelativePath = model.RelativePath, + Path = Path.Combine(artist.Path, model.RelativePath), + Size = model.Size, + DateAdded = model.DateAdded, + //SceneName = model.SceneName, + Quality = model.Quality, + QualityCutoffNotMet = qualityUpgradableSpecification.CutoffNotMet(artist.Profile.Value, model.Quality) + }; + } + } +} diff --git a/src/NzbDrone.Api/Tracks/RenameTrackModule.cs b/src/NzbDrone.Api/Tracks/RenameTrackModule.cs new file mode 100644 index 000000000..9467ec43e --- /dev/null +++ b/src/NzbDrone.Api/Tracks/RenameTrackModule.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using NzbDrone.Api.REST; +using NzbDrone.Core.MediaFiles; + +namespace NzbDrone.Api.Tracks +{ + public class RenameTrackModule : NzbDroneRestModule + { + private readonly IRenameTrackFileService _renameTrackFileService; + + public RenameTrackModule(IRenameTrackFileService renameTrackFileService) + : base("rename") + { + _renameTrackFileService = renameTrackFileService; + + GetResourceAll = GetTracks; + } + + private List GetTracks() + { + if (!Request.Query.ArtistId.HasValue) + { + throw new BadRequestException("artistId is missing"); + } + + var artistId = (int)Request.Query.ArtistId; + + if (Request.Query.AlbumId.HasValue) + { + var albumId = (int)Request.Query.AlbumId; + return _renameTrackFileService.GetRenamePreviews(artistId, albumId).ToResource(); + } + + return _renameTrackFileService.GetRenamePreviews(artistId).ToResource(); + } + } +} diff --git a/src/NzbDrone.Api/Tracks/RenameTrackResource.cs b/src/NzbDrone.Api/Tracks/RenameTrackResource.cs new file mode 100644 index 000000000..12f67bb60 --- /dev/null +++ b/src/NzbDrone.Api/Tracks/RenameTrackResource.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Api.REST; + +namespace NzbDrone.Api.Tracks +{ + public class RenameTrackResource : RestResource + { + public int ArtistId { get; set; } + public int AlbumId { get; set; } + public List TrackNumbers { get; set; } + public int TrackFileId { get; set; } + public string ExistingPath { get; set; } + public string NewPath { get; set; } + } + + public static class RenameTrackResourceMapper + { + public static RenameTrackResource ToResource(this Core.MediaFiles.RenameTrackFilePreview model) + { + if (model == null) return null; + + return new RenameTrackResource + { + ArtistId = model.ArtistId, + AlbumId = model.AlbumId, + TrackNumbers = model.TrackNumbers.ToList(), + TrackFileId = model.TrackFileId, + ExistingPath = model.ExistingPath, + NewPath = model.NewPath + }; + } + + public static List ToResource(this IEnumerable models) + { + return models.Select(ToResource).ToList(); + } + } +} diff --git a/src/NzbDrone.Api/Tracks/TrackModule.cs b/src/NzbDrone.Api/Tracks/TrackModule.cs new file mode 100644 index 000000000..fa3222c51 --- /dev/null +++ b/src/NzbDrone.Api/Tracks/TrackModule.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using NzbDrone.Api.REST; +using NzbDrone.Core.Music; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.SignalR; + +namespace NzbDrone.Api.Tracks +{ + public class TrackModule : TrackModuleWithSignalR + { + public TrackModule(IArtistService artistService, + ITrackService trackService, + IQualityUpgradableSpecification qualityUpgradableSpecification, + IBroadcastSignalRMessage signalRBroadcaster) + : base(trackService, artistService, qualityUpgradableSpecification, signalRBroadcaster) + { + GetResourceAll = GetTracks; + UpdateResource = SetMonitored; + } + + private List GetTracks() + { + if (!Request.Query.ArtistId.HasValue) + { + throw new BadRequestException("artistId is missing"); + } + + var artistId = (int)Request.Query.ArtistId; + + var resources = MapToResource(_trackService.GetTracksByArtist(artistId), false, true); + + return resources; + } + + private void SetMonitored(TrackResource trackResource) + { + _trackService.SetTrackMonitored(trackResource.Id, trackResource.Monitored); + } + } +} diff --git a/src/NzbDrone.Api/Tracks/TrackModuleWithSignalR.cs b/src/NzbDrone.Api/Tracks/TrackModuleWithSignalR.cs new file mode 100644 index 000000000..f5926768e --- /dev/null +++ b/src/NzbDrone.Api/Tracks/TrackModuleWithSignalR.cs @@ -0,0 +1,126 @@ +using System.Collections.Generic; +using NzbDrone.Common.Extensions; +using NzbDrone.Api.TrackFiles; +using NzbDrone.Api.Music; +using NzbDrone.Core.Datastore.Events; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Download; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; +using NzbDrone.SignalR; + +namespace NzbDrone.Api.Tracks +{ + public abstract class TrackModuleWithSignalR : NzbDroneRestModuleWithSignalR, + IHandle + { + protected readonly ITrackService _trackService; + protected readonly IArtistService _artistService; + protected readonly IQualityUpgradableSpecification _qualityUpgradableSpecification; + + protected TrackModuleWithSignalR(ITrackService trackService, + IArtistService artistService, + IQualityUpgradableSpecification qualityUpgradableSpecification, + IBroadcastSignalRMessage signalRBroadcaster) + : base(signalRBroadcaster) + { + _trackService = trackService; + _artistService = artistService; + _qualityUpgradableSpecification = qualityUpgradableSpecification; + + GetResourceById = GetTrack; + } + + protected TrackModuleWithSignalR(ITrackService trackService, + IArtistService artistService, + IQualityUpgradableSpecification qualityUpgradableSpecification, + IBroadcastSignalRMessage signalRBroadcaster, + string resource) + : base(signalRBroadcaster, resource) + { + _trackService = trackService; + _artistService = artistService; + _qualityUpgradableSpecification = qualityUpgradableSpecification; + + GetResourceById = GetTrack; + } + + protected TrackResource GetTrack(int id) + { + var track = _trackService.GetTrack(id); + var resource = MapToResource(track, true, true); + return resource; + } + + protected TrackResource MapToResource(Track track, bool includeArtist, bool includeTrackFile) + { + var resource = track.ToResource(); + + if (includeArtist || includeTrackFile) + { + var artist = track.Artist ?? _artistService.GetArtist(track.ArtistId); + + if (includeArtist) + { + resource.Artist = artist.ToResource(); + } + if (includeTrackFile && track.TrackFileId != 0) + { + resource.TrackFile = track.TrackFile.Value.ToResource(artist, _qualityUpgradableSpecification); + } + } + + return resource; + } + + protected List MapToResource(List tracks, bool includeArtist, bool includeTrackFile) + { + var result = tracks.ToResource(); + + if (includeArtist || includeTrackFile) + { + var artistDict = new Dictionary(); + for (var i = 0; i < tracks.Count; i++) + { + var track = tracks[i]; + var resource = result[i]; + + var artist = track.Artist ?? artistDict.GetValueOrDefault(tracks[i].ArtistId) ?? _artistService.GetArtist(tracks[i].ArtistId); + artistDict[artist.Id] = artist; + + if (includeArtist) + { + resource.Artist = artist.ToResource(); + } + if (includeTrackFile && tracks[i].TrackFileId != 0) + { + resource.TrackFile = tracks[i].TrackFile.Value.ToResource(artist, _qualityUpgradableSpecification); + } + } + } + + return result; + } + + //public void Handle(TrackGrabbedEvent message) + //{ + // foreach (var track in message.Track.Tracks) + // { + // var resource = track.ToResource(); + // resource.Grabbed = true; + + // BroadcastResourceChange(ModelAction.Updated, resource); + // } + //} + + public void Handle(TrackDownloadedEvent message) + { + foreach (var track in message.Track.Tracks) + { + BroadcastResourceChange(ModelAction.Updated, track.Id); + } + } + } +} diff --git a/src/NzbDrone.Api/Tracks/TrackResource.cs b/src/NzbDrone.Api/Tracks/TrackResource.cs new file mode 100644 index 000000000..f12a9948c --- /dev/null +++ b/src/NzbDrone.Api/Tracks/TrackResource.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using NzbDrone.Api.TrackFiles; +using NzbDrone.Api.REST; +using NzbDrone.Api.Music; +using NzbDrone.Core.Music; + +namespace NzbDrone.Api.Tracks +{ + public class TrackResource : RestResource + { + public int ArtistId { get; set; } + public int TrackFileId { get; set; } + public int AlbumId { get; set; } + //public int EpisodeNumber { get; set; } + public string Title { get; set; } + //public string AirDate { get; set; } + //public DateTime? AirDateUtc { get; set; } + //public string Overview { get; set; } + public TrackFileResource TrackFile { get; set; } + + public bool HasFile { get; set; } + public bool Monitored { get; set; } + //public int? AbsoluteEpisodeNumber { get; set; } + //public int? SceneAbsoluteEpisodeNumber { get; set; } + //public int? SceneEpisodeNumber { get; set; } + //public int? SceneSeasonNumber { get; set; } + //public bool UnverifiedSceneNumbering { get; set; } + //public string SeriesTitle { get; set; } + public ArtistResource Artist { get; set; } + + //Hiding this so people don't think its usable (only used to set the initial state) + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool Grabbed { get; set; } + } + + public static class TrackResourceMapper + { + public static TrackResource ToResource(this Track model) + { + if (model == null) return null; + + return new TrackResource + { + Id = model.Id, + + ArtistId = model.ArtistId, + TrackFileId = model.TrackFileId, + AlbumId = model.AlbumId, + //EpisodeNumber = model.EpisodeNumber, + Title = model.Title, + //AirDate = model.AirDate, + //AirDateUtc = model.AirDateUtc, + //Overview = model.Overview, + //EpisodeFile + + HasFile = model.HasFile, + Monitored = model.Monitored, + //AbsoluteEpisodeNumber = model.AbsoluteEpisodeNumber, + //SceneAbsoluteEpisodeNumber = model.SceneAbsoluteEpisodeNumber, + //SceneEpisodeNumber = model.SceneEpisodeNumber, + //SceneSeasonNumber = model.SceneSeasonNumber, + //UnverifiedSceneNumbering = model.UnverifiedSceneNumbering, + //SeriesTitle = model.SeriesTitle, + //Series = model.Series.MapToResource(), + }; + } + + public static List ToResource(this IEnumerable models) + { + if (models == null) return null; + + return models.Select(ToResource).ToList(); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/Commands/RenameArtistCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/RenameArtistCommand.cs new file mode 100644 index 000000000..af86d061b --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/Commands/RenameArtistCommand.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using NzbDrone.Core.Messaging.Commands; + +namespace NzbDrone.Core.MediaFiles.Commands +{ + public class RenameArtistCommand : Command + { + public List ArtistIds { get; set; } + + public override bool SendUpdatesToClient => true; + + public RenameArtistCommand() + { + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs index eb20ae076..ff426b021 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs @@ -19,6 +19,7 @@ namespace NzbDrone.Core.MediaFiles void Update(TrackFile trackFile); void Delete(TrackFile trackFile, DeleteMediaFileReason reason); List GetFilesByArtist(int artistId); + List GetFilesByAlbum(int artistId, int albumId); List GetFilesWithoutMediaInfo(); List FilterExistingFiles(List files, Artist artist); TrackFile Get(int id); @@ -97,5 +98,10 @@ namespace NzbDrone.Core.MediaFiles { return _mediaFileRepository.GetFilesByArtist(artistId); } + + public List GetFilesByAlbum(int artistId, int albumId) + { + return _mediaFileRepository.GetFilesByArtist(artistId); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaFiles/RenameTrackFilePreview.cs b/src/NzbDrone.Core/MediaFiles/RenameTrackFilePreview.cs new file mode 100644 index 000000000..25bfe75f7 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/RenameTrackFilePreview.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace NzbDrone.Core.MediaFiles +{ + public class RenameTrackFilePreview + { + public int ArtistId { get; set; } + public int AlbumId { get; set; } + public List TrackNumbers { get; set; } + public int TrackFileId { get; set; } + public string ExistingPath { get; set; } + public string NewPath { get; set; } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/RenameTrackFileService.cs b/src/NzbDrone.Core/MediaFiles/RenameTrackFileService.cs new file mode 100644 index 000000000..001774de5 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/RenameTrackFileService.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NLog; +using NzbDrone.Common.Disk; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Instrumentation.Extensions; +using NzbDrone.Core.MediaFiles.Commands; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Music; + +namespace NzbDrone.Core.MediaFiles +{ + public interface IRenameTrackFileService + { + List GetRenamePreviews(int artistId); + List GetRenamePreviews(int artistId, int albumId); + } + + public class RenameTrackFileService : IRenameTrackFileService, + IExecute, + IExecute + { + private readonly IArtistService _artistService; + private readonly IAlbumService _albumService; + private readonly IMediaFileService _mediaFileService; + private readonly IMoveTrackFiles _trackFileMover; + private readonly IEventAggregator _eventAggregator; + private readonly ITrackService _trackService; + private readonly IBuildFileNames _filenameBuilder; + private readonly IDiskProvider _diskProvider; + private readonly Logger _logger; + + public RenameTrackFileService(IArtistService artistService, + IAlbumService albumService, + IMediaFileService mediaFileService, + IMoveTrackFiles trackFileMover, + IEventAggregator eventAggregator, + ITrackService trackService, + IBuildFileNames filenameBuilder, + IDiskProvider diskProvider, + Logger logger) + { + _artistService = artistService; + _albumService = albumService; + _mediaFileService = mediaFileService; + _trackFileMover = trackFileMover; + _eventAggregator = eventAggregator; + _trackService = trackService; + _filenameBuilder = filenameBuilder; + _diskProvider = diskProvider; + _logger = logger; + } + + public List GetRenamePreviews(int artistId) + { + // TODO + throw new NotImplementedException(); + //var artist = _artistService.GetArtist(artistId); + //var tracks = _trackService.GetTracksByArtist(artistId); + //var files = _mediaFileService.GetFilesByArtist(artistId); + + //return GetPreviews(artist, tracks, files) + // .OrderByDescending(e => e.SeasonNumber) + // .ThenByDescending(e => e.TrackNumbers.First()) + // .ToList(); + } + + public List GetRenamePreviews(int artistId, int albumId) + { + // TODO + //throw new NotImplementedException(); + var artist = _artistService.GetArtist(artistId); + var album = _albumService.GetAlbum(albumId); + var tracks = _trackService.GetTracksByAlbum(artistId, albumId); + var files = _mediaFileService.GetFilesByAlbum(artistId, albumId); + + return GetPreviews(artist, album, tracks, files) + .OrderByDescending(e => e.TrackNumbers.First()).ToList(); + } + + private IEnumerable GetPreviews(Artist artist, Album album, List tracks, List files) + { + foreach (var f in files) + { + var file = f; + var tracksInFile = tracks.Where(e => e.TrackFileId == file.Id).ToList(); + var trackFilePath = Path.Combine(artist.Path, file.RelativePath); + + if (!tracksInFile.Any()) + { + _logger.Warn("File ({0}) is not linked to any tracks", trackFilePath); + continue; + } + + var albumId = tracksInFile.First().AlbumId; + var newName = _filenameBuilder.BuildTrackFileName(tracksInFile, artist, album, file); + var newPath = _filenameBuilder.BuildTrackFilePath(artist, album, newName, Path.GetExtension(trackFilePath)); + + if (!trackFilePath.PathEquals(newPath, StringComparison.Ordinal)) + { + yield return new RenameTrackFilePreview + { + ArtistId = artist.Id, + AlbumId = albumId, + TrackNumbers = tracksInFile.Select(e => e.TrackNumber).ToList(), + TrackFileId = file.Id, + ExistingPath = file.RelativePath, + NewPath = artist.Path.GetRelativePath(newPath) + }; + } + } + } + + private void RenameFiles(List trackFiles, Artist artist) + { + // TODO + throw new NotImplementedException(); + //var renamed = new List(); + + //foreach (var trackFile in trackFiles) + //{ + // var trackFilePath = Path.Combine(artist.Path, trackFile.RelativePath); + + // try + // { + // _logger.Debug("Renaming track file: {0}", trackFile); + // _trackFileMover.MoveTrackFile(trackFile, artist); + + // _mediaFileService.Update(trackFile); + // renamed.Add(trackFile); + + // _logger.Debug("Renamed track file: {0}", trackFile); + // } + // catch (SameFilenameException ex) + // { + // _logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename); + // } + // catch (Exception ex) + // { + // _logger.Error(ex, "Failed to rename file {0}", trackFilePath); + // } + //} + + //if (renamed.Any()) + //{ + // _diskProvider.RemoveEmptySubfolders(artist.Path); + + // _eventAggregator.PublishEvent(new ArtistRenamedEvent(artist)); + //} + } + + public void Execute(RenameFilesCommand message) + { + // TODO + throw new NotImplementedException(); + //var artist = _artistService.GetArtist(message.ArtistId); + //var trackFiles = _mediaFileService.Get(message.Files); + + //_logger.ProgressInfo("Renaming {0} files for {1}", trackFiles.Count, artist.Title); + //RenameFiles(trackFiles, artist); + //_logger.ProgressInfo("Selected track files renamed for {0}", artist.Title); + } + + public void Execute(RenameArtistCommand message) + { + // TODO + throw new NotImplementedException(); + //_logger.Debug("Renaming all files for selected artist"); + //var artistToRename = _artistService.GetArtist(message.ArtistIds); + + //foreach (var artist in artistToRename) + //{ + // var trackFiles = _mediaFileService.GetFilesByArtist(artist.Id); + // _logger.ProgressInfo("Renaming all files in artist: {0}", artist.Title); + // RenameFiles(trackFiles, artist); + // _logger.ProgressInfo("All track files renamed for {0}", artist.Title); + //} + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/TrackFile.cs b/src/NzbDrone.Core/MediaFiles/TrackFile.cs index e20b3b5dc..cade66616 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackFile.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackFile.cs @@ -12,7 +12,6 @@ namespace NzbDrone.Core.MediaFiles { public class TrackFile : ModelBase { - public string SpotifyTrackId { get; set; } public int AlbumId { get; set; } public int ArtistId { get; set; } public string RelativePath { get; set; } diff --git a/src/NzbDrone.Core/Music/Artist.cs b/src/NzbDrone.Core/Music/Artist.cs index cdce64860..0ed7ff34e 100644 --- a/src/NzbDrone.Core/Music/Artist.cs +++ b/src/NzbDrone.Core/Music/Artist.cs @@ -23,6 +23,10 @@ namespace NzbDrone.Core.Music } public string ForeignArtistId { get; set; } + public string MBId { get; set; } + public int TADBId { get; set; } + public int DiscogsId { get; set; } + public string AMId { get; set; } public string Name { get; set; } public string NameSlug { get; set; } public string CleanName { get; set; } @@ -52,6 +56,10 @@ namespace NzbDrone.Core.Music { ForeignArtistId = otherArtist.ForeignArtistId; + MBId = otherArtist.MBId; + TADBId = otherArtist.TADBId; + DiscogsId = otherArtist.DiscogsId; + AMId = otherArtist.AMId; Name = otherArtist.Name; NameSlug = otherArtist.NameSlug; CleanName = otherArtist.CleanName; diff --git a/src/NzbDrone.Core/Music/Track.cs b/src/NzbDrone.Core/Music/Track.cs index 8e281260a..f0f25ae96 100644 --- a/src/NzbDrone.Core/Music/Track.cs +++ b/src/NzbDrone.Core/Music/Track.cs @@ -19,7 +19,7 @@ namespace NzbDrone.Core.Music public string ForeignTrackId { get; set; } public int AlbumId { get; set; } - public LazyLoaded Artist { get; set; } + public Artist Artist { get; set; } public int ArtistId { get; set; } // This is the DB Id of the Artist, not the SpotifyId //public int CompilationId { get; set; } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 0818a8ce7..963338f76 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -724,7 +724,10 @@ + + + diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index f5d3c3c24..dd0bf3497 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -20,7 +20,9 @@ namespace NzbDrone.Core.Organizer string BuildFileName(List episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null); string BuildTrackFileName(List tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig namingConfig = null); string BuildFilePath(Series series, int seasonNumber, string fileName, string extension); + string BuildTrackFilePath(Artist artist, Album album, string fileName, string extension); string BuildSeasonPath(Series series, int seasonNumber); + string BuildAlbumPath(Artist artist, Album album); BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); string GetSeriesFolder(Series series, NamingConfig namingConfig = null); string GetArtistFolder(Artist artist, NamingConfig namingConfig = null); @@ -202,6 +204,15 @@ namespace NzbDrone.Core.Organizer return Path.Combine(path, fileName + extension); } + public string BuildTrackFilePath(Artist artist, Album album, string fileName, string extension) + { + Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); + + var path = BuildAlbumPath(artist, album); + + return Path.Combine(path, fileName + extension); + } + public string BuildSeasonPath(Series series, int seasonNumber) { var path = series.Path; @@ -225,6 +236,24 @@ namespace NzbDrone.Core.Organizer return path; } + public string BuildAlbumPath(Artist artist, Album album) + { + var path = artist.Path; + + if (artist.AlbumFolder) + { + + var albumFolder = GetAlbumFolder(artist, album); + + albumFolder = CleanFileName(albumFolder); + + path = Path.Combine(path, albumFolder); + + } + + return path; + } + public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec) { var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat).LastOrDefault(); diff --git a/src/UI/AddArtist/SearchResultViewTemplate.hbs b/src/UI/AddArtist/SearchResultViewTemplate.hbs index 3c27e1b97..0a3bbe016 100644 --- a/src/UI/AddArtist/SearchResultViewTemplate.hbs +++ b/src/UI/AddArtist/SearchResultViewTemplate.hbs @@ -16,11 +16,11 @@
    -
    +
    {{#unless existing}} {{#unless path}} diff --git a/src/UI/Artist/Details/InfoViewTemplate.hbs b/src/UI/Artist/Details/InfoViewTemplate.hbs index c6e689630..f305c976b 100644 --- a/src/UI/Artist/Details/InfoViewTemplate.hbs +++ b/src/UI/Artist/Details/InfoViewTemplate.hbs @@ -29,20 +29,18 @@
    - Trakt + MusicBrainz - The TVDB - - {{#if imdbId}} - IMDB + {{#if tadbId}} + The AudioDB {{/if}} - {{#if tvRageId}} - TV Rage + {{#if discogsId}} + Discogs {{/if}} - {{#if tvMazeId}} - TV Maze + {{#if amId}} + AllMusic {{/if}}
    diff --git a/src/UI/Artist/Index/ArtistIndexLayout.js b/src/UI/Artist/Index/ArtistIndexLayout.js index b06bd5311..81884d48c 100644 --- a/src/UI/Artist/Index/ArtistIndexLayout.js +++ b/src/UI/Artist/Index/ArtistIndexLayout.js @@ -321,15 +321,17 @@ module.exports = Marionette.Layout.extend({ _showFooter : function() { var footerModel = new FooterModel(); var artist = this.artistCollection.models.length; - var episodes = 0; - var episodeFiles = 0; + var albums = 0; + var tracks = 0; + var trackFiles = 0; var ended = 0; var continuing = 0; var monitored = 0; _.each(this.artistCollection.models, function(model) { - episodes += model.get('episodeCount'); // TODO: Refactor to Seasons and Tracks - episodeFiles += model.get('episodeFileCount'); + albums += model.get('albumCount'); + tracks += model.get('episodeCount'); // TODO: Refactor to Seasons and Tracks + trackFiles += model.get('episodeFileCount'); /*if (model.get('status').toLowerCase() === 'ended') { ended++; @@ -348,8 +350,9 @@ module.exports = Marionette.Layout.extend({ continuing : continuing, monitored : monitored, unmonitored : artist - monitored, - episodes : episodes, - episodeFiles : episodeFiles + albums : albums, + tracks : tracks, + trackFiles : trackFiles }); this.footer.show(new FooterView({ model : footerModel })); diff --git a/src/UI/Artist/Index/FooterViewTemplate.hbs b/src/UI/Artist/Index/FooterViewTemplate.hbs index 58d744386..f61baa356 100644 --- a/src/UI/Artist/Index/FooterViewTemplate.hbs +++ b/src/UI/Artist/Index/FooterViewTemplate.hbs @@ -34,11 +34,14 @@
    +
    Albums
    +
    {{albums}}
    +
    Tracks
    -
    {{episodes}}
    +
    {{tracks}}
    Files
    -
    {{episodeFiles}}
    +
    {{trackFiles}}
    diff --git a/src/UI/Artist/Index/TrackProgressPartial.hbs b/src/UI/Artist/Index/TrackProgressPartial.hbs index db5c49a2b..a9cec28f7 100644 --- a/src/UI/Artist/Index/TrackProgressPartial.hbs +++ b/src/UI/Artist/Index/TrackProgressPartial.hbs @@ -1,4 +1,4 @@ -
    - {{episodeFileCount}} / {{episodeCount}} -
    {{episodeFileCount}} / {{episodeCount}}
    +
    + {{trackFileCount}} / {{trackCount}} +
    {{trackFileCount}} / {{trackCount}}
    \ No newline at end of file diff --git a/src/UI/Artist/TrackCollection.js b/src/UI/Artist/TrackCollection.js index 33bd4c3a3..302c416ea 100644 --- a/src/UI/Artist/TrackCollection.js +++ b/src/UI/Artist/TrackCollection.js @@ -4,11 +4,11 @@ var TrackModel = require('./TrackModel'); require('./TrackCollection'); module.exports = PageableCollection.extend({ - url : window.NzbDrone.ApiRoot + '/episode', + url : window.NzbDrone.ApiRoot + '/track', model : TrackModel, state : { - sortKey : 'episodeNumber', + sortKey : 'trackNumber', order : 1, pageSize : 100000 }, @@ -18,28 +18,28 @@ module.exports = PageableCollection.extend({ originalFetch : Backbone.Collection.prototype.fetch, initialize : function(options) { - this.seriesId = options.seriesId; + this.artistId = options.artistId; }, - bySeason : function(season) { - var filtered = this.filter(function(episode) { - return episode.get('seasonNumber') === season; + bySeason : function(album) { + var filtered = this.filter(function(track) { + return track.get('albumId') === album; }); - var EpisodeCollection = require('./TrackCollection'); + var TrackCollection = require('./TrackCollection'); - return new EpisodeCollection(filtered); + return new TrackCollection(filtered); }, comparator : function(model1, model2) { - var episode1 = model1.get('episodeNumber'); - var episode2 = model2.get('episodeNumber'); + var track1 = model1.get('trackNumber'); + var track2 = model2.get('trackNumber'); - if (episode1 < episode2) { + if (track1 < track2) { return 1; } - if (episode1 > episode2) { + if (track1 > track2) { return -1; } @@ -47,15 +47,15 @@ module.exports = PageableCollection.extend({ }, fetch : function(options) { - if (!this.seriesId) { - throw 'seriesId is required'; + if (!this.artistId) { + throw 'artistId is required'; } if (!options) { options = {}; } - options.data = { seriesId : this.seriesId }; + options.data = { artistId : this.artistId }; return this.originalFetch.call(this, options); } diff --git a/src/UI/Artist/TrackFileCollection.js b/src/UI/Artist/TrackFileCollection.js index 19c58ebee..9b9909ce6 100644 --- a/src/UI/Artist/TrackFileCollection.js +++ b/src/UI/Artist/TrackFileCollection.js @@ -2,7 +2,7 @@ var Backbone = require('backbone'); var TrackFileModel = require('./TrackFileModel'); module.exports = Backbone.Collection.extend({ - url : window.NzbDrone.ApiRoot + '/episodefile', + url : window.NzbDrone.ApiRoot + '/trackfile', model : TrackFileModel, originalFetch : Backbone.Collection.prototype.fetch, @@ -21,7 +21,7 @@ module.exports = Backbone.Collection.extend({ options = {}; } - options.data = { seriesId : this.seriesId }; + options.data = { artistId : this.artistId }; return this.originalFetch.call(this, options); } diff --git a/src/UI/Artist/TrackModel.js b/src/UI/Artist/TrackModel.js index ebb72cf29..30a2702d7 100644 --- a/src/UI/Artist/TrackModel.js +++ b/src/UI/Artist/TrackModel.js @@ -2,12 +2,12 @@ var Backbone = require('backbone'); module.exports = Backbone.Model.extend({ defaults : { - seasonNumber : 0, + albumId : 0, status : 0 }, methodUrls : { - 'update' : window.NzbDrone.ApiRoot + '/episode' + 'update' : window.NzbDrone.ApiRoot + '/track' }, sync : function(method, model, options) { diff --git a/src/UI/Controller.js b/src/UI/Controller.js index 5125bbc30..04dcce602 100644 --- a/src/UI/Controller.js +++ b/src/UI/Controller.js @@ -19,11 +19,6 @@ module.exports = NzbDroneController.extend({ this.showMainRegion(new AddArtistLayout({ action : action })); }, - artistDetails: function(query) { - this.setTitle('Artist Detail'); - this.showMainRegion(new SeriesDetailsLayout()); - }, - calendar : function() { this.setTitle('Calendar'); this.showMainRegion(new CalendarLayout()); diff --git a/src/UI/Handlebars/Helpers/Artist.js b/src/UI/Handlebars/Helpers/Artist.js index 13990530c..36c8983ba 100644 --- a/src/UI/Handlebars/Helpers/Artist.js +++ b/src/UI/Handlebars/Helpers/Artist.js @@ -19,24 +19,20 @@ Handlebars.registerHelper('poster', function() { return new Handlebars.SafeString(''.format(placeholder)); }); -Handlebars.registerHelper('traktUrl', function() { - return 'http://trakt.tv/search/tvdb/' + this.tvdbId + '?id_type=show'; +Handlebars.registerHelper('MBUrl', function() { + return 'https://musicbrainz.org/artist/' + this.mbId; }); -Handlebars.registerHelper('imdbUrl', function() { - return 'http://imdb.com/title/' + this.imdbId; +Handlebars.registerHelper('TADBUrl', function() { + return 'http://www.theaudiodb.com/artist/' + this.tadbId; }); -Handlebars.registerHelper('tvdbUrl', function() { - return 'http://www.thetvdb.com/?tab=series&id=' + this.tvdbId; +Handlebars.registerHelper('discogsUrl', function() { + return 'https://www.discogs.com/artist/' + this.discogsId; }); -Handlebars.registerHelper('tvRageUrl', function() { - return 'http://www.tvrage.com/shows/id-' + this.tvRageId; -}); - -Handlebars.registerHelper('tvMazeUrl', function() { - return 'http://www.tvmaze.com/shows/' + this.tvMazeId + '/_'; +Handlebars.registerHelper('allMusicUrl', function() { + return 'http://www.allmusic.com/artist/' + this.amId; }); Handlebars.registerHelper('route', function() { @@ -56,6 +52,19 @@ Handlebars.registerHelper('percentOfEpisodes', function() { return percent; }); +Handlebars.registerHelper('percentOfTracks', function() { + var trackCount = this.trackCount; + var trackFileCount = this.trackFileCount; + + var percent = 100; + + if (trackCount > 0) { + percent = trackFileCount / trackCount * 100; + } + + return percent; +}); + Handlebars.registerHelper('seasonCountHelper', function() { var seasonCount = this.seasonCount; var continuing = this.status === 'continuing'; @@ -87,7 +96,7 @@ Handlebars.registerHelper('albumCountHelper', function() { var albumCount = this.albumCount; if (albumCount === 1) { - return new Handlebars.SafeString('{0} Albums'.format(albumCount)); + return new Handlebars.SafeString('{0} Album'.format(albumCount)); } return new Handlebars.SafeString('{0} Albums'.format(albumCount)); diff --git a/src/UI/Router.js b/src/UI/Router.js index ccc4b964d..ce8db831c 100644 --- a/src/UI/Router.js +++ b/src/UI/Router.js @@ -18,7 +18,6 @@ module.exports = Marionette.AppRouter.extend({ 'rss' : 'rss', 'system' : 'system', 'system/:action' : 'system', - 'artist/:query' : 'artistDetails', 'seasonpass' : 'seasonPass', 'artisteditor' : 'artistEditor', ':whatever' : 'showNotFound' From bf7f890c48f0dc947c4d37c8e7374757e09af2ce Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Wed, 21 Jun 2017 20:30:45 -0500 Subject: [PATCH 12/13] Restabilized add artist flow with NodeJS API --- src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs | 7 +------ src/NzbDrone.Core/MediaFiles/MediaFileService.cs | 5 +---- src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs | 4 ++-- src/NzbDrone.Core/Music/RefreshTrackService.cs | 3 +-- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs b/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs index 698d3b4a4..c53cb4f39 100644 --- a/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs +++ b/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs @@ -17,14 +17,9 @@ namespace NzbDrone.Common.Cloud Services = new HttpRequestBuilder("http://services.lidarr.tv/v1/") .CreateFactory(); - //Search = new HttpRequestBuilder("https://api.spotify.com/{version}/{route}/") // TODO: maybe use {version} - // .SetSegment("version", "v1") - // .CreateFactory(); - Search = new HttpRequestBuilder("http://localhost:3000/{route}/") // TODO: maybe use {version} + Search = new HttpRequestBuilder("http://localhost:3000/{route}/") // TODO: Add {version} once LidarrAPI.Metadata is released. .CreateFactory(); - InternalSearch = new HttpRequestBuilder("https://itunes.apple.com/WebObjects/MZStore.woa/wa/{route}") //viewArtist or search - .CreateFactory(); SkyHookTvdb = new HttpRequestBuilder("http://skyhook.lidarr.tv/v1/tvdb/{route}/{language}/") .SetSegment("language", "en") diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs index 6806a0422..e296e7c11 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs @@ -69,11 +69,8 @@ namespace NzbDrone.Core.MediaFiles public List FilterExistingFiles(List files, Artist artist) { -<<<<<<< HEAD - var artistFiles = GetFilesByArtist(artist.ForeignArtistId).Select(f => Path.Combine(artist.Path, f.RelativePath)).ToList(); -======= + //var artistFiles = GetFilesByArtist(artist.ForeignArtistId).Select(f => Path.Combine(artist.Path, f.RelativePath)).ToList(); var artistFiles = GetFilesByArtist(artist.Id).Select(f => Path.Combine(artist.Path, f.RelativePath)).ToList(); ->>>>>>> fafe4e93f34fea769c9a64d9a98015821738b1e4 if (!artistFiles.Any()) return files; diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index 03e5f2bdb..dec8c8c9b 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -81,7 +81,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook // We need to perform a direct lookup of the artist var httpRequest = _requestBuilder.Create() - .SetSegment("route", "artists/" + foreignArtistId) + .SetSegment("route", "artist/" + foreignArtistId) //.SetSegment("route", "search") //.AddQueryParam("type", "artist,album") //.AddQueryParam("q", spotifyId.ToString()) @@ -227,7 +227,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook var httpRequest = _requestBuilder.Create() .SetSegment("route", "search") - .AddQueryParam("type", "artist") // TODO: LidarrAPI.Metadata is getting , encoded. Needs to be raw , + .AddQueryParam("type", "artist") .AddQueryParam("query", title.ToLower().Trim()) .Build(); diff --git a/src/NzbDrone.Core/Music/RefreshTrackService.cs b/src/NzbDrone.Core/Music/RefreshTrackService.cs index 58754cf44..b1bd6900f 100644 --- a/src/NzbDrone.Core/Music/RefreshTrackService.cs +++ b/src/NzbDrone.Core/Music/RefreshTrackService.cs @@ -33,8 +33,7 @@ namespace NzbDrone.Core.Music var successCount = 0; var failCount = 0; - var existingTracks = _trackService.GetTracksByAlbum(album.ForeignAlbumId, album.ForeignAlbumId); - //var albums = artist.Albums; + var existingTracks = _trackService.GetTracksByAlbum(album.ArtistId, album.Id); // TODO: JOE: I believe this should be string, string var updateList = new List(); var newList = new List(); From c6ac0878a72f500ea0c64f1fb24cb05af8f8c7b9 Mon Sep 17 00:00:00 2001 From: Qstick Date: Wed, 21 Jun 2017 21:45:07 -0400 Subject: [PATCH 13/13] Fix Crash due to ForeignArtistId not being in DB for TrackFile Table Fix Crash due to ForeignArtistId not being in DB for TrackFile Table --- src/NzbDrone.Core/MediaFiles/TrackFile.cs | 4 ++-- .../MediaFiles/TrackImport/ImportApprovedTracks.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Core/MediaFiles/TrackFile.cs b/src/NzbDrone.Core/MediaFiles/TrackFile.cs index 6f46eaee7..9aab9a655 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackFile.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackFile.cs @@ -12,8 +12,8 @@ namespace NzbDrone.Core.MediaFiles { public class TrackFile : ModelBase { - public string ForeignTrackId { get; set; } - public string ForeignArtistId { get; set; } + //public string ForeignTrackId { get; set; } + //public string ForeignArtistId { get; set; } public int AlbumId { get; set; } public int ArtistId { get; set; } public string RelativePath { get; set; } diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs index fe45e064a..e38c27ca8 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs @@ -73,7 +73,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport var trackFile = new TrackFile(); trackFile.DateAdded = DateTime.UtcNow; - trackFile.ForeignArtistId = localTrack.Artist.ForeignArtistId; + trackFile.ArtistId = localTrack.Artist.Id; trackFile.Path = localTrack.Path.CleanFilePath(); trackFile.Size = _diskProvider.GetFileSize(localTrack.Path); trackFile.Quality = localTrack.Quality;