Preferred words

New: Ability to prefer releases based on terms in release title
pull/2789/head
Mark McDowall 6 years ago committed by Taloth Saldono
parent ac709c39ab
commit 853f25468c

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.Episodes;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Tv;
using NzbDrone.SignalR;

@ -5,6 +5,7 @@ using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Exceptions;
using NzbDrone.SignalR;
using Sonarr.Http;

@ -1,6 +1,7 @@
using System;
using System.IO;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.MediaFiles;
using Sonarr.Http.REST;
using NzbDrone.Core.Qualities;

@ -2,6 +2,7 @@
using Sonarr.Http.REST;
using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.SignalR;
namespace NzbDrone.Api.Episodes

@ -4,6 +4,7 @@ using NzbDrone.Api.Series;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;

@ -7,6 +7,7 @@ using Sonarr.Http.Extensions;
using NzbDrone.Api.Series;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download;
using NzbDrone.Core.History;
using Sonarr.Http;

@ -1,7 +1,7 @@
using System.Collections.Generic;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Restrictions;
using NzbDrone.Core.Profiles.Releases;
using Sonarr.Http;
using Sonarr.Http.Mapping;
@ -9,12 +9,12 @@ namespace NzbDrone.Api.Restrictions
{
public class RestrictionModule : SonarrRestModule<RestrictionResource>
{
private readonly IRestrictionService _restrictionService;
private readonly IReleaseProfileService _releaseProfileService;
public RestrictionModule(IRestrictionService restrictionService)
public RestrictionModule(IReleaseProfileService releaseProfileService)
{
_restrictionService = restrictionService;
_releaseProfileService = releaseProfileService;
GetResourceById = GetRestriction;
GetResourceAll = GetAllRestrictions;
@ -35,27 +35,27 @@ namespace NzbDrone.Api.Restrictions
private RestrictionResource GetRestriction(int id)
{
return _restrictionService.Get(id).ToResource();
return _releaseProfileService.Get(id).ToResource();
}
private List<RestrictionResource> GetAllRestrictions()
{
return _restrictionService.All().ToResource();
return _releaseProfileService.All().ToResource();
}
private int CreateRestriction(RestrictionResource resource)
{
return _restrictionService.Add(resource.ToModel()).Id;
return _releaseProfileService.Add(resource.ToModel()).Id;
}
private void UpdateRestriction(RestrictionResource resource)
{
_restrictionService.Update(resource.ToModel());
_releaseProfileService.Update(resource.ToModel());
}
private void DeleteRestriction(int id)
{
_restrictionService.Delete(id);
_releaseProfileService.Delete(id);
}
}
}

@ -1,14 +1,13 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Profiles.Releases;
using Sonarr.Http.REST;
using NzbDrone.Core.Restrictions;
namespace NzbDrone.Api.Restrictions
{
public class RestrictionResource : RestResource
{
public string Required { get; set; }
public string Preferred { get; set; }
public string Ignored { get; set; }
public HashSet<int> Tags { get; set; }
@ -20,7 +19,7 @@ namespace NzbDrone.Api.Restrictions
public static class RestrictionResourceMapper
{
public static RestrictionResource ToResource(this Restriction model)
public static RestrictionResource ToResource(this ReleaseProfile model)
{
if (model == null) return null;
@ -29,28 +28,26 @@ namespace NzbDrone.Api.Restrictions
Id = model.Id,
Required = model.Required,
Preferred = model.Preferred,
Ignored = model.Ignored,
Tags = new HashSet<int>(model.Tags)
};
}
public static Restriction ToModel(this RestrictionResource resource)
public static ReleaseProfile ToModel(this RestrictionResource resource)
{
if (resource == null) return null;
return new Restriction
return new ReleaseProfile
{
Id = resource.Id,
Required = resource.Required,
Preferred = resource.Preferred,
Ignored = resource.Ignored,
Tags = new HashSet<int>(resource.Tags)
};
}
public static List<RestrictionResource> ToResource(this IEnumerable<Restriction> models)
public static List<RestrictionResource> ToResource(this IEnumerable<ReleaseProfile> models)
{
return models.Select(ToResource).ToList();
}

@ -2,6 +2,7 @@ using System.Linq;
using NzbDrone.Api.Episodes;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Tv;
using NzbDrone.SignalR;
using Sonarr.Http;

@ -2,6 +2,7 @@ using System.Linq;
using NzbDrone.Api.Episodes;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Tv;
using NzbDrone.SignalR;
using Sonarr.Http;

@ -1,8 +1,8 @@
using FluentAssertions;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Languages;
@ -13,6 +13,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[TestFixture]
public class CutoffSpecificationFixture : CoreTest<UpgradableSpecification>
{
private static readonly int NoPreferredWordScore = 0;
[Test]
public void should_return_true_if_current_episode_is_less_than_cutoff()
{
@ -27,7 +29,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Languages = LanguageFixture.GetDefaultLanguages(Language.English),
Cutoff = Language.English
},
new QualityModel(Quality.DVD, new Revision(version: 2)), Language.English).Should().BeTrue();
new QualityModel(Quality.DVD, new Revision(version: 2)),
Language.English,
NoPreferredWordScore).Should().BeTrue();
}
[Test]
@ -44,7 +48,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Languages = LanguageFixture.GetDefaultLanguages(Language.English),
Cutoff = Language.English
},
new QualityModel(Quality.HDTV720p, new Revision(version: 2)), Language.English).Should().BeFalse();
new QualityModel(Quality.HDTV720p, new Revision(version: 2)),
Language.English,
NoPreferredWordScore).Should().BeFalse();
}
[Test]
@ -61,7 +67,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Languages = LanguageFixture.GetDefaultLanguages(Language.English),
Cutoff = Language.English
},
new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), Language.English).Should().BeFalse();
new QualityModel(Quality.Bluray1080p, new Revision(version: 2)),
Language.English,
NoPreferredWordScore).Should().BeFalse();
}
[Test]
@ -80,7 +88,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
},
new QualityModel(Quality.HDTV720p, new Revision(version: 1)),
Language.English,
new QualityModel(Quality.HDTV720p, new Revision(version: 2))).Should().BeTrue();
NoPreferredWordScore,
new QualityModel(Quality.HDTV720p, new Revision(version: 2)),
NoPreferredWordScore).Should().BeTrue();
}
[Test]
@ -99,13 +109,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
},
new QualityModel(Quality.HDTV720p, new Revision(version: 2)),
Language.English,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeFalse();
NoPreferredWordScore,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2)),
NoPreferredWordScore).Should().BeFalse();
}
[Test]
public void should_return_true_if_quality_cutoff_is_met_and_quality_is_higher_but_language_is_not_met()
{
Profile _profile = new Profile
{
Cutoff = Quality.HDTV720p.Id,
@ -122,13 +133,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_langProfile,
new QualityModel(Quality.HDTV720p, new Revision(version: 2)),
Language.English,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeTrue();
NoPreferredWordScore,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2)),
NoPreferredWordScore).Should().BeTrue();
}
[Test]
public void should_return_false_if_cutoff_is_met_and_quality_is_higher_and_language_is_met()
{
Profile _profile = new Profile
{
Cutoff = Quality.HDTV720p.Id,
@ -146,13 +158,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_langProfile,
new QualityModel(Quality.HDTV720p, new Revision(version: 2)),
Language.Spanish,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeFalse();
NoPreferredWordScore,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2)),
NoPreferredWordScore).Should().BeFalse();
}
[Test]
public void should_return_false_if_cutoff_is_met_and_quality_is_higher_and_language_is_higher()
{
Profile _profile = new Profile
{
Cutoff = Quality.HDTV720p.Id,
@ -170,13 +183,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_langProfile,
new QualityModel(Quality.HDTV720p, new Revision(version: 2)),
Language.French,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeFalse();
NoPreferredWordScore,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2)),
NoPreferredWordScore).Should().BeFalse();
}
[Test]
public void should_return_true_if_cutoff_is_not_met_and_new_quality_is_higher_and_language_is_higher()
{
Profile _profile = new Profile
{
Cutoff = Quality.HDTV720p.Id,
@ -194,13 +208,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_langProfile,
new QualityModel(Quality.SDTV, new Revision(version: 2)),
Language.French,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeTrue();
NoPreferredWordScore,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2)),
NoPreferredWordScore).Should().BeTrue();
}
[Test]
public void should_return_true_if_cutoff_is_not_met_and_language_is_higher()
{
Profile _profile = new Profile
{
Cutoff = Quality.HDTV720p.Id,
@ -217,7 +232,33 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_profile,
_langProfile,
new QualityModel(Quality.SDTV, new Revision(version: 2)),
Language.French).Should().BeTrue();
Language.French,
NoPreferredWordScore).Should().BeTrue();
}
[Test]
public void should_return_true_if_cutoffs_are_met_and_score_is_higher()
{
Profile _profile = new Profile
{
Cutoff = Quality.HDTV720p.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
};
LanguageProfile _langProfile = new LanguageProfile
{
Cutoff = Language.Spanish,
Languages = LanguageFixture.GetDefaultLanguages()
};
Subject.CutoffNotMet(
_profile,
_langProfile,
new QualityModel(Quality.HDTV720p, new Revision(version: 2)),
Language.Spanish,
NoPreferredWordScore,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2)),
10).Should().BeTrue();
}
}
}

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Moq;
@ -11,6 +11,7 @@ using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
using FizzWare.NBuilder;
using NzbDrone.Core.DecisionEngine.Specifications;
namespace NzbDrone.Core.Test.DecisionEngineTests
{

@ -1,9 +1,8 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Profiles.Languages;
using NzbDrone.Core.Parser.Model;
@ -26,6 +25,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private Series _otherSeries;
private Episode _otherEpisode;
private ReleaseInfo _releaseInfo;
[SetUp]
public void Setup()
{
@ -58,10 +59,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
.With(e => e.EpisodeNumber = 2)
.Build();
_releaseInfo = Builder<ReleaseInfo>.CreateNew()
.Build();
_remoteEpisode = Builder<RemoteEpisode>.CreateNew()
.With(r => r.Series = _series)
.With(r => r.Episodes = new List<Episode> { _episode })
.With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD) , Language = Language.Spanish})
.With(r => r.PreferredWordScore = 0)
.Build();
}
@ -97,6 +102,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
var remoteEpisode = Builder<RemoteEpisode>.CreateNew()
.With(r => r.Series = _otherSeries)
.With(r => r.Episodes = new List<Episode> { _episode })
.With(r => r.Release = _releaseInfo)
.Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode });
@ -117,6 +123,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.SDTV),
Language = Language.Spanish
})
.With(r => r.Release = _releaseInfo)
.Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode });
@ -137,6 +144,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.SDTV),
Language = Language.English
})
.With(r => r.Release = _releaseInfo)
.Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode });
@ -153,6 +161,27 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
Quality = new QualityModel(Quality.DVD)
})
.With(r => r.Release = _releaseInfo)
.Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode });
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_true_when_qualities_are_the_same_and_languages_are_the_same_with_higher_preferred_word_score()
{
_remoteEpisode.PreferredWordScore = 1;
var remoteEpisode = Builder<RemoteEpisode>.CreateNew()
.With(r => r.Series = _series)
.With(r => r.Episodes = new List<Episode> { _episode })
.With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo
{
Quality = new QualityModel(Quality.DVD),
Language = Language.Spanish,
})
.With(r => r.Release = _releaseInfo)
.Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode });
@ -170,6 +199,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.DVD),
Language = Language.Spanish,
})
.With(r => r.Release = _releaseInfo)
.Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode });
@ -187,6 +217,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.DVD),
Language = Language.English,
})
.With(r => r.Release = _releaseInfo)
.Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode });
@ -206,6 +237,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.HDTV720p),
Language = Language.English
})
.With(r => r.Release = _releaseInfo)
.Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode });
@ -223,6 +255,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.HDTV720p),
Language = Language.English
})
.With(r => r.Release = _releaseInfo)
.Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode });
@ -240,6 +273,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.HDTV720p),
Language = Language.English
})
.With(r => r.Release = _releaseInfo)
.Build();
_remoteEpisode.Episodes.Add(_otherEpisode);
@ -259,6 +293,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.HDTV720p),
Language = Language.English
})
.With(r => r.Release = _releaseInfo)
.Build();
_remoteEpisode.Episodes.Add(_otherEpisode);
@ -280,6 +315,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality.HDTV720p),
Language = Language.English
})
.With(r => r.Release = _releaseInfo)
.TheFirst(1)
.With(r => r.Episodes = new List<Episode> { _episode })
.TheNext(1)
@ -304,6 +340,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.HDTV720p),
Language = Language.Spanish
})
.With(r => r.Release = _releaseInfo)
.Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode });

@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Collections.Generic;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Restrictions;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
@ -35,11 +35,11 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private void GivenRestictions(string required, string ignored)
{
Mocker.GetMock<IRestrictionService>()
Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.AllForTags(It.IsAny<HashSet<int>>()))
.Returns(new List<Restriction>
.Returns(new List<ReleaseProfile>
{
new Restriction
new ReleaseProfile()
{
Required = required,
Ignored = ignored
@ -50,9 +50,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[Test]
public void should_be_true_when_restrictions_are_empty()
{
Mocker.GetMock<IRestrictionService>()
Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.AllForTags(It.IsAny<HashSet<int>>()))
.Returns(new List<Restriction>());
.Returns(new List<ReleaseProfile>());
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
@ -116,11 +116,11 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
_remoteEpisode.Release.Title = "[ www.Speed.cd ] -Whose.Line.is.it.Anyway.US.S10E24.720p.HDTV.x264-BAJSKORV";
Mocker.GetMock<IRestrictionService>()
Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.AllForTags(It.IsAny<HashSet<int>>()))
.Returns(new List<Restriction>
.Returns(new List<ReleaseProfile>
{
new Restriction { Required = "x264", Ignored = "www.Speed.cd" }
new ReleaseProfile { Required = "x264", Ignored = "www.Speed.cd" }
});
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
@ -6,7 +6,7 @@ using FluentAssertions;
using Marr.Data;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.DecisionEngine.Specifications.RssSync;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Indexers;
@ -86,14 +86,15 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
_remoteEpisode.Episodes.First().EpisodeFile = new LazyLoaded<EpisodeFile>(new EpisodeFile
{
Quality = quality,
Language = language
Language = language,
SceneName = "Series.Title.S01E01.720p.HDTV.x264-Sonarr"
});
}
private void GivenUpgradeForExistingFile()
{
Mocker.GetMock<IUpgradableSpecification>()
.Setup(s => s.IsUpgradable(It.IsAny<Profile>(), It.IsAny<LanguageProfile>(), It.IsAny<QualityModel>(), It.IsAny<Language>(), It.IsAny<QualityModel>(), It.IsAny<Language>()))
.Setup(s => s.IsUpgradable(It.IsAny<Profile>(), It.IsAny<LanguageProfile>(), It.IsAny<QualityModel>(), It.IsAny<Language>(), It.IsAny<int>(), It.IsAny<QualityModel>(), It.IsAny<Language>(), It.IsAny<int>()))
.Returns(true);
}

@ -13,6 +13,7 @@ using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Profiles.Languages;
using NzbDrone.Core.Languages;

@ -12,7 +12,7 @@ using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync

@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Test.Framework;

@ -1,9 +1,9 @@
using FluentAssertions;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Languages;
@ -13,7 +13,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
[TestFixture]
public class QualityUpgradeSpecificationFixture : CoreTest<UpgradableSpecification>
public class UpgradeSpecificationFixture : CoreTest<UpgradableSpecification>
{
public static object[] IsUpgradeTestCases =
{
@ -36,11 +36,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
new object[] { Quality.WEBDL720p, 1, Language.Spanish, Quality.HDTV720p, 2, Language.French, Quality.WEBDL720p, Language.Spanish, false }
};
[SetUp]
public void Setup()
{
}
private static readonly int NoPreferredWordScore = 0;
private void GivenAutoDownloadPropers(bool autoDownloadPropers)
{
@ -66,7 +62,15 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Cutoff = Language.English
};
Subject.IsUpgradable(profile, langProfile, new QualityModel(current, new Revision(version: currentVersion)), Language.English, new QualityModel(newQuality, new Revision(version: newVersion)), Language.English)
Subject.IsUpgradable(
profile,
langProfile,
new QualityModel(current, new Revision(version: currentVersion)),
Language.English,
NoPreferredWordScore,
new QualityModel(newQuality, new Revision(version: newVersion)),
Language.English,
NoPreferredWordScore)
.Should().Be(expected);
}
@ -87,7 +91,15 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Cutoff = languageCutoff
};
Subject.IsUpgradable(profile, langProfile, new QualityModel(current, new Revision(version: currentVersion)), currentLanguage, new QualityModel(newQuality, new Revision(version: newVersion)), newLanguage)
Subject.IsUpgradable(
profile,
langProfile,
new QualityModel(current, new Revision(version: currentVersion)),
currentLanguage,
NoPreferredWordScore,
new QualityModel(newQuality, new Revision(version: newVersion)),
newLanguage,
NoPreferredWordScore)
.Should().Be(expected);
}
@ -108,7 +120,15 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
};
Subject.IsUpgradable(profile, langProfile, new QualityModel(Quality.DVD, new Revision(version: 2)), Language.English, new QualityModel(Quality.DVD, new Revision(version: 1)), Language.English)
Subject.IsUpgradable(
profile,
langProfile,
new QualityModel(Quality.DVD, new Revision(version: 2)),
Language.English,
NoPreferredWordScore,
new QualityModel(Quality.DVD, new Revision(version: 1)),
Language.English,
NoPreferredWordScore)
.Should().BeFalse();
}
}

@ -1,10 +1,10 @@
using FizzWare.NBuilder;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Housekeeping.Housekeepers;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tags;
using NzbDrone.Core.Restrictions;
using NzbDrone.Core.Profiles.Releases;
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
{
@ -27,7 +27,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
var tags = Builder<Tag>.CreateListOfSize(2).BuildList();
Db.InsertMany(tags);
var restrictions = Builder<Restriction>.CreateListOfSize(2)
var restrictions = Builder<ReleaseProfile>.CreateListOfSize(2)
.All()
.With(v => v.Tags.Add(tags[0].Id))
.BuildList();

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
@ -42,7 +42,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests
.Build();
Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.BuildFileName(It.IsAny<List<Episode>>(), It.IsAny<Series>(), It.IsAny<EpisodeFile>(), null))
.Setup(s => s.BuildFileName(It.IsAny<List<Episode>>(), It.IsAny<Series>(), It.IsAny<EpisodeFile>(), null, null))
.Returns("File Name");
Mocker.GetMock<IBuildFileNames>()

@ -172,7 +172,7 @@
<Compile Include="DecisionEngineTests\ReleaseRestrictionsSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\PrioritizeDownloadDecisionFixture.cs" />
<Compile Include="DecisionEngineTests\QualityAllowedByProfileSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\QualityUpgradeSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\UpgradeSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\MinimumAgeSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\RetentionSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\RssSync\DelaySpecificationFixture.cs" />
@ -346,6 +346,7 @@
<Compile Include="ParserTests\ValidateParsedEpisodeInfoFixture.cs" />
<Compile Include="Profiles\Delay\DelayProfileServiceFixture.cs" />
<Compile Include="Profiles\Qualities\QualityIndexCompareToFixture.cs" />
<Compile Include="Profiles\Releases\PreferredWordService\CalculateFixture.cs" />
<Compile Include="Qualities\QualityFinderFixture.cs" />
<Compile Include="Qualities\RevisionComparableFixture.cs" />
<Compile Include="QueueTests\QueueServiceFixture.cs" />

@ -0,0 +1,95 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService
{
[TestFixture]
public class CalculateFixture : CoreTest<Core.Profiles.Releases.PreferredWordService>
{
private Series _series = null;
private List<ReleaseProfile> _releaseProfiles = null;
private string _title = "Series.Title.S01E01.720p.HDTV.x264-Sonarr";
[SetUp]
public void Setup()
{
_series = Builder<Series>.CreateNew()
.With(s => s.Tags = new HashSet<int>(new[] {1, 2}))
.Build();
_releaseProfiles = new List<ReleaseProfile>();
_releaseProfiles.Add(new ReleaseProfile
{
Preferred = new List<KeyValuePair<string, int>>
{
new KeyValuePair<string, int>("x264", 5),
new KeyValuePair<string, int>("x265", -10)
}
});
Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.AllForTags(It.IsAny<HashSet<int>>()))
.Returns(_releaseProfiles);
}
private void GivenMatchingTerms(params string[] terms)
{
Mocker.GetMock<ITermMatcher>()
.Setup(s => s.IsMatch(It.IsAny<string>(), _title))
.Returns<string, string>((term, title) => terms.Contains(term));
}
[Test]
public void should_return_0_when_there_are_no_release_profiles()
{
Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.AllForTags(It.IsAny<HashSet<int>>()))
.Returns(new List<ReleaseProfile>());
Subject.Calculate(_series, _title).Should().Be(0);
}
[Test]
public void should_return_0_when_there_are_no_matching_preferred_words()
{
GivenMatchingTerms();
Subject.Calculate(_series, _title).Should().Be(0);
}
[Test]
public void should_calculate_positive_score()
{
GivenMatchingTerms("x264");
Subject.Calculate(_series, _title).Should().Be(5);
}
[Test]
public void should_calculate_negative_score()
{
GivenMatchingTerms("x265");
Subject.Calculate(_series, _title).Should().Be(-10);
}
[Test]
public void should_calculate_using_multiple_profiles()
{
_releaseProfiles.Add(_releaseProfiles.First());
GivenMatchingTerms("x264");
Subject.Calculate(_series, _title).Should().Be(10);
}
}
}

@ -0,0 +1,15 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(127)]
public class rename_restrictions_to_release_profiles : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Rename.Table("Restrictions").To("ReleaseProfiles");
Alter.Table("ReleaseProfiles").AddColumn("IncludePreferredWhenRenaming").AsBoolean().WithDefaultValue(true);
}
}
}

@ -21,7 +21,6 @@ using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Restrictions;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.SeriesStats;
using NzbDrone.Core.Tags;
@ -37,6 +36,7 @@ using NzbDrone.Core.Extras.Subtitles;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Languages;
using NzbDrone.Core.Profiles.Releases;
namespace NzbDrone.Core.Datastore
{
@ -121,7 +121,7 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<RemotePathMapping>().RegisterModel("RemotePathMappings");
Mapper.Entity<Tag>().RegisterModel("Tags");
Mapper.Entity<Restriction>().RegisterModel("Restrictions");
Mapper.Entity<ReleaseProfile>().RegisterModel("ReleaseProfiles");
Mapper.Entity<DelayProfile>().RegisterModel("DelayProfiles");
Mapper.Entity<User>().RegisterModel("Users");
@ -149,6 +149,7 @@ namespace NzbDrone.Core.Datastore
MapRepository.Instance.RegisterTypeConverter(typeof(QualityModel), new EmbeddedDocumentConverter(new QualityIntConverter()));
MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary<string, string>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<int>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<KeyValuePair<string, int>>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(Language), new LanguageIntConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<string>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<LanguageProfileItem>), new EmbeddedDocumentConverter(new LanguageIntConverter()));

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Indexers;
@ -25,6 +25,7 @@ namespace NzbDrone.Core.DecisionEngine
{
CompareQuality,
CompareLanguage,
ComparePreferredWordScore,
CompareProtocol,
CompareEpisodeCount,
CompareEpisodeNumber,
@ -68,6 +69,11 @@ namespace NzbDrone.Core.DecisionEngine
return CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.Series.LanguageProfile.Value.Languages.FindIndex(l => l.Language == remoteEpisode.ParsedEpisodeInfo.Language));
}
private int ComparePreferredWordScore(DownloadDecision x, DownloadDecision y)
{
return CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.PreferredWordScore);
}
private int CompareProtocol(DownloadDecision x, DownloadDecision y)
{
var result = CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode =>

@ -5,6 +5,8 @@ using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download.Aggregation;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
@ -21,12 +23,17 @@ namespace NzbDrone.Core.DecisionEngine
{
private readonly IEnumerable<IDecisionEngineSpecification> _specifications;
private readonly IParsingService _parsingService;
private readonly IRemoteEpisodeAggregationService _aggregationService;
private readonly Logger _logger;
public DownloadDecisionMaker(IEnumerable<IDecisionEngineSpecification> specifications, IParsingService parsingService, Logger logger)
public DownloadDecisionMaker(IEnumerable<IDecisionEngineSpecification> specifications,
IParsingService parsingService,
IRemoteEpisodeAggregationService aggregationService,
Logger logger)
{
_specifications = specifications;
_parsingService = parsingService;
_aggregationService = aggregationService;
_logger = logger;
}
@ -89,6 +96,7 @@ namespace NzbDrone.Core.DecisionEngine
}
else
{
_aggregationService.Augment(remoteEpisode);
remoteEpisode.DownloadAllowed = remoteEpisode.Episodes.Any();
decision = GetDecisionForReport(remoteEpisode, searchCriteria);
}

@ -1,18 +1,21 @@
using System.Linq;
using System.Linq;
using NLog;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Releases;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class CutoffSpecification : IDecisionEngineSpecification
{
private readonly UpgradableSpecification _upgradableSpecification;
private readonly IPreferredWordService _preferredWordServiceCalculator;
private readonly Logger _logger;
public CutoffSpecification(UpgradableSpecification UpgradableSpecification, Logger logger)
public CutoffSpecification(UpgradableSpecification upgradableSpecification, IPreferredWordService preferredWordServiceCalculator, Logger logger)
{
_upgradableSpecification = UpgradableSpecification;
_upgradableSpecification = upgradableSpecification;
_preferredWordServiceCalculator = preferredWordServiceCalculator;
_logger = logger;
}
@ -30,13 +33,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
_logger.Debug("File is no longer available, skipping this file.");
continue;
}
_logger.Debug("Comparing file quality and language with report. Existing file is {0} - {1}", file.Quality, file.Language);
if (!_upgradableSpecification.CutoffNotMet(profile,
subject.Series.LanguageProfile,
file.Quality,
file.Language,
subject.ParsedEpisodeInfo.Quality))
_preferredWordServiceCalculator.Calculate(subject.Series, file.GetSceneOrFileName()),
subject.ParsedEpisodeInfo.Quality,
subject.PreferredWordScore))
{
_logger.Debug("Cutoff already met, rejecting.");

@ -1,7 +1,7 @@
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.DecisionEngine
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public interface IDecisionEngineSpecification
{

@ -2,6 +2,7 @@ using System.Linq;
using NLog;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Queue;
namespace NzbDrone.Core.DecisionEngine.Specifications
@ -10,14 +11,17 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
private readonly IQueueService _queueService;
private readonly UpgradableSpecification _upgradableSpecification;
private readonly IPreferredWordService _preferredWordServiceCalculator;
private readonly Logger _logger;
public QueueSpecification(IQueueService queueService,
UpgradableSpecification UpgradableSpecification,
IPreferredWordService preferredWordServiceCalculator,
Logger logger)
{
_queueService = queueService;
_upgradableSpecification = UpgradableSpecification;
_preferredWordServiceCalculator = preferredWordServiceCalculator;
_logger = logger;
}
@ -26,21 +30,24 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
var queue = _queueService.GetQueue()
.Select(q => q.RemoteEpisode).ToList();
var queue = _queueService.GetQueue();
var matchingSeries = queue.Where(q => q.Series.Id == subject.Series.Id);
var matchingEpisode = matchingSeries.Where(q => q.Episodes.Select(e => e.Id).Intersect(subject.Episodes.Select(e => e.Id)).Any());
var matchingEpisode = matchingSeries.Where(q => q.RemoteEpisode.Episodes.Select(e => e.Id).Intersect(subject.Episodes.Select(e => e.Id)).Any());
foreach (var remoteEpisode in matchingEpisode)
foreach (var queueItem in matchingEpisode)
{
var remoteEpisode = queueItem.RemoteEpisode;
_logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language);
var queuedItemPreferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Series, queueItem.Title);
if (!_upgradableSpecification.CutoffNotMet(subject.Series.Profile,
subject.Series.LanguageProfile,
remoteEpisode.ParsedEpisodeInfo.Quality,
remoteEpisode.ParsedEpisodeInfo.Language,
subject.ParsedEpisodeInfo.Quality))
queuedItemPreferredWordScore,
subject.ParsedEpisodeInfo.Quality,
subject.PreferredWordScore))
{
return Decision.Reject("Quality for release in queue already meets cutoff: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language);
}
@ -51,8 +58,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
subject.Series.LanguageProfile,
remoteEpisode.ParsedEpisodeInfo.Quality,
remoteEpisode.ParsedEpisodeInfo.Language,
queuedItemPreferredWordScore,
subject.ParsedEpisodeInfo.Quality,
subject.ParsedEpisodeInfo.Language))
subject.ParsedEpisodeInfo.Language,
subject.PreferredWordScore))
{
return Decision.Reject("Quality for release in queue is of equal or higher preference: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language);
}

@ -5,20 +5,20 @@ using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Restrictions;
using NzbDrone.Core.Profiles.Releases;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class ReleaseRestrictionsSpecification : IDecisionEngineSpecification
{
private readonly Logger _logger;
private readonly IRestrictionService _restrictionService;
private readonly IReleaseProfileService _releaseProfileService;
private readonly ITermMatcher _termMatcher;
public ReleaseRestrictionsSpecification(ITermMatcher termMatcher, IRestrictionService restrictionService, Logger logger)
public ReleaseRestrictionsSpecification(ITermMatcher termMatcher, IReleaseProfileService releaseProfileService, Logger logger)
{
_logger = logger;
_restrictionService = restrictionService;
_releaseProfileService = releaseProfileService;
_termMatcher = termMatcher;
}
@ -30,7 +30,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
_logger.Debug("Checking if release meets restrictions: {0}", subject);
var title = subject.Release.Title;
var restrictions = _restrictionService.AllForTags(subject.Series.Tags);
var restrictions = _releaseProfileService.AllForTags(subject.Series.Tags);
var required = restrictions.Where(r => r.Required.IsNotNullOrWhiteSpace());
var ignored = restrictions.Where(r => r.Ignored.IsNotNullOrWhiteSpace());

@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using NLog;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.IndexerSearch.Definitions;
@ -6,6 +6,7 @@ using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Releases;
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
{
@ -14,16 +15,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
private readonly IPendingReleaseService _pendingReleaseService;
private readonly IUpgradableSpecification _upgradableSpecification;
private readonly IDelayProfileService _delayProfileService;
private readonly IPreferredWordService _preferredWordServiceCalculator;
private readonly Logger _logger;
public DelaySpecification(IPendingReleaseService pendingReleaseService,
IUpgradableSpecification UpgradableSpecification,
IUpgradableSpecification upgradableSpecification,
IDelayProfileService delayProfileService,
IPreferredWordService preferredWordServiceCalculator,
Logger logger)
{
_pendingReleaseService = pendingReleaseService;
_upgradableSpecification = UpgradableSpecification;
_upgradableSpecification = upgradableSpecification;
_delayProfileService = delayProfileService;
_preferredWordServiceCalculator = preferredWordServiceCalculator;
_logger = logger;
}
@ -50,19 +54,22 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
return Decision.Accept();
}
var comparer = new QualityModelComparer(profile);
var comparerLanguage = new LanguageComparer(languageProfile);
var qualityComparer = new QualityModelComparer(profile);
var languageComparer = new LanguageComparer(languageProfile);
if (isPreferredProtocol)
{
foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
{
var upgradable = _upgradableSpecification.IsUpgradable(profile,
var upgradable = _upgradableSpecification.IsUpgradable(
profile,
languageProfile,
file.Quality,
file.Language,
_preferredWordServiceCalculator.Calculate(subject.Series, file.GetSceneOrFileName()),
subject.ParsedEpisodeInfo.Quality,
subject.ParsedEpisodeInfo.Language);
subject.ParsedEpisodeInfo.Language,
subject.PreferredWordScore);
if (upgradable)
{
@ -74,8 +81,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
// If quality meets or exceeds the best allowed quality in the profile accept it immediately
var bestQualityInProfile = profile.LastAllowedQuality();
var isBestInProfile = comparer.Compare(subject.ParsedEpisodeInfo.Quality.Quality, bestQualityInProfile) >= 0;
var isBestInProfileLanguage = comparerLanguage.Compare(subject.ParsedEpisodeInfo.Language, languageProfile.LastAllowedLanguage()) >= 0;
var isBestInProfile = qualityComparer.Compare(subject.ParsedEpisodeInfo.Quality.Quality, bestQualityInProfile) >= 0;
var isBestInProfileLanguage = languageComparer.Compare(subject.ParsedEpisodeInfo.Language, languageProfile.LastAllowedLanguage()) >= 0;
if (isBestInProfile && isBestInProfileLanguage && isPreferredProtocol)
{

@ -5,6 +5,7 @@ using NzbDrone.Core.Configuration;
using NzbDrone.Core.History;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Releases;
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
{
@ -13,16 +14,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
private readonly IHistoryService _historyService;
private readonly UpgradableSpecification _upgradableSpecification;
private readonly IConfigService _configService;
private readonly IPreferredWordService _preferredWordServiceCalculator;
private readonly Logger _logger;
public HistorySpecification(IHistoryService historyService,
UpgradableSpecification upgradableSpecification,
IConfigService configService,
IPreferredWordService preferredWordServiceCalculator,
Logger logger)
{
_historyService = historyService;
_upgradableSpecification = upgradableSpecification;
_configService = configService;
_preferredWordServiceCalculator = preferredWordServiceCalculator;
_logger = logger;
}
@ -48,8 +52,29 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed)
{
var recent = mostRecent.Date.After(DateTime.UtcNow.AddHours(-12));
var cutoffUnmet = _upgradableSpecification.CutoffNotMet(subject.Series.Profile, subject.Series.LanguageProfile, mostRecent.Quality, mostRecent.Language, subject.ParsedEpisodeInfo.Quality);
var upgradeable = _upgradableSpecification.IsUpgradable(subject.Series.Profile, subject.Series.LanguageProfile, mostRecent.Quality, mostRecent.Language, subject.ParsedEpisodeInfo.Quality, subject.ParsedEpisodeInfo.Language);
// The series will be the same as the one in history since it's the same episode.
// Instead of fetching the series from the DB reuse the known series.
var preferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Series, mostRecent.SourceTitle);
var cutoffUnmet = _upgradableSpecification.CutoffNotMet(
subject.Series.Profile,
subject.Series.LanguageProfile,
mostRecent.Quality,
mostRecent.Language,
preferredWordScore,
subject.ParsedEpisodeInfo.Quality,
subject.PreferredWordScore);
var upgradeable = _upgradableSpecification.IsUpgradable(
subject.Series.Profile,
subject.Series.LanguageProfile,
mostRecent.Quality,
mostRecent.Language,
preferredWordScore,
subject.ParsedEpisodeInfo.Quality,
subject.ParsedEpisodeInfo.Language,
subject.PreferredWordScore);
if (!recent && cdhEnabled)
{

@ -3,7 +3,7 @@ using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.DecisionEngine
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class SameEpisodesSpecification
{

@ -4,14 +4,14 @@ using NzbDrone.Core.Profiles.Languages;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.DecisionEngine
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public interface IUpgradableSpecification
{
bool IsUpgradable(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality, Language newLanguage);
bool IsUpgradable(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality, Language newLanguage, int newScore);
bool QualityCutoffNotMet(Profile profile, QualityModel currentQuality, QualityModel newQuality = null);
bool LanguageCutoffNotMet(LanguageProfile languageProfile, Language currentLanguage);
bool CutoffNotMet(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality = null);
bool CutoffNotMet(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality = null, int newScore = 0);
bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuality);
}
@ -51,19 +51,38 @@ namespace NzbDrone.Core.DecisionEngine
return true;
}
private bool IsPreferredWordUpgradable(int currentScore, int newScore)
{
return newScore > currentScore;
}
public bool IsUpgradable(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality, Language newLanguage)
public bool IsUpgradable(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality, Language newLanguage, int newScore)
{
// If qualities are the same then check language
if (newQuality != null && new QualityModelComparer(profile).Compare(newQuality, currentQuality) == 0)
if (IsQualityUpgradable(profile, currentQuality, newQuality))
{
return IsLanguageUpgradable(languageProfile, currentLanguage, newLanguage);
return true;
}
// If quality is worse then always return false
if (!IsQualityUpgradable(profile, currentQuality, newQuality))
if (new QualityModelComparer(profile).Compare(newQuality, currentQuality) != 0)
{
_logger.Debug("existing item has better quality. skipping");
_logger.Debug("Existing item has better qualitys, skipping");
return false;
}
if (IsLanguageUpgradable(languageProfile, currentLanguage, newLanguage))
{
return true;
}
if (new LanguageComparer(languageProfile).Compare(newLanguage, currentLanguage) != 0)
{
_logger.Debug("Existing item has better language, skipping");
return false;
}
if (!IsPreferredWordUpgradable(currentScore, newScore))
{
_logger.Debug("Existing item has a better preferred word score, skipping");
return false;
}
@ -94,9 +113,10 @@ namespace NzbDrone.Core.DecisionEngine
return languageCompare < 0;
}
public bool CutoffNotMet(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality = null)
public bool CutoffNotMet(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality = null, int newScore = 0)
{
// If we can upgrade the language (it is not the cutoff) then doesn't matter the quality we can always get same quality with prefered language
// If we can upgrade the language (it is not the cutoff) then the quality doesn't
// matter as we can always get same quality with prefered language.
if (LanguageCutoffNotMet(languageProfile, currentLanguage))
{
return true;
@ -107,6 +127,11 @@ namespace NzbDrone.Core.DecisionEngine
return true;
}
if (IsPreferredWordUpgradable(currentScore, newScore))
{
return true;
}
_logger.Debug("Existing item meets cut-off. skipping.");
return false;

@ -1,18 +1,21 @@
using System.Linq;
using System.Linq;
using NLog;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Releases;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class UpgradeDiskSpecification : IDecisionEngineSpecification
{
private readonly UpgradableSpecification _upgradableSpecification;
private readonly IPreferredWordService _preferredWordServiceCalculator;
private readonly Logger _logger;
public UpgradeDiskSpecification(UpgradableSpecification upgradableSpecification, Logger logger)
public UpgradeDiskSpecification(UpgradableSpecification upgradableSpecification, IPreferredWordService preferredWordServiceCalculator, Logger logger)
{
_upgradableSpecification = upgradableSpecification;
_preferredWordServiceCalculator = preferredWordServiceCalculator;
_logger = logger;
}
@ -36,8 +39,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
subject.Series.LanguageProfile,
file.Quality,
file.Language,
_preferredWordServiceCalculator.Calculate(subject.Series, file.GetSceneOrFileName()),
subject.ParsedEpisodeInfo.Quality,
subject.ParsedEpisodeInfo.Language))
subject.ParsedEpisodeInfo.Language,
subject.PreferredWordScore))
{
return Decision.Reject("Quality for existing file on disk is of equal or higher preference: {0} - {1}", file.Quality, file.Language);
}

@ -0,0 +1,22 @@
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Releases;
namespace NzbDrone.Core.Download.Aggregation.Aggregators
{
public class AggregatePreferredWordScore : IAggregateRemoteEpisode
{
private readonly IPreferredWordService _preferredWordServiceCalculator;
public AggregatePreferredWordScore(IPreferredWordService preferredWordServiceCalculator)
{
_preferredWordServiceCalculator = preferredWordServiceCalculator;
}
public RemoteEpisode Aggregate(RemoteEpisode remoteEpisode)
{
remoteEpisode.PreferredWordScore = _preferredWordServiceCalculator.Calculate(remoteEpisode.Series, remoteEpisode.Release.Title);
return remoteEpisode;
}
}
}

@ -0,0 +1,9 @@
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download.Aggregation.Aggregators
{
public interface IAggregateRemoteEpisode
{
RemoteEpisode Aggregate(RemoteEpisode remoteEpisode);
}
}

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using NLog;
using NzbDrone.Core.Download.Aggregation.Aggregators;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download.Aggregation
{
public interface IRemoteEpisodeAggregationService
{
RemoteEpisode Augment(RemoteEpisode remoteEpisode);
}
public class RemoteEpisodeAggregationService : IRemoteEpisodeAggregationService
{
private readonly IEnumerable<IAggregateRemoteEpisode> _augmenters;
private readonly Logger _logger;
public RemoteEpisodeAggregationService(IEnumerable<IAggregateRemoteEpisode> augmenters,
Logger logger)
{
_augmenters = augmenters;
_logger = logger;
}
public RemoteEpisode Augment(RemoteEpisode remoteEpisode)
{
foreach (var augmenter in _augmenters)
{
try
{
augmenter.Aggregate(remoteEpisode);
}
catch (Exception ex)
{
_logger.Warn(ex, ex.Message);
}
}
return remoteEpisode;
}
}
}

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Marr.Data;
using NzbDrone.Common.Serializer;
@ -19,7 +19,7 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
{
var mapper = _database.GetDataMapper();
var usedTags = new[] { "Series", "Notifications", "DelayProfiles", "Restrictions" }
var usedTags = new[] { "Series", "Notifications", "DelayProfiles", "ReleaseProfiles" }
.SelectMany(v => GetUsedTags(v, mapper))
.Distinct()
.ToArray();

@ -44,6 +44,11 @@ namespace NzbDrone.Core.MediaFiles
return System.IO.Path.GetFileName(RelativePath);
}
if (Path.IsNotNullOrWhiteSpace())
{
return System.IO.Path.GetFileName(Path);
}
return string.Empty;
}
}

@ -1,5 +1,6 @@
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;

@ -141,6 +141,7 @@
<Compile Include="CustomFilters\CustomFilter.cs" />
<Compile Include="CustomFilters\CustomFilterRepository.cs" />
<Compile Include="CustomFilters\CustomFilterService.cs" />
<Compile Include="Datastore\Migration\127_rename_release_profiles.cs" />
<Compile Include="Datastore\Migration\126_add_custom_filters.cs" />
<Compile Include="Extras\Metadata\MetadataSectionType.cs" />
<Compile Include="Download\Aggregation\RemoteEpisodeAggregationService.cs" />
@ -340,12 +341,12 @@
<Compile Include="DecisionEngine\DownloadDecisionComparer.cs" />
<Compile Include="DecisionEngine\DownloadDecisionMaker.cs" />
<Compile Include="DecisionEngine\DownloadDecisionPriorizationService.cs" />
<Compile Include="DecisionEngine\IDecisionEngineSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\IDecisionEngineSpecification.cs" />
<Compile Include="DecisionEngine\IRejectWithReason.cs" />
<Compile Include="DecisionEngine\UpgradableSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\UpgradableSpecification.cs" />
<Compile Include="DecisionEngine\Rejection.cs" />
<Compile Include="DecisionEngine\RejectionType.cs" />
<Compile Include="DecisionEngine\SameEpisodesSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\SameEpisodesSpecification.cs" />
<Compile Include="DecisionEngine\SpecificationPriority.cs" />
<Compile Include="DecisionEngine\Specifications\AcceptableSizeSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\BlacklistSpecification.cs" />
@ -1005,6 +1006,7 @@
<Compile Include="Profiles\Qualities\ProfileRepository.cs" />
<Compile Include="Profiles\Qualities\ProfileService.cs" />
<Compile Include="Profiles\Qualities\QualityIndex.cs" />
<Compile Include="Profiles\Releases\PreferredWordService.cs" />
<Compile Include="ProgressMessaging\ProgressMessageContext.cs" />
<Compile Include="Qualities\QualityDetectionSource.cs" />
<Compile Include="Qualities\QualityFinder.cs" />
@ -1134,11 +1136,11 @@
<Compile Include="Queue\Queue.cs" />
<Compile Include="Queue\QueueService.cs" />
<Compile Include="Queue\QueueUpdatedEvent.cs" />
<Compile Include="Restrictions\PerlRegexFactory.cs" />
<Compile Include="Restrictions\Restriction.cs" />
<Compile Include="Restrictions\RestrictionRepository.cs" />
<Compile Include="Restrictions\RestrictionService.cs" />
<Compile Include="Restrictions\TermMatcher.cs" />
<Compile Include="Profiles\Releases\PerlRegexFactory.cs" />
<Compile Include="Profiles\Releases\ReleaseProfile.cs" />
<Compile Include="Profiles\Releases\ReleaseProfileRepository.cs" />
<Compile Include="Profiles\Releases\ReleaseProfileService.cs" />
<Compile Include="Profiles\Releases\TermMatcher.cs" />
<Compile Include="Rest\JsonNetSerializer.cs" />
<Compile Include="Rest\RestClientFactory.cs" />
<Compile Include="Rest\RestException.cs" />

@ -10,6 +10,7 @@ using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
@ -17,7 +18,7 @@ namespace NzbDrone.Core.Organizer
{
public interface IBuildFileNames
{
string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null);
string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null, List<string> preferredWords = null);
string BuildFilePath(Series series, int seasonNumber, string fileName, string extension);
string BuildSeasonPath(Series series, int seasonNumber);
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
@ -30,6 +31,7 @@ namespace NzbDrone.Core.Organizer
{
private readonly INamingConfigService _namingConfigService;
private readonly IQualityDefinitionService _qualityDefinitionService;
private readonly IPreferredWordService _preferredWordService;
private readonly ICached<EpisodeFormat[]> _episodeFormatCache;
private readonly ICached<AbsoluteEpisodeFormat[]> _absoluteEpisodeFormatCache;
private readonly ICached<bool> _requiresEpisodeTitleCache;
@ -76,17 +78,19 @@ namespace NzbDrone.Core.Organizer
public FileNameBuilder(INamingConfigService namingConfigService,
IQualityDefinitionService qualityDefinitionService,
ICacheManager cacheManager,
IPreferredWordService preferredWordService,
Logger logger)
{
_namingConfigService = namingConfigService;
_qualityDefinitionService = qualityDefinitionService;
_preferredWordService = preferredWordService;
_episodeFormatCache = cacheManager.GetCache<EpisodeFormat[]>(GetType(), "episodeFormat");
_absoluteEpisodeFormatCache = cacheManager.GetCache<AbsoluteEpisodeFormat[]>(GetType(), "absoluteEpisodeFormat");
_requiresEpisodeTitleCache = cacheManager.GetCache<bool>(GetType(), "requiresEpisodeTitle");
_logger = logger;
}
public string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null)
public string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null, List<string> preferredWords = null)
{
if (namingConfig == null)
{
@ -137,6 +141,7 @@ namespace NzbDrone.Core.Organizer
AddEpisodeFileTokens(tokenHandlers, episodeFile);
AddQualityTokens(tokenHandlers, series, episodeFile);
AddMediaInfoTokens(tokenHandlers, episodeFile);
AddPreferredWords(tokenHandlers, series, episodeFile, preferredWords);
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
@ -564,6 +569,16 @@ namespace NzbDrone.Core.Organizer
tokenHandlers["{TvMazeId}"] = m => series.TvMazeId.ToString();
}
private void AddPreferredWords(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Series series, EpisodeFile episodeFile, List<string> preferredWords = null)
{
if (preferredWords == null)
{
preferredWords = _preferredWordService.GetMatchingPreferredWords(series, episodeFile.GetSceneOrFileName(), true);
}
tokenHandlers["{Preferred Words}"] = m => string.Join(" ", preferredWords);
}
private string GetLanguagesToken(string mediaInfoLanguages)
{
List<string> tokens = new List<string>();

@ -33,6 +33,7 @@ namespace NzbDrone.Core.Organizer
private static EpisodeFile _dailyEpisodeFile;
private static EpisodeFile _animeEpisodeFile;
private static EpisodeFile _animeMultiEpisodeFile;
private static List<string> _preferredWords;
public FileNameSampleService(IBuildFileNames buildFileNames)
{
@ -162,6 +163,11 @@ namespace NzbDrone.Core.Organizer
ReleaseGroup = "RlsGrp",
MediaInfo = mediaInfoAnime
};
_preferredWords = new List<string>
{
"iNTERNAL"
};
}
public SampleResult GetStandardSample(NamingConfig nameSpec)
@ -243,7 +249,7 @@ namespace NzbDrone.Core.Organizer
{
try
{
return _buildFileNames.BuildFileName(episodes, series, episodeFile, nameSpec);
return _buildFileNames.BuildFileName(episodes, series, episodeFile, nameSpec, _preferredWords);
}
catch (NamingFormatException)
{

@ -14,6 +14,7 @@ namespace NzbDrone.Core.Parser.Model
public List<Episode> Episodes { get; set; }
public bool DownloadAllowed { get; set; }
public TorrentSeedConfiguration SeedConfiguration { get; set; }
public int PreferredWordScore { get; set; }
public bool IsRecentEpisode()
{

@ -1,11 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System;
using System.Text.RegularExpressions;
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Restrictions
namespace NzbDrone.Core.Profiles.Releases
{
public static class PerlRegexFactory
{

@ -0,0 +1,76 @@
using NLog;
using NzbDrone.Core.Tv;
using System.Collections.Generic;
using System.Linq;
namespace NzbDrone.Core.Profiles.Releases
{
public interface IPreferredWordService
{
int Calculate(Series series, string title);
List<string> GetMatchingPreferredWords(Series series, string title, bool isRenaming);
}
public class PreferredWordService : IPreferredWordService
{
private readonly IReleaseProfileService _releaseProfileService;
private readonly ITermMatcher _termMatcher;
private readonly Logger _logger;
public PreferredWordService(IReleaseProfileService releaseProfileService, ITermMatcher termMatcher, Logger logger)
{
_releaseProfileService = releaseProfileService;
_termMatcher = termMatcher;
_logger = logger;
}
public int Calculate(Series series, string title)
{
_logger.Trace("Calculating preferred word score for '{0}'", title);
var matchingPairs = GetMatchingPairs(series, title, false);
var score = matchingPairs.Sum(p => p.Value);
_logger.Trace("Calculated preferred word score for '{0}': {1}", title, score);
return score;
}
public List<string> GetMatchingPreferredWords(Series series, string title, bool isRenaming)
{
var matchingPairs = GetMatchingPairs(series, title, isRenaming);
return matchingPairs.OrderByDescending(p => p.Value)
.Select(p => p.Key)
.ToList();
}
private List<KeyValuePair<string, int>> GetMatchingPairs(Series series, string title, bool isRenaming)
{
var releaseProfiles = _releaseProfileService.AllForTags(series.Tags);
var result = new List<KeyValuePair<string, int>>();
_logger.Trace("Calculating preferred word score for '{0}'", title);
foreach (var releaseProfile in releaseProfiles)
{
if (isRenaming && !releaseProfile.IncludePreferredWhenRenaming)
{
continue;
}
foreach (var preferredPair in releaseProfile.Preferred)
{
var term = preferredPair.Key;
if (_termMatcher.IsMatch(term, title))
{
result.Add(preferredPair);
}
}
}
return result;
}
}
}

@ -0,0 +1,21 @@
using System.Collections.Generic;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Profiles.Releases
{
public class ReleaseProfile : ModelBase
{
public string Required { get; set; }
public string Ignored { get; set; }
public List<KeyValuePair<string, int>> Preferred { get; set; }
public bool IncludePreferredWhenRenaming { get; set; }
public HashSet<int> Tags { get; set; }
public ReleaseProfile()
{
Preferred = new List<KeyValuePair<string, int>>();
IncludePreferredWhenRenaming = true;
Tags = new HashSet<int>();
}
}
}

@ -0,0 +1,17 @@
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Profiles.Releases
{
public interface IRestrictionRepository : IBasicRepository<ReleaseProfile>
{
}
public class ReleaseProfileRepository : BasicRepository<ReleaseProfile>, IRestrictionRepository
{
public ReleaseProfileRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
}
}

@ -0,0 +1,65 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Profiles.Releases
{
public interface IReleaseProfileService
{
List<ReleaseProfile> All();
List<ReleaseProfile> AllForTag(int tagId);
List<ReleaseProfile> AllForTags(HashSet<int> tagIds);
ReleaseProfile Get(int id);
void Delete(int id);
ReleaseProfile Add(ReleaseProfile restriction);
ReleaseProfile Update(ReleaseProfile restriction);
}
public class ReleaseProfileService : IReleaseProfileService
{
private readonly IRestrictionRepository _repo;
private readonly Logger _logger;
public ReleaseProfileService(IRestrictionRepository repo, Logger logger)
{
_repo = repo;
_logger = logger;
}
public List<ReleaseProfile> All()
{
return _repo.All().ToList();
}
public List<ReleaseProfile> AllForTag(int tagId)
{
return _repo.All().Where(r => r.Tags.Contains(tagId)).ToList();
}
public List<ReleaseProfile> AllForTags(HashSet<int> tagIds)
{
return _repo.All().Where(r => r.Tags.Intersect(tagIds).Any() || r.Tags.Empty()).ToList();
}
public ReleaseProfile Get(int id)
{
return _repo.Get(id);
}
public void Delete(int id)
{
_repo.Delete(id);
}
public ReleaseProfile Add(ReleaseProfile restriction)
{
return _repo.Insert(restriction);
}
public ReleaseProfile Update(ReleaseProfile restriction)
{
return _repo.Update(restriction);
}
}
}

@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NzbDrone.Common.Cache;
namespace NzbDrone.Core.Restrictions
namespace NzbDrone.Core.Profiles.Releases
{
public interface ITermMatcher
{

@ -1,18 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Restrictions
{
public class Restriction : ModelBase
{
public string Required { get; set; }
public string Preferred { get; set; }
public string Ignored { get; set; }
public HashSet<int> Tags { get; set; }
public Restriction()
{
Tags = new HashSet<int>();
}
}
}

@ -1,17 +0,0 @@
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Restrictions
{
public interface IRestrictionRepository : IBasicRepository<Restriction>
{
}
public class RestrictionRepository : BasicRepository<Restriction>, IRestrictionRepository
{
public RestrictionRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
}
}

@ -1,65 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Restrictions
{
public interface IRestrictionService
{
List<Restriction> All();
List<Restriction> AllForTag(int tagId);
List<Restriction> AllForTags(HashSet<int> tagIds);
Restriction Get(int id);
void Delete(int id);
Restriction Add(Restriction restriction);
Restriction Update(Restriction restriction);
}
public class RestrictionService : IRestrictionService
{
private readonly IRestrictionRepository _repo;
private readonly Logger _logger;
public RestrictionService(IRestrictionRepository repo, Logger logger)
{
_repo = repo;
_logger = logger;
}
public List<Restriction> All()
{
return _repo.All().ToList();
}
public List<Restriction> AllForTag(int tagId)
{
return _repo.All().Where(r => r.Tags.Contains(tagId)).ToList();
}
public List<Restriction> AllForTags(HashSet<int> tagIds)
{
return _repo.All().Where(r => r.Tags.Intersect(tagIds).Any() || r.Tags.Empty()).ToList();
}
public Restriction Get(int id)
{
return _repo.Get(id);
}
public void Delete(int id)
{
_repo.Delete(id);
}
public Restriction Add(Restriction restriction)
{
return _repo.Insert(restriction);
}
public Restriction Update(Restriction restriction)
{
return _repo.Update(restriction);
}
}
}

@ -3,7 +3,7 @@ using System.Linq;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Notifications;
using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Restrictions;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Tags
@ -27,21 +27,21 @@ namespace NzbDrone.Core.Tags
private readonly IEventAggregator _eventAggregator;
private readonly IDelayProfileService _delayProfileService;
private readonly INotificationFactory _notificationFactory;
private readonly IRestrictionService _restrictionService;
private readonly IReleaseProfileService _releaseProfileService;
private readonly ISeriesService _seriesService;
public TagService(ITagRepository repo,
IEventAggregator eventAggregator,
IDelayProfileService delayProfileService,
INotificationFactory notificationFactory,
IRestrictionService restrictionService,
IReleaseProfileService releaseProfileService,
ISeriesService seriesService)
{
_repo = repo;
_eventAggregator = eventAggregator;
_delayProfileService = delayProfileService;
_notificationFactory = notificationFactory;
_restrictionService = restrictionService;
_releaseProfileService = releaseProfileService;
_seriesService = seriesService;
}
@ -72,7 +72,7 @@ namespace NzbDrone.Core.Tags
var tag = GetTag(tagId);
var delayProfiles = _delayProfileService.AllForTag(tagId);
var notifications = _notificationFactory.AllForTag(tagId);
var restrictions = _restrictionService.AllForTag(tagId);
var restrictions = _releaseProfileService.AllForTag(tagId);
var series = _seriesService.AllForTag(tagId);
return new TagDetails
@ -91,7 +91,7 @@ namespace NzbDrone.Core.Tags
var tags = All();
var delayProfiles = _delayProfileService.All();
var notifications = _notificationFactory.All();
var restrictions = _restrictionService.All();
var restrictions = _releaseProfileService.All();
var series = _seriesService.GetAllSeries();
var details = new List<TagDetails>();

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Tv;
using NzbDrone.SignalR;
using Sonarr.Api.V3.Episodes;

@ -4,6 +4,7 @@ using System.Linq;
using Nancy;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;

@ -1,6 +1,7 @@
using System;
using System.IO;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Qualities;

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Nancy;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Tv;
using NzbDrone.SignalR;
using Sonarr.Http.Extensions;

@ -2,6 +2,7 @@ using System.Collections.Generic;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;

@ -4,6 +4,7 @@ using System.Linq;
using Nancy;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download;
using NzbDrone.Core.History;
using Sonarr.Api.V3.Episodes;

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using NzbDrone.Core.DecisionEngine;
using Sonarr.Http;
@ -28,9 +28,15 @@ namespace Sonarr.Api.V3.Indexers
if (decision.RemoteEpisode.Series != null)
{
release.QualityWeight = decision.RemoteEpisode.Series
release.QualityWeight = decision.RemoteEpisode
.Series
.Profile.Value
.Items.FindIndex(v => v.Quality == release.Quality.Quality) * 100;
release.LanguageWeight = decision.RemoteEpisode
.Series
.LanguageProfile.Value
.Languages.FindIndex(v => v.Language == release.Language) * 100;
}
release.QualityWeight += release.Quality.Revision.Real * 10;

@ -29,6 +29,7 @@ namespace Sonarr.Api.V3.Indexers
public bool SceneSource { get; set; }
public int SeasonNumber { get; set; }
public Language Language { get; set; }
public int LanguageWeight { get; set; }
public string AirDate { get; set; }
public string SeriesTitle { get; set; }
public int[] EpisodeNumbers { get; set; }
@ -45,6 +46,7 @@ namespace Sonarr.Api.V3.Indexers
public string InfoUrl { get; set; }
public bool DownloadAllowed { get; set; }
public int ReleaseWeight { get; set; }
public int PreferredWordScore { get; set; }
public string MagnetUrl { get; set; }
public string InfoHash { get; set; }
@ -104,7 +106,7 @@ namespace Sonarr.Api.V3.Indexers
InfoUrl = releaseInfo.InfoUrl,
DownloadAllowed = remoteEpisode.DownloadAllowed,
//ReleaseWeight
PreferredWordScore = remoteEpisode.PreferredWordScore,
MagnetUrl = torrentInfo.MagnetUrl,
InfoHash = torrentInfo.InfoHash,

@ -0,0 +1,60 @@
using System.Collections.Generic;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Profiles.Releases;
using Sonarr.Http;
namespace Sonarr.Api.V3.Profiles.Release
{
public class ReleaseProfileModule : SonarrRestModule<ReleaseProfileResource>
{
private readonly IReleaseProfileService _releaseProfileService;
public ReleaseProfileModule(IReleaseProfileService releaseProfileService)
{
_releaseProfileService = releaseProfileService;
GetResourceById = Get;
GetResourceAll = GetAll;
CreateResource = Create;
UpdateResource = Update;
DeleteResource = Delete;
SharedValidator.Custom(restriction =>
{
if (restriction.Ignored.IsNullOrWhiteSpace() && restriction.Required.IsNullOrWhiteSpace() && restriction.Preferred.Empty())
{
return new ValidationFailure("", "'Must contain', 'Must not contain' or 'Preferred' is required");
}
return null;
});
}
private ReleaseProfileResource Get(int id)
{
return _releaseProfileService.Get(id).ToResource();
}
private List<ReleaseProfileResource> GetAll()
{
return _releaseProfileService.All().ToResource();
}
private int Create(ReleaseProfileResource resource)
{
return _releaseProfileService.Add(resource.ToModel()).Id;
}
private void Update(ReleaseProfileResource resource)
{
_releaseProfileService.Update(resource.ToModel());
}
private void Delete(int id)
{
_releaseProfileService.Delete(id);
}
}
}

@ -1,18 +1,19 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Restrictions;
using NzbDrone.Core.Profiles.Releases;
using Sonarr.Http.REST;
namespace Sonarr.Api.V3.Restrictions
namespace Sonarr.Api.V3.Profiles.Release
{
public class RestrictionResource : RestResource
public class ReleaseProfileResource : RestResource
{
public string Required { get; set; }
public string Preferred { get; set; }
public string Ignored { get; set; }
public List<KeyValuePair<string, int>> Preferred { get; set; }
public bool IncludePreferredWhenRenaming { get; set; }
public HashSet<int> Tags { get; set; }
public RestrictionResource()
public ReleaseProfileResource()
{
Tags = new HashSet<int>();
}
@ -20,37 +21,39 @@ namespace Sonarr.Api.V3.Restrictions
public static class RestrictionResourceMapper
{
public static RestrictionResource ToResource(this Restriction model)
public static ReleaseProfileResource ToResource(this ReleaseProfile model)
{
if (model == null) return null;
return new RestrictionResource
return new ReleaseProfileResource
{
Id = model.Id,
Required = model.Required,
Preferred = model.Preferred,
Ignored = model.Ignored,
Preferred = model.Preferred,
IncludePreferredWhenRenaming = model.IncludePreferredWhenRenaming,
Tags = new HashSet<int>(model.Tags)
};
}
public static Restriction ToModel(this RestrictionResource resource)
public static ReleaseProfile ToModel(this ReleaseProfileResource resource)
{
if (resource == null) return null;
return new Restriction
return new ReleaseProfile
{
Id = resource.Id,
Required = resource.Required,
Preferred = resource.Preferred,
Ignored = resource.Ignored,
Preferred = resource.Preferred,
IncludePreferredWhenRenaming = resource.IncludePreferredWhenRenaming,
Tags = new HashSet<int>(resource.Tags)
};
}
public static List<RestrictionResource> ToResource(this IEnumerable<Restriction> models)
public static List<ReleaseProfileResource> ToResource(this IEnumerable<ReleaseProfile> models)
{
return models.Select(ToResource).ToList();
}

@ -1,60 +0,0 @@
using System.Collections.Generic;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Restrictions;
using Sonarr.Http;
namespace Sonarr.Api.V3.Restrictions
{
public class RestrictionModule : SonarrRestModule<RestrictionResource>
{
private readonly IRestrictionService _restrictionService;
public RestrictionModule(IRestrictionService restrictionService)
{
_restrictionService = restrictionService;
GetResourceById = Get;
GetResourceAll = GetAll;
CreateResource = Create;
UpdateResource = Update;
DeleteResource = Delete;
SharedValidator.Custom(restriction =>
{
if (restriction.Ignored.IsNullOrWhiteSpace() && restriction.Required.IsNullOrWhiteSpace())
{
return new ValidationFailure("", "Either 'Must contain' or 'Must not contain' is required");
}
return null;
});
}
private RestrictionResource Get(int id)
{
return _restrictionService.Get(id).ToResource();
}
private List<RestrictionResource> GetAll()
{
return _restrictionService.All().ToResource();
}
private int Create(RestrictionResource resource)
{
return _restrictionService.Add(resource.ToModel()).Id;
}
private void Update(RestrictionResource resource)
{
_restrictionService.Update(resource.ToModel());
}
private void Delete(int id)
{
_restrictionService.Delete(id);
}
}
}

@ -175,8 +175,8 @@
<Compile Include="Qualities\QualityDefinitionResource.cs" />
<Compile Include="Queue\QueueModule.cs" />
<Compile Include="Queue\QueueResource.cs" />
<Compile Include="Restrictions\RestrictionModule.cs" />
<Compile Include="Restrictions\RestrictionResource.cs" />
<Compile Include="Profiles\Release\ReleaseProfileModule.cs" />
<Compile Include="Profiles\Release\ReleaseProfileResource.cs" />
<Compile Include="RootFolders\RootFolderModule.cs" />
<Compile Include="RootFolders\RootFolderResource.cs" />
<Compile Include="SeasonPass\SeasonPassResource.cs" />

@ -1,6 +1,7 @@
using System.Linq;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Tv;
using NzbDrone.SignalR;
using Sonarr.Api.V3.Episodes;

@ -1,6 +1,7 @@
using System.Linq;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Tv;
using NzbDrone.SignalR;
using Sonarr.Api.V3.Episodes;

Loading…
Cancel
Save