Indexes are created with the same uniqueness when copying a table

New: Non-English episode support
New: Renamed Quality Profiles to Profiles and made them more powerful
New: Configurable wait time before grabbing a release to wait for a better quality
pull/4/head
Mark McDowall 10 years ago
parent b72678a9ad
commit 74a38415cf

@ -11,15 +11,16 @@ using NzbDrone.Api.History;
using NzbDrone.Api.Indexers;
using NzbDrone.Api.Logs;
using NzbDrone.Api.Mapping;
using NzbDrone.Api.Qualities;
using NzbDrone.Api.Profiles;
using NzbDrone.Api.RootFolders;
using NzbDrone.Api.Series;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Instrumentation;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Tv;
@ -41,8 +42,8 @@ namespace NzbDrone.Api.Test.MappingTests
[TestCase(typeof(ParsedEpisodeInfo), typeof(ReleaseResource))]
[TestCase(typeof(DownloadDecision), typeof(ReleaseResource))]
[TestCase(typeof(Core.History.History), typeof(HistoryResource))]
[TestCase(typeof(QualityProfile), typeof(QualityProfileResource))]
[TestCase(typeof(QualityProfileItem), typeof(QualityProfileItemResource))]
[TestCase(typeof(Profile), typeof(ProfileResource))]
[TestCase(typeof(ProfileQualityItem), typeof(ProfileQualityItemResource))]
[TestCase(typeof(Log), typeof(LogResource))]
[TestCase(typeof(Command), typeof(CommandResource))]
public void matching_fields(Type modelType, Type resourceType)
@ -105,16 +106,16 @@ namespace NzbDrone.Api.Test.MappingTests
[Test]
public void should_map_qualityprofile()
public void should_map_profile()
{
var profileResource = new QualityProfileResource
var profileResource = new ProfileResource
{
Cutoff = Quality.WEBDL1080p,
Items = new List<QualityProfileItemResource> { new QualityProfileItemResource { Quality = Quality.WEBDL1080p, Allowed = true } }
Items = new List<ProfileQualityItemResource> { new ProfileQualityItemResource { Quality = Quality.WEBDL1080p, Allowed = true } }
};
profileResource.InjectTo<QualityProfile>();
profileResource.InjectTo<Profile>();
}

@ -5,7 +5,6 @@ using Nancy;
using NLog;
using NzbDrone.Api.Mapping;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download;
using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.Indexers;
@ -15,7 +14,6 @@ using Omu.ValueInjecter;
using System.Linq;
using Nancy.ModelBinding;
using NzbDrone.Api.Extensions;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Api.Indexers
{
@ -106,14 +104,14 @@ namespace NzbDrone.Api.Indexers
release.InjectFrom(downloadDecision.RemoteEpisode.Release);
release.InjectFrom(downloadDecision.RemoteEpisode.ParsedEpisodeInfo);
release.InjectFrom(downloadDecision);
release.Rejections = downloadDecision.Rejections.ToList();
release.Rejections = downloadDecision.Rejections.Select(r => r.Reason).ToList();
release.DownloadAllowed = downloadDecision.RemoteEpisode.DownloadAllowed;
release.ReleaseWeight = result.Count;
if (downloadDecision.RemoteEpisode.Series != null)
{
release.QualityWeight = downloadDecision.RemoteEpisode.Series.QualityProfile.Value.Items.FindIndex(v => v.Quality == release.Quality.Quality) * 2;
release.QualityWeight = downloadDecision.RemoteEpisode.Series.Profile.Value.Items.FindIndex(v => v.Quality == release.Quality.Quality) * 2;
}
if (!release.Quality.Proper)

@ -28,6 +28,8 @@ namespace NzbDrone.Api.Indexers
public int[] EpisodeNumbers { get; set; }
public int[] AbsoluteEpisodeNumbers { get; set; }
public Boolean Approved { get; set; }
public Boolean TemporarilyRejected { get; set; }
public Boolean Rejected { get; set; }
public Int32 TvRageId { get; set; }
public IEnumerable<String> Rejections { get; set; }
public DateTime PublishDate { get; set; }

@ -150,6 +150,9 @@
<Compile Include="Metadata\MetadataResource.cs" />
<Compile Include="Metadata\MetadataModule.cs" />
<Compile Include="NzbDroneFeedModule.cs" />
<Compile Include="Profiles\Languages\LanguageModule.cs" />
<Compile Include="Profiles\Languages\LanguageResource.cs" />
<Compile Include="Profiles\LegacyProfileModule.cs" />
<Compile Include="ProviderResource.cs" />
<Compile Include="ProviderModuleBase.cs" />
<Compile Include="Indexers\IndexerModule.cs" />
@ -175,7 +178,7 @@
<Compile Include="Wanted\MissingModule.cs" />
<Compile Include="Config\NamingSampleResource.cs" />
<Compile Include="NzbDroneRestModuleWithSignalR.cs" />
<Compile Include="Qualities\QualityProfileValidation.cs" />
<Compile Include="Profiles\ProfileValidation.cs" />
<Compile Include="Queue\QueueModule.cs" />
<Compile Include="Queue\QueueResource.cs" />
<Compile Include="ResourceChangeMessage.cs" />
@ -183,7 +186,7 @@
<Compile Include="Notifications\NotificationResource.cs" />
<Compile Include="NzbDroneRestModule.cs" />
<Compile Include="PagingResource.cs" />
<Compile Include="Qualities\QualityProfileSchemaModule.cs" />
<Compile Include="Profiles\ProfileSchemaModule.cs" />
<Compile Include="REST\BadRequestException.cs" />
<Compile Include="REST\ResourceValidator.cs" />
<Compile Include="REST\RestModule.cs" />
@ -202,8 +205,8 @@
<Compile Include="Exceptions\InvalidApiKeyException.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="NzbDroneApiModule.cs" />
<Compile Include="Qualities\QualityProfileResource.cs" />
<Compile Include="Qualities\QualityProfileModule.cs" />
<Compile Include="Profiles\ProfileResource.cs" />
<Compile Include="Profiles\ProfileModule.cs" />
<Compile Include="Qualities\QualityDefinitionResource.cs" />
<Compile Include="Qualities\QualityDefinitionModule.cs" />
<Compile Include="Extensions\ReqResExtensions.cs" />

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Parser;
namespace NzbDrone.Api.Profiles.Languages
{
public class LanguageModule : NzbDroneRestModule<LanguageResource>
{
public LanguageModule()
{
GetResourceAll = GetAll;
GetResourceById = GetById;
}
private LanguageResource GetById(int id)
{
var language = (Language)id;
return new LanguageResource
{
Id = (int)language,
Name = language.ToString()
};
}
private List<LanguageResource> GetAll()
{
return ((Language[])Enum.GetValues(typeof (Language)))
.Select(l => new LanguageResource
{
Id = (int) l,
Name = l.ToString()
})
.OrderBy(l => l.Name)
.ToList();
}
}
}

@ -0,0 +1,14 @@
using System;
using Newtonsoft.Json;
using NzbDrone.Api.REST;
namespace NzbDrone.Api.Profiles.Languages
{
public class LanguageResource : RestResource
{
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)]
public Int32 Id { get; set; }
public String Name { get; set; }
public String NameLower { get { return Name.ToLowerInvariant(); } }
}
}

@ -0,0 +1,36 @@
using System;
using System.Text;
using Nancy;
using Nancy.Responses;
namespace NzbDrone.Api.Profiles
{
class LegacyProfileModule : NzbDroneApiModule
{
public LegacyProfileModule()
: base("qualityprofile")
{
Get["/"] = x =>
{
string queryString = ConvertQueryParams(Request.Query);
var url = String.Format("/api/profile?{0}", queryString);
return Response.AsRedirect(url, RedirectResponse.RedirectType.Permanent);
};
}
private string ConvertQueryParams(DynamicDictionary query)
{
var sb = new StringBuilder();
foreach (var key in query)
{
var value = query[key];
sb.AppendFormat("&{0}={1}", key, value);
}
return sb.ToString().Trim('&');
}
}
}

@ -0,0 +1,67 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Api.Mapping;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Validation;
namespace NzbDrone.Api.Profiles
{
public class ProfileModule : NzbDroneRestModule<ProfileResource>
{
private readonly IProfileService _profileService;
public ProfileModule(IProfileService profileService)
{
_profileService = profileService;
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;
UpdateResource = Update;
CreateResource = Create;
DeleteResource = DeleteProfile;
}
private int Create(ProfileResource resource)
{
var model = resource.InjectTo<Profile>();
model = _profileService.Add(model);
return model.Id;
}
private void DeleteProfile(int id)
{
_profileService.Delete(id);
}
private void Update(ProfileResource resource)
{
var model = _profileService.Get(resource.Id);
model.Name = resource.Name;
model.Cutoff = (Quality)resource.Cutoff.Id;
model.Items = resource.Items.InjectTo<List<ProfileQualityItem>>();
model.Language = resource.Language;
model.GrabDelay = resource.GrabDelay;
model.GrabDelayMode = resource.GrabDelayMode;
_profileService.Update(model);
}
private ProfileResource GetById(int id)
{
return _profileService.Get(id).InjectTo<ProfileResource>();
}
private List<ProfileResource> GetAll()
{
var profiles = _profileService.All().InjectTo<List<ProfileResource>>();
return profiles;
}
}
}

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using NzbDrone.Api.REST;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Api.Profiles
{
public class ProfileResource : RestResource
{
public String Name { get; set; }
public Quality Cutoff { get; set; }
public List<ProfileQualityItemResource> Items { get; set; }
public Language Language { get; set; }
public Int32 GrabDelay { get; set; }
public GrabDelayMode GrabDelayMode { get; set; }
}
public class ProfileQualityItemResource : RestResource
{
public Quality Quality { get; set; }
public bool Allowed { get; set; }
}
}

@ -1,34 +1,37 @@
using System.Collections.Generic;
using NzbDrone.Core.Qualities;
using NzbDrone.Api.Mapping;
using System.Linq;
using NzbDrone.Api.Mapping;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Api.Qualities
namespace NzbDrone.Api.Profiles
{
public class QualityProfileSchemaModule : NzbDroneRestModule<QualityProfileResource>
public class ProfileSchemaModule : NzbDroneRestModule<ProfileResource>
{
private readonly IQualityDefinitionService _qualityDefinitionService;
public QualityProfileSchemaModule(IQualityDefinitionService qualityDefinitionService)
: base("/qualityprofile/schema")
public ProfileSchemaModule(IQualityDefinitionService qualityDefinitionService)
: base("/profile/schema")
{
_qualityDefinitionService = qualityDefinitionService;
GetResourceAll = GetAll;
}
private List<QualityProfileResource> GetAll()
private List<ProfileResource> GetAll()
{
var items = _qualityDefinitionService.All()
.OrderBy(v => v.Weight)
.Select(v => new QualityProfileItem { Quality = v.Quality, Allowed = false })
.Select(v => new ProfileQualityItem { Quality = v.Quality, Allowed = false })
.ToList();
var profile = new QualityProfile();
var profile = new Profile();
profile.Cutoff = Quality.Unknown;
profile.Items = items;
profile.Language = Language.English;
return new List<QualityProfileResource> { profile.InjectTo<QualityProfileResource>() };
return new List<ProfileResource> { profile.InjectTo<ProfileResource>() };
}
}
}

@ -3,11 +3,11 @@ using System.Linq;
using FluentValidation;
using FluentValidation.Validators;
namespace NzbDrone.Api.Qualities
namespace NzbDrone.Api.Profiles
{
public static class QualityProfileValidation
public static class ProfileValidation
{
public static IRuleBuilderOptions<T, IList<QualityProfileItemResource>> MustHaveAllowedQuality<T>(this IRuleBuilder<T, IList<QualityProfileItemResource>> ruleBuilder)
public static IRuleBuilderOptions<T, IList<ProfileQualityItemResource>> MustHaveAllowedQuality<T>(this IRuleBuilder<T, IList<ProfileQualityItemResource>> ruleBuilder)
{
ruleBuilder.SetValidator(new NotEmptyValidator(null));
@ -25,7 +25,7 @@ namespace NzbDrone.Api.Qualities
protected override bool IsValid(PropertyValidatorContext context)
{
var list = context.PropertyValue as IList<QualityProfileItemResource>;
var list = context.PropertyValue as IList<ProfileQualityItemResource>;
if (list == null)
{

@ -1,60 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Core.Qualities;
using NzbDrone.Api.Mapping;
using FluentValidation;
namespace NzbDrone.Api.Qualities
{
public class QualityProfileModule : NzbDroneRestModule<QualityProfileResource>
{
private readonly IQualityProfileService _qualityProfileService;
public QualityProfileModule(IQualityProfileService qualityProfileService)
{
_qualityProfileService = qualityProfileService;
SharedValidator.RuleFor(c => c.Name).NotEmpty();
SharedValidator.RuleFor(c => c.Cutoff).NotNull();
SharedValidator.RuleFor(c => c.Items).MustHaveAllowedQuality();//.SetValidator(new AllowedValidator<QualityProfileItemResource>());
GetResourceAll = GetAll;
GetResourceById = GetById;
UpdateResource = Update;
CreateResource = Create;
DeleteResource = DeleteProfile;
}
private int Create(QualityProfileResource resource)
{
var model = resource.InjectTo<QualityProfile>();
model = _qualityProfileService.Add(model);
return model.Id;
}
private void DeleteProfile(int id)
{
_qualityProfileService.Delete(id);
}
private void Update(QualityProfileResource resource)
{
var model = _qualityProfileService.Get(resource.Id);
model.Name = resource.Name;
model.Cutoff = (Quality)resource.Cutoff.Id;
model.Items = resource.Items.InjectTo<List<QualityProfileItem>>();
_qualityProfileService.Update(model);
}
private QualityProfileResource GetById(int id)
{
return _qualityProfileService.Get(id).InjectTo<QualityProfileResource>();
}
private List<QualityProfileResource> GetAll()
{
var profiles = _qualityProfileService.All().InjectTo<List<QualityProfileResource>>();
return profiles;
}
}
}

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using NzbDrone.Api.REST;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Api.Qualities
{
public class QualityProfileResource : RestResource
{
public String Name { get; set; }
public Quality Cutoff { get; set; }
public List<QualityProfileItemResource> Items { get; set; }
}
public class QualityProfileItemResource : RestResource
{
public Quality Quality { get; set; }
public bool Allowed { get; set; }
}
}

@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Queue;
@ -7,25 +9,40 @@ using NzbDrone.Core.Queue;
namespace NzbDrone.Api.Queue
{
public class QueueModule : NzbDroneRestModuleWithSignalR<QueueResource, Core.Queue.Queue>,
IHandle<UpdateQueueEvent>
IHandle<UpdateQueueEvent>, IHandle<PendingReleasesUpdatedEvent>
{
private readonly IQueueService _queueService;
private readonly IPendingReleaseService _pendingReleaseService;
public QueueModule(ICommandExecutor commandExecutor, IQueueService queueService)
public QueueModule(ICommandExecutor commandExecutor, IQueueService queueService, IPendingReleaseService pendingReleaseService)
: base(commandExecutor)
{
_queueService = queueService;
_pendingReleaseService = pendingReleaseService;
GetResourceAll = GetQueue;
}
private List<QueueResource> GetQueue()
{
return ToListResource(_queueService.GetQueue);
return ToListResource(GetQueueItems);
}
private IEnumerable<Core.Queue.Queue> GetQueueItems()
{
var queue = _queueService.GetQueue();
var pending = _pendingReleaseService.GetPendingQueue();
return queue.Concat(pending);
}
public void Handle(UpdateQueueEvent message)
{
BroadcastResourceChange(ModelAction.Sync);
}
public void Handle(PendingReleasesUpdatedEvent message)
{
BroadcastResourceChange(ModelAction.Sync);
}
}
}

@ -15,7 +15,6 @@ using NzbDrone.Api.Mapping;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.Validation.Paths;
using NzbDrone.Core.DataAugmentation.Scene;
using Omu.ValueInjecter;
namespace NzbDrone.Api.Series
{
@ -27,7 +26,6 @@ namespace NzbDrone.Api.Series
IHandle<SeriesDeletedEvent>
{
private readonly ICommandExecutor _commandExecutor;
private readonly ISeriesService _seriesService;
private readonly ISeriesStatisticsService _seriesStatisticsService;
private readonly ISceneMappingService _sceneMappingService;
@ -47,7 +45,6 @@ namespace NzbDrone.Api.Series
)
: base(commandExecutor)
{
_commandExecutor = commandExecutor;
_seriesService = seriesService;
_seriesStatisticsService = seriesStatisticsService;
_sceneMappingService = sceneMappingService;
@ -60,7 +57,7 @@ namespace NzbDrone.Api.Series
UpdateResource = UpdateSeries;
DeleteResource = DeleteSeries;
SharedValidator.RuleFor(s => s.QualityProfileId).ValidId();
SharedValidator.RuleFor(s => s.ProfileId).ValidId();
SharedValidator.RuleFor(s => s.Path)
.Cascade(CascadeMode.StopOnFirstFailure)

@ -12,7 +12,7 @@ namespace NzbDrone.Api.Series
{
//Todo: Sorters should be done completely on the client
//Todo: Is there an easy way to keep IgnoreArticlesWhenSorting in sync between, Series, History, Missing?
//Todo: We should get the entire QualityProfile instead of ID and Name separately
//Todo: We should get the entire Profile instead of ID and Name separately
//View Only
public String Title { get; set; }
@ -32,7 +32,7 @@ namespace NzbDrone.Api.Series
public Int32 EpisodeCount { get; set; }
public Int32 EpisodeFileCount { get; set; }
public SeriesStatusType Status { get; set; }
public String QualityProfileName { get; set; }
public String ProfileName { get; set; }
public String Overview { get; set; }
public DateTime? NextAiring { get; set; }
public DateTime? PreviousAiring { get; set; }
@ -46,7 +46,7 @@ namespace NzbDrone.Api.Series
//View & Edit
public String Path { get; set; }
public Int32 QualityProfileId { get; set; }
public Int32 ProfileId { get; set; }
//Editing Only
public Boolean SeasonFolder { get; set; }
@ -65,5 +65,21 @@ namespace NzbDrone.Api.Series
public String RootFolderPath { get; set; }
public String Certification { get; set; }
public List<String> Genres { get; set; }
//Used to support legacy consumers
public Int32 QualityProfileId
{
get
{
return ProfileId;
}
set
{
if (value > 0 && ProfileId == 0)
{
ProfileId = value;
}
}
}
}
}

@ -1,6 +1,7 @@
using FizzWare.NBuilder;
using NUnit.Framework;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Qualities;
@ -15,19 +16,19 @@ namespace NzbDrone.Core.Test.Datastore
[SetUp]
public void Setup()
{
var qualityProfile = new NzbDrone.Core.Qualities.QualityProfile
var profile = new Profile
{
Name = "Test",
Cutoff = Quality.WEBDL720p,
Items = NzbDrone.Core.Test.Qualities.QualityFixture.GetDefaultQualities()
Items = Qualities.QualityFixture.GetDefaultQualities()
};
qualityProfile = Db.Insert(qualityProfile);
profile = Db.Insert(profile);
var series = Builder<Series>.CreateListOfSize(1)
.All()
.With(v => v.QualityProfileId = qualityProfile.Id)
.With(v => v.ProfileId = profile.Id)
.BuildListOfNew();
Db.InsertMany(series);
@ -50,7 +51,7 @@ namespace NzbDrone.Core.Test.Datastore
}
[Test]
public void should_lazy_load_qualityprofile_if_not_joined()
public void should_lazy_load_profile_if_not_joined()
{
var db = Mocker.Resolve<IDatabase>();
var DataMapper = db.GetDataMapper();
@ -62,7 +63,7 @@ namespace NzbDrone.Core.Test.Datastore
foreach (var episode in episodes)
{
Assert.IsNotNull(episode.Series);
Assert.IsFalse(episode.Series.QualityProfile.IsLoaded);
Assert.IsFalse(episode.Series.Profile.IsLoaded);
}
}
@ -84,20 +85,20 @@ namespace NzbDrone.Core.Test.Datastore
}
[Test]
public void should_explicit_load_qualityprofile_if_joined()
public void should_explicit_load_profile_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, QualityProfile>(Marr.Data.QGen.JoinType.Inner, v => v.QualityProfile, (l, r) => l.QualityProfileId == r.Id)
.Join<Series, Profile>(Marr.Data.QGen.JoinType.Inner, v => v.Profile, (l, r) => l.ProfileId == r.Id)
.ToList();
foreach (var episode in episodes)
{
Assert.IsNotNull(episode.Series);
Assert.IsTrue(episode.Series.QualityProfile.IsLoaded);
Assert.IsTrue(episode.Series.Profile.IsLoaded);
}
}

@ -6,6 +6,7 @@ using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using System.Linq;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Datastore.SQLiteMigrationHelperTests
{
@ -119,5 +120,23 @@ namespace NzbDrone.Core.Test.Datastore.SQLiteMigrationHelperTests
newColumns.Values.Should().HaveSameCount(columns.Values);
newIndexes.Should().Contain(i=>i.Column == "AirTime");
}
[Test]
public void should_create_indexes_with_the_same_uniqueness()
{
var columns = _subject.GetColumns("Series");
var indexes = _subject.GetIndexes("Series");
var tempIndexes = indexes.JsonClone();
tempIndexes[0].Unique = false;
tempIndexes[1].Unique = true;
_subject.CreateTable("Series_New", columns.Values, tempIndexes);
var newIndexes = _subject.GetIndexes("Series_New");
newIndexes.Should().HaveSameCount(tempIndexes);
newIndexes.ShouldAllBeEquivalentTo(tempIndexes, options => options.Excluding(o => o.IndexName).Excluding(o => o.Table));
}
}
}

@ -25,12 +25,12 @@ namespace NzbDrone.Core.Test.Datastore.SQLiteMigrationHelperTests
{
var series = Builder<Series>.CreateListOfSize(10)
.Random(3)
.With(c => c.QualityProfileId = 100)
.With(c => c.ProfileId = 100)
.BuildListOfNew();
Db.InsertMany(series);
var duplicates = _subject.GetDuplicates<int>("series", "QualityProfileId").ToList();
var duplicates = _subject.GetDuplicates<int>("series", "ProfileId").ToList();
duplicates.Should().HaveCount(1);

@ -1,5 +1,6 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine;
@ -13,35 +14,35 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[Test]
public void should_return_true_if_current_episode_is_less_than_cutoff()
{
Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities() },
Subject.CutoffNotMet(new Profile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities() },
new QualityModel(Quality.DVD, true)).Should().BeTrue();
}
[Test]
public void should_return_false_if_current_episode_is_equal_to_cutoff()
{
Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p, Items = Qualities.QualityFixture.GetDefaultQualities() },
Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p, Items = Qualities.QualityFixture.GetDefaultQualities() },
new QualityModel(Quality.HDTV720p, true)).Should().BeFalse();
}
[Test]
public void should_return_false_if_current_episode_is_greater_than_cutoff()
{
Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p, Items = Qualities.QualityFixture.GetDefaultQualities() },
Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p, Items = Qualities.QualityFixture.GetDefaultQualities() },
new QualityModel(Quality.Bluray1080p, true)).Should().BeFalse();
}
[Test]
public void should_return_true_when_new_episode_is_proper_but_existing_is_not()
{
Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p, Items = Qualities.QualityFixture.GetDefaultQualities() },
Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p, Items = Qualities.QualityFixture.GetDefaultQualities() },
new QualityModel(Quality.HDTV720p, false), new QualityModel(Quality.HDTV720p, true)).Should().BeTrue();
}
[Test]
public void should_return_false_if_cutoff_is_met_and_quality_is_higher()
{
Subject.CutoffNotMet(new QualityProfile { Cutoff = Quality.HDTV720p, Items = Qualities.QualityFixture.GetDefaultQualities() },
Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p, Items = Qualities.QualityFixture.GetDefaultQualities() },
new QualityModel(Quality.HDTV720p, true), new QualityModel(Quality.Bluray1080p, true)).Should().BeFalse();
}
}

@ -9,6 +9,7 @@ using NzbDrone.Core.Download.Clients.Sabnzbd;
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;
@ -42,7 +43,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
};
_fakeSeries = Builder<Series>.CreateNew()
.With(c => c.QualityProfile = new QualityProfile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities() })
.With(c => c.Profile = new Profile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities() })
.Build();
_parseResultMulti = new RemoteEpisode
@ -62,9 +63,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_upgradableQuality = new QualityModel(Quality.SDTV, false);
_notupgradableQuality = new QualityModel(Quality.HDTV1080p, true);
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<QualityProfile>(), 1)).Returns(_notupgradableQuality);
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<QualityProfile>(), 2)).Returns(_notupgradableQuality);
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<QualityProfile>(), 3)).Returns<QualityModel>(null);
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<Profile>(), 1)).Returns(_notupgradableQuality);
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<Profile>(), 2)).Returns(_notupgradableQuality);
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<Profile>(), 3)).Returns<QualityModel>(null);
Mocker.GetMock<IProvideDownloadClient>()
.Setup(c => c.GetDownloadClients())
@ -73,12 +74,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private void WithFirstReportUpgradable()
{
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<QualityProfile>(), 1)).Returns(_upgradableQuality);
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<Profile>(), 1)).Returns(_upgradableQuality);
}
private void WithSecondReportUpgradable()
{
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<QualityProfile>(), 2)).Returns(_upgradableQuality);
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<Profile>(), 2)).Returns(_upgradableQuality);
}
private void GivenSabnzbdDownloadClient()
@ -132,11 +133,11 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[Test]
public void should_not_be_upgradable_if_episode_is_of_same_quality_as_existing()
{
_fakeSeries.QualityProfile = new QualityProfile { Cutoff = Quality.WEBDL1080p, Items = Qualities.QualityFixture.GetDefaultQualities() };
_fakeSeries.Profile = new Profile { Cutoff = Quality.WEBDL1080p, Items = Qualities.QualityFixture.GetDefaultQualities() };
_parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL1080p, false);
_upgradableQuality = new QualityModel(Quality.WEBDL1080p, false);
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<QualityProfile>(), 1)).Returns(_upgradableQuality);
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<Profile>(), 1)).Returns(_upgradableQuality);
_upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Should().BeFalse();
}

@ -1,9 +1,12 @@
using FluentAssertions;
using Marr.Data;
using NUnit.Framework;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.DecisionEngineTests
{
@ -11,28 +14,35 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public class LanguageSpecificationFixture : CoreTest
{
private RemoteEpisode parseResult;
private RemoteEpisode _remoteEpisode;
private void WithEnglishRelease()
[SetUp]
public void Setup()
{
parseResult = new RemoteEpisode
_remoteEpisode = new RemoteEpisode
{
ParsedEpisodeInfo = new ParsedEpisodeInfo
{
ParsedEpisodeInfo = new ParsedEpisodeInfo
{
Language = Language.English
}
};
Language = Language.English
},
Series = new Series
{
Profile = new LazyLoaded<Profile>(new Profile
{
Language = Language.English
})
}
};
}
private void WithEnglishRelease()
{
_remoteEpisode.ParsedEpisodeInfo.Language = Language.English;
}
private void WithGermanRelease()
{
parseResult = new RemoteEpisode
{
ParsedEpisodeInfo = new ParsedEpisodeInfo
{
Language = Language.German
}
};
_remoteEpisode.ParsedEpisodeInfo.Language = Language.German;
}
[Test]
@ -40,7 +50,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
WithEnglishRelease();
Mocker.Resolve<LanguageSpecification>().IsSatisfiedBy(parseResult, null).Should().BeTrue();
Mocker.Resolve<LanguageSpecification>().IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue();
}
[Test]
@ -48,7 +58,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
WithGermanRelease();
Mocker.Resolve<LanguageSpecification>().IsSatisfiedBy(parseResult, null).Should().BeFalse();
Mocker.Resolve<LanguageSpecification>().IsSatisfiedBy(_remoteEpisode, null).Should().BeFalse();
}
}
}

@ -6,6 +6,7 @@ using NUnit.Framework;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Test.Framework;
@ -27,7 +28,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void Setup()
{
_series = Builder<Series>.CreateNew()
.With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() })
.With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() })
.Build();
_episode = Builder<Episode>.CreateNew()

@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.DecisionEngine;
@ -38,7 +40,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
remoteEpisode.Release.Size = size;
remoteEpisode.Series = Builder<Series>.CreateNew()
.With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() })
.With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() })
.Build();
return remoteEpisode;

@ -5,6 +5,7 @@ using Marr.Data;
using NUnit.Framework;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Test.Framework;
@ -35,7 +36,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void Setup()
{
var fakeSeries = Builder<Series>.CreateNew()
.With(c => c.QualityProfile = (LazyLoaded<QualityProfile>)new QualityProfile { Cutoff = Quality.Bluray1080p })
.With(c => c.Profile = (LazyLoaded<Profile>)new Profile { Cutoff = Quality.Bluray1080p })
.Build();
remoteEpisode = new RemoteEpisode
@ -49,7 +50,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_allow_if_quality_is_defined_in_profile(Quality qualityType)
{
remoteEpisode.ParsedEpisodeInfo.Quality.Quality = qualityType;
remoteEpisode.Series.QualityProfile.Value.Items = Qualities.QualityFixture.GetDefaultQualities(Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p);
remoteEpisode.Series.Profile.Value.Items = Qualities.QualityFixture.GetDefaultQualities(Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p);
Subject.IsSatisfiedBy(remoteEpisode, null).Should().BeTrue();
}
@ -58,7 +59,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_not_allow_if_quality_is_not_defined_in_profile(Quality qualityType)
{
remoteEpisode.ParsedEpisodeInfo.Quality.Quality = qualityType;
remoteEpisode.Series.QualityProfile.Value.Items = Qualities.QualityFixture.GetDefaultQualities(Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p);
remoteEpisode.Series.Profile.Value.Items = Qualities.QualityFixture.GetDefaultQualities(Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p);
Subject.IsSatisfiedBy(remoteEpisode, null).Should().BeFalse();
}

@ -2,6 +2,7 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine;
@ -43,9 +44,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
GivenAutoDownloadPropers(true);
var qualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() };
var profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() };
Subject.IsUpgradable(qualityProfile, new QualityModel(current, currentProper), new QualityModel(newQuality, newProper))
Subject.IsUpgradable(profile, new QualityModel(current, currentProper), new QualityModel(newQuality, newProper))
.Should().Be(expected);
}
@ -54,9 +55,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
GivenAutoDownloadPropers(false);
var qualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() };
var profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() };
Subject.IsUpgradable(qualityProfile, new QualityModel(Quality.DVD, true), new QualityModel(Quality.DVD, false))
Subject.IsUpgradable(profile, new QualityModel(Quality.DVD, true), new QualityModel(Quality.DVD, false))
.Should().BeFalse();
}
}

@ -0,0 +1,253 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Marr.Data;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.DecisionEngine.Specifications.RssSync;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.History;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.MediaFiles;
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;
namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
{
[TestFixture]
public class DelaySpecificationFixture : CoreTest<DelaySpecification>
{
private Profile _profile;
private RemoteEpisode _remoteEpisode;
[SetUp]
public void Setup()
{
_profile = Builder<Profile>.CreateNew()
.Build();
var series = Builder<Series>.CreateNew()
.With(s => s.Profile = _profile)
.Build();
_remoteEpisode = Builder<RemoteEpisode>.CreateNew()
.With(r => r.Series = series)
.Build();
_profile.Items = new List<ProfileQualityItem>();
_profile.Items.Add(new ProfileQualityItem { Allowed = true, Quality = Quality.HDTV720p });
_profile.Items.Add(new ProfileQualityItem { Allowed = true, Quality = Quality.WEBDL720p });
_profile.Items.Add(new ProfileQualityItem { Allowed = true, Quality = Quality.Bluray720p });
_profile.Cutoff = Quality.WEBDL720p;
_profile.GrabDelayMode = GrabDelayMode.Always;
_remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo();
_remoteEpisode.Release = new ReleaseInfo();
_remoteEpisode.Episodes = Builder<Episode>.CreateListOfSize(1).Build().ToList();
}
private void GivenExistingFile(QualityModel quality)
{
_remoteEpisode.Episodes[0].EpisodeFile = new LazyLoaded<EpisodeFile>(new EpisodeFile
{
Quality = quality
});
}
[Test]
public void should_be_true_when_search()
{
Subject.IsSatisfiedBy(new RemoteEpisode(), new SingleEpisodeSearchCriteria()).Should().BeTrue();
}
[Test]
public void should_be_true_when_profile_does_not_have_a_delay()
{
_profile.GrabDelay = 0;
Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue();
}
[Test]
public void should_be_true_when_quality_is_last_allowed_in_profile()
{
_remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.Bluray720p);
Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue();
}
[Test]
public void should_be_true_when_release_is_older_than_delay()
{
_remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p);
_remoteEpisode.Release.PublishDate = DateTime.UtcNow.AddHours(-10);
_profile.GrabDelay = 1;
Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue();
}
[Test]
public void should_be_false_when_release_is_younger_than_delay()
{
_remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.SDTV);
_remoteEpisode.Release.PublishDate = DateTime.UtcNow;
_profile.GrabDelay = 12;
Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeFalse();
}
[Test]
public void should_be_true_when_release_is_proper_for_existing_episode()
{
_remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p, true);
_remoteEpisode.Release.PublishDate = DateTime.UtcNow;
GivenExistingFile(new QualityModel(Quality.HDTV720p));
_profile.GrabDelay = 12;
Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue();
}
[Test]
public void should_be_false_when_release_is_proper_and_no_existing_episode()
{
}
[Test]
public void should_be_true_when_release_meets_cutoff_and_mode_is_cutoff()
{
_profile.GrabDelayMode = GrabDelayMode.Cutoff;
_remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL720p);
_remoteEpisode.Release.PublishDate = DateTime.UtcNow;
_profile.GrabDelay = 12;
Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue();
}
[Test]
public void should_be_true_when_release_exceeds_cutoff_and_mode_is_cutoff()
{
_profile.GrabDelayMode = GrabDelayMode.Cutoff;
_remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.Bluray720p);
_remoteEpisode.Release.PublishDate = DateTime.UtcNow;
_profile.GrabDelay = 12;
Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue();
}
[Test]
public void should_be_false_when_release_is_below_cutoff_and_mode_is_cutoff()
{
_profile.GrabDelayMode = GrabDelayMode.Cutoff;
_remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p);
_remoteEpisode.Release.PublishDate = DateTime.UtcNow;
_profile.GrabDelay = 12;
Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeFalse();
}
[Test]
public void should_be_false_when_release_is_proper_for_existing_episode_of_different_quality()
{
_remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p, true);
_remoteEpisode.Release.PublishDate = DateTime.UtcNow;
GivenExistingFile(new QualityModel(Quality.SDTV));
_profile.GrabDelay = 12;
Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeFalse();
}
[Test]
public void should_be_false_when_release_is_first_detected_and_mode_is_first()
{
_profile.GrabDelayMode = GrabDelayMode.First;
_remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p);
_remoteEpisode.Release.PublishDate = DateTime.UtcNow;
_profile.GrabDelay = 12;
Mocker.GetMock<IPendingReleaseService>()
.Setup(s => s.GetPendingRemoteEpisodes(It.IsAny<Int32>()))
.Returns(new List<RemoteEpisode>());
Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeFalse();
}
[Test]
public void should_be_false_when_release_is_not_first_but_oldest_has_not_expired_and_type_is_first()
{
_profile.GrabDelayMode = GrabDelayMode.First;
_remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p);
_remoteEpisode.Release.PublishDate = DateTime.UtcNow;
_profile.GrabDelay = 12;
var pendingRemoteEpisode = _remoteEpisode.JsonClone();
Mocker.GetMock<IPendingReleaseService>()
.Setup(s => s.GetPendingRemoteEpisodes(It.IsAny<Int32>()))
.Returns(new List<RemoteEpisode> { _remoteEpisode.JsonClone() });
Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeFalse();
}
[Test]
public void should_be_true_when_existing_pending_release_expired_and_mode_is_first()
{
_profile.GrabDelayMode = GrabDelayMode.First;
_remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL720p);
_remoteEpisode.Release.PublishDate = DateTime.UtcNow;
_profile.GrabDelay = 12;
var pendingRemoteEpisode = _remoteEpisode.JsonClone();
pendingRemoteEpisode.Release.PublishDate = DateTime.UtcNow.AddHours(-15);
Mocker.GetMock<IPendingReleaseService>()
.Setup(s => s.GetPendingRemoteEpisodes(It.IsAny<Int32>()))
.Returns(new List<RemoteEpisode> { pendingRemoteEpisode });
Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue();
}
[Test]
public void should_be_true_when_one_existing_pending_release_is_expired_and_mode_is_first()
{
_profile.GrabDelayMode = GrabDelayMode.First;
_remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL720p);
_remoteEpisode.Release.PublishDate = DateTime.UtcNow;
_profile.GrabDelay = 12;
var pendingRemoteEpisode1 = _remoteEpisode.JsonClone();
pendingRemoteEpisode1.Release.PublishDate = DateTime.UtcNow.AddHours(-15);
var pendingRemoteEpisode2 = _remoteEpisode.JsonClone();
pendingRemoteEpisode2.Release.PublishDate = DateTime.UtcNow.AddHours(5);
Mocker.GetMock<IPendingReleaseService>()
.Setup(s => s.GetPendingRemoteEpisodes(It.IsAny<Int32>()))
.Returns(new List<RemoteEpisode> { pendingRemoteEpisode1, pendingRemoteEpisode2 });
Subject.IsSatisfiedBy(_remoteEpisode, null).Should().BeTrue();
}
}
}

@ -8,6 +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.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine;
@ -37,7 +38,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
var doubleEpisodeList = new List<Episode> { new Episode { EpisodeFile = _firstFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = _secondFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = null } };
var fakeSeries = Builder<Series>.CreateNew()
.With(c => c.QualityProfile = new QualityProfile { Cutoff = Quality.Bluray1080p })
.With(c => c.Profile = new Profile { Cutoff = Quality.Bluray1080p })
.Build();
_parseResultMulti = new RemoteEpisode

@ -6,6 +6,7 @@ using NUnit.Framework;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine;
@ -38,7 +39,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
var doubleEpisodeList = new List<Episode> { new Episode { EpisodeFile = _firstFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = _secondFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = null } };
var fakeSeries = Builder<Series>.CreateNew()
.With(c => c.QualityProfile = new QualityProfile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities() })
.With(c => c.Profile = new Profile { Cutoff = Quality.Bluray1080p, Items = Qualities.QualityFixture.GetDefaultQualities() })
.Build();
_parseResultMulti = new RemoteEpisode

@ -4,19 +4,19 @@ using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
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 NzbDrone.Core.DecisionEngine;
namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
{
[TestFixture]
public class DownloadApprovedFixture : CoreTest<DownloadApprovedReports>
public class DownloadApprovedFixture : CoreTest<ProcessDownloadDecisions>
{
[SetUp]
public void SetUp()
@ -47,7 +47,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
remoteEpisode.Release.PublishDate = DateTime.UtcNow;
remoteEpisode.Series = Builder<Series>.CreateNew()
.With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() })
.With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() })
.Build();
return remoteEpisode;
@ -62,7 +62,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisode));
Subject.DownloadApproved(decisions);
Subject.ProcessDecisions(decisions);
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteEpisode>()), Times.Once());
}
@ -76,7 +76,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteEpisode));
decisions.Add(new DownloadDecision(remoteEpisode));
Subject.DownloadApproved(decisions);
Subject.ProcessDecisions(decisions);
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteEpisode>()), Times.Once());
}
@ -97,7 +97,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
Subject.DownloadApproved(decisions);
Subject.ProcessDecisions(decisions);
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteEpisode>()), Times.Once());
}
@ -110,7 +110,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisode));
Subject.DownloadApproved(decisions).Should().HaveCount(1);
Subject.ProcessDecisions(decisions).Grabbed.Should().HaveCount(1);
}
[Test]
@ -130,7 +130,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
Subject.DownloadApproved(decisions).Should().HaveCount(2);
Subject.ProcessDecisions(decisions).Grabbed.Should().HaveCount(2);
}
[Test]
@ -156,7 +156,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteEpisode2));
decisions.Add(new DownloadDecision(remoteEpisode3));
Subject.DownloadApproved(decisions).Should().HaveCount(2);
Subject.ProcessDecisions(decisions).Grabbed.Should().HaveCount(2);
}
[Test]
@ -169,7 +169,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteEpisode));
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.IsAny<RemoteEpisode>())).Throws(new Exception());
Subject.DownloadApproved(decisions).Should().BeEmpty();
Subject.ProcessDecisions(decisions).Grabbed.Should().BeEmpty();
ExceptionVerification.ExpectedWarns(1);
}
@ -177,8 +177,8 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
public void should_return_an_empty_list_when_none_are_appproved()
{
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(null, "Failure!"));
decisions.Add(new DownloadDecision(null, "Failure!"));
decisions.Add(new DownloadDecision(null, new Rejection("Failure!")));
decisions.Add(new DownloadDecision(null, new Rejection("Failure!")));
Subject.GetQualifiedReports(decisions).Should().BeEmpty();
}

@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FizzWare.NBuilder;
using FluentAssertions;
using Marr.Data;
using Moq;
using NUnit.Framework;
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.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
{
[TestFixture]
public class ProcessFixture : CoreTest<PendingReleaseService>
{
private DownloadDecision _temporarilyRejected;
private Series _series;
private Episode _episode;
private Profile _profile;
private ReleaseInfo _release;
private ParsedEpisodeInfo _parsedEpisodeInfo;
private RemoteEpisode _remoteEpisode;
[SetUp]
public void Setup()
{
_series = Builder<Series>.CreateNew()
.Build();
_episode = Builder<Episode>.CreateNew()
.Build();
_profile = new Profile
{
Name = "Test",
Cutoff = Quality.HDTV720p,
GrabDelay = 1,
Items = new List<ProfileQualityItem>
{
new ProfileQualityItem { Allowed = true, Quality = Quality.HDTV720p },
new ProfileQualityItem { Allowed = true, Quality = Quality.WEBDL720p },
new ProfileQualityItem { Allowed = true, Quality = Quality.Bluray720p }
},
};
_series.Profile = new LazyLoaded<Profile>(_profile);
_release = Builder<ReleaseInfo>.CreateNew().Build();
_parsedEpisodeInfo = Builder<ParsedEpisodeInfo>.CreateNew().Build();
_parsedEpisodeInfo.Quality = new QualityModel(Quality.HDTV720p);
_remoteEpisode = new RemoteEpisode();
_remoteEpisode.Episodes = new List<Episode>{ _episode };
_remoteEpisode.Series = _series;
_remoteEpisode.ParsedEpisodeInfo = _parsedEpisodeInfo;
_remoteEpisode.Release = _release;
_temporarilyRejected = new DownloadDecision(_remoteEpisode, new Rejection("Temp Rejected", RejectionType.Temporary));
Mocker.GetMock<IPendingReleaseRepository>()
.Setup(s => s.All())
.Returns(new List<PendingRelease>());
Mocker.GetMock<ISeriesService>()
.Setup(s => s.GetSeries(It.IsAny<Int32>()))
.Returns(_series);
Mocker.GetMock<IParsingService>()
.Setup(s => s.GetEpisodes(It.IsAny<ParsedEpisodeInfo>(), _series, true, null))
.Returns(new List<Episode> {_episode});
Mocker.GetMock<IPrioritizeDownloadDecision>()
.Setup(s => s.PrioritizeDecisions(It.IsAny<List<DownloadDecision>>()))
.Returns((List<DownloadDecision> d) => d);
}
private void GivenHeldRelease(String title, String indexer, DateTime publishDate)
{
var release = _release.JsonClone();
release.Indexer = indexer;
release.PublishDate = publishDate;
var heldReleases = Builder<PendingRelease>.CreateListOfSize(1)
.All()
.With(h => h.SeriesId = _series.Id)
.With(h => h.Title = title)
.With(h => h.Release = release)
.Build();
Mocker.GetMock<IPendingReleaseRepository>()
.Setup(s => s.All())
.Returns(heldReleases);
}
[Test]
public void should_add()
{
Subject.Add(_temporarilyRejected);
VerifyInsert();
}
[Test]
public void should_not_add_if_it_is_the_same_release_from_the_same_indexer()
{
GivenHeldRelease(_release.Title, _release.Indexer, _release.PublishDate);
Subject.Add(_temporarilyRejected);
VerifyNoInsert();
}
[Test]
public void should_add_if_title_is_different()
{
GivenHeldRelease(_release.Title + "-RP", _release.Indexer, _release.PublishDate);
Subject.Add(_temporarilyRejected);
VerifyInsert();
}
[Test]
public void should_add_if_indexer_is_different()
{
GivenHeldRelease(_release.Title, "AnotherIndexer", _release.PublishDate);
Subject.Add(_temporarilyRejected);
VerifyInsert();
}
[Test]
public void should_add_if_publish_date_is_different()
{
GivenHeldRelease(_release.Title, _release.Indexer, _release.PublishDate.AddHours(1));
Subject.Add(_temporarilyRejected);
VerifyInsert();
}
private void VerifyInsert()
{
Mocker.GetMock<IPendingReleaseRepository>()
.Verify(v => v.Insert(It.IsAny<PendingRelease>()), Times.Once());
}
private void VerifyNoInsert()
{
Mocker.GetMock<IPendingReleaseRepository>()
.Verify(v => v.Insert(It.IsAny<PendingRelease>()), Times.Never());
}
}
}

@ -1,4 +1,5 @@
using NUnit.Framework;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.History;
using NzbDrone.Core.Qualities;
@ -10,14 +11,14 @@ namespace NzbDrone.Core.Test.HistoryTests
{
public class HistoryServiceFixture : CoreTest<HistoryService>
{
private QualityProfile _profile;
private QualityProfile _profileCustom;
private Profile _profile;
private Profile _profileCustom;
[SetUp]
public void Setup()
{
_profile = new QualityProfile { Cutoff = Quality.WEBDL720p, Items = QualityFixture.GetDefaultQualities() };
_profileCustom = new QualityProfile { Cutoff = Quality.WEBDL720p, Items = QualityFixture.GetDefaultQualities(Quality.DVD) };
_profile = new Profile { Cutoff = Quality.WEBDL720p, Items = QualityFixture.GetDefaultQualities() };
_profileCustom = new Profile { Cutoff = Quality.WEBDL720p, Items = QualityFixture.GetDefaultQualities(Quality.DVD) };
}
[Test]

@ -0,0 +1,42 @@
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Blacklisting;
using NzbDrone.Core.Housekeeping.Housekeepers;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
{
[TestFixture]
public class CleanupOrphanedBlacklistFixture : DbTest<CleanupOrphanedBlacklist, Blacklist>
{
[Test]
public void should_delete_orphaned_blacklist_items()
{
var blacklist = Builder<Blacklist>.CreateNew()
.BuildNew();
Db.Insert(blacklist);
Subject.Clean();
AllStoredModels.Should().BeEmpty();
}
[Test]
public void should_not_delete_unorphaned_blacklist_items()
{
var series = Builder<Series>.CreateNew().BuildNew();
Db.Insert(series);
var blacklist = Builder<Blacklist>.CreateNew()
.With(b => b.SeriesId = series.Id)
.BuildNew();
Db.Insert(blacklist);
Subject.Clean();
AllStoredModels.Should().HaveCount(1);
}
}
}

@ -0,0 +1,42 @@
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Housekeeping.Housekeepers;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
{
[TestFixture]
public class CleanupOrphanedPendingReleasesFixture : DbTest<CleanupOrphanedPendingReleases, PendingRelease>
{
[Test]
public void should_delete_orphaned_pending_items()
{
var pendingRelease = Builder<PendingRelease>.CreateNew()
.BuildNew();
Db.Insert(pendingRelease);
Subject.Clean();
AllStoredModels.Should().BeEmpty();
}
[Test]
public void should_not_delete_unorphaned_pending_items()
{
var series = Builder<Series>.CreateNew().BuildNew();
Db.Insert(series);
var pendingRelease = Builder<PendingRelease>.CreateNew()
.With(h => h.SeriesId = series.Id)
.BuildNew();
Db.Insert(pendingRelease);
Subject.Clean();
AllStoredModels.Should().HaveCount(1);
}
}
}

@ -1,4 +1,5 @@
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.Test.Framework;
using FizzWare.NBuilder;
@ -30,9 +31,9 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
.Setup(s => s.GetAvailableProviders())
.Returns(new List<IIndexer> { indexer.Object });
Mocker.GetMock<DecisionEngine.IMakeDownloadDecision>()
Mocker.GetMock<IMakeDownloadDecision>()
.Setup(s => s.GetSearchDecision(It.IsAny<List<Parser.Model.ReleaseInfo>>(), It.IsAny<SearchCriteriaBase>()))
.Returns(new List<DecisionEngine.Specifications.DownloadDecision>());
.Returns(new List<DownloadDecision>());
_xemSeries = Builder<Series>.CreateNew()
.With(v => v.UseSceneNumbering = true)

@ -9,6 +9,7 @@ using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
@ -65,7 +66,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
_videoFiles = new List<string> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi" };
_series = Builder<Series>.CreateNew()
.With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() })
.With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() })
.Build();
_quality = new QualityModel(Quality.DVD);

@ -6,6 +6,7 @@ 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.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
@ -22,8 +23,8 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
public void Setup()
{
_series = Builder<Series>.CreateNew()
.With(s => s.SeriesType = SeriesTypes.Standard)
.With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() })
.With(s => s.SeriesType = SeriesTypes.Standard)
.With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() })
.Build();
_localEpisode = new LocalEpisode

@ -9,6 +9,7 @@ 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.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
@ -29,7 +30,7 @@ namespace NzbDrone.Core.Test.MediaFiles
_approvedDecisions = new List<ImportDecision>();
var series = Builder<Series>.CreateNew()
.With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() })
.With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() })
.Build();
var episodes = Builder<Episode>.CreateListOfSize(5)

@ -111,6 +111,7 @@
<Compile Include="DecisionEngineTests\NotInQueueSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\CutoffSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\NotRestrictedReleaseSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\RssSync\DelaySpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\RssSync\ProperSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\Search\SeriesSpecificationFixture.cs" />
<Compile Include="Download\DownloadApprovedReportsTests\DownloadApprovedFixture.cs" />
@ -123,6 +124,7 @@
<Compile Include="Download\DownloadServiceFixture.cs" />
<Compile Include="Download\CompletedDownloadServiceFixture.cs" />
<Compile Include="Download\FailedDownloadServiceFixture.cs" />
<Compile Include="Download\Pending\PendingReleaseServiceTests\ProcessFixture.cs" />
<Compile Include="Framework\CoreTest.cs" />
<Compile Include="Framework\DbTest.cs" />
<Compile Include="Framework\NBuilderExtensions.cs" />
@ -141,6 +143,8 @@
<Compile Include="Housekeeping\Housekeepers\CleanupAdditionalNamingSpecsFixture.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedMetadataFilesFixture.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupDuplicateMetadataFilesFixture.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedBlacklistFixture.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedPendingReleasesFixture.cs" />
<Compile Include="Housekeeping\Housekeepers\FixFutureRunScheduledTasksFixture.cs" />
<Compile Include="IndexerSearchTests\NzbSearchServiceFixture.cs" />
<Compile Include="IndexerSearchTests\SearchDefinitionFixture.cs" />
@ -209,7 +213,7 @@
<Compile Include="ParserTests\SeriesTitleInfoFixture.cs" />
<Compile Include="Providers\XemProxyFixture.cs" />
<Compile Include="Qualities\QualityDefinitionRepositoryFixture.cs" />
<Compile Include="Qualities\QualityProfileRepositoryFixture.cs" />
<Compile Include="Profiles\ProfileRepositoryFixture.cs" />
<Compile Include="RootFolderTests\FreeSpaceOnDrivesFixture.cs" />
<Compile Include="Qualities\QualityFixture.cs" />
<Compile Include="ParserTests\QualityParserFixture.cs" />
@ -242,7 +246,7 @@
<Compile Include="MediaFiles\DownloadedEpisodesImportServiceFixture.cs" />
<Compile Include="SeriesStatsTests\SeriesStatisticsFixture.cs" />
<Compile Include="TvTests\RefreshSeriesServiceFixture.cs" />
<Compile Include="TvTests\SeriesRepositoryTests\QualityProfileRepositoryFixture.cs" />
<Compile Include="TvTests\SeriesRepositoryTests\SeriesRepositoryFixture.cs" />
<Compile Include="TvTests\SeriesServiceTests\UpdateMultipleSeriesFixture.cs" />
<Compile Include="TvTests\SeriesServiceTests\UpdateSeriesFixture.cs" />
<Compile Include="TvTests\ShouldRefreshSeriesFixture.cs" />
@ -260,11 +264,10 @@
<Compile Include="HistoryTests\HistoryRepositoryFixture.cs" />
<Compile Include="MediaFiles\MediaFileServiceTest.cs" />
<Compile Include="Configuration\ConfigServiceFixture.cs" />
<Compile Include="TvTests\EpisodeProviderTests\EpisodeProviderTest.cs" />
<Compile Include="Framework\TestDbHelper.cs" />
<Compile Include="ParserTests\ParserFixture.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Qualities\QualityProfileServiceFixture.cs" />
<Compile Include="Profiles\ProfileServiceFixture.cs" />
<Compile Include="TvTests\SeriesServiceTests\AddSeriesFixture.cs" />
<Compile Include="UpdateTests\UpdatePackageProviderFixture.cs" />
<Compile Include="XbmcVersionTests.cs" />

@ -1,20 +1,20 @@
using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Qualities
namespace NzbDrone.Core.Test.Profiles
{
[TestFixture]
public class QualityProfileRepositoryFixture : DbTest<QualityProfileRepository, QualityProfile>
public class ProfileRepositoryFixture : DbTest<ProfileRepository, Profile>
{
[Test]
public void should_be_able_to_read_and_write()
{
var profile = new QualityProfile
var profile = new Profile
{
Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p),
Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p),
Cutoff = Quality.Bluray1080p,
Name = "TestProfile"
};
@ -23,8 +23,8 @@ namespace NzbDrone.Core.Test.Qualities
StoredModel.Name.Should().Be(profile.Name);
StoredModel.Cutoff.Should().Be(profile.Cutoff);
StoredModel.Items.Should().Equal(profile.Items, (a,b) => a.Quality == b.Quality && a.Allowed == b.Allowed);
StoredModel.Items.Should().Equal(profile.Items, (a, b) => a.Quality == b.Quality && a.Allowed == b.Allowed);
}

@ -3,23 +3,24 @@ using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Qualities
namespace NzbDrone.Core.Test.Profiles
{
[TestFixture]
public class QualityProfileServiceFixture : CoreTest<QualityProfileService>
public class ProfileServiceFixture : CoreTest<ProfileService>
{
[Test]
public void Init_should_add_two_profiles()
{
Subject.Handle(new ApplicationStartedEvent());
Mocker.GetMock<IQualityProfileRepository>()
.Verify(v => v.Insert(It.IsAny<QualityProfile>()), Times.Exactly(4));
Mocker.GetMock<IProfileRepository>()
.Verify(v => v.Insert(It.IsAny<Profile>()), Times.Exactly(4));
}
[Test]
@ -27,14 +28,14 @@ namespace NzbDrone.Core.Test.Qualities
//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<IQualityProfileRepository>()
Mocker.GetMock<IProfileRepository>()
.Setup(s => s.All())
.Returns(Builder<QualityProfile>.CreateListOfSize(2).Build().ToList());
.Returns(Builder<Profile>.CreateListOfSize(2).Build().ToList());
Subject.Handle(new ApplicationStartedEvent());
Mocker.GetMock<IQualityProfileRepository>()
.Verify(v => v.Insert(It.IsAny<QualityProfile>()), Times.Never());
Mocker.GetMock<IProfileRepository>()
.Verify(v => v.Insert(It.IsAny<Profile>()), Times.Never());
}
@ -43,15 +44,15 @@ namespace NzbDrone.Core.Test.Qualities
{
var seriesList = Builder<Series>.CreateListOfSize(3)
.Random(1)
.With(c => c.QualityProfileId = 2)
.With(c => c.ProfileId = 2)
.Build().ToList();
Mocker.GetMock<ISeriesService>().Setup(c => c.GetAllSeries()).Returns(seriesList);
Assert.Throws<QualityProfileInUseException>(() => Subject.Delete(2));
Assert.Throws<ProfileInUseException>(() => Subject.Delete(2));
Mocker.GetMock<IQualityProfileRepository>().Verify(c => c.Delete(It.IsAny<int>()), Times.Never());
Mocker.GetMock<IProfileRepository>().Verify(c => c.Delete(It.IsAny<int>()), Times.Never());
}
@ -61,7 +62,7 @@ namespace NzbDrone.Core.Test.Qualities
{
var seriesList = Builder<Series>.CreateListOfSize(3)
.All()
.With(c => c.QualityProfileId = 2)
.With(c => c.ProfileId = 2)
.Build().ToList();
@ -69,7 +70,7 @@ namespace NzbDrone.Core.Test.Qualities
Subject.Delete(1);
Mocker.GetMock<IQualityProfileRepository>().Verify(c => c.Delete(1), Times.Once());
Mocker.GetMock<IProfileRepository>().Verify(c => c.Delete(1), Times.Once());
}
}
}

@ -2,6 +2,7 @@
using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
@ -44,7 +45,7 @@ namespace NzbDrone.Core.Test.Qualities
i.Should().Be(expected);
}
public static List<QualityProfileItem> GetDefaultQualities(params Quality[] allowed)
public static List<ProfileQualityItem> GetDefaultQualities(params Quality[] allowed)
{
var qualities = new List<Quality>
{
@ -66,7 +67,7 @@ namespace NzbDrone.Core.Test.Qualities
var items = qualities
.Except(allowed)
.Concat(allowed)
.Select(v => new QualityProfileItem { Quality = v, Allowed = allowed.Contains(v) }).ToList();
.Select(v => new ProfileQualityItem { Quality = v, Allowed = allowed.Contains(v) }).ToList();
return items;
}

@ -2,6 +2,7 @@
using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Test.Framework;
@ -13,20 +14,20 @@ namespace NzbDrone.Core.Test.Qualities
{
public QualityModelComparer Subject { get; set; }
private void GivenDefaultQualityProfile()
private void GivenDefaultProfile()
{
Subject = new QualityModelComparer(new QualityProfile { Items = QualityFixture.GetDefaultQualities() });
Subject = new QualityModelComparer(new Profile { Items = QualityFixture.GetDefaultQualities() });
}
private void GivenCustomQualityProfile()
private void GivenCustomProfile()
{
Subject = new QualityModelComparer(new QualityProfile { Items = QualityFixture.GetDefaultQualities(Quality.Bluray720p, Quality.DVD) });
Subject = new QualityModelComparer(new Profile { Items = QualityFixture.GetDefaultQualities(Quality.Bluray720p, Quality.DVD) });
}
[Test]
public void Icomparer_greater_test()
{
GivenDefaultQualityProfile();
GivenDefaultProfile();
var first = new QualityModel(Quality.DVD, true);
var second = new QualityModel(Quality.Bluray1080p, true);
@ -39,7 +40,7 @@ namespace NzbDrone.Core.Test.Qualities
[Test]
public void Icomparer_greater_proper()
{
GivenDefaultQualityProfile();
GivenDefaultProfile();
var first = new QualityModel(Quality.Bluray1080p, false);
var second = new QualityModel(Quality.Bluray1080p, true);
@ -52,7 +53,7 @@ namespace NzbDrone.Core.Test.Qualities
[Test]
public void Icomparer_lesser()
{
GivenDefaultQualityProfile();
GivenDefaultProfile();
var first = new QualityModel(Quality.DVD, true);
var second = new QualityModel(Quality.Bluray1080p, true);
@ -65,7 +66,7 @@ namespace NzbDrone.Core.Test.Qualities
[Test]
public void Icomparer_lesser_proper()
{
GivenDefaultQualityProfile();
GivenDefaultProfile();
var first = new QualityModel(Quality.DVD, false);
var second = new QualityModel(Quality.DVD, true);
@ -78,7 +79,7 @@ namespace NzbDrone.Core.Test.Qualities
[Test]
public void Icomparer_greater_custom_order()
{
GivenCustomQualityProfile();
GivenCustomProfile();
var first = new QualityModel(Quality.DVD, true);
var second = new QualityModel(Quality.Bluray720p, true);

@ -4,6 +4,7 @@ using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Qualities;
@ -22,15 +23,15 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
[SetUp]
public void Setup()
{
var qualityProfile = new QualityProfile
var profile = new Profile
{
Id = 1,
Cutoff = Quality.WEBDL480p,
Items = new List<QualityProfileItem>
Items = new List<ProfileQualityItem>
{
new QualityProfileItem { Allowed = true, Quality = Quality.SDTV },
new QualityProfileItem { Allowed = true, Quality = Quality.WEBDL480p },
new QualityProfileItem { Allowed = true, Quality = Quality.RAWHD }
new ProfileQualityItem { Allowed = true, Quality = Quality.SDTV },
new ProfileQualityItem { Allowed = true, Quality = Quality.WEBDL480p },
new ProfileQualityItem { Allowed = true, Quality = Quality.RAWHD }
}
};
@ -39,7 +40,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
.With(s => s.Runtime = 30)
.With(s => s.Monitored = true)
.With(s => s.TitleSlug = "Title3")
.With(s => s.Id = qualityProfile.Id)
.With(s => s.Id = profile.Id)
.BuildNew();
_unmonitoredSeries = Builder<Series>.CreateNew()
@ -47,7 +48,7 @@ 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 = qualityProfile.Id)
.With(s => s.Id = profile.Id)
.BuildNew();
_monitoredSeries.Id = Db.Insert(_monitoredSeries).Id;
@ -63,7 +64,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
_qualitiesBelowCutoff = new List<QualitiesBelowCutoff>
{
new QualitiesBelowCutoff(qualityProfile.Id, new[] {Quality.SDTV.Id})
new QualitiesBelowCutoff(profile.Id, new[] {Quality.SDTV.Id})
};
var qualityMet = new EpisodeFile { Path = "a", Quality = new QualityModel { Quality = Quality.WEBDL480p } };

@ -1,7 +1,7 @@
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
@ -15,7 +15,7 @@ namespace NzbDrone.Core.Test.TvTests.SeriesRepositoryTests
[Test]
public void should_lazyload_quality_profile()
{
var profile = new QualityProfile
var profile = new Profile
{
Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p),
@ -24,15 +24,15 @@ namespace NzbDrone.Core.Test.TvTests.SeriesRepositoryTests
};
Mocker.Resolve<QualityProfileRepository>().Insert(profile);
Mocker.Resolve<ProfileRepository>().Insert(profile);
var series = Builder<Series>.CreateNew().BuildNew();
series.QualityProfileId = profile.Id;
series.ProfileId = profile.Id;
Subject.Insert(series);
StoredModel.QualityProfile.Should().NotBeNull();
StoredModel.Profile.Should().NotBeNull();
}

@ -20,7 +20,7 @@ namespace NzbDrone.Core.Test.TvTests.SeriesServiceTests
{
_series = Builder<Series>.CreateListOfSize(5)
.All()
.With(s => s.QualityProfileId = 1)
.With(s => s.ProfileId = 1)
.With(s => s.Monitored)
.With(s => s.SeasonFolder)
.With(s => s.Path = @"C:\Test\name".AsOsAgnostic())

@ -17,7 +17,6 @@ namespace NzbDrone.Core.Datastore.Extensions
query: (db, parent) => db.Query<TChild>().SingleOrDefault(c => c.Id == childIdSelector(parent)),
condition: parent => childIdSelector(parent) > 0
);
}
public static RelationshipBuilder<TParent> Relationship<TParent>(this ColumnMapBuilder<TParent> mapBuilder)
@ -25,16 +24,12 @@ namespace NzbDrone.Core.Datastore.Extensions
return mapBuilder.Relationships.AutoMapComplexTypeProperties<ILazyLoaded>();
}
public static RelationshipBuilder<TParent> HasMany<TParent, TChild>(this RelationshipBuilder<TParent> relationshipBuilder, Expression<Func<TParent, LazyList<TChild>>> portalExpression, Func<TParent, int> childIdSelector)
where TParent : ModelBase
where TChild : ModelBase
{
return relationshipBuilder.For(portalExpression.GetMemberName())
.LazyLoad((db, parent) => db.Query<TChild>().Where(c => c.Id == childIdSelector(parent)).ToList());
}
private static string GetMemberName<T, TMember>(this Expression<Func<T, TMember>> member)

@ -3,6 +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.Qualities;
using System.Collections.Generic;
using NzbDrone.Core.Datastore.Converters;
@ -41,7 +42,7 @@ namespace NzbDrone.Core.Datastore.Migration
var allowed = Json.Deserialize<List<Quality>>(allowedJson);
var items = Quality.DefaultQualityDefinitions.OrderBy(v => v.Weight).Select(v => new QualityProfileItem { Quality = v.Quality, Allowed = allowed.Contains(v.Quality) }).ToList();
var items = Quality.DefaultQualityDefinitions.OrderBy(v => v.Weight).Select(v => new ProfileQualityItem { Quality = v.Quality, Allowed = allowed.Contains(v.Quality) }).ToList();
var allowedNewJson = qualityProfileItemConverter.ToDB(items);

@ -0,0 +1,31 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(54)]
public class rename_profiles : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Rename.Table("QualityProfiles").To("Profiles");
Alter.Table("Profiles").AddColumn("Language").AsInt32().Nullable();
Alter.Table("Profiles").AddColumn("GrabDelay").AsInt32().Nullable();
Alter.Table("Profiles").AddColumn("GrabDelayMode").AsInt32().Nullable();
Execute.Sql("UPDATE Profiles SET Language = 1, GrabDelay = 0, GrabDelayMode = 0");
//Rename QualityProfileId in Series
Alter.Table("Series").AddColumn("ProfileId").AsInt32().Nullable();
Execute.Sql("UPDATE Series SET ProfileId = QualityProfileId");
//Add HeldReleases
Create.TableForModel("PendingReleases")
.WithColumn("SeriesId").AsInt32()
.WithColumn("Title").AsString()
.WithColumn("Added").AsDateTime()
.WithColumn("ParsedEpisodeInfo").AsString()
.WithColumn("Release").AsString();
}
}
}

@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(55)]
public class drop_old_profile_columns : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
SqLiteAlter.DropColumns("Series", new[] { "QualityProfileId" });
}
}
}

@ -33,7 +33,12 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
public string CreateSql(string tableName)
{
return string.Format(@"CREATE UNIQUE INDEX ""{2}"" ON ""{0}"" (""{1}"" ASC)", tableName, Column, IndexName);
if (Unique)
{
return String.Format(@"CREATE UNIQUE INDEX ""{2}"" ON ""{0}"" (""{1}"" ASC)", tableName, Column, IndexName);
}
return String.Format(@"CREATE INDEX ""{2}"" ON ""{0}"" (""{1}"" ASC)", tableName, Column, IndexName);
}
}
}

@ -147,14 +147,12 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
}
}
public void DropTable(string tableName)
{
var dropCommand = BuildCommand("DROP TABLE {0};", tableName);
dropCommand.ExecuteNonQuery();
}
public void RenameTable(string tableName, string newName)
{
var renameCommand = BuildCommand("ALTER TABLE {0} RENAME TO {1};", tableName, newName);
@ -184,7 +182,6 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
return Convert.ToInt32(countCommand.ExecuteScalar());
}
public SQLiteTransaction BeginTransaction()
{
return _connection.BeginTransaction();
@ -197,7 +194,6 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
return command;
}
public void ExecuteNonQuery(string command, params string[] args)
{
var sqLiteCommand = new SQLiteCommand(string.Format(command, args))
@ -226,7 +222,5 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
}
}
}
}

@ -9,6 +9,7 @@ using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Datastore.Converters;
using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Instrumentation;
using NzbDrone.Core.Jobs;
@ -17,6 +18,8 @@ using NzbDrone.Core.Metadata;
using NzbDrone.Core.Metadata.Files;
using NzbDrone.Core.Notifications;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.SeriesStats;
@ -27,7 +30,6 @@ namespace NzbDrone.Core.Datastore
{
public static class TableMapping
{
private static readonly FluentMappings Mapper = new FluentMappings(true);
public static void Map()
@ -52,7 +54,7 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<Series>().RegisterModel("Series")
.Ignore(s => s.RootFolderPath)
.Relationship()
.HasOne(s => s.QualityProfile, s => s.QualityProfileId);
.HasOne(s => s.Profile, s => s.ProfileId);
Mapper.Entity<Episode>().RegisterModel("Episodes")
.Ignore(e => e.SeriesTitle)
@ -64,14 +66,16 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<EpisodeFile>().RegisterModel("EpisodeFiles")
.Relationships.AutoMapICollectionOrComplexProperties();
Mapper.Entity<QualityProfile>().RegisterModel("QualityProfiles");
Mapper.Entity<Profile>().RegisterModel("Profiles");
Mapper.Entity<QualityDefinition>().RegisterModel("QualityDefinitions");
Mapper.Entity<Log>().RegisterModel("Logs");
Mapper.Entity<NamingConfig>().RegisterModel("NamingConfig");
Mapper.Entity<SeriesStatistics>().MapResultSet();
Mapper.Entity<Blacklist>().RegisterModel("Blacklist");
Mapper.Entity<MetadataFile>().RegisterModel("MetadataFiles");
Mapper.Entity<PendingRelease>().RegisterModel("PendingReleases")
.Ignore(e => e.RemoteEpisode);
}
private static void RegisterMappers()
@ -84,11 +88,13 @@ namespace NzbDrone.Core.Datastore
MapRepository.Instance.RegisterTypeConverter(typeof(Boolean), new BooleanIntConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(Enum), new EnumIntConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(Quality), new QualityIntConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<QualityProfileItem>), new EmbeddedDocumentConverter(new QualityIntConverter()));
MapRepository.Instance.RegisterTypeConverter(typeof(List<ProfileQualityItem>), new EmbeddedDocumentConverter(new QualityIntConverter()));
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(List<string>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(ParsedEpisodeInfo), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(ReleaseInfo), new EmbeddedDocumentConverter());
}
private static void RegisterProviderSettingConverter()

@ -2,12 +2,12 @@ using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications
namespace NzbDrone.Core.DecisionEngine
{
public class DownloadDecision
{
public RemoteEpisode RemoteEpisode { get; private set; }
public IEnumerable<string> Rejections { get; private set; }
public IEnumerable<Rejection> Rejections { get; private set; }
public bool Approved
{
@ -17,13 +17,28 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
}
}
public DownloadDecision(RemoteEpisode episode, params string[] rejections)
public bool TemporarilyRejected
{
RemoteEpisode = episode;
Rejections = rejections.ToList();
get
{
return Rejections.Any() && Rejections.All(r => r.Type == RejectionType.Temporary);
}
}
public bool Rejected
{
get
{
return Rejections.Any() && Rejections.All(r => r.Type == RejectionType.Permanent);
}
}
public DownloadDecision(RemoteEpisode episode, params Rejection[] rejections)
{
RemoteEpisode = episode;
Rejections = rejections.ToList();
}
public override string ToString()
{
if (Approved)

@ -4,7 +4,6 @@ using System.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Instrumentation.Extensions;
using NzbDrone.Core.Parser;
@ -17,6 +16,7 @@ namespace NzbDrone.Core.DecisionEngine
{
List<DownloadDecision> GetRssDecision(List<ReleaseInfo> reports);
List<DownloadDecision> GetSearchDecision(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteriaBase);
DownloadDecision GetDecisionForReport(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria = null);
}
public class DownloadDecisionMaker : IMakeDownloadDecision
@ -87,7 +87,7 @@ namespace NzbDrone.Core.DecisionEngine
}
else
{
decision = new DownloadDecision(remoteEpisode, "Unknown Series");
decision = new DownloadDecision(remoteEpisode, new Rejection("Unknown Series"));
}
}
}
@ -110,19 +110,19 @@ namespace NzbDrone.Core.DecisionEngine
}
}
private DownloadDecision GetDecisionForReport(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria = null)
public DownloadDecision GetDecisionForReport(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria = null)
{
var reasons = _specifications.Select(c => EvaluateSpec(c, remoteEpisode, searchCriteria))
.Where(c => !string.IsNullOrWhiteSpace(c));
.Where(c => c != null);
return new DownloadDecision(remoteEpisode, reasons.ToArray());
}
private string EvaluateSpec(IRejectWithReason spec, RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteriaBase = null)
private Rejection EvaluateSpec(IRejectWithReason spec, RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteriaBase = null)
{
try
{
if (string.IsNullOrWhiteSpace(spec.RejectionReason))
if (spec.RejectionReason.IsNullOrWhiteSpace())
{
throw new InvalidOperationException("[Need Rejection Text]");
}
@ -130,7 +130,7 @@ namespace NzbDrone.Core.DecisionEngine
var generalSpecification = spec as IDecisionEngineSpecification;
if (generalSpecification != null && !generalSpecification.IsSatisfiedBy(remoteEpisode, searchCriteriaBase))
{
return spec.RejectionReason;
return new Rejection(spec.RejectionReason, generalSpecification.Type);
}
}
catch (Exception e)
@ -138,7 +138,7 @@ namespace NzbDrone.Core.DecisionEngine
e.Data.Add("report", remoteEpisode.Release.ToJson());
e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson());
_logger.ErrorException("Couldn't evaluate decision on " + remoteEpisode.Release.Title, e);
return string.Format("{0}: {1}", spec.GetType().Name, e.Message);
return new Rejection(String.Format("{0}: {1}", spec.GetType().Name, e.Message));
}
return null;

@ -19,7 +19,7 @@ namespace NzbDrone.Core.DecisionEngine
return decisions
.Where(c => c.RemoteEpisode.Series != null)
.GroupBy(c => c.RemoteEpisode.Series.Id, (i, s) => s
.OrderByDescending(c => c.RemoteEpisode.ParsedEpisodeInfo.Quality, new QualityModelComparer(s.First().RemoteEpisode.Series.QualityProfile))
.OrderByDescending(c => c.RemoteEpisode.ParsedEpisodeInfo.Quality, new QualityModelComparer(s.First().RemoteEpisode.Series.Profile))
.ThenBy(c => c.RemoteEpisode.Episodes.Select(e => e.EpisodeNumber).MinOrDefault())
.ThenBy(c => c.RemoteEpisode.Release.Size.Round(200.Megabytes()) / Math.Max(1, c.RemoteEpisode.Episodes.Count))
.ThenBy(c => c.RemoteEpisode.Release.Age))

@ -1,3 +1,4 @@
using System;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
@ -5,6 +6,8 @@ namespace NzbDrone.Core.DecisionEngine
{
public interface IDecisionEngineSpecification : IRejectWithReason
{
bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria);
RejectionType Type { get; }
Boolean IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria);
}
}

@ -4,4 +4,4 @@ namespace NzbDrone.Core.DecisionEngine
{
string RejectionReason { get; }
}
}
}

@ -1,12 +1,13 @@
using NLog;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.DecisionEngine
{
public interface IQualityUpgradableSpecification
{
bool IsUpgradable(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null);
bool CutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null);
bool IsUpgradable(Profile profile, QualityModel currentQuality, QualityModel newQuality = null);
bool CutoffNotMet(Profile profile, QualityModel currentQuality, QualityModel newQuality = null);
bool IsProperUpgrade(QualityModel currentQuality, QualityModel newQuality);
}
@ -19,7 +20,7 @@ namespace NzbDrone.Core.DecisionEngine
_logger = logger;
}
public bool IsUpgradable(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null)
public bool IsUpgradable(Profile profile, QualityModel currentQuality, QualityModel newQuality = null)
{
if (newQuality != null)
{
@ -39,7 +40,7 @@ namespace NzbDrone.Core.DecisionEngine
return true;
}
public bool CutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null)
public bool CutoffNotMet(Profile profile, QualityModel currentQuality, QualityModel newQuality = null)
{
int compare = new QualityModelComparer(profile).Compare(currentQuality.Quality, profile.Cutoff);

@ -0,0 +1,21 @@
using System;
namespace NzbDrone.Core.DecisionEngine
{
public class Rejection
{
public String Reason { get; set; }
public RejectionType Type { get; set; }
public Rejection(string reason, RejectionType type = RejectionType.Permanent)
{
Reason = reason;
Type = type;
}
public override string ToString()
{
return String.Format("[{0}] {1}", Type, Reason);
}
}
}

@ -0,0 +1,8 @@
namespace NzbDrone.Core.DecisionEngine
{
public enum RejectionType
{
Permanent = 0,
Temporary = 1
}
}

@ -1,3 +1,4 @@
using System;
using System.Linq;
using NLog;
using NzbDrone.Core.IndexerSearch.Definitions;
@ -21,12 +22,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
_logger = logger;
}
public string RejectionReason
public String RejectionReason
{
get { return "File size too big or small"; }
}
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public RejectionType Type { get { return RejectionType.Permanent; } }
public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
_logger.Debug("Beginning size check for: {0}", subject);

@ -27,6 +27,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
}
}
public RejectionType Type { get { return RejectionType.Permanent; } }
public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (!_configService.EnableFailedDownloadHandling)

@ -24,6 +24,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
}
}
public RejectionType Type { get { return RejectionType.Permanent; } }
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
@ -31,7 +33,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
_logger.Debug("Comparing file quality with report. Existing file is {0}", file.Quality);
if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Series.QualityProfile, file.Quality, subject.ParsedEpisodeInfo.Quality))
if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Series.Profile, file.Quality, subject.ParsedEpisodeInfo.Quality))
{
_logger.Debug("Cutoff already met, rejecting.");
return false;

@ -1,6 +1,5 @@
using NLog;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications
@ -18,16 +17,21 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
get
{
return "Not English";
return "Language is not wanted";
}
}
public RejectionType Type { get { return RejectionType.Permanent; } }
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
var wantedLanguage = subject.Series.Profile.Value.Language;
_logger.Debug("Checking if report meets language requirements. {0}", subject.ParsedEpisodeInfo.Language);
if (subject.ParsedEpisodeInfo.Language != Language.English)
if (subject.ParsedEpisodeInfo.Language != wantedLanguage)
{
_logger.Debug("Report Language: {0} rejected because it is not English", subject.ParsedEpisodeInfo.Language);
_logger.Debug("Report Language: {0} rejected because it is not wanted, wanted {1}", subject.ParsedEpisodeInfo.Language, wantedLanguage);
return false;
}

@ -28,6 +28,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
}
}
public RejectionType Type { get { return RejectionType.Permanent; } }
public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
var queue = _downloadTrackingService.GetQueuedDownloads()
@ -46,7 +48,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
private bool IsInQueue(RemoteEpisode newEpisode, IEnumerable<RemoteEpisode> queue)
{
var matchingSeries = queue.Where(q => q.Series.Id == newEpisode.Series.Id);
var matchingSeriesAndQuality = matchingSeries.Where(q => new QualityModelComparer(q.Series.QualityProfile).Compare(q.ParsedEpisodeInfo.Quality, newEpisode.ParsedEpisodeInfo.Quality) >= 0);
var matchingSeriesAndQuality = matchingSeries.Where(q => new QualityModelComparer(q.Series.Profile).Compare(q.ParsedEpisodeInfo.Quality, newEpisode.ParsedEpisodeInfo.Quality) >= 0);
return matchingSeriesAndQuality.Any(q => q.Episodes.Select(e => e.Id).Intersect(newEpisode.Episodes.Select(e => e.Id)).Any());
}

@ -25,6 +25,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
}
}
public RejectionType Type { get { return RejectionType.Permanent; } }
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
_logger.Debug("Checking if release contains any restricted terms: {0}", subject);

@ -7,7 +7,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public class NotSampleSpecification : IDecisionEngineSpecification
{
private readonly Logger _logger;
public string RejectionReason { get { return "Sample"; } }
public RejectionType Type { get { return RejectionType.Permanent; } }
public NotSampleSpecification(Logger logger)
{

@ -21,10 +21,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
}
}
public RejectionType Type { get { return RejectionType.Permanent; } }
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
_logger.Debug("Checking if report meets quality requirements. {0}", subject.ParsedEpisodeInfo.Quality);
if (!subject.Series.QualityProfile.Value.Items.Exists(v => v.Allowed && v.Quality == subject.ParsedEpisodeInfo.Quality.Quality))
if (!subject.Series.Profile.Value.Items.Exists(v => v.Allowed && v.Quality == subject.ParsedEpisodeInfo.Quality.Quality))
{
_logger.Debug("Quality {0} rejected by Series' quality profile", subject.ParsedEpisodeInfo.Quality);
return false;

@ -25,6 +25,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
}
}
public RejectionType Type { get { return RejectionType.Permanent; } }
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
var age = subject.Release.Age;

@ -30,6 +30,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
}
}
public RejectionType Type { get { return RejectionType.Permanent; } }
public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (!_configService.EnableFailedDownloadHandling)

@ -0,0 +1,116 @@
using System.Linq;
using NLog;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
{
public class DelaySpecification : IDecisionEngineSpecification
{
private readonly IPendingReleaseService _pendingReleaseService;
private readonly Logger _logger;
public DelaySpecification(IPendingReleaseService pendingReleaseService, Logger logger)
{
_pendingReleaseService = pendingReleaseService;
_logger = logger;
}
public string RejectionReason
{
get
{
return "Waiting for better quality release";
}
}
public RejectionType Type { get { return RejectionType.Temporary; } }
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
//How do we want to handle drone being off and the automatic search being triggered?
//TODO: Add a flag to the search to state it is a "scheduled" search
if (searchCriteria != null)
{
_logger.Debug("Ignore delay for searches");
return true;
}
var profile = subject.Series.Profile.Value;
if (profile.GrabDelay == 0)
{
_logger.Debug("Profile does not delay before download");
return true;
}
var comparer = new QualityModelComparer(profile);
if (subject.ParsedEpisodeInfo.Quality.Proper)
{
foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
{
if (comparer.Compare(subject.ParsedEpisodeInfo.Quality, file.Quality) > 0)
{
var properCompare = subject.ParsedEpisodeInfo.Quality.Proper.CompareTo(file.Quality.Proper);
if (subject.ParsedEpisodeInfo.Quality.Quality == file.Quality.Quality && properCompare > 0)
{
_logger.Debug("New quality is a proper for existing quality, skipping delay");
return true;
}
}
}
}
//If quality meets or exceeds the best allowed quality in the profile accept it immediately
var bestQualityInProfile = new QualityModel(profile.Items.Last(q => q.Allowed).Quality);
var bestCompare = comparer.Compare(subject.ParsedEpisodeInfo.Quality, bestQualityInProfile);
if (bestCompare >= 0)
{
_logger.Debug("Quality is highest in profile, will not delay");
return true;
}
if (profile.GrabDelayMode == GrabDelayMode.Cutoff)
{
var cutoff = new QualityModel(profile.Cutoff);
var cutoffCompare = comparer.Compare(subject.ParsedEpisodeInfo.Quality, cutoff);
if (cutoffCompare >= 0)
{
_logger.Debug("Quality meets or exceeds the cutoff, will not delay");
return true;
}
}
if (profile.GrabDelayMode == GrabDelayMode.First)
{
var episodeIds = subject.Episodes.Select(e => e.Id);
var oldest = _pendingReleaseService.GetPendingRemoteEpisodes(subject.Series.Id)
.Where(r => r.Episodes.Select(e => e.Id).Intersect(episodeIds).Any())
.OrderByDescending(p => p.Release.AgeHours)
.FirstOrDefault();
if (oldest != null && oldest.Release.AgeHours > profile.GrabDelay)
{
return true;
}
}
if (subject.Release.AgeHours < profile.GrabDelay)
{
_logger.Debug("Age ({0}) is less than delay {1}, delaying", subject.Release.AgeHours, profile.GrabDelay);
return false;
}
return true;
}
}
}

@ -34,6 +34,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
}
}
public RejectionType Type { get { return RejectionType.Permanent; } }
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (searchCriteria != null)
@ -63,11 +65,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
foreach (var episode in subject.Episodes)
{
var bestQualityInHistory = _historyService.GetBestQualityInHistory(subject.Series.QualityProfile, episode.Id);
var bestQualityInHistory = _historyService.GetBestQualityInHistory(subject.Series.Profile, episode.Id);
if (bestQualityInHistory != null)
{
_logger.Debug("Comparing history quality with report. History is {0}", bestQualityInHistory);
if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.QualityProfile, bestQualityInHistory, subject.ParsedEpisodeInfo.Quality))
if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.Profile, bestQualityInHistory, subject.ParsedEpisodeInfo.Quality))
return false;
}
}

@ -22,6 +22,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
}
}
public RejectionType Type { get { return RejectionType.Permanent; } }
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (searchCriteria != null)

@ -28,6 +28,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
}
}
public RejectionType Type { get { return RejectionType.Permanent; } }
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (searchCriteria != null)

@ -23,6 +23,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
return "Episode doesn't match";
}
}
public RejectionType Type { get { return RejectionType.Permanent; } }
public bool IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
{
if (searchCriteria == null)

@ -23,6 +23,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
}
}
public RejectionType Type { get { return RejectionType.Permanent; } }
public bool IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
{
if (searchCriteria == null)

@ -21,6 +21,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
}
}
public RejectionType Type { get { return RejectionType.Permanent; } }
public bool IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
{
if (searchCriteria == null)

@ -21,6 +21,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
}
}
public RejectionType Type { get { return RejectionType.Permanent; } }
public bool IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
{
if (searchCriteria == null)

@ -22,6 +22,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
}
}
public RejectionType Type { get { return RejectionType.Permanent; } }
public bool IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
{
if (searchCriteria == null)

@ -24,13 +24,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
}
}
public RejectionType Type { get { return RejectionType.Permanent; } }
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
{
_logger.Debug("Comparing file quality with report. Existing file is {0}", file.Quality);
if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.QualityProfile, file.Quality, subject.ParsedEpisodeInfo.Quality))
if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.Profile, file.Quality, subject.ParsedEpisodeInfo.Quality))
{
return false;
}

@ -1,67 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Download
{
public interface IDownloadApprovedReports
{
List<DownloadDecision> DownloadApproved(List<DownloadDecision> decisions);
}
public class DownloadApprovedReports : IDownloadApprovedReports
{
private readonly IDownloadService _downloadService;
private readonly IPrioritizeDownloadDecision _prioritizeDownloadDecision;
private readonly Logger _logger;
public DownloadApprovedReports(IDownloadService downloadService, IPrioritizeDownloadDecision prioritizeDownloadDecision, Logger logger)
{
_downloadService = downloadService;
_prioritizeDownloadDecision = prioritizeDownloadDecision;
_logger = logger;
}
public List<DownloadDecision> DownloadApproved(List<DownloadDecision> decisions)
{
var qualifiedReports = GetQualifiedReports(decisions);
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(qualifiedReports);
var downloadedReports = new List<DownloadDecision>();
foreach (var report in prioritizedDecisions)
{
var remoteEpisode = report.RemoteEpisode;
try
{
if (downloadedReports.SelectMany(r => r.RemoteEpisode.Episodes)
.Select(e => e.Id)
.ToList()
.Intersect(remoteEpisode.Episodes.Select(e => e.Id))
.Any())
{
continue;
}
_downloadService.DownloadReport(remoteEpisode);
downloadedReports.Add(report);
}
catch (Exception e)
{
_logger.WarnException("Couldn't add report to download queue. " + remoteEpisode, e);
}
}
return downloadedReports;
}
public List<DownloadDecision> GetQualifiedReports(IEnumerable<DownloadDecision> decisions)
{
return decisions.Where(c => c.Approved && c.RemoteEpisode.Episodes.Any()).ToList();
}
}
}

@ -1,8 +1,5 @@
using NzbDrone.Core.Parser.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Download
{

@ -0,0 +1,18 @@
using System;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download.Pending
{
public class PendingRelease : ModelBase
{
public Int32 SeriesId { get; set; }
public String Title { get; set; }
public DateTime Added { get; set; }
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
public ReleaseInfo Release { get; set; }
//Not persisted
public RemoteEpisode RemoteEpisode { get; set; }
}
}

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Download.Pending
{
public interface IPendingReleaseRepository : IBasicRepository<PendingRelease>
{
void DeleteBySeriesId(Int32 seriesId);
List<PendingRelease> AllBySeriesId(Int32 seriesId);
}
public class PendingReleaseRepository : BasicRepository<PendingRelease>, IPendingReleaseRepository
{
public PendingReleaseRepository(IDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
public void DeleteBySeriesId(Int32 seriesId)
{
Delete(r => r.SeriesId == seriesId);
}
public List<PendingRelease> AllBySeriesId(Int32 seriesId)
{
return Query.Where(p => p.SeriesId == seriesId);
}
}
}

@ -0,0 +1,219 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events;
namespace NzbDrone.Core.Download.Pending
{
public interface IPendingReleaseService
{
void Add(DownloadDecision decision);
void RemoveGrabbed(List<DownloadDecision> grabbed);
void RemoveRejected(List<DownloadDecision> rejected);
List<ReleaseInfo> GetPending();
List<RemoteEpisode> GetPendingRemoteEpisodes(Int32 seriesId);
List<Queue.Queue> GetPendingQueue();
}
public class PendingReleaseService : IPendingReleaseService, IHandle<SeriesDeletedEvent>
{
private readonly IPendingReleaseRepository _repository;
private readonly ISeriesService _seriesService;
private readonly IParsingService _parsingService;
private readonly IDownloadService _downloadService;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
public PendingReleaseService(IPendingReleaseRepository repository,
ISeriesService seriesService,
IParsingService parsingService,
IDownloadService downloadService,
IEventAggregator eventAggregator,
Logger logger)
{
_repository = repository;
_seriesService = seriesService;
_parsingService = parsingService;
_downloadService = downloadService;
_eventAggregator = eventAggregator;
_logger = logger;
}
public void Add(DownloadDecision decision)
{
var alreadyPending = GetPendingReleases();
var episodeIds = decision.RemoteEpisode.Episodes.Select(e => e.Id);
var existingReports = alreadyPending.Where(r => r.RemoteEpisode.Episodes.Select(e => e.Id)
.Intersect(episodeIds)
.Any());
if (existingReports.Any(MatchingReleasePredicate(decision)))
{
_logger.Debug("This release is already pending, not adding again");
return;
}
_logger.Debug("Adding release to pending releases");
Insert(decision);
}
public void RemoveGrabbed(List<DownloadDecision> grabbed)
{
_logger.Debug("Removing grabbed releases from pending");
var alreadyPending = GetPendingReleases();
foreach (var decision in grabbed)
{
var decisionLocal = decision;
var episodeIds = decisionLocal.RemoteEpisode.Episodes.Select(e => e.Id);
var existingReports = alreadyPending.Where(r => r.RemoteEpisode.Episodes.Select(e => e.Id)
.Intersect(episodeIds)
.Any());
foreach (var existingReport in existingReports)
{
_logger.Debug("Removing previously pending release, as it was grabbed.");
Delete(existingReport);
}
}
}
public void RemoveRejected(List<DownloadDecision> rejected)
{
_logger.Debug("Removing failed releases from pending");
var pending = GetPendingReleases();
foreach (var rejectedRelease in rejected)
{
var matching = pending.SingleOrDefault(MatchingReleasePredicate(rejectedRelease));
if (matching != null)
{
_logger.Debug("Removing previously pending release, as it has now been rejected.");
Delete(matching);
}
}
}
public List<ReleaseInfo> GetPending()
{
return _repository.All().Select(p => p.Release).ToList();
}
public List<RemoteEpisode> GetPendingRemoteEpisodes(int seriesId)
{
return _repository.AllBySeriesId(seriesId).Select(GetRemoteEpisode).ToList();
}
public List<Queue.Queue> GetPendingQueue()
{
var queued = new List<Queue.Queue>();
foreach (var pendingRelease in GetPendingReleases())
{
foreach (var episode in pendingRelease.RemoteEpisode.Episodes)
{
var queue = new Queue.Queue
{
Id = episode.Id ^ (pendingRelease.Id << 16),
Series = pendingRelease.RemoteEpisode.Series,
Episode = episode,
Quality = pendingRelease.RemoteEpisode.ParsedEpisodeInfo.Quality,
Title = pendingRelease.Title,
Size = pendingRelease.RemoteEpisode.Release.Size,
Sizeleft = pendingRelease.RemoteEpisode.Release.Size,
Timeleft =
pendingRelease.Release.PublishDate.AddHours(
pendingRelease.RemoteEpisode.Series.Profile.Value.GrabDelay)
.Subtract(DateTime.UtcNow),
Status = "Pending",
RemoteEpisode = pendingRelease.RemoteEpisode
};
queued.Add(queue);
}
}
return queued;
}
private List<PendingRelease> GetPendingReleases()
{
var result = new List<PendingRelease>();
foreach (var release in _repository.All())
{
var remoteEpisode = GetRemoteEpisode(release);
if (remoteEpisode == null) continue;
release.RemoteEpisode = remoteEpisode;
result.Add(release);
}
return result;
}
private RemoteEpisode GetRemoteEpisode(PendingRelease release)
{
var series = _seriesService.GetSeries(release.SeriesId);
//Just in case the series was removed, but wasn't cleaned up yet (housekeeper will clean it up)
if (series == null) return null;
var episodes = _parsingService.GetEpisodes(release.ParsedEpisodeInfo, series, true);
return new RemoteEpisode
{
Series = series,
Episodes = episodes,
ParsedEpisodeInfo = release.ParsedEpisodeInfo,
Release = release.Release
};
}
private void Insert(DownloadDecision decision)
{
_repository.Insert(new PendingRelease
{
SeriesId = decision.RemoteEpisode.Series.Id,
ParsedEpisodeInfo = decision.RemoteEpisode.ParsedEpisodeInfo,
Release = decision.RemoteEpisode.Release,
Title = decision.RemoteEpisode.Release.Title,
Added = DateTime.UtcNow
});
_eventAggregator.PublishEvent(new PendingReleasesUpdatedEvent());
}
private void Delete(PendingRelease pendingRelease)
{
_repository.Delete(pendingRelease);
_eventAggregator.PublishEvent(new PendingReleasesUpdatedEvent());
}
private Func<PendingRelease, bool> MatchingReleasePredicate(DownloadDecision decision)
{
return p => p.Title == decision.RemoteEpisode.Release.Title &&
p.Release.PublishDate == decision.RemoteEpisode.Release.PublishDate &&
p.Release.Indexer == decision.RemoteEpisode.Release.Indexer;
}
public void Handle(SeriesDeletedEvent message)
{
_repository.DeleteBySeriesId(message.Series.Id);
}
}
}

@ -0,0 +1,8 @@
using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.Download.Pending
{
public class PendingReleasesUpdatedEvent : IEvent
{
}
}

@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download
{
public interface IProcessDownloadDecisions
{
ProcessedDecisions ProcessDecisions(List<DownloadDecision> decisions);
}
public class ProcessDownloadDecisions : IProcessDownloadDecisions
{
private readonly IDownloadService _downloadService;
private readonly IPrioritizeDownloadDecision _prioritizeDownloadDecision;
private readonly IPendingReleaseService _pendingReleaseService;
private readonly Logger _logger;
public ProcessDownloadDecisions(IDownloadService downloadService,
IPrioritizeDownloadDecision prioritizeDownloadDecision,
IPendingReleaseService pendingReleaseService,
Logger logger)
{
_downloadService = downloadService;
_prioritizeDownloadDecision = prioritizeDownloadDecision;
_pendingReleaseService = pendingReleaseService;
_logger = logger;
}
public ProcessedDecisions ProcessDecisions(List<DownloadDecision> decisions)
{
var qualifiedReports = GetQualifiedReports(decisions);
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(qualifiedReports);
var downloadedReports = new List<DownloadDecision>();
var pendingReports = new List<DownloadDecision>();
foreach (var report in prioritizedDecisions)
{
var remoteEpisode = report.RemoteEpisode;
if (DownloadingOrPending(downloadedReports, pendingReports, remoteEpisode))
{
continue;
}
if (report.TemporarilyRejected)
{
_pendingReleaseService.Add(report);
pendingReports.Add(report);
continue;
}
try
{
_downloadService.DownloadReport(remoteEpisode);
downloadedReports.Add(report);
}
catch (Exception e)
{
//TODO: support for store & forward
_logger.WarnException("Couldn't add report to download queue. " + remoteEpisode, e);
}
}
return new ProcessedDecisions(downloadedReports, pendingReports);
}
internal List<DownloadDecision> GetQualifiedReports(IEnumerable<DownloadDecision> decisions)
{
//Process both approved and temporarily rejected
return decisions.Where(c => (c.Approved || c.TemporarilyRejected) && c.RemoteEpisode.Episodes.Any()).ToList();
}
private bool DownloadingOrPending(List<DownloadDecision> downloading, List<DownloadDecision> pending, RemoteEpisode remoteEpisode)
{
var episodeIds = remoteEpisode.Episodes.Select(e => e.Id).ToList();
if (downloading.SelectMany(r => r.RemoteEpisode.Episodes)
.Select(e => e.Id)
.ToList()
.Intersect(episodeIds)
.Any())
{
return true;
}
if (pending.SelectMany(r => r.RemoteEpisode.Episodes)
.Select(e => e.Id)
.ToList()
.Intersect(episodeIds)
.Any())
{
return true;
}
return false;
}
}
}

@ -0,0 +1,17 @@
using System.Collections.Generic;
using NzbDrone.Core.DecisionEngine;
namespace NzbDrone.Core.Download
{
public class ProcessedDecisions
{
public List<DownloadDecision> Grabbed { get; set; }
public List<DownloadDecision> Pending { get; set; }
public ProcessedDecisions(List<DownloadDecision> grabbed, List<DownloadDecision> pending)
{
Grabbed = grabbed;
Pending = pending;
}
}
}

@ -7,6 +7,7 @@ using NzbDrone.Core.Datastore;
using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
@ -17,7 +18,7 @@ namespace NzbDrone.Core.History
List<History> All();
void Purge();
void Trim();
QualityModel GetBestQualityInHistory(QualityProfile qualityProfile, int episodeId);
QualityModel GetBestQualityInHistory(Profile profile, int episodeId);
PagingSpec<History> Paged(PagingSpec<History> pagingSpec);
List<History> BetweenDates(DateTime startDate, DateTime endDate, HistoryEventType eventType);
List<History> Failed();
@ -95,9 +96,9 @@ namespace NzbDrone.Core.History
_historyRepository.Trim();
}
public QualityModel GetBestQualityInHistory(QualityProfile qualityProfile, int episodeId)
public QualityModel GetBestQualityInHistory(Profile profile, int episodeId)
{
var comparer = new QualityModelComparer(qualityProfile);
var comparer = new QualityModelComparer(profile);
return _historyRepository.GetBestQualityInHistory(episodeId)
.OrderByDescending(q => q, comparer)
.FirstOrDefault();

@ -0,0 +1,31 @@
using NLog;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
{
public class CleanupOrphanedPendingReleases : IHousekeepingTask
{
private readonly IDatabase _database;
private readonly Logger _logger;
public CleanupOrphanedPendingReleases(IDatabase database, Logger logger)
{
_database = database;
_logger = logger;
}
public void Clean()
{
_logger.Debug("Running orphaned pending releases cleanup");
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM PendingReleases
WHERE Id IN (
SELECT PendingReleases.Id FROM PendingReleases
LEFT OUTER JOIN Series
ON PendingReleases.SeriesId = Series.Id
WHERE Series.Id IS NULL)");
}
}
}

@ -21,19 +21,19 @@ namespace NzbDrone.Core.IndexerSearch
public class MissingEpisodeSearchService : IEpisodeSearchService, IExecute<EpisodeSearchCommand>, IExecute<MissingEpisodeSearchCommand>
{
private readonly ISearchForNzb _nzbSearchService;
private readonly IDownloadApprovedReports _downloadApprovedReports;
private readonly IProcessDownloadDecisions _processDownloadDecisions;
private readonly IEpisodeService _episodeService;
private readonly IQueueService _queueService;
private readonly Logger _logger;
public MissingEpisodeSearchService(ISearchForNzb nzbSearchService,
IDownloadApprovedReports downloadApprovedReports,
IProcessDownloadDecisions processDownloadDecisions,
IEpisodeService episodeService,
IQueueService queueService,
Logger logger)
{
_nzbSearchService = nzbSearchService;
_downloadApprovedReports = downloadApprovedReports;
_processDownloadDecisions = processDownloadDecisions;
_episodeService = episodeService;
_queueService = queueService;
_logger = logger;
@ -52,9 +52,10 @@ namespace NzbDrone.Core.IndexerSearch
foreach (var episode in missing)
{
//TODO: Add a flag to the search to state it is a "scheduled" search
var decisions = _nzbSearchService.EpisodeSearch(episode);
var downloaded = _downloadApprovedReports.DownloadApproved(decisions);
downloadedCount += downloaded.Count;
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
downloadedCount += processed.Grabbed.Count;
}
_logger.ProgressInfo("Completed search for {0} episodes. {1} reports downloaded.", missing.Count, downloadedCount);
@ -65,9 +66,9 @@ namespace NzbDrone.Core.IndexerSearch
foreach (var episodeId in message.EpisodeIds)
{
var decisions = _nzbSearchService.EpisodeSearch(episodeId);
var downloaded = _downloadApprovedReports.DownloadApproved(decisions);
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
_logger.ProgressInfo("Episode search completed. {0} reports downloaded.", downloaded.Count);
_logger.ProgressInfo("Episode search completed. {0} reports downloaded.", processed.Grabbed.Count);
}
}
@ -97,8 +98,8 @@ namespace NzbDrone.Core.IndexerSearch
{
rateGate.WaitToProceed();
var decisions = _nzbSearchService.EpisodeSearch(episode);
var downloaded = _downloadApprovedReports.DownloadApproved(decisions);
downloadedCount += downloaded.Count;
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
downloadedCount += processed.Grabbed.Count;
}
}

@ -8,24 +8,24 @@ namespace NzbDrone.Core.IndexerSearch
public class SeasonSearchService : IExecute<SeasonSearchCommand>
{
private readonly ISearchForNzb _nzbSearchService;
private readonly IDownloadApprovedReports _downloadApprovedReports;
private readonly IProcessDownloadDecisions _processDownloadDecisions;
private readonly Logger _logger;
public SeasonSearchService(ISearchForNzb nzbSearchService,
IDownloadApprovedReports downloadApprovedReports,
IProcessDownloadDecisions processDownloadDecisions,
Logger logger)
{
_nzbSearchService = nzbSearchService;
_downloadApprovedReports = downloadApprovedReports;
_processDownloadDecisions = processDownloadDecisions;
_logger = logger;
}
public void Execute(SeasonSearchCommand message)
{
var decisions = _nzbSearchService.SeasonSearch(message.SeriesId, message.SeasonNumber);
var downloaded = _downloadApprovedReports.DownloadApproved(decisions);
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
_logger.ProgressInfo("Season search completed. {0} reports downloaded.", downloaded.Count);
_logger.ProgressInfo("Season search completed. {0} reports downloaded.", processed.Grabbed.Count);
}
}
}

@ -10,17 +10,17 @@ namespace NzbDrone.Core.IndexerSearch
{
private readonly ISeriesService _seriesService;
private readonly ISearchForNzb _nzbSearchService;
private readonly IDownloadApprovedReports _downloadApprovedReports;
private readonly IProcessDownloadDecisions _processDownloadDecisions;
private readonly Logger _logger;
public SeriesSearchService(ISeriesService seriesService,
ISearchForNzb nzbSearchService,
IDownloadApprovedReports downloadApprovedReports,
IProcessDownloadDecisions processDownloadDecisions,
Logger logger)
{
_seriesService = seriesService;
_nzbSearchService = nzbSearchService;
_downloadApprovedReports = downloadApprovedReports;
_processDownloadDecisions = processDownloadDecisions;
_logger = logger;
}
@ -39,7 +39,7 @@ namespace NzbDrone.Core.IndexerSearch
}
var decisions = _nzbSearchService.SeasonSearch(message.SeriesId, season.SeasonNumber);
downloadedCount += _downloadApprovedReports.DownloadApproved(decisions).Count;
downloadedCount += _processDownloadDecisions.ProcessDecisions(decisions).Grabbed.Count;
}
_logger.ProgressInfo("Series search completed. {0} reports downloaded.", downloadedCount);

@ -0,0 +1,8 @@
using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.Indexers
{
public class RssSyncCompleteEvent : IEvent
{
}
}

@ -3,11 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.Instrumentation.Extensions;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers
{
@ -20,20 +21,26 @@ namespace NzbDrone.Core.Indexers
{
private readonly IFetchAndParseRss _rssFetcherAndParser;
private readonly IMakeDownloadDecision _downloadDecisionMaker;
private readonly IDownloadApprovedReports _downloadApprovedReports;
private readonly IProcessDownloadDecisions _processDownloadDecisions;
private readonly IEpisodeSearchService _episodeSearchService;
private readonly IPendingReleaseService _pendingReleaseService;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
public RssSyncService(IFetchAndParseRss rssFetcherAndParser,
IMakeDownloadDecision downloadDecisionMaker,
IDownloadApprovedReports downloadApprovedReports,
IProcessDownloadDecisions processDownloadDecisions,
IEpisodeSearchService episodeSearchService,
IPendingReleaseService pendingReleaseService,
IEventAggregator eventAggregator,
Logger logger)
{
_rssFetcherAndParser = rssFetcherAndParser;
_downloadDecisionMaker = downloadDecisionMaker;
_downloadApprovedReports = downloadApprovedReports;
_processDownloadDecisions = processDownloadDecisions;
_episodeSearchService = episodeSearchService;
_pendingReleaseService = pendingReleaseService;
_eventAggregator = eventAggregator;
_logger = logger;
}
@ -42,24 +49,35 @@ namespace NzbDrone.Core.Indexers
{
_logger.ProgressInfo("Starting RSS Sync");
var reports = _rssFetcherAndParser.Fetch();
var reports = _rssFetcherAndParser.Fetch().Concat(_pendingReleaseService.GetPending()).ToList();
var decisions = _downloadDecisionMaker.GetRssDecision(reports);
var downloaded = _downloadApprovedReports.DownloadApproved(decisions);
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
_pendingReleaseService.RemoveGrabbed(processed.Grabbed);
_pendingReleaseService.RemoveRejected(decisions.Where(d => d.Rejected).ToList());
_logger.ProgressInfo("RSS Sync Completed. Reports found: {0}, Reports downloaded: {1}", reports.Count, downloaded.Count());
var message = String.Format("RSS Sync Completed. Reports found: {0}, Reports grabbed: {1}", reports.Count, processed.Grabbed.Count);
return downloaded;
if (processed.Pending.Any())
{
message += ", Reports pending: " + processed.Pending.Count;
}
_logger.ProgressInfo(message);
return processed.Grabbed.Concat(processed.Pending).ToList();
}
public void Execute(RssSyncCommand message)
{
var downloaded = Sync();
var processed = Sync();
if (message.LastExecutionTime.HasValue && DateTime.UtcNow.Subtract(message.LastExecutionTime.Value).TotalHours > 3)
{
_logger.Info("RSS Sync hasn't run since: {0}. Searching for any missing episodes since then.", message.LastExecutionTime.Value);
_episodeSearchService.MissingEpisodesAiredAfter(message.LastExecutionTime.Value.AddDays(-1), downloaded.SelectMany(d => d.RemoteEpisode.Episodes).Select(e => e.Id));
_episodeSearchService.MissingEpisodesAiredAfter(message.LastExecutionTime.Value.AddDays(-1), processed.SelectMany(d => d.RemoteEpisode.Episodes).Select(e => e.Id));
}
_eventAggregator.PublishEvent(new RssSyncCompleteEvent());
}
}
}

@ -44,7 +44,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.QualityProfile))
.OrderByDescending(c => c.LocalEpisode.Quality, new QualityModelComparer(s.First().LocalEpisode.Series.Profile))
.ThenByDescending(c => c.LocalEpisode.Size))
.SelectMany(c => c)
.ToList();

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser;
@ -61,7 +60,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
if (parsedEpisode != null)
{
if (quality != null && new QualityModelComparer(parsedEpisode.Series.QualityProfile).Compare(quality, parsedEpisode.Quality) > 0)
if (quality != null && new QualityModelComparer(parsedEpisode.Series.Profile).Compare(quality, parsedEpisode.Quality) > 0)
{
_logger.Debug("Using quality from folder: {0}", quality);
parsedEpisode.Quality = quality;

@ -2,7 +2,6 @@
using NLog;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
{
@ -19,7 +18,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
public bool IsSatisfiedBy(LocalEpisode localEpisode)
{
var qualityComparer = new QualityModelComparer(localEpisode.Series.QualityProfile);
var qualityComparer = new QualityModelComparer(localEpisode.Series.Profile);
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);

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

Loading…
Cancel
Save