Fixed: Cutoff Specification not Respecting Profile Order (#660)

* Fixed: Cutoff Specification not Repsecting Profile Order

* Fixed: Incorrect wording in UpgradeAllowed logging

* Fixed: Change Logic to update if upgrade for any, downgrade for none.

* Fixed: Removed Double Preferred Word Logic

* New: Add Test Cases to Disk Upgrade Spec

* Fixed: Cleanup UpgradableSpecification

* Add ConcatToString extension and fix logging

* Fixed: Enum Naming, Commas
This commit is contained in:
Qstick 2019-03-21 18:02:16 -04:00 committed by GitHub
parent 0ebaa90f54
commit 4d8bcd12e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 602 additions and 169 deletions

View File

@ -137,5 +137,15 @@ namespace NzbDrone.Common.Extensions
{
return source.Select(predicate).ToList();
}
public static string ConcatToString<TSource>(this IEnumerable<TSource> source, string separator = ", ")
{
return string.Join(separator, source.Select(x => x.ToString()));
}
public static string ConcatToString<TSource>(this IEnumerable<TSource> source, Func<TSource, string> predicate, string separator = ", ")
{
return string.Join(separator, source.Select(predicate));
}
}
}

View File

@ -7,6 +7,7 @@ using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Languages;
using NzbDrone.Core.Test.Languages;
using System.Collections.Generic;
namespace NzbDrone.Core.Test.DecisionEngineTests
{
@ -30,7 +31,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Languages = LanguageFixture.GetDefaultLanguages(Language.English),
Cutoff = Language.English
},
new QualityModel(Quality.MP3_192, new Revision(version: 2)), Language.English, NoPreferredWordScore).Should().BeTrue();
new List<QualityModel> { new QualityModel(Quality.MP3_192, new Revision(version: 2)) },
new List<Language> { Language.English }, NoPreferredWordScore).Should().BeTrue();
}
[Test]
@ -47,7 +49,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Languages = LanguageFixture.GetDefaultLanguages(Language.English),
Cutoff = Language.English
},
new QualityModel(Quality.MP3_256, new Revision(version: 2)), Language.English, NoPreferredWordScore).Should().BeFalse();
new List<QualityModel> { new QualityModel(Quality.MP3_256, new Revision(version: 2)) },
new List<Language> { Language.English }, NoPreferredWordScore).Should().BeFalse();
}
[Test]
@ -65,7 +68,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Languages = LanguageFixture.GetDefaultLanguages(Language.English),
Cutoff = Language.English
},
new QualityModel(Quality.MP3_320, new Revision(version: 2)), Language.English, NoPreferredWordScore).Should().BeFalse();
new List<QualityModel> { new QualityModel(Quality.MP3_320, new Revision(version: 2)) },
new List<Language> { Language.English }, NoPreferredWordScore).Should().BeFalse();
}
[Test]
@ -83,8 +87,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Languages = LanguageFixture.GetDefaultLanguages(Language.English),
Cutoff = Language.English
},
new QualityModel(Quality.MP3_320, new Revision(version: 1)),
Language.English,
new List<QualityModel> { new QualityModel(Quality.MP3_320, new Revision(version: 1)) },
new List<Language> { Language.English },
NoPreferredWordScore,
new QualityModel(Quality.MP3_320, new Revision(version: 2))).Should().BeTrue();
@ -105,7 +109,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Languages = LanguageFixture.GetDefaultLanguages(Language.English),
Cutoff = Language.English
},
new QualityModel(Quality.MP3_320, new Revision(version: 2)), Language.English,
new List<QualityModel> { new QualityModel(Quality.MP3_320, new Revision(version: 2)) },
new List<Language> { Language.English },
NoPreferredWordScore,
new QualityModel(Quality.FLAC, new Revision(version: 2))).Should().BeFalse();
}
@ -128,8 +133,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.CutoffNotMet(_profile,
_langProfile,
new QualityModel(Quality.MP3_320, new Revision(version: 2)),
Language.English,
new List<QualityModel> { new QualityModel(Quality.MP3_320, new Revision(version: 2)) },
new List<Language> { Language.English },
NoPreferredWordScore,
new QualityModel(Quality.FLAC, new Revision(version: 2))).Should().BeTrue();
}
@ -153,8 +158,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.CutoffNotMet(
_profile,
_langProfile,
new QualityModel(Quality.MP3_320, new Revision(version: 2)),
Language.Spanish,
new List<QualityModel> { new QualityModel(Quality.MP3_320, new Revision(version: 2)) },
new List<Language> { Language.Spanish },
NoPreferredWordScore,
new QualityModel(Quality.FLAC, new Revision(version: 2))).Should().BeFalse();
}
@ -178,8 +183,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.CutoffNotMet(
_profile,
_langProfile,
new QualityModel(Quality.MP3_320, new Revision(version: 2)),
Language.French,
new List<QualityModel> { new QualityModel(Quality.MP3_320, new Revision(version: 2)) },
new List<Language> { Language.French },
NoPreferredWordScore,
new QualityModel(Quality.FLAC, new Revision(version: 2))).Should().BeFalse();
}
@ -203,8 +208,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.CutoffNotMet(
_profile,
_langProfile,
new QualityModel(Quality.MP3_256, new Revision(version: 2)),
Language.French,
new List<QualityModel> { new QualityModel(Quality.MP3_256, new Revision(version: 2)) },
new List<Language> { Language.French },
NoPreferredWordScore,
new QualityModel(Quality.FLAC, new Revision(version: 2))).Should().BeTrue();
}
@ -228,8 +233,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.CutoffNotMet(
_profile,
_langProfile,
new QualityModel(Quality.MP3_256, new Revision(version: 2)),
Language.French, NoPreferredWordScore).Should().BeTrue();
new List<QualityModel> { new QualityModel(Quality.MP3_256, new Revision(version: 2)) },
new List<Language> { Language.French },
NoPreferredWordScore).Should().BeTrue();
}
[Test]
@ -250,8 +256,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.CutoffNotMet(
_profile,
_langProfile,
new QualityModel(Quality.MP3_320, new Revision(version: 2)),
Language.Spanish,
new List<QualityModel> { new QualityModel(Quality.MP3_320, new Revision(version: 2)) },
new List<Language> { Language.Spanish },
NoPreferredWordScore,
new QualityModel(Quality.FLAC, new Revision(version: 2)),
10).Should().BeTrue();

View File

@ -96,7 +96,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
private void GivenUpgradeForExistingFile()
{
Mocker.GetMock<IUpgradableSpecification>()
.Setup(s => s.IsUpgradable(It.IsAny<QualityProfile>(), It.IsAny<LanguageProfile>(), It.IsAny<QualityModel>(), It.IsAny<Language>(), It.IsAny<int>(), It.IsAny<QualityModel>(), It.IsAny<Language>(), It.IsAny<int>()))
.Setup(s => s.IsUpgradable(It.IsAny<QualityProfile>(), It.IsAny<LanguageProfile>(), It.IsAny<List<QualityModel>>(), It.IsAny<List<Language>>(), It.IsAny<int>(), It.IsAny<QualityModel>(), It.IsAny<Language>(), It.IsAny<int>()))
.Returns(true);
}

View File

@ -0,0 +1,293 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Languages;
using NzbDrone.Core.Test.Languages;
using System.Collections.Generic;
namespace NzbDrone.Core.Test.DecisionEngineTests
{
[TestFixture]
public class UpgradeAllowedSpecificationFixture : CoreTest<UpgradableSpecification>
{
[Test]
public void should_return_false_when_quality_are_the_same_language_is_better_and_upgrade_allowed_is_false_for_language_profile()
{
Subject.IsUpgradeAllowed(
new QualityProfile
{
Cutoff = Quality.FLAC.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
UpgradeAllowed = true
},
new LanguageProfile
{
Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French),
Cutoff = Language.French,
UpgradeAllowed = false
},
new List<QualityModel> { new QualityModel(Quality.MP3_320) },
new List<Language> { Language.English },
new QualityModel(Quality.MP3_320),
Language.French
).Should().BeFalse();
}
[Test]
public void should_return_false_when_quality_is_better_languages_are_the_same_and_upgrade_allowed_is_false_for_quality_profile()
{
Subject.IsUpgradeAllowed(
new QualityProfile
{
Cutoff = Quality.FLAC.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
UpgradeAllowed = false
},
new LanguageProfile
{
Languages = LanguageFixture.GetDefaultLanguages(Language.English),
Cutoff = Language.English,
UpgradeAllowed = true
},
new List<QualityModel> { new QualityModel(Quality.MP3_320) },
new List<Language> { Language.English },
new QualityModel(Quality.FLAC),
Language.English
).Should().BeFalse();
}
[Test]
public void should_return_true_for_language_upgrade_when_upgrading_is_allowed()
{
Subject.IsUpgradeAllowed(
new QualityProfile
{
Cutoff = Quality.FLAC.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
UpgradeAllowed = true
},
new LanguageProfile
{
Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French),
Cutoff = Language.French,
UpgradeAllowed = true
},
new List<QualityModel> { new QualityModel(Quality.MP3_320) },
new List<Language> { Language.English },
new QualityModel(Quality.MP3_320),
Language.French
).Should().BeTrue();
}
[Test]
public void should_return_true_for_same_language_when_upgrading_is_allowed()
{
Subject.IsUpgradeAllowed(
new QualityProfile
{
Cutoff = Quality.FLAC.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
UpgradeAllowed = true
},
new LanguageProfile
{
Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French),
Cutoff = Language.French,
UpgradeAllowed = true
},
new List<QualityModel> { new QualityModel(Quality.MP3_320) },
new List<Language> { Language.English },
new QualityModel(Quality.MP3_320),
Language.English
).Should().BeTrue();
}
[Test]
public void should_return_true_for_same_language_when_upgrading_is_not_allowed()
{
Subject.IsUpgradeAllowed(
new QualityProfile
{
Cutoff = Quality.FLAC.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
UpgradeAllowed = true
},
new LanguageProfile
{
Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French),
Cutoff = Language.French,
UpgradeAllowed = false
},
new List<QualityModel> { new QualityModel(Quality.MP3_320) },
new List<Language> { Language.French },
new QualityModel(Quality.MP3_320),
Language.English
).Should().BeTrue();
}
[Test]
public void should_return_true_for_lower_language_when_upgrading_is_allowed()
{
Subject.IsUpgradeAllowed(
new QualityProfile
{
Cutoff = Quality.FLAC.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
UpgradeAllowed = true
},
new LanguageProfile
{
Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French),
Cutoff = Language.French,
UpgradeAllowed = true
},
new List<QualityModel> { new QualityModel(Quality.MP3_320) },
new List<Language> { Language.French },
new QualityModel(Quality.MP3_320),
Language.English
).Should().BeTrue();
}
[Test]
public void should_return_true_for_lower_language_when_upgrading_is_not_allowed()
{
Subject.IsUpgradeAllowed(
new QualityProfile
{
Cutoff = Quality.FLAC.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
UpgradeAllowed = true
},
new LanguageProfile
{
Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French),
Cutoff = Language.French,
UpgradeAllowed = false
},
new List<QualityModel> { new QualityModel(Quality.MP3_320) },
new List<Language> { Language.French },
new QualityModel(Quality.MP3_320),
Language.English
).Should().BeTrue();
}
[Test]
public void should_return_true_for_quality_upgrade_when_upgrading_is_allowed()
{
Subject.IsUpgradeAllowed(
new QualityProfile
{
Cutoff = Quality.FLAC.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
UpgradeAllowed = true
},
new LanguageProfile
{
Languages = LanguageFixture.GetDefaultLanguages(Language.English),
Cutoff = Language.English,
UpgradeAllowed = true
},
new List<QualityModel> { new QualityModel(Quality.MP3_320) },
new List<Language> { Language.English },
new QualityModel(Quality.FLAC),
Language.English
).Should().BeTrue();
}
[Test]
public void should_return_true_for_same_quality_when_upgrading_is_allowed()
{
Subject.IsUpgradeAllowed(
new QualityProfile
{
Cutoff = Quality.FLAC.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
UpgradeAllowed = true
},
new LanguageProfile
{
Languages = LanguageFixture.GetDefaultLanguages(Language.English),
Cutoff = Language.English,
UpgradeAllowed = true
},
new List<QualityModel> { new QualityModel(Quality.MP3_320) },
new List<Language> { Language.English },
new QualityModel(Quality.MP3_320),
Language.English
).Should().BeTrue();
}
[Test]
public void should_return_true_for_same_quality_when_upgrading_is_not_allowed()
{
Subject.IsUpgradeAllowed(
new QualityProfile
{
Cutoff = Quality.FLAC.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
UpgradeAllowed = false
},
new LanguageProfile
{
Languages = LanguageFixture.GetDefaultLanguages(Language.English),
Cutoff = Language.English,
UpgradeAllowed = true
},
new List<QualityModel> { new QualityModel(Quality.MP3_320) },
new List<Language> { Language.English },
new QualityModel(Quality.MP3_320),
Language.English
).Should().BeTrue();
}
[Test]
public void should_return_true_for_lower_quality_when_upgrading_is_allowed()
{
Subject.IsUpgradeAllowed(
new QualityProfile
{
Cutoff = Quality.FLAC.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
UpgradeAllowed = true
},
new LanguageProfile
{
Languages = LanguageFixture.GetDefaultLanguages(Language.English),
Cutoff = Language.English,
UpgradeAllowed = true
},
new List<QualityModel> { new QualityModel(Quality.MP3_320) },
new List<Language> { Language.English },
new QualityModel(Quality.MP3_256),
Language.English
).Should().BeTrue();
}
[Test]
public void should_return_true_for_lower_quality_when_upgrading_is_not_allowed()
{
Subject.IsUpgradeAllowed(
new QualityProfile
{
Cutoff = Quality.FLAC.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
UpgradeAllowed = false
},
new LanguageProfile
{
Languages = LanguageFixture.GetDefaultLanguages(Language.English),
Cutoff = Language.English,
UpgradeAllowed = true
},
new List<QualityModel>{ new QualityModel(Quality.MP3_320) },
new List<Language> { Language.English },
new QualityModel(Quality.MP3_256),
Language.English
).Should().BeTrue();
}
}
}

View File

@ -34,8 +34,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_firstFile = new TrackFile { Quality = new QualityModel(Quality.FLAC, new Revision(version: 2)), DateAdded = DateTime.Now, Language = Language.English };
_secondFile = new TrackFile { Quality = new QualityModel(Quality.FLAC, new Revision(version: 2)), DateAdded = DateTime.Now, Language = Language.English };
var singleEpisodeList = new List<Album> { new Album {}};
var doubleEpisodeList = new List<Album> { new Album {}, new Album {}, new Album {} };
var singleAlbumList = new List<Album> { new Album {}};
var doubleAlbumList = new List<Album> { new Album {}, new Album {}, new Album {} };
var languages = Languages.LanguageFixture.GetDefaultLanguages(Language.English, Language.Spanish);
@ -66,14 +66,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
Artist = fakeArtist,
ParsedAlbumInfo = new ParsedAlbumInfo { Quality = new QualityModel(Quality.MP3_256, new Revision(version: 2)), Language = Language.English },
Albums = doubleEpisodeList
Albums = doubleAlbumList
};
_parseResultSingle = new RemoteAlbum
{
Artist = fakeArtist,
ParsedAlbumInfo = new ParsedAlbumInfo { Quality = new QualityModel(Quality.MP3_256, new Revision(version: 2)), Language = Language.English },
Albums = singleEpisodeList
Albums = singleAlbumList
};
}
@ -127,9 +127,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
}
[Test]
public void should_be_upgradable_if_album_is_upgradable()
public void should_be_upgradable_if_all_files_are_upgradable()
{
WithFirstFileUpgradable();
WithSecondFileUpgradable();
Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue();
}
@ -137,6 +138,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_not_be_upgradable_if_qualities_are_the_same()
{
_firstFile.Quality = new QualityModel(Quality.MP3_320);
_secondFile.Quality = new QualityModel(Quality.MP3_320);
_parseResultSingle.ParsedAlbumInfo.Quality = new QualityModel(Quality.MP3_320);
Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse();
}
@ -146,5 +148,21 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse();
}
[Test]
public void should_be_true_if_some_tracks_are_upgradable_and_none_are_downgrades()
{
WithFirstFileUpgradable();
_parseResultSingle.ParsedAlbumInfo.Quality = _secondFile.Quality;
Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_false_if_some_tracks_are_upgradable_and_some_are_downgrades()
{
WithFirstFileUpgradable();
_parseResultSingle.ParsedAlbumInfo.Quality = new QualityModel(Quality.MP3_320);
Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse();
}
}
}

View File

@ -8,6 +8,7 @@ using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Languages;
using NzbDrone.Core.Test.Languages;
using System.Collections.Generic;
namespace NzbDrone.Core.Test.DecisionEngineTests
{
@ -65,8 +66,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.IsUpgradable(
profile,
langProfile,
new QualityModel(current, new Revision(version: currentVersion)),
Language.English,
new List<QualityModel> { new QualityModel(current, new Revision(version: currentVersion)) },
new List<Language> { Language.English },
NoPreferredWordScore,
new QualityModel(newQuality, new Revision(version: newVersion)),
Language.English,
@ -96,8 +97,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.IsUpgradable(
profile,
langProfile,
new QualityModel(current, new Revision(version: currentVersion)),
currentLanguage,
new List<QualityModel> { new QualityModel(current, new Revision(version: currentVersion)) },
new List<Language> { currentLanguage },
NoPreferredWordScore,
new QualityModel(newQuality, new Revision(version: newVersion)),
newLanguage,
@ -125,8 +126,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.IsUpgradable(
profile,
langProfile,
new QualityModel(Quality.MP3_256, new Revision(version: 2)),
Language.English,
new List<QualityModel> { new QualityModel(Quality.MP3_256, new Revision(version: 2)) },
new List<Language> { Language.English },
NoPreferredWordScore,
new QualityModel(Quality.MP3_256, new Revision(version: 1)),
Language.English,

View File

@ -147,6 +147,7 @@
<Compile Include="DecisionEngineTests\ReleaseRestrictionsSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\PrioritizeDownloadDecisionFixture.cs" />
<Compile Include="DecisionEngineTests\QualityAllowedByProfileSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\UpgradeAllowedSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\UpgradeSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\MinimumAgeSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\RetentionSpecificationFixture.cs" />

View File

@ -1,75 +0,0 @@
using NLog;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.DecisionEngine
{
public interface ILanguageUpgradableSpecification
{
bool IsUpgradable(Profile profile, LanguageModel currentLanguage, LanguageModel newLanguage = null);
bool CutoffNotMet(Profile profile, LanguageModel currentLanguage, LanguageModel newLanguage = null);
bool IsRevisionUpgrade(LanguageModel currentLanguage, LanguageModel newLanguage);
}
public class LanguageUpgradableSpecification : ILanguageUpgradableSpecification
{
private readonly Logger _logger;
public LanguageUpgradableSpecification(Logger logger)
{
_logger = logger;
}
public bool IsUpgradable(Profile profile, LanguageModel currentLanguage, LanguageModel newLanguage = null)
{
if (newLanguage != null)
{
int compare = new LanguageModelComparer(profile).Compare(newLanguage, currentLanguage);
if (compare <= 0)
{
_logger.Debug("existing item has better or equal language. skipping");
return false;
}
if (IsRevisionUpgrade(currentLanguage, newLanguage))
{
return true;
}
}
return true;
}
public bool CutoffNotMet(Profile profile, LanguageModel currentLanguage, LanguageModel newLanguage = null)
{
int compare = new LanguageModelComparer(profile).Compare(currentLanguage.Language, profile.Languages.Find(v => v.Allowed == true).Language);
if (compare >= 0)
{
if (newLanguage != null && IsRevisionUpgrade(currentLanguage, newLanguage))
{
return true;
}
_logger.Debug("Existing item meets cut-off. skipping.");
return false;
}
return true;
}
public bool IsRevisionUpgrade(LanguageModel currentLanguage, LanguageModel newLanguage)
{
int compare = newLanguage.Revision.CompareTo(currentLanguage.Revision);
if (currentLanguage.Language == newLanguage.Language && compare > 0)
{
_logger.Debug("New language is a better revision for existing quality");
return true;
}
return false;
}
}
}

View File

@ -7,6 +7,7 @@ using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Music;
using NzbDrone.Common.Cache;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
@ -39,8 +40,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
{
var profile = subject.Artist.QualityProfile.Value;
var qualityProfile = subject.Artist.QualityProfile.Value;
var languageProfile = subject.Artist.LanguageProfile.Value;
foreach (var album in subject.Albums)
{
@ -50,23 +51,26 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
if (!tracksMissing && trackFiles.Any())
{
var lowestQuality = trackFiles.Select(c => c.Quality).OrderBy(c => c.Quality.Id).First();
// Get a distinct list of all current track qualities and languages for a given album
var currentQualities = trackFiles.Select(c => c.Quality).Distinct().ToList();
var currentLanguages = trackFiles.Select(c => c.Language).Distinct().ToList();
_logger.Debug("Comparing file quality and language with report. Existing file is {0}", lowestQuality.Quality);
_logger.Debug("Comparing file quality and language with report. Existing files contain {0} : {1}", currentQualities.ConcatToString(), currentLanguages.ConcatToString());
if (!_upgradableSpecification.CutoffNotMet(profile,
subject.Artist.LanguageProfile,
lowestQuality,
trackFiles[0].Language,
if (!_upgradableSpecification.CutoffNotMet(qualityProfile,
languageProfile,
currentQualities,
currentLanguages,
_preferredWordServiceCalculator.Calculate(subject.Artist, trackFiles[0].GetSceneOrFileName()),
subject.ParsedAlbumInfo.Quality,
subject.PreferredWordScore))
{
_logger.Debug("Cutoff already met, rejecting.");
var qualityCutoffIndex = profile.GetIndex(profile.Cutoff);
var qualityCutoff = profile.Items[qualityCutoffIndex.Index];
_logger.Debug("Cutoff already met by existing files, rejecting.");
return Decision.Reject("Existing file meets cutoff: {0} - {1}", qualityCutoff, subject.Artist.LanguageProfile.Value.Cutoff);
var qualityCutoffIndex = qualityProfile.GetIndex(qualityProfile.Cutoff);
var qualityCutoff = qualityProfile.Items[qualityCutoffIndex.Index];
return Decision.Reject("Existing files meets cutoff: {0} - {1}", qualityCutoff, languageProfile.Cutoff);
}
}

View File

@ -1,8 +1,11 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Queue;
namespace NzbDrone.Core.DecisionEngine.Specifications
@ -41,34 +44,48 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
foreach (var queueItem in matchingAlbum)
{
var remoteAlbum = queueItem.RemoteAlbum;
var qualityProfile = subject.Artist.QualityProfile.Value;
var languageProfile = subject.Artist.LanguageProfile.Value;
_logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0} - {1}", remoteAlbum.ParsedAlbumInfo.Quality, remoteAlbum.ParsedAlbumInfo.Language);
var queuedItemPreferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Artist, queueItem.Title);
if (!_upgradableSpecification.CutoffNotMet(subject.Artist.QualityProfile,
subject.Artist.LanguageProfile,
remoteAlbum.ParsedAlbumInfo.Quality,
remoteAlbum.ParsedAlbumInfo.Language,
if (!_upgradableSpecification.CutoffNotMet(qualityProfile,
languageProfile,
new List<QualityModel> { remoteAlbum.ParsedAlbumInfo.Quality },
new List<Language> { remoteAlbum.ParsedAlbumInfo.Language },
queuedItemPreferredWordScore,
subject.ParsedAlbumInfo.Quality,
subject.PreferredWordScore))
{
return Decision.Reject("Quality for release in queue already meets cutoff: {0}", remoteAlbum.ParsedAlbumInfo.Quality);
return Decision.Reject("Release in queue already meets cutoff: {0}", remoteAlbum.ParsedAlbumInfo.Quality);
}
_logger.Debug("Checking if release is higher quality than queued release. Queued quality is: {0} - {1}", remoteAlbum.ParsedAlbumInfo.Quality, remoteAlbum.ParsedAlbumInfo.Language);
_logger.Debug("Checking if release is higher quality than queued release. Queued: {0} - {1}", remoteAlbum.ParsedAlbumInfo.Quality, remoteAlbum.ParsedAlbumInfo.Language);
if (!_upgradableSpecification.IsUpgradable(subject.Artist.QualityProfile,
subject.Artist.LanguageProfile,
remoteAlbum.ParsedAlbumInfo.Quality,
remoteAlbum.ParsedAlbumInfo.Language,
if (!_upgradableSpecification.IsUpgradable(qualityProfile,
languageProfile,
new List<QualityModel> { remoteAlbum.ParsedAlbumInfo.Quality },
new List<Language> { remoteAlbum.ParsedAlbumInfo.Language },
queuedItemPreferredWordScore,
subject.ParsedAlbumInfo.Quality,
subject.ParsedAlbumInfo.Language,
subject.PreferredWordScore))
{
return Decision.Reject("Quality for release in queue is of equal or higher preference: {0} - {1}", remoteAlbum.ParsedAlbumInfo.Quality, remoteAlbum.ParsedAlbumInfo.Language);
return Decision.Reject("Release in queue is of equal or higher preference: {0} - {1}", remoteAlbum.ParsedAlbumInfo.Quality, remoteAlbum.ParsedAlbumInfo.Language);
}
_logger.Debug("Checking if profiles allow upgrading. Queued: {0} - {1}", remoteAlbum.ParsedAlbumInfo.Quality, remoteAlbum.ParsedAlbumInfo.Language);
if (!_upgradableSpecification.IsUpgradeAllowed(qualityProfile,
languageProfile,
new List<QualityModel> { remoteAlbum.ParsedAlbumInfo.Quality },
new List<Language> { remoteAlbum.ParsedAlbumInfo.Language },
subject.ParsedAlbumInfo.Quality,
subject.ParsedAlbumInfo.Language))
{
return Decision.Reject("Another release is queued and the Quality or Language profile does not allow upgrades");
}
}

View File

@ -69,11 +69,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
if (trackFiles.Any())
{
var lowestQuality = trackFiles.Select(c => c.Quality).OrderBy(c => c.Quality.Id).First();
var currentQualities = trackFiles.Select(c => c.Quality).Distinct().ToList();
var currentLanguages = trackFiles.Select(c => c.Language).Distinct().ToList();
var upgradable = _upgradableSpecification.IsUpgradable(qualityProfile,
languageProfile,
lowestQuality,
trackFiles[0].Language,
currentQualities,
currentLanguages,
_preferredWordServiceCalculator.Calculate(subject.Artist, trackFiles[0].GetSceneOrFileName()),
subject.ParsedAlbumInfo.Quality,
subject.ParsedAlbumInfo.Language,

View File

@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.History;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
{
@ -59,8 +62,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
var cutoffUnmet = _upgradableSpecification.CutoffNotMet(
subject.Artist.QualityProfile,
subject.Artist.LanguageProfile,
mostRecent.Quality,
mostRecent.Language,
new List<QualityModel> { mostRecent.Quality },
new List<Language> { mostRecent.Language },
preferredWordScore,
subject.ParsedAlbumInfo.Quality,
subject.PreferredWordScore);
@ -68,8 +71,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
var upgradeable = _upgradableSpecification.IsUpgradable(
subject.Artist.QualityProfile,
subject.Artist.LanguageProfile,
mostRecent.Quality,
mostRecent.Language,
new List<QualityModel> { mostRecent.Quality },
new List<Language> { mostRecent.Language },
preferredWordScore,
subject.ParsedAlbumInfo.Quality,
subject.ParsedAlbumInfo.Language,

View File

@ -3,16 +3,18 @@ using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Languages;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities;
using System.Collections.Generic;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public interface IUpgradableSpecification
{
bool IsUpgradable(QualityProfile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality, Language newLanguage, int newScore);
bool IsUpgradable(QualityProfile profile, LanguageProfile languageProfile, List<QualityModel> currentQualities, List<Language> currentLanguages, int currentScore, QualityModel newQuality, Language newLanguage, int newScore);
bool QualityCutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null);
bool LanguageCutoffNotMet(LanguageProfile languageProfile, Language currentLanguage);
bool CutoffNotMet(QualityProfile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality = null, int newScore = 0);
bool CutoffNotMet(QualityProfile profile, LanguageProfile languageProfile, List<QualityModel> currentQualities, List<Language> currentLanguages, int currentScore, QualityModel newQuality = null, int newScore = 0);
bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuality);
bool IsUpgradeAllowed(QualityProfile qualityProfile, LanguageProfile languageProfile, List<QualityModel> currentQualities, List<Language> currentLanguages, QualityModel newQuality, Language newLanguage);
}
public class UpgradableSpecification : IUpgradableSpecification
@ -24,32 +26,60 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
_logger = logger;
}
private bool IsLanguageUpgradable(LanguageProfile profile, Language currentLanguage, Language newLanguage = null)
private ProfileComparisonResult IsLanguageUpgradable(LanguageProfile profile, List<Language> currentLanguages, Language newLanguage = null)
{
if (newLanguage != null)
{
var compare = new LanguageComparer(profile).Compare(newLanguage, currentLanguage);
if (compare <= 0)
var totalCompare = 0;
foreach (var language in currentLanguages)
{
return false;
var compare = new LanguageComparer(profile).Compare(newLanguage, language);
totalCompare += compare;
// Not upgradable if new language is a downgrade for any current lanaguge
if (compare < 0)
{
return ProfileComparisonResult.Downgrade;
}
}
// Not upgradable if new language is equal to all current languages
if (totalCompare == 0)
{
return ProfileComparisonResult.Equal;
}
}
return true;
return ProfileComparisonResult.Upgrade;
}
private bool IsQualityUpgradable(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null)
private ProfileComparisonResult IsQualityUpgradable(QualityProfile profile, List<QualityModel> currentQualities, QualityModel newQuality = null)
{
if (newQuality != null)
{
var compare = new QualityModelComparer(profile).Compare(newQuality, currentQuality);
var totalCompare = 0;
if (compare <= 0)
foreach (var quality in currentQualities)
{
_logger.Debug("Existing item has better quality, skipping");
return false;
var compare = new QualityModelComparer(profile).Compare(newQuality, quality);
totalCompare += compare;
if (compare < 0)
{
// Not upgradable if new quality is a downgrade for any current quality
return ProfileComparisonResult.Downgrade;
}
}
// Not upgradable if new quality is equal to all current qualities
if (totalCompare == 0) {
return ProfileComparisonResult.Equal;
}
}
return true;
return ProfileComparisonResult.Upgrade;
}
private bool IsPreferredWordUpgradable(int currentScore, int newScore)
@ -57,25 +87,30 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return newScore > currentScore;
}
public bool IsUpgradable(QualityProfile qualityProfile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality, Language newLanguage, int newScore)
public bool IsUpgradable(QualityProfile qualityProfile, LanguageProfile languageProfile, List<QualityModel> currentQualities, List<Language> currentLanguages, int currentScore, QualityModel newQuality, Language newLanguage, int newScore)
{
if (IsQualityUpgradable(qualityProfile, currentQuality, newQuality) && qualityProfile.UpgradeAllowed)
var qualityUpgrade = IsQualityUpgradable(qualityProfile, currentQualities, newQuality);
if (qualityUpgrade == ProfileComparisonResult.Upgrade)
{
return true;
}
if (new QualityModelComparer(qualityProfile).Compare(newQuality, currentQuality) < 0)
if (qualityUpgrade == ProfileComparisonResult.Downgrade)
{
_logger.Debug("Existing item has better quality, skipping");
return false;
}
if (IsLanguageUpgradable(languageProfile, currentLanguage, newLanguage) && languageProfile.UpgradeAllowed)
var languageUpgrade = IsLanguageUpgradable(languageProfile, currentLanguages, newLanguage);
if (languageUpgrade == ProfileComparisonResult.Upgrade)
{
return true;
}
if (new LanguageComparer(languageProfile).Compare(newLanguage, currentLanguage) < 0)
if (languageUpgrade == ProfileComparisonResult.Downgrade)
{
_logger.Debug("Existing item has better language, skipping");
return false;
@ -87,12 +122,6 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return false;
}
if (!IsPreferredWordUpgradable(currentScore, newScore))
{
_logger.Debug("Existing item has a better preferred word score, skipping");
return false;
}
return true;
}
@ -120,18 +149,24 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return languageCompare < 0;
}
public bool CutoffNotMet(QualityProfile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality = null, int newScore = 0)
public bool CutoffNotMet(QualityProfile profile, LanguageProfile languageProfile, List<QualityModel> currentQualities, List<Language> currentLanguages, int currentScore, QualityModel newQuality = null, int newScore = 0)
{
// 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))
foreach (var language in currentLanguages)
{
return true;
if (LanguageCutoffNotMet(languageProfile, language))
{
return true;
}
}
if (QualityCutoffNotMet(profile, currentQuality, newQuality))
foreach (var quality in currentQualities)
{
return true;
if (QualityCutoffNotMet(profile, quality, newQuality))
{
return true;
}
}
if (IsPreferredWordUpgradable(currentScore, newScore))
@ -157,5 +192,44 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return false;
}
public bool IsUpgradeAllowed(QualityProfile qualityProfile, LanguageProfile languageProfile, List<QualityModel> currentQualities, List<Language> currentLanguages, QualityModel newQuality, Language newLanguage)
{
var isQualityUpgrade = IsQualityUpgradable(qualityProfile, currentQualities, newQuality);
var isLanguageUpgrade = IsLanguageUpgradable(languageProfile, currentLanguages, newLanguage);
return CheckUpgradeAllowed(qualityProfile, languageProfile, isQualityUpgrade, isLanguageUpgrade);
}
private bool CheckUpgradeAllowed (QualityProfile qualityProfile, LanguageProfile languageProfile, ProfileComparisonResult isQualityUpgrade, ProfileComparisonResult isLanguageUpgrade)
{
if (isQualityUpgrade == ProfileComparisonResult.Upgrade && qualityProfile.UpgradeAllowed ||
isLanguageUpgrade == ProfileComparisonResult.Upgrade && languageProfile.UpgradeAllowed)
{
_logger.Debug("At least one profile allows upgrading");
return true;
}
if (isQualityUpgrade == ProfileComparisonResult.Upgrade && !qualityProfile.UpgradeAllowed)
{
_logger.Debug("Quality profile does not allow upgrades, skipping");
return false;
}
if (isLanguageUpgrade == ProfileComparisonResult.Upgrade && !languageProfile.UpgradeAllowed)
{
_logger.Debug("Language profile does not allow upgrades, skipping");
return false;
}
return true;
}
private enum ProfileComparisonResult
{
Downgrade = -1,
Equal = 0,
Upgrade = 1
}
}
}

View File

@ -0,0 +1,76 @@
using System;
using System.Linq;
using NLog;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Music;
using NzbDrone.Common.Cache;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class UpgradeAllowedSpecification : IDecisionEngineSpecification
{
private readonly UpgradableSpecification _upgradableSpecification;
private readonly IMediaFileService _mediaFileService;
private readonly ITrackService _trackService;
private readonly Logger _logger;
private readonly ICached<bool> _missingFilesCache;
public UpgradeAllowedSpecification(UpgradableSpecification upgradableSpecification,
Logger logger,
ICacheManager cacheManager,
IMediaFileService mediaFileService,
ITrackService trackService)
{
_upgradableSpecification = upgradableSpecification;
_mediaFileService = mediaFileService;
_trackService = trackService;
_missingFilesCache = cacheManager.GetCache<bool>(GetType());
_logger = logger;
}
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
{
var qualityProfile = subject.Artist.QualityProfile.Value;
var languageProfile = subject.Artist.LanguageProfile.Value;
foreach (var album in subject.Albums)
{
var tracksMissing = _missingFilesCache.Get(album.Id.ToString(), () => _trackService.TracksWithoutFiles(album.Id).Any(),
TimeSpan.FromSeconds(30));
var trackFiles = _mediaFileService.GetFilesByAlbum(album.Id);
if (!tracksMissing && trackFiles.Any())
{
// Get a distinct list of all current track qualities and languages for a given album
var currentQualities = trackFiles.Select(c => c.Quality).Distinct().ToList();
var currentLanguages = trackFiles.Select(c => c.Language).Distinct().ToList();
_logger.Debug("Comparing file quality and language with report. Existing files contain {0} : {1}", currentQualities.ConcatToString(), currentLanguages.ConcatToString());
if (!_upgradableSpecification.IsUpgradeAllowed(qualityProfile,
languageProfile,
currentQualities,
currentLanguages,
subject.ParsedAlbumInfo.Quality,
subject.ParsedAlbumInfo.Language))
{
_logger.Debug("Upgrading is not allowed by the quality or language profile");
return Decision.Reject("Existing files and the Quality or Language profile does not allow upgrades");
}
}
}
return Decision.Accept();
}
}
}

View File

@ -7,6 +7,7 @@ using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Music;
using NzbDrone.Common.Cache;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
@ -48,18 +49,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
if (!tracksMissing && trackFiles.Any())
{
var lowestQuality = trackFiles.Select(c => c.Quality).OrderBy(c => c.Quality.Id).First();
var currentQualities = trackFiles.Select(c => c.Quality).Distinct().ToList();
var currentLanguages = trackFiles.Select(c => c.Language).Distinct().ToList();
if (!_upgradableSpecification.IsUpgradable(subject.Artist.QualityProfile,
subject.Artist.LanguageProfile,
lowestQuality,
trackFiles[0].Language,
currentQualities,
currentLanguages,
_preferredWordServiceCalculator.Calculate(subject.Artist, trackFiles[0].GetSceneOrFileName()),
subject.ParsedAlbumInfo.Quality,
subject.ParsedAlbumInfo.Language,
subject.PreferredWordScore))
{
return Decision.Reject("Existing file on disk is of equal or higher preference: {0} - {1}", lowestQuality, trackFiles[0].Language);
return Decision.Reject("Existing files on disk is of equal or higher preference: {0} - {1}", currentQualities.ConcatToString(), currentLanguages.ConcatToString());
}
}

View File

@ -258,6 +258,7 @@
<Compile Include="DecisionEngine\Specifications\TorrentSeedingSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\SameTracksGrabSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\RawDiskSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\UpgradeAllowedSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\UpgradeDiskSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\UpgradableSpecification.cs" />
<Compile Include="DiskSpace\DiskSpace.cs" />