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