From 8569084255a9f9a5de4206f416fad776b22b9659 Mon Sep 17 00:00:00 2001 From: Qstick Date: Fri, 25 Aug 2017 22:00:14 -0400 Subject: [PATCH] Refactor and Enable Renaming for Album and Artist Files (#61) Refactor and Enable Renaming for Album and Artist Files --- src/NzbDrone.Api/Config/NamingConfigModule.cs | 60 +- .../Config/NamingConfigResource.cs | 32 +- .../Episodes/RenameEpisodeModule.cs | 37 -- .../Episodes/RenameEpisodeResource.cs | 39 -- src/NzbDrone.Api/NzbDrone.Api.csproj | 2 - .../Extensions/StringExtensions.cs | 12 +- ...gressExtensions.cs => LoggerExtensions.cs} | 2 +- .../Extensions/SentryLoggerExtensions.cs | 58 ++ .../Instrumentation/NzbDroneLogger.cs | 60 +- .../Instrumentation/Sentry/SentryTarget.cs | 45 +- src/NzbDrone.Common/NzbDrone.Common.csproj | 3 +- .../MoveEpisodeFileFixture.cs | 124 ---- .../FormattedAudioChannelsFixture.cs | 120 ---- .../FormatAudioCodecFixture.cs | 50 ++ .../UpdateMediaInfoServiceFixture.cs | 63 +- ...re.cs => RenameTrackFileServiceFixture.cs} | 25 +- .../MoveTrackFileFixture.cs | 124 ++++ .../MoveArtistServiceFixture.cs} | 54 +- .../NzbDrone.Core.Test.csproj | 14 +- .../OrganizerTests/BuildFilePathFixture.cs | 46 +- .../FileNameBuilderTests/CleanTitleFixture.cs | 47 +- .../EpisodeTitleCollapseFixture.cs | 113 ---- .../FileNameBuilderFixture.cs | 619 ++++++------------ .../MultiEpisodeFixture.cs | 271 -------- .../OrganizerTests/GetAlbumFolderFixture.cs | 35 + .../OrganizerTests/GetArtistFolderFixture.cs | 39 ++ .../OrganizerTests/GetSeasonFolderFixture.cs | 34 - .../OrganizerTests/GetSeriesFolderFixture.cs | 39 -- .../Migration/114_remove_tv_naming.cs | 27 + .../MediaFiles/Commands/RenameFilesCommand.cs | 8 +- .../MediaFiles/EpisodeFileMoveResult.cs | 15 - .../MediaFiles/EpisodeFileMovingService.cs | 215 ------ .../Events/TrackFolderCreatedEvent.cs | 20 + src/NzbDrone.Core/MediaFiles/FileDateType.cs | 5 +- .../MediaFiles/MediaFileRepository.cs | 6 + .../MediaFiles/MediaFileService.cs | 4 +- .../MediaInfo/MediaInfoFormatter.cs | 200 ++++++ .../MediaFiles/MediaInfo/MediaInfoModel.cs | 34 +- .../MediaInfo/UpdateMediaInfoService.cs | 10 +- .../MediaInfo/VideoFileInfoReader.cs | 37 +- .../MediaFiles/RenameEpisodeFilePreview.cs | 14 - .../MediaFiles/RenameEpisodeFileService.cs | 181 ----- .../MediaFiles/RenameTrackFileService.cs | 140 ++-- .../MediaFiles/TrackFileMovingService.cs | 164 +++-- .../MediaFiles/UpdateEpisodeFileService.cs | 173 ----- .../MediaFiles/UpdateTrackFileService.cs | 128 ++++ .../MediaFiles/UpgradeMediaFileService.cs | 14 +- .../Commands/MoveArtistCommand.cs} | 6 +- .../Events/ArtistMovedEvent.cs} | 10 +- .../MoveArtistService.cs} | 36 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 23 +- .../Organizer/AbsoluteEpisodeFormat.cs | 10 - .../Organizer/AbsoluteTrackFormat.cs | 9 + .../Organizer/BasicNamingConfig.cs | 4 +- .../Organizer/FileNameBuilder.cs | 508 ++------------ .../Organizer/FileNameSampleService.cs | 212 +----- src/NzbDrone.Core/Organizer/NamingConfig.cs | 16 +- .../{EpisodeFormat.cs => TrackFormat.cs} | 4 +- src/NzbDrone.Core/Tv/AddSeriesService.cs | 4 +- .../ApiTests/NamingConfigFixture.cs | 71 +- src/UI/Artist/Details/AlbumLayout.js | 4 +- src/UI/Artist/Details/ArtistDetailsLayout.js | 4 +- .../Editor/Organize/OrganizeFilesView.js | 4 +- .../Organize/OrganizeFilesViewTemplate.hbs | 8 +- src/UI/Rename/RenamePreviewCollection.js | 18 +- src/UI/Rename/RenamePreviewFormatView.js | 5 +- src/UI/Rename/RenamePreviewLayout.js | 20 +- .../FileManagementViewTemplate.hbs | 5 +- .../Naming/Basic/BasicNamingView.js | 72 +- .../Naming/Basic/BasicNamingViewTemplate.hbs | 16 +- 70 files changed, 1512 insertions(+), 3119 deletions(-) delete mode 100644 src/NzbDrone.Api/Episodes/RenameEpisodeModule.cs delete mode 100644 src/NzbDrone.Api/Episodes/RenameEpisodeResource.cs rename src/NzbDrone.Common/Instrumentation/Extensions/{LoggerProgressExtensions.cs => LoggerExtensions.cs} (98%) create mode 100644 src/NzbDrone.Common/Instrumentation/Extensions/SentryLoggerExtensions.cs delete mode 100644 src/NzbDrone.Core.Test/MediaFiles/EpisodeFileMovingServiceTests/MoveEpisodeFileFixture.cs delete mode 100644 src/NzbDrone.Core.Test/MediaFiles/MediaInfo/FormattedAudioChannelsFixture.cs create mode 100644 src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioCodecFixture.cs rename src/NzbDrone.Core.Test/MediaFiles/{RenameEpisodeFileServiceFixture.cs => RenameTrackFileServiceFixture.cs} (84%) create mode 100644 src/NzbDrone.Core.Test/MediaFiles/TrackFileMovingServiceTests/MoveTrackFileFixture.cs rename src/NzbDrone.Core.Test/{TvTests/MoveSeriesServiceFixture.cs => MusicTests/MoveArtistServiceFixture.cs} (55%) delete mode 100644 src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/EpisodeTitleCollapseFixture.cs delete mode 100644 src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/MultiEpisodeFixture.cs create mode 100644 src/NzbDrone.Core.Test/OrganizerTests/GetAlbumFolderFixture.cs create mode 100644 src/NzbDrone.Core.Test/OrganizerTests/GetArtistFolderFixture.cs delete mode 100644 src/NzbDrone.Core.Test/OrganizerTests/GetSeasonFolderFixture.cs delete mode 100644 src/NzbDrone.Core.Test/OrganizerTests/GetSeriesFolderFixture.cs create mode 100644 src/NzbDrone.Core/Datastore/Migration/114_remove_tv_naming.cs delete mode 100644 src/NzbDrone.Core/MediaFiles/EpisodeFileMoveResult.cs delete mode 100644 src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs create mode 100644 src/NzbDrone.Core/MediaFiles/Events/TrackFolderCreatedEvent.cs create mode 100644 src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs delete mode 100644 src/NzbDrone.Core/MediaFiles/RenameEpisodeFilePreview.cs delete mode 100644 src/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs delete mode 100644 src/NzbDrone.Core/MediaFiles/UpdateEpisodeFileService.cs create mode 100644 src/NzbDrone.Core/MediaFiles/UpdateTrackFileService.cs rename src/NzbDrone.Core/{Tv/Commands/MoveSeriesCommand.cs => Music/Commands/MoveArtistCommand.cs} (63%) rename src/NzbDrone.Core/{Tv/Events/SeriesMovedEvent.cs => Music/Events/ArtistMovedEvent.cs} (56%) rename src/NzbDrone.Core/{Tv/MoveSeriesService.cs => Music/MoveArtistService.cs} (65%) delete mode 100644 src/NzbDrone.Core/Organizer/AbsoluteEpisodeFormat.cs create mode 100644 src/NzbDrone.Core/Organizer/AbsoluteTrackFormat.cs rename src/NzbDrone.Core/Organizer/{EpisodeFormat.cs => TrackFormat.cs} (50%) diff --git a/src/NzbDrone.Api/Config/NamingConfigModule.cs b/src/NzbDrone.Api/Config/NamingConfigModule.cs index d2f05539a..abe7096c9 100644 --- a/src/NzbDrone.Api/Config/NamingConfigModule.cs +++ b/src/NzbDrone.Api/Config/NamingConfigModule.cs @@ -34,12 +34,7 @@ namespace NzbDrone.Api.Config Get["/samples"] = x => GetExamples(this.Bind()); 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(); } @@ -57,7 +52,7 @@ namespace NzbDrone.Api.Config var nameSpec = _namingConfigService.GetConfig(); var resource = nameSpec.ToResource(); - if (resource.StandardEpisodeFormat.IsNotNullOrWhiteSpace()) + if (resource.StandardTrackFormat.IsNotNullOrWhiteSpace()) { var basicConfig = _filenameBuilder.GetBasicNamingConfig(nameSpec); basicConfig.AddToResource(resource); @@ -76,45 +71,13 @@ namespace NzbDrone.Api.Config var nameSpec = config.ToModel(); 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); - var animeMultiEpisodeSampleResult = _filenameSampleService.GetAnimeMultiEpisodeSample(nameSpec); - sampleResource.SingleEpisodeExample = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult) != null - ? "Invalid format" - : singleEpisodeSampleResult.FileName; + var singleTrackSampleResult = _filenameSampleService.GetStandardTrackSample(nameSpec); sampleResource.SingleTrackExample = _filenameValidationService.ValidateTrackFilename(singleTrackSampleResult) != null ? "Invalid format" : singleTrackSampleResult.FileName; - sampleResource.MultiEpisodeExample = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult) != null - ? "Invalid format" - : multiEpisodeSampleResult.FileName; - - sampleResource.DailyEpisodeExample = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult) != null - ? "Invalid format" - : dailyEpisodeSampleResult.FileName; - - sampleResource.AnimeEpisodeExample = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult) != null - ? "Invalid format" - : animeEpisodeSampleResult.FileName; - - sampleResource.AnimeMultiEpisodeExample = _filenameValidationService.ValidateAnimeFilename(animeMultiEpisodeSampleResult) != null - ? "Invalid format" - : animeMultiEpisodeSampleResult.FileName; - - sampleResource.SeriesFolderExample = nameSpec.SeriesFolderFormat.IsNullOrWhiteSpace() - ? "Invalid format" - : _filenameSampleService.GetSeriesFolderSample(nameSpec); - - sampleResource.SeasonFolderExample = nameSpec.SeasonFolderFormat.IsNullOrWhiteSpace() - ? "Invalid format" - : _filenameSampleService.GetSeasonFolderSample(nameSpec); - sampleResource.ArtistFolderExample = nameSpec.ArtistFolderFormat.IsNullOrWhiteSpace() ? "Invalid format" : _filenameSampleService.GetArtistFolderSample(nameSpec); @@ -128,28 +91,15 @@ namespace NzbDrone.Api.Config 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 singleTrackSampleResult = _filenameSampleService.GetStandardTrackSample(nameSpec); + var singleTrackValidationResult = _filenameValidationService.ValidateTrackFilename(singleTrackSampleResult); - var multiEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult); - var dailyEpisodeValidationResult = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult); - var animeEpisodeValidationResult = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult); - var animeMultiEpisodeValidationResult = _filenameValidationService.ValidateAnimeFilename(animeMultiEpisodeSampleResult); var validationFailures = new List(); - validationFailures.AddIfNotNull(singleEpisodeValidationResult); validationFailures.AddIfNotNull(singleTrackValidationResult); - validationFailures.AddIfNotNull(multiEpisodeValidationResult); - validationFailures.AddIfNotNull(dailyEpisodeValidationResult); - validationFailures.AddIfNotNull(animeEpisodeValidationResult); - validationFailures.AddIfNotNull(animeMultiEpisodeValidationResult); + if (validationFailures.Any()) { diff --git a/src/NzbDrone.Api/Config/NamingConfigResource.cs b/src/NzbDrone.Api/Config/NamingConfigResource.cs index d49c39936..47acd6a24 100644 --- a/src/NzbDrone.Api/Config/NamingConfigResource.cs +++ b/src/NzbDrone.Api/Config/NamingConfigResource.cs @@ -1,24 +1,18 @@ -using NzbDrone.Api.REST; +using NzbDrone.Api.REST; using NzbDrone.Core.Organizer; 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 IncludeArtistName { get; set; } + public bool IncludeAlbumTitle { get; set; } public bool IncludeQuality { get; set; } public bool ReplaceSpaces { get; set; } public string Separator { get; set; } @@ -33,16 +27,9 @@ 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, ArtistFolderFormat = model.ArtistFolderFormat, AlbumFolderFormat = model.AlbumFolderFormat //IncludeSeriesTitle @@ -56,8 +43,8 @@ namespace NzbDrone.Api.Config public static void AddToResource(this BasicNamingConfig basicNamingConfig, NamingConfigResource resource) { - resource.IncludeSeriesTitle = basicNamingConfig.IncludeSeriesTitle; - resource.IncludeEpisodeTitle = basicNamingConfig.IncludeEpisodeTitle; + resource.IncludeArtistName = basicNamingConfig.IncludeArtistName; + resource.IncludeAlbumTitle = basicNamingConfig.IncludeAlbumTitle; resource.IncludeQuality = basicNamingConfig.IncludeQuality; resource.ReplaceSpaces = basicNamingConfig.ReplaceSpaces; resource.Separator = basicNamingConfig.Separator; @@ -70,19 +57,12 @@ 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, ArtistFolderFormat = resource.ArtistFolderFormat, AlbumFolderFormat = resource.AlbumFolderFormat }; } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Api/Episodes/RenameEpisodeModule.cs b/src/NzbDrone.Api/Episodes/RenameEpisodeModule.cs deleted file mode 100644 index 87f39b964..000000000 --- a/src/NzbDrone.Api/Episodes/RenameEpisodeModule.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections.Generic; -using NzbDrone.Api.REST; -using NzbDrone.Core.MediaFiles; - -namespace NzbDrone.Api.Episodes -{ - public class RenameEpisodeModule : NzbDroneRestModule - { - private readonly IRenameEpisodeFileService _renameEpisodeFileService; - - public RenameEpisodeModule(IRenameEpisodeFileService renameEpisodeFileService) - : base("rename") - { - _renameEpisodeFileService = renameEpisodeFileService; - - GetResourceAll = GetEpisodes; - } - - private List GetEpisodes() - { - if (!Request.Query.SeriesId.HasValue) - { - throw new BadRequestException("seriesId is missing"); - } - - var seriesId = (int)Request.Query.SeriesId; - - if (Request.Query.SeasonNumber.HasValue) - { - var seasonNumber = (int)Request.Query.SeasonNumber; - return _renameEpisodeFileService.GetRenamePreviews(seriesId, seasonNumber).ToResource(); - } - - return _renameEpisodeFileService.GetRenamePreviews(seriesId).ToResource(); - } - } -} diff --git a/src/NzbDrone.Api/Episodes/RenameEpisodeResource.cs b/src/NzbDrone.Api/Episodes/RenameEpisodeResource.cs deleted file mode 100644 index c48f2cdf4..000000000 --- a/src/NzbDrone.Api/Episodes/RenameEpisodeResource.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using NzbDrone.Api.REST; - -namespace NzbDrone.Api.Episodes -{ - public class RenameEpisodeResource : RestResource - { - public int SeriesId { get; set; } - public int SeasonNumber { get; set; } - public List EpisodeNumbers { get; set; } - public int EpisodeFileId { get; set; } - public string ExistingPath { get; set; } - public string NewPath { get; set; } - } - - public static class RenameEpisodeResourceMapper - { - public static RenameEpisodeResource ToResource(this Core.MediaFiles.RenameEpisodeFilePreview model) - { - if (model == null) return null; - - return new RenameEpisodeResource - { - SeriesId = model.SeriesId, - SeasonNumber = model.SeasonNumber, - EpisodeNumbers = model.EpisodeNumbers.ToList(), - EpisodeFileId = model.EpisodeFileId, - ExistingPath = model.ExistingPath, - NewPath = model.NewPath - }; - } - - public static List ToResource(this IEnumerable models) - { - return models.Select(ToResource).ToList(); - } - } -} diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 0cd539e45..47bed9e5e 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -164,8 +164,6 @@ - - diff --git a/src/NzbDrone.Common/Extensions/StringExtensions.cs b/src/NzbDrone.Common/Extensions/StringExtensions.cs index 247274e29..9784794b8 100644 --- a/src/NzbDrone.Common/Extensions/StringExtensions.cs +++ b/src/NzbDrone.Common/Extensions/StringExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Linq; using System.Text; @@ -78,6 +78,16 @@ namespace NzbDrone.Common.Extensions return !string.IsNullOrWhiteSpace(text); } + public static bool StartsWithIgnoreCase(this string text, string startsWith) + { + return text.StartsWith(startsWith, StringComparison.InvariantCultureIgnoreCase); + } + + public static bool EqualsIgnoreCase(this string text, string equals) + { + return text.Equals(equals, StringComparison.InvariantCultureIgnoreCase); + } + public static bool ContainsIgnoreCase(this string text, string contains) { return text.IndexOf(contains, StringComparison.InvariantCultureIgnoreCase) > -1; diff --git a/src/NzbDrone.Common/Instrumentation/Extensions/LoggerProgressExtensions.cs b/src/NzbDrone.Common/Instrumentation/Extensions/LoggerExtensions.cs similarity index 98% rename from src/NzbDrone.Common/Instrumentation/Extensions/LoggerProgressExtensions.cs rename to src/NzbDrone.Common/Instrumentation/Extensions/LoggerExtensions.cs index 5abeeb6ba..0f4773bd3 100644 --- a/src/NzbDrone.Common/Instrumentation/Extensions/LoggerProgressExtensions.cs +++ b/src/NzbDrone.Common/Instrumentation/Extensions/LoggerExtensions.cs @@ -1,4 +1,4 @@ -using NLog; +using NLog; namespace NzbDrone.Common.Instrumentation.Extensions { diff --git a/src/NzbDrone.Common/Instrumentation/Extensions/SentryLoggerExtensions.cs b/src/NzbDrone.Common/Instrumentation/Extensions/SentryLoggerExtensions.cs new file mode 100644 index 000000000..d3d462b9d --- /dev/null +++ b/src/NzbDrone.Common/Instrumentation/Extensions/SentryLoggerExtensions.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NLog; +using NLog.Fluent; + +namespace NzbDrone.Common.Instrumentation.Extensions +{ + public static class SentryLoggerExtensions + { + public static readonly Logger SentryLogger = LogManager.GetLogger("Sentry"); + + public static LogBuilder SentryFingerprint(this LogBuilder logBuilder, params string[] fingerprint) + { + return logBuilder.Property("Sentry", fingerprint); + } + + public static LogBuilder WriteSentryDebug(this LogBuilder logBuilder, params string[] fingerprint) + { + return LogSentryMessage(logBuilder, LogLevel.Debug, fingerprint); + } + + public static LogBuilder WriteSentryInfo(this LogBuilder logBuilder, params string[] fingerprint) + { + return LogSentryMessage(logBuilder, LogLevel.Info, fingerprint); + } + + public static LogBuilder WriteSentryWarn(this LogBuilder logBuilder, params string[] fingerprint) + { + return LogSentryMessage(logBuilder, LogLevel.Warn, fingerprint); + } + + public static LogBuilder WriteSentryError(this LogBuilder logBuilder, params string[] fingerprint) + { + return LogSentryMessage(logBuilder, LogLevel.Error, fingerprint); + } + + private static LogBuilder LogSentryMessage(LogBuilder logBuilder, LogLevel level, string[] fingerprint) + { + SentryLogger.Log(level) + .CopyLogEvent(logBuilder.LogEventInfo) + .SentryFingerprint(fingerprint) + .Write(); + + return logBuilder.Property("Sentry", null); + } + + private static LogBuilder CopyLogEvent(this LogBuilder logBuilder, LogEventInfo logEvent) + { + return logBuilder.LoggerName(logEvent.LoggerName) + .TimeStamp(logEvent.TimeStamp) + .Message(logEvent.Message, logEvent.Parameters) + .Properties((Dictionary)logEvent.Properties) + .Exception(logEvent.Exception); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs index f808740db..b9c94be9b 100644 --- a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs +++ b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.IO; using System.Linq; @@ -89,32 +89,38 @@ namespace NzbDrone.Common.Instrumentation private static void RegisterSentry(bool updateClient) { - // string dsn; - - // if (updateClient) - // { - // dsn = RuntimeInfo.IsProduction - // ? "https://b85aa82c65b84b0e99e3b7c281438357:392b5bc007974147a922c5d841c47cf9@sentry.lidarr.audio/11" - // : "https://6168f0946aba4e60ac23e469ac08eac5:bd59e8454ccc454ea27a90cff1f814ca@sentry.lidarr.audio/9"; - - // } - // else - // { - // dsn = RuntimeInfo.IsProduction - // ? "https://3e8a38b1a4df4de8b0453a724f5a1139:5a708dd75c724b32ae5128b6a895650f@sentry.lidarr.audio/8" - // : "https://4ee3580e01d8407c96a7430fbc953512:5f2d07227a0b4fde99dea07041a3ff93@sentry.lidarr.audio/10"; - // } - - // var target = new SentryTarget(dsn) - // { - // Name = "sentryTarget", - // Layout = "${message}" - // }; - - // var loggingRule = new LoggingRule("*", updateClient ? LogLevel.Trace : LogLevel.Error, target); - // LogManager.Configuration.AddTarget("sentryTarget", target); - // LogManager.Configuration.LoggingRules.Add(loggingRule); - } + // TODO Enable above when we recieve sentry service account. + + string dsn; + + if (updateClient) + { + dsn = RuntimeInfo.IsProduction + ? "https://b85aa82c65b84b0e99e3b7c281438357:392b5bc007974147a922c5d841c47cf9@sentry.lidarr.audio/11" + : "https://6168f0946aba4e60ac23e469ac08eac5:bd59e8454ccc454ea27a90cff1f814ca@sentry.lidarr.audio/9"; + + } + else + { + dsn = RuntimeInfo.IsProduction + ? "https://3e8a38b1a4df4de8b0453a724f5a1139:5a708dd75c724b32ae5128b6a895650f@sentry.lidarr.audio/8" + : "https://4ee3580e01d8407c96a7430fbc953512:5f2d07227a0b4fde99dea07041a3ff93@sentry.lidarr.audio/10"; + } + + var target = new SentryTarget(dsn) + { + Name = "sentryTarget", + Layout = "${message}" + }; + + var loggingRule = new LoggingRule("*", updateClient ? LogLevel.Trace : LogLevel.Warn, target); + LogManager.Configuration.AddTarget("sentryTarget", target); + LogManager.Configuration.LoggingRules.Add(loggingRule); + + // Events logged to Sentry go only to Sentry. + var loggingRuleSentry = new LoggingRule("Sentry", LogLevel.Debug, target) { Final = true }; + LogManager.Configuration.LoggingRules.Insert(0, loggingRuleSentry); + } private static void RegisterDebugger() { diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs index b9062134d..7a98622e0 100644 --- a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs +++ b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net; @@ -71,6 +72,11 @@ namespace NzbDrone.Common.Instrumentation.Sentry private static List GetFingerPrint(LogEventInfo logEvent) { + if (logEvent.Properties.ContainsKey("Sentry")) + { + return ((string[])logEvent.Properties["Sentry"]).ToList(); + } + var fingerPrint = new List { logEvent.Level.Ordinal.ToString(), @@ -94,13 +100,33 @@ namespace NzbDrone.Common.Instrumentation.Sentry return fingerPrint; } + private bool IsSentryMessage(LogEventInfo logEvent) + { + if (logEvent.Properties.ContainsKey("Sentry")) + { + return logEvent.Properties["Sentry"] != null; + } + + if (logEvent.Level >= LogLevel.Error && logEvent.Exception != null) + { + return true; + } + + return false; + } + protected override void Write(LogEventInfo logEvent) { + if (_unauthorized) + { + return; + } + try { // don't report non-critical events without exceptions - if (logEvent.Exception == null || _unauthorized) + if (!IsSentryMessage(logEvent)) { return; } @@ -112,8 +138,16 @@ namespace NzbDrone.Common.Instrumentation.Sentry } var extras = logEvent.Properties.ToDictionary(x => x.Key.ToString(), x => x.Value.ToString()); + extras.Remove("Sentry"); _client.Logger = logEvent.LoggerName; + if (logEvent.Exception != null) + { + foreach (DictionaryEntry data in logEvent.Exception.Data) + { + extras.Add(data.Key.ToString(), data.Value.ToString()); + } + } var sentryMessage = new SentryMessage(logEvent.Message, logEvent.Parameters); @@ -135,11 +169,16 @@ namespace NzbDrone.Common.Instrumentation.Sentry sentryEvent.Fingerprint.Add(logEvent.Exception.GetType().FullName); } + if (logEvent.Properties.ContainsKey("Sentry")) + { + sentryEvent.Fingerprint.Clear(); + Array.ForEach((string[])logEvent.Properties["Sentry"], sentryEvent.Fingerprint.Add); + } + var osName = Environment.GetEnvironmentVariable("OS_NAME"); var osVersion = Environment.GetEnvironmentVariable("OS_VERSION"); var runTimeVersion = Environment.GetEnvironmentVariable("RUNTIME_VERSION"); - sentryEvent.Tags.Add("os_name", osName); sentryEvent.Tags.Add("os_version", $"{osName} {osVersion}"); sentryEvent.Tags.Add("runtime_version", $"{PlatformInfo.PlatformName} {runTimeVersion}"); @@ -152,4 +191,4 @@ namespace NzbDrone.Common.Instrumentation.Sentry } } } -} +} \ No newline at end of file diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index 34ee38755..874650375 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -175,7 +175,8 @@ - + + diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeFileMovingServiceTests/MoveEpisodeFileFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeFileMovingServiceTests/MoveEpisodeFileFixture.cs deleted file mode 100644 index 595a19dd4..000000000 --- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeFileMovingServiceTests/MoveEpisodeFileFixture.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using FizzWare.NBuilder; -using Moq; -using NUnit.Framework; -using NzbDrone.Common.Disk; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.MediaFiles; -using NzbDrone.Core.MediaFiles.Events; -using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.Organizer; -using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; -using NzbDrone.Test.Common; - -namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests -{ - [TestFixture] - public class MoveEpisodeFileFixture : CoreTest - { - private Series _series; - private EpisodeFile _episodeFile; - private LocalEpisode _localEpisode; - - [SetUp] - public void Setup() - { - _series = Builder.CreateNew() - .With(s => s.Path = @"C:\Test\TV\Series".AsOsAgnostic()) - .Build(); - - _episodeFile = Builder.CreateNew() - .With(f => f.Path = null) - .With(f => f.RelativePath = @"Season 1\File.avi") - .Build(); - - _localEpisode = Builder.CreateNew() - .With(l => l.Series = _series) - .With(l => l.Episodes = Builder.CreateListOfSize(1).Build().ToList()) - .Build(); - - Mocker.GetMock() - .Setup(s => s.BuildFileName(It.IsAny>(), It.IsAny(), It.IsAny(), null)) - .Returns("File Name"); - - Mocker.GetMock() - .Setup(s => s.BuildFilePath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(@"C:\Test\TV\Series\Season 01\File Name.avi".AsOsAgnostic()); - - Mocker.GetMock() - .Setup(s => s.BuildSeasonPath(It.IsAny(), It.IsAny())) - .Returns(@"C:\Test\TV\Series\Season 01".AsOsAgnostic()); - - var rootFolder = @"C:\Test\TV\".AsOsAgnostic(); - Mocker.GetMock() - .Setup(s => s.FolderExists(rootFolder)) - .Returns(true); - - Mocker.GetMock() - .Setup(s => s.FileExists(It.IsAny())) - .Returns(true); - } - - [Test] - public void should_catch_UnauthorizedAccessException_during_folder_inheritance() - { - WindowsOnly(); - - Mocker.GetMock() - .Setup(s => s.InheritFolderPermissions(It.IsAny())) - .Throws(); - - Subject.MoveEpisodeFile(_episodeFile, _localEpisode); - } - - [Test] - public void should_catch_InvalidOperationException_during_folder_inheritance() - { - WindowsOnly(); - - Mocker.GetMock() - .Setup(s => s.InheritFolderPermissions(It.IsAny())) - .Throws(); - - Subject.MoveEpisodeFile(_episodeFile, _localEpisode); - } - - [Test] - public void should_notify_on_series_folder_creation() - { - Subject.MoveEpisodeFile(_episodeFile, _localEpisode); - - Mocker.GetMock() - .Verify(s => s.PublishEvent(It.Is(p => - p.SeriesFolder.IsNotNullOrWhiteSpace())), Times.Once()); - } - - [Test] - public void should_notify_on_season_folder_creation() - { - Subject.MoveEpisodeFile(_episodeFile, _localEpisode); - - Mocker.GetMock() - .Verify(s => s.PublishEvent(It.Is(p => - p.SeasonFolder.IsNotNullOrWhiteSpace())), Times.Once()); - } - - [Test] - public void should_not_notify_if_series_folder_already_exists() - { - Mocker.GetMock() - .Setup(s => s.FolderExists(_series.Path)) - .Returns(true); - - Subject.MoveEpisodeFile(_episodeFile, _localEpisode); - - Mocker.GetMock() - .Verify(s => s.PublishEvent(It.Is(p => - p.SeriesFolder.IsNotNullOrWhiteSpace())), Times.Never()); - } - } -} diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/FormattedAudioChannelsFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/FormattedAudioChannelsFixture.cs deleted file mode 100644 index c344c0906..000000000 --- a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/FormattedAudioChannelsFixture.cs +++ /dev/null @@ -1,120 +0,0 @@ -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.MediaFiles.MediaInfo; - -namespace NzbDrone.Core.Test.MediaFiles.MediaInfo -{ - [TestFixture] - public class FormattedAudioChannelsFixture - { - [Test] - public void should_subtract_one_from_AudioChannels_as_total_channels_if_LFE_in_AudioChannelPositionsText() - { - var mediaInfoModel = new MediaInfoModel - { - AudioChannels = 6, - AudioChannelPositions = null, - AudioChannelPositionsText = "Front: L C R, Side: L R, LFE" - }; - - mediaInfoModel.FormattedAudioChannels.Should().Be(5.1m); - } - - [Test] - public void should_use_AudioChannels_as_total_channels_if_LFE_not_in_AudioChannelPositionsText() - { - var mediaInfoModel = new MediaInfoModel - { - AudioChannels = 2, - AudioChannelPositions = null, - AudioChannelPositionsText = "Front: L R" - }; - - mediaInfoModel.FormattedAudioChannels.Should().Be(2); - } - - [Test] - public void should_return_0_if_schema_revision_is_less_than_3_and_other_properties_are_null() - { - var mediaInfoModel = new MediaInfoModel - { - AudioChannels = 2, - AudioChannelPositions = null, - AudioChannelPositionsText = null, - SchemaRevision = 2 - }; - - mediaInfoModel.FormattedAudioChannels.Should().Be(0); - } - - [Test] - public void should_use_AudioChannels_if_schema_revision_is_3_and_other_properties_are_null() - { - var mediaInfoModel = new MediaInfoModel - { - AudioChannels = 2, - AudioChannelPositions = null, - AudioChannelPositionsText = null, - SchemaRevision = 3 - }; - - mediaInfoModel.FormattedAudioChannels.Should().Be(2); - } - - [Test] - public void should_sum_AudioChannelPositions() - { - var mediaInfoModel = new MediaInfoModel - { - AudioChannels = 2, - AudioChannelPositions = "2/0/0", - AudioChannelPositionsText = null, - SchemaRevision = 3 - }; - - mediaInfoModel.FormattedAudioChannels.Should().Be(2); - } - - [Test] - public void should_sum_AudioChannelPositions_including_decimal() - { - var mediaInfoModel = new MediaInfoModel - { - AudioChannels = 2, - AudioChannelPositions = "3/2/0.1", - AudioChannelPositionsText = null, - SchemaRevision = 3 - }; - - mediaInfoModel.FormattedAudioChannels.Should().Be(5.1m); - } - - [Test] - public void should_cleanup_extraneous_text_from_AudioChannelPositions() - { - var mediaInfoModel = new MediaInfoModel - { - AudioChannels = 2, - AudioChannelPositions = "Object Based / 3/2/2.1", - AudioChannelPositionsText = null, - SchemaRevision = 3 - }; - - mediaInfoModel.FormattedAudioChannels.Should().Be(7.1m); - } - - [Test] - public void should_sum_first_series_of_numbers_from_AudioChannelPositions() - { - var mediaInfoModel = new MediaInfoModel - { - AudioChannels = 2, - AudioChannelPositions = "3/2/2.1 / 3/2/2.1", - AudioChannelPositionsText = null, - SchemaRevision = 3 - }; - - mediaInfoModel.FormattedAudioChannels.Should().Be(7.1m); - } - } -} diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioCodecFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioCodecFixture.cs new file mode 100644 index 000000000..2e7e1d227 --- /dev/null +++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/MediaInfoFormatterTests/FormatAudioCodecFixture.cs @@ -0,0 +1,50 @@ +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.MediaFiles.MediaInfo; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests +{ + [TestFixture] + public class FormatAudioCodecFixture : TestBase + { + [TestCase("AC-3", "AC3")] + [TestCase("E-AC-3", "EAC3")] + [TestCase("MPEG Audio", "MPEG Audio")] + [TestCase("DTS", "DTS")] + public void should_format_audio_format(string audioFormat, string expectedFormat) + { + var mediaInfoModel = new MediaInfoModel + { + AudioFormat = audioFormat + }; + + MediaInfoFormatter.FormatAudioCodec(mediaInfoModel).Should().Be(expectedFormat); + } + + [Test] + public void should_return_MP3_for_MPEG_Audio_with_Layer_3_for_the_profile() + { + var mediaInfoModel = new MediaInfoModel + { + AudioFormat = "MPEG Audio", + AudioProfile = "Layer 3" + }; + + MediaInfoFormatter.FormatAudioCodec(mediaInfoModel).Should().Be("MP3"); + } + + [Test] + public void should_return_AudioFormat_by_default() + { + var mediaInfoModel = new MediaInfoModel + { + AudioFormat = "Other Audio Format", + AudioCodecID = "Other Audio Codec" + }; + + MediaInfoFormatter.FormatAudioCodec(mediaInfoModel).Should().Be(mediaInfoModel.AudioFormat); + ExceptionVerification.ExpectedWarns(1); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs index e48026475..0ca268907 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MediaInfo/UpdateMediaInfoServiceFixture.cs @@ -7,7 +7,6 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; using NzbDrone.Core.Music; using NzbDrone.Test.Common; using NzbDrone.Core.Configuration; @@ -23,10 +22,10 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo public void Setup() { _artist = new Artist - { - Id = 1, - Path = @"C:\artist".AsOsAgnostic() - }; + { + Id = 1, + Path = @"C:\artist".AsOsAgnostic() + }; Mocker.GetMock() .SetupGet(s => s.EnableMediaInfo) @@ -59,9 +58,9 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo { var trackFiles = Builder.CreateListOfSize(3) .All() - .With(v => v.RelativePath = "media.mkv") + .With(v => v.RelativePath = "media.flac") .TheFirst(1) - .With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = 3 }) + .With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = UpdateMediaInfoService.CURRENT_MEDIA_INFO_SCHEMA_REVISION }) .BuildList(); Mocker.GetMock() @@ -74,7 +73,33 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo Subject.Handle(new ArtistScannedEvent(_artist)); Mocker.GetMock() - .Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.mkv")), Times.Exactly(2)); + .Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.flac")), Times.Exactly(2)); + + Mocker.GetMock() + .Verify(v => v.Update(It.IsAny()), Times.Exactly(2)); + } + + [Test] + public void should_skip_not_yet_date_media_info() + { + var trackFiles = Builder.CreateListOfSize(3) + .All() + .With(v => v.RelativePath = "media.flac") + .TheFirst(1) + .With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = UpdateMediaInfoService.MINIMUM_MEDIA_INFO_SCHEMA_REVISION }) + .BuildList(); + + Mocker.GetMock() + .Setup(v => v.GetFilesByArtist(1)) + .Returns(trackFiles); + + GivenFileExists(); + GivenSuccessfulScan(); + + Subject.Handle(new ArtistScannedEvent(_artist)); + + Mocker.GetMock() + .Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.flac")), Times.Exactly(2)); Mocker.GetMock() .Verify(v => v.Update(It.IsAny()), Times.Exactly(2)); @@ -85,7 +110,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo { var trackFiles = Builder.CreateListOfSize(3) .All() - .With(v => v.RelativePath = "media.mkv") + .With(v => v.RelativePath = "media.flac") .TheFirst(1) .With(v => v.MediaInfo = new MediaInfoModel()) .BuildList(); @@ -100,7 +125,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo Subject.Handle(new ArtistScannedEvent(_artist)); Mocker.GetMock() - .Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.mkv")), Times.Exactly(3)); + .Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.flac")), Times.Exactly(3)); Mocker.GetMock() .Verify(v => v.Update(It.IsAny()), Times.Exactly(3)); @@ -111,7 +136,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo { var trackFiles = Builder.CreateListOfSize(2) .All() - .With(v => v.RelativePath = "media.mkv") + .With(v => v.RelativePath = "media.flac") .BuildList(); Mocker.GetMock() @@ -123,7 +148,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo Subject.Handle(new ArtistScannedEvent(_artist)); Mocker.GetMock() - .Verify(v => v.GetMediaInfo("media.mkv"), Times.Never()); + .Verify(v => v.GetMediaInfo("media.flac"), Times.Never()); Mocker.GetMock() .Verify(v => v.Update(It.IsAny()), Times.Never()); @@ -132,28 +157,28 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo [Test] public void should_continue_after_failure() { - var trackFiles = Builder.CreateListOfSize(2) + var episodeFiles = Builder.CreateListOfSize(2) .All() - .With(v => v.RelativePath = "media.mkv") + .With(v => v.RelativePath = "media.flac") .TheFirst(1) - .With(v => v.RelativePath = "media2.mkv") + .With(v => v.RelativePath = "media2.flac") .BuildList(); Mocker.GetMock() .Setup(v => v.GetFilesByArtist(1)) - .Returns(trackFiles); + .Returns(episodeFiles); GivenFileExists(); GivenSuccessfulScan(); - GivenFailedScan(Path.Combine(_artist.Path, "media2.mkv")); + GivenFailedScan(Path.Combine(_artist.Path, "media2.flac")); Subject.Handle(new ArtistScannedEvent(_artist)); Mocker.GetMock() - .Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.mkv")), Times.Exactly(1)); + .Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.flac")), Times.Exactly(1)); Mocker.GetMock() .Verify(v => v.Update(It.IsAny()), Times.Exactly(1)); } } -} +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/MediaFiles/RenameEpisodeFileServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/RenameTrackFileServiceFixture.cs similarity index 84% rename from src/NzbDrone.Core.Test/MediaFiles/RenameEpisodeFileServiceFixture.cs rename to src/NzbDrone.Core.Test/MediaFiles/RenameTrackFileServiceFixture.cs index 6d6deabc5..650d1f5a2 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/RenameEpisodeFileServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/RenameTrackFileServiceFixture.cs @@ -8,12 +8,11 @@ using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; using NzbDrone.Core.Music; namespace NzbDrone.Core.Test.MediaFiles { - public class RenameEpisodeFileServiceFixture : CoreTest + public class RenameTrackFileServiceFixture : CoreTest { private Artist _artist; private List _trackFiles; @@ -35,14 +34,14 @@ namespace NzbDrone.Core.Test.MediaFiles .Returns(_artist); } - private void GivenNoEpisodeFiles() + private void GivenNoTrackFiles() { Mocker.GetMock() .Setup(s => s.Get(It.IsAny>())) .Returns(new List()); } - private void GivenEpisodeFiles() + private void GivenTrackFiles() { Mocker.GetMock() .Setup(s => s.Get(It.IsAny>())) @@ -58,18 +57,18 @@ namespace NzbDrone.Core.Test.MediaFiles [Test] public void should_not_publish_event_if_no_files_to_rename() { - GivenNoEpisodeFiles(); + GivenNoTrackFiles(); Subject.Execute(new RenameFilesCommand(_artist.Id, new List{1})); Mocker.GetMock() - .Verify(v => v.PublishEvent(It.IsAny()), Times.Never()); + .Verify(v => v.PublishEvent(It.IsAny()), Times.Never()); } [Test] public void should_not_publish_event_if_no_files_are_renamed() { - GivenEpisodeFiles(); + GivenTrackFiles(); Mocker.GetMock() .Setup(s => s.MoveTrackFile(It.IsAny(), It.IsAny())) @@ -78,25 +77,25 @@ namespace NzbDrone.Core.Test.MediaFiles Subject.Execute(new RenameFilesCommand(_artist.Id, new List { 1 })); Mocker.GetMock() - .Verify(v => v.PublishEvent(It.IsAny()), Times.Never()); + .Verify(v => v.PublishEvent(It.IsAny()), Times.Never()); } [Test] public void should_publish_event_if_files_are_renamed() { - GivenEpisodeFiles(); + GivenTrackFiles(); GivenMovedFiles(); Subject.Execute(new RenameFilesCommand(_artist.Id, new List { 1 })); Mocker.GetMock() - .Verify(v => v.PublishEvent(It.IsAny()), Times.Once()); + .Verify(v => v.PublishEvent(It.IsAny()), Times.Once()); } [Test] public void should_update_moved_files() { - GivenEpisodeFiles(); + GivenTrackFiles(); GivenMovedFiles(); Subject.Execute(new RenameFilesCommand(_artist.Id, new List { 1 })); @@ -106,9 +105,9 @@ namespace NzbDrone.Core.Test.MediaFiles } [Test] - public void should_get_episodefiles_by_ids_only() + public void should_get_trackfiles_by_ids_only() { - GivenEpisodeFiles(); + GivenTrackFiles(); GivenMovedFiles(); var files = new List { 1 }; diff --git a/src/NzbDrone.Core.Test/MediaFiles/TrackFileMovingServiceTests/MoveTrackFileFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/TrackFileMovingServiceTests/MoveTrackFileFixture.cs new file mode 100644 index 000000000..df5f09e6b --- /dev/null +++ b/src/NzbDrone.Core.Test/MediaFiles/TrackFileMovingServiceTests/MoveTrackFileFixture.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Disk; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Music; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.MediaFiles.TrackFileMovingServiceTests +{ + [TestFixture] + public class MoveTrackFileFixture : CoreTest + { + private Artist _artist; + private TrackFile _trackFile; + private LocalTrack _localtrack; + + [SetUp] + public void Setup() + { + _artist = Builder.CreateNew() + .With(s => s.Path = @"C:\Test\Music\Artist".AsOsAgnostic()) + .Build(); + + _trackFile = Builder.CreateNew() + .With(f => f.Path = null) + .With(f => f.RelativePath = @"Album\File.mp3") + .Build(); + + _localtrack = Builder.CreateNew() + .With(l => l.Artist = _artist) + .With(l => l.Tracks = Builder.CreateListOfSize(1).Build().ToList()) + .Build(); + + Mocker.GetMock() + .Setup(s => s.BuildTrackFileName(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), null)) + .Returns("File Name"); + + Mocker.GetMock() + .Setup(s => s.BuildTrackFilePath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(@"C:\Test\Music\Artist\Album\File Name.mp3".AsOsAgnostic()); + + Mocker.GetMock() + .Setup(s => s.BuildAlbumPath(It.IsAny(), It.IsAny())) + .Returns(@"C:\Test\Music\Artist\Album".AsOsAgnostic()); + + var rootFolder = @"C:\Test\Music\".AsOsAgnostic(); + Mocker.GetMock() + .Setup(s => s.FolderExists(rootFolder)) + .Returns(true); + + Mocker.GetMock() + .Setup(s => s.FileExists(It.IsAny())) + .Returns(true); + } + + [Test] + public void should_catch_UnauthorizedAccessException_during_folder_inheritance() + { + WindowsOnly(); + + Mocker.GetMock() + .Setup(s => s.InheritFolderPermissions(It.IsAny())) + .Throws(); + + Subject.MoveTrackFile(_trackFile, _localtrack); + } + + [Test] + public void should_catch_InvalidOperationException_during_folder_inheritance() + { + WindowsOnly(); + + Mocker.GetMock() + .Setup(s => s.InheritFolderPermissions(It.IsAny())) + .Throws(); + + Subject.MoveTrackFile(_trackFile, _localtrack); + } + + [Test] + public void should_notify_on_artist_folder_creation() + { + Subject.MoveTrackFile(_trackFile, _localtrack); + + Mocker.GetMock() + .Verify(s => s.PublishEvent(It.Is(p => + p.ArtistFolder.IsNotNullOrWhiteSpace())), Times.Once()); + } + + [Test] + public void should_notify_on_album_folder_creation() + { + Subject.MoveTrackFile(_trackFile, _localtrack); + + Mocker.GetMock() + .Verify(s => s.PublishEvent(It.Is(p => + p.AlbumFolder.IsNotNullOrWhiteSpace())), Times.Once()); + } + + [Test] + public void should_not_notify_if_artist_folder_already_exists() + { + Mocker.GetMock() + .Setup(s => s.FolderExists(_artist.Path)) + .Returns(true); + + Subject.MoveTrackFile(_trackFile, _localtrack); + + Mocker.GetMock() + .Verify(s => s.PublishEvent(It.Is(p => + p.ArtistFolder.IsNotNullOrWhiteSpace())), Times.Never()); + } + } +} diff --git a/src/NzbDrone.Core.Test/TvTests/MoveSeriesServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/MoveArtistServiceFixture.cs similarity index 55% rename from src/NzbDrone.Core.Test/TvTests/MoveSeriesServiceFixture.cs rename to src/NzbDrone.Core.Test/MusicTests/MoveArtistServiceFixture.cs index 528816c99..d7dfa6c5a 100644 --- a/src/NzbDrone.Core.Test/TvTests/MoveSeriesServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/MoveArtistServiceFixture.cs @@ -5,35 +5,35 @@ using NUnit.Framework; using NzbDrone.Common.Disk; using NzbDrone.Core.Organizer; using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; -using NzbDrone.Core.Tv.Commands; +using NzbDrone.Core.Music; +using NzbDrone.Core.Music.Commands; using NzbDrone.Test.Common; -namespace NzbDrone.Core.Test.TvTests +namespace NzbDrone.Core.Test.MusicTests { [TestFixture] - public class MoveSeriesServiceFixture : CoreTest + public class MoveArtistServiceFixture : CoreTest { - private Series _series; - private MoveSeriesCommand _command; + private Artist _artist; + private MoveArtistCommand _command; [SetUp] public void Setup() { - _series = Builder + _artist = Builder .CreateNew() .Build(); - _command = new MoveSeriesCommand - { - SeriesId = 1, - SourcePath = @"C:\Test\TV\Series".AsOsAgnostic(), - DestinationPath = @"C:\Test\TV2\Series".AsOsAgnostic() + _command = new MoveArtistCommand + { + ArtistId = 1, + SourcePath = @"C:\Test\Music\Artist".AsOsAgnostic(), + DestinationPath = @"C:\Test\Music2\Artist".AsOsAgnostic() }; - Mocker.GetMock() - .Setup(s => s.GetSeries(It.IsAny())) - .Returns(_series); + Mocker.GetMock() + .Setup(s => s.GetArtist(It.IsAny())) + .Returns(_artist); } private void GivenFailedMove() @@ -54,7 +54,7 @@ namespace NzbDrone.Core.Test.TvTests } [Test] - public void should_no_update_series_path_on_error() + public void should_no_update_artist_path_on_error() { GivenFailedMove(); @@ -62,26 +62,26 @@ namespace NzbDrone.Core.Test.TvTests ExceptionVerification.ExpectedErrors(1); - Mocker.GetMock() - .Verify(v => v.UpdateSeries(It.IsAny()), Times.Never()); + Mocker.GetMock() + .Verify(v => v.UpdateArtist(It.IsAny()), Times.Never()); } [Test] public void should_build_new_path_when_root_folder_is_provided() { _command.DestinationPath = null; - _command.DestinationRootFolder = @"C:\Test\TV3".AsOsAgnostic(); + _command.DestinationRootFolder = @"C:\Test\Music3".AsOsAgnostic(); - var expectedPath = @"C:\Test\TV3\Series".AsOsAgnostic(); + var expectedPath = @"C:\Test\Music3\Artist".AsOsAgnostic(); Mocker.GetMock() - .Setup(s => s.GetSeriesFolder(It.IsAny(), null)) - .Returns("Series"); + .Setup(s => s.GetArtistFolder(It.IsAny(), null)) + .Returns("Artist"); Subject.Execute(_command); - Mocker.GetMock() - .Verify(v => v.UpdateSeries(It.Is(s => s.Path == expectedPath)), Times.Once()); + Mocker.GetMock() + .Verify(v => v.UpdateArtist(It.Is(s => s.Path == expectedPath)), Times.Once()); } [Test] @@ -89,11 +89,11 @@ namespace NzbDrone.Core.Test.TvTests { Subject.Execute(_command); - Mocker.GetMock() - .Verify(v => v.UpdateSeries(It.Is(s => s.Path == _command.DestinationPath)), Times.Once()); + Mocker.GetMock() + .Verify(v => v.UpdateArtist(It.Is(s => s.Path == _command.DestinationPath)), Times.Once()); Mocker.GetMock() - .Verify(v => v.GetSeriesFolder(It.IsAny(), null), Times.Never()); + .Verify(v => v.GetArtistFolder(It.IsAny(), null), Times.Never()); } } } diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index d168599c8..9f10fa6d5 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -283,7 +283,8 @@ - + + @@ -291,7 +292,6 @@ - @@ -300,8 +300,6 @@ - - @@ -312,7 +310,7 @@ - + @@ -329,9 +327,9 @@ - + - + @@ -378,7 +376,7 @@ - + diff --git a/src/NzbDrone.Core.Test/OrganizerTests/BuildFilePathFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/BuildFilePathFixture.cs index 3848659c9..0d41c5a31 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/BuildFilePathFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/BuildFilePathFixture.cs @@ -1,8 +1,8 @@ -using FizzWare.NBuilder; +using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Organizer; -using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common; @@ -24,41 +24,25 @@ namespace NzbDrone.Core.Test.OrganizerTests } [Test] - [TestCase("30 Rock - S01E05 - Episode Title", 1, true, "Season {season:00}", @"C:\Test\30 Rock\Season 01\30 Rock - S01E05 - Episode Title.mkv")] - [TestCase("30 Rock - S01E05 - Episode Title", 1, true, "Season {season}", @"C:\Test\30 Rock\Season 1\30 Rock - S01E05 - Episode Title.mkv")] - [TestCase("30 Rock - S01E05 - Episode Title", 1, false, "Season {season:00}", @"C:\Test\30 Rock\30 Rock - S01E05 - Episode Title.mkv")] - [TestCase("30 Rock - S01E05 - Episode Title", 1, false, "Season {season}", @"C:\Test\30 Rock\30 Rock - S01E05 - Episode Title.mkv")] - [TestCase("30 Rock - S01E05 - Episode Title", 1, true, "ReallyUglySeasonFolder {season}", @"C:\Test\30 Rock\ReallyUglySeasonFolder 1\30 Rock - S01E05 - Episode Title.mkv")] - [TestCase("30 Rock - S00E05 - Episode Title", 0, true, "Season {season}", @"C:\Test\30 Rock\Specials\30 Rock - S00E05 - Episode Title.mkv")] - public void CalculateFilePath_SeasonFolder_SingleNumber(string filename, int seasonNumber, bool useSeasonFolder, string seasonFolderFormat, string expectedPath) + public void should_clean_album_folder_when_it_contains_illegal_characters_in_album_or_artist_title() { - var fakeSeries = Builder.CreateNew() - .With(s => s.Title = "30 Rock") - .With(s => s.Path = @"C:\Test\30 Rock".AsOsAgnostic()) - .With(s => s.SeasonFolder = useSeasonFolder) - .Build(); - - namingConfig.SeasonFolderFormat = seasonFolderFormat; - - Subject.BuildFilePath(fakeSeries, seasonNumber, filename, ".mkv").Should().Be(expectedPath.AsOsAgnostic()); - } + var filename = @"02 - Track Title"; + var expectedPath = @"C:\Test\Fake- The Artist\Fake- The Artist Fake- Album\02 - Track Title.flac"; - [Test] - public void should_clean_season_folder_when_it_contains_illegal_characters_in_series_title() - { - var filename = @"S01E05 - Episode Title"; - var seasonNumber = 1; - var expectedPath = @"C:\Test\NCIS- Los Angeles\NCIS- Los Angeles Season 1\S01E05 - Episode Title.mkv"; + var fakeArtist = Builder.CreateNew() + .With(s => s.Name = "Fake: The Artist") + .With(s => s.Path = @"C:\Test\Fake- The Artist".AsOsAgnostic()) + .With(s => s.AlbumFolder = true) + .Build(); - var fakeSeries = Builder.CreateNew() - .With(s => s.Title = "NCIS: Los Angeles") - .With(s => s.Path = @"C:\Test\NCIS- Los Angeles".AsOsAgnostic()) - .With(s => s.SeasonFolder = true) + var fakeAlbum = Builder.CreateNew() + .With(s => s.Title = "Fake: Album") + .With(s => s.Path = @"C:\Test\Fake- The Artist\Fake- Album".AsOsAgnostic()) .Build(); - namingConfig.SeasonFolderFormat = "{Series Title} Season {season:0}"; + namingConfig.AlbumFolderFormat = "{Artist Name} {Album Title}"; - Subject.BuildFilePath(fakeSeries, seasonNumber, filename, ".mkv").Should().Be(expectedPath.AsOsAgnostic()); + Subject.BuildTrackFilePath(fakeArtist, fakeAlbum, filename, ".flac").Should().Be(expectedPath.AsOsAgnostic()); } } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleFixture.cs index 4f8f54901..a13a313e9 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleFixture.cs @@ -7,37 +7,41 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Organizer; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests { [TestFixture] public class CleanTitleFixture : CoreTest { - private Series _series; - private Episode _episode; - private EpisodeFile _episodeFile; + private Artist _artist; + private Album _album; + private Track _track; + private TrackFile _trackFile; private NamingConfig _namingConfig; [SetUp] public void Setup() { - _series = Builder + _artist = Builder .CreateNew() - .With(s => s.Title = "South Park") + .With(s => s.Name = "Avenged Sevenfold") .Build(); - _episode = Builder.CreateNew() - .With(e => e.Title = "City Sushi") - .With(e => e.SeasonNumber = 15) - .With(e => e.EpisodeNumber = 6) - .With(e => e.AbsoluteEpisodeNumber = 100) + _album = Builder + .CreateNew() + .With(s => s.Title = "Hail to the King") + .Build(); + + _track = Builder.CreateNew() + .With(e => e.Title = "Doing Time") + .With(e => e.TrackNumber = 3) .Build(); - _episodeFile = new EpisodeFile { Quality = new QualityModel(Quality.MP3_256), ReleaseGroup = "LidarrTest" }; + _trackFile = new TrackFile { Quality = new QualityModel(Quality.MP3_256), ReleaseGroup = "LidarrTest" }; _namingConfig = NamingConfig.Default; - _namingConfig.RenameEpisodes = true; + _namingConfig.RenameTracks = true; Mocker.GetMock() .Setup(c => c.GetConfig()).Returns(_namingConfig); @@ -65,20 +69,19 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests [TestCase("[a] title", "a title")] [TestCase("backslash \\ backlash", "backslash backlash")] [TestCase("I'm the Boss", "Im the Boss")] - //[TestCase("", "")] - public void should_get_expected_title_back(string title, string expected) + public void should_get_expected_title_back(string name, string expected) { - _series.Title = title; - _namingConfig.StandardEpisodeFormat = "{Series CleanTitle}"; + _artist.Name = name; + _namingConfig.StandardTrackFormat = "{Artist CleanName}"; - Subject.BuildFileName(new List { _episode }, _series, _episodeFile) + Subject.BuildTrackFileName(new List { _track }, _artist, _album, _trackFile) .Should().Be(expected); } [Test] public void should_use_and_as_separator_for_multiple_episodes() { - var episodes = Builder.CreateListOfSize(2) + var tracks = Builder.CreateListOfSize(2) .TheFirst(1) .With(e => e.Title = "Surrender Benson") .TheNext(1) @@ -86,10 +89,10 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests .Build() .ToList(); - _namingConfig.StandardEpisodeFormat = "{Episode CleanTitle}"; + _namingConfig.StandardTrackFormat = "{Track CleanTitle}"; - Subject.BuildFileName(episodes, _series, _episodeFile) - .Should().Be(episodes.First().Title + " and " + episodes.Last().Title); + Subject.BuildTrackFileName(tracks, _artist, _album, _trackFile) + .Should().Be(tracks.First().Title + " and " + tracks.Last().Title); } } } diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/EpisodeTitleCollapseFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/EpisodeTitleCollapseFixture.cs deleted file mode 100644 index ba7af7d83..000000000 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/EpisodeTitleCollapseFixture.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using FizzWare.NBuilder; -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.MediaFiles; -using NzbDrone.Core.Organizer; -using NzbDrone.Core.Qualities; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests -{ - [TestFixture] - public class EpisodeTitleCollapseFixture : CoreTest - { - private Series _series; - private Episode _episode1; - private Episode _episode2; - private Episode _episode3; - private EpisodeFile _episodeFile; - private NamingConfig _namingConfig; - - [SetUp] - public void Setup() - { - _series = Builder - .CreateNew() - .With(s => s.Title = "South Park") - .Build(); - - - _namingConfig = NamingConfig.Default; - _namingConfig.RenameEpisodes = true; - - - Mocker.GetMock() - .Setup(c => c.GetConfig()).Returns(_namingConfig); - - _episode1 = Builder.CreateNew() - .With(e => e.Title = "City Sushi") - .With(e => e.SeasonNumber = 15) - .With(e => e.EpisodeNumber = 6) - .With(e => e.AbsoluteEpisodeNumber = 100) - .Build(); - - _episode2 = Builder.CreateNew() - .With(e => e.Title = "City Sushi") - .With(e => e.SeasonNumber = 15) - .With(e => e.EpisodeNumber = 7) - .With(e => e.AbsoluteEpisodeNumber = 101) - .Build(); - - _episode3 = Builder.CreateNew() - .With(e => e.Title = "City Sushi") - .With(e => e.SeasonNumber = 15) - .With(e => e.EpisodeNumber = 8) - .With(e => e.AbsoluteEpisodeNumber = 102) - .Build(); - - _episodeFile = new EpisodeFile { Quality = new QualityModel(Quality.MP3_256), ReleaseGroup = "LidarrTest" }; - - Mocker.GetMock() - .Setup(v => v.Get(Moq.It.IsAny())) - .Returns(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v)); - } - - - [TestCase("Hey, Baby, What's Wrong (1)", "Hey, Baby, What's Wrong (2)", "Hey, Baby, What's Wrong")] - [TestCase("Meet the Guys and Girls of Cycle 20 Part 1", "Meet the Guys and Girls of Cycle 20 Part 2", "Meet the Guys and Girls of Cycle 20")] - [TestCase("Meet the Guys and Girls of Cycle 20 Part1", "Meet the Guys and Girls of Cycle 20 Part2", "Meet the Guys and Girls of Cycle 20")] - [TestCase("Meet the Guys and Girls of Cycle 20 Part01", "Meet the Guys and Girls of Cycle 20 Part02", "Meet the Guys and Girls of Cycle 20")] - [TestCase("Meet the Guys and Girls of Cycle 20 Part 01", "Meet the Guys and Girls of Cycle 20 Part 02", "Meet the Guys and Girls of Cycle 20")] - [TestCase("Meet the Guys and Girls of Cycle 20 part 1", "Meet the Guys and Girls of Cycle 20 part 2", "Meet the Guys and Girls of Cycle 20")] - [TestCase("Meet the Guys and Girls of Cycle 20 pt 1", "Meet the Guys and Girls of Cycle 20 pt 2", "Meet the Guys and Girls of Cycle 20")] - [TestCase("Meet the Guys and Girls of Cycle 20 pt. 1", "Meet the Guys and Girls of Cycle 20 pt. 2", "Meet the Guys and Girls of Cycle 20")] - public void should_collapse_episode_titles_when_episode_titles_are_the_same(string title1, string title2, string expected) - { - _namingConfig.StandardEpisodeFormat = "{Episode Title}"; - - _episode1.Title = title1; - _episode2.Title = title2; - - Subject.BuildFileName(new List { _episode1, _episode2 }, _series, _episodeFile) - .Should().Be(expected); - } - - [Test] - public void should_not_collapse_episode_titles_when_episode_titles_are_not_the_same() - { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}"; - _namingConfig.MultiEpisodeStyle = 3; - - _episode1.Title = "Hello"; - _episode2.Title = "World"; - - Subject.BuildFileName(new List { _episode1, _episode2 }, _series, _episodeFile) - .Should().Be("South Park - S15E06-E07 - Hello + World"); - } - - [Test] - public void should_not_collaspe_when_result_is_empty() - { - _namingConfig.StandardEpisodeFormat = "{Episode Title}"; - - _episode1.Title = "Part 1"; - _episode2.Title = "Part 2"; - - Subject.BuildFileName(new List { _episode1, _episode2 }, _series, _episodeFile) - .Should().Be("Part 1 + Part 2"); - } - } -} diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs index 0d99e0ed8..fefa3eb6a 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using FizzWare.NBuilder; @@ -8,7 +8,7 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Organizer; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests { @@ -16,35 +16,39 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests public class FileNameBuilderFixture : CoreTest { - private Series _series; - private Episode _episode1; - private EpisodeFile _episodeFile; + private Artist _artist; + private Album _album; + private Track _track1; + private TrackFile _trackFile; private NamingConfig _namingConfig; [SetUp] public void Setup() { - _series = Builder + _artist = Builder .CreateNew() - .With(s => s.Title = "South Park") + .With(s => s.Name = "Linkin Park") + .Build(); + + _album = Builder + .CreateNew() + .With(s => s.Title = "Hybrid Theory") .Build(); _namingConfig = NamingConfig.Default; - _namingConfig.RenameEpisodes = true; + _namingConfig.RenameTracks = true; Mocker.GetMock() .Setup(c => c.GetConfig()).Returns(_namingConfig); - _episode1 = Builder.CreateNew() + _track1 = Builder.CreateNew() .With(e => e.Title = "City Sushi") - .With(e => e.SeasonNumber = 15) - .With(e => e.EpisodeNumber = 6) - .With(e => e.AbsoluteEpisodeNumber = 100) + .With(e => e.TrackNumber = 6) .Build(); - _episodeFile = new EpisodeFile { Quality = new QualityModel(Quality.MP3_256), ReleaseGroup = "LidarrTest" }; + _trackFile = new TrackFile { Quality = new QualityModel(Quality.MP3_256), ReleaseGroup = "LidarrTest" }; Mocker.GetMock() .Setup(v => v.Get(Moq.It.IsAny())) @@ -53,592 +57,413 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests private void GivenProper() { - _episodeFile.Quality.Revision.Version = 2; + _trackFile.Quality.Revision.Version = 2; } private void GivenReal() { - _episodeFile.Quality.Revision.Real = 1; + _trackFile.Quality.Revision.Real = 1; } [Test] - public void should_replace_Series_space_Title() + public void should_replace_Artist_space_Name() { - _namingConfig.StandardEpisodeFormat = "{Series Title}"; + _namingConfig.StandardTrackFormat = "{Artist Name}"; - Subject.BuildFileName(new List {_episode1}, _series, _episodeFile) - .Should().Be("South Park"); + Subject.BuildTrackFileName(new List {_track1}, _artist, _album, _trackFile) + .Should().Be("Linkin Park"); } [Test] - public void should_replace_Series_underscore_Title() + public void should_replace_Artist_underscore_Name() { - _namingConfig.StandardEpisodeFormat = "{Series_Title}"; + _namingConfig.StandardTrackFormat = "{Artist_Name}"; - Subject.BuildFileName(new List {_episode1}, _series, _episodeFile) - .Should().Be("South_Park"); + Subject.BuildTrackFileName(new List {_track1}, _artist, _album, _trackFile) + .Should().Be("Linkin_Park"); } [Test] - public void should_replace_Series_dot_Title() + public void should_replace_Artist_dot_Name() { - _namingConfig.StandardEpisodeFormat = "{Series.Title}"; + _namingConfig.StandardTrackFormat = "{Artist.Name}"; - Subject.BuildFileName(new List {_episode1}, _series, _episodeFile) - .Should().Be("South.Park"); + Subject.BuildTrackFileName(new List {_track1}, _artist, _album, _trackFile) + .Should().Be("Linkin.Park"); } [Test] - public void should_replace_Series_dash_Title() + public void should_replace_Artist_dash_Name() { - _namingConfig.StandardEpisodeFormat = "{Series-Title}"; + _namingConfig.StandardTrackFormat = "{Artist-Name}"; - Subject.BuildFileName(new List {_episode1}, _series, _episodeFile) - .Should().Be("South-Park"); + Subject.BuildTrackFileName(new List {_track1}, _artist, _album, _trackFile) + .Should().Be("Linkin-Park"); } [Test] - public void should_replace_SERIES_TITLE_with_all_caps() + public void should_replace_ARTIST_NAME_with_all_caps() { - _namingConfig.StandardEpisodeFormat = "{SERIES TITLE}"; + _namingConfig.StandardTrackFormat = "{ARTIST NAME}"; - Subject.BuildFileName(new List {_episode1}, _series, _episodeFile) - .Should().Be("SOUTH PARK"); + Subject.BuildTrackFileName(new List {_track1}, _artist, _album, _trackFile) + .Should().Be("LINKIN PARK"); } [Test] - public void should_replace_SERIES_TITLE_with_random_casing_should_keep_original_casing() + public void should_replace_ARTIST_NAME_with_random_casing_should_keep_original_casing() { - _namingConfig.StandardEpisodeFormat = "{sErIES-tItLE}"; + _namingConfig.StandardTrackFormat = "{aRtIST-nAmE}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be(_series.Title.Replace(' ', '-')); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be(_artist.Name.Replace(' ', '-')); } [Test] - public void should_replace_series_title_with_all_lower_case() + public void should_replace_artist_name_with_all_lower_case() { - _namingConfig.StandardEpisodeFormat = "{series title}"; + _namingConfig.StandardTrackFormat = "{artist name}"; - Subject.BuildFileName(new List {_episode1}, _series, _episodeFile) - .Should().Be("south park"); + Subject.BuildTrackFileName(new List {_track1}, _artist, _album, _trackFile) + .Should().Be("linkin park"); } [Test] - public void should_cleanup_Series_Title() + public void should_cleanup_Artist_Name() { - _namingConfig.StandardEpisodeFormat = "{Series.CleanTitle}"; - _series.Title = "South Park (1997)"; + _namingConfig.StandardTrackFormat = "{Artist.CleanName}"; + _artist.Name = "Linkin Park (1997)"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South.Park.1997"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("Linkin.Park.1997"); } [Test] - public void should_replace_episode_title() + public void should_replace_Album_space_Title() { - _namingConfig.StandardEpisodeFormat = "{Episode Title}"; + _namingConfig.StandardTrackFormat = "{Album Title}"; - Subject.BuildFileName(new List {_episode1}, _series, _episodeFile) - .Should().Be("City Sushi"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("Hybrid Theory"); } [Test] - public void should_replace_episode_title_if_pattern_has_random_casing() + public void should_replace_Album_underscore_Title() { - _namingConfig.StandardEpisodeFormat = "{ePisOde-TitLe}"; + _namingConfig.StandardTrackFormat = "{Album_Title}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("City-Sushi"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("Hybrid_Theory"); } [Test] - public void should_replace_season_number_with_single_digit() + public void should_replace_Album_dot_Title() { - _episode1.SeasonNumber = 1; - _namingConfig.StandardEpisodeFormat = "{season}x{episode}"; + _namingConfig.StandardTrackFormat = "{Album.Title}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("1x6"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("Hybrid.Theory"); } [Test] - public void should_replace_season00_number_with_two_digits() + public void should_replace_Album_dash_Title() { - _episode1.SeasonNumber = 1; - _namingConfig.StandardEpisodeFormat = "{season:00}x{episode}"; + _namingConfig.StandardTrackFormat = "{Album-Title}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("01x6"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("Hybrid-Theory"); } [Test] - public void should_replace_episode_number_with_single_digit() + public void should_replace_ALBUM_TITLE_with_all_caps() { - _episode1.SeasonNumber = 1; - _namingConfig.StandardEpisodeFormat = "{season}x{episode}"; + _namingConfig.StandardTrackFormat = "{ALBUM TITLE}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("1x6"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("HYBRID THEORY"); } [Test] - public void should_replace_episode00_number_with_two_digits() + public void should_replace_ALBUM_TITLE_with_random_casing_should_keep_original_casing() { - _episode1.SeasonNumber = 1; - _namingConfig.StandardEpisodeFormat = "{season}x{episode:00}"; + _namingConfig.StandardTrackFormat = "{aLbUM-tItLE}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("1x06"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be(_album.Title.Replace(' ', '-')); } [Test] - public void should_replace_quality_title() + public void should_replace_album_title_with_all_lower_case() { - _namingConfig.StandardEpisodeFormat = "{Quality Title}"; + _namingConfig.StandardTrackFormat = "{album title}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("HDTV-720p"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("hybrid theory"); } [Test] - public void should_replace_quality_proper_with_proper() + public void should_cleanup_Album_Title() { - _namingConfig.StandardEpisodeFormat = "{Quality Proper}"; - GivenProper(); + _namingConfig.StandardTrackFormat = "{Artist.CleanName}"; + _artist.Name = "Hybrid Theory (2000)"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("Proper"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("Hybrid.Theory.2000"); } [Test] - public void should_replace_quality_real_with_real() + public void should_replace_track_title() { - _namingConfig.StandardEpisodeFormat = "{Quality Real}"; - GivenReal(); + _namingConfig.StandardTrackFormat = "{Track Title}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("REAL"); + Subject.BuildTrackFileName(new List {_track1}, _artist, _album, _trackFile) + .Should().Be("City Sushi"); } [Test] - public void should_replace_all_contents_in_pattern() + public void should_replace_track_title_if_pattern_has_random_casing() { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} [{Quality Title}]"; + _namingConfig.StandardTrackFormat = "{tRaCK-TitLe}"; - Subject.BuildFileName(new List {_episode1}, _series, _episodeFile) - .Should().Be("South Park - S15E06 - City Sushi [HDTV-720p]"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("City-Sushi"); } [Test] - public void use_file_name_when_sceneName_is_null() + public void should_replace_track_number_with_single_digit() { - _namingConfig.RenameEpisodes = false; - _episodeFile.RelativePath = "30 Rock - S01E01 - Test"; + _track1.TrackNumber = 1; + _namingConfig.StandardTrackFormat = "{track}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be(Path.GetFileNameWithoutExtension(_episodeFile.RelativePath)); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("1"); } [Test] - public void use_path_when_sceneName_and_relative_path_are_null() + public void should_replace_track00_number_with_two_digits() { - _namingConfig.RenameEpisodes = false; - _episodeFile.RelativePath = null; - _episodeFile.Path = @"C:\Test\Unsorted\Series - S01E01 - Test"; + _track1.TrackNumber = 1; + _namingConfig.StandardTrackFormat = "{track:00}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be(Path.GetFileNameWithoutExtension(_episodeFile.Path)); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("01"); } [Test] - public void use_file_name_when_sceneName_is_not_null() + public void should_replace_quality_title() { - _namingConfig.RenameEpisodes = false; - _episodeFile.SceneName = "30.Rock.S01E01.xvid-LOL"; - _episodeFile.RelativePath = "30 Rock - S01E01 - Test"; + _namingConfig.StandardTrackFormat = "{Quality Title}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("30.Rock.S01E01.xvid-LOL"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("MP3-256"); } [Test] - public void should_use_airDate_if_series_isDaily() + public void should_replace_all_contents_in_pattern() { - _namingConfig.DailyEpisodeFormat = "{Series Title} - {air-date} - {Episode Title}"; + _namingConfig.StandardTrackFormat = "{Artist Name} - {Album Title} - {track:00} - {Track Title} [{Quality Title}]"; - _series.Title = "The Daily Show with Jon Stewart"; - _series.SeriesType = SeriesTypes.Daily; - - _episode1.AirDate = "2012-12-13"; - _episode1.Title = "Kristen Stewart"; - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("The Daily Show with Jon Stewart - 2012-12-13 - Kristen Stewart"); + Subject.BuildTrackFileName(new List {_track1}, _artist, _album, _trackFile) + .Should().Be("Linkin Park - Hybrid Theory - 06 - City Sushi [MP3-256]"); } [Test] - public void should_set_airdate_to_unknown_if_not_available() + public void use_file_name_when_sceneName_is_null() { - _namingConfig.DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title}"; - - _series.Title = "The Daily Show with Jon Stewart"; - _series.SeriesType = SeriesTypes.Daily; + _namingConfig.RenameTracks = false; + _trackFile.RelativePath = "Linkin Park - 06 - Test"; - _episode1.AirDate = null; - _episode1.Title = "Kristen Stewart"; - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("The Daily Show with Jon Stewart - Unknown - Kristen Stewart"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be(Path.GetFileNameWithoutExtension(_trackFile.RelativePath)); } [Test] - public void should_not_clean_episode_title_if_there_is_only_one() + public void use_path_when_sceneName_and_relative_path_are_null() { - var title = "City Sushi (1)"; - _episode1.Title = title; + _namingConfig.RenameTracks = false; + _trackFile.RelativePath = null; + _trackFile.Path = @"C:\Test\Unsorted\Artist - 01 - Test"; - _namingConfig.StandardEpisodeFormat = "{Episode Title}"; - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be(title); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be(Path.GetFileNameWithoutExtension(_trackFile.Path)); } - [Test] - public void should_should_replace_release_group() - { - _namingConfig.StandardEpisodeFormat = "{Release Group}"; - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be(_episodeFile.ReleaseGroup); - } [Test] - public void should_be_able_to_use_original_title() + public void should_not_clean_track_title_if_there_is_only_one() { - _series.Title = "30 Rock"; - _namingConfig.StandardEpisodeFormat = "{Series Title} - {Original Title}"; + var title = "City Sushi (1)"; + _track1.Title = title; - _episodeFile.SceneName = "30.Rock.S01E01.xvid-LOL"; - _episodeFile.RelativePath = "30 Rock - S01E01 - Test"; + _namingConfig.StandardTrackFormat = "{Track Title}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("30 Rock - 30.Rock.S01E01.xvid-LOL"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be(title); } [Test] - public void should_trim_periods_from_end_of_episode_title() + public void should_should_replace_release_group() { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}"; - _namingConfig.MultiEpisodeStyle = 3; - - var episode = Builder.CreateNew() - .With(e => e.Title = "Part 1.") - .With(e => e.SeasonNumber = 6) - .With(e => e.EpisodeNumber = 6) - .Build(); - + _namingConfig.StandardTrackFormat = "{Release Group}"; - Subject.BuildFileName(new List { episode }, new Series { Title = "30 Rock" }, _episodeFile) - .Should().Be("30 Rock - S06E06 - Part 1"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be(_trackFile.ReleaseGroup); } [Test] - public void should_trim_question_marks_from_end_of_episode_title() + public void should_be_able_to_use_original_title() { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}"; - _namingConfig.MultiEpisodeStyle = 3; - - var episode = Builder.CreateNew() - .With(e => e.Title = "Part 1?") - .With(e => e.SeasonNumber = 6) - .With(e => e.EpisodeNumber = 6) - .Build(); + _artist.Name = "Linkin Park"; + _namingConfig.StandardTrackFormat = "{Artist Name} - {Original Title} - {Track Title}"; + _trackFile.SceneName = "Linkin.Park.Meteora.320-LOL"; + _trackFile.RelativePath = "30 Rock - 01 - Test"; - Subject.BuildFileName(new List { episode }, new Series { Title = "30 Rock" }, _episodeFile) - .Should().Be("30 Rock - S06E06 - Part 1"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("Linkin Park - Linkin.Park.Meteora.320-LOL - City Sushi"); } [Test] public void should_replace_double_period_with_single_period() { - _namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}"; + _namingConfig.StandardTrackFormat = "{Artist.Name}.{track:00}.{Track.Title}"; - var episode = Builder.CreateNew() + var track = Builder.CreateNew() .With(e => e.Title = "Part 1") - .With(e => e.SeasonNumber = 6) - .With(e => e.EpisodeNumber = 6) + .With(e => e.TrackNumber = 6) .Build(); - Subject.BuildFileName(new List { episode }, new Series { Title = "Chicago P.D." }, _episodeFile) - .Should().Be("Chicago.P.D.S06E06.Part.1"); + Subject.BuildTrackFileName(new List { track }, new Artist { Name = "In The Woods." }, new Album { Title = "30 Rock" }, _trackFile) + .Should().Be("In.The.Woods.06.Part.1"); } [Test] public void should_replace_triple_period_with_single_period() { - _namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}"; + _namingConfig.StandardTrackFormat = "{Artist.Name}.{track:00}.{Track.Title}"; - var episode = Builder.CreateNew() + var track = Builder.CreateNew() .With(e => e.Title = "Part 1") - .With(e => e.SeasonNumber = 6) - .With(e => e.EpisodeNumber = 6) + .With(e => e.TrackNumber = 6) .Build(); - Subject.BuildFileName(new List { episode }, new Series { Title = "Chicago P.D.." }, _episodeFile) - .Should().Be("Chicago.P.D.S06E06.Part.1"); - } - - [Test] - public void should_not_replace_absolute_numbering_when_series_is_not_anime() - { - _namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{absolute:00}.{Episode.Title}"; - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South.Park.S15E06.City.Sushi"); - } - - [Test] - public void should_replace_standard_and_absolute_numbering_when_series_is_anime() - { - _series.SeriesType = SeriesTypes.Anime; - _namingConfig.AnimeEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{absolute:00}.{Episode.Title}"; - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South.Park.S15E06.100.City.Sushi"); - } - - [Test] - public void should_replace_standard_numbering_when_series_is_anime() - { - _series.SeriesType = SeriesTypes.Anime; - _namingConfig.AnimeEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}"; - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South.Park.S15E06.City.Sushi"); - } - - [Test] - public void should_replace_absolute_numbering_when_series_is_anime() - { - _series.SeriesType = SeriesTypes.Anime; - _namingConfig.AnimeEpisodeFormat = "{Series.Title}.{absolute:00}.{Episode.Title}"; - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South.Park.100.City.Sushi"); - } - - [Test] - public void should_replace_duplicate_numbering_individually() - { - _series.SeriesType = SeriesTypes.Anime; - _namingConfig.AnimeEpisodeFormat = "{Series.Title}.{season}x{episode:00}.{absolute:000}\\{Series.Title}.S{season:00}E{episode:00}.{absolute:00}.{Episode.Title}"; - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South.Park.15x06.100\\South.Park.S15E06.100.City.Sushi"); - } - - [Test] - public void should_replace_individual_season_episode_tokens() - { - _series.SeriesType = SeriesTypes.Anime; - _namingConfig.AnimeEpisodeFormat = "{Series Title} Season {season:0000} Episode {episode:0000}\\{Series.Title}.S{season:00}E{episode:00}.{absolute:00}.{Episode.Title}"; - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South Park Season 0015 Episode 0006\\South.Park.S15E06.100.City.Sushi"); - } - - [Test] - public void should_use_standard_naming_when_anime_episode_has_no_absolute_number() - { - _series.SeriesType = SeriesTypes.Anime; - _episode1.AbsoluteEpisodeNumber = null; - - _namingConfig.StandardEpisodeFormat = "{Series Title} - {season:0}x{episode:00} - {Episode Title}"; - _namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}"; - - Subject.BuildFileName(new List { _episode1, }, _series, _episodeFile) - .Should().Be("South Park - 15x06 - City Sushi"); + Subject.BuildTrackFileName(new List { track }, new Artist { Name = "In The Woods..." }, new Album { Title = "30 Rock" }, _trackFile) + .Should().Be("In.The.Woods.06.Part.1"); } [Test] public void should_include_affixes_if_value_not_empty() { - _namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}{_Episode.Title_}{Quality.Title}"; + _namingConfig.StandardTrackFormat = "{Artist.Name}.{track:00}{_Track.Title_}{Quality.Title}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South.Park.S15E06_City.Sushi_HDTV-720p"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("Linkin.Park.06_City.Sushi_MP3-256"); } [Test] public void should_not_include_affixes_if_value_empty() { - _namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}{_Episode.Title_}"; + _namingConfig.StandardTrackFormat = "{Artist.Name}.{track:00}{_Track.Title_}"; - _episode1.Title = ""; + _track1.Title = ""; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South.Park.S15E06"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("Linkin.Park.06"); } [Test] public void should_format_mediainfo_properly() { - _namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}.{MEDIAINFO.FULL}"; + _namingConfig.StandardTrackFormat = "{Artist.Name}.{track:00}.{Track.Title}.{MEDIAINFO.FULL}"; - _episodeFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel() + _trackFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel() { - VideoCodec = "AVC", - AudioFormat = "DTS", + AudioFormat = "FLAC", AudioLanguages = "English/Spanish", Subtitles = "English/Spanish/Italian" }; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South.Park.S15E06.City.Sushi.X264.DTS[EN+ES].[EN+ES+IT]"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("Linkin.Park.06.City.Sushi.FLAC[EN+ES].[EN+ES+IT]"); } [Test] public void should_exclude_english_in_mediainfo_audio_language() { - _namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}.{Episode.Title}.{MEDIAINFO.FULL}"; + _namingConfig.StandardTrackFormat = "{Artist.Name}.{track:00}.{Track.Title}.{MEDIAINFO.FULL}"; - _episodeFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel() + _trackFile.MediaInfo = new Core.MediaFiles.MediaInfo.MediaInfoModel { - VideoCodec = "AVC", - AudioFormat = "DTS", + AudioFormat = "FLAC", AudioLanguages = "English", Subtitles = "English/Spanish/Italian" }; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South.Park.S15E06.City.Sushi.X264.DTS.[EN+ES+IT]"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("Linkin.Park.06.City.Sushi.FLAC.[EN+ES+IT]"); } [Test] public void should_remove_duplicate_non_word_characters() { - _series.Title = "Venture Bros."; - _namingConfig.StandardEpisodeFormat = "{Series.Title}.{season}x{episode:00}"; + _artist.Name = "Venture Bros."; + _namingConfig.StandardTrackFormat = "{Artist.Name}.{Album.Title}-{track:00}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("Venture.Bros.15x06"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("Venture.Bros.Hybrid.Theory-06"); } [Test] public void should_use_existing_filename_when_scene_name_is_not_available() { - _namingConfig.RenameEpisodes = true; - _namingConfig.StandardEpisodeFormat = "{Original Title}"; + _namingConfig.RenameTracks = true; + _namingConfig.StandardTrackFormat = "{Original Title}"; - _episodeFile.SceneName = null; - _episodeFile.RelativePath = "existing.file.mkv"; + _trackFile.SceneName = null; + _trackFile.RelativePath = "existing.file.mkv"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be(Path.GetFileNameWithoutExtension(_episodeFile.RelativePath)); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be(Path.GetFileNameWithoutExtension(_trackFile.RelativePath)); } [Test] public void should_be_able_to_use_only_original_title() { - _series.Title = "30 Rock"; - _namingConfig.StandardEpisodeFormat = "{Original Title}"; + _artist.Name = "30 Rock"; + _namingConfig.StandardTrackFormat = "{Original Title}"; - _episodeFile.SceneName = "30.Rock.S01E01.xvid-LOL"; - _episodeFile.RelativePath = "30 Rock - S01E01 - Test"; + _trackFile.SceneName = "30.Rock.S01E01.xvid-LOL"; + _trackFile.RelativePath = "30 Rock - S01E01 - Test"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) .Should().Be("30.Rock.S01E01.xvid-LOL"); } - [Test] - public void should_allow_period_between_season_and_episode() - { - _namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}.E{episode:00}.{Episode.Title}"; - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South.Park.S15.E06.City.Sushi"); - } - - [Test] - public void should_allow_space_between_season_and_episode() - { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00} E{episode:00} - {Episode Title}"; - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South Park - S15 E06 - City Sushi"); - } - - [Test] - public void should_replace_quality_proper_with_v2_for_anime_v2() - { - _series.SeriesType = SeriesTypes.Anime; - _namingConfig.AnimeEpisodeFormat = "{Quality Proper}"; - - GivenProper(); - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("v2"); - } - [Test] public void should_not_include_quality_proper_when_release_is_not_a_proper() { - _namingConfig.StandardEpisodeFormat = "{Quality Title} {Quality Proper}"; + _namingConfig.StandardTrackFormat = "{Quality Title} {Quality Proper}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("HDTV-720p"); - } - - [Test] - public void should_wrap_proper_in_square_brackets() - { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} [{Quality Title}] {[Quality Proper]}"; - - GivenProper(); - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South Park - S15E06 [HDTV-720p] [Proper]"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("MP3-256"); } [Test] public void should_not_wrap_proper_in_square_brackets_when_not_a_proper() { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} [{Quality Title}] {[Quality Proper]}"; + _namingConfig.StandardTrackFormat = "{Artist Name} - {track:00} [{Quality Title}] {[Quality Proper]}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South Park - S15E06 [HDTV-720p]"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("Linkin Park - 06 [MP3-256]"); } [Test] public void should_replace_quality_full_with_quality_title_only_when_not_a_proper() { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} [{Quality Full}]"; + _namingConfig.StandardTrackFormat = "{Artist Name} - {track:00} [{Quality Full}]"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South Park - S15E06 [HDTV-720p]"); - } - - [Test] - public void should_replace_quality_full_with_quality_title_and_proper_only_when_a_proper() - { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} [{Quality Full}]"; - - GivenProper(); - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South Park - S15E06 [HDTV-720p Proper]"); - } - - [Test] - public void should_replace_quality_full_with_quality_title_and_real_when_a_real() - { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} [{Quality Full}]"; - GivenReal(); - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South Park - S15E06 [HDTV-720p REAL]"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("Linkin Park - 06 [MP3-256]"); } [TestCase(' ')] @@ -647,10 +472,10 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests [TestCase('_')] public void should_trim_extra_separators_from_end_when_quality_proper_is_not_included(char separator) { - _namingConfig.StandardEpisodeFormat = string.Format("{{Quality{0}Title}}{0}{{Quality{0}Proper}}", separator); + _namingConfig.StandardTrackFormat = string.Format("{{Quality{0}Title}}{0}{{Quality{0}Proper}}", separator); - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("HDTV-720p"); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be("MP3-256"); } [TestCase(' ')] @@ -659,67 +484,59 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests [TestCase('_')] public void should_trim_extra_separators_from_middle_when_quality_proper_is_not_included(char separator) { - _namingConfig.StandardEpisodeFormat = string.Format("{{Quality{0}Title}}{0}{{Quality{0}Proper}}{0}{{Episode{0}Title}}", separator); + _namingConfig.StandardTrackFormat = string.Format("{{Quality{0}Title}}{0}{{Quality{0}Proper}}{0}{{Track{0}Title}}", separator); - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be(string.Format("HDTV-720p{0}City{0}Sushi", separator)); + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) + .Should().Be(string.Format("MP3-256{0}City{0}Sushi", separator)); } - [Test] - public void should_not_require_a_separator_between_tokens() - { - _series.SeriesType = SeriesTypes.Anime; - _namingConfig.AnimeEpisodeFormat = "[{Release Group}]{Series.CleanTitle}.{absolute:000}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("[LidarrTest]South.Park.100"); - } [Test] public void should_be_able_to_use_original_filename() { - _series.Title = "30 Rock"; - _namingConfig.StandardEpisodeFormat = "{Series Title} - {Original Filename}"; + _artist.Name = "30 Rock"; + _namingConfig.StandardTrackFormat = "{Artist Name} - {Original Filename}"; - _episodeFile.SceneName = "30.Rock.S01E01.xvid-LOL"; - _episodeFile.RelativePath = "30 Rock - S01E01 - Test"; + _trackFile.SceneName = "30.Rock.S01E01.xvid-LOL"; + _trackFile.RelativePath = "30 Rock - S01E01 - Test"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) .Should().Be("30 Rock - 30 Rock - S01E01 - Test"); } [Test] public void should_be_able_to_use_original_filename_only() { - _series.Title = "30 Rock"; - _namingConfig.StandardEpisodeFormat = "{Original Filename}"; + _artist.Name = "30 Rock"; + _namingConfig.StandardTrackFormat = "{Original Filename}"; - _episodeFile.SceneName = "30.Rock.S01E01.xvid-LOL"; - _episodeFile.RelativePath = "30 Rock - S01E01 - Test"; + _trackFile.SceneName = "30.Rock.S01E01.xvid-LOL"; + _trackFile.RelativePath = "30 Rock - S01E01 - Test"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) .Should().Be("30 Rock - S01E01 - Test"); } [Test] public void should_use_Lidarr_as_release_group_when_not_available() { - _episodeFile.ReleaseGroup = null; - _namingConfig.StandardEpisodeFormat = "{Release Group}"; + _trackFile.ReleaseGroup = null; + _namingConfig.StandardTrackFormat = "{Release Group}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) .Should().Be("Lidarr"); } - [TestCase("{Episode Title}{-Release Group}", "City Sushi")] - [TestCase("{Episode Title}{ Release Group}", "City Sushi")] - [TestCase("{Episode Title}{ [Release Group]}", "City Sushi")] + [TestCase("{Track Title}{-Release Group}", "City Sushi")] + [TestCase("{Track Title}{ Release Group}", "City Sushi")] + [TestCase("{Track Title}{ [Release Group]}", "City Sushi")] public void should_not_use_Lidarr_as_release_group_if_pattern_has_separator(string pattern, string expectedFileName) { - _episodeFile.ReleaseGroup = null; - _namingConfig.StandardEpisodeFormat = pattern; + _trackFile.ReleaseGroup = null; + _namingConfig.StandardTrackFormat = pattern; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) .Should().Be(expectedFileName); } @@ -728,10 +545,10 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests [TestCase("IMMERSE")] public void should_use_existing_casing_for_release_group(string releaseGroup) { - _episodeFile.ReleaseGroup = releaseGroup; - _namingConfig.StandardEpisodeFormat = "{Release Group}"; + _trackFile.ReleaseGroup = releaseGroup; + _namingConfig.StandardTrackFormat = "{Release Group}"; - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) + Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) .Should().Be(releaseGroup); } } diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/MultiEpisodeFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/MultiEpisodeFixture.cs deleted file mode 100644 index 4c63f6717..000000000 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/MultiEpisodeFixture.cs +++ /dev/null @@ -1,271 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using FizzWare.NBuilder; -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.MediaFiles; -using NzbDrone.Core.Organizer; -using NzbDrone.Core.Qualities; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests -{ - [TestFixture] - - public class MultiEpisodeFixture : CoreTest - { - private Series _series; - private Episode _episode1; - private Episode _episode2; - private Episode _episode3; - private EpisodeFile _episodeFile; - private NamingConfig _namingConfig; - - [SetUp] - public void Setup() - { - _series = Builder - .CreateNew() - .With(s => s.Title = "South Park") - .Build(); - - - _namingConfig = NamingConfig.Default; - _namingConfig.RenameEpisodes = true; - - - Mocker.GetMock() - .Setup(c => c.GetConfig()).Returns(_namingConfig); - - _episode1 = Builder.CreateNew() - .With(e => e.Title = "City Sushi") - .With(e => e.SeasonNumber = 15) - .With(e => e.EpisodeNumber = 6) - .With(e => e.AbsoluteEpisodeNumber = 100) - .Build(); - - _episode2 = Builder.CreateNew() - .With(e => e.Title = "City Sushi") - .With(e => e.SeasonNumber = 15) - .With(e => e.EpisodeNumber = 7) - .With(e => e.AbsoluteEpisodeNumber = 101) - .Build(); - - _episode3 = Builder.CreateNew() - .With(e => e.Title = "City Sushi") - .With(e => e.SeasonNumber = 15) - .With(e => e.EpisodeNumber = 8) - .With(e => e.AbsoluteEpisodeNumber = 102) - .Build(); - - _episodeFile = new EpisodeFile { Quality = new QualityModel(Quality.MP3_256), ReleaseGroup = "LidarrTest" }; - - Mocker.GetMock() - .Setup(v => v.Get(Moq.It.IsAny())) - .Returns(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v)); - } - - private void GivenProper() - { - _episodeFile.Quality.Revision.Version = 2; - } - - [Test] - public void should_replace_Series_space_Title() - { - _namingConfig.StandardEpisodeFormat = "{Series Title}"; - - Subject.BuildFileName(new List {_episode1}, _series, _episodeFile) - .Should().Be("South Park"); - } - - [Test] - public void should_format_extend_multi_episode_properly() - { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}"; - _namingConfig.MultiEpisodeStyle = 0; - - Subject.BuildFileName(new List {_episode1, _episode2}, _series, _episodeFile) - .Should().Be("South Park - S15E06-07 - City Sushi"); - } - - [Test] - public void should_format_duplicate_multi_episode_properly() - { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}"; - _namingConfig.MultiEpisodeStyle = 1; - - Subject.BuildFileName(new List { _episode1, _episode2 }, _series, _episodeFile) - .Should().Be("South Park - S15E06 - S15E07 - City Sushi"); - } - - [Test] - public void should_format_repeat_multi_episode_properly() - { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}"; - _namingConfig.MultiEpisodeStyle = 2; - - Subject.BuildFileName(new List { _episode1, _episode2 }, _series, _episodeFile) - .Should().Be("South Park - S15E06E07 - City Sushi"); - } - - [Test] - public void should_format_scene_multi_episode_properly() - { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}"; - _namingConfig.MultiEpisodeStyle = 3; - - Subject.BuildFileName(new List { _episode1, _episode2 }, _series, _episodeFile) - .Should().Be("South Park - S15E06-E07 - City Sushi"); - } - - [Test] - public void should_use_dash_as_separator_when_multi_episode_style_is_extend_for_anime() - { - _series.SeriesType = SeriesTypes.Anime; - _namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}"; - - Subject.BuildFileName(new List { _episode1, _episode2 }, _series, _episodeFile) - .Should().Be("South Park - 100-101 - City Sushi"); - } - - [Test] - public void should_duplicate_absolute_pattern_when_multi_episode_style_is_duplicate() - { - _series.SeriesType = SeriesTypes.Anime; - _namingConfig.MultiEpisodeStyle = (int)MultiEpisodeStyle.Duplicate; - _namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}"; - - Subject.BuildFileName(new List { _episode1, _episode2, _episode3 }, _series, _episodeFile) - .Should().Be("South Park - 100 - 101 - 102 - City Sushi"); - } - - [Test] - public void should_get_proper_filename_when_multi_episode_is_duplicated_and_bracket_follows_pattern() - { - _namingConfig.StandardEpisodeFormat = - "{Series Title} - S{season:00}E{episode:00} - ({Quality Title}, {MediaInfo Full}, {Release Group}) - {Episode Title}"; - _namingConfig.MultiEpisodeStyle = (int) MultiEpisodeStyle.Duplicate; - - Subject.BuildFileName(new List { _episode1, _episode2 }, _series, _episodeFile) - .Should().Be("South Park - S15E06 - S15E07 - (HDTV-720p, , LidarrTest) - City Sushi"); - } - - [Test] - public void should_format_range_multi_episode_properly() - { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}"; - _namingConfig.MultiEpisodeStyle = 4; - - Subject.BuildFileName(new List { _episode1, _episode2, _episode3 }, _series, _episodeFile) - .Should().Be("South Park - S15E06-08 - City Sushi"); - } - - [Test] - public void should_format_range_multi_episode_anime_properly() - { - _series.SeriesType = SeriesTypes.Anime; - _namingConfig.MultiEpisodeStyle = 4; - _namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}"; - - Subject.BuildFileName(new List { _episode1, _episode2, _episode3 }, _series, _episodeFile) - .Should().Be("South Park - 100-102 - City Sushi"); - } - - [Test] - public void should_format_repeat_multi_episode_anime_properly() - { - _series.SeriesType = SeriesTypes.Anime; - _namingConfig.MultiEpisodeStyle = 2; - _namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}"; - - Subject.BuildFileName(new List { _episode1, _episode2, _episode3 }, _series, _episodeFile) - .Should().Be("South Park - 100-101-102 - City Sushi"); - } - - [Test] - public void should_format_single_episode_with_range_multi_episode_properly() - { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}"; - _namingConfig.MultiEpisodeStyle = 4; - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South Park - S15E06 - City Sushi"); - } - - [Test] - public void should_format_single_anime_episode_with_range_multi_episode_properly() - { - _series.SeriesType = SeriesTypes.Anime; - _namingConfig.MultiEpisodeStyle = 4; - _namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}"; - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South Park - 100 - City Sushi"); - } - - [Test] - public void should_default_to_dash_when_serparator_is_not_set_for_absolute_number() - { - _series.SeriesType = SeriesTypes.Anime; - _namingConfig.MultiEpisodeStyle = (int)MultiEpisodeStyle.Duplicate; - _namingConfig.AnimeEpisodeFormat = "{Series Title} - {season}x{episode:00} - [{absolute:000}] - {Episode Title} - {Quality Title}"; - - Subject.BuildFileName(new List { _episode1, _episode2 }, _series, _episodeFile) - .Should().Be("South Park - 15x06 - 15x07 - [100-101] - City Sushi - HDTV-720p"); - } - - [Test] - public void should_format_prefixed_range_multi_episode_properly() - { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}"; - _namingConfig.MultiEpisodeStyle = 5; - - Subject.BuildFileName(new List { _episode1, _episode2, _episode3 }, _series, _episodeFile) - .Should().Be("South Park - S15E06-E08 - City Sushi"); - } - - [Test] - public void should_format_prefixed_range_multi_episode_anime_properly() - { - _series.SeriesType = SeriesTypes.Anime; - _namingConfig.MultiEpisodeStyle = 5; - _namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}"; - - Subject.BuildFileName(new List { _episode1, _episode2, _episode3 }, _series, _episodeFile) - .Should().Be("South Park - 100-102 - City Sushi"); - } - - [Test] - public void should_format_single_episode_with_prefixed_range_multi_episode_properly() - { - _namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title}"; - _namingConfig.MultiEpisodeStyle = 5; - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South Park - S15E06 - City Sushi"); - } - - [Test] - public void should_format_single_anime_episode_with_prefixed_range_multi_episode_properly() - { - _series.SeriesType = SeriesTypes.Anime; - _namingConfig.MultiEpisodeStyle = 5; - _namingConfig.AnimeEpisodeFormat = "{Series Title} - {absolute:000} - {Episode Title}"; - - Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile) - .Should().Be("South Park - 100 - City Sushi"); - } - - [Test] - public void should_format_prefixed_range_multi_episode_using_episode_separator() - { - _namingConfig.StandardEpisodeFormat = "{Series Title} - {season:0}x{episode:00} - {Episode Title}"; - _namingConfig.MultiEpisodeStyle = 5; - - Subject.BuildFileName(new List { _episode1, _episode2, _episode3 }, _series, _episodeFile) - .Should().Be("South Park - 15x06-x08 - City Sushi"); - } - } -} diff --git a/src/NzbDrone.Core.Test/OrganizerTests/GetAlbumFolderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/GetAlbumFolderFixture.cs new file mode 100644 index 000000000..dec3d8318 --- /dev/null +++ b/src/NzbDrone.Core.Test/OrganizerTests/GetAlbumFolderFixture.cs @@ -0,0 +1,35 @@ +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Music; + +namespace NzbDrone.Core.Test.OrganizerTests +{ + [TestFixture] + public class GetAlbumFolderFixture : CoreTest + { + private NamingConfig namingConfig; + + [SetUp] + public void Setup() + { + namingConfig = NamingConfig.Default; + + Mocker.GetMock() + .Setup(c => c.GetConfig()).Returns(namingConfig); + } + + [TestCase("Venture Bros.", "Today", "{Artist.Name}.{Album.Title}", "Venture.Bros.Today")] + [TestCase("Venture Bros.", "Today", "{Artist Name} {Album Title}", "Venture Bros. Today")] + public void should_use_albumFolderFormat_to_build_folder_name(string artistName, string albumTitle, string format, string expected) + { + namingConfig.AlbumFolderFormat = format; + + var artist = new Artist { Name = artistName }; + var album = new Album { Title = albumTitle }; + + Subject.GetAlbumFolder(artist, album, namingConfig).Should().Be(expected); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/OrganizerTests/GetArtistFolderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/GetArtistFolderFixture.cs new file mode 100644 index 000000000..1a9a2b400 --- /dev/null +++ b/src/NzbDrone.Core.Test/OrganizerTests/GetArtistFolderFixture.cs @@ -0,0 +1,39 @@ +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Music; + +namespace NzbDrone.Core.Test.OrganizerTests +{ + [TestFixture] + + public class GetArtistFolderFixture : CoreTest + { + private NamingConfig namingConfig; + + [SetUp] + public void Setup() + { + namingConfig = NamingConfig.Default; + + Mocker.GetMock() + .Setup(c => c.GetConfig()).Returns(namingConfig); + } + + [TestCase("Avenged Sevenfold", "{Artist Name}", "Avenged Sevenfold")] + [TestCase("Avenged Sevenfold", "{Artist.Name}", "Avenged.Sevenfold")] + [TestCase("AC/DC", "{Artist Name}", "AC+DC")] + [TestCase("In the Woods...", "{Artist.Name}", "In.the.Woods")] + [TestCase("3OH!3", "{Artist.Name}", "3OH!3")] + [TestCase("Avenged Sevenfold", ".{Artist.Name}.", "Avenged.Sevenfold")] + public void should_use_artistFolderFormat_to_build_folder_name(string artistName, string format, string expected) + { + namingConfig.ArtistFolderFormat = format; + + var artist = new Artist { Name = artistName }; + + Subject.GetArtistFolder(artist).Should().Be(expected); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/OrganizerTests/GetSeasonFolderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/GetSeasonFolderFixture.cs deleted file mode 100644 index 796a0881f..000000000 --- a/src/NzbDrone.Core.Test/OrganizerTests/GetSeasonFolderFixture.cs +++ /dev/null @@ -1,34 +0,0 @@ -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.Organizer; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.Test.OrganizerTests -{ - [TestFixture] - public class GetSeasonFolderFixture : CoreTest - { - private NamingConfig namingConfig; - - [SetUp] - public void Setup() - { - namingConfig = NamingConfig.Default; - - Mocker.GetMock() - .Setup(c => c.GetConfig()).Returns(namingConfig); - } - - [TestCase("Venture Bros.", 1, "{Series.Title}.{season:00}", "Venture.Bros.01")] - [TestCase("Venture Bros.", 1, "{Series Title} Season {season:00}", "Venture Bros. Season 01")] - public void should_use_seriesFolderFormat_to_build_folder_name(string seriesTitle, int seasonNumber, string format, string expected) - { - namingConfig.SeasonFolderFormat = format; - - var series = new Series { Title = seriesTitle }; - - Subject.GetSeasonFolder(series, seasonNumber, namingConfig).Should().Be(expected); - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/OrganizerTests/GetSeriesFolderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/GetSeriesFolderFixture.cs deleted file mode 100644 index 9cf0b5e01..000000000 --- a/src/NzbDrone.Core.Test/OrganizerTests/GetSeriesFolderFixture.cs +++ /dev/null @@ -1,39 +0,0 @@ -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.Organizer; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.Test.OrganizerTests -{ - [TestFixture] - - public class GetSeriesFolderFixture : CoreTest - { - private NamingConfig namingConfig; - - [SetUp] - public void Setup() - { - namingConfig = NamingConfig.Default; - - Mocker.GetMock() - .Setup(c => c.GetConfig()).Returns(namingConfig); - } - - [TestCase("30 Rock", "{Series Title}", "30 Rock")] - [TestCase("30 Rock", "{Series.Title}", "30.Rock")] - [TestCase("24/7 Road to the NHL Winter Classic", "{Series Title}", "24+7 Road to the NHL Winter Classic")] - [TestCase("Venture Bros.", "{Series.Title}", "Venture.Bros")] - [TestCase(".hack", "{Series.Title}", "hack")] - [TestCase("30 Rock", ".{Series.Title}.", "30.Rock")] - public void should_use_seriesFolderFormat_to_build_folder_name(string seriesTitle, string format, string expected) - { - namingConfig.SeriesFolderFormat = format; - - var series = new Series { Title = seriesTitle }; - - Subject.GetSeriesFolder(series).Should().Be(expected); - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Core/Datastore/Migration/114_remove_tv_naming.cs b/src/NzbDrone.Core/Datastore/Migration/114_remove_tv_naming.cs new file mode 100644 index 000000000..7d24e8626 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/114_remove_tv_naming.cs @@ -0,0 +1,27 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(114)] + public class remove_tv_naming : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Delete.Column("RenameEpisodes").FromTable("NamingConfig"); + Delete.Column("StandardEpisodeFormat").FromTable("NamingConfig"); + Delete.Column("DailyEpisodeFormat").FromTable("NamingConfig"); + Delete.Column("AnimeEpisodeFormat").FromTable("NamingConfig"); + Delete.Column("SeasonFolderFormat").FromTable("NamingConfig"); + Delete.Column("SeriesFolderFormat").FromTable("NamingConfig"); + Delete.Column("MultiEpisodeStyle").FromTable("NamingConfig"); + + Execute.Sql("DELETE FROM Config WHERE [Key] = 'filedate'"); + } + + } +} diff --git a/src/NzbDrone.Core/MediaFiles/Commands/RenameFilesCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/RenameFilesCommand.cs index e0dc34e10..4154116a9 100644 --- a/src/NzbDrone.Core/MediaFiles/Commands/RenameFilesCommand.cs +++ b/src/NzbDrone.Core/MediaFiles/Commands/RenameFilesCommand.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; +using System.Collections.Generic; using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.MediaFiles.Commands { public class RenameFilesCommand : Command { - public int SeriesId { get; set; } + public int ArtistId { get; set; } public List Files { get; set; } public override bool SendUpdatesToClient => true; @@ -14,9 +14,9 @@ namespace NzbDrone.Core.MediaFiles.Commands { } - public RenameFilesCommand(int seriesId, List files) + public RenameFilesCommand(int artistId, List files) { - SeriesId = seriesId; + ArtistId = artistId; Files = files; } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeFileMoveResult.cs b/src/NzbDrone.Core/MediaFiles/EpisodeFileMoveResult.cs deleted file mode 100644 index e88a10d29..000000000 --- a/src/NzbDrone.Core/MediaFiles/EpisodeFileMoveResult.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; - -namespace NzbDrone.Core.MediaFiles -{ - public class EpisodeFileMoveResult - { - public EpisodeFileMoveResult() - { - OldFiles = new List(); - } - - public EpisodeFile EpisodeFile { get; set; } - public List OldFiles { get; set; } - } -} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs deleted file mode 100644 index f2a0b9be6..000000000 --- a/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -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.Organizer; -using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.MediaFiles -{ - public interface IMoveEpisodeFiles - { - EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, Series series); - EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode); - EpisodeFile CopyEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode); - } - - public class EpisodeFileMovingService : IMoveEpisodeFiles - { - private readonly IEpisodeService _episodeService; - private readonly IUpdateEpisodeFileService _updateEpisodeFileService; - 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 EpisodeFileMovingService(IEpisodeService episodeService, - IUpdateEpisodeFileService updateEpisodeFileService, - IBuildFileNames buildFileNames, - IDiskTransferService diskTransferService, - IDiskProvider diskProvider, - IMediaFileAttributeService mediaFileAttributeService, - IEventAggregator eventAggregator, - IConfigService configService, - Logger logger) - { - _episodeService = episodeService; - _updateEpisodeFileService = updateEpisodeFileService; - _buildFileNames = buildFileNames; - _diskTransferService = diskTransferService; - _diskProvider = diskProvider; - _mediaFileAttributeService = mediaFileAttributeService; - _eventAggregator = eventAggregator; - _configService = configService; - _logger = logger; - } - - public EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, Series series) - { - var episodes = _episodeService.GetEpisodesByFileId(episodeFile.Id); - var newFileName = _buildFileNames.BuildFileName(episodes, series, episodeFile); - var filePath = _buildFileNames.BuildFilePath(series, episodes.First().SeasonNumber, newFileName, Path.GetExtension(episodeFile.RelativePath)); - - EnsureEpisodeFolder(episodeFile, series, episodes.Select(v => v.SeasonNumber).First(), filePath); - - _logger.Debug("Renaming episode file: {0} to {1}", episodeFile, filePath); - - return TransferFile(episodeFile, series, episodes, filePath, TransferMode.Move); - } - - public EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode) - { - 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 EpisodeFile CopyEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode) - { - 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) - { - 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); - - _updateEpisodeFileService.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/Events/TrackFolderCreatedEvent.cs b/src/NzbDrone.Core/MediaFiles/Events/TrackFolderCreatedEvent.cs new file mode 100644 index 000000000..79fb40666 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/Events/TrackFolderCreatedEvent.cs @@ -0,0 +1,20 @@ +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Music; + +namespace NzbDrone.Core.MediaFiles.Events +{ + public class TrackFolderCreatedEvent : IEvent + { + public Artist Artist { get; private set; } + public TrackFile TrackFile { get; private set; } + public string ArtistFolder { get; set; } + public string AlbumFolder { get; set; } + public string TrackFolder { get; set; } + + public TrackFolderCreatedEvent(Artist artist, TrackFile trackFile) + { + Artist = artist; + TrackFile = trackFile; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/FileDateType.cs b/src/NzbDrone.Core/MediaFiles/FileDateType.cs index 6d78be960..5173d8c0f 100644 --- a/src/NzbDrone.Core/MediaFiles/FileDateType.cs +++ b/src/NzbDrone.Core/MediaFiles/FileDateType.cs @@ -1,9 +1,8 @@ -namespace NzbDrone.Core.MediaFiles +namespace NzbDrone.Core.MediaFiles { public enum FileDateType { None = 0, - LocalAirDate = 1, - UtcAirDate = 2 + AlbumReleaseDate = 1 } } diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs index 4b93d8591..052db6fc4 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs @@ -9,6 +9,7 @@ namespace NzbDrone.Core.MediaFiles public interface IMediaFileRepository : IBasicRepository { List GetFilesByArtist(int artistId); + List GetFilesByAlbum(int albumId); List GetFilesWithoutMediaInfo(); } @@ -29,5 +30,10 @@ namespace NzbDrone.Core.MediaFiles { return Query.Where(c => c.ArtistId == artistId).ToList(); } + + public List GetFilesByAlbum(int albumId) + { + return Query.Where(c => c.AlbumId == albumId).ToList(); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs index 1b6af1bd5..e485344ca 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs @@ -4,8 +4,6 @@ using System.Linq; using NLog; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.Tv; -using NzbDrone.Core.Tv.Events; using NzbDrone.Common; using NzbDrone.Core.Music; using System; @@ -100,7 +98,7 @@ namespace NzbDrone.Core.MediaFiles public List GetFilesByAlbum(int artistId, int albumId) { - return _mediaFileRepository.GetFilesByArtist(artistId); + return _mediaFileRepository.GetFilesByAlbum(albumId); } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs new file mode 100644 index 000000000..7efd2cb73 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoFormatter.cs @@ -0,0 +1,200 @@ +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using NLog; +using NLog.Fluent; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Instrumentation; +using NzbDrone.Common.Instrumentation.Extensions; + +namespace NzbDrone.Core.MediaFiles.MediaInfo +{ + public static class MediaInfoFormatter + { + private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(MediaInfoFormatter)); + + public static decimal FormatAudioChannels(MediaInfoModel mediaInfo) + { + var audioChannelPositions = mediaInfo.AudioChannelPositions; + var audioChannelPositionsText = mediaInfo.AudioChannelPositionsText; + var audioChannels = mediaInfo.AudioChannels; + + if (audioChannelPositions.IsNullOrWhiteSpace()) + { + if (audioChannelPositionsText.IsNullOrWhiteSpace()) + { + if (mediaInfo.SchemaRevision >= 3) + { + return audioChannels; + } + + return 0; + } + + return mediaInfo.AudioChannelPositionsText.ContainsIgnoreCase("LFE") ? audioChannels - 1 + 0.1m : audioChannels; + } + + return audioChannelPositions.Replace("Object Based / ", "") + .Split(new [] { " / " }, StringSplitOptions.None) + .First() + .Split('/') + .Sum(s => decimal.Parse(s, CultureInfo.InvariantCulture)); + } + + public static string FormatAudioCodec(MediaInfoModel mediaInfo) + { + if (mediaInfo.AudioCodecID == null) + { + return FormatAudioCodecLegacy(mediaInfo); + } + + var audioFormat = mediaInfo.AudioFormat; + var audioCodecID = mediaInfo.AudioCodecID ?? string.Empty; + var audioProfile = mediaInfo.AudioProfile ?? string.Empty; + var audioCodecLibrary = mediaInfo.AudioCodecLibrary ?? string.Empty; + + if (audioFormat.IsNullOrWhiteSpace()) + { + return string.Empty; + } + + if (audioFormat.EqualsIgnoreCase("AC-3")) + { + return "AC3"; + } + + if (audioFormat.EqualsIgnoreCase("E-AC-3")) + { + return "EAC3"; + } + + if (audioFormat.EqualsIgnoreCase("AAC")) + { + if (audioCodecID == "A_AAC/MPEG4/LC/SBR") + { + return "HE-AAC"; + } + + return "AAC"; + } + + if (audioFormat.EqualsIgnoreCase("DTS")) + { + return "DTS"; + } + + if (audioFormat.EqualsIgnoreCase("FLAC")) + { + return "FLAC"; + } + + if (audioFormat.Trim().EqualsIgnoreCase("mp3")) + { + return "MP3"; + } + + if (audioFormat.EqualsIgnoreCase("MPEG Audio")) + { + if (mediaInfo.AudioCodecID == "55" || mediaInfo.AudioCodecID == "A_MPEG/L3" || mediaInfo.AudioProfile == "Layer 3") + { + return "MP3"; + } + + if (mediaInfo.AudioCodecID == "A_MPEG/L2" || mediaInfo.AudioProfile == "Layer 2") + { + return "MP2"; + } + } + + if (audioFormat.EqualsIgnoreCase("Opus")) + { + return "Opus"; + } + + if (audioFormat.EqualsIgnoreCase("PCM")) + { + return "PCM"; + } + + if (audioFormat.EqualsIgnoreCase("TrueHD")) + { + return "TrueHD"; + } + + if (audioFormat.EqualsIgnoreCase("Vorbis")) + { + return "Vorbis"; + } + + if (audioFormat == "WMA") + { + return "WMA"; + } + + Logger.Debug() + .Message("Unknown audio format: '{0}' in '{1}'.", string.Join(", ", audioFormat, audioCodecID, audioProfile, audioCodecLibrary)) + .WriteSentryWarn("UnknownAudioFormat", mediaInfo.ContainerFormat, audioFormat, audioCodecID) + .Write(); + + return audioFormat; + } + + public static string FormatAudioCodecLegacy(MediaInfoModel mediaInfo) + { + var audioFormat = mediaInfo.AudioFormat; + + if (audioFormat.IsNullOrWhiteSpace()) + { + return audioFormat; + } + + if (audioFormat.EqualsIgnoreCase("AC-3")) + { + return "AC3"; + } + + if (audioFormat.EqualsIgnoreCase("E-AC-3")) + { + return "EAC3"; + } + + if (audioFormat.EqualsIgnoreCase("AAC")) + { + return "AAC"; + } + + if (audioFormat.EqualsIgnoreCase("MPEG Audio") && mediaInfo.AudioProfile == "Layer 3") + { + return "MP3"; + } + + if (audioFormat.EqualsIgnoreCase("DTS")) + { + return "DTS"; + } + + if (audioFormat.EqualsIgnoreCase("TrueHD")) + { + return "TrueHD"; + } + + if (audioFormat.EqualsIgnoreCase("FLAC")) + { + return "FLAC"; + } + + if (audioFormat.EqualsIgnoreCase("Vorbis")) + { + return "Vorbis"; + } + + if (audioFormat.EqualsIgnoreCase("Opus")) + { + return "Opus"; + } + + return audioFormat; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs index 7e33729ab..b28cc33fc 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/MediaInfoModel.cs @@ -9,12 +9,19 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo { public class MediaInfoModel : IEmbeddedDocument { + public string ContainerFormat { get; set; } public string VideoCodec { get; set; } + public string VideoFormat { get; set; } + public string VideoCodecID { get; set; } + public string VideoProfile { get; set; } + public string VideoCodecLibrary { get; set; } public int VideoBitrate { get; set; } public int VideoBitDepth { get; set; } public int Width { get; set; } public int Height { get; set; } public string AudioFormat { get; set; } + public string AudioCodecID { get; set; } + public string AudioCodecLibrary { get; set; } public int AudioBitrate { get; set; } public string AudioBitrateMode { get; set; } public TimeSpan RunTime { get; set; } @@ -29,32 +36,5 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo public string ScanType { get; set; } public int SchemaRevision { get; set; } - [JsonIgnore] - public decimal FormattedAudioChannels - { - get - { - if (AudioChannelPositions.IsNullOrWhiteSpace()) - { - if (AudioChannelPositionsText.IsNullOrWhiteSpace()) - { - if (SchemaRevision >= 3) - { - return AudioChannels; - } - - return 0; - } - - return AudioChannelPositionsText.ContainsIgnoreCase("LFE") ? AudioChannels - 1 + 0.1m : AudioChannels; - } - - return AudioChannelPositions.Replace("Object Based / ", "") - .Split(new string[] { " / " }, StringSplitOptions.None) - .First() - .Split('/') - .Sum(s => decimal.Parse(s, CultureInfo.InvariantCulture)); - } - } } } diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs index 593cdcee6..61e46abb3 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs @@ -3,11 +3,10 @@ using NLog; using NzbDrone.Common.Disk; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; using System.Collections.Generic; using System.Linq; using NzbDrone.Core.Configuration; -using NzbDrone.Core.Music; namespace NzbDrone.Core.MediaFiles.MediaInfo { @@ -19,7 +18,8 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo private readonly IConfigService _configService; private readonly Logger _logger; - private const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 3; + public const int MINIMUM_MEDIA_INFO_SCHEMA_REVISION = 3; + public const int CURRENT_MEDIA_INFO_SCHEMA_REVISION = 4; public UpdateMediaInfoService(IDiskProvider diskProvider, IMediaFileService mediaFileService, @@ -66,9 +66,9 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo } var allMediaFiles = _mediaFileService.GetFilesByArtist(message.Artist.Id); - var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < CURRENT_MEDIA_INFO_SCHEMA_REVISION).ToList(); + var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < MINIMUM_MEDIA_INFO_SCHEMA_REVISION).ToList(); UpdateMediaInfo(message.Artist, filteredMediaFiles); } } -} +} \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs index 3bd5d88db..bca9310b6 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/VideoFileInfoReader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.IO; using NLog; @@ -105,47 +105,37 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo int.TryParse(mediaInfo.Get(StreamKind.General, 0, "PlayTime"), out generalRuntime); string audioBitRateMode = mediaInfo.Get(StreamKind.Audio, 0, "BitRate_Mode"); - string aBitRate = mediaInfo.Get(StreamKind.Audio, 0, "BitRate"); - int aBindex = aBitRate.IndexOf(" /", StringComparison.InvariantCultureIgnoreCase); - if (aBindex > 0) - { - aBitRate = aBitRate.Remove(aBindex); - } + string aBitRate = mediaInfo.Get(StreamKind.Audio, 0, "BitRate").Split(new [] { " /" }, StringSplitOptions.None)[0].Trim(); int.TryParse(aBitRate, out audioBitRate); int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "StreamCount"), out streamCount); - string audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)"); - int aCindex = audioChannelsStr.IndexOf(" /", StringComparison.InvariantCultureIgnoreCase); - - if (aCindex > 0) - { - audioChannelsStr = audioChannelsStr.Remove(aCindex); - } + string audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)").Split(new [] { " /" }, StringSplitOptions.None)[0].Trim(); var audioChannelPositions = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions/String2"); var audioChannelPositionsText = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions"); string audioLanguages = mediaInfo.Get(StreamKind.General, 0, "Audio_Language_List"); - string audioProfile = mediaInfo.Get(StreamKind.Audio, 0, "Format_Profile"); - - int aPindex = audioProfile.IndexOf(" /", StringComparison.InvariantCultureIgnoreCase); - - if (aPindex > 0) - { - audioProfile = audioProfile.Remove(aPindex); - } + string videoProfile = mediaInfo.Get(StreamKind.Video, 0, "Format_Profile").Split(new [] { " /" }, StringSplitOptions.None)[0].Trim(); + string audioProfile = mediaInfo.Get(StreamKind.Audio, 0, "Format_Profile").Split(new [] { " /" }, StringSplitOptions.None)[0].Trim(); int.TryParse(audioChannelsStr, out audioChannels); var mediaInfoModel = new MediaInfoModel { - VideoCodec = mediaInfo.Get(StreamKind.Video, 0, "Codec/String"), + ContainerFormat = mediaInfo.Get(StreamKind.General, 0, "Format"), + VideoFormat = mediaInfo.Get(StreamKind.Video, 0, "Format"), + VideoCodecID = mediaInfo.Get(StreamKind.Video, 0, "CodecID"), + VideoProfile = videoProfile, + VideoCodecLibrary = mediaInfo.Get(StreamKind.Video, 0, "Encoded_Library"), VideoBitrate = videoBitRate, VideoBitDepth = videoBitDepth, Height = height, Width = width, AudioFormat = mediaInfo.Get(StreamKind.Audio, 0, "Format"), + AudioCodecID = mediaInfo.Get(StreamKind.Audio, 0, "CodecID"), + AudioProfile = audioProfile, + AudioCodecLibrary = mediaInfo.Get(StreamKind.Audio, 0, "Encoded_Library"), AudioBitrateMode = audioBitRateMode, AudioBitrate = audioBitRate, RunTime = GetBestRuntime(audioRuntime, videoRuntime, generalRuntime), @@ -153,7 +143,6 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo AudioChannels = audioChannels, AudioChannelPositions = audioChannelPositions, AudioChannelPositionsText = audioChannelPositionsText, - AudioProfile = audioProfile.Trim(), VideoFps = videoFrameRate, AudioLanguages = audioLanguages, Subtitles = subtitles, diff --git a/src/NzbDrone.Core/MediaFiles/RenameEpisodeFilePreview.cs b/src/NzbDrone.Core/MediaFiles/RenameEpisodeFilePreview.cs deleted file mode 100644 index 72ba4b247..000000000 --- a/src/NzbDrone.Core/MediaFiles/RenameEpisodeFilePreview.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; - -namespace NzbDrone.Core.MediaFiles -{ - public class RenameEpisodeFilePreview - { - public int SeriesId { get; set; } - public int SeasonNumber { get; set; } - public List EpisodeNumbers { get; set; } - public int EpisodeFileId { get; set; } - public string ExistingPath { get; set; } - public string NewPath { get; set; } - } -} diff --git a/src/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs b/src/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs deleted file mode 100644 index d279b7289..000000000 --- a/src/NzbDrone.Core/MediaFiles/RenameEpisodeFileService.cs +++ /dev/null @@ -1,181 +0,0 @@ -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.Tv; - -namespace NzbDrone.Core.MediaFiles -{ - public interface IRenameEpisodeFileService - { - List GetRenamePreviews(int seriesId); - List GetRenamePreviews(int seriesId, int seasonNumber); - } - - public class RenameEpisodeFileService : IRenameEpisodeFileService, - IExecute, - IExecute - { - private readonly ISeriesService _seriesService; - private readonly IMediaFileService _mediaFileService; - private readonly IMoveEpisodeFiles _episodeFileMover; - private readonly IEventAggregator _eventAggregator; - private readonly IEpisodeService _episodeService; - private readonly IBuildFileNames _filenameBuilder; - private readonly IDiskProvider _diskProvider; - private readonly Logger _logger; - - public RenameEpisodeFileService(ISeriesService seriesService, - IMediaFileService mediaFileService, - IMoveEpisodeFiles episodeFileMover, - IEventAggregator eventAggregator, - IEpisodeService episodeService, - IBuildFileNames filenameBuilder, - IDiskProvider diskProvider, - Logger logger) - { - _seriesService = seriesService; - _mediaFileService = mediaFileService; - _episodeFileMover = episodeFileMover; - _eventAggregator = eventAggregator; - _episodeService = episodeService; - _filenameBuilder = filenameBuilder; - _diskProvider = diskProvider; - _logger = logger; - } - - public List GetRenamePreviews(int seriesId) - { - // 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) - { - // 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) - { - foreach (var f in files) - { - var file = f; - var episodesInFile = episodes.Where(e => e.EpisodeFileId == file.Id).ToList(); - var episodeFilePath = Path.Combine(series.Path, file.RelativePath); - - if (!episodesInFile.Any()) - { - _logger.Warn("File ({0}) is not linked to any episodes", episodeFilePath); - continue; - } - - var seasonNumber = episodesInFile.First().SeasonNumber; - var newName = _filenameBuilder.BuildFileName(episodesInFile, series, file); - var newPath = _filenameBuilder.BuildFilePath(series, seasonNumber, newName, Path.GetExtension(episodeFilePath)); - - if (!episodeFilePath.PathEquals(newPath, StringComparison.Ordinal)) - { - yield return new RenameEpisodeFilePreview - { - SeriesId = series.Id, - SeasonNumber = seasonNumber, - EpisodeNumbers = episodesInFile.Select(e => e.EpisodeNumber).ToList(), - EpisodeFileId = file.Id, - ExistingPath = file.RelativePath, - NewPath = series.Path.GetRelativePath(newPath) - }; - } - } - } - - private void RenameFiles(List episodeFiles, Series 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) - { - // 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) - { - // 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/RenameTrackFileService.cs b/src/NzbDrone.Core/MediaFiles/RenameTrackFileService.cs index 001774de5..bea565262 100644 --- a/src/NzbDrone.Core/MediaFiles/RenameTrackFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/RenameTrackFileService.cs @@ -21,9 +21,7 @@ namespace NzbDrone.Core.MediaFiles List GetRenamePreviews(int artistId, int albumId); } - public class RenameTrackFileService : IRenameTrackFileService, - IExecute, - IExecute + public class RenameTrackFileService : IRenameTrackFileService, IExecute, IExecute { private readonly IArtistService _artistService; private readonly IAlbumService _albumService; @@ -58,32 +56,29 @@ namespace NzbDrone.Core.MediaFiles 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(); + + var artist = _artistService.GetArtist(artistId); + var tracks = _trackService.GetTracksByArtist(artistId); + var files = _mediaFileService.GetFilesByArtist(artistId); + + return GetPreviews(artist, tracks, files) + .OrderByDescending(e => e.AlbumId) + .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) + return GetPreviews(artist, tracks, files) .OrderByDescending(e => e.TrackNumbers.First()).ToList(); } - private IEnumerable GetPreviews(Artist artist, Album album, List tracks, List files) + private IEnumerable GetPreviews(Artist artist, List tracks, List files) { foreach (var f in files) { @@ -97,7 +92,8 @@ namespace NzbDrone.Core.MediaFiles continue; } - var albumId = tracksInFile.First().AlbumId; + var album = _albumService.GetAlbum(tracksInFile.First().AlbumId); + var newName = _filenameBuilder.BuildTrackFileName(tracksInFile, artist, album, file); var newPath = _filenameBuilder.BuildTrackFilePath(artist, album, newName, Path.GetExtension(trackFilePath)); @@ -106,7 +102,7 @@ namespace NzbDrone.Core.MediaFiles yield return new RenameTrackFilePreview { ArtistId = artist.Id, - AlbumId = albumId, + AlbumId = album.Id, TrackNumbers = tracksInFile.Select(e => e.TrackNumber).ToList(), TrackFileId = file.Id, ExistingPath = file.RelativePath, @@ -118,68 +114,64 @@ namespace NzbDrone.Core.MediaFiles 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)); - //} + 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); + + var artist = _artistService.GetArtist(message.ArtistId); + var trackFiles = _mediaFileService.Get(message.Files); + + _logger.ProgressInfo("Renaming {0} files for {1}", trackFiles.Count, artist.Name); + RenameFiles(trackFiles, artist); + _logger.ProgressInfo("Selected track files renamed for {0}", artist.Name); } 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); - //} + + _logger.Debug("Renaming all files for selected artist"); + var artistToRename = _artistService.GetArtists(message.ArtistIds); + + foreach (var artist in artistToRename) + { + var trackFiles = _mediaFileService.GetFilesByArtist(artist.Id); + _logger.ProgressInfo("Renaming all files in artist: {0}", artist.Name); + RenameFiles(trackFiles, artist); + _logger.ProgressInfo("All track files renamed for {0}", artist.Name); + } } } } diff --git a/src/NzbDrone.Core/MediaFiles/TrackFileMovingService.cs b/src/NzbDrone.Core/MediaFiles/TrackFileMovingService.cs index 5500f6007..2107fcd79 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackFileMovingService.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackFileMovingService.cs @@ -1,4 +1,4 @@ -using NLog; +using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.EnsureThat; using NzbDrone.Common.Extensions; @@ -27,7 +27,8 @@ namespace NzbDrone.Core.MediaFiles public class TrackFileMovingService : IMoveTrackFiles { private readonly ITrackService _trackService; - //private readonly IUpdateTrackFileService _updateTrackFileService; + private readonly IAlbumService _albumService; + private readonly IUpdateTrackFileService _updateTrackFileService; private readonly IBuildFileNames _buildFileNames; private readonly IDiskTransferService _diskTransferService; private readonly IDiskProvider _diskProvider; @@ -36,8 +37,9 @@ namespace NzbDrone.Core.MediaFiles private readonly IConfigService _configService; private readonly Logger _logger; - public TrackFileMovingService(ITrackService episodeService, - //IUpdateEpisodeFileService updateEpisodeFileService, + public TrackFileMovingService(ITrackService trackService, + IAlbumService albumService, + IUpdateTrackFileService updateTrackFileService, IBuildFileNames buildFileNames, IDiskTransferService diskTransferService, IDiskProvider diskProvider, @@ -46,8 +48,9 @@ namespace NzbDrone.Core.MediaFiles IConfigService configService, Logger logger) { - _trackService = episodeService; - //_updateTrackFileService = updateEpisodeFileService; + _trackService = trackService; + _albumService = albumService; + _updateTrackFileService = updateTrackFileService; _buildFileNames = buildFileNames; _diskTransferService = diskTransferService; _diskProvider = diskProvider; @@ -59,112 +62,107 @@ namespace NzbDrone.Core.MediaFiles 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); + var tracks = _trackService.GetTracksByFileId(trackFile.Id); + var album = _albumService.GetAlbum(trackFile.AlbumId); + var newFileName = _buildFileNames.BuildTrackFileName(tracks, artist, album, trackFile); + var filePath = _buildFileNames.BuildTrackFilePath(artist, album, newFileName, Path.GetExtension(trackFile.RelativePath)); - //_logger.Debug("Renaming track file: {0} to {1}", trackFile, filePath); + EnsureTrackFolder(trackFile, artist, album, filePath); - //return TransferFile(trackFile, artist, tracks, filePath, TransferMode.Move); + _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); + var newFileName = _buildFileNames.BuildTrackFileName(localTrack.Tracks, localTrack.Artist, localTrack.Album, trackFile); + var filePath = _buildFileNames.BuildTrackFilePath(localTrack.Artist, localTrack.Album, newFileName, Path.GetExtension(localTrack.Path)); + + EnsureTrackFolder(trackFile, localTrack, filePath); - //_logger.Debug("Moving episode file: {0} to {1}", episodeFile.Path, filePath); + _logger.Debug("Moving track file: {0} to {1}", trackFile.Path, filePath); - //return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Move); + return TransferFile(trackFile, localTrack.Artist, localTrack.Tracks, 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)); + var newFileName = _buildFileNames.BuildTrackFileName(localTrack.Tracks, localTrack.Artist, localTrack.Album, trackFile); + var filePath = _buildFileNames.BuildTrackFilePath(localTrack.Artist, localTrack.Album, newFileName, Path.GetExtension(localTrack.Path)); - //EnsureEpisodeFolder(episodeFile, localEpisode, filePath); + EnsureTrackFolder(trackFile, localTrack, 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); - //} + if (_configService.CopyUsingHardlinks) + { + _logger.Debug("Hardlinking track file: {0} to {1}", trackFile.Path, filePath); + return TransferFile(trackFile, localTrack.Artist, localTrack.Tracks, filePath, TransferMode.HardLinkOrCopy); + } - //_logger.Debug("Copying episode file: {0} to {1}", episodeFile.Path, filePath); - //return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Copy); + _logger.Debug("Copying track file: {0} to {1}", trackFile.Path, filePath); + return TransferFile(trackFile, localTrack.Artist, localTrack.Tracks, filePath, TransferMode.Copy); } - private EpisodeFile TransferFile(EpisodeFile episodeFile, Series series, List episodes, string destinationFilePath, TransferMode mode) + private TrackFile TransferFile(TrackFile trackFile, Artist artist, List tracks, string destinationFilePath, TransferMode mode) { - // TODO - throw new System.NotImplementedException(); - //Ensure.That(episodeFile, () => episodeFile).IsNotNull(); - //Ensure.That(series, () => series).IsNotNull(); - //Ensure.That(destinationFilePath, () => destinationFilePath).IsValidPath(); + Ensure.That(trackFile, () => trackFile).IsNotNull(); + Ensure.That(artist, () => artist).IsNotNull(); + Ensure.That(destinationFilePath, () => destinationFilePath).IsValidPath(); - //var episodeFilePath = episodeFile.Path ?? Path.Combine(series.Path, episodeFile.RelativePath); + var trackFilePath = trackFile.Path ?? Path.Combine(artist.Path, trackFile.RelativePath); - //if (!_diskProvider.FileExists(episodeFilePath)) - //{ - // throw new FileNotFoundException("Episode file path does not exist", episodeFilePath); - //} + if (!_diskProvider.FileExists(trackFilePath)) + { + throw new FileNotFoundException("Track file path does not exist", trackFilePath); + } - //if (episodeFilePath == destinationFilePath) - //{ - // throw new SameFilenameException("File not moved, source and destination are the same", episodeFilePath); - //} + if (trackFilePath == destinationFilePath) + { + throw new SameFilenameException("File not moved, source and destination are the same", trackFilePath); + } - //_diskTransferService.TransferFile(episodeFilePath, destinationFilePath, mode); + _diskTransferService.TransferFile(trackFilePath, destinationFilePath, mode); - //episodeFile.RelativePath = series.Path.GetRelativePath(destinationFilePath); + trackFile.RelativePath = artist.Path.GetRelativePath(destinationFilePath); - //_updateTrackFileService.ChangeFileDateForFile(episodeFile, series, episodes); + _updateTrackFileService.ChangeFileDateForFile(trackFile, artist, tracks); - //try - //{ - // _mediaFileAttributeService.SetFolderLastWriteTime(series.Path, episodeFile.DateAdded); + try + { + _mediaFileAttributeService.SetFolderLastWriteTime(artist.Path, trackFile.DateAdded); - // if (series.SeasonFolder) - // { - // var seasonFolder = Path.GetDirectoryName(destinationFilePath); + if (artist.AlbumFolder) + { + var albumFolder = Path.GetDirectoryName(destinationFilePath); - // _mediaFileAttributeService.SetFolderLastWriteTime(seasonFolder, episodeFile.DateAdded); - // } - //} + _mediaFileAttributeService.SetFolderLastWriteTime(albumFolder, trackFile.DateAdded); + } + } - //catch (Exception ex) - //{ - // _logger.Warn(ex, "Unable to set last write time"); - //} + catch (Exception ex) + { + _logger.Warn(ex, "Unable to set last write time"); + } - //_mediaFileAttributeService.SetFilePermissions(destinationFilePath); + _mediaFileAttributeService.SetFilePermissions(destinationFilePath); - //return episodeFile; + return trackFile; } - private void EnsureEpisodeFolder(EpisodeFile episodeFile, LocalEpisode localEpisode, string filePath) + private void EnsureTrackFolder(TrackFile trackFile, LocalTrack localTrack, string filePath) { - EnsureEpisodeFolder(episodeFile, localEpisode.Series, localEpisode.SeasonNumber, filePath); + EnsureTrackFolder(trackFile, localTrack.Artist, localTrack.Album, filePath); } - private void EnsureEpisodeFolder(EpisodeFile episodeFile, Series series, int seasonNumber, string filePath) + private void EnsureTrackFolder(TrackFile trackFile, Artist artist, Album album, string filePath) { - var episodeFolder = Path.GetDirectoryName(filePath); - var seasonFolder = _buildFileNames.BuildSeasonPath(series, seasonNumber); - var seriesFolder = series.Path; - var rootFolder = new OsPath(seriesFolder).Directory.FullPath; + var trackFolder = Path.GetDirectoryName(filePath); + var albumFolder = _buildFileNames.BuildAlbumPath(artist, album); + var artistFolder = artist.Path; + var rootFolder = new OsPath(artistFolder).Directory.FullPath; if (!_diskProvider.FolderExists(rootFolder)) { @@ -172,26 +170,26 @@ namespace NzbDrone.Core.MediaFiles } var changed = false; - var newEvent = new EpisodeFolderCreatedEvent(series, episodeFile); + var newEvent = new TrackFolderCreatedEvent(artist, trackFile); - if (!_diskProvider.FolderExists(seriesFolder)) + if (!_diskProvider.FolderExists(artistFolder)) { - CreateFolder(seriesFolder); - newEvent.SeriesFolder = seriesFolder; + CreateFolder(artistFolder); + newEvent.ArtistFolder = artistFolder; changed = true; } - if (seriesFolder != seasonFolder && !_diskProvider.FolderExists(seasonFolder)) + if (artistFolder != albumFolder && !_diskProvider.FolderExists(albumFolder)) { - CreateFolder(seasonFolder); - newEvent.SeasonFolder = seasonFolder; + CreateFolder(albumFolder); + newEvent.AlbumFolder = albumFolder; changed = true; } - if (seasonFolder != episodeFolder && !_diskProvider.FolderExists(episodeFolder)) + if (albumFolder != trackFolder && !_diskProvider.FolderExists(trackFolder)) { - CreateFolder(episodeFolder); - newEvent.EpisodeFolder = episodeFolder; + CreateFolder(trackFolder); + newEvent.TrackFolder = trackFolder; changed = true; } diff --git a/src/NzbDrone.Core/MediaFiles/UpdateEpisodeFileService.cs b/src/NzbDrone.Core/MediaFiles/UpdateEpisodeFileService.cs deleted file mode 100644 index d157a6147..000000000 --- a/src/NzbDrone.Core/MediaFiles/UpdateEpisodeFileService.cs +++ /dev/null @@ -1,173 +0,0 @@ -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.Configuration; -using NzbDrone.Core.MediaFiles.Events; -using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.Tv; - -namespace NzbDrone.Core.MediaFiles -{ - public interface IUpdateEpisodeFileService - { - void ChangeFileDateForFile(EpisodeFile episodeFile, Series series, List episodes); - } - - public class UpdateEpisodeFileService : IUpdateEpisodeFileService, - IHandle - { - private readonly IDiskProvider _diskProvider; - private readonly IConfigService _configService; - private readonly IEpisodeService _episodeService; - private readonly Logger _logger; - - public UpdateEpisodeFileService(IDiskProvider diskProvider, - IConfigService configService, - IEpisodeService episodeService, - Logger logger) - { - _diskProvider = diskProvider; - _configService = configService; - _episodeService = episodeService; - _logger = logger; - } - - public void ChangeFileDateForFile(EpisodeFile episodeFile, Series series, List episodes) - { - ChangeFileDate(episodeFile, series, episodes); - } - - private bool ChangeFileDate(EpisodeFile episodeFile, Series series, List episodes) - { - var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath); - - switch (_configService.FileDate) - { - case FileDateType.LocalAirDate: - { - var airDate = episodes.First().AirDate; - var airTime = series.AirTime; - - if (airDate.IsNullOrWhiteSpace() || airTime.IsNullOrWhiteSpace()) - { - return false; - } - - return ChangeFileDateToLocalAirDate(episodeFilePath, airDate, airTime); - } - - case FileDateType.UtcAirDate: - { - var airDateUtc = episodes.First().AirDateUtc; - - if (!airDateUtc.HasValue) - { - return false; - } - - return ChangeFileDateToUtcAirDate(episodeFilePath, airDateUtc.Value); - } - } - - return false; - } - - public void Handle(SeriesScannedEvent message) - { - if (_configService.FileDate == FileDateType.None) - { - return; - } - - var episodes = _episodeService.EpisodesWithFiles(message.Series.Id); - - var episodeFiles = new List(); - var updated = new List(); - - foreach (var group in episodes.GroupBy(e => e.EpisodeFileId)) - { - var episodesInFile = group.Select(e => e).ToList(); - var episodeFile = episodesInFile.First().EpisodeFile; - - episodeFiles.Add(episodeFile); - - if (ChangeFileDate(episodeFile, message.Series, episodesInFile)) - { - updated.Add(episodeFile); - } - } - - if (updated.Any()) - { - _logger.ProgressDebug("Changed file date for {0} files of {1} in {2}", updated.Count, episodeFiles.Count, message.Series.Title); - } - - else - { - _logger.ProgressDebug("No file dates changed for {0}", message.Series.Title); - } - } - - private bool ChangeFileDateToLocalAirDate(string filePath, string fileDate, string fileTime) - { - DateTime airDate; - - if (DateTime.TryParse(fileDate + ' ' + fileTime, out airDate)) - { - // avoiding false +ve checks and set date skewing by not using UTC (Windows) - DateTime oldDateTime = _diskProvider.FileGetLastWrite(filePath); - - if (!DateTime.Equals(airDate, oldDateTime)) - { - try - { - _diskProvider.FileSetLastWriteTime(filePath, airDate); - _logger.Debug("Date of file [{0}] changed from '{1}' to '{2}'", filePath, oldDateTime, airDate); - - return true; - } - - catch (Exception ex) - { - _logger.Warn(ex, "Unable to set date of file [" + filePath + "]"); - } - } - } - - else - { - _logger.Debug("Could not create valid date to change file [{0}]", filePath); - } - - return false; - } - - private bool ChangeFileDateToUtcAirDate(string filePath, DateTime airDateUtc) - { - DateTime oldLastWrite = _diskProvider.FileGetLastWrite(filePath); - - if (!DateTime.Equals(airDateUtc, oldLastWrite)) - { - try - { - _diskProvider.FileSetLastWriteTime(filePath, airDateUtc); - _logger.Debug("Date of file [{0}] changed from '{1}' to '{2}'", filePath, oldLastWrite, airDateUtc); - - return true; - } - - catch (Exception ex) - { - _logger.Warn(ex, "Unable to set date of file [" + filePath + "]"); - } - } - - return false; - } - } -} diff --git a/src/NzbDrone.Core/MediaFiles/UpdateTrackFileService.cs b/src/NzbDrone.Core/MediaFiles/UpdateTrackFileService.cs new file mode 100644 index 000000000..e873a7970 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/UpdateTrackFileService.cs @@ -0,0 +1,128 @@ +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.Configuration; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Music; + +namespace NzbDrone.Core.MediaFiles +{ + public interface IUpdateTrackFileService + { + void ChangeFileDateForFile(TrackFile trackFile, Artist artist, List tracks); + } + + public class UpdateTrackFileService : IUpdateTrackFileService, + IHandle + { + private readonly IDiskProvider _diskProvider; + private readonly IAlbumService _albumService; + private readonly IConfigService _configService; + private readonly ITrackService _trackService; + private readonly Logger _logger; + + public UpdateTrackFileService(IDiskProvider diskProvider, + IConfigService configService, + ITrackService trackService, + IAlbumService albumService, + Logger logger) + { + _diskProvider = diskProvider; + _configService = configService; + _trackService = trackService; + _albumService = albumService; + _logger = logger; + } + + public void ChangeFileDateForFile(TrackFile trackFile, Artist artist, List tracks) + { + ChangeFileDate(trackFile, artist, tracks); + } + + private bool ChangeFileDate(TrackFile trackFile, Artist artist, List tracks) + { + var trackFilePath = Path.Combine(artist.Path, trackFile.RelativePath); + + switch (_configService.FileDate) + { + case FileDateType.AlbumReleaseDate: + { + var album = _albumService.GetAlbum(trackFile.AlbumId); + + if (!album.ReleaseDate.HasValue) + { + _logger.Debug("Could not create valid date to change file [{0}]", trackFilePath); + return false; + } + + var relDate = album.ReleaseDate.Value; + + // avoiding false +ve checks and set date skewing by not using UTC (Windows) + DateTime oldDateTime = _diskProvider.FileGetLastWrite(trackFilePath); + + if (!DateTime.Equals(relDate, oldDateTime)) + { + try + { + _diskProvider.FileSetLastWriteTime(trackFilePath, relDate); + _logger.Debug("Date of file [{0}] changed from '{1}' to '{2}'", trackFilePath, oldDateTime, relDate); + + return true; + } + + catch (Exception ex) + { + _logger.Warn(ex, "Unable to set date of file [" + trackFilePath + "]"); + } + } + + return false; + } + } + + return false; + } + + public void Handle(ArtistScannedEvent message) + { + if (_configService.FileDate == FileDateType.None) + { + return; + } + + var episodes = _trackService.TracksWithFiles(message.Artist.Id); + + var trackFiles = new List(); + var updated = new List(); + + foreach (var group in episodes.GroupBy(e => e.TrackFileId)) + { + var tracksInFile = group.Select(e => e).ToList(); + var trackFile = tracksInFile.First().TrackFile; + + trackFiles.Add(trackFile); + + if (ChangeFileDate(trackFile, message.Artist, tracksInFile)) + { + updated.Add(trackFile); + } + } + + if (updated.Any()) + { + _logger.ProgressDebug("Changed file date for {0} files of {1} in {2}", updated.Count, trackFiles.Count, message.Artist.Name); + } + + else + { + _logger.ProgressDebug("No file dates changed for {0}", message.Artist.Name); + } + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs index 0e952a676..465370caf 100644 --- a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using System.Linq; using NLog; using NzbDrone.Common.Disk; @@ -9,7 +9,6 @@ namespace NzbDrone.Core.MediaFiles { public interface IUpgradeMediaFiles { - //EpisodeFileMoveResult UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode, bool copyOnly = false); TrackFileMoveResult UpgradeTrackFile(TrackFile trackFile, LocalTrack localTrack, bool copyOnly = false); } @@ -17,7 +16,6 @@ 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; @@ -46,13 +44,13 @@ namespace NzbDrone.Core.MediaFiles foreach (var existingFile in existingFiles) { var file = existingFile.First(); - var episodeFilePath = Path.Combine(localTrack.Artist.Path, file.RelativePath); - var subfolder = _diskProvider.GetParentFolder(localTrack.Artist.Path).GetRelativePath(_diskProvider.GetParentFolder(episodeFilePath)); + var trackFilePath = Path.Combine(localTrack.Artist.Path, file.RelativePath); + var subfolder = _diskProvider.GetParentFolder(localTrack.Artist.Path).GetRelativePath(_diskProvider.GetParentFolder(trackFilePath)); - if (_diskProvider.FileExists(episodeFilePath)) + if (_diskProvider.FileExists(trackFilePath)) { - _logger.Debug("Removing existing episode file: {0}", file); - _recycleBinProvider.DeleteFile(episodeFilePath, subfolder); + _logger.Debug("Removing existing track file: {0}", file); + _recycleBinProvider.DeleteFile(trackFilePath, subfolder); } moveFileResult.OldFiles.Add(file); diff --git a/src/NzbDrone.Core/Tv/Commands/MoveSeriesCommand.cs b/src/NzbDrone.Core/Music/Commands/MoveArtistCommand.cs similarity index 63% rename from src/NzbDrone.Core/Tv/Commands/MoveSeriesCommand.cs rename to src/NzbDrone.Core/Music/Commands/MoveArtistCommand.cs index 1a283e80d..78dc161e0 100644 --- a/src/NzbDrone.Core/Tv/Commands/MoveSeriesCommand.cs +++ b/src/NzbDrone.Core/Music/Commands/MoveArtistCommand.cs @@ -1,10 +1,10 @@ using NzbDrone.Core.Messaging.Commands; -namespace NzbDrone.Core.Tv.Commands +namespace NzbDrone.Core.Music.Commands { - public class MoveSeriesCommand : Command + public class MoveArtistCommand : Command { - public int SeriesId { get; set; } + public int ArtistId { get; set; } public string SourcePath { get; set; } public string DestinationPath { get; set; } public string DestinationRootFolder { get; set; } diff --git a/src/NzbDrone.Core/Tv/Events/SeriesMovedEvent.cs b/src/NzbDrone.Core/Music/Events/ArtistMovedEvent.cs similarity index 56% rename from src/NzbDrone.Core/Tv/Events/SeriesMovedEvent.cs rename to src/NzbDrone.Core/Music/Events/ArtistMovedEvent.cs index 72c48c269..1de12f9f7 100644 --- a/src/NzbDrone.Core/Tv/Events/SeriesMovedEvent.cs +++ b/src/NzbDrone.Core/Music/Events/ArtistMovedEvent.cs @@ -1,16 +1,16 @@ using NzbDrone.Common.Messaging; -namespace NzbDrone.Core.Tv.Events +namespace NzbDrone.Core.Music.Events { - public class SeriesMovedEvent : IEvent + public class ArtistMovedEvent : IEvent { - public Series Series { get; set; } + public Artist Artist { get; set; } public string SourcePath { get; set; } public string DestinationPath { get; set; } - public SeriesMovedEvent(Series series, string sourcePath, string destinationPath) + public ArtistMovedEvent(Artist artist, string sourcePath, string destinationPath) { - Series = series; + Artist = artist; SourcePath = sourcePath; DestinationPath = destinationPath; } diff --git a/src/NzbDrone.Core/Tv/MoveSeriesService.cs b/src/NzbDrone.Core/Music/MoveArtistService.cs similarity index 65% rename from src/NzbDrone.Core/Tv/MoveSeriesService.cs rename to src/NzbDrone.Core/Music/MoveArtistService.cs index a6ffb0578..b1f4c52a1 100644 --- a/src/NzbDrone.Core/Tv/MoveSeriesService.cs +++ b/src/NzbDrone.Core/Music/MoveArtistService.cs @@ -6,45 +6,45 @@ using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Organizer; -using NzbDrone.Core.Tv.Commands; -using NzbDrone.Core.Tv.Events; +using NzbDrone.Core.Music.Commands; +using NzbDrone.Core.Music.Events; -namespace NzbDrone.Core.Tv +namespace NzbDrone.Core.Music { - public class MoveSeriesService : IExecute + public class MoveArtistService : IExecute { - private readonly ISeriesService _seriesService; + private readonly IArtistService _artistService; private readonly IBuildFileNames _filenameBuilder; private readonly IDiskTransferService _diskTransferService; private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; - public MoveSeriesService(ISeriesService seriesService, + public MoveArtistService(IArtistService artistService, IBuildFileNames filenameBuilder, IDiskTransferService diskTransferService, IEventAggregator eventAggregator, Logger logger) { - _seriesService = seriesService; + _artistService = artistService; _filenameBuilder = filenameBuilder; _diskTransferService = diskTransferService; _eventAggregator = eventAggregator; _logger = logger; } - public void Execute(MoveSeriesCommand message) + public void Execute(MoveArtistCommand message) { - var series = _seriesService.GetSeries(message.SeriesId); + var artist = _artistService.GetArtist(message.ArtistId); var source = message.SourcePath; var destination = message.DestinationPath; if (!message.DestinationRootFolder.IsNullOrWhiteSpace()) { - _logger.Debug("Buiding destination path using root folder: {0} and the series title", message.DestinationRootFolder); - destination = Path.Combine(message.DestinationRootFolder, _filenameBuilder.GetSeriesFolder(series)); + _logger.Debug("Buiding destination path using root folder: {0} and the artist name", message.DestinationRootFolder); + destination = Path.Combine(message.DestinationRootFolder, _filenameBuilder.GetArtistFolder(artist)); } - _logger.ProgressInfo("Moving {0} from '{1}' to '{2}'", series.Title, source, destination); + _logger.ProgressInfo("Moving {0} from '{1}' to '{2}'", artist.Name, source, destination); //TODO: Move to transactional disk operations try @@ -53,17 +53,17 @@ namespace NzbDrone.Core.Tv } catch (IOException ex) { - _logger.Error(ex, "Unable to move series from '{0}' to '{1}'", source, destination); + _logger.Error(ex, "Unable to move artist from '{0}' to '{1}'", source, destination); throw; } - _logger.ProgressInfo("{0} moved successfully to {1}", series.Title, series.Path); + _logger.ProgressInfo("{0} moved successfully to {1}", artist.Name, artist.Path); - //Update the series path to the new path - series.Path = destination; - series = _seriesService.UpdateSeries(series); + //Update the artist path to the new path + artist.Path = destination; + artist = _artistService.UpdateArtist(artist); - _eventAggregator.PublishEvent(new SeriesMovedEvent(series, source, destination)); + _eventAggregator.PublishEvent(new ArtistMovedEvent(artist, source, destination)); } } } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index aac2ccb85..c15be3261 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -295,6 +295,7 @@ Code + @@ -731,7 +732,10 @@ + + + @@ -749,8 +753,6 @@ - - @@ -771,7 +773,6 @@ - @@ -793,15 +794,13 @@ - - - + @@ -828,6 +827,7 @@ + @@ -861,9 +861,11 @@ - + + + @@ -945,6 +947,8 @@ + + @@ -1055,9 +1059,7 @@ Code - - @@ -1143,7 +1145,6 @@ - @@ -1157,11 +1158,9 @@ - - diff --git a/src/NzbDrone.Core/Organizer/AbsoluteEpisodeFormat.cs b/src/NzbDrone.Core/Organizer/AbsoluteEpisodeFormat.cs deleted file mode 100644 index a56466b7a..000000000 --- a/src/NzbDrone.Core/Organizer/AbsoluteEpisodeFormat.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace NzbDrone.Core.Organizer -{ - public class EpisodeFormat - { - public string Separator { get; set; } - public string EpisodePattern { get; set; } - public string EpisodeSeparator { get; set; } - public string SeasonEpisodePattern { get; set; } - } -} diff --git a/src/NzbDrone.Core/Organizer/AbsoluteTrackFormat.cs b/src/NzbDrone.Core/Organizer/AbsoluteTrackFormat.cs new file mode 100644 index 000000000..77febda7b --- /dev/null +++ b/src/NzbDrone.Core/Organizer/AbsoluteTrackFormat.cs @@ -0,0 +1,9 @@ +namespace NzbDrone.Core.Organizer +{ + public class TrackFormat + { + public string Separator { get; set; } + public string TrackPattern { get; set; } + public string TrackSeparator { get; set; } + } +} diff --git a/src/NzbDrone.Core/Organizer/BasicNamingConfig.cs b/src/NzbDrone.Core/Organizer/BasicNamingConfig.cs index b0dc16f6a..824f45cf8 100644 --- a/src/NzbDrone.Core/Organizer/BasicNamingConfig.cs +++ b/src/NzbDrone.Core/Organizer/BasicNamingConfig.cs @@ -2,8 +2,8 @@ { public class BasicNamingConfig { - public bool IncludeSeriesTitle { get; set; } - public bool IncludeEpisodeTitle { get; set; } + public bool IncludeArtistName { get; set; } + public bool IncludeAlbumTitle { get; set; } public bool IncludeQuality { get; set; } public bool ReplaceSpaces { get; set; } public string Separator { get; set; } diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index c67e23e48..b8374f866 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -9,6 +9,7 @@ using NzbDrone.Common.Cache; using NzbDrone.Common.EnsureThat; using NzbDrone.Common.Extensions; using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.Music; @@ -17,28 +18,20 @@ 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 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); string GetAlbumFolder(Artist artist, Album album, NamingConfig namingConfig = null); - string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null); - - // TODO: Implement Music functions - //string GetArtistFolder(Artist artist, NamingConfig namingConfig = null); } public class FileNameBuilder : IBuildFileNames { private readonly INamingConfigService _namingConfigService; private readonly IQualityDefinitionService _qualityDefinitionService; - private readonly ICached _episodeFormatCache; - private readonly ICached _absoluteEpisodeFormatCache; + private readonly ICached _trackFormatCache; + private readonly ICached _absoluteTrackFormatCache; private readonly Logger _logger; private static readonly Regex TitleRegex = new Regex(@"\{(?[- ._\[(]*)(?(?:[a-z0-9]+)(?:(?[- ._]+)(?:[a-z0-9]+))?)(?::(?[a-z0-9]+))?(?[- ._)\]]*)\}", @@ -91,69 +84,11 @@ namespace NzbDrone.Core.Organizer { _namingConfigService = namingConfigService; _qualityDefinitionService = qualityDefinitionService; - _episodeFormatCache = cacheManager.GetCache(GetType(), "episodeFormat"); - _absoluteEpisodeFormatCache = cacheManager.GetCache(GetType(), "absoluteEpisodeFormat"); + _trackFormatCache = cacheManager.GetCache(GetType(), "trackFormat"); + _absoluteTrackFormatCache = cacheManager.GetCache(GetType(), "absoluteTrackFormat"); _logger = logger; } - public string BuildFileName(List episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null) - { - if (namingConfig == null) - { - namingConfig = _namingConfigService.GetConfig(); - } - - if (!namingConfig.RenameEpisodes) - { - return GetOriginalTitle(episodeFile); - } - - if (namingConfig.StandardEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Standard) - { - throw new NamingFormatException("Standard episode format cannot be empty"); - } - - if (namingConfig.DailyEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Daily) - { - throw new NamingFormatException("Daily episode format cannot be empty"); - } - - if (namingConfig.AnimeEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Anime) - { - throw new NamingFormatException("Anime episode format cannot be empty"); - } - - var pattern = namingConfig.StandardEpisodeFormat; - var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); - - episodes = episodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber).ToList(); - - if (series.SeriesType == SeriesTypes.Daily) - { - pattern = namingConfig.DailyEpisodeFormat; - } - - if (series.SeriesType == SeriesTypes.Anime && episodes.All(e => e.AbsoluteEpisodeNumber.HasValue)) - { - pattern = namingConfig.AnimeEpisodeFormat; - } - - pattern = AddSeasonEpisodeNumberingTokens(pattern, tokenHandlers, episodes, namingConfig); - pattern = AddAbsoluteNumberingTokens(pattern, tokenHandlers, series, episodes, namingConfig); - - AddSeriesTokens(tokenHandlers, series); - AddEpisodeTokens(tokenHandlers, episodes); - AddEpisodeFileTokens(tokenHandlers, episodeFile); - AddQualityTokens(tokenHandlers, series, episodeFile); - AddMediaInfoTokens(tokenHandlers, episodeFile); - - 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 BuildTrackFileName(List tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig namingConfig = null) { if (namingConfig == null) @@ -175,8 +110,6 @@ namespace NzbDrone.Core.Organizer 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); @@ -186,7 +119,7 @@ namespace NzbDrone.Core.Organizer AddTrackTokens(tokenHandlers, tracks); AddTrackFileTokens(tokenHandlers, trackFile); AddQualityTokens(tokenHandlers, artist, trackFile); - //AddMediaInfoTokens(tokenHandlers, trackFile); TODO ReWork MediaInfo for Tracks + AddMediaInfoTokens(tokenHandlers, trackFile); var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString()); @@ -195,15 +128,6 @@ namespace NzbDrone.Core.Organizer return fileName; } - public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension) - { - Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); - - var path = BuildSeasonPath(series, seasonNumber); - - return Path.Combine(path, fileName + extension); - } - public string BuildTrackFilePath(Artist artist, Album album, string fileName, string extension) { Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); @@ -213,29 +137,6 @@ namespace NzbDrone.Core.Organizer return Path.Combine(path, fileName + extension); } - public string BuildSeasonPath(Series series, int seasonNumber) - { - var path = series.Path; - - if (series.SeasonFolder) - { - if (seasonNumber == 0) - { - path = Path.Combine(path, "Specials"); - } - else - { - var seasonFolder = GetSeasonFolder(series, seasonNumber); - - seasonFolder = CleanFileName(seasonFolder); - - path = Path.Combine(path, seasonFolder); - } - } - - return path; - } - public string BuildAlbumPath(Artist artist, Album album) { var path = artist.Path; @@ -256,20 +157,19 @@ namespace NzbDrone.Core.Organizer public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec) { - var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat).LastOrDefault(); + var trackFormat = GetTrackFormat(nameSpec.StandardTrackFormat).LastOrDefault(); - if (episodeFormat == null) + if (trackFormat == null) { return new BasicNamingConfig(); } var basicNamingConfig = new BasicNamingConfig { - Separator = episodeFormat.Separator, - NumberStyle = episodeFormat.SeasonEpisodePattern + Separator = trackFormat.Separator }; - var titleTokens = TitleRegex.Matches(nameSpec.StandardEpisodeFormat); + var titleTokens = TitleRegex.Matches(nameSpec.StandardTrackFormat); foreach (Match match in titleTokens) { @@ -281,14 +181,14 @@ namespace NzbDrone.Core.Organizer basicNamingConfig.ReplaceSpaces = true; } - if (token.StartsWith("{Series", StringComparison.InvariantCultureIgnoreCase)) + if (token.StartsWith("{Artist", StringComparison.InvariantCultureIgnoreCase)) { - basicNamingConfig.IncludeSeriesTitle = true; + basicNamingConfig.IncludeArtistName = true; } - if (token.StartsWith("{Episode", StringComparison.InvariantCultureIgnoreCase)) + if (token.StartsWith("{Album", StringComparison.InvariantCultureIgnoreCase)) { - basicNamingConfig.IncludeEpisodeTitle = true; + basicNamingConfig.IncludeAlbumTitle = true; } if (token.StartsWith("{Quality", StringComparison.InvariantCultureIgnoreCase)) @@ -300,20 +200,6 @@ namespace NzbDrone.Core.Organizer return basicNamingConfig; } - public string GetSeriesFolder(Series series, NamingConfig namingConfig = null) - { - if (namingConfig == null) - { - namingConfig = _namingConfigService.GetConfig(); - } - - var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); - - AddSeriesTokens(tokenHandlers, series); - - return CleanFolderName(ReplaceTokens(namingConfig.SeriesFolderFormat, tokenHandlers, namingConfig)); - } - public string GetArtistFolder(Artist artist, NamingConfig namingConfig = null) { if (namingConfig == null) @@ -328,21 +214,6 @@ namespace NzbDrone.Core.Organizer return CleanFolderName(ReplaceTokens(namingConfig.ArtistFolderFormat, tokenHandlers, namingConfig)); } - public string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null) - { - if (namingConfig == null) - { - namingConfig = _namingConfigService.GetConfig(); - } - - var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); - - AddSeriesTokens(tokenHandlers, series); - AddSeasonTokens(tokenHandlers, seasonNumber); - - return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig)); - } - public string GetAlbumFolder(Artist artist, Album album, NamingConfig namingConfig = null) { if (namingConfig == null) @@ -387,12 +258,6 @@ namespace NzbDrone.Core.Organizer return name.Trim(' ', '.'); } - private void AddSeriesTokens(Dictionary> tokenHandlers, Series series) - { - tokenHandlers["{Series Title}"] = m => series.Title; - tokenHandlers["{Series CleanTitle}"] = m => CleanTitle(series.Title); - } - private void AddArtistTokens(Dictionary> tokenHandlers, Artist artist) { tokenHandlers["{Artist Name}"] = m => artist.Name; @@ -413,163 +278,12 @@ namespace NzbDrone.Core.Organizer } } - private string AddSeasonEpisodeNumberingTokens(string pattern, Dictionary> tokenHandlers, List episodes, NamingConfig namingConfig) - { - var episodeFormats = GetEpisodeFormat(pattern).DistinctBy(v => v.SeasonEpisodePattern).ToList(); - - int index = 1; - foreach (var episodeFormat in episodeFormats) - { - var seasonEpisodePattern = episodeFormat.SeasonEpisodePattern; - string formatPattern; - - switch ((MultiEpisodeStyle)namingConfig.MultiEpisodeStyle) - { - case MultiEpisodeStyle.Duplicate: - formatPattern = episodeFormat.Separator + episodeFormat.SeasonEpisodePattern; - seasonEpisodePattern = FormatNumberTokens(seasonEpisodePattern, formatPattern, episodes); - break; - - case MultiEpisodeStyle.Repeat: - formatPattern = episodeFormat.EpisodeSeparator + episodeFormat.EpisodePattern; - seasonEpisodePattern = FormatNumberTokens(seasonEpisodePattern, formatPattern, episodes); - break; - - case MultiEpisodeStyle.Scene: - formatPattern = "-" + episodeFormat.EpisodeSeparator + episodeFormat.EpisodePattern; - seasonEpisodePattern = FormatNumberTokens(seasonEpisodePattern, formatPattern, episodes); - break; - - case MultiEpisodeStyle.Range: - formatPattern = "-" + episodeFormat.EpisodePattern; - seasonEpisodePattern = FormatRangeNumberTokens(seasonEpisodePattern, formatPattern, episodes); - break; - - case MultiEpisodeStyle.PrefixedRange: - formatPattern = "-" + episodeFormat.EpisodeSeparator + episodeFormat.EpisodePattern; - seasonEpisodePattern = FormatRangeNumberTokens(seasonEpisodePattern, formatPattern, episodes); - break; - - //MultiEpisodeStyle.Extend - default: - formatPattern = "-" + episodeFormat.EpisodePattern; - seasonEpisodePattern = FormatNumberTokens(seasonEpisodePattern, formatPattern, episodes); - break; - } - - var token = string.Format("{{Season Episode{0}}}", index++); - pattern = pattern.Replace(episodeFormat.SeasonEpisodePattern, token); - tokenHandlers[token] = m => seasonEpisodePattern; - } - - AddSeasonTokens(tokenHandlers, episodes.First().SeasonNumber); - - if (episodes.Count > 1) - { - tokenHandlers["{Episode}"] = m => episodes.First().EpisodeNumber.ToString(m.CustomFormat) + "-" + episodes.Last().EpisodeNumber.ToString(m.CustomFormat); - } - else - { - tokenHandlers["{Episode}"] = m => episodes.First().EpisodeNumber.ToString(m.CustomFormat); - } - - return pattern; - } - - private string AddAbsoluteNumberingTokens(string pattern, Dictionary> tokenHandlers, Series series, List episodes, NamingConfig namingConfig) - { - var absoluteEpisodeFormats = GetAbsoluteFormat(pattern).DistinctBy(v => v.AbsoluteEpisodePattern).ToList(); - - int index = 1; - foreach (var absoluteEpisodeFormat in absoluteEpisodeFormats) - { - if (series.SeriesType != SeriesTypes.Anime) - { - pattern = pattern.Replace(absoluteEpisodeFormat.AbsoluteEpisodePattern, ""); - continue; - } - - var absoluteEpisodePattern = absoluteEpisodeFormat.AbsoluteEpisodePattern; - string formatPattern; - - switch ((MultiEpisodeStyle) namingConfig.MultiEpisodeStyle) - { - - case MultiEpisodeStyle.Duplicate: - formatPattern = absoluteEpisodeFormat.Separator + absoluteEpisodeFormat.AbsoluteEpisodePattern; - absoluteEpisodePattern = FormatAbsoluteNumberTokens(absoluteEpisodePattern, formatPattern, episodes); - break; - - case MultiEpisodeStyle.Repeat: - var repeatSeparator = absoluteEpisodeFormat.Separator.Trim().IsNullOrWhiteSpace() ? " " : absoluteEpisodeFormat.Separator.Trim(); - - formatPattern = repeatSeparator + absoluteEpisodeFormat.AbsoluteEpisodePattern; - absoluteEpisodePattern = FormatAbsoluteNumberTokens(absoluteEpisodePattern, formatPattern, episodes); - break; - - case MultiEpisodeStyle.Scene: - formatPattern = "-" + absoluteEpisodeFormat.AbsoluteEpisodePattern; - absoluteEpisodePattern = FormatAbsoluteNumberTokens(absoluteEpisodePattern, formatPattern, episodes); - break; - - case MultiEpisodeStyle.Range: - case MultiEpisodeStyle.PrefixedRange: - formatPattern = "-" + absoluteEpisodeFormat.AbsoluteEpisodePattern; - var eps = new List {episodes.First()}; - - if (episodes.Count > 1) eps.Add(episodes.Last()); - - absoluteEpisodePattern = FormatAbsoluteNumberTokens(absoluteEpisodePattern, formatPattern, eps); - break; - - //MultiEpisodeStyle.Extend - default: - formatPattern = "-" + absoluteEpisodeFormat.AbsoluteEpisodePattern; - absoluteEpisodePattern = FormatAbsoluteNumberTokens(absoluteEpisodePattern, formatPattern, episodes); - break; - } - - var token = string.Format("{{Absolute Pattern{0}}}", index++); - pattern = pattern.Replace(absoluteEpisodeFormat.AbsoluteEpisodePattern, token); - tokenHandlers[token] = m => absoluteEpisodePattern; - } - - return pattern; - } - - private void AddSeasonTokens(Dictionary> tokenHandlers, int seasonNumber) - { - tokenHandlers["{Season}"] = m => seasonNumber.ToString(m.CustomFormat); - } - - private void AddEpisodeTokens(Dictionary> tokenHandlers, List episodes) - { - if (!episodes.First().AirDate.IsNullOrWhiteSpace()) - { - tokenHandlers["{Air Date}"] = m => episodes.First().AirDate.Replace('-', ' '); - } - else - { - tokenHandlers["{Air Date}"] = m => "Unknown"; - } - - tokenHandlers["{Episode Title}"] = m => GetEpisodeTitle(episodes, "+"); - 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); - tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(episodeFile); - tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? m.DefaultValue("Lidarr"); - } - private void AddTrackFileTokens(Dictionary> tokenHandlers, TrackFile trackFile) { tokenHandlers["{Original Title}"] = m => GetOriginalTitle(trackFile); @@ -601,79 +315,20 @@ namespace NzbDrone.Core.Organizer //tokenHandlers["{Quality Real}"] = m => qualityReal; } - private void AddMediaInfoTokens(Dictionary> tokenHandlers, EpisodeFile episodeFile) - { - if (episodeFile.MediaInfo == null) return; - - string videoCodec; - switch (episodeFile.MediaInfo.VideoCodec) - { - case "AVC": - if (episodeFile.SceneName.IsNotNullOrWhiteSpace() && Path.GetFileNameWithoutExtension(episodeFile.SceneName).Contains("h264")) - { - videoCodec = "h264"; - } - else - { - videoCodec = "x264"; - } - break; - - case "V_MPEGH/ISO/HEVC": - if (episodeFile.SceneName.IsNotNullOrWhiteSpace() && Path.GetFileNameWithoutExtension(episodeFile.SceneName).Contains("h265")) - { - videoCodec = "h265"; - } - else - { - videoCodec = "x265"; - } - break; - - case "MPEG-2 Video": - videoCodec = "MPEG2"; - break; - - default: - videoCodec = episodeFile.MediaInfo.VideoCodec; - break; - } - - string audioCodec; - switch (episodeFile.MediaInfo.AudioFormat) - { - case "AC-3": - audioCodec = "AC3"; - break; - - case "E-AC-3": - audioCodec = "EAC3"; - break; - - case "MPEG Audio": - if (episodeFile.MediaInfo.AudioProfile == "Layer 3") - { - audioCodec = "MP3"; - } - else - { - audioCodec = episodeFile.MediaInfo.AudioFormat; - } - break; - - case "DTS": - audioCodec = episodeFile.MediaInfo.AudioFormat; - break; - - default: - audioCodec = episodeFile.MediaInfo.AudioFormat; - break; - } - - var mediaInfoAudioLanguages = GetLanguagesToken(episodeFile.MediaInfo.AudioLanguages); + private void AddMediaInfoTokens(Dictionary> tokenHandlers, TrackFile trackFile) + { + if (trackFile.MediaInfo == null) + { + return; + } + + var audioCodec = MediaInfoFormatter.FormatAudioCodec(trackFile.MediaInfo); + var audioChannels = MediaInfoFormatter.FormatAudioChannels(trackFile.MediaInfo); + + var mediaInfoAudioLanguages = GetLanguagesToken(trackFile.MediaInfo.AudioLanguages); if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace()) { - mediaInfoAudioLanguages = string.Format("[{0}]", mediaInfoAudioLanguages); + mediaInfoAudioLanguages = $"[{mediaInfoAudioLanguages}]"; } if (mediaInfoAudioLanguages == "[EN]") @@ -681,28 +336,24 @@ namespace NzbDrone.Core.Organizer mediaInfoAudioLanguages = string.Empty; } - var mediaInfoSubtitleLanguages = GetLanguagesToken(episodeFile.MediaInfo.Subtitles); + var mediaInfoSubtitleLanguages = GetLanguagesToken(trackFile.MediaInfo.Subtitles); if (!mediaInfoSubtitleLanguages.IsNullOrWhiteSpace()) { - mediaInfoSubtitleLanguages = string.Format("[{0}]", mediaInfoSubtitleLanguages); + mediaInfoSubtitleLanguages = $"[{mediaInfoSubtitleLanguages}]"; } - var videoBitDepth = episodeFile.MediaInfo.VideoBitDepth > 0 ? episodeFile.MediaInfo.VideoBitDepth.ToString() : string.Empty; - var audioChannels = episodeFile.MediaInfo.FormattedAudioChannels > 0 ? - episodeFile.MediaInfo.FormattedAudioChannels.ToString("F1", CultureInfo.InvariantCulture) : + var videoBitDepth = trackFile.MediaInfo.VideoBitDepth > 0 ? trackFile.MediaInfo.VideoBitDepth.ToString() : string.Empty; + var audioChannelsFormatted = audioChannels > 0 ? + audioChannels.ToString("F1", CultureInfo.InvariantCulture) : string.Empty; - tokenHandlers["{MediaInfo Video}"] = m => videoCodec; - tokenHandlers["{MediaInfo VideoCodec}"] = m => videoCodec; - tokenHandlers["{MediaInfo VideoBitDepth}"] = m => videoBitDepth; - tokenHandlers["{MediaInfo Audio}"] = m => audioCodec; tokenHandlers["{MediaInfo AudioCodec}"] = m => audioCodec; - tokenHandlers["{MediaInfo AudioChannels}"] = m => audioChannels; + tokenHandlers["{MediaInfo AudioChannels}"] = m => audioChannelsFormatted; - tokenHandlers["{MediaInfo Simple}"] = m => string.Format("{0} {1}", videoCodec, audioCodec); + tokenHandlers["{MediaInfo Simple}"] = m => $"{audioCodec}"; - tokenHandlers["{MediaInfo Full}"] = m => string.Format("{0} {1}{2} {3}", videoCodec, audioCodec, mediaInfoAudioLanguages, mediaInfoSubtitleLanguages); + tokenHandlers["{MediaInfo Full}"] = m => $"{audioCodec}{mediaInfoAudioLanguages} {mediaInfoSubtitleLanguages}"; } private string GetLanguagesToken(string mediaInfoLanguages) @@ -782,20 +433,6 @@ namespace NzbDrone.Core.Organizer return replacementText; } - private string FormatNumberTokens(string basePattern, string formatPattern, List episodes) - { - var pattern = string.Empty; - - for (int i = 0; i < episodes.Count; i++) - { - var patternToReplace = i == 0 ? basePattern : formatPattern; - - pattern += EpisodeRegex.Replace(patternToReplace, match => ReplaceNumberToken(match.Groups["episode"].Value, episodes[i].EpisodeNumber)); - } - - return ReplaceSeasonTokens(pattern, episodes.First().SeasonNumber); - } - private string FormatTrackNumberTokens(string basePattern, string formatPattern, List tracks) { var pattern = string.Empty; @@ -810,34 +447,6 @@ namespace NzbDrone.Core.Organizer return pattern; } - private string FormatAbsoluteNumberTokens(string basePattern, string formatPattern, List episodes) - { - var pattern = string.Empty; - - for (int i = 0; i < episodes.Count; i++) - { - var patternToReplace = i == 0 ? basePattern : formatPattern; - - pattern += AbsoluteEpisodeRegex.Replace(patternToReplace, match => ReplaceNumberToken(match.Groups["absolute"].Value, episodes[i].AbsoluteEpisodeNumber.Value)); - } - - return ReplaceSeasonTokens(pattern, episodes.First().SeasonNumber); - } - - private string FormatRangeNumberTokens(string seasonEpisodePattern, string formatPattern, List episodes) - { - var eps = new List { episodes.First() }; - - if (episodes.Count > 1) eps.Add(episodes.Last()); - - return FormatNumberTokens(seasonEpisodePattern, formatPattern, eps); - } - - private string ReplaceSeasonTokens(string pattern, int seasonNumber) - { - return SeasonRegex.Replace(pattern, match => ReplaceNumberToken(match.Groups["season"].Value, seasonNumber)); - } - private string ReplaceNumberToken(string token, int value) { var split = token.Trim('{', '}').Split(':'); @@ -846,52 +455,27 @@ namespace NzbDrone.Core.Organizer return value.ToString(split[1]); } - private EpisodeFormat[] GetEpisodeFormat(string pattern) + private TrackFormat[] GetTrackFormat(string pattern) { - return _episodeFormatCache.Get(pattern, () => SeasonEpisodePatternRegex.Matches(pattern).OfType() - .Select(match => new EpisodeFormat + return _trackFormatCache.Get(pattern, () => SeasonEpisodePatternRegex.Matches(pattern).OfType() + .Select(match => new TrackFormat { - EpisodeSeparator = match.Groups["episodeSeparator"].Value, + TrackSeparator = match.Groups["episodeSeparator"].Value, Separator = match.Groups["separator"].Value, - EpisodePattern = match.Groups["episode"].Value, - SeasonEpisodePattern = match.Groups["seasonEpisode"].Value, + TrackPattern = match.Groups["episode"].Value, }).ToArray()); } - private AbsoluteEpisodeFormat[] GetAbsoluteFormat(string pattern) + private AbsoluteTrackFormat[] GetAbsoluteFormat(string pattern) { - return _absoluteEpisodeFormatCache.Get(pattern, () => AbsoluteEpisodePatternRegex.Matches(pattern).OfType() - .Select(match => new AbsoluteEpisodeFormat + return _absoluteTrackFormatCache.Get(pattern, () => AbsoluteEpisodePatternRegex.Matches(pattern).OfType() + .Select(match => new AbsoluteTrackFormat { Separator = match.Groups["separator"].Value.IsNotNullOrWhiteSpace() ? match.Groups["separator"].Value : "-", - AbsoluteEpisodePattern = match.Groups["absolute"].Value + AbsoluteTrackPattern = match.Groups["absolute"].Value }).ToArray()); } - private string GetEpisodeTitle(List episodes, string separator) - { - separator = string.Format(" {0} ", separator.Trim()); - - if (episodes.Count == 1) - { - return episodes.First().Title.TrimEnd(EpisodeTitleTrimCharacters); - } - - var titles = episodes.Select(c => c.Title.TrimEnd(EpisodeTitleTrimCharacters)) - .Select(CleanupEpisodeTitle) - .Distinct() - .ToList(); - - if (titles.All(t => t.IsNullOrWhiteSpace())) - { - titles = episodes.Select(c => c.Title.TrimEnd(EpisodeTitleTrimCharacters)) - .Distinct() - .ToList(); - } - - return string.Join(separator, titles); - } - private string GetTrackTitle(List tracks, string separator) { separator = string.Format(" {0} ", separator.Trim()); @@ -902,7 +486,7 @@ namespace NzbDrone.Core.Organizer } var titles = tracks.Select(c => c.Title.TrimEnd(EpisodeTitleTrimCharacters)) - .Select(CleanupEpisodeTitle) + .Select(CleanupTrackTitle) .Distinct() .ToList(); @@ -916,7 +500,7 @@ namespace NzbDrone.Core.Organizer return string.Join(separator, titles); } - private string CleanupEpisodeTitle(string title) + private string CleanupTrackTitle(string title) { //this will remove (1),(2) from the end of multi part episodes. return MultiPartCleanupRegex.Replace(title, string.Empty).Trim(); diff --git a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs index 2d17ba65e..1ef6af4c9 100644 --- a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs +++ b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Qualities; -using NzbDrone.Core.Tv; using NzbDrone.Core.Music; using NzbDrone.Core.MediaFiles.MediaInfo; @@ -9,14 +8,8 @@ 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); } @@ -24,35 +17,17 @@ namespace NzbDrone.Core.Organizer 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; - private static EpisodeFile _animeMultiEpisodeFile; public FileNameSampleService(IBuildFileNames buildFileNames) { _buildFileNames = buildFileNames; - _standardSeries = new Series - { - SeriesType = SeriesTypes.Standard, - Title = "Series Title (2010)" - }; - _standardArtist = new Artist { Name = "Artist Name" @@ -64,18 +39,6 @@ namespace NzbDrone.Core.Organizer ReleaseDate = System.DateTime.Today }; - _dailySeries = new Series - { - SeriesType = SeriesTypes.Daily, - Title = "Series Title (2010)" - }; - - _animeSeries = new Series - { - SeriesType = SeriesTypes.Anime, - Title = "Series Title (2010)" - }; - _track1 = new Track { TrackNumber = 3, @@ -84,66 +47,19 @@ namespace NzbDrone.Core.Organizer }; - _episode1 = new Episode - { - SeasonNumber = 1, - EpisodeNumber = 1, - Title = "Episode Title (1)", - AirDate = "2013-10-30", - AbsoluteEpisodeNumber = 1, - }; - - _episode2 = new Episode - { - SeasonNumber = 1, - EpisodeNumber = 2, - Title = "Episode Title (2)", - AbsoluteEpisodeNumber = 2 - }; - - _episode3 = new Episode - { - SeasonNumber = 1, - EpisodeNumber = 3, - Title = "Episode Title (3)", - AbsoluteEpisodeNumber = 3 - }; - - _singleEpisode = new List { _episode1 }; _singleTrack = new List { _track1 }; - _multiEpisodes = new List { _episode1, _episode2, _episode3 }; var mediaInfo = new MediaInfoModel() { VideoCodec = "AVC", VideoBitDepth = 8, - AudioFormat = "DTS", + AudioFormat = "FLAC", AudioChannels = 6, AudioChannelPositions = "3/2/0.1", AudioLanguages = "English", Subtitles = "English/German" }; - var mediaInfoAnime = new MediaInfoModel() - { - VideoCodec = "AVC", - VideoBitDepth = 8, - AudioFormat = "DTS", - AudioChannels = 6, - AudioChannelPositions = "3/2/0.1", - AudioLanguages = "Japanese", - Subtitles = "Japanese/English" - }; - - _singleEpisodeFile = new EpisodeFile - { - Quality = new QualityModel(Quality.MP3_256, new Revision(2)), - RelativePath = "Series.Title.S01E01.720p.HDTV.x264-EVOLVE.mkv", - SceneName = "Series.Title.S01E01.720p.HDTV.x264-EVOLVE", - ReleaseGroup = "RlsGrp", - MediaInfo = mediaInfo - }; - _singleTrackFile = new TrackFile { Quality = new QualityModel(Quality.MP3_256, new Revision(2)), @@ -153,54 +69,6 @@ namespace NzbDrone.Core.Organizer MediaInfo = mediaInfo }; - _multiEpisodeFile = new EpisodeFile - { - Quality = new QualityModel(Quality.MP3_256, new Revision(2)), - RelativePath = "Series.Title.S01E01-E03.720p.HDTV.x264-EVOLVE.mkv", - SceneName = "Series.Title.S01E01-E03.720p.HDTV.x264-EVOLVE", - ReleaseGroup = "RlsGrp", - MediaInfo = mediaInfo, - }; - - _dailyEpisodeFile = new EpisodeFile - { - Quality = new QualityModel(Quality.MP3_256, new Revision(2)), - RelativePath = "Series.Title.2013.10.30.HDTV.x264-EVOLVE.mkv", - SceneName = "Series.Title.2013.10.30.HDTV.x264-EVOLVE", - ReleaseGroup = "RlsGrp", - MediaInfo = mediaInfo - }; - - _animeEpisodeFile = new EpisodeFile - { - Quality = new QualityModel(Quality.MP3_256, new Revision(2)), - RelativePath = "[RlsGroup] Series Title - 001 [720p].mkv", - SceneName = "[RlsGroup] Series Title - 001 [720p]", - ReleaseGroup = "RlsGrp", - MediaInfo = mediaInfoAnime - }; - - _animeMultiEpisodeFile = new EpisodeFile - { - Quality = new QualityModel(Quality.MP3_256, new Revision(2)), - RelativePath = "[RlsGroup] Series Title - 001 - 103 [720p].mkv", - SceneName = "[RlsGroup] Series Title - 001 - 103 [720p]", - ReleaseGroup = "RlsGrp", - MediaInfo = mediaInfoAnime - }; - } - - public SampleResult GetStandardSample(NamingConfig nameSpec) - { - var result = new SampleResult - { - FileName = BuildSample(_singleEpisode, _standardSeries, _singleEpisodeFile, nameSpec), - Series = _standardSeries, - Episodes = _singleEpisode, - EpisodeFile = _singleEpisodeFile - }; - - return result; } public SampleResult GetStandardTrackSample(NamingConfig nameSpec) @@ -217,68 +85,6 @@ namespace NzbDrone.Core.Organizer return result; } - public SampleResult GetMultiEpisodeSample(NamingConfig nameSpec) - { - var result = new SampleResult - { - FileName = BuildSample(_multiEpisodes, _standardSeries, _multiEpisodeFile, nameSpec), - Series = _standardSeries, - Episodes = _multiEpisodes, - EpisodeFile = _multiEpisodeFile - }; - - return result; - } - - public SampleResult GetDailySample(NamingConfig nameSpec) - { - var result = new SampleResult - { - FileName = BuildSample(_singleEpisode, _dailySeries, _dailyEpisodeFile, nameSpec), - Series = _dailySeries, - Episodes = _singleEpisode, - EpisodeFile = _dailyEpisodeFile - }; - - return result; - } - - public SampleResult GetAnimeSample(NamingConfig nameSpec) - { - var result = new SampleResult - { - FileName = BuildSample(_singleEpisode, _animeSeries, _animeEpisodeFile, nameSpec), - Series = _animeSeries, - Episodes = _singleEpisode, - EpisodeFile = _animeEpisodeFile - }; - - return result; - } - - public SampleResult GetAnimeMultiEpisodeSample(NamingConfig nameSpec) - { - var result = new SampleResult - { - FileName = BuildSample(_multiEpisodes, _animeSeries, _animeMultiEpisodeFile, nameSpec), - Series = _animeSeries, - Episodes = _multiEpisodes, - EpisodeFile = _animeMultiEpisodeFile - }; - - return result; - } - - public string GetSeriesFolderSample(NamingConfig nameSpec) - { - return _buildFileNames.GetSeriesFolder(_standardSeries, nameSpec); - } - - public string GetSeasonFolderSample(NamingConfig nameSpec) - { - return _buildFileNames.GetSeasonFolder(_standardSeries, _episode1.SeasonNumber, nameSpec); - } - public string GetArtistFolderSample(NamingConfig nameSpec) { return _buildFileNames.GetArtistFolder(_standardArtist, nameSpec); @@ -289,18 +95,6 @@ namespace NzbDrone.Core.Organizer return _buildFileNames.GetAlbumFolder(_standardArtist, _standardAlbum, nameSpec); } - private string BuildSample(List episodes, Series series, EpisodeFile episodeFile, NamingConfig nameSpec) - { - try - { - return _buildFileNames.BuildFileName(episodes, series, episodeFile, nameSpec); - } - catch (NamingFormatException) - { - return string.Empty; - } - } - private string BuildTrackSample(List tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig nameSpec) { try diff --git a/src/NzbDrone.Core/Organizer/NamingConfig.cs b/src/NzbDrone.Core/Organizer/NamingConfig.cs index b0a4d811a..d81063bba 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 { @@ -6,30 +6,16 @@ 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 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; } - public string SeasonFolderFormat { get; set; } public string ArtistFolderFormat { get; set; } public string AlbumFolderFormat { get; set; } } diff --git a/src/NzbDrone.Core/Organizer/EpisodeFormat.cs b/src/NzbDrone.Core/Organizer/TrackFormat.cs similarity index 50% rename from src/NzbDrone.Core/Organizer/EpisodeFormat.cs rename to src/NzbDrone.Core/Organizer/TrackFormat.cs index c23dc85aa..bf37011e3 100644 --- a/src/NzbDrone.Core/Organizer/EpisodeFormat.cs +++ b/src/NzbDrone.Core/Organizer/TrackFormat.cs @@ -1,8 +1,8 @@ namespace NzbDrone.Core.Organizer { - public class AbsoluteEpisodeFormat + public class AbsoluteTrackFormat { public string Separator { get; set; } - public string AbsoluteEpisodePattern { get; set; } + public string AbsoluteTrackPattern { get; set; } } } diff --git a/src/NzbDrone.Core/Tv/AddSeriesService.cs b/src/NzbDrone.Core/Tv/AddSeriesService.cs index f8a049777..45d70e734 100644 --- a/src/NzbDrone.Core/Tv/AddSeriesService.cs +++ b/src/NzbDrone.Core/Tv/AddSeriesService.cs @@ -47,8 +47,8 @@ namespace NzbDrone.Core.Tv if (string.IsNullOrWhiteSpace(newSeries.Path)) { - var folderName = _fileNameBuilder.GetSeriesFolder(newSeries); - newSeries.Path = Path.Combine(newSeries.RootFolderPath, folderName); + //var folderName = _fileNameBuilder.GetSeriesFolder(newSeries); + //newSeries.Path = Path.Combine(newSeries.RootFolderPath, folderName); } newSeries.CleanTitle = newSeries.Title.CleanSeriesTitle(); diff --git a/src/NzbDrone.Integration.Test/ApiTests/NamingConfigFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/NamingConfigFixture.cs index 3dcfa5c83..4f29b0841 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/NamingConfigFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/NamingConfigFixture.cs @@ -25,100 +25,65 @@ namespace NzbDrone.Integration.Test.ApiTests public void should_be_able_to_update() { var config = NamingConfig.GetSingle(); - config.RenameEpisodes = false; - config.StandardEpisodeFormat = "{Series Title} - {season}x{episode:00} - {Episode Title}"; - config.DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title}"; - config.AnimeEpisodeFormat = "{Series Title} - {season}x{episode:00} - {Episode Title}"; + config.RenameTracks = false; + config.StandardTrackFormat = "{Artist Name} - {track:00} - {Album Title}"; var result = NamingConfig.Put(config); - result.RenameEpisodes.Should().BeFalse(); - result.StandardEpisodeFormat.Should().Be(config.StandardEpisodeFormat); - result.DailyEpisodeFormat.Should().Be(config.DailyEpisodeFormat); - result.AnimeEpisodeFormat.Should().Be(config.AnimeEpisodeFormat); + result.RenameTracks.Should().BeFalse(); + result.StandardTrackFormat.Should().Be(config.StandardTrackFormat); } [Test] public void should_get_bad_request_if_standard_format_is_empty() { var config = NamingConfig.GetSingle(); - config.RenameEpisodes = true; - config.StandardEpisodeFormat = ""; - config.DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title}"; - config.AnimeEpisodeFormat = "{Series Title} - {season}x{episode:00} - {Episode Title}"; + config.RenameTracks = true; + config.StandardTrackFormat = ""; - var errors = NamingConfig.InvalidPut(config); - errors.Should().NotBeNull(); - } - - [Test] - public void should_get_bad_request_if_standard_format_doesnt_contain_season_and_episode() - { - var config = NamingConfig.GetSingle(); - config.RenameEpisodes = true; - config.StandardEpisodeFormat = "{season}"; - config.DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title}"; - config.AnimeEpisodeFormat = "{Series Title} - {season}x{episode:00} - {Episode Title}"; - - var errors = NamingConfig.InvalidPut(config); - errors.Should().NotBeNull(); - } - - [Test] - public void should_get_bad_request_if_daily_format_doesnt_contain_season_and_episode_or_air_date() - { - var config = NamingConfig.GetSingle(); - config.RenameEpisodes = true; - config.StandardEpisodeFormat = "{Series Title} - {season}x{episode:00} - {Episode Title}"; - config.DailyEpisodeFormat = "{Series Title} - {season} - {Episode Title}"; - config.AnimeEpisodeFormat = "{Series Title} - {season}x{episode:00} - {Episode Title}"; var errors = NamingConfig.InvalidPut(config); errors.Should().NotBeNull(); } [Test] - public void should_get_bad_request_if_anime_format_doesnt_contain_season_and_episode_or_absolute() + public void should_get_bad_request_if_standard_format_doesnt_contain_track_number_and_title() { var config = NamingConfig.GetSingle(); - config.RenameEpisodes = false; - config.StandardEpisodeFormat = "{Series Title} - {season}x{episode:00} - {Episode Title}"; - config.DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title}"; - config.AnimeEpisodeFormat = "{Series Title} - {season} - {Episode Title}"; + config.RenameTracks = true; + config.StandardTrackFormat = "{track:00}"; var errors = NamingConfig.InvalidPut(config); errors.Should().NotBeNull(); } [Test] - public void should_not_require_format_when_rename_episodes_is_false() + public void should_not_require_format_when_rename_tracks_is_false() { var config = NamingConfig.GetSingle(); - config.RenameEpisodes = false; - config.StandardEpisodeFormat = ""; - config.DailyEpisodeFormat = ""; + config.RenameTracks = false; + config.StandardTrackFormat = ""; var errors = NamingConfig.InvalidPut(config); errors.Should().NotBeNull(); } [Test] - public void should_require_format_when_rename_episodes_is_true() + public void should_require_format_when_rename_tracks_is_true() { var config = NamingConfig.GetSingle(); - config.RenameEpisodes = true; - config.StandardEpisodeFormat = ""; - config.DailyEpisodeFormat = ""; + config.RenameTracks = true; + config.StandardTrackFormat = ""; var errors = NamingConfig.InvalidPut(config); errors.Should().NotBeNull(); } [Test] - public void should_get_bad_request_if_series_folder_format_does_not_contain_series_title() + public void should_get_bad_request_if_artist_folder_format_does_not_contain_artist_name() { var config = NamingConfig.GetSingle(); - config.RenameEpisodes = true; - config.SeriesFolderFormat = "This and That"; + config.RenameTracks = true; + config.ArtistFolderFormat = "This and That"; var errors = NamingConfig.InvalidPut(config); errors.Should().NotBeNull(); diff --git a/src/UI/Artist/Details/AlbumLayout.js b/src/UI/Artist/Details/AlbumLayout.js index d1870d95e..11686631b 100644 --- a/src/UI/Artist/Details/AlbumLayout.js +++ b/src/UI/Artist/Details/AlbumLayout.js @@ -161,7 +161,7 @@ module.exports = Marionette.Layout.extend({ command : { name : 'albumSearch', artistId : this.artist.id, - albumIds : [this.model.get('id')] + albumIds : [this.model.get('id')] } }); @@ -170,7 +170,7 @@ module.exports = Marionette.Layout.extend({ command : { name : 'renameFiles', artistId : this.artist.id, - albumId : this.model.get('id') + albumId : this.model.get('id') } }); }, diff --git a/src/UI/Artist/Details/ArtistDetailsLayout.js b/src/UI/Artist/Details/ArtistDetailsLayout.js index f408e8d78..1bd338a22 100644 --- a/src/UI/Artist/Details/ArtistDetailsLayout.js +++ b/src/UI/Artist/Details/ArtistDetailsLayout.js @@ -86,8 +86,8 @@ module.exports = Marionette.Layout.extend({ element : this.ui.rename, command : { name : 'renameFiles', - seriesId : this.model.id, - seasonNumber : -1 + artistId : this.model.id, + albumId : -1 } }); }, diff --git a/src/UI/Artist/Editor/Organize/OrganizeFilesView.js b/src/UI/Artist/Editor/Organize/OrganizeFilesView.js index b18ba784a..bdd478c4c 100644 --- a/src/UI/Artist/Editor/Organize/OrganizeFilesView.js +++ b/src/UI/Artist/Editor/Organize/OrganizeFilesView.js @@ -14,8 +14,8 @@ module.exports = Marionette.ItemView.extend({ initialize : function(options) { this.artist = options.artist; this.templateHelpers = { - numberOfArtist : this.artist.length, - artist : new Backbone.Collection(this.artist).toJSON() + numberOfArtists : this.artist.length, + artist : new Backbone.Collection(this.artist).toJSON() }; }, diff --git a/src/UI/Artist/Editor/Organize/OrganizeFilesViewTemplate.hbs b/src/UI/Artist/Editor/Organize/OrganizeFilesViewTemplate.hbs index 9729b7fdf..07d548748 100644 --- a/src/UI/Artist/Editor/Organize/OrganizeFilesViewTemplate.hbs +++ b/src/UI/Artist/Editor/Organize/OrganizeFilesViewTemplate.hbs @@ -1,7 +1,7 @@ -
+
diff --git a/src/UI/Settings/MediaManagement/Naming/Basic/BasicNamingView.js b/src/UI/Settings/MediaManagement/Naming/Basic/BasicNamingView.js index 916a15aed..279febf54 100644 --- a/src/UI/Settings/MediaManagement/Naming/Basic/BasicNamingView.js +++ b/src/UI/Settings/MediaManagement/Naming/Basic/BasicNamingView.js @@ -10,9 +10,7 @@ var view = Marionette.ItemView.extend({ ui : { namingOptions : '.x-naming-options', - singleEpisodeExample : '.x-single-episode-example', - multiEpisodeExample : '.x-multi-episode-example', - dailyEpisodeExample : '.x-daily-episode-example' + singleTrackExample : '.x-single-track-example' }, initialize : function(options) { @@ -26,12 +24,12 @@ var view = Marionette.ItemView.extend({ }, _parseNamingModel : function() { - var standardFormat = this.namingModel.get('standardEpisodeFormat'); + var standardFormat = this.namingModel.get('standardTrackFormat'); - var includeSeriesTitle = standardFormat.match(/\{Series[-_. ]Title\}/i); - var includeEpisodeTitle = standardFormat.match(/\{Episode[-_. ]Title\}/i); + var includeArtistName = standardFormat.match(/\{Artist[-_. ]Name\}/i); + var includeAlbumTitle = standardFormat.match(/\{Album[-_. ]Title\}/i); var includeQuality = standardFormat.match(/\{Quality[-_. ]Title\}/i); - var numberStyle = standardFormat.match(/s?\{season(?:\:0+)?\}[ex]\{episode(?:\:0+)?\}/i); + var numberStyle = standardFormat.match(/\{track(?:\:0+)?\}/i); var replaceSpaces = standardFormat.indexOf(' ') === -1; var separator = standardFormat.match(/\}( - |\.-\.|\.| )|( - |\.-\.|\.| )\{/i); @@ -42,14 +40,14 @@ var view = Marionette.ItemView.extend({ } if (numberStyle === null) { - numberStyle = 'S{season:00}E{episode:00}'; + numberStyle = '{track:00}'; } else { numberStyle = numberStyle[0]; } this.model.set({ - includeSeriesTitle : includeSeriesTitle !== null, - includeEpisodeTitle : includeEpisodeTitle !== null, + includeArtistName : includeArtistName !== null, + includeAlbumTitle : includeAlbumTitle !== null, includeQuality : includeQuality !== null, numberStyle : numberStyle, replaceSpaces : replaceSpaces, @@ -62,56 +60,52 @@ var view = Marionette.ItemView.extend({ return; } - var standardEpisodeFormat = ''; - var dailyEpisodeFormat = ''; + var standardTrackFormat = ''; - if (this.model.get('includeSeriesTitle')) { + if (this.model.get('includeArtistName')) { if (this.model.get('replaceSpaces')) { - standardEpisodeFormat += '{Series.Title}'; - dailyEpisodeFormat += '{Series.Title}'; + standardTrackFormat += '{Artist.Name}'; } else { - standardEpisodeFormat += '{Series Title}'; - dailyEpisodeFormat += '{Series Title}'; + standardTrackFormat += '{Artist Name}'; } - standardEpisodeFormat += this.model.get('separator'); - dailyEpisodeFormat += this.model.get('separator'); + standardTrackFormat += this.model.get('separator'); } - standardEpisodeFormat += this.model.get('numberStyle'); - dailyEpisodeFormat += '{Air-Date}'; - - if (this.model.get('includeEpisodeTitle')) { - standardEpisodeFormat += this.model.get('separator'); - dailyEpisodeFormat += this.model.get('separator'); - + if (this.model.get('includeAlbumTitle')) { if (this.model.get('replaceSpaces')) { - standardEpisodeFormat += '{Episode.Title}'; - dailyEpisodeFormat += '{Episode.Title}'; + standardTrackFormat += '{Album.Title}'; } else { - standardEpisodeFormat += '{Episode Title}'; - dailyEpisodeFormat += '{Episode Title}'; + standardTrackFormat += '{Album Title}'; } + + standardTrackFormat += this.model.get('separator'); + } + + standardTrackFormat += this.model.get('numberStyle'); + + standardTrackFormat += this.model.get('separator'); + + if (this.model.get('replaceSpaces')) { + standardTrackFormat += '{Track.Title}'; + } else { + standardTrackFormat += '{Track Title}'; } + if (this.model.get('includeQuality')) { if (this.model.get('replaceSpaces')) { - standardEpisodeFormat += ' {Quality.Title}'; - dailyEpisodeFormat += ' {Quality.Title}'; + standardTrackFormat += ' {Quality.Title}'; } else { - standardEpisodeFormat += ' {Quality Title}'; - dailyEpisodeFormat += ' {Quality Title}'; + standardTrackFormat += ' {Quality Title}'; } } if (this.model.get('replaceSpaces')) { - standardEpisodeFormat = standardEpisodeFormat.replace(/\s/g, '.'); - dailyEpisodeFormat = dailyEpisodeFormat.replace(/\s/g, '.'); + standardTrackFormat = standardTrackFormat.replace(/\s/g, '.'); } - this.namingModel.set('standardEpisodeFormat', standardEpisodeFormat); - this.namingModel.set('dailyEpisodeFormat', dailyEpisodeFormat); - this.namingModel.set('animeEpisodeFormat', standardEpisodeFormat); + this.namingModel.set('standardTrackFormat', standardTrackFormat); } }); diff --git a/src/UI/Settings/MediaManagement/Naming/Basic/BasicNamingViewTemplate.hbs b/src/UI/Settings/MediaManagement/Naming/Basic/BasicNamingViewTemplate.hbs index 06429a722..512245138 100644 --- a/src/UI/Settings/MediaManagement/Naming/Basic/BasicNamingViewTemplate.hbs +++ b/src/UI/Settings/MediaManagement/Naming/Basic/BasicNamingViewTemplate.hbs @@ -1,10 +1,10 @@
- +
- -
- +