Refactored most code for track parsing.

pull/7/head
Joseph Milazzo 7 years ago
parent d1eb9ff16c
commit 76db95947c

@ -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<Track> Tracks { get; set; }
public List<string> QueryTitles => SceneTitles.Select(GetQueryTitle).ToList();
public static string GetQueryTitle(string title)

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

@ -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<string> FilterFiles(Series series, IEnumerable<string> files);
@ -30,13 +33,16 @@ namespace NzbDrone.Core.MediaFiles
public class DiskScanService :
IDiskScanService,
IHandle<SeriesUpdatedEvent>,
IExecute<RescanSeriesCommand>
IExecute<RescanSeriesCommand>,
IHandle<ArtistUpdatedEvent>,
IExecute<RescanArtistCommand>
{
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<string>());
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<string> 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<string> FilterFiles(Artist artist, IEnumerable<string> 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);
}
}
}
}
}

@ -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<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series);
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource);
//List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series);
List<ImportDecision> GetImportDecisions(List<string> musicFiles, Artist artist);
//List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource);
List<ImportDecision> GetImportDecisions(List<string> musicFiles, Artist artist, ParsedTrackInfo folderInfo);
}
public class ImportDecisionMaker : IMakeImportDecision
@ -48,65 +50,87 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
_logger = logger;
}
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series)
//public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series)
//{
// return GetImportDecisions(videoFiles, series, null, false);
//}
//public List<ImportDecision> GetImportDecisions(List<string> 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<ImportDecision>();
// foreach (var file in newFiles)
// {
// decisions.AddIfNotNull(GetDecision(file, series, folderInfo, sceneSource, shouldUseFolderName));
// }
// return decisions;
//}
public List<ImportDecision> GetImportDecisions(List<string> musicFiles, Artist artist)
{
return GetImportDecisions(videoFiles, series, null, false);
return GetImportDecisions(musicFiles, artist, null);
}
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource)
public List<ImportDecision> GetImportDecisions(List<string> 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<ImportDecision>();
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<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo)
private bool ShouldUseFolderName(List<string> 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;
}

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

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

@ -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<EpisodeFile>
public interface IMediaFileRepository : IBasicRepository<TrackFile>
{
List<EpisodeFile> GetFilesBySeries(int seriesId);
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
List<EpisodeFile> GetFilesWithoutMediaInfo();
List<TrackFile> GetFilesByArtist(string artistId);
List<TrackFile> GetFilesWithoutMediaInfo();
}
public class MediaFileRepository : BasicRepository<EpisodeFile>, IMediaFileRepository
public class MediaFileRepository : BasicRepository<TrackFile>, IMediaFileRepository
{
public MediaFileRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
public List<EpisodeFile> GetFilesBySeries(int seriesId)
public List<TrackFile> GetFilesWithoutMediaInfo()
{
return Query.Where(c => c.SeriesId == seriesId).ToList();
}
public List<EpisodeFile> 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<EpisodeFile> GetFilesWithoutMediaInfo()
public List<TrackFile> GetFilesByArtist(string artistId)
{
return Query.Where(c => c.MediaInfo == null).ToList();
return Query.Where(c => c.SpotifyTrackId == artistId).ToList();
}
}
}

@ -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<EpisodeFile> GetFilesBySeries(int seriesId);
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
List<EpisodeFile> GetFilesWithoutMediaInfo();
List<string> FilterExistingFiles(List<string> files, Series series);
EpisodeFile Get(int id);
List<EpisodeFile> Get(IEnumerable<int> ids);
TrackFile Add(TrackFile trackFile);
void Update(TrackFile trackFile);
void Delete(TrackFile trackFile, DeleteMediaFileReason reason);
List<TrackFile> GetFilesByArtist(string artistId);
List<TrackFile> GetFilesWithoutMediaInfo();
List<string> FilterExistingFiles(List<string> files, Artist artist);
TrackFile Get(int id);
List<TrackFile> Get(IEnumerable<int> ids);
}
public class MediaFileService : IMediaFileService, IHandleAsync<SeriesDeletedEvent>
public class MediaFileService : IMediaFileService, IHandleAsync<ArtistDeletedEvent>
{
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<EpisodeFile> GetFilesBySeries(int seriesId)
{
return _mediaFileRepository.GetFilesBySeries(seriesId);
}
public List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber)
{
return _mediaFileRepository.GetFilesBySeason(seriesId, seasonNumber);
}
public List<EpisodeFile> GetFilesWithoutMediaInfo()
public List<TrackFile> GetFilesWithoutMediaInfo()
{
return _mediaFileRepository.GetFilesWithoutMediaInfo();
}
public List<string> FilterExistingFiles(List<string> files, Series series)
public List<string> FilterExistingFiles(List<string> 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<EpisodeFile> Get(IEnumerable<int> ids)
public List<TrackFile> Get(IEnumerable<int> 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<TrackFile> GetFilesByArtist(string artistId)
{
return _mediaFileRepository.GetFilesByArtist(artistId);
}
}
}

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

@ -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<Artist> GetArtists(IEnumerable<int> artistIds)
{
return _artistRepository.Get(artistIds).ToList();

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

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

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

@ -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<Track> GetStandardTracks(Artist artist, ParsedTrackInfo parsedTrackInfo, SearchCriteriaBase searchCriteria)
{
var result = new List<Track>();
if (parsedTrackInfo.TrackNumbers == null)
{
return result;
}
foreach (var trackNumber in parsedTrackInfo.TrackNumbers)
{
//if (series.UseSceneNumbering && sceneSource)
//{
// List<Episode> episodes = new List<Episode>();
// 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<Episode> GetStandardEpisodes(Series series, ParsedEpisodeInfo parsedEpisodeInfo, bool sceneSource, SearchCriteriaBase searchCriteria)
{
var result = new List<Episode>();
@ -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<Track> GetTracks(ParsedTrackInfo parsedTrackInfo, Artist artist, bool sceneSource)
private List<Track> 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);
}
}
}

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

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

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

@ -8,7 +8,7 @@
<div class="col-md-10 col-xs-9">
<div class="row">
<div class="col-md-10 col-xs-10">
<a href="artist/{{artistNameSlug}}" target="_blank">
<a href="artist/{{artistSlug}}" target="_blank">
<h2>{{artistName}}</h2>
</a>
</div>

Loading…
Cancel
Save