diff --git a/src/NzbDrone.Api/Blacklist/BlacklistResource.cs b/src/NzbDrone.Api/Blacklist/BlacklistResource.cs index e7bba03a3..51d9297d7 100644 --- a/src/NzbDrone.Api/Blacklist/BlacklistResource.cs +++ b/src/NzbDrone.Api/Blacklist/BlacklistResource.cs @@ -4,6 +4,7 @@ using Sonarr.Http.REST; using NzbDrone.Core.Qualities; using NzbDrone.Api.Series; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Languages; namespace NzbDrone.Api.Blacklist { @@ -17,6 +18,7 @@ namespace NzbDrone.Api.Blacklist public DownloadProtocol Protocol { get; set; } public string Indexer { get; set; } public string Message { get; set; } + public Language Language { get; set; } public SeriesResource Series { get; set; } } diff --git a/src/NzbDrone.Api/Calendar/CalendarModule.cs b/src/NzbDrone.Api/Calendar/CalendarModule.cs index f403b79c7..9a4d01dd4 100644 --- a/src/NzbDrone.Api/Calendar/CalendarModule.cs +++ b/src/NzbDrone.Api/Calendar/CalendarModule.cs @@ -12,9 +12,9 @@ namespace NzbDrone.Api.Calendar { public CalendarModule(IEpisodeService episodeService, ISeriesService seriesService, - IQualityUpgradableSpecification qualityUpgradableSpecification, + IUpgradableSpecification upgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster) - : base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "calendar") + : base(episodeService, seriesService, upgradableSpecification, signalRBroadcaster, "calendar") { GetResourceAll = GetCalendar; } diff --git a/src/NzbDrone.Api/EpisodeFiles/EpisodeFileModule.cs b/src/NzbDrone.Api/EpisodeFiles/EpisodeFileModule.cs index 9919e8907..1fc1603b9 100644 --- a/src/NzbDrone.Api/EpisodeFiles/EpisodeFileModule.cs +++ b/src/NzbDrone.Api/EpisodeFiles/EpisodeFileModule.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Sonarr.Http.REST; using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.Events; @@ -8,6 +7,7 @@ using NzbDrone.Core.Tv; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Exceptions; using NzbDrone.SignalR; +using Sonarr.Http; using HttpStatusCode = System.Net.HttpStatusCode; namespace NzbDrone.Api.EpisodeFiles @@ -18,19 +18,19 @@ namespace NzbDrone.Api.EpisodeFiles private readonly IMediaFileService _mediaFileService; private readonly IDeleteMediaFiles _mediaFileDeletionService; private readonly ISeriesService _seriesService; - private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification; + private readonly IUpgradableSpecification _upgradableSpecification; public EpisodeFileModule(IBroadcastSignalRMessage signalRBroadcaster, IMediaFileService mediaFileService, IDeleteMediaFiles mediaFileDeletionService, ISeriesService seriesService, - IQualityUpgradableSpecification qualityUpgradableSpecification) + IUpgradableSpecification upgradableSpecification) : base(signalRBroadcaster) { _mediaFileService = mediaFileService; _mediaFileDeletionService = mediaFileDeletionService; _seriesService = seriesService; - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = upgradableSpecification; GetResourceById = GetEpisodeFile; GetResourceAll = GetEpisodeFiles; UpdateResource = SetQuality; @@ -42,7 +42,7 @@ namespace NzbDrone.Api.EpisodeFiles var episodeFile = _mediaFileService.Get(id); var series = _seriesService.GetSeries(episodeFile.SeriesId); - return episodeFile.ToResource(series, _qualityUpgradableSpecification); + return episodeFile.ToResource(series, _upgradableSpecification); } private List GetEpisodeFiles() @@ -56,13 +56,14 @@ namespace NzbDrone.Api.EpisodeFiles var series = _seriesService.GetSeries(seriesId); - return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _qualityUpgradableSpecification)); + return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _upgradableSpecification)); } private void SetQuality(EpisodeFileResource episodeFileResource) { var episodeFile = _mediaFileService.Get(episodeFileResource.Id); episodeFile.Quality = episodeFileResource.Quality; + episodeFile.Language = episodeFileResource.Language; _mediaFileService.Update(episodeFile); } diff --git a/src/NzbDrone.Api/EpisodeFiles/EpisodeFileResource.cs b/src/NzbDrone.Api/EpisodeFiles/EpisodeFileResource.cs index 9ce4d9938..af033ab51 100644 --- a/src/NzbDrone.Api/EpisodeFiles/EpisodeFileResource.cs +++ b/src/NzbDrone.Api/EpisodeFiles/EpisodeFileResource.cs @@ -4,6 +4,7 @@ using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.MediaFiles; using Sonarr.Http.REST; using NzbDrone.Core.Qualities; +using NzbDrone.Core.Languages; namespace NzbDrone.Api.EpisodeFiles { @@ -17,6 +18,7 @@ namespace NzbDrone.Api.EpisodeFiles public DateTime DateAdded { get; set; } public string SceneName { get; set; } public QualityModel Quality { get; set; } + public Language Language { get; set; } public MediaInfoResource MediaInfo { get; set; } public string OriginalFilePath { get; set; } @@ -46,7 +48,7 @@ namespace NzbDrone.Api.EpisodeFiles }; } - public static EpisodeFileResource ToResource(this EpisodeFile model, Core.Tv.Series series, IQualityUpgradableSpecification qualityUpgradableSpecification) + public static EpisodeFileResource ToResource(this EpisodeFile model, Core.Tv.Series series, IUpgradableSpecification upgradableSpecification) { if (model == null) return null; @@ -62,6 +64,7 @@ namespace NzbDrone.Api.EpisodeFiles DateAdded = model.DateAdded, SceneName = model.SceneName, Quality = model.Quality, + Language = model.Language, QualityCutoffNotMet = upgradableSpecification.QualityCutoffNotMet(series.Profile.Value, model.Quality), MediaInfo = model.MediaInfo.ToResource(model.SceneName), OriginalFilePath = model.OriginalFilePath diff --git a/src/NzbDrone.Api/Episodes/EpisodeModule.cs b/src/NzbDrone.Api/Episodes/EpisodeModule.cs index bd57332b7..6ee311bfc 100644 --- a/src/NzbDrone.Api/Episodes/EpisodeModule.cs +++ b/src/NzbDrone.Api/Episodes/EpisodeModule.cs @@ -10,9 +10,9 @@ namespace NzbDrone.Api.Episodes { public EpisodeModule(ISeriesService seriesService, IEpisodeService episodeService, - IQualityUpgradableSpecification qualityUpgradableSpecification, + IUpgradableSpecification upgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster) - : base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster) + : base(episodeService, seriesService, upgradableSpecification, signalRBroadcaster) { GetResourceAll = GetEpisodes; UpdateResource = SetMonitored; diff --git a/src/NzbDrone.Api/Episodes/EpisodeModuleWithSignalR.cs b/src/NzbDrone.Api/Episodes/EpisodeModuleWithSignalR.cs index ffdc051fa..291626eb0 100644 --- a/src/NzbDrone.Api/Episodes/EpisodeModuleWithSignalR.cs +++ b/src/NzbDrone.Api/Episodes/EpisodeModuleWithSignalR.cs @@ -19,31 +19,31 @@ namespace NzbDrone.Api.Episodes { protected readonly IEpisodeService _episodeService; protected readonly ISeriesService _seriesService; - protected readonly IQualityUpgradableSpecification _qualityUpgradableSpecification; + protected readonly IUpgradableSpecification _upgradableSpecification; protected EpisodeModuleWithSignalR(IEpisodeService episodeService, ISeriesService seriesService, - IQualityUpgradableSpecification qualityUpgradableSpecification, + IUpgradableSpecification upgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster) : base(signalRBroadcaster) { _episodeService = episodeService; _seriesService = seriesService; - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = upgradableSpecification; GetResourceById = GetEpisode; } protected EpisodeModuleWithSignalR(IEpisodeService episodeService, ISeriesService seriesService, - IQualityUpgradableSpecification qualityUpgradableSpecification, + IUpgradableSpecification upgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster, string resource) : base(signalRBroadcaster, resource) { _episodeService = episodeService; _seriesService = seriesService; - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = upgradableSpecification; GetResourceById = GetEpisode; } @@ -69,7 +69,7 @@ namespace NzbDrone.Api.Episodes } if (includeEpisodeFile && episode.EpisodeFileId != 0) { - resource.EpisodeFile = episode.EpisodeFile.Value.ToResource(series, _qualityUpgradableSpecification); + resource.EpisodeFile = episode.EpisodeFile.Value.ToResource(series, _upgradableSpecification); } } @@ -97,7 +97,7 @@ namespace NzbDrone.Api.Episodes } if (includeEpisodeFile && episodes[i].EpisodeFileId != 0) { - resource.EpisodeFile = episodes[i].EpisodeFile.Value.ToResource(series, _qualityUpgradableSpecification); + resource.EpisodeFile = episodes[i].EpisodeFile.Value.ToResource(series, _upgradableSpecification); } } } diff --git a/src/NzbDrone.Api/History/HistoryModule.cs b/src/NzbDrone.Api/History/HistoryModule.cs index 3b441dd6b..a5f4a3584 100644 --- a/src/NzbDrone.Api/History/HistoryModule.cs +++ b/src/NzbDrone.Api/History/HistoryModule.cs @@ -17,15 +17,15 @@ namespace NzbDrone.Api.History public class HistoryModule : SonarrRestModule { private readonly IHistoryService _historyService; - private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification; + private readonly IUpgradableSpecification _upgradableSpecification; private readonly IFailedDownloadService _failedDownloadService; public HistoryModule(IHistoryService historyService, - IQualityUpgradableSpecification qualityUpgradableSpecification, + IUpgradableSpecification upgradableSpecification, IFailedDownloadService failedDownloadService) { _historyService = historyService; - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = upgradableSpecification; _failedDownloadService = failedDownloadService; GetResourcePaged = GetHistory; @@ -42,7 +42,7 @@ namespace NzbDrone.Api.History if (model.Series != null) { - resource.QualityCutoffNotMet = _qualityUpgradableSpecification.CutoffNotMet(model.Series.Profile.Value, model.Quality); + resource.QualityCutoffNotMet = _upgradableSpecification.QualityCutoffNotMet(model.Series.Profile.Value, model.Quality); } return resource; diff --git a/src/NzbDrone.Api/History/HistoryResource.cs b/src/NzbDrone.Api/History/HistoryResource.cs index ad5786f34..e15bb7f7e 100644 --- a/src/NzbDrone.Api/History/HistoryResource.cs +++ b/src/NzbDrone.Api/History/HistoryResource.cs @@ -5,6 +5,7 @@ using Sonarr.Http.REST; using NzbDrone.Api.Series; using NzbDrone.Core.History; using NzbDrone.Core.Qualities; +using NzbDrone.Core.Languages; namespace NzbDrone.Api.History { @@ -17,6 +18,7 @@ namespace NzbDrone.Api.History public bool QualityCutoffNotMet { get; set; } public DateTime Date { get; set; } public string DownloadId { get; set; } + public Language Language { get; set; } public HistoryEventType EventType { get; set; } diff --git a/src/NzbDrone.Api/Indexers/ReleaseResource.cs b/src/NzbDrone.Api/Indexers/ReleaseResource.cs index c9c0f6d78..85d3d7ada 100644 --- a/src/NzbDrone.Api/Indexers/ReleaseResource.cs +++ b/src/NzbDrone.Api/Indexers/ReleaseResource.cs @@ -5,6 +5,7 @@ using Sonarr.Http.REST; using NzbDrone.Core.Parser; using NzbDrone.Core.Qualities; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Languages; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.DecisionEngine; using System.Linq; diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 039f70edd..cb4aeaf29 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -112,6 +112,8 @@ + + @@ -164,8 +166,6 @@ - - diff --git a/src/NzbDrone.Api/Profiles/Languages/LanguageModule.cs b/src/NzbDrone.Api/Profiles/Languages/LanguageModule.cs index 5576c8e9f..f9c816aec 100644 --- a/src/NzbDrone.Api/Profiles/Languages/LanguageModule.cs +++ b/src/NzbDrone.Api/Profiles/Languages/LanguageModule.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using NzbDrone.Core.Parser; +using NzbDrone.Core.Languages; using Sonarr.Http; namespace NzbDrone.Api.Profiles.Languages diff --git a/src/NzbDrone.Api/Profiles/ProfileModule.cs b/src/NzbDrone.Api/Profiles/ProfileModule.cs index 4290c5a56..aadef54f8 100644 --- a/src/NzbDrone.Api/Profiles/ProfileModule.cs +++ b/src/NzbDrone.Api/Profiles/ProfileModule.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using FluentValidation; -using NzbDrone.Core.Profiles; -using NzbDrone.Core.Validation; +using NzbDrone.Core.Profiles.Qualities; using Sonarr.Http; using Sonarr.Http.Mapping; @@ -17,7 +16,6 @@ namespace NzbDrone.Api.Profiles SharedValidator.RuleFor(c => c.Name).NotEmpty(); SharedValidator.RuleFor(c => c.Cutoff).NotNull(); SharedValidator.RuleFor(c => c.Items).MustHaveAllowedQuality(); - SharedValidator.RuleFor(c => c.Language).ValidLanguage(); GetResourceAll = GetAll; GetResourceById = GetById; diff --git a/src/NzbDrone.Api/Profiles/ProfileResource.cs b/src/NzbDrone.Api/Profiles/ProfileResource.cs index d407942dc..0365e0935 100644 --- a/src/NzbDrone.Api/Profiles/ProfileResource.cs +++ b/src/NzbDrone.Api/Profiles/ProfileResource.cs @@ -3,6 +3,7 @@ using System.Linq; using Sonarr.Http.REST; using NzbDrone.Core.Parser; using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; namespace NzbDrone.Api.Profiles @@ -12,7 +13,6 @@ namespace NzbDrone.Api.Profiles public string Name { get; set; } public Quality Cutoff { get; set; } public List Items { get; set; } - public Language Language { get; set; } } public class ProfileQualityItemResource : RestResource @@ -33,8 +33,7 @@ namespace NzbDrone.Api.Profiles Name = model.Name, Cutoff = model.Cutoff, - Items = model.Items.ConvertAll(ToResource), - Language = model.Language + Items = model.Items.ConvertAll(ToResource) }; } @@ -59,8 +58,7 @@ namespace NzbDrone.Api.Profiles Name = resource.Name, Cutoff = (Quality)resource.Cutoff.Id, - Items = resource.Items.ConvertAll(ToModel), - Language = resource.Language + Items = resource.Items.ConvertAll(ToModel) }; } diff --git a/src/NzbDrone.Api/Profiles/ProfileSchemaModule.cs b/src/NzbDrone.Api/Profiles/ProfileSchemaModule.cs index 423db02bd..b628948b3 100644 --- a/src/NzbDrone.Api/Profiles/ProfileSchemaModule.cs +++ b/src/NzbDrone.Api/Profiles/ProfileSchemaModule.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Linq; -using NzbDrone.Core.Parser; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using Sonarr.Http; using Sonarr.Http.Mapping; @@ -30,7 +29,6 @@ namespace NzbDrone.Api.Profiles var profile = new Profile(); profile.Cutoff = Quality.Unknown; profile.Items = items; - profile.Language = Language.English; return new List { profile.ToResource() }; } diff --git a/src/NzbDrone.Api/Series/SeriesEditorModule.cs b/src/NzbDrone.Api/Series/SeriesEditorModule.cs index 7fa65a16e..34d412f42 100644 --- a/src/NzbDrone.Api/Series/SeriesEditorModule.cs +++ b/src/NzbDrone.Api/Series/SeriesEditorModule.cs @@ -1,20 +1,22 @@ using System.Collections.Generic; using System.Linq; using Nancy; +using NzbDrone.Core.Profiles.Languages; using Sonarr.Http.Extensions; using NzbDrone.Core.Tv; -using Sonarr.Http.Mapping; namespace NzbDrone.Api.Series { public class SeriesEditorModule : NzbDroneApiModule { private readonly ISeriesService _seriesService; + private readonly ILanguageProfileService _languageProfileService; - public SeriesEditorModule(ISeriesService seriesService) + public SeriesEditorModule(ISeriesService seriesService, ILanguageProfileService languageProfileService) : base("/series/editor") { _seriesService = seriesService; + _languageProfileService = languageProfileService; Put["/"] = series => SaveAll(); } @@ -22,9 +24,24 @@ namespace NzbDrone.Api.Series { var resources = Request.Body.FromJson>(); - var series = resources.Select(seriesResource => seriesResource.ToModel(_seriesService.GetSeries(seriesResource.Id))).ToList(); + var seriesToUpdate = resources.Select(seriesResource => + { + var series = _seriesService.GetSeries(seriesResource.Id); + var updatedSeries = seriesResource.ToModel(series); - return _seriesService.UpdateSeries(series, true) + // If the new language profile doens't exist, keep it the same. + // This could happen if a 3rd-party app uses this endpoint to update a + // series and doesn't pass the languageProfileI as well. + + if (!_languageProfileService.Exists(updatedSeries.LanguageProfileId)) + { + updatedSeries.LanguageProfileId = series.LanguageProfileId; + } + + return updatedSeries; + }).ToList(); + + return _seriesService.UpdateSeries(seriesToUpdate, true) .ToResource(false) .AsResponse(HttpStatusCode.Accepted); } diff --git a/src/NzbDrone.Api/Series/SeriesModule.cs b/src/NzbDrone.Api/Series/SeriesModule.cs index 2ba912aec..be1558179 100644 --- a/src/NzbDrone.Api/Series/SeriesModule.cs +++ b/src/NzbDrone.Api/Series/SeriesModule.cs @@ -13,6 +13,7 @@ using NzbDrone.Core.Tv; using NzbDrone.Core.Tv.Events; using NzbDrone.Core.Validation.Paths; using NzbDrone.Core.DataAugmentation.Scene; +using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Validation; using NzbDrone.SignalR; using Sonarr.Http; @@ -35,6 +36,7 @@ namespace NzbDrone.Api.Series private readonly ISeriesStatisticsService _seriesStatisticsService; private readonly ISceneMappingService _sceneMappingService; private readonly IMapCoversToLocal _coverMapper; + private readonly ILanguageProfileService _languageProfileService; public SeriesModule(IBroadcastSignalRMessage signalRBroadcaster, ISeriesService seriesService, @@ -42,13 +44,15 @@ namespace NzbDrone.Api.Series ISeriesStatisticsService seriesStatisticsService, ISceneMappingService sceneMappingService, IMapCoversToLocal coverMapper, + ILanguageProfileService languageProfileService, RootFolderValidator rootFolderValidator, SeriesPathValidator seriesPathValidator, SeriesExistsValidator seriesExistsValidator, DroneFactoryValidator droneFactoryValidator, SeriesAncestorValidator seriesAncestorValidator, SystemFolderValidator systemFolderValidator, - ProfileExistsValidator profileExistsValidator + ProfileExistsValidator profileExistsValidator, + LanguageProfileExistsValidator languageProfileExistsValidator ) : base(signalRBroadcaster) { @@ -58,6 +62,7 @@ namespace NzbDrone.Api.Series _sceneMappingService = sceneMappingService; _coverMapper = coverMapper; + _languageProfileService = languageProfileService; GetResourceAll = AllSeries; GetResourceById = GetSeries; @@ -66,6 +71,7 @@ namespace NzbDrone.Api.Series DeleteResource = DeleteSeries; SharedValidator.RuleFor(s => s.ProfileId).ValidId(); + SharedValidator.RuleFor(s => s.LanguageProfileId); SharedValidator.RuleFor(s => s.Path) .Cascade(CascadeMode.StopOnFirstFailure) @@ -82,8 +88,12 @@ namespace NzbDrone.Api.Series PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace()); PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace()); PostValidator.RuleFor(s => s.TvdbId).GreaterThan(0).SetValidator(seriesExistsValidator); + PostValidator.RuleFor(s => s.LanguageProfileId).SetValidator(languageProfileExistsValidator).When(s => s.LanguageProfileId != 0); PutValidator.RuleFor(s => s.Path).IsValidPath(); + + // Ensure any editing has a valid LanguageProfile + PutValidator.RuleFor(s => s.LanguageProfileId).SetValidator(languageProfileExistsValidator); } private SeriesResource GetSeries(int id) @@ -111,6 +121,12 @@ namespace NzbDrone.Api.Series { var model = seriesResource.ToModel(); + // Set a default LanguageProfileId to maintain backwards compatibility with apps using the v2 API + if (model.LanguageProfileId == 0 || !_languageProfileService.Exists(model.LanguageProfileId)) + { + model.LanguageProfileId = _languageProfileService.All().First().Id; + } + return _addSeriesService.AddSeries(model).Id; } diff --git a/src/NzbDrone.Api/Series/SeriesResource.cs b/src/NzbDrone.Api/Series/SeriesResource.cs index e73b9db3e..755b201a3 100644 --- a/src/NzbDrone.Api/Series/SeriesResource.cs +++ b/src/NzbDrone.Api/Series/SeriesResource.cs @@ -52,6 +52,7 @@ namespace NzbDrone.Api.Series //View & Edit public string Path { get; set; } public int ProfileId { get; set; } + public int LanguageProfileId { get; set; } //Editing Only public bool SeasonFolder { get; set; } @@ -124,6 +125,7 @@ namespace NzbDrone.Api.Series Path = model.Path, ProfileId = model.ProfileId, + LanguageProfileId = model.LanguageProfileId, SeasonFolder = model.SeasonFolder, Monitored = model.Monitored, @@ -178,6 +180,7 @@ namespace NzbDrone.Api.Series Path = resource.Path, ProfileId = resource.ProfileId, + LanguageProfileId = resource.LanguageProfileId, SeasonFolder = resource.SeasonFolder, Monitored = resource.Monitored, diff --git a/src/NzbDrone.Api/Wanted/CutoffModule.cs b/src/NzbDrone.Api/Wanted/CutoffModule.cs index 58a4f748f..901711076 100644 --- a/src/NzbDrone.Api/Wanted/CutoffModule.cs +++ b/src/NzbDrone.Api/Wanted/CutoffModule.cs @@ -15,9 +15,9 @@ namespace NzbDrone.Api.Wanted public CutoffModule(IEpisodeCutoffService episodeCutoffService, IEpisodeService episodeService, ISeriesService seriesService, - IQualityUpgradableSpecification qualityUpgradableSpecification, + IUpgradableSpecification upgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster) - : base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/cutoff") + : base(episodeService, seriesService, upgradableSpecification, signalRBroadcaster, "wanted/cutoff") { _episodeCutoffService = episodeCutoffService; GetResourcePaged = GetCutoffUnmetEpisodes; diff --git a/src/NzbDrone.Api/Wanted/MissingModule.cs b/src/NzbDrone.Api/Wanted/MissingModule.cs index e30dc9cf4..afd5c2730 100644 --- a/src/NzbDrone.Api/Wanted/MissingModule.cs +++ b/src/NzbDrone.Api/Wanted/MissingModule.cs @@ -12,9 +12,9 @@ namespace NzbDrone.Api.Wanted { public MissingModule(IEpisodeService episodeService, ISeriesService seriesService, - IQualityUpgradableSpecification qualityUpgradableSpecification, + IUpgradableSpecification upgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster) - : base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/missing") + : base(episodeService, seriesService, upgradableSpecification, signalRBroadcaster, "wanted/missing") { GetResourcePaged = GetMissingEpisodes; } diff --git a/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs b/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs index 761ad59cb..b736fb7a6 100644 --- a/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs @@ -6,6 +6,7 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; +using NzbDrone.Core.Languages; namespace NzbDrone.Core.Test.Datastore { @@ -16,14 +17,15 @@ namespace NzbDrone.Core.Test.Datastore public void one_to_one() { var episodeFile = Builder.CreateNew() - .With(c => c.Quality = new QualityModel()) - .BuildNew(); + .With(c => c.Quality = new QualityModel()) + .With(c => c.Language = Language.English) + .BuildNew(); Db.Insert(episodeFile); var episode = Builder.CreateNew() - .With(c => c.EpisodeFileId = episodeFile.Id) - .BuildNew(); + .With(c => c.EpisodeFileId = episodeFile.Id) + .BuildNew(); Db.Insert(episode); @@ -43,8 +45,8 @@ namespace NzbDrone.Core.Test.Datastore public void one_to_one_should_not_query_db_if_foreign_key_is_zero() { var episode = Builder.CreateNew() - .With(c => c.EpisodeFileId = 0) - .BuildNew(); + .With(c => c.EpisodeFileId = 0) + .BuildNew(); Db.Insert(episode); @@ -58,9 +60,9 @@ namespace NzbDrone.Core.Test.Datastore var quality = new QualityModel { Quality = Quality.Bluray720p, Revision = new Revision(version: 2 )}; var history = Builder.CreateNew() - .With(c => c.Id = 0) - .With(c => c.Quality = quality) - .Build(); + .With(c => c.Id = 0) + .With(c => c.Quality = quality) + .Build(); Db.Insert(history); @@ -72,8 +74,8 @@ namespace NzbDrone.Core.Test.Datastore public void embedded_list_of_document_with_json() { var history = Builder.CreateListOfSize(2) - .All().With(c => c.Id = 0) - .Build().ToList(); + .All().With(c => c.Id = 0) + .Build().ToList(); history[0].Quality = new QualityModel(Quality.HDTV1080p, new Revision(version: 2)); history[1].Quality = new QualityModel(Quality.Bluray720p, new Revision(version: 2)); diff --git a/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs b/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs index 49d67f063..b3df3828c 100644 --- a/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs @@ -1,11 +1,15 @@ using FizzWare.NBuilder; using NUnit.Framework; +using System.Linq; using NzbDrone.Core.Datastore; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Core.Qualities; using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles.Languages; +using NzbDrone.Core.Test.Languages; namespace NzbDrone.Core.Test.Datastore { @@ -20,15 +24,23 @@ namespace NzbDrone.Core.Test.Datastore { Name = "Test", Cutoff = Quality.WEBDL720p, - Items = Qualities.QualityFixture.GetDefaultQualities() + Items = Qualities.QualityFixture.GetDefaultQualities(), + }; + + var languageProfile = new LanguageProfile + { + Name = "Test", + Languages = LanguageFixture.GetDefaultLanguages(Language.English), + Cutoff = Language.English }; - profile = Db.Insert(profile); + languageProfile = Db.Insert(languageProfile); var series = Builder.CreateListOfSize(1) .All() .With(v => v.ProfileId = profile.Id) + .With(v => v.LanguageProfileId = languageProfile.Id) .BuildListOfNew(); Db.InsertMany(series); @@ -65,6 +77,7 @@ namespace NzbDrone.Core.Test.Datastore { Assert.IsNotNull(episode.Series); Assert.IsFalse(episode.Series.Profile.IsLoaded); + Assert.IsFalse(episode.Series.LanguageProfile.IsLoaded); } } @@ -100,8 +113,27 @@ namespace NzbDrone.Core.Test.Datastore { Assert.IsNotNull(episode.Series); Assert.IsTrue(episode.Series.Profile.IsLoaded); + Assert.IsFalse(episode.Series.LanguageProfile.IsLoaded); } } + [Test] + public void should_explicit_load_languageprofile_if_joined() + { + var db = Mocker.Resolve(); + var DataMapper = db.GetDataMapper(); + + var episodes = DataMapper.Query() + .Join(Marr.Data.QGen.JoinType.Inner, v => v.Series, (l, r) => l.SeriesId == r.Id) + .Join(Marr.Data.QGen.JoinType.Inner, v => v.LanguageProfile, (l, r) => l.ProfileId == r.Id) + .ToList(); + + foreach (var episode in episodes) + { + Assert.IsNotNull(episode.Series); + Assert.IsFalse(episode.Series.Profile.IsLoaded); + Assert.IsTrue(episode.Series.LanguageProfile.IsLoaded); + } + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/075_force_lib_updateFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/075_force_lib_updateFixture.cs index 1d6c113b8..c6b2b3cc7 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/075_force_lib_updateFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/075_force_lib_updateFixture.cs @@ -55,6 +55,14 @@ namespace NzbDrone.Core.Test.Datastore.Migration { var db = WithMigrationTestDb(c => { + c.Insert.IntoTable("Profiles").Row(new + { + Name = "Profile1", + CutOff = 0, + Items = "[]", + Language = 1 + }); + c.Insert.IntoTable("Series").Row(new { Tvdbid = 1, @@ -69,7 +77,8 @@ namespace NzbDrone.Core.Test.Datastore.Migration Runtime= 0, SeriesType=0, UseSceneNumbering =0, - LastInfoSync = "2000-01-01 00:00:00" + LastInfoSync = "2000-01-01 00:00:00", + ProfileId = 1 }); c.Insert.IntoTable("Series").Row(new @@ -86,7 +95,8 @@ namespace NzbDrone.Core.Test.Datastore.Migration Runtime = 0, SeriesType = 0, UseSceneNumbering = 0, - LastInfoSync = "2000-01-01 00:00:00" + LastInfoSync = "2000-01-01 00:00:00", + ProfileId = 1 }); }); diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/079_dedupe_tagsFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/079_dedupe_tagsFixture.cs index e333fb9a1..cdee33a8d 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/079_dedupe_tagsFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/079_dedupe_tagsFixture.cs @@ -14,6 +14,14 @@ namespace NzbDrone.Core.Test.Datastore.Migration { var db = WithMigrationTestDb(c => { + c.Insert.IntoTable("Profiles").Row(new + { + Name = "Profile1", + CutOff = 0, + Items = "[]", + Language = 1 + }); + c.Insert.IntoTable("Series").Row(new { Tvdbid = 1, @@ -28,7 +36,8 @@ namespace NzbDrone.Core.Test.Datastore.Migration Runtime = 0, SeriesType = 0, UseSceneNumbering = 0, - LastInfoSync = "2000-01-01 00:00:00" + LastInfoSync = "2000-01-01 00:00:00", + ProfileId = 1 }); c.Insert.IntoTable("Tags").Row(new @@ -46,6 +55,14 @@ namespace NzbDrone.Core.Test.Datastore.Migration { var db = WithMigrationTestDb(c => { + c.Insert.IntoTable("Profiles").Row(new + { + Name = "Profile1", + CutOff = 0, + Items = "[]", + Language = 1 + }); + c.Insert.IntoTable("Series").Row(new { Tvdbid = 1, @@ -61,7 +78,8 @@ namespace NzbDrone.Core.Test.Datastore.Migration SeriesType = 0, UseSceneNumbering = 0, LastInfoSync = "2000-01-01 00:00:00", - Tags = "[]" + Tags = "[]", + ProfileId = 1 }); c.Insert.IntoTable("Tags").Row(new @@ -113,6 +131,14 @@ namespace NzbDrone.Core.Test.Datastore.Migration { var db = WithMigrationTestDb(c => { + c.Insert.IntoTable("Profiles").Row(new + { + Name = "Profile1", + CutOff = 0, + Items = "[]", + Language = 1 + }); + c.Insert.IntoTable("Series").Row(new { Tvdbid = 1, @@ -128,7 +154,8 @@ namespace NzbDrone.Core.Test.Datastore.Migration SeriesType = 0, UseSceneNumbering = 0, LastInfoSync = "2000-01-01 00:00:00", - Tags = "[2]" + Tags = "[2]", + ProfileId = 1 }); c.Insert.IntoTable("Tags").Row(new @@ -151,6 +178,14 @@ namespace NzbDrone.Core.Test.Datastore.Migration { var db = WithMigrationTestDb(c => { + c.Insert.IntoTable("Profiles").Row(new + { + Name = "Profile1", + CutOff = 0, + Items = "[]", + Language = 1 + }); + c.Insert.IntoTable("Series").Row(new { Tvdbid = 1, @@ -166,7 +201,8 @@ namespace NzbDrone.Core.Test.Datastore.Migration SeriesType = 0, UseSceneNumbering = 0, LastInfoSync = "2000-01-01 00:00:00", - Tags = "[2]" + Tags = "[2]", + ProfileId = 1 }); c.Insert.IntoTable("Series").Row(new @@ -184,7 +220,8 @@ namespace NzbDrone.Core.Test.Datastore.Migration SeriesType = 0, UseSceneNumbering = 0, LastInfoSync = "2000-01-01 00:00:00", - Tags = "[]" + Tags = "[]", + ProfileId = 1 }); c.Insert.IntoTable("Tags").Row(new diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/108_fix_metadata_file_extensionsFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/108_fix_metadata_file_extensionsFixture.cs index 5a8a1fe02..8254c3a6c 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/108_fix_metadata_file_extensionsFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/108_fix_metadata_file_extensionsFixture.cs @@ -2,6 +2,7 @@ using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Datastore.Migration; +using NzbDrone.Core.Languages; using NzbDrone.Core.Parser; using NzbDrone.Core.Test.Framework; @@ -45,7 +46,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration RelativePath = "Series.Title.S01E01.en.srt", Added = "2016-05-30 20:23:02.3725923", LastUpdated = "2016-05-30 20:23:02.3725923", - Language = Language.English, + Language = (int)Language.English, Extension = "en.srt" }); }); diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/AnimeVersionUpgradeSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/AnimeVersionUpgradeSpecificationFixture.cs index 2a555a186..f8a1a407c 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/AnimeVersionUpgradeSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/AnimeVersionUpgradeSpecificationFixture.cs @@ -23,7 +23,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [SetUp] public void Setup() { - Mocker.Resolve(); + Mocker.Resolve(); _subject = Mocker.Resolve(); _episodeFile = new EpisodeFile diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs index e038ba82c..8729c33c1 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs @@ -1,50 +1,223 @@ using FluentAssertions; using NUnit.Framework; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles.Languages; +using NzbDrone.Core.Test.Languages; namespace NzbDrone.Core.Test.DecisionEngineTests { [TestFixture] - public class CutoffSpecificationFixture : CoreTest + public class CutoffSpecificationFixture : CoreTest { [Test] public void should_return_true_if_current_episode_is_less_than_cutoff() { - Subject.CutoffNotMet(new Profile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities() }, - new QualityModel(Quality.DVD, new Revision(version: 2))).Should().BeTrue(); + Subject.CutoffNotMet( + new Profile + { + Cutoff = Quality.Bluray1080p, + Items = Qualities.QualityFixture.GetDefaultQualities() + }, + new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English), + Cutoff = Language.English + }, + new QualityModel(Quality.DVD, new Revision(version: 2)), Language.English).Should().BeTrue(); } [Test] public void should_return_false_if_current_episode_is_equal_to_cutoff() { - Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p, Items = Qualities.QualityFixture.GetDefaultQualities() }, - new QualityModel(Quality.HDTV720p, new Revision(version: 2))).Should().BeFalse(); + Subject.CutoffNotMet( + new Profile + { + Cutoff = Quality.HDTV720p, + Items = Qualities.QualityFixture.GetDefaultQualities() + }, + new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English), + Cutoff = Language.English + }, + new QualityModel(Quality.HDTV720p, new Revision(version: 2)), Language.English).Should().BeFalse(); } [Test] public void should_return_false_if_current_episode_is_greater_than_cutoff() { - Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p, Items = Qualities.QualityFixture.GetDefaultQualities() }, - new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeFalse(); + Subject.CutoffNotMet( + new Profile + { + Cutoff = Quality.HDTV720p, + Items = Qualities.QualityFixture.GetDefaultQualities() + }, + new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English), + Cutoff = Language.English + }, + new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), Language.English).Should().BeFalse(); } [Test] public void should_return_true_when_new_episode_is_proper_but_existing_is_not() { - Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p, Items = Qualities.QualityFixture.GetDefaultQualities() }, - new QualityModel(Quality.HDTV720p, new Revision(version: 1)), - new QualityModel(Quality.HDTV720p, new Revision(version: 2))).Should().BeTrue(); + Subject.CutoffNotMet( + new Profile + { + Cutoff = Quality.HDTV720p, + Items = Qualities.QualityFixture.GetDefaultQualities() + }, + new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English), + Cutoff = Language.English + }, + new QualityModel(Quality.HDTV720p, new Revision(version: 1)), + Language.English, + new QualityModel(Quality.HDTV720p, new Revision(version: 2))).Should().BeTrue(); } [Test] public void should_return_false_if_cutoff_is_met_and_quality_is_higher() { - Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p, Items = Qualities.QualityFixture.GetDefaultQualities() }, - new QualityModel(Quality.HDTV720p, new Revision(version: 2)), - new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeFalse(); + Subject.CutoffNotMet( + new Profile + { + Cutoff = Quality.HDTV720p, + Items = Qualities.QualityFixture.GetDefaultQualities() + }, + new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English), + Cutoff = Language.English + }, + new QualityModel(Quality.HDTV720p, new Revision(version: 2)), + Language.English, + new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeFalse(); + } + + [Test] + public void should_return_true_if_quality_cutoff_is_met_and_quality_is_higher_but_language_is_not_met() + { + + Profile _profile = new Profile + { + Cutoff = Quality.HDTV720p, + Items = Qualities.QualityFixture.GetDefaultQualities(), + }; + + LanguageProfile _langProfile = new LanguageProfile + { + Cutoff = Language.Spanish, + Languages = LanguageFixture.GetDefaultLanguages() + }; + + Subject.CutoffNotMet(_profile, + _langProfile, + new QualityModel(Quality.HDTV720p, new Revision(version: 2)), + Language.English, + new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeTrue(); + } + + [Test] + public void should_return_false_if_cutoff_is_met_and_quality_is_higher_and_language_is_met() + { + + Profile _profile = new Profile + { + Cutoff = Quality.HDTV720p, + Items = Qualities.QualityFixture.GetDefaultQualities(), + }; + + LanguageProfile _langProfile = new LanguageProfile + { + Cutoff = Language.Spanish, + Languages = LanguageFixture.GetDefaultLanguages() + }; + + Subject.CutoffNotMet( + _profile, + _langProfile, + new QualityModel(Quality.HDTV720p, new Revision(version: 2)), + Language.Spanish, + new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeFalse(); + } + + [Test] + public void should_return_false_if_cutoff_is_met_and_quality_is_higher_and_language_is_higher() + { + + Profile _profile = new Profile + { + Cutoff = Quality.HDTV720p, + Items = Qualities.QualityFixture.GetDefaultQualities(), + }; + + LanguageProfile _langProfile = new LanguageProfile + { + Cutoff = Language.Spanish, + Languages = LanguageFixture.GetDefaultLanguages() + }; + + Subject.CutoffNotMet( + _profile, + _langProfile, + new QualityModel(Quality.HDTV720p, new Revision(version: 2)), + Language.French, + new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeFalse(); + } + + [Test] + public void should_return_true_if_cutoff_is_not_met_and_new_quality_is_higher_and_language_is_higher() + { + + Profile _profile = new Profile + { + Cutoff = Quality.HDTV720p, + Items = Qualities.QualityFixture.GetDefaultQualities(), + }; + + LanguageProfile _langProfile = new LanguageProfile + { + Cutoff = Language.Spanish, + Languages = LanguageFixture.GetDefaultLanguages() + }; + + Subject.CutoffNotMet( + _profile, + _langProfile, + new QualityModel(Quality.SDTV, new Revision(version: 2)), + Language.French, + new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeTrue(); + } + + [Test] + public void should_return_true_if_cutoff_is_not_met_and_language_is_higher() + { + + Profile _profile = new Profile + { + Cutoff = Quality.HDTV720p, + Items = Qualities.QualityFixture.GetDefaultQualities(), + }; + + LanguageProfile _langProfile = new LanguageProfile + { + Cutoff = Language.Spanish, + Languages = LanguageFixture.GetDefaultLanguages() + }; + + Subject.CutoffNotMet( + _profile, + _langProfile, + new QualityModel(Quality.SDTV, new Revision(version: 2)), + Language.French).Should().BeTrue(); } } } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/LanguageSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/LanguageSpecificationFixture.cs index f190677c3..45f5c9c04 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/LanguageSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/LanguageSpecificationFixture.cs @@ -2,11 +2,12 @@ using Marr.Data; using NUnit.Framework; using NzbDrone.Core.DecisionEngine.Specifications; -using NzbDrone.Core.Parser; +using NzbDrone.Core.Languages; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; +using NzbDrone.Core.Profiles.Languages; +using NzbDrone.Core.Test.Languages; namespace NzbDrone.Core.Test.DecisionEngineTests { @@ -19,6 +20,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [SetUp] public void Setup() { + LanguageProfile _profile = new LazyLoaded (new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.Spanish), + Cutoff = Language.Spanish + }); + _remoteEpisode = new RemoteEpisode { ParsedEpisodeInfo = new ParsedEpisodeInfo @@ -26,12 +33,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Language = Language.English }, Series = new Series - { - Profile = new LazyLoaded(new Profile - { - Language = Language.English - }) - } + { + LanguageProfile = _profile + } }; } @@ -40,6 +44,16 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _remoteEpisode.ParsedEpisodeInfo.Language = Language.English; } + private void WithSpanishRelease() + { + _remoteEpisode.ParsedEpisodeInfo.Language = Language.Spanish; + } + + private void WithFrenchRelease() + { + _remoteEpisode.ParsedEpisodeInfo.Language = Language.French; + } + private void WithGermanRelease() { _remoteEpisode.ParsedEpisodeInfo.Language = Language.German; @@ -60,5 +74,23 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Mocker.Resolve().IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse(); } + + [Test] + public void should_return_false_if_language_is_french() + { + WithFrenchRelease(); + + Mocker.Resolve().IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse(); + } + + + [Test] + public void should_return_true_if_language_is_spanish() + { + WithSpanishRelease(); + + Mocker.Resolve().IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); + } + } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs index c9e75ab1d..e9aa2f109 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs @@ -5,7 +5,7 @@ using Moq; using NzbDrone.Core.Indexers; using NzbDrone.Core.Profiles.Delay; using NzbDrone.Core.Tv; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.DecisionEngine; @@ -14,6 +14,9 @@ using FluentAssertions; using FizzWare.NBuilder; using NzbDrone.Common.Extensions; using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles.Languages; +using NzbDrone.Core.Test.Languages; namespace NzbDrone.Core.Test.DecisionEngineTests { @@ -34,11 +37,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests .Build(); } - private RemoteEpisode GivenRemoteEpisode(List episodes, QualityModel quality, int age = 0, long size = 0, DownloadProtocol downloadProtocol = DownloadProtocol.Usenet) + private RemoteEpisode GivenRemoteEpisode(List episodes, QualityModel quality, Language language, int age = 0, long size = 0, DownloadProtocol downloadProtocol = DownloadProtocol.Usenet) { var remoteEpisode = new RemoteEpisode(); remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo(); remoteEpisode.ParsedEpisodeInfo.Quality = quality; + remoteEpisode.ParsedEpisodeInfo.Language = language; remoteEpisode.Episodes = new List(); remoteEpisode.Episodes.AddRange(episodes); @@ -49,7 +53,15 @@ namespace NzbDrone.Core.Test.DecisionEngineTests remoteEpisode.Release.DownloadProtocol = downloadProtocol; remoteEpisode.Series = Builder.CreateNew() - .With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }) + .With(e => e.Profile = new Profile + { + Items = Qualities.QualityFixture.GetDefaultQualities() + }) + .With(l => l.LanguageProfile = new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(), + Cutoff = Language.Spanish + }) .Build(); return remoteEpisode; @@ -68,8 +80,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_put_propers_before_non_propers() { - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p, new Revision(version: 1))); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p, new Revision(version: 2))); + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p, new Revision(version: 1)), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p, new Revision(version: 2)), Language.English); var decisions = new List(); decisions.Add(new DownloadDecision(remoteEpisode1)); @@ -82,8 +94,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_put_higher_quality_before_lower() { - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.SDTV)); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p)); + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.SDTV), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English); var decisions = new List(); decisions.Add(new DownloadDecision(remoteEpisode1)); @@ -96,8 +108,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_order_by_lowest_number_of_episodes() { - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(2) }, new QualityModel(Quality.HDTV720p)); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p)); + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(2) }, new QualityModel(Quality.HDTV720p), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English); var decisions = new List(); decisions.Add(new DownloadDecision(remoteEpisode1)); @@ -110,8 +122,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_order_by_lowest_number_of_episodes_with_multiple_episodes() { - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(2), GivenEpisode(3) }, new QualityModel(Quality.HDTV720p)); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1), GivenEpisode(2) }, new QualityModel(Quality.HDTV720p)); + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(2), GivenEpisode(3) }, new QualityModel(Quality.HDTV720p), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1), GivenEpisode(2) }, new QualityModel(Quality.HDTV720p), Language.English); var decisions = new List(); decisions.Add(new DownloadDecision(remoteEpisode1)); @@ -124,10 +136,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_order_by_age_then_largest_rounded_to_200mb() { - var remoteEpisodeSd = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.SDTV), size: 100.Megabytes(), age: 1); - var remoteEpisodeHdSmallOld = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), size: 1200.Megabytes(), age: 1000); - var remoteEpisodeSmallYoung = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), size: 1250.Megabytes(), age: 10); - var remoteEpisodeHdLargeYoung = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), size: 3000.Megabytes(), age: 1); + var remoteEpisodeSd = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.SDTV), Language.English, size: 100.Megabytes(), age: 1); + var remoteEpisodeHdSmallOld = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, size: 1200.Megabytes(), age: 1000); + var remoteEpisodeSmallYoung = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, size: 1250.Megabytes(), age: 10); + var remoteEpisodeHdLargeYoung = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, size: 3000.Megabytes(), age: 1); var decisions = new List(); decisions.Add(new DownloadDecision(remoteEpisodeSd)); @@ -142,8 +154,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_order_by_youngest() { - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), age: 10); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), age: 5); + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, age: 10); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, age: 5); var decisions = new List(); @@ -157,8 +169,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_not_throw_if_no_episodes_are_found() { - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), size: 500.Megabytes()); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), size: 500.Megabytes()); + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, size: 500.Megabytes()); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, size: 500.Megabytes()); remoteEpisode1.Episodes = new List(); @@ -174,8 +186,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { GivenPreferredDownloadProtocol(DownloadProtocol.Usenet); - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), downloadProtocol: DownloadProtocol.Torrent); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), downloadProtocol: DownloadProtocol.Usenet); + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, downloadProtocol: DownloadProtocol.Torrent); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, downloadProtocol: DownloadProtocol.Usenet); var decisions = new List(); decisions.Add(new DownloadDecision(remoteEpisode1)); @@ -190,8 +202,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { GivenPreferredDownloadProtocol(DownloadProtocol.Torrent); - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), downloadProtocol: DownloadProtocol.Torrent); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), downloadProtocol: DownloadProtocol.Usenet); + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, downloadProtocol: DownloadProtocol.Torrent); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, downloadProtocol: DownloadProtocol.Usenet); var decisions = new List(); decisions.Add(new DownloadDecision(remoteEpisode1)); @@ -204,8 +216,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_prefer_season_pack_above_single_episode() { - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1), GivenEpisode(2) }, new QualityModel(Quality.HDTV720p)); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p)); + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1), GivenEpisode(2) }, new QualityModel(Quality.HDTV720p), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English); remoteEpisode1.ParsedEpisodeInfo.FullSeason = true; @@ -220,8 +232,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_prefer_multiepisode_over_single_episode_for_anime() { - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1), GivenEpisode(2) }, new QualityModel(Quality.HDTV720p)); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p)); + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1), GivenEpisode(2) }, new QualityModel(Quality.HDTV720p), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English); remoteEpisode1.Series.SeriesType = SeriesTypes.Anime; remoteEpisode2.Series.SeriesType = SeriesTypes.Anime; @@ -237,8 +249,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_prefer_single_episode_over_multi_episode_for_non_anime() { - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1), GivenEpisode(2) }, new QualityModel(Quality.HDTV720p)); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p)); + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1), GivenEpisode(2) }, new QualityModel(Quality.HDTV720p), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English); var decisions = new List(); decisions.Add(new DownloadDecision(remoteEpisode1)); @@ -251,8 +263,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_prefer_releases_with_more_seeders() { - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p)); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p)); + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English); var torrentInfo1 = new TorrentInfo(); torrentInfo1.PublishDate = DateTime.Now; @@ -277,8 +289,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_prefer_releases_with_more_peers_given_equal_number_of_seeds() { - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p)); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p)); + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English); var torrentInfo1 = new TorrentInfo(); torrentInfo1.PublishDate = DateTime.Now; @@ -305,8 +317,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_prefer_releases_with_more_peers_no_seeds() { - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p)); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p)); + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English); var torrentInfo1 = new TorrentInfo(); torrentInfo1.PublishDate = DateTime.Now; @@ -334,8 +346,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_prefer_first_release_if_peers_and_size_are_too_similar() { - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p)); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p)); + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English); var torrentInfo1 = new TorrentInfo(); torrentInfo1.PublishDate = DateTime.Now; @@ -363,8 +375,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_prefer_first_release_if_age_and_size_are_too_similar() { - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p)); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p)); + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English); remoteEpisode1.Release.PublishDate = DateTime.UtcNow.AddDays(-100); remoteEpisode1.Release.Size = 200.Megabytes(); @@ -383,8 +395,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_prefer_quality_over_the_number_of_peers() { - var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.Bluray1080p)); - var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.SDTV)); + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.Bluray1080p), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.SDTV), Language.English); var torrentInfo1 = new TorrentInfo(); torrentInfo1.PublishDate = DateTime.Now; @@ -408,5 +420,37 @@ namespace NzbDrone.Core.Test.DecisionEngineTests var qualifiedReports = Subject.PrioritizeDecisions(decisions); ((TorrentInfo)qualifiedReports.First().RemoteEpisode.Release).Should().Be(torrentInfo1); } + + [Test] + public void should_order_by_language() + { + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.French); + var remoteEpisode3 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.German); + + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode1)); + decisions.Add(new DownloadDecision(remoteEpisode2)); + decisions.Add(new DownloadDecision(remoteEpisode3)); + + var qualifiedReports = Subject.PrioritizeDecisions(decisions); + qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Language.Should().Be(Language.French); + qualifiedReports.Last().RemoteEpisode.ParsedEpisodeInfo.Language.Should().Be(Language.German); + } + + [Test] + public void should_put_higher_quality_before_lower_allways() + { + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.SDTV), Language.French); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.German); + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode1)); + decisions.Add(new DownloadDecision(remoteEpisode2)); + + var qualifiedReports = Subject.PrioritizeDecisions(decisions); + qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Quality.Should().Be(Quality.HDTV720p); + } } } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityAllowedByProfileSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/QualityAllowedByProfileSpecificationFixture.cs index 207f38225..d412e4bb0 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityAllowedByProfileSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/QualityAllowedByProfileSpecificationFixture.cs @@ -4,7 +4,7 @@ using Marr.Data; using NUnit.Framework; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.Test.Framework; diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs index 1a4307bd1..0bede228a 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs @@ -1,16 +1,19 @@ using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Configuration; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles.Languages; +using NzbDrone.Core.Test.Languages; namespace NzbDrone.Core.Test.DecisionEngineTests { [TestFixture] - public class QualityUpgradeSpecificationFixture : CoreTest + public class QualityUpgradeSpecificationFixture : CoreTest { public static object[] IsUpgradeTestCases = { @@ -22,7 +25,17 @@ namespace NzbDrone.Core.Test.DecisionEngineTests new object[] { Quality.WEBDL720p, 1, Quality.WEBDL720p, 1, Quality.WEBDL720p, false }, new object[] { Quality.WEBDL1080p, 1, Quality.WEBDL1080p, 1, Quality.WEBDL1080p, false } }; - + + public static object[] IsUpgradeTestCasesLanguages = + { + new object[] { Quality.SDTV, 1, Language.English, Quality.SDTV, 2, Language.English, Quality.SDTV, Language.Spanish, true }, + new object[] { Quality.SDTV, 1, Language.English, Quality.SDTV, 1, Language.Spanish, Quality.SDTV, Language.Spanish, true }, + new object[] { Quality.WEBDL720p, 1, Language.French, Quality.WEBDL720p, 2, Language.English, Quality.WEBDL720p, Language.Spanish, true }, + new object[] { Quality.SDTV, 1, Language.English, Quality.SDTV, 1, Language.English, Quality.SDTV, Language.English, false }, + new object[] { Quality.WEBDL720p, 1, Language.English, Quality.HDTV720p, 2, Language.Spanish, Quality.Bluray720p, Language.Spanish, false }, + new object[] { Quality.WEBDL720p, 1, Language.Spanish, Quality.HDTV720p, 2, Language.French, Quality.WEBDL720p, Language.Spanish, false } + }; + [SetUp] public void Setup() { @@ -41,9 +54,40 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { GivenAutoDownloadPropers(true); - var profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }; - Subject.IsUpgradable(profile, new QualityModel(current, new Revision(version: currentVersion)), new QualityModel(newQuality, new Revision(version: newVersion))) + var profile = new Profile + { + Items = Qualities.QualityFixture.GetDefaultQualities() + }; + + var langProfile = new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(), + Cutoff = Language.English + }; + + Subject.IsUpgradable(profile, langProfile, new QualityModel(current, new Revision(version: currentVersion)), Language.English, new QualityModel(newQuality, new Revision(version: newVersion)), Language.English) + .Should().Be(expected); + } + + [Test, TestCaseSource("IsUpgradeTestCasesLanguages")] + public void IsUpgradeTestLanguage(Quality current, int currentVersion, Language currentLanguage, Quality newQuality, int newVersion, Language newLanguage, Quality cutoff, Language languageCutoff, bool expected) + { + GivenAutoDownloadPropers(true); + + var profile = new Profile + { + Items = Qualities.QualityFixture.GetDefaultQualities(), + Cutoff = cutoff, + }; + + var langProfile = new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(), + Cutoff = languageCutoff + }; + + Subject.IsUpgradable(profile, langProfile, new QualityModel(current, new Revision(version: currentVersion)), currentLanguage, new QualityModel(newQuality, new Revision(version: newVersion)), newLanguage) .Should().Be(expected); } @@ -52,9 +96,19 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { GivenAutoDownloadPropers(false); - var profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }; + var profile = new Profile + { + Items = Qualities.QualityFixture.GetDefaultQualities(), + }; - Subject.IsUpgradable(profile, new QualityModel(Quality.DVD, new Revision(version: 2)), new QualityModel(Quality.DVD, new Revision(version: 1))) + var langProfile = new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(), + Cutoff = Language.English + }; + + + Subject.IsUpgradable(profile, langProfile, new QualityModel(Quality.DVD, new Revision(version: 2)), Language.English, new QualityModel(Quality.DVD, new Revision(version: 1)), Language.English) .Should().BeFalse(); } } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs index 6ed0cbde4..adf625e3c 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs @@ -5,12 +5,14 @@ using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine.Specifications; +using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Queue; using NzbDrone.Core.Tv; using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Languages; namespace NzbDrone.Core.Test.DecisionEngineTests { @@ -27,10 +29,18 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [SetUp] public void Setup() { - Mocker.Resolve(); + Mocker.Resolve(); _series = Builder.CreateNew() - .With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }) + .With(e => e.Profile = new Profile + { + Items = Qualities.QualityFixture.GetDefaultQualities(), + }) + .With(l => l.LanguageProfile = new LanguageProfile + { + Languages = Languages.LanguageFixture.GetDefaultLanguages(), + Cutoff = Language.Spanish + }) .Build(); _episode = Builder.CreateNew() @@ -51,7 +61,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _remoteEpisode = Builder.CreateNew() .With(r => r.Series = _series) .With(r => r.Episodes = new List { _episode }) - .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD) }) + .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD) , Language = Language.Spanish}) .Build(); } @@ -97,13 +107,15 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void should_return_true_when_quality_in_queue_is_lower() { _series.Profile.Value.Cutoff = Quality.Bluray1080p; + _series.LanguageProfile.Value.Cutoff = Language.Spanish; var remoteEpisode = Builder.CreateNew() .With(r => r.Series = _series) .With(r => r.Episodes = new List { _episode }) .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { - Quality = new QualityModel(Quality.SDTV) + Quality = new QualityModel(Quality.SDTV), + Language = Language.Spanish }) .Build(); @@ -111,6 +123,26 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); } + [Test] + public void should_return_true_when_quality_in_queue_is_lower_but_language_is_higher() + { + _series.Profile.Value.Cutoff = Quality.Bluray1080p; + _series.LanguageProfile.Value.Cutoff = Language.Spanish; + + var remoteEpisode = Builder.CreateNew() + .With(r => r.Series = _series) + .With(r => r.Episodes = new List { _episode }) + .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo + { + Quality = new QualityModel(Quality.SDTV), + Language = Language.English + }) + .Build(); + + GivenQueue(new List { remoteEpisode }); + Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); + } + [Test] public void should_return_true_when_episode_doesnt_match() { @@ -128,14 +160,15 @@ namespace NzbDrone.Core.Test.DecisionEngineTests } [Test] - public void should_return_false_when_qualities_are_the_same() + public void should_return_false_when_qualities_are_the_same_and_languages_are_the_same() { var remoteEpisode = Builder.CreateNew() .With(r => r.Series = _series) .With(r => r.Episodes = new List { _episode }) .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { - Quality = new QualityModel(Quality.DVD) + Quality = new QualityModel(Quality.DVD), + Language = Language.Spanish, }) .Build(); @@ -143,6 +176,23 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse(); } + [Test] + public void should_return_true_when_qualities_are_the_same_but_language_is_better() + { + var remoteEpisode = Builder.CreateNew() + .With(r => r.Series = _series) + .With(r => r.Episodes = new List { _episode }) + .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo + { + Quality = new QualityModel(Quality.DVD), + Language = Language.English, + }) + .Build(); + + GivenQueue(new List { remoteEpisode }); + Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); + } + [Test] public void should_return_false_when_quality_in_queue_is_better() { @@ -153,7 +203,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests .With(r => r.Episodes = new List { _episode }) .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { - Quality = new QualityModel(Quality.HDTV720p) + Quality = new QualityModel(Quality.HDTV720p), + Language = Language.English }) .Build(); @@ -169,7 +220,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests .With(r => r.Episodes = new List { _episode, _otherEpisode }) .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { - Quality = new QualityModel(Quality.HDTV720p) + Quality = new QualityModel(Quality.HDTV720p), + Language = Language.English }) .Build(); @@ -185,7 +237,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests .With(r => r.Episodes = new List { _episode }) .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { - Quality = new QualityModel(Quality.HDTV720p) + Quality = new QualityModel(Quality.HDTV720p), + Language = Language.English }) .Build(); @@ -203,7 +256,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests .With(r => r.Episodes = new List { _episode, _otherEpisode }) .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { - Quality = new QualityModel(Quality.HDTV720p) + Quality = new QualityModel(Quality.HDTV720p), + Language = Language.English }) .Build(); @@ -223,7 +277,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { Quality = new QualityModel( - Quality.HDTV720p) + Quality.HDTV720p), + Language = Language.English }) .TheFirst(1) .With(r => r.Episodes = new List { _episode }) @@ -237,7 +292,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests } [Test] - public void should_return_false_if_quality_in_queue_meets_cutoff() + public void should_return_false_if_quality_and_language_in_queue_meets_cutoff() { _series.Profile.Value.Cutoff = _remoteEpisode.ParsedEpisodeInfo.Quality.Quality; @@ -246,7 +301,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests .With(r => r.Episodes = new List { _episode }) .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { - Quality = new QualityModel(Quality.HDTV720p) + Quality = new QualityModel(Quality.HDTV720p), + Language = Language.Spanish }) .Build(); diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs index 2bbe1ae24..c60d6be82 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs @@ -13,11 +13,13 @@ using NzbDrone.Core.Indexers; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles; using NzbDrone.Core.Profiles.Delay; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles.Qualities; +using NzbDrone.Core.Profiles.Languages; namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync { @@ -25,6 +27,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync public class DelaySpecificationFixture : CoreTest { private Profile _profile; + private LanguageProfile _langProfile; private DelayProfile _delayProfile; private RemoteEpisode _remoteEpisode; @@ -34,12 +37,16 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync _profile = Builder.CreateNew() .Build(); + _langProfile = Builder.CreateNew() + .Build(); + _delayProfile = Builder.CreateNew() .With(d => d.PreferredProtocol = DownloadProtocol.Usenet) .Build(); var series = Builder.CreateNew() .With(s => s.Profile = _profile) + .With(s => s.LanguageProfile = _langProfile) .Build(); _remoteEpisode = Builder.CreateNew() @@ -53,6 +60,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync _profile.Cutoff = Quality.WEBDL720p; + _langProfile.Cutoff = Language.Spanish; + _langProfile.Languages = Languages.LanguageFixture.GetDefaultLanguages(); + _remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo(); _remoteEpisode.Release = new ReleaseInfo(); _remoteEpisode.Release.DownloadProtocol = DownloadProtocol.Usenet; @@ -69,20 +79,21 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync .Returns(new List()); } - private void GivenExistingFile(QualityModel quality) + private void GivenExistingFile(QualityModel quality, Language language) { _remoteEpisode.Episodes.First().EpisodeFileId = 1; _remoteEpisode.Episodes.First().EpisodeFile = new LazyLoaded(new EpisodeFile { - Quality = quality + Quality = quality, + Language = language }); } private void GivenUpgradeForExistingFile() { - Mocker.GetMock() - .Setup(s => s.IsUpgradable(It.IsAny(), It.IsAny(), It.IsAny())) + Mocker.GetMock() + .Setup(s => s.IsUpgradable(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(true); } @@ -112,9 +123,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync } [Test] - public void should_be_true_when_quality_is_last_allowed_in_profile() + public void should_be_true_when_quality_and_language_is_last_allowed_in_profile() { _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.Bluray720p); + _remoteEpisode.ParsedEpisodeInfo.Language = Language.French; Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); } @@ -147,10 +159,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p, new Revision(version: 2)); _remoteEpisode.Release.PublishDate = DateTime.UtcNow; - GivenExistingFile(new QualityModel(Quality.HDTV720p)); + GivenExistingFile(new QualityModel(Quality.HDTV720p), Language.English); GivenUpgradeForExistingFile(); - Mocker.GetMock() + Mocker.GetMock() .Setup(s => s.IsRevisionUpgrade(It.IsAny(), It.IsAny())) .Returns(true); @@ -165,10 +177,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p, new Revision(real: 1)); _remoteEpisode.Release.PublishDate = DateTime.UtcNow; - GivenExistingFile(new QualityModel(Quality.HDTV720p)); + GivenExistingFile(new QualityModel(Quality.HDTV720p), Language.English); GivenUpgradeForExistingFile(); - Mocker.GetMock() + Mocker.GetMock() .Setup(s => s.IsRevisionUpgrade(It.IsAny(), It.IsAny())) .Returns(true); @@ -183,7 +195,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p, new Revision(version: 2)); _remoteEpisode.Release.PublishDate = DateTime.UtcNow; - GivenExistingFile(new QualityModel(Quality.SDTV)); + GivenExistingFile(new QualityModel(Quality.SDTV), Language.English); _delayProfile.UsenetDelay = 720; diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedEpisodeFileSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedEpisodeFileSpecificationFixture.cs index a1e3691a7..72559e807 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedEpisodeFileSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedEpisodeFileSpecificationFixture.cs @@ -18,6 +18,7 @@ using NzbDrone.Common.Disk; using Moq; using NzbDrone.Test.Common; using System.IO; +using NzbDrone.Core.Profiles.Qualities; namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync { diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/HistorySpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/HistorySpecificationFixture.cs index e781daab9..eb0927662 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/HistorySpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/HistorySpecificationFixture.cs @@ -9,12 +9,14 @@ using NzbDrone.Core.DecisionEngine.Specifications.RssSync; using NzbDrone.Core.History; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; -using NzbDrone.Core.DecisionEngine; - using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Profiles.Qualities; +using NzbDrone.Core.Profiles.Languages; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Test.Languages; namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync { @@ -25,8 +27,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync private RemoteEpisode _parseResultMulti; private RemoteEpisode _parseResultSingle; - private QualityModel _upgradableQuality; - private QualityModel _notupgradableQuality; + private Tuple _upgradableQuality; + private Tuple _notupgradableQuality; private Series _fakeSeries; private const int FIRST_EPISODE_ID = 1; private const int SECOND_EPISODE_ID = 2; @@ -34,7 +36,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync [SetUp] public void Setup() { - Mocker.Resolve(); + Mocker.Resolve(); _upgradeHistory = Mocker.Resolve(); var singleEpisodeList = new List { new Episode { Id = FIRST_EPISODE_ID, SeasonNumber = 12, EpisodeNumber = 3 } }; @@ -46,34 +48,36 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync _fakeSeries = Builder.CreateNew() .With(c => c.Profile = new Profile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities() }) + .With(l => l.LanguageProfile = new LanguageProfile { Cutoff = Language.Spanish, Languages = LanguageFixture.GetDefaultLanguages() }) .Build(); _parseResultMulti = new RemoteEpisode { Series = _fakeSeries, - ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)) }, + ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)), Language = Language.English }, Episodes = doubleEpisodeList }; _parseResultSingle = new RemoteEpisode { Series = _fakeSeries, - ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)) }, + ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)), Language = Language.English }, Episodes = singleEpisodeList }; - _upgradableQuality = new QualityModel(Quality.SDTV, new Revision(version: 1)); - _notupgradableQuality = new QualityModel(Quality.HDTV1080p, new Revision(version: 2)); + _upgradableQuality = new Tuple (new QualityModel(Quality.SDTV, new Revision(version: 1)), Language.English); + + _notupgradableQuality = new Tuple (new QualityModel(Quality.HDTV1080p, new Revision(version: 2)), Language.English); Mocker.GetMock() .SetupGet(s => s.EnableCompletedDownloadHandling) .Returns(true); } - private void GivenMostRecentForEpisode(int episodeId, string downloadId, QualityModel quality, DateTime date, HistoryEventType eventType) + private void GivenMostRecentForEpisode(int episodeId, string downloadId, Tuple quality, DateTime date, HistoryEventType eventType) { Mocker.GetMock().Setup(s => s.MostRecentForEpisode(episodeId)) - .Returns(new History.History { DownloadId = downloadId, Quality = quality, Date = date, EventType = eventType }); + .Returns(new History.History { DownloadId = downloadId, Quality = quality.Item1, Date = date, EventType = eventType, Language = quality.Item2 }); } private void GivenCdhDisabled() @@ -161,19 +165,32 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync { _fakeSeries.Profile = new Profile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities() }; _parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)); - _upgradableQuality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)); + _upgradableQuality = new Tuple(new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)), Language.English); GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed); _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); } + [Test] + public void should_be_upgradable_if_episode_is_of_same_quality_as_existing_but_new_has_better_language() + { + _fakeSeries.Profile = new Profile { Cutoff = Quality.WEBDL1080p, Items = Qualities.QualityFixture.GetDefaultQualities() }; + _parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)); + _parseResultSingle.ParsedEpisodeInfo.Language = Language.Spanish; + _upgradableQuality = new Tuple(new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)), Language.English); + + GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed); + + _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue(); + } + [Test] public void should_not_be_upgradable_if_cutoff_already_met() { _fakeSeries.Profile = new Profile { Cutoff = Quality.WEBDL1080p, Items = Qualities.QualityFixture.GetDefaultQualities() }; _parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)); - _upgradableQuality = new QualityModel(Quality.Bluray1080p, new Revision(version: 1)); + _upgradableQuality = new Tuple(new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)), Language.Spanish); GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed); @@ -201,7 +218,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync GivenCdhDisabled(); _fakeSeries.Profile = new Profile { Cutoff = Quality.WEBDL1080p, Items = Qualities.QualityFixture.GetDefaultQualities() }; _parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 1)); - _upgradableQuality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)); + _upgradableQuality = new Tuple(new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)), Language.Spanish); GivenMostRecentForEpisode(FIRST_EPISODE_ID, "test", _upgradableQuality, DateTime.UtcNow.AddDays(-100), HistoryEventType.Grabbed); diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs index 0d711c1a0..59745e929 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs @@ -8,7 +8,7 @@ using NzbDrone.Core.DecisionEngine.Specifications.RssSync; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.DecisionEngine; @@ -29,7 +29,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync [SetUp] public void Setup() { - Mocker.Resolve(); + Mocker.Resolve(); _firstFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 1)), DateAdded = DateTime.Now }; _secondFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 1)), DateAdded = DateTime.Now }; diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs index ab5795267..9935b52f3 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs @@ -6,12 +6,13 @@ using NUnit.Framework; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.DecisionEngine; - using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles.Languages; namespace NzbDrone.Core.Test.DecisionEngineTests { @@ -29,30 +30,33 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [SetUp] public void Setup() { - Mocker.Resolve(); + Mocker.Resolve(); _upgradeDisk = Mocker.Resolve(); - _firstFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), DateAdded = DateTime.Now }; - _secondFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), DateAdded = DateTime.Now }; + _firstFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), DateAdded = DateTime.Now, Language = Language.English }; + _secondFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), DateAdded = DateTime.Now, Language = Language.English }; var singleEpisodeList = new List { new Episode { EpisodeFile = _firstFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = null } }; var doubleEpisodeList = new List { new Episode { EpisodeFile = _firstFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = _secondFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = null } }; + var languages = Languages.LanguageFixture.GetDefaultLanguages(Language.English, Language.Spanish); + var fakeSeries = Builder.CreateNew() - .With(c => c.Profile = new Profile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities() }) + .With(c => c.Profile = new Profile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities()}) + .With(l => l.LanguageProfile = new LanguageProfile { Cutoff = Language.Spanish, Languages = languages }) .Build(); _parseResultMulti = new RemoteEpisode { Series = fakeSeries, - ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)) }, + ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)), Language = Language.English }, Episodes = doubleEpisodeList }; _parseResultSingle = new RemoteEpisode { Series = fakeSeries, - ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)) }, + ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)), Language = Language.English }, Episodes = singleEpisodeList }; } diff --git a/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs index 391d50410..77a405d23 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs @@ -11,7 +11,7 @@ using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Exceptions; using NzbDrone.Core.Indexers; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; diff --git a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/AddFixture.cs b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/AddFixture.cs index 5820b1f0b..2064e01dd 100644 --- a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/AddFixture.cs +++ b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/AddFixture.cs @@ -10,7 +10,7 @@ using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; diff --git a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveGrabbedFixture.cs b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveGrabbedFixture.cs index 70ec02288..ebc2bbdda 100644 --- a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveGrabbedFixture.cs +++ b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveGrabbedFixture.cs @@ -10,7 +10,7 @@ using NzbDrone.Core.Download; using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; diff --git a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveRejectedFixture.cs b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveRejectedFixture.cs index 0089af185..7d466305b 100644 --- a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveRejectedFixture.cs +++ b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveRejectedFixture.cs @@ -11,7 +11,7 @@ using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Indexers; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; diff --git a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs index d57c01893..2f4139433 100644 --- a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs +++ b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs @@ -7,15 +7,15 @@ using NUnit.Framework; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.History; using NzbDrone.Core.Qualities; -using System.Collections.Generic; using NzbDrone.Core.Test.Qualities; -using FluentAssertions; using NzbDrone.Core.Download; using NzbDrone.Core.Tv; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles.Languages; namespace NzbDrone.Core.Test.HistoryTests { @@ -23,48 +23,31 @@ namespace NzbDrone.Core.Test.HistoryTests { private Profile _profile; private Profile _profileCustom; + private LanguageProfile _languageProfile; [SetUp] public void Setup() { - _profile = new Profile { Cutoff = Quality.WEBDL720p, Items = QualityFixture.GetDefaultQualities() }; - _profileCustom = new Profile { Cutoff = Quality.WEBDL720p, Items = QualityFixture.GetDefaultQualities(Quality.DVD) }; - } + _profile = new Profile + { + Cutoff = Quality.WEBDL720p, + Items = QualityFixture.GetDefaultQualities(), + }; - [Test] - public void should_return_null_if_no_history() - { - Mocker.GetMock() - .Setup(v => v.GetBestQualityInHistory(2)) - .Returns(new List()); + _profileCustom = new Profile + { + Cutoff = Quality.WEBDL720p, + Items = QualityFixture.GetDefaultQualities(Quality.DVD), - var quality = Subject.GetBestQualityInHistory(_profile, 2); + }; - quality.Should().BeNull(); - } - [Test] - public void should_return_best_quality() - { - Mocker.GetMock() - .Setup(v => v.GetBestQualityInHistory(2)) - .Returns(new List { new QualityModel(Quality.DVD), new QualityModel(Quality.Bluray1080p) }); - - var quality = Subject.GetBestQualityInHistory(_profile, 2); - - quality.Should().Be(new QualityModel(Quality.Bluray1080p)); - } - - [Test] - public void should_return_best_quality_with_custom_order() - { - Mocker.GetMock() - .Setup(v => v.GetBestQualityInHistory(2)) - .Returns(new List { new QualityModel(Quality.DVD), new QualityModel(Quality.Bluray1080p) }); - - var quality = Subject.GetBestQualityInHistory(_profileCustom, 2); - - quality.Should().Be(new QualityModel(Quality.DVD)); + _languageProfile = new LanguageProfile + { + Cutoff = Language.Spanish, + Languages = Languages.LanguageFixture.GetDefaultLanguages() + }; + } [Test] diff --git a/src/NzbDrone.Core.Test/Languages/LanguageFixture.cs b/src/NzbDrone.Core.Test/Languages/LanguageFixture.cs new file mode 100644 index 000000000..91c97b13e --- /dev/null +++ b/src/NzbDrone.Core.Test/Languages/LanguageFixture.cs @@ -0,0 +1,100 @@ +using System.Linq; +using System.Collections.Generic; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Profiles.Languages; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Languages; + +namespace NzbDrone.Core.Test.Languages +{ + [TestFixture] + public class LanguageFixture : CoreTest + { + public static object[] FromIntCases = + { + new object[] {1, Language.English}, + new object[] {2, Language.French}, + new object[] {3, Language.Spanish}, + new object[] {4, Language.German}, + new object[] {5, Language.Italian}, + new object[] {6, Language.Danish}, + new object[] {7, Language.Dutch}, + new object[] {8, Language.Japanese}, + new object[] {9, Language.Cantonese}, + new object[] {10, Language.Mandarin}, + new object[] {11, Language.Russian}, + new object[] {12, Language.Polish}, + new object[] {13, Language.Vietnamese}, + new object[] {14, Language.Swedish}, + new object[] {15, Language.Norwegian}, + new object[] {16, Language.Finnish}, + new object[] {17, Language.Turkish}, + new object[] {18, Language.Portuguese}, + new object[] {19, Language.Flemish}, + new object[] {20, Language.Greek}, + new object[] {21, Language.Korean}, + new object[] {22, Language.Hungarian} + }; + + public static object[] ToIntCases = + { + new object[] {Language.English, 1}, + new object[] {Language.French, 2}, + new object[] {Language.Spanish, 3}, + new object[] {Language.German, 4}, + new object[] {Language.Italian, 5}, + new object[] {Language.Danish, 6}, + new object[] {Language.Dutch, 7}, + new object[] {Language.Japanese, 8}, + new object[] {Language.Cantonese, 9}, + new object[] {Language.Mandarin, 10}, + new object[] {Language.Russian, 11}, + new object[] {Language.Polish, 12}, + new object[] {Language.Vietnamese, 13}, + new object[] {Language.Swedish, 14}, + new object[] {Language.Norwegian, 15}, + new object[] {Language.Finnish, 16}, + new object[] {Language.Turkish, 17}, + new object[] {Language.Portuguese, 18}, + new object[] {Language.Flemish, 19}, + new object[] {Language.Greek, 20}, + new object[] {Language.Korean, 21}, + new object[] {Language.Hungarian, 22} + }; + + [Test, TestCaseSource("FromIntCases")] + public void should_be_able_to_convert_int_to_languageTypes(int source, Language expected) + { + var language = (Language)source; + language.Should().Be(expected); + } + + [Test, TestCaseSource("ToIntCases")] + public void should_be_able_to_convert_languageTypes_to_int(Language source, int expected) + { + var i = (int)source; + i.Should().Be(expected); + } + + public static List GetDefaultLanguages(params Language[] allowed) + { + var languages = new List + { + Language.English, + Language.Spanish, + Language.French + }; + + if (allowed.Length == 0) + allowed = languages.ToArray(); + + var items = languages + .Except(allowed) + .Concat(allowed) + .Select(v => new LanguageProfileItem { Language = v, Allowed = allowed.Contains(v) }).ToList(); + + return items; + } + } +} diff --git a/src/NzbDrone.Core.Test/Languages/LanguageProfileRepositoryFixture.cs b/src/NzbDrone.Core.Test/Languages/LanguageProfileRepositoryFixture.cs new file mode 100644 index 000000000..8b04967be --- /dev/null +++ b/src/NzbDrone.Core.Test/Languages/LanguageProfileRepositoryFixture.cs @@ -0,0 +1,32 @@ +using FluentAssertions; +using System.Linq; +using NUnit.Framework; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Profiles.Languages; + +namespace NzbDrone.Core.Test.Languages +{ + [TestFixture] + public class LanguageProfileRepositoryFixture : DbTest + { + [Test] + public void should_be_able_to_read_and_write() + { + var profile = new LanguageProfile + { + Languages = Language.All.OrderByDescending(l => l.Name).Select(l => new LanguageProfileItem {Language = l, Allowed = l == Language.English}).ToList(), + Name = "TestProfile", + Cutoff = Language.English + }; + + Subject.Insert(profile); + + + StoredModel.Name.Should().Be(profile.Name); + StoredModel.Cutoff.Should().Be(profile.Cutoff); + + StoredModel.Languages.Should().Equal(profile.Languages, (a, b) => a.Language == b.Language && a.Allowed == b.Allowed); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/Languages/LanguageProfileServiceFixture.cs b/src/NzbDrone.Core.Test/Languages/LanguageProfileServiceFixture.cs new file mode 100644 index 000000000..873787d13 --- /dev/null +++ b/src/NzbDrone.Core.Test/Languages/LanguageProfileServiceFixture.cs @@ -0,0 +1,75 @@ +using System.Linq; +using FizzWare.NBuilder; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Profiles.Languages; + +namespace NzbDrone.Core.Test.Languages +{ + [TestFixture] + + public class LanguageProfileServiceFixture : CoreTest + { + [Test] + public void init_should_add_default_profiles() + { + Subject.Handle(new ApplicationStartedEvent()); + + Mocker.GetMock() + .Verify(v => v.Insert(It.IsAny()), Times.Once()); + } + + [Test] + //This confirms that new profiles are added only if no other profiles exists. + //We don't want to keep adding them back if a user deleted them on purpose. + public void Init_should_skip_if_any_profiles_already_exist() + { + Mocker.GetMock() + .Setup(s => s.All()) + .Returns(Builder.CreateListOfSize(2).Build().ToList()); + + Subject.Handle(new ApplicationStartedEvent()); + + Mocker.GetMock() + .Verify(v => v.Insert(It.IsAny()), Times.Never()); + } + + + [Test] + public void should_not_be_able_to_delete_profile_if_assigned_to_series() + { + var seriesList = Builder.CreateListOfSize(3) + .Random(1) + .With(c => c.LanguageProfileId = 2) + .Build().ToList(); + + + Mocker.GetMock().Setup(c => c.GetAllSeries()).Returns(seriesList); + + Assert.Throws(() => Subject.Delete(2)); + + Mocker.GetMock().Verify(c => c.Delete(It.IsAny()), Times.Never()); + + } + + + [Test] + public void should_delete_profile_if_not_assigned_to_series() + { + var seriesList = Builder.CreateListOfSize(3) + .All() + .With(c => c.LanguageProfileId = 2) + .Build().ToList(); + + + Mocker.GetMock().Setup(c => c.GetAllSeries()).Returns(seriesList); + + Subject.Delete(1); + + Mocker.GetMock().Verify(c => c.Delete(1), Times.Once()); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguageFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguageFixture.cs new file mode 100644 index 000000000..8fc911b03 --- /dev/null +++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguageFixture.cs @@ -0,0 +1,72 @@ +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Languages; +using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators +{ + [TestFixture] + public class AggregateLanguageFixture : CoreTest + { + private LocalEpisode _localEpisode; + + [SetUp] + public void Setup() + { + _localEpisode = Builder.CreateNew() + .With(l => l.DownloadClientEpisodeInfo = null) + .With(l => l.FolderEpisodeInfo = null) + .With(l => l.FileEpisodeInfo = null) + .Build(); + } + + private ParsedEpisodeInfo GetParsedEpisodeInfo(Language language) + { + return new ParsedEpisodeInfo + { + Language = language + }; + } + + [Test] + public void should_return_file_language_when_only_file_info_is_known() + { + _localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(Language.English); + + Subject.Aggregate(_localEpisode, false).Language.Should().Be(_localEpisode.FileEpisodeInfo.Language); + } + + [Test] + public void should_return_folder_language_when_folder_info_is_known() + { + _localEpisode.FolderEpisodeInfo = GetParsedEpisodeInfo(Language.English); + _localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(Language.English); + + Subject.Aggregate(_localEpisode, false).Language.Should().Be(_localEpisode.FolderEpisodeInfo.Language); + } + + [Test] + public void should_return_download_client_item_language_when_download_client_item_info_is_known() + { + _localEpisode.DownloadClientEpisodeInfo = GetParsedEpisodeInfo(Language.English); + _localEpisode.FolderEpisodeInfo = GetParsedEpisodeInfo(Language.English); + _localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(Language.English); + + Subject.Aggregate(_localEpisode, false).Language.Should().Be(_localEpisode.DownloadClientEpisodeInfo.Language); + + } + + [Test] + public void should_return_file_language_when_file_language_is_higher_than_others() + { + _localEpisode.DownloadClientEpisodeInfo = GetParsedEpisodeInfo(Language.English); + _localEpisode.FolderEpisodeInfo = GetParsedEpisodeInfo(Language.English); + _localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(Language.French); + + Subject.Aggregate(_localEpisode, false).Language.Should().Be(_localEpisode.FileEpisodeInfo.Language); + } + } +} diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs index ac9d8d97b..511903467 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using FluentAssertions; using Moq; @@ -7,14 +7,16 @@ using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Test.Common; using FizzWare.NBuilder; using NzbDrone.Core.Download; +using NzbDrone.Core.Languages; using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation; +using NzbDrone.Core.Profiles.Qualities; +using NzbDrone.Core.Profiles.Languages; namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport { @@ -56,6 +58,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport _series = Builder.CreateNew() .With(e => e.Path = @"C:\Test\Series".AsOsAgnostic()) .With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }) + .With(e => e.LanguageProfile = new LanguageProfile { Languages = Languages.LanguageFixture.GetDefaultLanguages() }) .Build(); _quality = new QualityModel(Quality.DVD); @@ -64,11 +67,12 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport { Series = _series, Quality = _quality, + Language = Language.Spanish, Episodes = new List { new Episode() }, - Path = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi" + Path = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.Spanish.XviD-OSiTV.avi" }; - GivenVideoFiles(new List { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi".AsOsAgnostic() }); + GivenVideoFiles(new List { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.Spanish.XviD-OSiTV.avi".AsOsAgnostic() }); } private void GivenSpecifications(params Mock[] mocks) @@ -179,7 +183,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport ExceptionVerification.ExpectedErrors(3); } - [Test] public void should_not_throw_if_episodes_are_not_found() { GivenSpecifications(_pass1); diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs index 3dbb2c7a8..14f936eca 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/Specifications/UpgradeSpecificationFixture.cs @@ -6,10 +6,12 @@ using NUnit.Framework; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles.Languages; namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications { @@ -24,13 +26,22 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications { _series = Builder.CreateNew() .With(s => s.SeriesType = SeriesTypes.Standard) - .With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }) + .With(e => e.Profile = new Profile + { + Items = Qualities.QualityFixture.GetDefaultQualities(), + }) + .With(l => l.LanguageProfile = new LanguageProfile + { + Languages = Languages.LanguageFixture.GetDefaultLanguages(), + Cutoff = Language.Spanish, + }) .Build(); _localEpisode = new LocalEpisode { Path = @"C:\Test\30 Rock\30.rock.s01e01.avi", Quality = new QualityModel(Quality.HDTV720p, new Revision(version: 1)), + Language = Language.Spanish, Series = _series }; } @@ -70,7 +81,8 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications .With(e => e.EpisodeFile = new LazyLoaded( new EpisodeFile { - Quality = new QualityModel(Quality.SDTV, new Revision(version: 1)) + Quality = new QualityModel(Quality.SDTV, new Revision(version: 1)), + Language = Language.Spanish })) .Build() .ToList(); @@ -78,6 +90,43 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue(); } + [Test] + public void should_return_true_if_language_upgrade_for_existing_episodeFile_and_quality_is_same() + { + _localEpisode.Episodes = Builder.CreateListOfSize(1) + .All() + .With(e => e.EpisodeFileId = 1) + .With(e => e.EpisodeFile = new LazyLoaded( + new EpisodeFile + { + Quality = new QualityModel(Quality.HDTV720p, new Revision(version: 1)), + Language = Language.English + })) + .Build() + .ToList(); + + Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_false_if_language_upgrade_for_existing_episodeFile_and_quality_is_worse() + { + _localEpisode.Episodes = Builder.CreateListOfSize(1) + .All() + .With(e => e.EpisodeFileId = 1) + .With(e => e.EpisodeFile = new LazyLoaded( + new EpisodeFile + { + Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 1)), + Language = Language.English + })) + .Build() + .ToList(); + + Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeFalse(); + } + + [Test] public void should_return_true_if_upgrade_for_existing_episodeFile_for_multi_episodes() { @@ -95,6 +144,43 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue(); } + [Test] + public void should_return_true_if_language_upgrade_for_existing_episodeFile_for_multi_episodes_and_quality_is_same() + { + _localEpisode.Episodes = Builder.CreateListOfSize(2) + .All() + .With(e => e.EpisodeFileId = 1) + .With(e => e.EpisodeFile = new LazyLoaded( + new EpisodeFile + { + Quality = new QualityModel(Quality.HDTV720p, new Revision(version: 1)), + Language = Language.English + })) + .Build() + .ToList(); + + Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_false_if_language_upgrade_for_existing_episodeFile_for_multi_episodes_and_quality_is_worse() + { + _localEpisode.Episodes = Builder.CreateListOfSize(2) + .All() + .With(e => e.EpisodeFileId = 1) + .With(e => e.EpisodeFile = new LazyLoaded( + new EpisodeFile + { + Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 1)), + Language = Language.English + })) + .Build() + .ToList(); + + Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeFalse(); + } + + [Test] public void should_return_false_if_not_an_upgrade_for_existing_episodeFile() { diff --git a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs index 2ecb0b1f9..19e4392f1 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs @@ -13,11 +13,13 @@ using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Test.Common; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles.Languages; namespace NzbDrone.Core.Test.MediaFiles { @@ -39,6 +41,11 @@ namespace NzbDrone.Core.Test.MediaFiles var series = Builder.CreateNew() .With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }) + .With(l => l.LanguageProfile = new LanguageProfile + { + Cutoff = Language.Spanish, + Languages = Languages.LanguageFixture.GetDefaultLanguages() + }) .With(s => s.Path = @"C:\Test\TV\30 Rock".AsOsAgnostic()) .Build(); diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 4f23ec461..2e582fbad 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -301,6 +301,7 @@ + @@ -309,6 +310,7 @@ + @@ -392,6 +394,8 @@ + + diff --git a/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs index 306e5290b..f4429af0f 100644 --- a/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs @@ -1,5 +1,6 @@ using FluentAssertions; using NUnit.Framework; +using NzbDrone.Core.Languages; using NzbDrone.Core.Parser; using NzbDrone.Core.Test.Framework; @@ -9,61 +10,208 @@ namespace NzbDrone.Core.Test.ParserTests [TestFixture] public class LanguageParserFixture : CoreTest { - [TestCase("Castle.2009.S01E14.English.HDTV.XviD-LOL", Language.English)] - [TestCase("Castle.2009.S01E14.French.HDTV.XviD-LOL", Language.French)] - [TestCase("Castle.2009.S01E14.Spanish.HDTV.XviD-LOL", Language.Spanish)] - [TestCase("Castle.2009.S01E14.German.HDTV.XviD-LOL", Language.German)] - [TestCase("Castle.2009.S01E14.Germany.HDTV.XviD-LOL", Language.English)] - [TestCase("Castle.2009.S01E14.Italian.HDTV.XviD-LOL", Language.Italian)] - [TestCase("Castle.2009.S01E14.Danish.HDTV.XviD-LOL", Language.Danish)] - [TestCase("Castle.2009.S01E14.Dutch.HDTV.XviD-LOL", Language.Dutch)] - [TestCase("Castle.2009.S01E14.Japanese.HDTV.XviD-LOL", Language.Japanese)] - [TestCase("Castle.2009.S01E14.Cantonese.HDTV.XviD-LOL", Language.Cantonese)] - [TestCase("Castle.2009.S01E14.Mandarin.HDTV.XviD-LOL", Language.Mandarin)] - [TestCase("Castle.2009.S01E14.Korean.HDTV.XviD-LOL", Language.Korean)] - [TestCase("Castle.2009.S01E14.Russian.HDTV.XviD-LOL", Language.Russian)] - [TestCase("Castle.2009.S01E14.Polish.HDTV.XviD-LOL", Language.Polish)] - [TestCase("Castle.2009.S01E14.Vietnamese.HDTV.XviD-LOL", Language.Vietnamese)] - [TestCase("Castle.2009.S01E14.Swedish.HDTV.XviD-LOL", Language.Swedish)] - [TestCase("Castle.2009.S01E14.Norwegian.HDTV.XviD-LOL", Language.Norwegian)] - [TestCase("Castle.2009.S01E14.Finnish.HDTV.XviD-LOL", Language.Finnish)] - [TestCase("Castle.2009.S01E14.Turkish.HDTV.XviD-LOL", Language.Turkish)] - [TestCase("Castle.2009.S01E14.Portuguese.HDTV.XviD-LOL", Language.Portuguese)] - [TestCase("Castle.2009.S01E14.HDTV.XviD-LOL", Language.English)] - [TestCase("person.of.interest.1x19.ita.720p.bdmux.x264-novarip", Language.Italian)] - [TestCase("Salamander.S01E01.FLEMISH.HDTV.x264-BRiGAND", Language.Flemish)] - [TestCase("H.Polukatoikia.S03E13.Greek.PDTV.XviD-Ouzo", Language.Greek)] - [TestCase("Burn.Notice.S04E15.Brotherly.Love.GERMAN.DUBBED.WS.WEBRiP.XviD.REPACK-TVP", Language.German)] - [TestCase("Ray Donovan - S01E01.720p.HDtv.x264-Evolve (NLsub)", Language.Dutch)] - [TestCase("Shield,.The.1x13.Tueurs.De.Flics.FR.DVDRip.XviD", Language.French)] - [TestCase("True.Detective.S01E01.1080p.WEB-DL.Rus.Eng.TVKlondike", Language.Russian)] - [TestCase("The.Trip.To.Italy.S02E01.720p.HDTV.x264-TLA", Language.English)] - [TestCase("Revolution S01E03 No Quarter 2012 WEB-DL 720p Nordic-philipo mkv", Language.Norwegian)] - [TestCase("Extant.S01E01.VOSTFR.HDTV.x264-RiDERS", Language.French)] - [TestCase("Constantine.2014.S01E01.WEBRiP.H264.AAC.5.1-NL.SUBS", Language.Dutch)] - [TestCase("Elementary - S02E16 - Kampfhaehne - mkv - by Videomann", Language.German)] - [TestCase("Two.Greedy.Italians.S01E01.The.Family.720p.HDTV.x264-FTP", Language.English)] - [TestCase("Castle.2009.S01E14.HDTV.XviD.HUNDUB-LOL", Language.Hungarian)] - [TestCase("Castle.2009.S01E14.HDTV.XviD.ENG.HUN-LOL", Language.Hungarian)] - [TestCase("Castle.2009.S01E14.HDTV.XviD.HUN-LOL", Language.Hungarian)] - [TestCase("Avatar.The.Last.Airbender.S01-03.DVDRip.HebDub",Language.Hebrew)] - [TestCase("Prison.Break.S05E01.WEBRip.x264.AC3.LT.EN-CNN", Language.Lithuanian)] - [TestCase("The.​Walking.​Dead.​S07E11.​WEB Rip.​XviD.​Louige-​CZ.​EN.​5.​1", Language.Czech)] - public void should_parse_language(string postTitle, Language language) + [TestCase("Castle.2009.S01E14.English.HDTV.XviD-LOL")] + [TestCase("Castle.2009.S01E14.Germany.HDTV.XviD-LOL")] + [TestCase("Castle.2009.S01E14.HDTV.XviD-LOL")] + [TestCase("Two.Greedy.Italians.S01E01.The.Family.720p.HDTV.x264-FTP")] + [TestCase("The.Trip.To.Italy.S02E01.720p.HDTV.x264-TLA")] + [TestCase("2 Broke Girls - S01E01 - Pilot.en.sub")] + [TestCase("2 Broke Girls - S01E01 - Pilot.eng.sub")] + [TestCase("2 Broke Girls - S01E01 - Pilot.English.sub")] + [TestCase("2 Broke Girls - S01E01 - Pilot.english.sub")] + public void should_parse_language_english(string postTitle) { var result = LanguageParser.ParseLanguage(postTitle); - result.Should().Be(language); + result.Should().Be(Language.English); } - [TestCase("2 Broke Girls - S01E01 - Pilot.en.sub", Language.English)] - [TestCase("2 Broke Girls - S01E01 - Pilot.eng.sub", Language.English)] - [TestCase("2 Broke Girls - S01E01 - Pilot.English.sub", Language.English)] - [TestCase("2 Broke Girls - S01E01 - Pilot.english.sub", Language.English)] - [TestCase("2 Broke Girls - S01E01 - Pilot.sub", Language.Unknown)] - public void should_parse_subtitle_language(string fileName, Language language) + [TestCase("2 Broke Girls - S01E01 - Pilot.sub")] + public void should_parse_subtitle_language_unknown(string fileName) { var result = LanguageParser.ParseSubtitleLanguage(fileName); - result.Should().Be(language); + result.Should().Be(Language.Unknown); } + + [TestCase("Castle.2009.S01E14.French.HDTV.XviD-LOL")] + [TestCase("Extant.S01E01.VOSTFR.HDTV.x264-RiDERS")] + [TestCase("Shield,.The.1x13.Tueurs.De.Flics.FR.DVDRip.XviD")] + public void should_parse_language_french(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.French.Id); + } + + [TestCase("Castle.2009.S01E14.Spanish.HDTV.XviD-LOL")] + public void should_parse_language_spanish(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Spanish.Id); + } + + [TestCase("Castle.2009.S01E14.German.HDTV.XviD-LOL")] + [TestCase("Burn.Notice.S04E15.Brotherly.Love.GERMAN.DUBBED.WS.WEBRiP.XviD.REPACK-TVP")] + [TestCase("Elementary - S02E16 - Kampfhaehne - mkv - by Videomann")] + public void should_parse_language_german(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.German.Id); + } + + [TestCase("Castle.2009.S01E14.Italian.HDTV.XviD-LOL")] + [TestCase("person.of.interest.1x19.ita.720p.bdmux.x264-novarip")] + public void should_parse_language_italian(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Italian.Id); + } + + [TestCase("Castle.2009.S01E14.Danish.HDTV.XviD-LOL")] + public void should_parse_language_danish(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Danish.Id); + } + + [TestCase("Castle.2009.S01E14.Dutch.HDTV.XviD-LOL")] + [TestCase("Constantine.2014.S01E01.WEBRiP.H264.AAC.5.1-NL.SUBS")] + [TestCase("Ray Donovan - S01E01.720p.HDtv.x264-Evolve (NLsub)")] + public void should_parse_language_dutch(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Dutch.Id); + } + + [TestCase("Castle.2009.S01E14.Japanese.HDTV.XviD-LOL")] + public void should_parse_language_japanese(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Japanese.Id); + } + + [TestCase("Castle.2009.S01E14.Cantonese.HDTV.XviD-LOL")] + public void should_parse_language_cantonese(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Cantonese.Id); + } + + [TestCase("Castle.2009.S01E14.Mandarin.HDTV.XviD-LOL")] + public void should_parse_language_mandarin(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Mandarin.Id); + } + + [TestCase("Castle.2009.S01E14.Korean.HDTV.XviD-LOL")] + public void should_parse_language_korean(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Korean.Id); + } + + [TestCase("Castle.2009.S01E14.Russian.HDTV.XviD-LOL")] + [TestCase("True.Detective.S01E01.1080p.WEB-DL.Rus.Eng.TVKlondike")] + public void should_parse_language_russian(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Russian.Id); + } + + [TestCase("Castle.2009.S01E14.Polish.HDTV.XviD-LOL")] + public void should_parse_language_polish(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Polish.Id); + } + + [TestCase("Castle.2009.S01E14.Vietnamese.HDTV.XviD-LOL")] + public void should_parse_language_vietnamese(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Vietnamese.Id); + } + + [TestCase("Castle.2009.S01E14.Swedish.HDTV.XviD-LOL")] + public void should_parse_language_swedish(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Swedish.Id); + } + + [TestCase("Castle.2009.S01E14.Norwegian.HDTV.XviD-LOL")] + [TestCase("Revolution S01E03 No Quarter 2012 WEB-DL 720p Nordic-philipo mkv")] + public void should_parse_language_norwegian(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Norwegian.Id); + } + + [TestCase("Castle.2009.S01E14.Finnish.HDTV.XviD-LOL")] + public void should_parse_language_finnish(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Finnish.Id); + } + + [TestCase("Castle.2009.S01E14.Turkish.HDTV.XviD-LOL")] + public void should_parse_language_turkish(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Turkish.Id); + } + + [TestCase("Castle.2009.S01E14.Portuguese.HDTV.XviD-LOL")] + public void should_parse_language_portuguese(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Portuguese.Id); + } + + [TestCase("Salamander.S01E01.FLEMISH.HDTV.x264-BRiGAND")] + public void should_parse_language_flemish(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Flemish.Id); + } + + [TestCase("H.Polukatoikia.S03E13.Greek.PDTV.XviD-Ouzo")] + public void should_parse_language_greek(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Greek.Id); + } + + [TestCase("Castle.2009.S01E14.HDTV.XviD.HUNDUB-LOL")] + [TestCase("Castle.2009.S01E14.HDTV.XviD.ENG.HUN-LOL")] + [TestCase("Castle.2009.S01E14.HDTV.XviD.HUN-LOL")] + public void should_parse_language_hungarian(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Hungarian.Id); + } + + [TestCase("Avatar.The.Last.Airbender.S01-03.DVDRip.HebDub")] + public void should_parse_language_hebrew(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Hebrew.Id); + } + + + [TestCase("Prison.Break.S05E01.WEBRip.x264.AC3.LT.EN-CNN")] + public void should_parse_language_lithuanian(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Lithuanian.Id); + } + + + [TestCase("The.​Walking.​Dead.​S07E11.​WEB Rip.​XviD.​Louige-​CZ.​EN.​5.​1")] + public void should_parse_language_czech(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Language.Id.Should().Be(Language.Czech.Id); + } + } } diff --git a/src/NzbDrone.Core.Test/Profiles/ProfileRepositoryFixture.cs b/src/NzbDrone.Core.Test/Profiles/ProfileRepositoryFixture.cs index 3c9003da7..90657296a 100644 --- a/src/NzbDrone.Core.Test/Profiles/ProfileRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/Profiles/ProfileRepositoryFixture.cs @@ -1,6 +1,6 @@ using FluentAssertions; using NUnit.Framework; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; diff --git a/src/NzbDrone.Core.Test/Profiles/ProfileServiceFixture.cs b/src/NzbDrone.Core.Test/Profiles/ProfileServiceFixture.cs index 737ddfd05..e0e68639b 100644 --- a/src/NzbDrone.Core.Test/Profiles/ProfileServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Profiles/ProfileServiceFixture.cs @@ -3,7 +3,7 @@ using FizzWare.NBuilder; using Moq; using NUnit.Framework; using NzbDrone.Core.Lifecycle; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; diff --git a/src/NzbDrone.Core.Test/Qualities/QualityFixture.cs b/src/NzbDrone.Core.Test/Qualities/QualityFixture.cs index 0087ef3e7..bbbfd3fbe 100644 --- a/src/NzbDrone.Core.Test/Qualities/QualityFixture.cs +++ b/src/NzbDrone.Core.Test/Qualities/QualityFixture.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using FluentAssertions; using NUnit.Framework; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; diff --git a/src/NzbDrone.Core.Test/Qualities/QualityModelComparerFixture.cs b/src/NzbDrone.Core.Test/Qualities/QualityModelComparerFixture.cs index 47ecbde16..8a5aac2fc 100644 --- a/src/NzbDrone.Core.Test/Qualities/QualityModelComparerFixture.cs +++ b/src/NzbDrone.Core.Test/Qualities/QualityModelComparerFixture.cs @@ -1,6 +1,6 @@ using FluentAssertions; using NUnit.Framework; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; diff --git a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWhereCutoffUnmetFixture.cs b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWhereCutoffUnmetFixture.cs index 363214a5d..9e44fbac8 100644 --- a/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWhereCutoffUnmetFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWhereCutoffUnmetFixture.cs @@ -5,11 +5,13 @@ using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Datastore; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Core.Qualities; using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles.Languages; namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests { @@ -20,6 +22,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests private Series _unmonitoredSeries; private PagingSpec _pagingSpec; private List _qualitiesBelowCutoff; + private List _languagesBelowCutoff; private List _unairedEpisodes; [SetUp] @@ -37,12 +40,20 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests } }; + var langProfile = new LanguageProfile + { + Id = 1, + Languages = Languages.LanguageFixture.GetDefaultLanguages(), + Cutoff = Language.Spanish + }; + _monitoredSeries = Builder.CreateNew() .With(s => s.TvRageId = RandomNumber) .With(s => s.Runtime = 30) .With(s => s.Monitored = true) .With(s => s.TitleSlug = "Title3") - .With(s => s.Id = profile.Id) + .With(s => s.ProfileId = profile.Id) + .With(s => s.LanguageProfileId = langProfile.Id) .BuildNew(); _unmonitoredSeries = Builder.CreateNew() @@ -50,7 +61,8 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests .With(s => s.Runtime = 30) .With(s => s.Monitored = false) .With(s => s.TitleSlug = "Title2") - .With(s => s.Id = profile.Id) + .With(s => s.ProfileId = profile.Id) + .With(s => s.LanguageProfileId = langProfile.Id) .BuildNew(); _monitoredSeries.Id = Db.Insert(_monitoredSeries).Id; @@ -69,15 +81,32 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests new QualitiesBelowCutoff(profile.Id, new[] {Quality.SDTV.Id}) }; - var qualityMet = new EpisodeFile { RelativePath = "a", Quality = new QualityModel { Quality = Quality.WEBDL480p } }; - var qualityUnmet = new EpisodeFile { RelativePath = "b", Quality = new QualityModel { Quality = Quality.SDTV } }; - var qualityRawHD = new EpisodeFile { RelativePath = "c", Quality = new QualityModel { Quality = Quality.RAWHD } }; + _languagesBelowCutoff = new List + { + new LanguagesBelowCutoff(profile.Id, new[] {Language.English.Id}) + }; + + var qualityMetLanguageUnmet = new EpisodeFile { RelativePath = "a", Quality = new QualityModel { Quality = Quality.WEBDL480p } , Language = Language.English }; + var qualityMetLanguageMet = new EpisodeFile { RelativePath = "b", Quality = new QualityModel { Quality = Quality.WEBDL480p }, Language = Language.Spanish }; + var qualityMetLanguageExceed = new EpisodeFile { RelativePath = "c", Quality = new QualityModel { Quality = Quality.WEBDL480p }, Language = Language.French }; + var qualityUnmetLanguageUnmet = new EpisodeFile { RelativePath = "d", Quality = new QualityModel { Quality = Quality.SDTV }, Language = Language.English }; + var qualityUnmetLanguageMet = new EpisodeFile { RelativePath = "e", Quality = new QualityModel { Quality = Quality.SDTV }, Language = Language.Spanish }; + var qualityUnmetLanguageExceed = new EpisodeFile { RelativePath = "f", Quality = new QualityModel { Quality = Quality.SDTV }, Language = Language.French }; + var qualityRawHDLanguageUnmet = new EpisodeFile { RelativePath = "g", Quality = new QualityModel { Quality = Quality.RAWHD }, Language = Language.English }; + var qualityRawHDLanguageMet = new EpisodeFile { RelativePath = "h", Quality = new QualityModel { Quality = Quality.RAWHD }, Language = Language.Spanish }; + var qualityRawHDLanguageExceed = new EpisodeFile { RelativePath = "i", Quality = new QualityModel { Quality = Quality.RAWHD }, Language = Language.French }; MediaFileRepository fileRepository = Mocker.Resolve(); - qualityMet = fileRepository.Insert(qualityMet); - qualityUnmet = fileRepository.Insert(qualityUnmet); - qualityRawHD = fileRepository.Insert(qualityRawHD); + qualityMetLanguageUnmet = fileRepository.Insert(qualityMetLanguageUnmet); + qualityMetLanguageMet = fileRepository.Insert(qualityMetLanguageMet); + qualityMetLanguageExceed = fileRepository.Insert(qualityMetLanguageExceed); + qualityUnmetLanguageUnmet = fileRepository.Insert(qualityUnmetLanguageUnmet); + qualityUnmetLanguageMet = fileRepository.Insert(qualityUnmetLanguageMet); + qualityUnmetLanguageExceed = fileRepository.Insert(qualityUnmetLanguageExceed); + qualityRawHDLanguageUnmet = fileRepository.Insert(qualityRawHDLanguageUnmet); + qualityRawHDLanguageMet = fileRepository.Insert(qualityRawHDLanguageMet); + qualityRawHDLanguageExceed = fileRepository.Insert(qualityRawHDLanguageExceed); var monitoredSeriesEpisodes = Builder.CreateListOfSize(4) .All() @@ -85,12 +114,12 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests .With(e => e.SeriesId = _monitoredSeries.Id) .With(e => e.AirDateUtc = DateTime.Now.AddDays(-5)) .With(e => e.Monitored = true) - .With(e => e.EpisodeFileId = qualityUnmet.Id) + .With(e => e.EpisodeFileId = qualityUnmetLanguageUnmet.Id) .TheFirst(1) .With(e => e.Monitored = false) - .With(e => e.EpisodeFileId = qualityMet.Id) + .With(e => e.EpisodeFileId = qualityMetLanguageMet.Id) .TheNext(1) - .With(e => e.EpisodeFileId = qualityRawHD.Id) + .With(e => e.EpisodeFileId = qualityRawHDLanguageExceed.Id) .TheLast(1) .With(e => e.SeasonNumber = 0) .Build(); @@ -101,10 +130,10 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests .With(e => e.SeriesId = _unmonitoredSeries.Id) .With(e => e.AirDateUtc = DateTime.Now.AddDays(-5)) .With(e => e.Monitored = true) - .With(e => e.EpisodeFileId = qualityUnmet.Id) + .With(e => e.EpisodeFileId = qualityRawHDLanguageUnmet.Id) .TheFirst(1) .With(e => e.Monitored = false) - .With(e => e.EpisodeFileId = qualityMet.Id) + .With(e => e.EpisodeFileId = qualityMetLanguageMet.Id) .TheLast(1) .With(e => e.SeasonNumber = 0) .Build(); @@ -116,7 +145,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests .With(e => e.SeriesId = _monitoredSeries.Id) .With(e => e.AirDateUtc = DateTime.Now.AddDays(5)) .With(e => e.Monitored = true) - .With(e => e.EpisodeFileId = qualityUnmet.Id) + .With(e => e.EpisodeFileId = qualityUnmetLanguageUnmet.Id) .Build() .ToList(); @@ -139,7 +168,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests { GivenMonitoredFilterExpression(); - var spec = Subject.EpisodesWhereCutoffUnmet(_pagingSpec, _qualitiesBelowCutoff, false); + var spec = Subject.EpisodesWhereCutoffUnmet(_pagingSpec, _qualitiesBelowCutoff, _languagesBelowCutoff, false); spec.Records.Should().HaveCount(1); spec.Records.Should().OnlyContain(e => e.EpisodeFile.Value.Quality.Quality == Quality.SDTV); @@ -150,7 +179,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests { GivenMonitoredFilterExpression(); - var spec = Subject.EpisodesWhereCutoffUnmet(_pagingSpec, _qualitiesBelowCutoff, false); + var spec = Subject.EpisodesWhereCutoffUnmet(_pagingSpec, _qualitiesBelowCutoff, _languagesBelowCutoff, false); spec.Records.Should().HaveCount(1); spec.Records.Should().OnlyContain(e => e.Monitored); @@ -161,7 +190,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests { GivenMonitoredFilterExpression(); - var spec = Subject.EpisodesWhereCutoffUnmet(_pagingSpec, _qualitiesBelowCutoff, false); + var spec = Subject.EpisodesWhereCutoffUnmet(_pagingSpec, _qualitiesBelowCutoff, _languagesBelowCutoff, false); spec.Records.Should().HaveCount(1); spec.Records.Should().OnlyContain(e => e.Series.Monitored); @@ -174,7 +203,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests GivenMonitoredFilterExpression(); - var spec = Subject.EpisodesWhereCutoffUnmet(_pagingSpec, _qualitiesBelowCutoff, false); + var spec = Subject.EpisodesWhereCutoffUnmet(_pagingSpec, _qualitiesBelowCutoff, _languagesBelowCutoff, false); spec.Records.Should().HaveCount(2); spec.Records.Should().OnlyContain(e => e.Series.Monitored); diff --git a/src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/SeriesRepositoryFixture.cs b/src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/SeriesRepositoryFixture.cs index bbd18e7e1..8816e0231 100644 --- a/src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/SeriesRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/SeriesRepositoryTests/SeriesRepositoryFixture.cs @@ -1,10 +1,13 @@ using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; -using NzbDrone.Core.Profiles; +using System.Linq; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; +using NzbDrone.Core.Profiles.Languages; namespace NzbDrone.Core.Test.TvTests.SeriesRepositoryTests { @@ -23,16 +26,26 @@ namespace NzbDrone.Core.Test.TvTests.SeriesRepositoryTests Name = "TestProfile" }; + var langProfile = new LanguageProfile + { + Name = "TestProfile", + Languages = Languages.LanguageFixture.GetDefaultLanguages(Language.English), + Cutoff = Language.English + }; + Mocker.Resolve().Insert(profile); + Mocker.Resolve().Insert(langProfile); var series = Builder.CreateNew().BuildNew(); series.ProfileId = profile.Id; + series.LanguageProfileId = langProfile.Id; Subject.Insert(series); StoredModel.Profile.Should().NotBeNull(); + StoredModel.LanguageProfile.Should().NotBeNull(); } diff --git a/src/NzbDrone.Core/Blacklisting/Blacklist.cs b/src/NzbDrone.Core/Blacklisting/Blacklist.cs index 1c0813ac0..3ca37cdc2 100644 --- a/src/NzbDrone.Core/Blacklisting/Blacklist.cs +++ b/src/NzbDrone.Core/Blacklisting/Blacklist.cs @@ -4,6 +4,7 @@ using NzbDrone.Core.Datastore; using NzbDrone.Core.Indexers; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; +using NzbDrone.Core.Languages; namespace NzbDrone.Core.Blacklisting { @@ -21,5 +22,6 @@ namespace NzbDrone.Core.Blacklisting public string Indexer { get; set; } public string Message { get; set; } public string TorrentInfoHash { get; set; } + public Language Language { get; set; } } } diff --git a/src/NzbDrone.Core/Blacklisting/BlacklistService.cs b/src/NzbDrone.Core/Blacklisting/BlacklistService.cs index 1c0829004..c08020b22 100644 --- a/src/NzbDrone.Core/Blacklisting/BlacklistService.cs +++ b/src/NzbDrone.Core/Blacklisting/BlacklistService.cs @@ -138,7 +138,8 @@ namespace NzbDrone.Core.Blacklisting Indexer = message.Data.GetValueOrDefault("indexer"), Protocol = (DownloadProtocol)Convert.ToInt32(message.Data.GetValueOrDefault("protocol")), Message = message.Message, - TorrentInfoHash = message.Data.GetValueOrDefault("torrentInfoHash") + TorrentInfoHash = message.Data.GetValueOrDefault("torrentInfoHash"), + Language = message.Language }; _blacklistRepository.Insert(blacklist); diff --git a/src/NzbDrone.Core/Datastore/Converters/LanguageIntConverter.cs b/src/NzbDrone.Core/Datastore/Converters/LanguageIntConverter.cs new file mode 100644 index 000000000..0d71b1b72 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Converters/LanguageIntConverter.cs @@ -0,0 +1,65 @@ +using System; +using Marr.Data.Converters; +using Marr.Data.Mapping; +using Newtonsoft.Json; +using NzbDrone.Core.Languages; + +namespace NzbDrone.Core.Datastore.Converters +{ + public class LanguageIntConverter : JsonConverter, IConverter + { + public object FromDB(ConverterContext context) + { + if (context.DbValue == DBNull.Value) + { + return Language.Unknown; + } + + var val = Convert.ToInt32(context.DbValue); + + return (Language)val; + } + + public object FromDB(ColumnMap map, object dbValue) + { + return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue }); + } + + public object ToDB(object clrValue) + { + if (clrValue == DBNull.Value) return 0; + + if (clrValue as Language == null) + { + throw new InvalidOperationException("Attempted to save a language that isn't really a language"); + } + + var language = clrValue as Language; + return (int)language; + } + + public Type DbType + { + get + { + return typeof(int); + } + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Language); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var item = reader.Value; + return (Language)Convert.ToInt32(item); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteValue(ToDB(value)); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Datastore/Migration/036_update_with_quality_converters.cs b/src/NzbDrone.Core/Datastore/Migration/036_update_with_quality_converters.cs index 37d94e33d..32fbdd842 100644 --- a/src/NzbDrone.Core/Datastore/Migration/036_update_with_quality_converters.cs +++ b/src/NzbDrone.Core/Datastore/Migration/036_update_with_quality_converters.cs @@ -3,7 +3,7 @@ using NzbDrone.Core.Datastore.Migration.Framework; using System.Data; using System.Linq; using NzbDrone.Common.Serializer; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using System.Collections.Generic; using NzbDrone.Core.Datastore.Converters; diff --git a/src/NzbDrone.Core/Datastore/Migration/102_add_language_to_episodeFiles_history_and_blacklist.cs b/src/NzbDrone.Core/Datastore/Migration/102_add_language_to_episodeFiles_history_and_blacklist.cs index 8070cce3a..39f46e333 100644 --- a/src/NzbDrone.Core/Datastore/Migration/102_add_language_to_episodeFiles_history_and_blacklist.cs +++ b/src/NzbDrone.Core/Datastore/Migration/102_add_language_to_episodeFiles_history_and_blacklist.cs @@ -1 +1,97 @@ -// This is a placeholder for migration 102 \ No newline at end of file +using System.Data; +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; +using NzbDrone.Core.Datastore.Converters; +using NzbDrone.Core.Languages; +using System; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(102)] + public class add_language_to_episodeFiles_history_and_blacklist : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("EpisodeFiles") + .AddColumn("Language").AsInt32().NotNullable().WithDefaultValue(0); + + Alter.Table("History") + .AddColumn("Language").AsInt32().NotNullable().WithDefaultValue(0); + + Alter.Table("Blacklist") + .AddColumn("Language").AsInt32().NotNullable().WithDefaultValue(0); + + Execute.WithConnection(UpdateLanguage); + } + + private void UpdateLanguage(IDbConnection conn, IDbTransaction tran) + { + var LanguageConverter = new EmbeddedDocumentConverter(new LanguageIntConverter()); + + using (IDbCommand getSeriesCmd = conn.CreateCommand()) + { + getSeriesCmd.Transaction = tran; + getSeriesCmd.CommandText = @"SELECT Id, ProfileId FROM Series"; + using (IDataReader seriesReader = getSeriesCmd.ExecuteReader()) + { + while (seriesReader.Read()) + { + var seriesId = seriesReader.GetInt32(0); + var seriesProfileId = seriesReader.GetInt32(1); + + using (IDbCommand getProfileCmd = conn.CreateCommand()) + { + getProfileCmd.Transaction = tran; + getProfileCmd.CommandText = "SELECT Language FROM Profiles WHERE Id = ?"; + getProfileCmd.AddParameter(seriesProfileId); + IDataReader profilesReader = getProfileCmd.ExecuteReader(); + while (profilesReader.Read()) + { + var episodeLanguage = Language.English.Id; + try + { + episodeLanguage = profilesReader.GetInt32(0); + } catch (InvalidCastException e) + { + _logger.Debug("Language field not found in Profiles, using English as default." + e.Message); + } + + var validJson = LanguageConverter.ToDB(Language.FindById(episodeLanguage)); + + using (IDbCommand updateEpisodeFilesCmd = conn.CreateCommand()) + { + updateEpisodeFilesCmd.Transaction = tran; + updateEpisodeFilesCmd.CommandText = "UPDATE EpisodeFiles SET Language = ? WHERE SeriesId = ?"; + updateEpisodeFilesCmd.AddParameter(validJson); + updateEpisodeFilesCmd.AddParameter(seriesId); + + updateEpisodeFilesCmd.ExecuteNonQuery(); + } + + using (IDbCommand updateHistoryCmd = conn.CreateCommand()) + { + updateHistoryCmd.Transaction = tran; + updateHistoryCmd.CommandText = "UPDATE History SET Language = ? WHERE SeriesId = ?"; + updateHistoryCmd.AddParameter(validJson); + updateHistoryCmd.AddParameter(seriesId); + + updateHistoryCmd.ExecuteNonQuery(); + } + + using (IDbCommand updateBlacklistCmd = conn.CreateCommand()) + { + updateBlacklistCmd.Transaction = tran; + updateBlacklistCmd.CommandText = "UPDATE Blacklist SET Language = ? WHERE SeriesId = ?"; + updateBlacklistCmd.AddParameter(validJson); + updateBlacklistCmd.AddParameter(seriesId); + + updateBlacklistCmd.ExecuteNonQuery(); + } + } + } + } + } + } + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/111_create_language_profiles.cs b/src/NzbDrone.Core/Datastore/Migration/111_create_language_profiles.cs index d6776a1ed..6f1835a72 100644 --- a/src/NzbDrone.Core/Datastore/Migration/111_create_language_profiles.cs +++ b/src/NzbDrone.Core/Datastore/Migration/111_create_language_profiles.cs @@ -1 +1,180 @@ -// This is a placeholder for migration 111 \ No newline at end of file +using FluentMigrator; +using NzbDrone.Core.Datastore.Converters; +using NzbDrone.Core.Datastore.Migration.Framework; +using NzbDrone.Core.Languages; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using NzbDrone.Common.Extensions; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(111)] + public class create_language_profiles : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Create.TableForModel("LanguageProfiles").WithColumn("Name").AsString().Unique() + .WithColumn("Languages").AsString() + .WithColumn("Cutoff").AsInt32(); + + Alter.Table("Series").AddColumn("LanguageProfileId").AsInt32().WithDefaultValue(1); + + Execute.WithConnection(InsertDefaultLanguages); + + Delete.Column("Language").FromTable("Profiles"); + } + + private void InsertDefaultLanguages(IDbConnection conn, IDbTransaction tran) + { + var profiles = GetLanguageProfiles(conn, tran); + var languageConverter = new EmbeddedDocumentConverter(new LanguageIntConverter()); + + foreach (var profile in profiles.OrderBy(p => p.Id)) + { + using (IDbCommand insertNewLanguageProfileCmd = conn.CreateCommand()) + { + var itemsJson = languageConverter.ToDB(profile.Languages); + insertNewLanguageProfileCmd.Transaction = tran; + insertNewLanguageProfileCmd.CommandText = "INSERT INTO LanguageProfiles (Id, Name, Cutoff, Languages) VALUES (?, ?, ?, ?)"; + insertNewLanguageProfileCmd.AddParameter(profile.Id); + insertNewLanguageProfileCmd.AddParameter(profile.Name); + insertNewLanguageProfileCmd.AddParameter(profile.Cutoff.Id); + insertNewLanguageProfileCmd.AddParameter(itemsJson); + + insertNewLanguageProfileCmd.ExecuteNonQuery(); + } + + using (IDbCommand updateSeriesCmd = conn.CreateCommand()) + { + foreach (var profileId in profile.ProfileIds) + { + updateSeriesCmd.Transaction = tran; + updateSeriesCmd.CommandText = "UPDATE Series SET LanguageProfileId = ? WHERE ProfileId = ?"; + updateSeriesCmd.AddParameter(profile.Id); + updateSeriesCmd.AddParameter(profileId); + updateSeriesCmd.ExecuteNonQuery(); + } + } + } + } + + private List GetDefaultLanguageProfiles() + { + var profiles = new List(); + + var languages = GetOrderedLanguages().Select(v => new LanguageProfileItem111 { Language = v, Allowed = v == Language.English }) + .ToList(); + + profiles.Add(new LanguageProfile111 + { + Id = 1, + Name = "English", + Cutoff = Language.English, + Languages = languages + }); + + return profiles; + + } + + private List GetLanguageProfiles(IDbConnection conn, IDbTransaction tran) + { + var profiles = GetDefaultLanguageProfiles(); + var thereAreProfiles = false; + + using (IDbCommand getProfilesCmd = conn.CreateCommand()) + { + getProfilesCmd.Transaction = tran; + getProfilesCmd.CommandText = @"SELECT Id, Language FROM Profiles"; + + using (IDataReader profileReader = getProfilesCmd.ExecuteReader()) + { + while (profileReader.Read()) + { + thereAreProfiles = true; + var profileId = profileReader.GetInt32(0); + var lang = Language.English.Id; + + try + { + lang = profileReader.GetInt32(1); + } + catch (InvalidCastException e) + { + _logger.Debug("Language field not found in Profiles, using English as default." + e.Message); + } + + if (profiles.None(p => p.Cutoff.Id == lang)) + { + var language = Language.FindById(lang); + var languages = GetOrderedLanguages().Select(l => new LanguageProfileItem111 { Language = l, Allowed = l.Id == lang }) + .ToList(); + + profiles.Add(new LanguageProfile111 + { + Id = profiles.Count + 1, + Name = language.Name, + Cutoff = language, + Languages = languages, + ProfileIds = new List { profileId } + }); + } + else + { + profiles = profiles.Select(p => + { + if (p.Cutoff.Id == lang) + { + p.ProfileIds.Add(profileId); + } + + return p; + }).ToList(); + } + } + } + } + + if (!thereAreProfiles) + { + return new List(); + } + + return profiles; + } + + private List GetOrderedLanguages() + { + var orderedLanguages = Language.All + .Where(l => l != Language.Unknown) + .OrderByDescending(l => l.Name) + .ToList(); + + orderedLanguages.Insert(0, Language.Unknown); + + return orderedLanguages; + } + + private class LanguageProfile111 + { + public int Id { get; set; } + public List ProfileIds { get; set; } + public string Name { get; set; } + public Language Cutoff { get; set; } + public List Languages { get; set; } + + public LanguageProfile111 () + { + ProfileIds = new List(); + } + } + + private class LanguageProfileItem111 + { + public Language Language { get; set; } + public bool Allowed { get; set; } + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index 945ed40b2..49ea94bea 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -19,7 +19,7 @@ using NzbDrone.Core.RemotePathMappings; using NzbDrone.Core.Notifications; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Restrictions; using NzbDrone.Core.RootFolders; @@ -35,6 +35,8 @@ using NzbDrone.Core.Extras.Metadata.Files; using NzbDrone.Core.Extras.Others; using NzbDrone.Core.Extras.Subtitles; using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles.Languages; namespace NzbDrone.Core.Datastore { @@ -82,7 +84,8 @@ namespace NzbDrone.Core.Datastore Mapper.Entity().RegisterModel("Series") .Ignore(s => s.RootFolderPath) .Relationship() - .HasOne(s => s.Profile, s => s.ProfileId); + .HasOne(s => s.Profile, s => s.ProfileId) + .HasOne(s => s.LanguageProfile, s => s.LanguageProfileId); Mapper.Entity().RegisterModel("EpisodeFiles") .Ignore(f => f.Path) @@ -103,6 +106,7 @@ namespace NzbDrone.Core.Datastore .Ignore(d => d.Weight); Mapper.Entity().RegisterModel("Profiles"); + Mapper.Entity().RegisterModel("LanguageProfiles"); Mapper.Entity().RegisterModel("Logs"); Mapper.Entity().RegisterModel("NamingConfig"); Mapper.Entity().MapResultSet(); @@ -144,7 +148,9 @@ namespace NzbDrone.Core.Datastore MapRepository.Instance.RegisterTypeConverter(typeof(QualityModel), new EmbeddedDocumentConverter(new QualityIntConverter())); MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary), new EmbeddedDocumentConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(List), new EmbeddedDocumentConverter()); + MapRepository.Instance.RegisterTypeConverter(typeof(Language), new LanguageIntConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(List), new EmbeddedDocumentConverter()); + MapRepository.Instance.RegisterTypeConverter(typeof(List), new EmbeddedDocumentConverter(new LanguageIntConverter())); MapRepository.Instance.RegisterTypeConverter(typeof(ParsedEpisodeInfo), new EmbeddedDocumentConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(ReleaseInfo), new EmbeddedDocumentConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(HashSet), new EmbeddedDocumentConverter()); diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs index a85410046..91d983955 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs @@ -24,6 +24,7 @@ namespace NzbDrone.Core.DecisionEngine var comparers = new List { CompareQuality, + CompareLanguage, CompareProtocol, CompareEpisodeCount, CompareEpisodeNumber, @@ -62,6 +63,11 @@ namespace NzbDrone.Core.DecisionEngine CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.ParsedEpisodeInfo.Quality.Revision.Version)); } + private int CompareLanguage(DownloadDecision x, DownloadDecision y) + { + return CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.Series.LanguageProfile.Value.Languages.FindIndex(l => l.Language == remoteEpisode.ParsedEpisodeInfo.Language)); + } + private int CompareProtocol(DownloadDecision x, DownloadDecision y) { var result = CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs index 33fc32f5d..344c7544c 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs @@ -1,6 +1,7 @@ using System.Linq; using System.Collections.Generic; using NzbDrone.Core.Profiles.Delay; +using NzbDrone.Core.Languages; namespace NzbDrone.Core.DecisionEngine { diff --git a/src/NzbDrone.Core/DecisionEngine/LanguageUpgradableSpecification.cs b/src/NzbDrone.Core/DecisionEngine/LanguageUpgradableSpecification.cs new file mode 100644 index 000000000..4f150279b --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/LanguageUpgradableSpecification.cs @@ -0,0 +1,75 @@ +using NLog; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles; +using NzbDrone.Core.Qualities; + +namespace NzbDrone.Core.DecisionEngine +{ + public interface ILanguageUpgradableSpecification + { + bool IsUpgradable(Profile profile, LanguageModel currentLanguage, LanguageModel newLanguage = null); + bool CutoffNotMet(Profile profile, LanguageModel currentLanguage, LanguageModel newLanguage = null); + bool IsRevisionUpgrade(LanguageModel currentLanguage, LanguageModel newLanguage); + } + + public class LanguageUpgradableSpecification : ILanguageUpgradableSpecification + { + private readonly Logger _logger; + + public LanguageUpgradableSpecification(Logger logger) + { + _logger = logger; + } + + public bool IsUpgradable(Profile profile, LanguageModel currentLanguage, LanguageModel newLanguage = null) + { + if (newLanguage != null) + { + int compare = new LanguageModelComparer(profile).Compare(newLanguage, currentLanguage); + if (compare <= 0) + { + _logger.Debug("existing item has better or equal language. skipping"); + return false; + } + + if (IsRevisionUpgrade(currentLanguage, newLanguage)) + { + return true; + } + } + + return true; + } + + public bool CutoffNotMet(Profile profile, LanguageModel currentLanguage, LanguageModel newLanguage = null) + { + int compare = new LanguageModelComparer(profile).Compare(currentLanguage.Language, profile.Languages.Find(v => v.Allowed == true).Language); + + if (compare >= 0) + { + if (newLanguage != null && IsRevisionUpgrade(currentLanguage, newLanguage)) + { + return true; + } + + _logger.Debug("Existing item meets cut-off. skipping."); + return false; + } + + return true; + } + + public bool IsRevisionUpgrade(LanguageModel currentLanguage, LanguageModel newLanguage) + { + int compare = newLanguage.Revision.CompareTo(currentLanguage.Revision); + + if (currentLanguage.Language == newLanguage.Language && compare > 0) + { + _logger.Debug("New language is a better revision for existing quality"); + return true; + } + + return false; + } + } +} diff --git a/src/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs b/src/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs deleted file mode 100644 index 22c4824af..000000000 --- a/src/NzbDrone.Core/DecisionEngine/QualityUpgradableSpecification.cs +++ /dev/null @@ -1,72 +0,0 @@ -using NLog; -using NzbDrone.Core.Profiles; -using NzbDrone.Core.Qualities; - -namespace NzbDrone.Core.DecisionEngine -{ - public interface IQualityUpgradableSpecification - { - bool IsUpgradable(Profile profile, QualityModel currentQuality, QualityModel newQuality = null); - bool CutoffNotMet(Profile profile, QualityModel currentQuality, QualityModel newQuality = null); - bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuality); - } - - public class QualityUpgradableSpecification : IQualityUpgradableSpecification - { - private readonly Logger _logger; - - public QualityUpgradableSpecification(Logger logger) - { - _logger = logger; - } - - public bool IsUpgradable(Profile profile, QualityModel currentQuality, QualityModel newQuality = null) - { - if (newQuality != null) - { - int compare = new QualityModelComparer(profile).Compare(newQuality, currentQuality); - if (compare <= 0) - { - return false; - } - - if (IsRevisionUpgrade(currentQuality, newQuality)) - { - return true; - } - } - - return true; - } - - public bool CutoffNotMet(Profile profile, QualityModel currentQuality, QualityModel newQuality = null) - { - var compare = new QualityModelComparer(profile).Compare(currentQuality.Quality, profile.Cutoff); - - if (compare < 0) - { - return true; - } - - if (newQuality != null && IsRevisionUpgrade(currentQuality, newQuality)) - { - return true; - } - - return false; - - } - - public bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuality) - { - var compare = newQuality.Revision.CompareTo(currentQuality.Revision); - - if (currentQuality.Quality == newQuality.Quality && compare > 0) - { - return true; - } - - return false; - } - } -} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/AnimeVersionUpgradeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/AnimeVersionUpgradeSpecification.cs index 3adb65df0..7d687c1aa 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/AnimeVersionUpgradeSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/AnimeVersionUpgradeSpecification.cs @@ -9,12 +9,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { public class AnimeVersionUpgradeSpecification : IDecisionEngineSpecification { - private readonly QualityUpgradableSpecification _qualityUpgradableSpecification; + private readonly UpgradableSpecification _upgradableSpecification; private readonly Logger _logger; - public AnimeVersionUpgradeSpecification(QualityUpgradableSpecification qualityUpgradableSpecification, Logger logger) + public AnimeVersionUpgradeSpecification(UpgradableSpecification UpgradableSpecification, Logger logger) { - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = UpgradableSpecification; _logger = logger; } @@ -32,7 +32,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) { - if (_qualityUpgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedEpisodeInfo.Quality)) + if (_upgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedEpisodeInfo.Quality)) { if (file.ReleaseGroup.IsNullOrWhiteSpace()) { diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs index 39102b0fc..a5ebdee26 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs @@ -7,12 +7,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { public class CutoffSpecification : IDecisionEngineSpecification { - private readonly QualityUpgradableSpecification _qualityUpgradableSpecification; + private readonly UpgradableSpecification _upgradableSpecification; private readonly Logger _logger; - public CutoffSpecification(QualityUpgradableSpecification qualityUpgradableSpecification, Logger logger) + public CutoffSpecification(UpgradableSpecification UpgradableSpecification, Logger logger) { - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = UpgradableSpecification; _logger = logger; } @@ -28,13 +28,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger.Debug("File is no longer available, skipping this file."); continue; } + _logger.Debug("Comparing file quality and language with report. Existing file is {0} - {1}", file.Quality, file.Language); - _logger.Debug("Comparing file quality with report. Existing file is {0}", file.Quality); - - if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Series.Profile, file.Quality, subject.ParsedEpisodeInfo.Quality)) + if (!_upgradableSpecification.CutoffNotMet(subject.Series.Profile, + subject.Series.LanguageProfile, + file.Quality, + file.Language, + subject.ParsedEpisodeInfo.Quality)) { _logger.Debug("Cutoff already met, rejecting."); - return Decision.Reject("Existing file meets cutoff: {0}", subject.Series.Profile.Value.Cutoff); + return Decision.Reject("Existing file meets cutoff: {0} - {1}", subject.Series.Profile.Value.Cutoff, subject.Series.LanguageProfile.Value.Cutoff); } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs index edef58757..cf32e6e33 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs @@ -1,4 +1,5 @@ using NLog; +using System.Linq; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -18,14 +19,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { - var wantedLanguage = subject.Series.Profile.Value.Language; + var wantedLanguage = subject.Series.LanguageProfile.Value.Languages; + var _language = subject.ParsedEpisodeInfo.Language; _logger.Debug("Checking if report meets language requirements. {0}", subject.ParsedEpisodeInfo.Language); - - if (subject.ParsedEpisodeInfo.Language != wantedLanguage) + + if (!wantedLanguage.Exists(v => v.Allowed && v.Language == _language)) { - _logger.Debug("Report Language: {0} rejected because it is not wanted, wanted {1}", subject.ParsedEpisodeInfo.Language, wantedLanguage); - return Decision.Reject("{0} is wanted, but found {1}", wantedLanguage, subject.ParsedEpisodeInfo.Language); + _logger.Debug("Report Language: {0} rejected because it is not wanted in profile {1}", _language, subject.Series.LanguageProfile.Value.Name); + return Decision.Reject("{0} is not allowed in profile {1}", _language, subject.Series.LanguageProfile.Value.Name); } return Decision.Accept(); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs index 838d8a80d..e024683e4 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs @@ -9,15 +9,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications public class QueueSpecification : IDecisionEngineSpecification { private readonly IQueueService _queueService; - private readonly QualityUpgradableSpecification _qualityUpgradableSpecification; + private readonly UpgradableSpecification _upgradableSpecification; private readonly Logger _logger; public QueueSpecification(IQueueService queueService, - QualityUpgradableSpecification qualityUpgradableSpecification, + UpgradableSpecification UpgradableSpecification, Logger logger) { _queueService = queueService; - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = UpgradableSpecification; _logger = logger; } @@ -34,18 +34,27 @@ namespace NzbDrone.Core.DecisionEngine.Specifications foreach (var remoteEpisode in matchingEpisode) { - _logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0}", remoteEpisode.ParsedEpisodeInfo.Quality); + _logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); - if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Series.Profile, remoteEpisode.ParsedEpisodeInfo.Quality, subject.ParsedEpisodeInfo.Quality)) + if (!_upgradableSpecification.CutoffNotMet(subject.Series.Profile, + subject.Series.LanguageProfile, + remoteEpisode.ParsedEpisodeInfo.Quality, + remoteEpisode.ParsedEpisodeInfo.Language, + subject.ParsedEpisodeInfo.Quality)) { - return Decision.Reject("Quality for release in queue already meets cutoff: {0}", remoteEpisode.ParsedEpisodeInfo.Quality); + return Decision.Reject("Quality for release in queue already meets cutoff: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); } - _logger.Debug("Checking if release is higher quality than queued release. Queued quality is: {0}", remoteEpisode.ParsedEpisodeInfo.Quality); + _logger.Debug("Checking if release is higher quality than queued release. Queued quality is: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); - if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.Profile, remoteEpisode.ParsedEpisodeInfo.Quality, subject.ParsedEpisodeInfo.Quality)) + if (!_upgradableSpecification.IsUpgradable(subject.Series.Profile, + subject.Series.LanguageProfile, + remoteEpisode.ParsedEpisodeInfo.Quality, + remoteEpisode.ParsedEpisodeInfo.Language, + subject.ParsedEpisodeInfo.Quality, + subject.ParsedEpisodeInfo.Language)) { - return Decision.Reject("Quality for release in queue is of equal or higher preference: {0}", remoteEpisode.ParsedEpisodeInfo.Quality); + return Decision.Reject("Quality for release in queue is of equal or higher preference: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs index 254eceb91..5da33d0da 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs @@ -5,23 +5,24 @@ using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Profiles.Delay; using NzbDrone.Core.Qualities; +using NzbDrone.Core.Languages; namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync { public class DelaySpecification : IDecisionEngineSpecification { private readonly IPendingReleaseService _pendingReleaseService; - private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification; + private readonly IUpgradableSpecification _upgradableSpecification; private readonly IDelayProfileService _delayProfileService; private readonly Logger _logger; public DelaySpecification(IPendingReleaseService pendingReleaseService, - IQualityUpgradableSpecification qualityUpgradableSpecification, + IUpgradableSpecification UpgradableSpecification, IDelayProfileService delayProfileService, Logger logger) { _pendingReleaseService = pendingReleaseService; - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = UpgradableSpecification; _delayProfileService = delayProfileService; _logger = logger; } @@ -38,6 +39,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync } var profile = subject.Series.Profile.Value; + var languageProfile = subject.Series.LanguageProfile.Value; var delayProfile = _delayProfileService.BestForTags(subject.Series.Tags); var delay = delayProfile.GetProtocolDelay(subject.Release.DownloadProtocol); var isPreferredProtocol = subject.Release.DownloadProtocol == delayProfile.PreferredProtocol; @@ -49,22 +51,23 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync } var comparer = new QualityModelComparer(profile); + var comparerLanguage = new LanguageComparer(languageProfile); if (isPreferredProtocol) { foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) { - var upgradable = _qualityUpgradableSpecification.IsUpgradable(profile, file.Quality, subject.ParsedEpisodeInfo.Quality); + var upgradable = _upgradableSpecification.IsUpgradable(profile, + languageProfile, + file.Quality, + file.Language, + subject.ParsedEpisodeInfo.Quality, + subject.ParsedEpisodeInfo.Language); if (upgradable) { - var revisionUpgrade = _qualityUpgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedEpisodeInfo.Quality); - - if (revisionUpgrade) - { - _logger.Debug("New quality is a better revision for existing quality, skipping delay"); - return Decision.Accept(); - } + _logger.Debug("New quality is a better revision for existing quality, skipping delay"); + return Decision.Accept(); } } } @@ -72,10 +75,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync // If quality meets or exceeds the best allowed quality in the profile accept it immediately var bestQualityInProfile = new QualityModel(profile.LastAllowedQuality()); var isBestInProfile = comparer.Compare(subject.ParsedEpisodeInfo.Quality, bestQualityInProfile) >= 0; + var isBestInProfileLanguage = comparerLanguage.Compare(subject.ParsedEpisodeInfo.Language, languageProfile.LastAllowedLanguage()) >= 0; - if (isBestInProfile && isPreferredProtocol) + if (isBestInProfile && isBestInProfileLanguage && isPreferredProtocol) { - _logger.Debug("Quality is highest in profile for preferred protocol, will not delay"); + _logger.Debug("Quality and language is highest in profile for preferred protocol, will not delay"); return Decision.Accept(); } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs index 8ce71848c..c05135bf4 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs @@ -11,17 +11,17 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync public class HistorySpecification : IDecisionEngineSpecification { private readonly IHistoryService _historyService; - private readonly QualityUpgradableSpecification _qualityUpgradableSpecification; + private readonly UpgradableSpecification _upgradableSpecification; private readonly IConfigService _configService; private readonly Logger _logger; public HistorySpecification(IHistoryService historyService, - QualityUpgradableSpecification qualityUpgradableSpecification, + UpgradableSpecification upgradableSpecification, IConfigService configService, Logger logger) { _historyService = historyService; - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = upgradableSpecification; _configService = configService; _logger = logger; } @@ -48,8 +48,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed) { var recent = mostRecent.Date.After(DateTime.UtcNow.AddHours(-12)); - var cutoffUnmet = _qualityUpgradableSpecification.CutoffNotMet(subject.Series.Profile, mostRecent.Quality, subject.ParsedEpisodeInfo.Quality); - var upgradeable = _qualityUpgradableSpecification.IsUpgradable(subject.Series.Profile, mostRecent.Quality, subject.ParsedEpisodeInfo.Quality); + var cutoffUnmet = _upgradableSpecification.CutoffNotMet(subject.Series.Profile, subject.Series.LanguageProfile, mostRecent.Quality, mostRecent.Language, subject.ParsedEpisodeInfo.Quality); + var upgradeable = _upgradableSpecification.IsUpgradable(subject.Series.Profile, subject.Series.LanguageProfile, mostRecent.Quality, mostRecent.Language, subject.ParsedEpisodeInfo.Quality, subject.ParsedEpisodeInfo.Language); if (!recent && cdhEnabled) { diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs index f3f5d4e8d..163fbea84 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs @@ -9,13 +9,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync { public class ProperSpecification : IDecisionEngineSpecification { - private readonly QualityUpgradableSpecification _qualityUpgradableSpecification; + private readonly UpgradableSpecification _upgradableSpecification; private readonly IConfigService _configService; private readonly Logger _logger; - public ProperSpecification(QualityUpgradableSpecification qualityUpgradableSpecification, IConfigService configService, Logger logger) + public ProperSpecification(UpgradableSpecification upgradableSpecification, IConfigService configService, Logger logger) { - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = upgradableSpecification; _configService = configService; _logger = logger; } @@ -32,7 +32,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) { - if (_qualityUpgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedEpisodeInfo.Quality)) + if (_upgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedEpisodeInfo.Quality)) { if (file.DateAdded < DateTime.Today.AddDays(-7)) { diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs index 8554c0c40..789074268 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs @@ -7,12 +7,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { public class UpgradeDiskSpecification : IDecisionEngineSpecification { - private readonly QualityUpgradableSpecification _qualityUpgradableSpecification; + private readonly UpgradableSpecification _upgradableSpecification; private readonly Logger _logger; - public UpgradeDiskSpecification(QualityUpgradableSpecification qualityUpgradableSpecification, Logger logger) + public UpgradeDiskSpecification(UpgradableSpecification upgradableSpecification, Logger logger) { - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = upgradableSpecification; _logger = logger; } @@ -23,17 +23,23 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) { - if(file == null) + if (file == null) { _logger.Debug("File is no longer available, skipping this file."); continue; } - _logger.Debug("Comparing file quality with report. Existing file is {0}", file.Quality); + _logger.Debug("Comparing file quality and language with report. Existing file is {0} - {1}", file.Quality, file.Language); - if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.Profile, file.Quality, subject.ParsedEpisodeInfo.Quality)) + + if (!_upgradableSpecification.IsUpgradable(subject.Series.Profile, + subject.Series.LanguageProfile, + file.Quality, + file.Language, + subject.ParsedEpisodeInfo.Quality, + subject.ParsedEpisodeInfo.Language)) { - return Decision.Reject("Quality for existing file on disk is of equal or higher preference: {0}", file.Quality); + return Decision.Reject("Quality for existing file on disk is of equal or higher preference: {0} - {1}", file.Quality, file.Language); } } diff --git a/src/NzbDrone.Core/DecisionEngine/UpgradableSpecification.cs b/src/NzbDrone.Core/DecisionEngine/UpgradableSpecification.cs new file mode 100644 index 000000000..a8eec69e3 --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/UpgradableSpecification.cs @@ -0,0 +1,128 @@ +using NLog; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles.Languages; +using NzbDrone.Core.Profiles.Qualities; +using NzbDrone.Core.Qualities; + +namespace NzbDrone.Core.DecisionEngine +{ + public interface IUpgradableSpecification + { + bool IsUpgradable(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality, Language newLanguage); + bool QualityCutoffNotMet(Profile profile, QualityModel currentQuality, QualityModel newQuality = null); + bool LanguageCutoffNotMet(LanguageProfile languageProfile, Language currentLanguage); + bool CutoffNotMet(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality = null); + bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuality); + } + + public class UpgradableSpecification : IUpgradableSpecification + { + private readonly Logger _logger; + + public UpgradableSpecification(Logger logger) + { + _logger = logger; + } + + private bool IsLanguageUpgradable(LanguageProfile profile, Language currentLanguage, Language newLanguage = null) + { + if (newLanguage != null) + { + var compare = new LanguageComparer(profile).Compare(newLanguage, currentLanguage); + if (compare <= 0) + { + return false; + } + } + return true; + } + + private bool IsQualityUpgradable(Profile profile, QualityModel currentQuality, QualityModel newQuality = null) + { + if (newQuality != null) + { + var compare = new QualityModelComparer(profile).Compare(newQuality, currentQuality); + if (compare <= 0) + { + _logger.Debug("existing item has better quality. skipping"); + return false; + } + } + return true; + } + + + public bool IsUpgradable(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality, Language newLanguage) + { + // If qualities are the same then check language + if (newQuality != null && currentQuality == newQuality) + { + return IsLanguageUpgradable(languageProfile, currentLanguage, newLanguage); + } + + // If quality is worse then always return false + if (!IsQualityUpgradable(profile, currentQuality, newQuality)) + { + _logger.Debug("existing item has better quality. skipping"); + return false; + } + + return true; + } + + public bool QualityCutoffNotMet(Profile profile, QualityModel currentQuality, QualityModel newQuality = null) + { + var qualityCompare = new QualityModelComparer(profile).Compare(currentQuality.Quality, profile.Cutoff); + + if (qualityCompare < 0) + { + return true; + } + + if (qualityCompare == 0 && newQuality != null && IsRevisionUpgrade(currentQuality, newQuality)) + { + return true; + } + + return false; + } + + public bool LanguageCutoffNotMet(LanguageProfile languageProfile, Language currentLanguage) + { + var languageCompare = new LanguageComparer(languageProfile).Compare(currentLanguage, languageProfile.Cutoff); + + return languageCompare < 0; + } + + public bool CutoffNotMet(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality = null) + { + // If we can upgrade the language (it is not the cutoff) then doesn't matter the quality we can always get same quality with prefered language + if (LanguageCutoffNotMet(languageProfile, currentLanguage)) + { + return true; + } + + if (QualityCutoffNotMet(profile, currentQuality, newQuality)) + { + return true; + } + + _logger.Debug("Existing item meets cut-off. skipping."); + + return false; + } + + public bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuality) + { + var compare = newQuality.Revision.CompareTo(currentQuality.Revision); + + if (currentQuality.Quality == newQuality.Quality && compare > 0) + { + _logger.Debug("New quality is a better revision for existing quality"); + return true; + } + + return false; + } + } +} diff --git a/src/NzbDrone.Core/Download/DownloadFailedEvent.cs b/src/NzbDrone.Core/Download/DownloadFailedEvent.cs index 1c0ca855a..556f13529 100644 --- a/src/NzbDrone.Core/Download/DownloadFailedEvent.cs +++ b/src/NzbDrone.Core/Download/DownloadFailedEvent.cs @@ -2,6 +2,7 @@ using NzbDrone.Common.Messaging; using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.Qualities; +using NzbDrone.Core.Languages; namespace NzbDrone.Core.Download { @@ -21,5 +22,6 @@ namespace NzbDrone.Core.Download public string Message { get; set; } public Dictionary Data { get; set; } public TrackedDownload TrackedDownload { get; set; } + public Language Language { get; set; } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Download/FailedDownloadService.cs b/src/NzbDrone.Core/Download/FailedDownloadService.cs index d56349f7f..2158a5d5c 100644 --- a/src/NzbDrone.Core/Download/FailedDownloadService.cs +++ b/src/NzbDrone.Core/Download/FailedDownloadService.cs @@ -95,7 +95,8 @@ namespace NzbDrone.Core.Download DownloadId = historyItem.DownloadId, Message = message, Data = historyItem.Data, - TrackedDownload = trackedDownload + TrackedDownload = trackedDownload, + Language = historyItem.Language }; _eventAggregator.PublishEvent(downloadFailedEvent); diff --git a/src/NzbDrone.Core/Extras/Subtitles/SubtitleFile.cs b/src/NzbDrone.Core/Extras/Subtitles/SubtitleFile.cs index 0ccd3ede6..2811cc27d 100644 --- a/src/NzbDrone.Core/Extras/Subtitles/SubtitleFile.cs +++ b/src/NzbDrone.Core/Extras/Subtitles/SubtitleFile.cs @@ -1,5 +1,5 @@ using NzbDrone.Core.Extras.Files; -using NzbDrone.Core.Parser; +using NzbDrone.Core.Languages; namespace NzbDrone.Core.Extras.Subtitles { diff --git a/src/NzbDrone.Core/Extras/Subtitles/SubtitleService.cs b/src/NzbDrone.Core/Extras/Subtitles/SubtitleService.cs index 52910a285..1ffa8db24 100644 --- a/src/NzbDrone.Core/Extras/Subtitles/SubtitleService.cs +++ b/src/NzbDrone.Core/Extras/Subtitles/SubtitleService.cs @@ -7,6 +7,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; using NzbDrone.Core.Extras.Files; +using NzbDrone.Core.Languages; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Parser; using NzbDrone.Core.Tv; diff --git a/src/NzbDrone.Core/History/History.cs b/src/NzbDrone.Core/History/History.cs index e6c2fe223..1604a2822 100644 --- a/src/NzbDrone.Core/History/History.cs +++ b/src/NzbDrone.Core/History/History.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using NzbDrone.Core.Datastore; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; +using NzbDrone.Core.Languages; namespace NzbDrone.Core.History { @@ -24,6 +25,7 @@ namespace NzbDrone.Core.History public Series Series { get; set; } public HistoryEventType EventType { get; set; } public Dictionary Data { get; set; } + public Language Language { get; set; } public string DownloadId { get; set; } diff --git a/src/NzbDrone.Core/History/HistoryRepository.cs b/src/NzbDrone.Core/History/HistoryRepository.cs index ce461911e..78b803a9b 100644 --- a/src/NzbDrone.Core/History/HistoryRepository.cs +++ b/src/NzbDrone.Core/History/HistoryRepository.cs @@ -11,7 +11,6 @@ namespace NzbDrone.Core.History { public interface IHistoryRepository : IBasicRepository { - List GetBestQualityInHistory(int episodeId); History MostRecentForEpisode(int episodeId); List FindByEpisodeId(int episodeId); History MostRecentForDownloadId(string downloadId); @@ -31,14 +30,6 @@ namespace NzbDrone.Core.History { } - - public List GetBestQualityInHistory(int episodeId) - { - var history = Query.Where(c => c.EpisodeId == episodeId); - - return history.Select(h => h.Quality).ToList(); - } - public History MostRecentForEpisode(int episodeId) { return Query.Where(h => h.EpisodeId == episodeId) diff --git a/src/NzbDrone.Core/History/HistoryService.cs b/src/NzbDrone.Core/History/HistoryService.cs index bd49021b2..d2c5719a8 100644 --- a/src/NzbDrone.Core/History/HistoryService.cs +++ b/src/NzbDrone.Core/History/HistoryService.cs @@ -11,15 +11,16 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles; using NzbDrone.Core.Tv; using NzbDrone.Core.Tv.Events; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles.Qualities; +using NzbDrone.Core.Profiles.Languages; namespace NzbDrone.Core.History { public interface IHistoryService { - QualityModel GetBestQualityInHistory(Profile profile, int episodeId); PagingSpec Paged(PagingSpec pagingSpec); History MostRecentForEpisode(int episodeId); List FindByEpisodeId(int episodeId); @@ -94,14 +95,6 @@ namespace NzbDrone.Core.History return _historyRepository.FindByDownloadId(downloadId); } - public QualityModel GetBestQualityInHistory(Profile profile, int episodeId) - { - var comparer = new QualityModelComparer(profile); - return _historyRepository.GetBestQualityInHistory(episodeId) - .OrderByDescending(q => q, comparer) - .FirstOrDefault(); - } - private string FindDownloadId(EpisodeImportedEvent trackedDownload) { _logger.Debug("Trying to find downloadId for {0} from history", trackedDownload.ImportedEpisode.Path); @@ -159,7 +152,8 @@ namespace NzbDrone.Core.History SourceTitle = message.Episode.Release.Title, SeriesId = episode.SeriesId, EpisodeId = episode.Id, - DownloadId = message.DownloadId + DownloadId = message.DownloadId, + Language = message.Episode.ParsedEpisodeInfo.Language }; history.Data.Add("Indexer", message.Episode.Release.Indexer); @@ -217,7 +211,8 @@ namespace NzbDrone.Core.History SourceTitle = message.ImportedEpisode.SceneName ?? Path.GetFileNameWithoutExtension(message.EpisodeInfo.Path), SeriesId = message.ImportedEpisode.SeriesId, EpisodeId = episode.Id, - DownloadId = downloadId + DownloadId = downloadId, + Language = message.EpisodeInfo.Language }; //Won't have a value since we publish this event before saving to DB. @@ -242,7 +237,8 @@ namespace NzbDrone.Core.History SourceTitle = message.SourceTitle, SeriesId = message.SeriesId, EpisodeId = episodeId, - DownloadId = message.DownloadId + DownloadId = message.DownloadId, + Language = message.Language }; history.Data.Add("DownloadClient", message.DownloadClient); diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/EnsureValidLanguageProfileId.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/EnsureValidLanguageProfileId.cs new file mode 100644 index 000000000..25104a769 --- /dev/null +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/EnsureValidLanguageProfileId.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Profiles.Languages; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Housekeeping.Housekeepers +{ + // For some unknown reason series added through the v2 API can be added without a lanuage profile ID, which breaks things later. + // This ensures there is a language profile ID and it's valid as a safety net. + + public class EnsureValidLanguageProfileId : IHousekeepingTask + { + private readonly ISeriesRepository _seriesRepository; + private readonly ILanguageProfileService _languageProfileService; + + public EnsureValidLanguageProfileId(ISeriesRepository seriesRepository, ILanguageProfileService languageProfileService) + { + _seriesRepository = seriesRepository; + _languageProfileService = languageProfileService; + } + + public void Clean() + { + var languageProfiles = _languageProfileService.All(); + var firstLangaugeProfile = languageProfiles.First(); + var series = _seriesRepository.All().ToList(); + var seriesToUpdate = new List(); + + series.ForEach(s => + { + if (s.LanguageProfileId == 0 || languageProfiles.None(l => l.Id == s.LanguageProfileId)) + { + s.LanguageProfileId = firstLangaugeProfile.Id; + seriesToUpdate.Add(s); + } + }); + + _seriesRepository.UpdateMany(seriesToUpdate); + } + } +} diff --git a/src/NzbDrone.Core/Languages/Language.cs b/src/NzbDrone.Core/Languages/Language.cs new file mode 100644 index 000000000..bb252ce07 --- /dev/null +++ b/src/NzbDrone.Core/Languages/Language.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.Datastore; + +namespace NzbDrone.Core.Languages +{ + public class Language : IEmbeddedDocument, IEquatable + { + public int Id { get; set; } + public string Name { get; set; } + + public Language() + { + } + + private Language(int id, string name) + { + Id = id; + Name = name; + } + + public override string ToString() + { + return Name; + } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } + + public bool Equals(Language other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id.Equals(other.Id); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + + return Equals(obj as Language); + } + + public static bool operator ==(Language left, Language right) + { + return Equals(left, right); + } + + public static bool operator !=(Language left, Language right) + { + return !Equals(left, right); + } + + public static Language Unknown { get { return new Language(0, "Unknown"); } } + public static Language English { get { return new Language(1, "English"); } } + public static Language French { get { return new Language(2, "French"); } } + public static Language Spanish { get { return new Language(3, "Spanish"); } } + public static Language German { get { return new Language(4, "German"); } } + public static Language Italian { get { return new Language(5, "Italian"); } } + public static Language Danish { get { return new Language(6, "Danish"); } } + public static Language Dutch { get { return new Language(7, "Dutch"); } } + public static Language Japanese { get { return new Language(8, "Japanese"); } } + public static Language Cantonese { get { return new Language(9, "Cantonese"); } } + public static Language Mandarin { get { return new Language(10, "Mandarin"); } } + public static Language Russian { get { return new Language(11, "Russian"); } } + public static Language Polish { get { return new Language(12, "Polish"); } } + public static Language Vietnamese { get { return new Language(13, "Vietnamese"); } } + public static Language Swedish { get { return new Language(14, "Swedish"); } } + public static Language Norwegian { get { return new Language(15, "Norwegian"); } } + public static Language Finnish { get { return new Language(16, "Finnish"); } } + public static Language Turkish { get { return new Language(17, "Turkish"); } } + public static Language Portuguese { get { return new Language(18, "Portuguese"); } } + public static Language Flemish { get { return new Language(19, "Flemish"); } } + public static Language Greek { get { return new Language(20, "Greek"); } } + public static Language Korean { get { return new Language(21, "Korean"); } } + public static Language Hungarian { get { return new Language(22, "Hungarian"); } } + public static Language Hebrew { get { return new Language(23, "Hebrew"); } } + public static Language Lithuanian { get { return new Language(24, "Lithuanian"); } } + public static Language Czech { get { return new Language(25, "Czech"); } } + + + public static List All + { + get + { + return new List + { + Unknown, + English, + French, + Spanish, + German, + Italian, + Danish, + Dutch, + Japanese, + Cantonese, + Mandarin, + Russian, + Polish, + Vietnamese, + Swedish, + Norwegian, + Finnish, + Turkish, + Portuguese, + Flemish, + Greek, + Korean, + Hungarian, + Hebrew, + Lithuanian, + Czech + }; + } + } + + public static Language FindById(int id) + { + if (id == 0) return Unknown; + + Language language = All.FirstOrDefault(v => v.Id == id); + + if (language == null) + { + throw new ArgumentException("ID does not match a known language", nameof(id)); + } + + return language; + } + + public static explicit operator Language(int id) + { + return FindById(id); + } + + public static explicit operator int(Language language) + { + return language.Id; + } + + public static explicit operator Language(string lang) + { + var language = All.FirstOrDefault(v => v.Name.Equals(lang, StringComparison.InvariantCultureIgnoreCase)); + + if (language == null) + { + throw new ArgumentException("Language does not match a known language", nameof(lang)); + } + + return language; + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Languages/LanguageComparer.cs b/src/NzbDrone.Core/Languages/LanguageComparer.cs new file mode 100644 index 000000000..e286aa681 --- /dev/null +++ b/src/NzbDrone.Core/Languages/LanguageComparer.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using NzbDrone.Common.EnsureThat; +using NzbDrone.Core.Profiles.Languages; + +namespace NzbDrone.Core.Languages +{ + public class LanguageComparer : IComparer + { + private readonly LanguageProfile _profile; + + public LanguageComparer(LanguageProfile profile) + { + Ensure.That(profile, () => profile).IsNotNull(); + Ensure.That(profile.Languages, () => profile.Languages).HasItems(); + + _profile = profile; + } + + public int Compare(Language left, Language right) + { + int leftIndex = _profile.Languages.FindIndex(v => v.Language == left); + int rightIndex = _profile.Languages.FindIndex(v => v.Language == right); + + return leftIndex.CompareTo(rightIndex); + } + } +} diff --git a/src/NzbDrone.Core/Languages/LanguagesBelowCutoff.cs b/src/NzbDrone.Core/Languages/LanguagesBelowCutoff.cs new file mode 100644 index 000000000..3b04f024a --- /dev/null +++ b/src/NzbDrone.Core/Languages/LanguagesBelowCutoff.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace NzbDrone.Core.Languages +{ + public class LanguagesBelowCutoff + { + public int ProfileId { get; set; } + public IEnumerable LanguageIds { get; set; } + + public LanguagesBelowCutoff(int profileId, IEnumerable languageIds) + { + ProfileId = profileId; + LanguageIds = languageIds; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs b/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs index 58dd7605b..4f3fd25f9 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs @@ -6,6 +6,7 @@ using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Common.Extensions; +using NzbDrone.Core.Languages; namespace NzbDrone.Core.MediaFiles { @@ -24,6 +25,7 @@ namespace NzbDrone.Core.MediaFiles public MediaInfoModel MediaInfo { get; set; } public LazyLoaded> Episodes { get; set; } public LazyLoaded Series { get; set; } + public Language Language { get; set; } public override string ToString() { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguage.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguage.cs new file mode 100644 index 000000000..11fc758ca --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregateLanguage.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation.Aggregators +{ + public class AggregateLanguage : IAggregateLocalEpisode + { + private readonly Logger _logger; + + public AggregateLanguage(Logger logger) + { + _logger = logger; + } + + public LocalEpisode Aggregate(LocalEpisode localEpisode, bool otherFiles) + { + // Get languages in preferred order, download client item, folder and finally file. + // Non-English languages will be preferred later, in the event there is a conflict + // between parsed languages the more preferred item will be used. + + var languages = new List + { + GetLanguage(localEpisode.DownloadClientEpisodeInfo), + GetLanguage(localEpisode.FolderEpisodeInfo), + GetLanguage(localEpisode.FileEpisodeInfo) + }; + + var language = languages.FirstOrDefault(l => l != Language.English) ?? Language.English; + + _logger.Debug("Using language: {0}", language); + + localEpisode.Language = language; + + return localEpisode; + } + + private Language GetLanguage(ParsedEpisodeInfo parsedEpisodeInfo) + { + if (parsedEpisodeInfo == null) + { + // English is the default language when otherwise unknown + + return Language.English; + } + + return parsedEpisodeInfo.Language; + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index bd4da4d18..7b93486d0 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using NLog; using NzbDrone.Common.Disk; +using NzbDrone.Common.Exceptions; using NzbDrone.Common.Extensions; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; @@ -12,7 +13,7 @@ using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; using NzbDrone.Core.Download; using NzbDrone.Core.Extras; -using NzbDrone.Common.Exceptions; +using NzbDrone.Core.Languages; namespace NzbDrone.Core.MediaFiles.EpisodeImport { @@ -50,6 +51,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport var qualifiedImports = decisions.Where(c => c.Approved) .GroupBy(c => c.LocalEpisode.Series.Id, (i, s) => s .OrderByDescending(c => c.LocalEpisode.Quality, new QualityModelComparer(s.First().LocalEpisode.Series.Profile)) + .ThenByDescending(c => c.LocalEpisode.Language, new LanguageComparer(s.First().LocalEpisode.Series.LanguageProfile)) .ThenByDescending(c => c.LocalEpisode.Size)) .SelectMany(c => c) .ToList(); @@ -84,6 +86,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport episodeFile.SeasonNumber = localEpisode.SeasonNumber; episodeFile.Episodes = localEpisode.Episodes; episodeFile.ReleaseGroup = localEpisode.ReleaseGroup; + episodeFile.Language = localEpisode.Language; bool copyOnly; switch (importMode) diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs index 1dddf8fbc..96db342f9 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using NLog; using NzbDrone.Common.Disk; @@ -76,14 +75,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport foreach (var file in newFiles) { var localEpisode = new LocalEpisode - { - Series = series, - DownloadClientEpisodeInfo = downloadClientItemInfo, - FolderEpisodeInfo = folderInfo, - Path = file, - SceneSource = sceneSource, - ExistingFile = series.Path.IsParentPath(file) - }; + { + Series = series, + DownloadClientEpisodeInfo = downloadClientItemInfo, + FolderEpisodeInfo = folderInfo, + Path = file, + SceneSource = sceneSource, + ExistingFile = series.Path.IsParentPath(file) + }; decisions.AddIfNotNull(GetDecision(localEpisode, downloadClientItem, nonSampleVideoFileCount > 1)); } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs index a418bfa76..2a7af0413 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/UpgradeSpecification.cs @@ -4,6 +4,9 @@ using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles.Qualities; +using System.Collections.Generic; namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications { @@ -19,11 +22,22 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem) { var qualityComparer = new QualityModelComparer(localEpisode.Series.Profile); + var languageComparer = new LanguageComparer(localEpisode.Series.LanguageProfile); + var profile = localEpisode.Series.Profile.Value; + if (localEpisode.Episodes.Any(e => e.EpisodeFileId != 0 && qualityComparer.Compare(e.EpisodeFile.Value.Quality, localEpisode.Quality) > 0)) { - _logger.Debug("This file isn't an upgrade for all episodes. Skipping {0}", localEpisode.Path); + _logger.Debug("This file isn't a quality upgrade for all episodes. Skipping {0}", localEpisode.Path); return Decision.Reject("Not an upgrade for existing episode file(s)"); } + + if (localEpisode.Episodes.Any(e => e.EpisodeFileId != 0 && + languageComparer.Compare(e.EpisodeFile.Value.Language, localEpisode.Language) > 0 && + qualityComparer.Compare(e.EpisodeFile.Value.Quality, localEpisode.Quality) == 0)) + { + _logger.Debug("This file isn't a language upgrade for all episodes. Skipping {0}", localEpisode.Path); + return Decision.Reject("Not an upgrade for existing episode file(s)"); + } return Decision.Accept(); } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 9c1904db0..2960a7190 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -151,6 +151,10 @@ + + + + @@ -175,6 +179,7 @@ + @@ -268,7 +273,6 @@ - @@ -312,6 +316,7 @@ Code + @@ -339,7 +344,7 @@ - + @@ -769,6 +774,12 @@ + + + + + + @@ -985,7 +996,13 @@ - + + + + + + + @@ -1097,7 +1114,6 @@ - @@ -1108,11 +1124,8 @@ - - - - + @@ -1229,7 +1242,6 @@ - @@ -1245,6 +1257,7 @@ + diff --git a/src/NzbDrone.Core/Parser/IsoLanguage.cs b/src/NzbDrone.Core/Parser/IsoLanguage.cs index 1bd198e50..78101bcb8 100644 --- a/src/NzbDrone.Core/Parser/IsoLanguage.cs +++ b/src/NzbDrone.Core/Parser/IsoLanguage.cs @@ -1,4 +1,6 @@ -namespace NzbDrone.Core.Parser +using NzbDrone.Core.Languages; + +namespace NzbDrone.Core.Parser { public class IsoLanguage { diff --git a/src/NzbDrone.Core/Parser/IsoLanguages.cs b/src/NzbDrone.Core/Parser/IsoLanguages.cs index b87b7ea5b..0d453df35 100644 --- a/src/NzbDrone.Core/Parser/IsoLanguages.cs +++ b/src/NzbDrone.Core/Parser/IsoLanguages.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using NzbDrone.Core.Languages; namespace NzbDrone.Core.Parser { diff --git a/src/NzbDrone.Core/Parser/Language.cs b/src/NzbDrone.Core/Parser/Language.cs deleted file mode 100644 index 0e9623377..000000000 --- a/src/NzbDrone.Core/Parser/Language.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace NzbDrone.Core.Parser -{ - public enum Language - { - Unknown = 0, - English = 1, - French = 2, - Spanish = 3, - German = 4, - Italian = 5, - Danish = 6, - Dutch = 7, - Japanese = 8, - Cantonese = 9, - Mandarin = 10, - Russian = 11, - Polish = 12, - Vietnamese = 13, - Swedish = 14, - Norwegian = 15, - Finnish = 16, - Turkish = 17, - Portuguese = 18, - Flemish = 19, - Greek = 20, - Korean = 21, - Hungarian = 22, - Hebrew = 23, - Lithuanian = 24, - Czech = 25 - } -} diff --git a/src/NzbDrone.Core/Parser/LanguageParser.cs b/src/NzbDrone.Core/Parser/LanguageParser.cs index 024a15c70..c751d1523 100644 --- a/src/NzbDrone.Core/Parser/LanguageParser.cs +++ b/src/NzbDrone.Core/Parser/LanguageParser.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text.RegularExpressions; using NLog; using NzbDrone.Common.Instrumentation; +using NzbDrone.Core.Languages; namespace NzbDrone.Core.Parser { diff --git a/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs b/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs index 94a0ee5cd..4e05abec9 100644 --- a/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs +++ b/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs @@ -4,6 +4,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.MediaFiles.MediaInfo; +using NzbDrone.Core.Languages; namespace NzbDrone.Core.Parser.Model { @@ -22,6 +23,7 @@ namespace NzbDrone.Core.Parser.Model public Series Series { get; set; } public List Episodes { get; set; } public QualityModel Quality { get; set; } + public Language Language { get; set; } public MediaInfoModel MediaInfo { get; set; } public bool ExistingFile { get; set; } public bool SceneSource { get; set; } diff --git a/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs b/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs index 6dc338dcf..d4e845dc8 100644 --- a/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs +++ b/src/NzbDrone.Core/Parser/Model/ParsedEpisodeInfo.cs @@ -1,6 +1,7 @@ using System.Linq; using NzbDrone.Common.Extensions; using NzbDrone.Core.Qualities; +using NzbDrone.Core.Languages; namespace NzbDrone.Core.Parser.Model { diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index 884d2bec2..741b3e781 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -8,6 +8,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Tv; +using NzbDrone.Core.Languages; namespace NzbDrone.Core.Parser { @@ -322,6 +323,9 @@ namespace NzbDrone.Core.Parser private static readonly Regex AnimeReleaseGroupRegex = new Regex(@"^(?:\[(?(?!\s).+?(?\b(?:ita|italian)\b)|(?german\b|videomann)|(?flemish)|(?greek)|(?(?:\W|_)(?:FR|VOSTFR)(?:\W|_))|(?\brus\b)|(?nl\W?subs?)|(?\b(?:HUNDUB|HUN)\b)|(?\b(?:español|castellano)\b)", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex YearInTitleRegex = new Regex(@"^(?.+?)(?:\W|_)?(?<year>\d{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled); @@ -575,6 +579,99 @@ namespace NzbDrone.Core.Parser return title; } + public static Language ParseLanguage(string title) + { + var lowerTitle = title.ToLower(); + + if (lowerTitle.Contains("english")) + return Language.English; + + if (lowerTitle.Contains("french")) + return Language.French; + + if (lowerTitle.Contains("spanish")) + return Language.Spanish; + + if (lowerTitle.Contains("danish")) + return Language.Danish; + + if (lowerTitle.Contains("dutch")) + return Language.Dutch; + + if (lowerTitle.Contains("japanese")) + return Language.Japanese; + + if (lowerTitle.Contains("cantonese")) + return Language.Cantonese; + + if (lowerTitle.Contains("mandarin")) + return Language.Mandarin; + + if (lowerTitle.Contains("korean")) + return Language.Korean; + + if (lowerTitle.Contains("russian")) + return Language.Russian; + + if (lowerTitle.Contains("polish")) + return Language.Polish; + + if (lowerTitle.Contains("vietnamese")) + return Language.Vietnamese; + + if (lowerTitle.Contains("swedish")) + return Language.Swedish; + + if (lowerTitle.Contains("norwegian")) + return Language.Norwegian; + + if (lowerTitle.Contains("nordic")) + return Language.Norwegian; + + if (lowerTitle.Contains("finnish")) + return Language.Finnish; + + if (lowerTitle.Contains("turkish")) + return Language.Turkish; + + if (lowerTitle.Contains("portuguese")) + return Language.Portuguese; + + if (lowerTitle.Contains("hungarian")) + return Language.Hungarian; + + var match = LanguageRegex.Match(title); + + if (match.Groups["italian"].Captures.Cast<Capture>().Any()) + return Language.Italian; + + if (match.Groups["german"].Captures.Cast<Capture>().Any()) + return Language.German; + + if (match.Groups["flemish"].Captures.Cast<Capture>().Any()) + return Language.Flemish; + + if (match.Groups["greek"].Captures.Cast<Capture>().Any()) + return Language.Greek; + + if (match.Groups["spanish"].Captures.Cast<Capture>().Any()) + return Language.Spanish; + + if (match.Groups["french"].Success) + return Language.French; + + if (match.Groups["russian"].Success) + return Language.Russian; + + if (match.Groups["dutch"].Success) + return Language.Dutch; + + if (match.Groups["hungarian"].Success) + return Language.Hungarian; + + return Language.English; + } + private static SeriesTitleInfo GetSeriesTitleInfo(string title) { var seriesTitleInfo = new SeriesTitleInfo(); diff --git a/src/NzbDrone.Core/Profiles/Languages/LanguageProfile.cs b/src/NzbDrone.Core/Profiles/Languages/LanguageProfile.cs new file mode 100644 index 000000000..102785577 --- /dev/null +++ b/src/NzbDrone.Core/Profiles/Languages/LanguageProfile.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Languages; + +namespace NzbDrone.Core.Profiles.Languages +{ + public class LanguageProfile : ModelBase + { + public string Name { get; set; } + public List<LanguageProfileItem> Languages { get; set; } + public Language Cutoff { get; set; } + + public Language LastAllowedLanguage() + { + return Languages.Last(q => q.Allowed).Language; + } + } +} diff --git a/src/NzbDrone.Core/Profiles/Languages/LanguageProfileInUseException.cs b/src/NzbDrone.Core/Profiles/Languages/LanguageProfileInUseException.cs new file mode 100644 index 000000000..bfba8045d --- /dev/null +++ b/src/NzbDrone.Core/Profiles/Languages/LanguageProfileInUseException.cs @@ -0,0 +1,13 @@ +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.Profiles.Languages +{ + public class LanguageProfileInUseException : NzbDroneException + { + public LanguageProfileInUseException(int profileId) + : base("Language profile [{0}] is in use.", profileId) + { + + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Profiles/Languages/LanguageProfileItem.cs b/src/NzbDrone.Core/Profiles/Languages/LanguageProfileItem.cs new file mode 100644 index 000000000..9ef49508a --- /dev/null +++ b/src/NzbDrone.Core/Profiles/Languages/LanguageProfileItem.cs @@ -0,0 +1,11 @@ +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Languages; + +namespace NzbDrone.Core.Profiles.Languages +{ + public class LanguageProfileItem : IEmbeddedDocument + { + public Language Language { get; set; } + public bool Allowed { get; set; } + } +} diff --git a/src/NzbDrone.Core/Profiles/Languages/LanguageProfileRepository.cs b/src/NzbDrone.Core/Profiles/Languages/LanguageProfileRepository.cs new file mode 100644 index 000000000..797c14efe --- /dev/null +++ b/src/NzbDrone.Core/Profiles/Languages/LanguageProfileRepository.cs @@ -0,0 +1,23 @@ +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Events; + +namespace NzbDrone.Core.Profiles.Languages +{ + public interface ILanguageProfileRepository : IBasicRepository<LanguageProfile> + { + bool Exists(int id); + } + + public class LanguageProfileRepository : BasicRepository<LanguageProfile>, ILanguageProfileRepository + { + public LanguageProfileRepository(IMainDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) + { + } + + public bool Exists(int id) + { + return DataMapper.Query<LanguageProfile>().Where(p => p.Id == id).GetRowCount() == 1; + } + } +} diff --git a/src/NzbDrone.Core/Profiles/Languages/LanguageProfileService.cs b/src/NzbDrone.Core/Profiles/Languages/LanguageProfileService.cs new file mode 100644 index 000000000..bf6903562 --- /dev/null +++ b/src/NzbDrone.Core/Profiles/Languages/LanguageProfileService.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Profiles.Languages +{ + public interface ILanguageProfileService + { + LanguageProfile Add(LanguageProfile profile); + void Update(LanguageProfile profile); + void Delete(int id); + List<LanguageProfile> All(); + LanguageProfile Get(int id); + bool Exists(int id); + } + + public class LanguageProfileService : ILanguageProfileService, IHandle<ApplicationStartedEvent> + { + private readonly ILanguageProfileRepository _profileRepository; + private readonly ISeriesService _seriesService; + private readonly Logger _logger; + + public LanguageProfileService(ILanguageProfileRepository profileRepository, ISeriesService seriesService, Logger logger) + { + _profileRepository = profileRepository; + _seriesService = seriesService; + _logger = logger; + } + + public LanguageProfile Add(LanguageProfile profile) + { + return _profileRepository.Insert(profile); + } + + public void Update(LanguageProfile profile) + { + _profileRepository.Update(profile); + } + + public void Delete(int id) + { + if (_seriesService.GetAllSeries().Any(c => c.LanguageProfileId == id)) + { + throw new LanguageProfileInUseException(id); + } + + _profileRepository.Delete(id); + } + + public List<LanguageProfile> All() + { + return _profileRepository.All().ToList(); + } + + public LanguageProfile Get(int id) + { + return _profileRepository.Get(id); + } + + public bool Exists(int id) + { + return _profileRepository.Exists(id); + } + + private LanguageProfile AddDefaultProfile(string name, Language cutoff, params Language[] allowed) + { + var languages = Language.All + .OrderByDescending(l => l.Name) + .Select(v => new LanguageProfileItem { Language = v, Allowed = allowed.Contains(v) }) + .ToList(); + + var profile = new LanguageProfile + { + Name = name, + Cutoff = cutoff, + Languages = languages, + }; + + return Add(profile); + } + + public void Handle(ApplicationStartedEvent message) + { + if (All().Any()) return; + + _logger.Info("Setting up default language profiles"); + + AddDefaultProfile("English", Language.English, Language.English); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Profiles/Profile.cs b/src/NzbDrone.Core/Profiles/Qualities/Profile.cs similarity index 80% rename from src/NzbDrone.Core/Profiles/Profile.cs rename to src/NzbDrone.Core/Profiles/Qualities/Profile.cs index 6215e9474..b4eb565a0 100644 --- a/src/NzbDrone.Core/Profiles/Profile.cs +++ b/src/NzbDrone.Core/Profiles/Qualities/Profile.cs @@ -1,17 +1,15 @@ using System.Collections.Generic; using System.Linq; using NzbDrone.Core.Datastore; -using NzbDrone.Core.Parser; using NzbDrone.Core.Qualities; -namespace NzbDrone.Core.Profiles +namespace NzbDrone.Core.Profiles.Qualities { public class Profile : ModelBase { public string Name { get; set; } public Quality Cutoff { get; set; } public List<ProfileQualityItem> Items { get; set; } - public Language Language { get; set; } public Quality LastAllowedQuality() { diff --git a/src/NzbDrone.Core/Profiles/ProfileInUseException.cs b/src/NzbDrone.Core/Profiles/Qualities/ProfileInUseException.cs similarity index 86% rename from src/NzbDrone.Core/Profiles/ProfileInUseException.cs rename to src/NzbDrone.Core/Profiles/Qualities/ProfileInUseException.cs index b48488a4d..44e583a42 100644 --- a/src/NzbDrone.Core/Profiles/ProfileInUseException.cs +++ b/src/NzbDrone.Core/Profiles/Qualities/ProfileInUseException.cs @@ -1,7 +1,7 @@ using System.Net; using NzbDrone.Core.Exceptions; -namespace NzbDrone.Core.Profiles +namespace NzbDrone.Core.Profiles.Qualities { public class ProfileInUseException : NzbDroneClientException { diff --git a/src/NzbDrone.Core/Profiles/ProfileQualityItem.cs b/src/NzbDrone.Core/Profiles/Qualities/ProfileQualityItem.cs similarity index 83% rename from src/NzbDrone.Core/Profiles/ProfileQualityItem.cs rename to src/NzbDrone.Core/Profiles/Qualities/ProfileQualityItem.cs index 35c9ce360..25da900ef 100644 --- a/src/NzbDrone.Core/Profiles/ProfileQualityItem.cs +++ b/src/NzbDrone.Core/Profiles/Qualities/ProfileQualityItem.cs @@ -1,7 +1,7 @@ using NzbDrone.Core.Datastore; using NzbDrone.Core.Qualities; -namespace NzbDrone.Core.Profiles +namespace NzbDrone.Core.Profiles.Qualities { public class ProfileQualityItem : IEmbeddedDocument { diff --git a/src/NzbDrone.Core/Profiles/ProfileRepository.cs b/src/NzbDrone.Core/Profiles/Qualities/ProfileRepository.cs similarity index 93% rename from src/NzbDrone.Core/Profiles/ProfileRepository.cs rename to src/NzbDrone.Core/Profiles/Qualities/ProfileRepository.cs index 4e071a0cf..638101b37 100644 --- a/src/NzbDrone.Core/Profiles/ProfileRepository.cs +++ b/src/NzbDrone.Core/Profiles/Qualities/ProfileRepository.cs @@ -1,7 +1,7 @@ using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; -namespace NzbDrone.Core.Profiles +namespace NzbDrone.Core.Profiles.Qualities { public interface IProfileRepository : IBasicRepository<Profile> { diff --git a/src/NzbDrone.Core/Profiles/ProfileService.cs b/src/NzbDrone.Core/Profiles/Qualities/ProfileService.cs similarity index 95% rename from src/NzbDrone.Core/Profiles/ProfileService.cs rename to src/NzbDrone.Core/Profiles/Qualities/ProfileService.cs index b5da50ab4..31d19343d 100644 --- a/src/NzbDrone.Core/Profiles/ProfileService.cs +++ b/src/NzbDrone.Core/Profiles/Qualities/ProfileService.cs @@ -3,11 +3,10 @@ using System.Linq; using NLog; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.Parser; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; -namespace NzbDrone.Core.Profiles +namespace NzbDrone.Core.Profiles.Qualities { public interface IProfileService { @@ -75,7 +74,10 @@ namespace NzbDrone.Core.Profiles .Select(v => new ProfileQualityItem { Quality = v.Quality, Allowed = allowed.Contains(v.Quality) }) .ToList(); - var profile = new Profile { Name = name, Cutoff = cutoff, Items = items, Language = Language.English }; + var profile = new Profile { Name = name, + Cutoff = cutoff, + Items = items, + }; return Add(profile); } diff --git a/src/NzbDrone.Core/Qualities/QualityModelComparer.cs b/src/NzbDrone.Core/Qualities/QualityModelComparer.cs index 64f1939b8..61be027d6 100644 --- a/src/NzbDrone.Core/Qualities/QualityModelComparer.cs +++ b/src/NzbDrone.Core/Qualities/QualityModelComparer.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using NzbDrone.Common.EnsureThat; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; namespace NzbDrone.Core.Qualities { diff --git a/src/NzbDrone.Core/Tv/EpisodeCutoffService.cs b/src/NzbDrone.Core/Tv/EpisodeCutoffService.cs index 6747aa87e..b3e65739e 100644 --- a/src/NzbDrone.Core/Tv/EpisodeCutoffService.cs +++ b/src/NzbDrone.Core/Tv/EpisodeCutoffService.cs @@ -2,8 +2,10 @@ using System.Linq; using NLog; using NzbDrone.Core.Datastore; -using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles.Qualities; +using NzbDrone.Core.Profiles.Languages; namespace NzbDrone.Core.Tv { @@ -16,33 +18,47 @@ namespace NzbDrone.Core.Tv { private readonly IEpisodeRepository _episodeRepository; private readonly IProfileService _profileService; + private readonly ILanguageProfileService _languageProfileService; private readonly Logger _logger; - public EpisodeCutoffService(IEpisodeRepository episodeRepository, IProfileService profileService, Logger logger) + public EpisodeCutoffService(IEpisodeRepository episodeRepository, IProfileService profileService, ILanguageProfileService languageProfileService, Logger logger) { _episodeRepository = episodeRepository; _profileService = profileService; + _languageProfileService = languageProfileService; _logger = logger; } public PagingSpec<Episode> EpisodesWhereCutoffUnmet(PagingSpec<Episode> pagingSpec) { var qualitiesBelowCutoff = new List<QualitiesBelowCutoff>(); + var languagesBelowCutoff = new List<LanguagesBelowCutoff>(); var profiles = _profileService.All(); + var languageProfiles = _languageProfileService.All(); //Get all items less than the cutoff foreach (var profile in profiles) { var cutoffIndex = profile.Items.FindIndex(v => v.Quality == profile.Cutoff); var belowCutoff = profile.Items.Take(cutoffIndex).ToList(); - if (belowCutoff.Any()) { qualitiesBelowCutoff.Add(new QualitiesBelowCutoff(profile.Id, belowCutoff.Select(i => i.Quality.Id))); } } - return _episodeRepository.EpisodesWhereCutoffUnmet(pagingSpec, qualitiesBelowCutoff, false); + foreach (var profile in languageProfiles) + { + var languageCutoffIndex = profile.Languages.FindIndex(v => v.Language == profile.Cutoff); + var belowLanguageCutoff = profile.Languages.Take(languageCutoffIndex).ToList(); + + if (belowLanguageCutoff.Any()) + { + languagesBelowCutoff.Add(new LanguagesBelowCutoff(profile.Id, belowLanguageCutoff.Select(l => l.Language.Id))); + } + } + + return _episodeRepository.EpisodesWhereCutoffUnmet(pagingSpec, qualitiesBelowCutoff, languagesBelowCutoff, false); } } } diff --git a/src/NzbDrone.Core/Tv/EpisodeRepository.cs b/src/NzbDrone.Core/Tv/EpisodeRepository.cs index 16c548faa..ebda26c0a 100644 --- a/src/NzbDrone.Core/Tv/EpisodeRepository.cs +++ b/src/NzbDrone.Core/Tv/EpisodeRepository.cs @@ -9,6 +9,7 @@ using NzbDrone.Core.Datastore.Extensions; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Qualities; +using NzbDrone.Core.Languages; namespace NzbDrone.Core.Tv { @@ -23,7 +24,7 @@ namespace NzbDrone.Core.Tv List<Episode> GetEpisodeByFileId(int fileId); List<Episode> EpisodesWithFiles(int seriesId); PagingSpec<Episode> EpisodesWithoutFiles(PagingSpec<Episode> pagingSpec, bool includeSpecials); - PagingSpec<Episode> EpisodesWhereCutoffUnmet(PagingSpec<Episode> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff, bool includeSpecials); + PagingSpec<Episode> EpisodesWhereCutoffUnmet(PagingSpec<Episode> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff, List<LanguagesBelowCutoff> languagesBelowCutoff, bool includeSpecials); List<Episode> FindEpisodesBySceneNumbering(int seriesId, int seasonNumber, int episodeNumber); List<Episode> FindEpisodesBySceneNumbering(int seriesId, int sceneAbsoluteEpisodeNumber); List<Episode> EpisodesBetweenDates(DateTime startDate, DateTime endDate, bool includeUnmonitored); @@ -116,7 +117,7 @@ namespace NzbDrone.Core.Tv return pagingSpec; } - public PagingSpec<Episode> EpisodesWhereCutoffUnmet(PagingSpec<Episode> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff, bool includeSpecials) + public PagingSpec<Episode> EpisodesWhereCutoffUnmet(PagingSpec<Episode> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff, List<LanguagesBelowCutoff> languagesBelowCutoff, bool includeSpecials) { var startingSeasonNumber = 1; @@ -125,8 +126,8 @@ namespace NzbDrone.Core.Tv startingSeasonNumber = 0; } - pagingSpec.TotalRecords = EpisodesWhereCutoffUnmetQuery(pagingSpec, qualitiesBelowCutoff, startingSeasonNumber).GetRowCount(); - pagingSpec.Records = EpisodesWhereCutoffUnmetQuery(pagingSpec, qualitiesBelowCutoff, startingSeasonNumber).ToList(); + pagingSpec.TotalRecords = EpisodesWhereCutoffUnmetQuery(pagingSpec, qualitiesBelowCutoff, languagesBelowCutoff, startingSeasonNumber).GetRowCount(); + pagingSpec.Records = EpisodesWhereCutoffUnmetQuery(pagingSpec, qualitiesBelowCutoff, languagesBelowCutoff, startingSeasonNumber).ToList(); return pagingSpec; } @@ -214,14 +215,18 @@ namespace NzbDrone.Core.Tv .Take(pagingSpec.PageSize); } - private SortBuilder<Episode> EpisodesWhereCutoffUnmetQuery(PagingSpec<Episode> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff, int startingSeasonNumber) + private SortBuilder<Episode> EpisodesWhereCutoffUnmetQuery(PagingSpec<Episode> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff, List<LanguagesBelowCutoff> languagesBelowCutoff, int startingSeasonNumber) { return Query.Join<Episode, Series>(JoinType.Inner, e => e.Series, (e, s) => e.SeriesId == s.Id) .Join<Episode, EpisodeFile>(JoinType.Left, e => e.EpisodeFile, (e, s) => e.EpisodeFileId == s.Id) .Where(pagingSpec.FilterExpressions.FirstOrDefault()) .AndWhere(e => e.EpisodeFileId != 0) .AndWhere(e => e.SeasonNumber >= startingSeasonNumber) - .AndWhere(BuildQualityCutoffWhereClause(qualitiesBelowCutoff)) + .AndWhere( + String.Format("({0} OR {1})", + BuildLanguageCutoffWhereClause(languagesBelowCutoff), + BuildQualityCutoffWhereClause(qualitiesBelowCutoff))) + //.AndWhere(BuildQualityCutoffWhereClause(qualitiesBelowCutoff, languagesBelowCutoff)) .OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection()) .Skip(pagingSpec.PagingOffset()) .Take(pagingSpec.PageSize); @@ -233,6 +238,22 @@ namespace NzbDrone.Core.Tv currentTime.ToString("yyyy-MM-dd HH:mm:ss")); } + private string BuildLanguageCutoffWhereClause(List<LanguagesBelowCutoff> languagesBelowCutoff) + { + var clauses = new List<String>(); + + foreach (var language in languagesBelowCutoff) + { + foreach (var belowCutoff in language.LanguageIds) + { + clauses.Add(String.Format("([t1].[LanguageProfileId] = {0} AND [t2].[Language] = {1})", language.ProfileId, belowCutoff)); + } + } + + return String.Format("({0})", String.Join(" OR ", clauses)); + } + + private string BuildQualityCutoffWhereClause(List<QualitiesBelowCutoff> qualitiesBelowCutoff) { var clauses = new List<string>(); diff --git a/src/NzbDrone.Core/Tv/Series.cs b/src/NzbDrone.Core/Tv/Series.cs index 8542d183b..c7c4b5d23 100644 --- a/src/NzbDrone.Core/Tv/Series.cs +++ b/src/NzbDrone.Core/Tv/Series.cs @@ -3,7 +3,8 @@ using System.Collections.Generic; using Marr.Data; using NzbDrone.Common.Extensions; using NzbDrone.Core.Datastore; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; +using NzbDrone.Core.Profiles.Languages; namespace NzbDrone.Core.Tv { @@ -30,6 +31,7 @@ namespace NzbDrone.Core.Tv public string AirTime { get; set; } public bool Monitored { get; set; } public int ProfileId { get; set; } + public int LanguageProfileId { get; set; } public bool SeasonFolder { get; set; } public DateTime? LastInfoSync { get; set; } public int Runtime { get; set; } @@ -48,6 +50,7 @@ namespace NzbDrone.Core.Tv public DateTime Added { get; set; } public DateTime? FirstAired { get; set; } public LazyLoaded<Profile> Profile { get; set; } + public LazyLoaded<LanguageProfile> LanguageProfile { get; set; } public List<Season> Seasons { get; set; } public HashSet<int> Tags { get; set; } @@ -65,6 +68,7 @@ namespace NzbDrone.Core.Tv Seasons = otherSeries.Seasons; Path = otherSeries.Path; ProfileId = otherSeries.ProfileId; + LanguageProfileId = otherSeries.LanguageProfileId; SeasonFolder = otherSeries.SeasonFolder; Monitored = otherSeries.Monitored; diff --git a/src/NzbDrone.Core/Validation/LanguageProfileExistsValidator.cs b/src/NzbDrone.Core/Validation/LanguageProfileExistsValidator.cs new file mode 100644 index 000000000..90450e3e5 --- /dev/null +++ b/src/NzbDrone.Core/Validation/LanguageProfileExistsValidator.cs @@ -0,0 +1,23 @@ +using FluentValidation.Validators; +using NzbDrone.Core.Profiles.Languages; + +namespace NzbDrone.Core.Validation +{ + public class LanguageProfileExistsValidator : PropertyValidator + { + private readonly ILanguageProfileService _profileService; + + public LanguageProfileExistsValidator(ILanguageProfileService profileService) + : base("Language profile does not exist") + { + _profileService = profileService; + } + + protected override bool IsValid(PropertyValidatorContext context) + { + if (context.PropertyValue == null) return true; + + return _profileService.Exists((int)context.PropertyValue); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Validation/LanguageValidator.cs b/src/NzbDrone.Core/Validation/LanguageValidator.cs deleted file mode 100644 index 9edfc9085..000000000 --- a/src/NzbDrone.Core/Validation/LanguageValidator.cs +++ /dev/null @@ -1,21 +0,0 @@ -using FluentValidation.Validators; - -namespace NzbDrone.Core.Validation -{ - public class LanguageValidator : PropertyValidator - { - public LanguageValidator() - : base("Unknown Language") - { - } - - protected override bool IsValid(PropertyValidatorContext context) - { - if (context.PropertyValue == null) return false; - - if ((int) context.PropertyValue == 0) return false; - - return true; - } - } -} diff --git a/src/NzbDrone.Core/Validation/ProfileExistsValidator.cs b/src/NzbDrone.Core/Validation/ProfileExistsValidator.cs index e7ff62b67..0eb5293dd 100644 --- a/src/NzbDrone.Core/Validation/ProfileExistsValidator.cs +++ b/src/NzbDrone.Core/Validation/ProfileExistsValidator.cs @@ -1,5 +1,5 @@ using FluentValidation.Validators; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; namespace NzbDrone.Core.Validation { diff --git a/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs b/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs index 2fae03d6c..df9043fd0 100644 --- a/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs +++ b/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs @@ -58,11 +58,6 @@ namespace NzbDrone.Core.Validation }); } - public static IRuleBuilderOptions<T, Language> ValidLanguage<T>(this IRuleBuilder<T, Language> ruleBuilder) - { - return ruleBuilder.SetValidator(new LanguageValidator()); - } - public static IRuleBuilderOptions<T, TProp> AsWarning<T, TProp>(this IRuleBuilderOptions<T, TProp> ruleBuilder) { return ruleBuilder.WithState(v => NzbDroneValidationState.Warning); diff --git a/src/NzbDrone.Integration.Test/ApiTests/EpisodeFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/EpisodeFixture.cs index b59cf1668..636ce00ed 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/EpisodeFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/EpisodeFixture.cs @@ -23,6 +23,7 @@ namespace NzbDrone.Integration.Test.ApiTests var newSeries = Series.Lookup("archer").Single(c => c.TvdbId == 110381); newSeries.ProfileId = 1; + newSeries.LanguageProfileId = 1; newSeries.Path = @"C:\Test\Archer".AsOsAgnostic(); newSeries = Series.Post(newSeries); diff --git a/src/NzbDrone.Integration.Test/ApiTests/SeriesEditorFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/SeriesEditorFixture.cs index e6a36ca0d..046da4fbf 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/SeriesEditorFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/SeriesEditorFixture.cs @@ -15,6 +15,7 @@ namespace NzbDrone.Integration.Test.ApiTests var newSeries = Series.Lookup(title).First(); newSeries.ProfileId = 1; + newSeries.LanguageProfileId = 1; newSeries.Path = string.Format(@"C:\Test\{0}", title).AsOsAgnostic(); Series.Post(newSeries); diff --git a/src/NzbDrone.Integration.Test/ApiTests/SeriesFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/SeriesFixture.cs index 3c44e2336..fe7f2cd25 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/SeriesFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/SeriesFixture.cs @@ -18,6 +18,7 @@ namespace NzbDrone.Integration.Test.ApiTests var series = Series.Lookup("tvdb:266189").Single(); series.ProfileId = 1; + series.LanguageProfileId = 1; series.Path = Path.Combine(SeriesRootFolder, series.Title); series.Tags = new HashSet<int>(); series.Tags.Add(tag.Id); @@ -60,6 +61,7 @@ namespace NzbDrone.Integration.Test.ApiTests var series = Series.Lookup("tvdb:266189").Single(); series.ProfileId = 1; + series.LanguageProfileId = 1; series.Path = Path.Combine(SeriesRootFolder, series.Title); var result = Series.Post(series); @@ -67,6 +69,7 @@ namespace NzbDrone.Integration.Test.ApiTests result.Should().NotBeNull(); result.Id.Should().NotBe(0); result.ProfileId.Should().Be(1); + result.LanguageProfileId.Should().Be(1); result.Path.Should().Be(Path.Combine(SeriesRootFolder, series.Title)); } diff --git a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs index 16b6681c8..3a20d4c70 100644 --- a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs +++ b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs @@ -213,6 +213,7 @@ namespace NzbDrone.Integration.Test var lookup = Series.Lookup("tvdb:" + tvdbId); var series = lookup.First(); series.ProfileId = 1; + series.LanguageProfileId = 1; series.Path = Path.Combine(SeriesRootFolder, series.Title); series.Monitored = true; series.Seasons.ForEach(v => v.Monitored = true); diff --git a/src/Sonarr.Api.V3/Blacklist/BlacklistResource.cs b/src/Sonarr.Api.V3/Blacklist/BlacklistResource.cs index 615a49e85..90ef3becf 100644 --- a/src/Sonarr.Api.V3/Blacklist/BlacklistResource.cs +++ b/src/Sonarr.Api.V3/Blacklist/BlacklistResource.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Languages; using NzbDrone.Core.Qualities; using Sonarr.Api.V3.Series; using Sonarr.Http.REST; @@ -12,6 +13,7 @@ namespace Sonarr.Api.V3.Blacklist public int SeriesId { get; set; } public List<int> EpisodeIds { get; set; } public string SourceTitle { get; set; } + public Language Language { get; set; } public QualityModel Quality { get; set; } public DateTime Date { get; set; } public DownloadProtocol Protocol { get; set; } @@ -34,6 +36,7 @@ namespace Sonarr.Api.V3.Blacklist SeriesId = model.SeriesId, EpisodeIds = model.EpisodeIds, SourceTitle = model.SourceTitle, + Language = model.Language, Quality = model.Quality, Date = model.Date, Protocol = model.Protocol, diff --git a/src/Sonarr.Api.V3/Calendar/CalendarModule.cs b/src/Sonarr.Api.V3/Calendar/CalendarModule.cs index 6aeb8220b..07092c21c 100644 --- a/src/Sonarr.Api.V3/Calendar/CalendarModule.cs +++ b/src/Sonarr.Api.V3/Calendar/CalendarModule.cs @@ -13,9 +13,9 @@ namespace Sonarr.Api.V3.Calendar { public CalendarModule(IEpisodeService episodeService, ISeriesService seriesService, - IQualityUpgradableSpecification qualityUpgradableSpecification, + IUpgradableSpecification ugradableSpecification, IBroadcastSignalRMessage signalRBroadcaster) - : base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "calendar") + : base(episodeService, seriesService, ugradableSpecification, signalRBroadcaster, "calendar") { GetResourceAll = GetCalendar; } diff --git a/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileListResource.cs b/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileListResource.cs index 3f6e13afc..4a540fe52 100644 --- a/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileListResource.cs +++ b/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileListResource.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using NzbDrone.Core.Languages; using NzbDrone.Core.Qualities; namespace Sonarr.Api.V3.EpisodeFiles @@ -6,6 +7,7 @@ namespace Sonarr.Api.V3.EpisodeFiles public class EpisodeFileListResource { public List<int> EpisodeFileIds { get; set; } + public Language Language { get; set; } public QualityModel Quality { get; set; } } } diff --git a/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileModule.cs b/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileModule.cs index 4b0b3a053..3f5b8e7a0 100644 --- a/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileModule.cs +++ b/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileModule.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using Nancy; -using NLog; using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Exceptions; @@ -12,7 +10,6 @@ using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv; using NzbDrone.SignalR; -using Sonarr.Api.V3.Series; using Sonarr.Http; using Sonarr.Http.Extensions; using BadRequestException = Sonarr.Http.REST.BadRequestException; @@ -27,19 +24,19 @@ namespace Sonarr.Api.V3.EpisodeFiles private readonly IDeleteMediaFiles _mediaFileDeletionService; private readonly IRecycleBinProvider _recycleBinProvider; private readonly ISeriesService _seriesService; - private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification; + private readonly IUpgradableSpecification _upgradableSpecification; public EpisodeFileModule(IBroadcastSignalRMessage signalRBroadcaster, IMediaFileService mediaFileService, IDeleteMediaFiles mediaFileDeletionService, ISeriesService seriesService, - IQualityUpgradableSpecification qualityUpgradableSpecification, + IUpgradableSpecification upgradableSpecification) : base(signalRBroadcaster) { _mediaFileService = mediaFileService; _mediaFileDeletionService = mediaFileDeletionService; _seriesService = seriesService; - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = upgradableSpecification; GetResourceById = GetEpisodeFile; GetResourceAll = GetEpisodeFiles; @@ -55,7 +52,7 @@ namespace Sonarr.Api.V3.EpisodeFiles var episodeFile = _mediaFileService.Get(id); var series = _seriesService.GetSeries(episodeFile.SeriesId); - return episodeFile.ToResource(series, _qualityUpgradableSpecification); + return episodeFile.ToResource(series, _upgradableSpecification); } private List<EpisodeFileResource> GetEpisodeFiles() @@ -73,7 +70,7 @@ namespace Sonarr.Api.V3.EpisodeFiles int seriesId = Convert.ToInt32(seriesIdQuery.Value); var series = _seriesService.GetSeries(seriesId); - return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _qualityUpgradableSpecification)); + return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _upgradableSpecification)); } else @@ -88,7 +85,7 @@ namespace Sonarr.Api.V3.EpisodeFiles return episodeFiles.GroupBy(e => e.SeriesId) .SelectMany(f => f.ToList() - .ConvertAll( e => e.ToResource(_seriesService.GetSeries(f.Key), _qualityUpgradableSpecification))) + .ConvertAll( e => e.ToResource(_seriesService.GetSeries(f.Key), _upgradableSpecification))) .ToList(); } } @@ -107,14 +104,22 @@ namespace Sonarr.Api.V3.EpisodeFiles foreach (var episodeFile in episodeFiles) { - episodeFile.Quality = resource.Quality; + if (resource.Language != null) + { + episodeFile.Language = resource.Language; + } + + if (resource.Quality != null) + { + episodeFile.Quality = resource.Quality; + } } _mediaFileService.Update(episodeFiles); var series = _seriesService.GetSeries(episodeFiles.First().SeriesId); - return episodeFiles.ConvertAll(f => f.ToResource(series, _qualityUpgradableSpecification)) + return episodeFiles.ConvertAll(f => f.ToResource(series, _upgradableSpecification)) .AsResponse(HttpStatusCode.Accepted); } diff --git a/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileResource.cs b/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileResource.cs index 0920c3a5a..7e7783eb3 100644 --- a/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileResource.cs +++ b/src/Sonarr.Api.V3/EpisodeFiles/EpisodeFileResource.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.IO; using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Languages; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Qualities; using Sonarr.Http.REST; @@ -16,10 +17,12 @@ namespace Sonarr.Api.V3.EpisodeFiles public long Size { get; set; } public DateTime DateAdded { get; set; } public string SceneName { get; set; } + public Language Language { get; set; } public QualityModel Quality { get; set; } public MediaInfoResource MediaInfo { get; set; } public bool QualityCutoffNotMet { get; set; } + public bool LanguageCutoffNotMet { get; set; } } public static class EpisodeFileResourceMapper @@ -39,6 +42,7 @@ namespace Sonarr.Api.V3.EpisodeFiles Size = model.Size, DateAdded = model.DateAdded, SceneName = model.SceneName, + Language = model.Language, Quality = model.Quality, MediaInfo = model.MediaInfo.ToResource(model.SceneName) //QualityCutoffNotMet @@ -46,7 +50,7 @@ namespace Sonarr.Api.V3.EpisodeFiles } - public static EpisodeFileResource ToResource(this EpisodeFile model, NzbDrone.Core.Tv.Series series, IQualityUpgradableSpecification qualityUpgradableSpecification) + public static EpisodeFileResource ToResource(this EpisodeFile model, NzbDrone.Core.Tv.Series series, IUpgradableSpecification upgradableSpecification) { if (model == null) return null; @@ -61,9 +65,11 @@ namespace Sonarr.Api.V3.EpisodeFiles Size = model.Size, DateAdded = model.DateAdded, SceneName = model.SceneName, + Language = model.Language, Quality = model.Quality, MediaInfo = model.MediaInfo.ToResource(model.SceneName), - QualityCutoffNotMet = qualityUpgradableSpecification.CutoffNotMet(series.Profile.Value, model.Quality) + QualityCutoffNotMet = upgradableSpecification.QualityCutoffNotMet(series.Profile.Value, model.Quality), + LanguageCutoffNotMet = upgradableSpecification.LanguageCutoffNotMet(series.LanguageProfile.Value, model.Language) }; } } diff --git a/src/Sonarr.Api.V3/Episodes/EpisodeModule.cs b/src/Sonarr.Api.V3/Episodes/EpisodeModule.cs index bae18ec00..57637482d 100644 --- a/src/Sonarr.Api.V3/Episodes/EpisodeModule.cs +++ b/src/Sonarr.Api.V3/Episodes/EpisodeModule.cs @@ -14,9 +14,9 @@ namespace Sonarr.Api.V3.Episodes { public EpisodeModule(ISeriesService seriesService, IEpisodeService episodeService, - IQualityUpgradableSpecification qualityUpgradableSpecification, + IUpgradableSpecification upgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster) - : base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster) + : base(episodeService, seriesService, upgradableSpecification, signalRBroadcaster) { GetResourceAll = GetEpisodes; Put[@"/(?<id>[\d]{1,10})"] = x => SetEpisodeMonitored(x.Id); diff --git a/src/Sonarr.Api.V3/Episodes/EpisodeModuleWithSignalR.cs b/src/Sonarr.Api.V3/Episodes/EpisodeModuleWithSignalR.cs index 692ad319d..004da690b 100644 --- a/src/Sonarr.Api.V3/Episodes/EpisodeModuleWithSignalR.cs +++ b/src/Sonarr.Api.V3/Episodes/EpisodeModuleWithSignalR.cs @@ -1,6 +1,4 @@ using System.Collections.Generic; -using System.IO; -using System.Linq; using NzbDrone.Common.Extensions; using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.DecisionEngine; @@ -12,8 +10,6 @@ using NzbDrone.SignalR; using Sonarr.Api.V3.EpisodeFiles; using Sonarr.Api.V3.Series; using Sonarr.Http; -using Sonarr.Http.Extensions; -using Sonarr.Http.Mapping; namespace Sonarr.Api.V3.Episodes { @@ -24,31 +20,31 @@ namespace Sonarr.Api.V3.Episodes { protected readonly IEpisodeService _episodeService; protected readonly ISeriesService _seriesService; - protected readonly IQualityUpgradableSpecification _qualityUpgradableSpecification; + protected readonly IUpgradableSpecification _upgradableSpecification; protected EpisodeModuleWithSignalR(IEpisodeService episodeService, ISeriesService seriesService, - IQualityUpgradableSpecification qualityUpgradableSpecification, + IUpgradableSpecification upgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster) : base(signalRBroadcaster) { _episodeService = episodeService; _seriesService = seriesService; - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = upgradableSpecification; GetResourceById = GetEpisode; } protected EpisodeModuleWithSignalR(IEpisodeService episodeService, ISeriesService seriesService, - IQualityUpgradableSpecification qualityUpgradableSpecification, + IUpgradableSpecification upgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster, string resource) : base(signalRBroadcaster, resource) { _episodeService = episodeService; _seriesService = seriesService; - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = upgradableSpecification; GetResourceById = GetEpisode; } @@ -75,7 +71,7 @@ namespace Sonarr.Api.V3.Episodes if (includeEpisodeFile && episode.EpisodeFileId != 0) { - resource.EpisodeFile = episode.EpisodeFile.Value.ToResource(series, _qualityUpgradableSpecification); + resource.EpisodeFile = episode.EpisodeFile.Value.ToResource(series, _upgradableSpecification); } if (includeImages) @@ -109,7 +105,7 @@ namespace Sonarr.Api.V3.Episodes if (includeEpisodeFile && episode.EpisodeFileId != 0) { - resource.EpisodeFile = episode.EpisodeFile.Value.ToResource(series, _qualityUpgradableSpecification); + resource.EpisodeFile = episode.EpisodeFile.Value.ToResource(series, _upgradableSpecification); } if (includeImages) diff --git a/src/Sonarr.Api.V3/History/HistoryModule.cs b/src/Sonarr.Api.V3/History/HistoryModule.cs index f4276746b..5551febb2 100644 --- a/src/Sonarr.Api.V3/History/HistoryModule.cs +++ b/src/Sonarr.Api.V3/History/HistoryModule.cs @@ -17,15 +17,15 @@ namespace Sonarr.Api.V3.History public class HistoryModule : SonarrRestModule<HistoryResource> { private readonly IHistoryService _historyService; - private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification; + private readonly IUpgradableSpecification _upgradableSpecification; private readonly IFailedDownloadService _failedDownloadService; public HistoryModule(IHistoryService historyService, - IQualityUpgradableSpecification qualityUpgradableSpecification, + IUpgradableSpecification upgradableSpecification, IFailedDownloadService failedDownloadService) { _historyService = historyService; - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = upgradableSpecification; _failedDownloadService = failedDownloadService; GetResourcePaged = GetHistory; @@ -50,7 +50,8 @@ namespace Sonarr.Api.V3.History if (model.Series != null) { - resource.QualityCutoffNotMet = _qualityUpgradableSpecification.CutoffNotMet(model.Series.Profile.Value, model.Quality); + resource.QualityCutoffNotMet = _upgradableSpecification.QualityCutoffNotMet(model.Series.Profile.Value, model.Quality); + resource.LanguageCutoffNotMet = _upgradableSpecification.LanguageCutoffNotMet(model.Series.LanguageProfile, model.Language); } return resource; diff --git a/src/Sonarr.Api.V3/History/HistoryResource.cs b/src/Sonarr.Api.V3/History/HistoryResource.cs index 85d421e37..3bfae7090 100644 --- a/src/Sonarr.Api.V3/History/HistoryResource.cs +++ b/src/Sonarr.Api.V3/History/HistoryResource.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using NzbDrone.Core.History; +using NzbDrone.Core.Languages; using NzbDrone.Core.Qualities; using Sonarr.Api.V3.Episodes; using Sonarr.Api.V3.Series; @@ -13,8 +14,10 @@ namespace Sonarr.Api.V3.History public int EpisodeId { get; set; } public int SeriesId { get; set; } public string SourceTitle { get; set; } + public Language Language { get; set; } public QualityModel Quality { get; set; } public bool QualityCutoffNotMet { get; set; } + public bool LanguageCutoffNotMet { get; set; } public DateTime Date { get; set; } public string DownloadId { get; set; } @@ -39,6 +42,7 @@ namespace Sonarr.Api.V3.History EpisodeId = model.EpisodeId, SeriesId = model.SeriesId, SourceTitle = model.SourceTitle, + Language = model.Language, Quality = model.Quality, //QualityCutoffNotMet Date = model.Date, diff --git a/src/Sonarr.Api.V3/Indexers/ReleaseResource.cs b/src/Sonarr.Api.V3/Indexers/ReleaseResource.cs index 62296ab8e..bf28791d0 100644 --- a/src/Sonarr.Api.V3/Indexers/ReleaseResource.cs +++ b/src/Sonarr.Api.V3/Indexers/ReleaseResource.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Indexers; -using NzbDrone.Core.Parser; +using NzbDrone.Core.Languages; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; using Sonarr.Http.REST; diff --git a/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileModule.cs b/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileModule.cs new file mode 100644 index 000000000..b424e724e --- /dev/null +++ b/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileModule.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using FluentValidation; +using NzbDrone.Core.Profiles.Languages; +using Sonarr.Http; + +namespace Sonarr.Api.V3.Profiles.Language +{ + public class LanguageProfileModule : SonarrRestModule<LanguageProfileResource> + { + private readonly ILanguageProfileService _profileService; + + public LanguageProfileModule(ILanguageProfileService profileService) + { + _profileService = profileService; + SharedValidator.RuleFor(c => c.Name).NotEmpty(); + SharedValidator.RuleFor(c => c.Cutoff).NotNull(); + SharedValidator.RuleFor(c => c.Languages).MustHaveAllowedLanguage(); + + GetResourceAll = GetAll; + GetResourceById = GetById; + UpdateResource = Update; + CreateResource = Create; + DeleteResource = DeleteProfile; + } + + private int Create(LanguageProfileResource resource) + { + var model = resource.ToModel(); + model = _profileService.Add(model); + return model.Id; + } + + private void DeleteProfile(int id) + { + _profileService.Delete(id); + } + + private void Update(LanguageProfileResource resource) + { + var model = resource.ToModel(); + + _profileService.Update(model); + } + + private LanguageProfileResource GetById(int id) + { + return _profileService.Get(id).ToResource(); + } + + private List<LanguageProfileResource> GetAll() + { + var profiles = _profileService.All().ToResource(); + + return profiles; + } + } +} \ No newline at end of file diff --git a/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileResource.cs b/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileResource.cs new file mode 100644 index 000000000..4a7716202 --- /dev/null +++ b/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileResource.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.Profiles.Languages; +using Sonarr.Http.REST; + +namespace Sonarr.Api.V3.Profiles.Language +{ + public class LanguageProfileResource : RestResource + { + public string Name { get; set; } + public NzbDrone.Core.Languages.Language Cutoff { get; set; } + public List<LanguageProfileItemResource> Languages { get; set; } + } + + public class LanguageProfileItemResource : RestResource + { + public NzbDrone.Core.Languages.Language Language { get; set; } + public bool Allowed { get; set; } + } + + public static class LanguageProfileResourceMapper + { + public static LanguageProfileResource ToResource(this LanguageProfile model) + { + if (model == null) return null; + + return new LanguageProfileResource + { + Id = model.Id, + Name = model.Name, + Cutoff = model.Cutoff, + Languages = model.Languages.ConvertAll(ToResource) + }; + } + + public static LanguageProfileItemResource ToResource(this LanguageProfileItem model) + { + if (model == null) return null; + + return new LanguageProfileItemResource + { + Language = model.Language, + Allowed = model.Allowed + }; + } + + public static LanguageProfile ToModel(this LanguageProfileResource resource) + { + if (resource == null) return null; + + return new LanguageProfile + { + Id = resource.Id, + Name = resource.Name, + Cutoff = (NzbDrone.Core.Languages.Language)resource.Cutoff.Id, + Languages = resource.Languages.ConvertAll(ToModel) + }; + } + + public static LanguageProfileItem ToModel(this LanguageProfileItemResource resource) + { + if (resource == null) return null; + + return new LanguageProfileItem + { + Language = (NzbDrone.Core.Languages.Language)resource.Language.Id, + Allowed = resource.Allowed + }; + } + + public static List<LanguageProfileResource> ToResource(this IEnumerable<LanguageProfile> models) + { + return models.Select(ToResource).ToList(); + } + } +} diff --git a/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileSchemaModule.cs b/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileSchemaModule.cs new file mode 100644 index 000000000..d37e42173 --- /dev/null +++ b/src/Sonarr.Api.V3/Profiles/Language/LanguageProfileSchemaModule.cs @@ -0,0 +1,37 @@ +using System.Linq; +using NzbDrone.Core.Profiles.Languages; +using Sonarr.Http; + +namespace Sonarr.Api.V3.Profiles.Language +{ + public class LanguageProfileSchemaModule : SonarrRestModule<LanguageProfileResource> + { + + public LanguageProfileSchemaModule() + : base("/languageprofile/schema") + { + GetResourceSingle = GetAll; + } + + private LanguageProfileResource GetAll() + { + var orderedLanguages = NzbDrone.Core.Languages.Language.All + .Where(l => l != NzbDrone.Core.Languages.Language.Unknown) + .OrderByDescending(l => l.Name) + .ToList(); + + orderedLanguages.Insert(0, NzbDrone.Core.Languages.Language.Unknown); + + var languages = orderedLanguages.Select(v => new LanguageProfileItem {Language = v, Allowed = false}) + .ToList(); + + var profile = new LanguageProfile + { + Cutoff = NzbDrone.Core.Languages.Language.Unknown, + Languages = languages + }; + + return profile.ToResource(); + } + } +} \ No newline at end of file diff --git a/src/Sonarr.Api.V3/Profiles/Language/LanguageValidator.cs b/src/Sonarr.Api.V3/Profiles/Language/LanguageValidator.cs new file mode 100644 index 000000000..ab84b472b --- /dev/null +++ b/src/Sonarr.Api.V3/Profiles/Language/LanguageValidator.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Linq; +using FluentValidation; +using FluentValidation.Validators; + +namespace Sonarr.Api.V3.Profiles.Language +{ + public static class LanguageValidation + { + public static IRuleBuilderOptions<T, IList<LanguageProfileItemResource>> MustHaveAllowedLanguage<T>(this IRuleBuilder<T, IList<LanguageProfileItemResource>> ruleBuilder) + { + ruleBuilder.SetValidator(new NotEmptyValidator(null)); + + return ruleBuilder.SetValidator(new LanguageValidator<T>()); + } + } + + + public class LanguageValidator<T> : PropertyValidator + { + public LanguageValidator() + : base("Must have at least one allowed language") + { + } + + protected override bool IsValid(PropertyValidatorContext context) + { + var list = context.PropertyValue as IList<LanguageProfileItemResource>; + + if (list == null) + { + return false; + } + + if (!list.Any(c => c.Allowed)) + { + return false; + } + + return true; + } + } +} diff --git a/src/Sonarr.Api.V3/Profiles/Languages/LanguageModule.cs b/src/Sonarr.Api.V3/Profiles/Languages/LanguageModule.cs deleted file mode 100644 index 9d5a09ff0..000000000 --- a/src/Sonarr.Api.V3/Profiles/Languages/LanguageModule.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NzbDrone.Core.Parser; -using Sonarr.Http; - -namespace Sonarr.Api.V3.Profiles.Languages -{ - public class LanguageModule : SonarrRestModule<LanguageResource> - { - public LanguageModule() - { - GetResourceAll = GetAll; - GetResourceById = GetById; - } - - private LanguageResource GetById(int id) - { - var language = (Language)id; - - return new LanguageResource - { - Id = (int)language, - Name = language.ToString() - }; - } - - private List<LanguageResource> GetAll() - { - return ((Language[])Enum.GetValues(typeof (Language))) - .Select(l => new LanguageResource - { - Id = (int) l, - Name = l.ToString() - }) - .OrderBy(l => l.Name) - .ToList(); - } - } -} \ No newline at end of file diff --git a/src/Sonarr.Api.V3/Profiles/Languages/LanguageResource.cs b/src/Sonarr.Api.V3/Profiles/Languages/LanguageResource.cs deleted file mode 100644 index a4452ae29..000000000 --- a/src/Sonarr.Api.V3/Profiles/Languages/LanguageResource.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Newtonsoft.Json; -using Sonarr.Http.REST; - -namespace Sonarr.Api.V3.Profiles.Languages -{ - public class LanguageResource : RestResource - { - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)] - public int Id { get; set; } - public string Name { get; set; } - public string NameLower { get { return Name.ToLowerInvariant(); } } - } -} \ No newline at end of file diff --git a/src/Sonarr.Api.V3/Profiles/Quality/QualityProfileModule.cs b/src/Sonarr.Api.V3/Profiles/Quality/QualityProfileModule.cs index 359c91306..12588af1f 100644 --- a/src/Sonarr.Api.V3/Profiles/Quality/QualityProfileModule.cs +++ b/src/Sonarr.Api.V3/Profiles/Quality/QualityProfileModule.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using FluentValidation; -using NzbDrone.Core.Profiles; -using NzbDrone.Core.Validation; +using NzbDrone.Core.Profiles.Qualities; using Sonarr.Http; namespace Sonarr.Api.V3.Profiles.Quality @@ -16,7 +15,6 @@ namespace Sonarr.Api.V3.Profiles.Quality SharedValidator.RuleFor(c => c.Name).NotEmpty(); SharedValidator.RuleFor(c => c.Cutoff).NotNull(); SharedValidator.RuleFor(c => c.Items).MustHaveAllowedQuality(); - SharedValidator.RuleFor(c => c.Language).ValidLanguage(); GetResourceAll = GetAll; GetResourceById = GetById; diff --git a/src/Sonarr.Api.V3/Profiles/Quality/QualityProfileResource.cs b/src/Sonarr.Api.V3/Profiles/Quality/QualityProfileResource.cs index 809b286a0..8a72ab313 100644 --- a/src/Sonarr.Api.V3/Profiles/Quality/QualityProfileResource.cs +++ b/src/Sonarr.Api.V3/Profiles/Quality/QualityProfileResource.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Linq; -using NzbDrone.Core.Parser; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using Sonarr.Http.REST; namespace Sonarr.Api.V3.Profiles.Quality @@ -11,7 +10,6 @@ namespace Sonarr.Api.V3.Profiles.Quality public string Name { get; set; } public NzbDrone.Core.Qualities.Quality Cutoff { get; set; } public List<QualityProfileQualityItemResource> Items { get; set; } - public Language Language { get; set; } } public class QualityProfileQualityItemResource : RestResource @@ -33,7 +31,6 @@ namespace Sonarr.Api.V3.Profiles.Quality Name = model.Name, Cutoff = model.Cutoff, Items = model.Items.ConvertAll(ToResource), - Language = model.Language }; } @@ -58,8 +55,7 @@ namespace Sonarr.Api.V3.Profiles.Quality Name = resource.Name, Cutoff = (NzbDrone.Core.Qualities.Quality)resource.Cutoff.Id, - Items = resource.Items.ConvertAll(ToModel), - Language = resource.Language + Items = resource.Items.ConvertAll(ToModel) }; } diff --git a/src/Sonarr.Api.V3/Profiles/Quality/QualityProfileSchemaModule.cs b/src/Sonarr.Api.V3/Profiles/Quality/QualityProfileSchemaModule.cs index 4a02612bf..afcdfd82b 100644 --- a/src/Sonarr.Api.V3/Profiles/Quality/QualityProfileSchemaModule.cs +++ b/src/Sonarr.Api.V3/Profiles/Quality/QualityProfileSchemaModule.cs @@ -1,6 +1,5 @@ using System.Linq; -using NzbDrone.Core.Parser; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using Sonarr.Http; @@ -28,7 +27,6 @@ namespace Sonarr.Api.V3.Profiles.Quality var qualityProfile = new Profile(); qualityProfile.Cutoff = NzbDrone.Core.Qualities.Quality.Unknown; qualityProfile.Items = items; - profile.Language = Language.English; return qualityProfile.ToResource(); } diff --git a/src/Sonarr.Api.V3/Series/SeriesEditorModule.cs b/src/Sonarr.Api.V3/Series/SeriesEditorModule.cs index f8ac84e04..dec3f9267 100644 --- a/src/Sonarr.Api.V3/Series/SeriesEditorModule.cs +++ b/src/Sonarr.Api.V3/Series/SeriesEditorModule.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Nancy; using NzbDrone.Common.Extensions; @@ -41,6 +41,11 @@ namespace Sonarr.Api.V3.Series series.ProfileId = resource.QualityProfileId.Value; } + if (resource.LanguageProfileId.HasValue) + { + series.LanguageProfileId = resource.LanguageProfileId.Value; + } + if (resource.SeriesType.HasValue) { series.SeriesType = resource.SeriesType.Value; diff --git a/src/Sonarr.Api.V3/Series/SeriesEditorResource.cs b/src/Sonarr.Api.V3/Series/SeriesEditorResource.cs index 07244de21..a9828a282 100644 --- a/src/Sonarr.Api.V3/Series/SeriesEditorResource.cs +++ b/src/Sonarr.Api.V3/Series/SeriesEditorResource.cs @@ -8,6 +8,7 @@ namespace Sonarr.Api.V3.Series public List<int> SeriesIds { get; set; } public bool? Monitored { get; set; } public int? QualityProfileId { get; set; } + public int? LanguageProfileId { get; set; } public SeriesTypes? SeriesType { get; set; } public bool? SeasonFolder { get; set; } public string RootFolderPath { get; set; } diff --git a/src/Sonarr.Api.V3/Series/SeriesModule.cs b/src/Sonarr.Api.V3/Series/SeriesModule.cs index f133bb9e3..d6c70cafe 100644 --- a/src/Sonarr.Api.V3/Series/SeriesModule.cs +++ b/src/Sonarr.Api.V3/Series/SeriesModule.cs @@ -55,7 +55,8 @@ namespace Sonarr.Api.V3.Series DroneFactoryValidator droneFactoryValidator, SeriesAncestorValidator seriesAncestorValidator, SystemFolderValidator systemFolderValidator, - ProfileExistsValidator profileExistsValidator + ProfileExistsValidator profileExistsValidator, + LanguageProfileExistsValidator languageProfileExistsValidator ) : base(signalRBroadcaster) { @@ -88,6 +89,7 @@ namespace Sonarr.Api.V3.Series .When(s => !s.Path.IsNullOrWhiteSpace()); SharedValidator.RuleFor(s => s.QualityProfileId).SetValidator(profileExistsValidator); + SharedValidator.RuleFor(s => s.LanguageProfileId).SetValidator(languageProfileExistsValidator); PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace()); PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace()); diff --git a/src/Sonarr.Api.V3/Sonarr.Api.V3.csproj b/src/Sonarr.Api.V3/Sonarr.Api.V3.csproj index b8d75dc35..22229c496 100644 --- a/src/Sonarr.Api.V3/Sonarr.Api.V3.csproj +++ b/src/Sonarr.Api.V3/Sonarr.Api.V3.csproj @@ -167,12 +167,6 @@ <Compile Include="Series\SeriesImportModule.cs" /> <Compile Include="SonarrV3FeedModule.cs" /> <Compile Include="SonarrV3Module.cs" /> - <Compile Include="Profiles\Languages\LanguageModule.cs" /> - <Compile Include="Profiles\Languages\LanguageResource.cs" /> - <Compile Include="Profiles\ProfileModule.cs" /> - <Compile Include="Profiles\ProfileResource.cs" /> - <Compile Include="Profiles\ProfileSchemaModule.cs" /> - <Compile Include="Profiles\ProfileValidation.cs" /> <Compile Include="Profiles\Quality\QualityProfileModule.cs" /> <Compile Include="Profiles\Quality\QualityProfileResource.cs" /> <Compile Include="Profiles\Quality\QualityProfileSchemaModule.cs" /> diff --git a/src/Sonarr.Api.V3/Wanted/CutoffModule.cs b/src/Sonarr.Api.V3/Wanted/CutoffModule.cs index 2210fbe5c..5cf59f670 100644 --- a/src/Sonarr.Api.V3/Wanted/CutoffModule.cs +++ b/src/Sonarr.Api.V3/Wanted/CutoffModule.cs @@ -16,9 +16,9 @@ namespace Sonarr.Api.V3.Wanted public CutoffModule(IEpisodeCutoffService episodeCutoffService, IEpisodeService episodeService, ISeriesService seriesService, - IQualityUpgradableSpecification qualityUpgradableSpecification, + IUpgradableSpecification upgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster) - : base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/cutoff") + : base(episodeService, seriesService, upgradableSpecification, signalRBroadcaster, "wanted/cutoff") { _episodeCutoffService = episodeCutoffService; GetResourcePaged = GetCutoffUnmetEpisodes; diff --git a/src/Sonarr.Api.V3/Wanted/MissingModule.cs b/src/Sonarr.Api.V3/Wanted/MissingModule.cs index d14d247e8..310b0c742 100644 --- a/src/Sonarr.Api.V3/Wanted/MissingModule.cs +++ b/src/Sonarr.Api.V3/Wanted/MissingModule.cs @@ -13,9 +13,9 @@ namespace Sonarr.Api.V3.Wanted { public MissingModule(IEpisodeService episodeService, ISeriesService seriesService, - IQualityUpgradableSpecification qualityUpgradableSpecification, + IUpgradableSpecification upgradableSpecification, IBroadcastSignalRMessage signalRBroadcaster) - : base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/missing") + : base(episodeService, seriesService, upgradableSpecification, signalRBroadcaster, "wanted/missing") { GetResourcePaged = GetMissingEpisodes; }