New: Language Profiles

Closes #274
pull/2789/head
vawen 9 years ago committed by Taloth Saldono
parent 7297c1b8e4
commit 068ea1e934

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

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

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

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

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

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

@ -17,15 +17,15 @@ namespace NzbDrone.Api.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;
@ -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;

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

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

@ -112,6 +112,8 @@
<Compile Include="Profiles\Delay\DelayProfileModule.cs" />
<Compile Include="Profiles\Delay\DelayProfileResource.cs" />
<Compile Include="ProviderModuleBase.cs" />
<Compile Include="Profiles\ProfileModule.cs" />
<Compile Include="Profiles\ProfileResource.cs" />
<Compile Include="Queue\QueueActionModule.cs" />
<Compile Include="RemotePathMappings\RemotePathMappingModule.cs" />
<Compile Include="RemotePathMappings\RemotePathMappingResource.cs" />
@ -164,8 +166,6 @@
<Compile Include="Profiles\Languages\LanguageModule.cs" />
<Compile Include="Profiles\Languages\LanguageResource.cs" />
<Compile Include="Profiles\LegacyProfileModule.cs" />
<Compile Include="Profiles\ProfileModule.cs" />
<Compile Include="Profiles\ProfileResource.cs" />
<Compile Include="Profiles\ProfileSchemaModule.cs" />
<Compile Include="Profiles\ProfileValidation.cs" />
<Compile Include="Properties\AssemblyInfo.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

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

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

@ -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<ProfileResource> { profile.ToResource() };
}

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

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

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

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

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

@ -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<EpisodeFile>.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<Episode>.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<Episode>.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<History.History>.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<History.History>.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));

@ -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<Series>.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<IDatabase>();
var DataMapper = db.GetDataMapper();
var episodes = DataMapper.Query<Episode>()
.Join<Episode, Series>(Marr.Data.QGen.JoinType.Inner, v => v.Series, (l, r) => l.SeriesId == r.Id)
.Join<Series, LanguageProfile>(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);
}
}
}
}

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

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

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

@ -23,7 +23,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[SetUp]
public void Setup()
{
Mocker.Resolve<QualityUpgradableSpecification>();
Mocker.Resolve<UpgradableSpecification>();
_subject = Mocker.Resolve<AnimeVersionUpgradeSpecification>();
_episodeFile = new EpisodeFile

@ -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<QualityUpgradableSpecification>
public class CutoffSpecificationFixture : CoreTest<UpgradableSpecification>
{
[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();
}
}
}

@ -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<LanguageProfile> (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<Profile>(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<LanguageSpecification>().IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();
}
[Test]
public void should_return_false_if_language_is_french()
{
WithFrenchRelease();
Mocker.Resolve<LanguageSpecification>().IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();
}
[Test]
public void should_return_true_if_language_is_spanish()
{
WithSpanishRelease();
Mocker.Resolve<LanguageSpecification>().IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
}
}

@ -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<Episode> episodes, QualityModel quality, int age = 0, long size = 0, DownloadProtocol downloadProtocol = DownloadProtocol.Usenet)
private RemoteEpisode GivenRemoteEpisode(List<Episode> 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<Episode>();
remoteEpisode.Episodes.AddRange(episodes);
@ -49,7 +53,15 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
remoteEpisode.Release.DownloadProtocol = downloadProtocol;
remoteEpisode.Series = Builder<Series>.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<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p, new Revision(version: 1)));
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p, new Revision(version: 2)));
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p, new Revision(version: 1)), Language.English);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p, new Revision(version: 2)), Language.English);
var decisions = new List<DownloadDecision>();
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<Episode> { GivenEpisode(1) }, new QualityModel(Quality.SDTV));
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.SDTV), Language.English);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English);
var decisions = new List<DownloadDecision>();
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<Episode> { GivenEpisode(2) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(2) }, new QualityModel(Quality.HDTV720p), Language.English);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English);
var decisions = new List<DownloadDecision>();
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<Episode> { GivenEpisode(2), GivenEpisode(3) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1), GivenEpisode(2) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(2), GivenEpisode(3) }, new QualityModel(Quality.HDTV720p), Language.English);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1), GivenEpisode(2) }, new QualityModel(Quality.HDTV720p), Language.English);
var decisions = new List<DownloadDecision>();
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<Episode> { GivenEpisode(1) }, new QualityModel(Quality.SDTV), size: 100.Megabytes(), age: 1);
var remoteEpisodeHdSmallOld = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), size: 1200.Megabytes(), age: 1000);
var remoteEpisodeSmallYoung = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), size: 1250.Megabytes(), age: 10);
var remoteEpisodeHdLargeYoung = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), size: 3000.Megabytes(), age: 1);
var remoteEpisodeSd = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.SDTV), Language.English, size: 100.Megabytes(), age: 1);
var remoteEpisodeHdSmallOld = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, size: 1200.Megabytes(), age: 1000);
var remoteEpisodeSmallYoung = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, size: 1250.Megabytes(), age: 10);
var remoteEpisodeHdLargeYoung = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, size: 3000.Megabytes(), age: 1);
var decisions = new List<DownloadDecision>();
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<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), age: 10);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), age: 5);
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, age: 10);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, age: 5);
var decisions = new List<DownloadDecision>();
@ -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<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), size: 500.Megabytes());
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), size: 500.Megabytes());
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, size: 500.Megabytes());
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, size: 500.Megabytes());
remoteEpisode1.Episodes = new List<Episode>();
@ -174,8 +186,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
GivenPreferredDownloadProtocol(DownloadProtocol.Usenet);
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), downloadProtocol: DownloadProtocol.Torrent);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), downloadProtocol: DownloadProtocol.Usenet);
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, downloadProtocol: DownloadProtocol.Torrent);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, downloadProtocol: DownloadProtocol.Usenet);
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisode1));
@ -190,8 +202,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
GivenPreferredDownloadProtocol(DownloadProtocol.Torrent);
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), downloadProtocol: DownloadProtocol.Torrent);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), downloadProtocol: DownloadProtocol.Usenet);
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, downloadProtocol: DownloadProtocol.Torrent);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English, downloadProtocol: DownloadProtocol.Usenet);
var decisions = new List<DownloadDecision>();
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<Episode> { GivenEpisode(1), GivenEpisode(2) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1), GivenEpisode(2) }, new QualityModel(Quality.HDTV720p), Language.English);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { 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<Episode> { GivenEpisode(1), GivenEpisode(2) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1), GivenEpisode(2) }, new QualityModel(Quality.HDTV720p), Language.English);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { 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<Episode> { GivenEpisode(1), GivenEpisode(2) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1), GivenEpisode(2) }, new QualityModel(Quality.HDTV720p), Language.English);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English);
var decisions = new List<DownloadDecision>();
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<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { 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<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { 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<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { 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<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { 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<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p));
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { 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<Episode> { GivenEpisode(1) }, new QualityModel(Quality.Bluray1080p));
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.SDTV));
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.Bluray1080p), Language.English);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { 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<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.French);
var remoteEpisode3 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.German);
var decisions = new List<DownloadDecision>();
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<Episode> { GivenEpisode(1) }, new QualityModel(Quality.SDTV), Language.French);
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.German);
var decisions = new List<DownloadDecision>();
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);
}
}
}

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

@ -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<QualityUpgradableSpecification>
public class QualityUpgradeSpecificationFixture : CoreTest<UpgradableSpecification>
{
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(),
};
var langProfile = new LanguageProfile
{
Languages = LanguageFixture.GetDefaultLanguages(),
Cutoff = Language.English
};
Subject.IsUpgradable(profile, new QualityModel(Quality.DVD, new Revision(version: 2)), new QualityModel(Quality.DVD, new Revision(version: 1)))
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();
}
}

@ -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<QualityUpgradableSpecification>();
Mocker.Resolve<UpgradableSpecification>();
_series = Builder<Series>.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<Episode>.CreateNew()
@ -51,7 +61,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_remoteEpisode = Builder<RemoteEpisode>.CreateNew()
.With(r => r.Series = _series)
.With(r => r.Episodes = new List<Episode> { _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<RemoteEpisode>.CreateNew()
.With(r => r.Series = _series)
.With(r => r.Episodes = new List<Episode> { _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<RemoteEpisode>.CreateNew()
.With(r => r.Series = _series)
.With(r => r.Episodes = new List<Episode> { _episode })
.With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo
{
Quality = new QualityModel(Quality.SDTV),
Language = Language.English
})
.Build();
GivenQueue(new List<RemoteEpisode> { 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<RemoteEpisode>.CreateNew()
.With(r => r.Series = _series)
.With(r => r.Episodes = new List<Episode> { _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<RemoteEpisode>.CreateNew()
.With(r => r.Series = _series)
.With(r => r.Episodes = new List<Episode> { _episode })
.With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo
{
Quality = new QualityModel(Quality.DVD),
Language = Language.English,
})
.Build();
GivenQueue(new List<RemoteEpisode> { 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> { _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> { _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> { _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> { _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> { _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> { _episode })
.With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo
{
Quality = new QualityModel(Quality.HDTV720p)
Quality = new QualityModel(Quality.HDTV720p),
Language = Language.Spanish
})
.Build();

@ -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<DelaySpecification>
{
private Profile _profile;
private LanguageProfile _langProfile;
private DelayProfile _delayProfile;
private RemoteEpisode _remoteEpisode;
@ -34,12 +37,16 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
_profile = Builder<Profile>.CreateNew()
.Build();
_langProfile = Builder<LanguageProfile>.CreateNew()
.Build();
_delayProfile = Builder<DelayProfile>.CreateNew()
.With(d => d.PreferredProtocol = DownloadProtocol.Usenet)
.Build();
var series = Builder<Series>.CreateNew()
.With(s => s.Profile = _profile)
.With(s => s.LanguageProfile = _langProfile)
.Build();
_remoteEpisode = Builder<RemoteEpisode>.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<RemoteEpisode>());
}
private void GivenExistingFile(QualityModel quality)
private void GivenExistingFile(QualityModel quality, Language language)
{
_remoteEpisode.Episodes.First().EpisodeFileId = 1;
_remoteEpisode.Episodes.First().EpisodeFile = new LazyLoaded<EpisodeFile>(new EpisodeFile
{
Quality = quality
Quality = quality,
Language = language
});
}
private void GivenUpgradeForExistingFile()
{
Mocker.GetMock<IQualityUpgradableSpecification>()
.Setup(s => s.IsUpgradable(It.IsAny<Profile>(), It.IsAny<QualityModel>(), It.IsAny<QualityModel>()))
Mocker.GetMock<IUpgradableSpecification>()
.Setup(s => s.IsUpgradable(It.IsAny<Profile>(), It.IsAny<LanguageProfile>(), It.IsAny<QualityModel>(), It.IsAny<Language>(), It.IsAny<QualityModel>(), It.IsAny<Language>()))
.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<IQualityUpgradableSpecification>()
Mocker.GetMock<IUpgradableSpecification>()
.Setup(s => s.IsRevisionUpgrade(It.IsAny<QualityModel>(), It.IsAny<QualityModel>()))
.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<IQualityUpgradableSpecification>()
Mocker.GetMock<IUpgradableSpecification>()
.Setup(s => s.IsRevisionUpgrade(It.IsAny<QualityModel>(), It.IsAny<QualityModel>()))
.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;

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

@ -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<QualityModel, Language> _upgradableQuality;
private Tuple<QualityModel, Language> _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<QualityUpgradableSpecification>();
Mocker.Resolve<UpgradableSpecification>();
_upgradeHistory = Mocker.Resolve<HistorySpecification>();
var singleEpisodeList = new List<Episode> { new Episode { Id = FIRST_EPISODE_ID, SeasonNumber = 12, EpisodeNumber = 3 } };
@ -46,34 +48,36 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
_fakeSeries = Builder<Series>.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<QualityModel, Language> (new QualityModel(Quality.SDTV, new Revision(version: 1)), Language.English);
_notupgradableQuality = new Tuple<QualityModel, Language> (new QualityModel(Quality.HDTV1080p, new Revision(version: 2)), Language.English);
Mocker.GetMock<IConfigService>()
.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<QualityModel, Language> quality, DateTime date, HistoryEventType eventType)
{
Mocker.GetMock<IHistoryService>().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<QualityModel, Language>(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<QualityModel, Language>(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<QualityModel, Language>(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<QualityModel, Language>(new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)), Language.Spanish);
GivenMostRecentForEpisode(FIRST_EPISODE_ID, "test", _upgradableQuality, DateTime.UtcNow.AddDays(-100), HistoryEventType.Grabbed);

@ -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<QualityUpgradableSpecification>();
Mocker.Resolve<UpgradableSpecification>();
_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 };

@ -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<QualityUpgradableSpecification>();
Mocker.Resolve<UpgradableSpecification>();
_upgradeDisk = Mocker.Resolve<UpgradeDiskSpecification>();
_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<Episode> { new Episode { EpisodeFile = _firstFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = null } };
var doubleEpisodeList = new List<Episode> { 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<Series>.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
};
}

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

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

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

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

@ -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) };
}
[Test]
public void should_return_null_if_no_history()
{
Mocker.GetMock<IHistoryRepository>()
.Setup(v => v.GetBestQualityInHistory(2))
.Returns(new List<QualityModel>());
var quality = Subject.GetBestQualityInHistory(_profile, 2);
quality.Should().BeNull();
}
[Test]
public void should_return_best_quality()
{
Mocker.GetMock<IHistoryRepository>()
.Setup(v => v.GetBestQualityInHistory(2))
.Returns(new List<QualityModel> { 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<IHistoryRepository>()
.Setup(v => v.GetBestQualityInHistory(2))
.Returns(new List<QualityModel> { new QualityModel(Quality.DVD), new QualityModel(Quality.Bluray1080p) });
var quality = Subject.GetBestQualityInHistory(_profileCustom, 2);
quality.Should().Be(new QualityModel(Quality.DVD));
_profile = new Profile
{
Cutoff = Quality.WEBDL720p,
Items = QualityFixture.GetDefaultQualities(),
};
_profileCustom = new Profile
{
Cutoff = Quality.WEBDL720p,
Items = QualityFixture.GetDefaultQualities(Quality.DVD),
};
_languageProfile = new LanguageProfile
{
Cutoff = Language.Spanish,
Languages = Languages.LanguageFixture.GetDefaultLanguages()
};
}
[Test]

@ -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<LanguageProfileItem> GetDefaultLanguages(params Language[] allowed)
{
var languages = new List<Language>
{
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;
}
}
}

@ -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<LanguageProfileRepository, LanguageProfile>
{
[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);
}
}
}

@ -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<LanguageProfileService>
{
[Test]
public void init_should_add_default_profiles()
{
Subject.Handle(new ApplicationStartedEvent());
Mocker.GetMock<ILanguageProfileRepository>()
.Verify(v => v.Insert(It.IsAny<LanguageProfile>()), 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<ILanguageProfileRepository>()
.Setup(s => s.All())
.Returns(Builder<LanguageProfile>.CreateListOfSize(2).Build().ToList());
Subject.Handle(new ApplicationStartedEvent());
Mocker.GetMock<ILanguageProfileRepository>()
.Verify(v => v.Insert(It.IsAny<LanguageProfile>()), Times.Never());
}
[Test]
public void should_not_be_able_to_delete_profile_if_assigned_to_series()
{
var seriesList = Builder<Series>.CreateListOfSize(3)
.Random(1)
.With(c => c.LanguageProfileId = 2)
.Build().ToList();
Mocker.GetMock<ISeriesService>().Setup(c => c.GetAllSeries()).Returns(seriesList);
Assert.Throws<LanguageProfileInUseException>(() => Subject.Delete(2));
Mocker.GetMock<ILanguageProfileRepository>().Verify(c => c.Delete(It.IsAny<int>()), Times.Never());
}
[Test]
public void should_delete_profile_if_not_assigned_to_series()
{
var seriesList = Builder<Series>.CreateListOfSize(3)
.All()
.With(c => c.LanguageProfileId = 2)
.Build().ToList();
Mocker.GetMock<ISeriesService>().Setup(c => c.GetAllSeries()).Returns(seriesList);
Subject.Delete(1);
Mocker.GetMock<ILanguageProfileRepository>().Verify(c => c.Delete(1), Times.Once());
}
}
}

@ -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<AggregateLanguage>
{
private LocalEpisode _localEpisode;
[SetUp]
public void Setup()
{
_localEpisode = Builder<LocalEpisode>.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);
}
}
}

@ -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<Series>.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<Episode> { 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<string> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi".AsOsAgnostic() });
GivenVideoFiles(new List<string> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.Spanish.XviD-OSiTV.avi".AsOsAgnostic() });
}
private void GivenSpecifications(params Mock<IImportDecisionEngineSpecification>[] 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);

@ -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<Series>.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,26 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
.With(e => e.EpisodeFile = new LazyLoaded<EpisodeFile>(
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();
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<Episode>.CreateListOfSize(1)
.All()
.With(e => e.EpisodeFileId = 1)
.With(e => e.EpisodeFile = new LazyLoaded<EpisodeFile>(
new EpisodeFile
{
Quality = new QualityModel(Quality.HDTV720p, new Revision(version: 1)),
Language = Language.English
}))
.Build()
.ToList();
@ -78,6 +108,25 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
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<Episode>.CreateListOfSize(1)
.All()
.With(e => e.EpisodeFileId = 1)
.With(e => e.EpisodeFile = new LazyLoaded<EpisodeFile>(
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<Episode>.CreateListOfSize(2)
.All()
.With(e => e.EpisodeFileId = 1)
.With(e => e.EpisodeFile = new LazyLoaded<EpisodeFile>(
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<Episode>.CreateListOfSize(2)
.All()
.With(e => e.EpisodeFileId = 1)
.With(e => e.EpisodeFile = new LazyLoaded<EpisodeFile>(
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()
{

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

@ -301,6 +301,7 @@
<Compile Include="Instrumentation\DatabaseTargetFixture.cs" />
<Compile Include="JobTests\JobRepositoryFixture.cs" />
<Compile Include="JobTests\TestJobs.cs" />
<Compile Include="Languages\LanguageFixture.cs" />
<Compile Include="MediaCoverTests\CoverExistsSpecificationFixture.cs" />
<Compile Include="MediaCoverTests\ImageResizerFixture.cs" />
<Compile Include="MediaCoverTests\MediaCoverServiceFixture.cs" />
@ -309,6 +310,7 @@
<Compile Include="MediaFiles\DownloadedEpisodesImportServiceFixture.cs" />
<Compile Include="MediaFiles\EpisodeFileMovingServiceTests\MoveEpisodeFileFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\AggregateEpisodesFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\AggregateLanguageFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\AggregateQualityFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\Augmenters\Quality\AugmentQualityFromMediaInfoFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportDecisionMakerFixture.cs" />
@ -392,6 +394,8 @@
<Compile Include="ParserTests\SceneCheckerFixture.cs" />
<Compile Include="Profiles\ProfileRepositoryFixture.cs" />
<Compile Include="Profiles\ProfileServiceFixture.cs" />
<Compile Include="Languages\LanguageProfileRepositoryFixture.cs" />
<Compile Include="Languages\LanguageProfileServiceFixture.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Providers\XemProxyFixture.cs" />
<Compile Include="ProviderTests\DiskProviderTests\ArchiveProviderFixture.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);
}
}
}

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

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

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

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

@ -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<Episode> _pagingSpec;
private List<QualitiesBelowCutoff> _qualitiesBelowCutoff;
private List<LanguagesBelowCutoff> _languagesBelowCutoff;
private List<Episode> _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<Series>.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<Series>.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<LanguagesBelowCutoff>
{
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<MediaFileRepository>();
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<Episode>.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);

@ -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<ProfileRepository>().Insert(profile);
Mocker.Resolve<LanguageProfileRepository>().Insert(langProfile);
var series = Builder<Series>.CreateNew().BuildNew();
series.ProfileId = profile.Id;
series.LanguageProfileId = langProfile.Id;
Subject.Insert(series);
StoredModel.Profile.Should().NotBeNull();
StoredModel.LanguageProfile.Should().NotBeNull();
}

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

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

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

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

@ -1 +1,97 @@
// This is a placeholder for migration 102
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();
}
}
}
}
}
}
}
}
}

@ -1 +1,180 @@
// This is a placeholder for migration 111
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<LanguageProfile111> GetDefaultLanguageProfiles()
{
var profiles = new List<LanguageProfile111>();
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<LanguageProfile111> 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<int> { profileId }
});
}
else
{
profiles = profiles.Select(p =>
{
if (p.Cutoff.Id == lang)
{
p.ProfileIds.Add(profileId);
}
return p;
}).ToList();
}
}
}
}
if (!thereAreProfiles)
{
return new List<LanguageProfile111>();
}
return profiles;
}
private List<Language> 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<int> ProfileIds { get; set; }
public string Name { get; set; }
public Language Cutoff { get; set; }
public List<LanguageProfileItem111> Languages { get; set; }
public LanguageProfile111 ()
{
ProfileIds = new List<int>();
}
}
private class LanguageProfileItem111
{
public Language Language { get; set; }
public bool Allowed { get; set; }
}
}
}

@ -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<Series>().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<EpisodeFile>().RegisterModel("EpisodeFiles")
.Ignore(f => f.Path)
@ -103,6 +106,7 @@ namespace NzbDrone.Core.Datastore
.Ignore(d => d.Weight);
Mapper.Entity<Profile>().RegisterModel("Profiles");
Mapper.Entity<LanguageProfile>().RegisterModel("LanguageProfiles");
Mapper.Entity<Log>().RegisterModel("Logs");
Mapper.Entity<NamingConfig>().RegisterModel("NamingConfig");
Mapper.Entity<SeasonStatistics>().MapResultSet();
@ -144,7 +148,9 @@ namespace NzbDrone.Core.Datastore
MapRepository.Instance.RegisterTypeConverter(typeof(QualityModel), new EmbeddedDocumentConverter(new QualityIntConverter()));
MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary<string, string>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<int>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(Language), new LanguageIntConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<string>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<LanguageProfileItem>), new EmbeddedDocumentConverter(new LanguageIntConverter()));
MapRepository.Instance.RegisterTypeConverter(typeof(ParsedEpisodeInfo), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(ReleaseInfo), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(HashSet<int>), new EmbeddedDocumentConverter());

@ -24,6 +24,7 @@ namespace NzbDrone.Core.DecisionEngine
var comparers = new List<CompareDelegate>
{
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 =>

@ -1,6 +1,7 @@
using System.Linq;
using System.Collections.Generic;
using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Languages;
namespace NzbDrone.Core.DecisionEngine
{

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

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

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

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

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

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

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

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

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

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

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

@ -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<string, string> Data { get; set; }
public TrackedDownload TrackedDownload { get; set; }
public Language Language { get; set; }
}
}

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

@ -1,5 +1,5 @@
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Languages;
namespace NzbDrone.Core.Extras.Subtitles
{

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

@ -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<string, string> Data { get; set; }
public Language Language { get; set; }
public string DownloadId { get; set; }

@ -11,7 +11,6 @@ namespace NzbDrone.Core.History
{
public interface IHistoryRepository : IBasicRepository<History>
{
List<QualityModel> GetBestQualityInHistory(int episodeId);
History MostRecentForEpisode(int episodeId);
List<History> FindByEpisodeId(int episodeId);
History MostRecentForDownloadId(string downloadId);
@ -31,14 +30,6 @@ namespace NzbDrone.Core.History
{
}
public List<QualityModel> 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)

@ -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<History> Paged(PagingSpec<History> pagingSpec);
History MostRecentForEpisode(int episodeId);
List<History> 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);

@ -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>();
series.ForEach(s =>
{
if (s.LanguageProfileId == 0 || languageProfiles.None(l => l.Id == s.LanguageProfileId))
{
s.LanguageProfileId = firstLangaugeProfile.Id;
seriesToUpdate.Add(s);
}
});
_seriesRepository.UpdateMany(seriesToUpdate);
}
}
}

@ -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<Language>
{
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<Language> All
{
get
{
return new List<Language>
{
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;
}
}
}

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

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
namespace NzbDrone.Core.Languages
{
public class LanguagesBelowCutoff
{
public int ProfileId { get; set; }
public IEnumerable<int> LanguageIds { get; set; }
public LanguagesBelowCutoff(int profileId, IEnumerable<int> languageIds)
{
ProfileId = profileId;
LanguageIds = languageIds;
}
}
}

@ -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<List<Episode>> Episodes { get; set; }
public LazyLoaded<Series> Series { get; set; }
public Language Language { get; set; }
public override string ToString()
{

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

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

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

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

@ -151,6 +151,10 @@
<Compile Include="CustomFilters\CustomFilterService.cs" />
<Compile Include="Datastore\Migration\126_add_custom_filters.cs" />
<Compile Include="Extras\Metadata\MetadataSectionType.cs" />
<Compile Include="Download\Aggregation\RemoteEpisodeAggregationService.cs" />
<Compile Include="Download\Aggregation\Aggregators\AggregatePreferredWordScore.cs" />
<Compile Include="Download\Aggregation\Aggregators\IAggregateRemoteEpisode.cs" />
<Compile Include="Housekeeping\Housekeepers\EnsureValidLanguageProfileId.cs" />
<Compile Include="Indexers\SeedConfigProvider.cs" />
<Compile Include="DataAugmentation\DailySeries\DailySeries.cs" />
<Compile Include="DataAugmentation\DailySeries\DailySeriesDataProxy.cs" />
@ -175,6 +179,7 @@
<Compile Include="Datastore\Converters\DoubleConverter.cs" />
<Compile Include="Datastore\Converters\EmbeddedDocumentConverter.cs" />
<Compile Include="Datastore\Converters\EnumIntConverter.cs" />
<Compile Include="Datastore\Converters\LanguageIntConverter.cs" />
<Compile Include="Datastore\Converters\TimeSpanConverter.cs" />
<Compile Include="Datastore\Converters\Int32Converter.cs" />
<Compile Include="Datastore\Converters\GuidConverter.cs" />
@ -268,7 +273,6 @@
<Compile Include="Datastore\Migration\069_quality_proper.cs" />
<Compile Include="Datastore\Migration\070_delay_profile.cs" />
<Compile Include="Datastore\Migration\116_disable_nyaa.cs" />
<Compile Include="Datastore\Migration\102_add_language_to_episodeFiles_history_and_blacklist.cs" />
<Compile Include="Datastore\Migration\113_consolidate_indexer_baseurl.cs" />
<Compile Include="Datastore\Migration\114_rename_indexer_status_id.cs" />
<Compile Include="Datastore\Migration\112_added_regex_to_scenemapping.cs" />
@ -312,6 +316,7 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="Datastore\Migration\105_rename_torrent_downloadstation.cs" />
<Compile Include="Datastore\Migration\102_add_language_to_episodeFiles_history_and_blacklist.cs" />
<Compile Include="Datastore\Migration\111_create_language_profiles.cs" />
<Compile Include="Datastore\Migration\115_add_downloadclient_status.cs" />
<Compile Include="Datastore\Migration\121_update_animetosho_url.cs" />
@ -339,7 +344,7 @@
<Compile Include="DecisionEngine\DownloadDecisionPriorizationService.cs" />
<Compile Include="DecisionEngine\IDecisionEngineSpecification.cs" />
<Compile Include="DecisionEngine\IRejectWithReason.cs" />
<Compile Include="DecisionEngine\QualityUpgradableSpecification.cs" />
<Compile Include="DecisionEngine\UpgradableSpecification.cs" />
<Compile Include="DecisionEngine\Rejection.cs" />
<Compile Include="DecisionEngine\RejectionType.cs" />
<Compile Include="DecisionEngine\SameEpisodesSpecification.cs" />
@ -769,6 +774,12 @@
<Compile Include="Jobs\ScheduledTask.cs" />
<Compile Include="Jobs\Scheduler.cs" />
<Compile Include="Jobs\TaskManager.cs" />
<Compile Include="Languages\Language.cs" />
<Compile Include="Languages\LanguageComparer.cs" />
<Compile Include="Languages\LanguagesBelowCutoff.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\AggregateLanguage.cs" />
<Compile Include="Profiles\Languages\LanguageProfile.cs" />
<Compile Include="Profiles\Languages\LanguageProfileInUseException.cs" />
<Compile Include="Lifecycle\ApplicationShutdownRequested.cs" />
<Compile Include="Lifecycle\ApplicationStartedEvent.cs" />
<Compile Include="Lifecycle\Commands\RestartCommand.cs" />
@ -985,7 +996,13 @@
<Compile Include="Profiles\Delay\DelayProfile.cs" />
<Compile Include="Profiles\Delay\DelayProfileService.cs" />
<Compile Include="Profiles\Delay\DelayProfileTagInUseValidator.cs" />
<Compile Include="Profiles\ProfileRepository.cs" />
<Compile Include="Profiles\Languages\LanguageProfileItem.cs" />
<Compile Include="Profiles\Languages\LanguageProfileRepository.cs" />
<Compile Include="Profiles\Qualities\Profile.cs" />
<Compile Include="Profiles\Qualities\ProfileInUseException.cs" />
<Compile Include="Profiles\Qualities\ProfileQualityItem.cs" />
<Compile Include="Profiles\Qualities\ProfileRepository.cs" />
<Compile Include="Profiles\Qualities\ProfileService.cs" />
<Compile Include="ProgressMessaging\ProgressMessageContext.cs" />
<Compile Include="Qualities\QualityDetectionSource.cs" />
<Compile Include="Qualities\QualitySource.cs" />
@ -1097,7 +1114,6 @@
<Compile Include="Organizer\NamingConfigService.cs" />
<Compile Include="Organizer\SampleResult.cs" />
<Compile Include="Parser\InvalidDateException.cs" />
<Compile Include="Parser\Language.cs" />
<Compile Include="Parser\Model\LocalEpisode.cs" />
<Compile Include="Parser\Model\ParsedEpisodeInfo.cs" />
<Compile Include="Parser\Model\ReleaseInfo.cs" />
@ -1108,11 +1124,8 @@
<Compile Include="Parser\ParsingService.cs" />
<Compile Include="Parser\SceneChecker.cs" />
<Compile Include="Parser\QualityParser.cs" />
<Compile Include="Profiles\Profile.cs" />
<Compile Include="Profiles\ProfileInUseException.cs" />
<Compile Include="Profiles\ProfileQualityItem.cs" />
<Compile Include="Profiles\Delay\DelayProfileRepository.cs" />
<Compile Include="Profiles\ProfileService.cs" />
<Compile Include="Profiles\Languages\LanguageProfileService.cs" />
<Compile Include="ProgressMessaging\CommandUpdatedEvent.cs" />
<Compile Include="ProgressMessaging\ProgressMessageTarget.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
@ -1229,7 +1242,6 @@
<Compile Include="Update\UpdateVerificationFailedException.cs" />
<Compile Include="Validation\FolderValidator.cs" />
<Compile Include="Validation\IpValidation.cs" />
<Compile Include="Validation\LanguageValidator.cs" />
<Compile Include="Validation\NzbDroneValidationExtensions.cs" />
<Compile Include="Validation\NzbDroneValidationFailure.cs" />
<Compile Include="Validation\NzbDroneValidationResult.cs" />
@ -1245,6 +1257,7 @@
<Compile Include="Validation\Paths\SeriesAncestorValidator.cs" />
<Compile Include="Validation\Paths\SeriesExistsValidator.cs" />
<Compile Include="Validation\Paths\SeriesPathValidator.cs" />
<Compile Include="Validation\LanguageProfileExistsValidator.cs" />
<Compile Include="Validation\ProfileExistsValidator.cs" />
<Compile Include="Validation\RuleBuilderExtensions.cs" />
<Compile Include="Validation\UrlValidator.cs" />

@ -1,4 +1,6 @@
namespace NzbDrone.Core.Parser
using NzbDrone.Core.Languages;
namespace NzbDrone.Core.Parser
{
public class IsoLanguage
{

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Languages;
namespace NzbDrone.Core.Parser
{

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

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

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

@ -1,6 +1,7 @@
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Languages;
namespace NzbDrone.Core.Parser.Model
{

@ -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(@"^(?:\[(?<subgroup>(?!\s).+?(?<!\s))\](?:_|-|\s|\.)?)",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?<italian>\b(?:ita|italian)\b)|(?<german>german\b|videomann)|(?<flemish>flemish)|(?<greek>greek)|(?<french>(?:\W|_)(?:FR|VOSTFR)(?:\W|_))|(?<russian>\brus\b)|(?<dutch>nl\W?subs?)|(?<hungarian>\b(?:HUNDUB|HUN)\b)|(?<spanish>\b(?:español|castellano)\b)",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex YearInTitleRegex = new Regex(@"^(?<title>.+?)(?:\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();

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save