Preferred words

New: Ability to prefer releases based on terms in release title
This commit is contained in:
Mark McDowall 2018-10-20 13:27:51 -07:00 committed by Taloth Saldono
parent ac709c39ab
commit 853f25468c
70 changed files with 852 additions and 347 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
}
@ -95,9 +100,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_return_true_when_series_doesnt_match()
{
var remoteEpisode = Builder<RemoteEpisode>.CreateNew()
.With(r => r.Series = _otherSeries)
.With(r => r.Episodes = new List<Episode> { _episode })
.Build();
.With(r => r.Series = _otherSeries)
.With(r => r.Episodes = new List<Episode> { _episode })
.With(r => r.Release = _releaseInfo)
.Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode });
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
@ -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,12 +161,33 @@ 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 });
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_false_when_qualities_are_the_same_and_languages_are_the_same()
{
@ -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 });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
languageProfile,
file.Quality,
file.Language,
subject.ParsedEpisodeInfo.Quality,
subject.ParsedEpisodeInfo.Language);
var upgradable = _upgradableSpecification.IsUpgradable(
profile,
languageProfile,
file.Quality,
file.Language,
_preferredWordServiceCalculator.Calculate(subject.Series, file.GetSceneOrFileName()),
subject.ParsedEpisodeInfo.Quality,
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)
{

View File

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

View File

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

View File

@ -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;
}
public bool IsUpgradable(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality, Language newLanguage)
private bool IsPreferredWordUpgradable(int currentScore, int newScore)
{
// If qualities are the same then check language
if (newQuality != null && new QualityModelComparer(profile).Compare(newQuality, currentQuality) == 0)
return newScore > currentScore;
}
public bool IsUpgradable(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality, Language newLanguage, int newScore)
{
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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
.Profile.Value
.Items.FindIndex(v => v.Quality == release.Quality.Quality) * 100;
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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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