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
pull/6/head
Qstick 5 years ago committed by GitHub
parent 0ebaa90f54
commit 4d8bcd12e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -137,5 +137,15 @@ namespace NzbDrone.Common.Extensions
{ {
return source.Select(predicate).ToList(); 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));
}
} }
} }

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

@ -96,7 +96,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
private void GivenUpgradeForExistingFile() private void GivenUpgradeForExistingFile()
{ {
Mocker.GetMock<IUpgradableSpecification>() 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); .Returns(true);
} }

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

@ -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 }; _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 }; _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 singleAlbumList = new List<Album> { new Album {}};
var doubleEpisodeList = new List<Album> { new Album {}, new Album {}, new Album {} }; var doubleAlbumList = new List<Album> { new Album {}, new Album {}, new Album {} };
var languages = Languages.LanguageFixture.GetDefaultLanguages(Language.English, Language.Spanish); var languages = Languages.LanguageFixture.GetDefaultLanguages(Language.English, Language.Spanish);
@ -66,14 +66,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{ {
Artist = fakeArtist, Artist = fakeArtist,
ParsedAlbumInfo = new ParsedAlbumInfo { Quality = new QualityModel(Quality.MP3_256, new Revision(version: 2)), Language = Language.English }, ParsedAlbumInfo = new ParsedAlbumInfo { Quality = new QualityModel(Quality.MP3_256, new Revision(version: 2)), Language = Language.English },
Albums = doubleEpisodeList Albums = doubleAlbumList
}; };
_parseResultSingle = new RemoteAlbum _parseResultSingle = new RemoteAlbum
{ {
Artist = fakeArtist, Artist = fakeArtist,
ParsedAlbumInfo = new ParsedAlbumInfo { Quality = new QualityModel(Quality.MP3_256, new Revision(version: 2)), Language = Language.English }, 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] [Test]
public void should_be_upgradable_if_album_is_upgradable() public void should_be_upgradable_if_all_files_are_upgradable()
{ {
WithFirstFileUpgradable(); WithFirstFileUpgradable();
WithSecondFileUpgradable();
Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue(); 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() public void should_not_be_upgradable_if_qualities_are_the_same()
{ {
_firstFile.Quality = new QualityModel(Quality.MP3_320); _firstFile.Quality = new QualityModel(Quality.MP3_320);
_secondFile.Quality = new QualityModel(Quality.MP3_320);
_parseResultSingle.ParsedAlbumInfo.Quality = new QualityModel(Quality.MP3_320); _parseResultSingle.ParsedAlbumInfo.Quality = new QualityModel(Quality.MP3_320);
Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse();
} }
@ -146,5 +148,21 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{ {
Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); 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();
}
} }
} }

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

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

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

@ -7,6 +7,7 @@ using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Music; using NzbDrone.Core.Music;
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
using NzbDrone.Core.Profiles.Releases; using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.DecisionEngine.Specifications namespace NzbDrone.Core.DecisionEngine.Specifications
{ {
@ -39,8 +40,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
{ {
var qualityProfile = subject.Artist.QualityProfile.Value;
var profile = subject.Artist.QualityProfile.Value; var languageProfile = subject.Artist.LanguageProfile.Value;
foreach (var album in subject.Albums) foreach (var album in subject.Albums)
{ {
@ -50,23 +51,26 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
if (!tracksMissing && trackFiles.Any()) 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, if (!_upgradableSpecification.CutoffNotMet(qualityProfile,
subject.Artist.LanguageProfile, languageProfile,
lowestQuality, currentQualities,
trackFiles[0].Language, currentLanguages,
_preferredWordServiceCalculator.Calculate(subject.Artist, trackFiles[0].GetSceneOrFileName()), _preferredWordServiceCalculator.Calculate(subject.Artist, trackFiles[0].GetSceneOrFileName()),
subject.ParsedAlbumInfo.Quality, subject.ParsedAlbumInfo.Quality,
subject.PreferredWordScore)) subject.PreferredWordScore))
{ {
_logger.Debug("Cutoff already met, rejecting."); _logger.Debug("Cutoff already met by existing files, rejecting.");
var qualityCutoffIndex = profile.GetIndex(profile.Cutoff);
var qualityCutoff = profile.Items[qualityCutoffIndex.Index]; var qualityCutoffIndex = qualityProfile.GetIndex(qualityProfile.Cutoff);
var qualityCutoff = qualityProfile.Items[qualityCutoffIndex.Index];
return Decision.Reject("Existing file meets cutoff: {0} - {1}", qualityCutoff, subject.Artist.LanguageProfile.Value.Cutoff); return Decision.Reject("Existing files meets cutoff: {0} - {1}", qualityCutoff, languageProfile.Cutoff);
} }
} }

@ -1,8 +1,11 @@
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Releases; using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Queue; using NzbDrone.Core.Queue;
namespace NzbDrone.Core.DecisionEngine.Specifications namespace NzbDrone.Core.DecisionEngine.Specifications
@ -41,34 +44,48 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
foreach (var queueItem in matchingAlbum) foreach (var queueItem in matchingAlbum)
{ {
var remoteAlbum = queueItem.RemoteAlbum; 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); _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); var queuedItemPreferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Artist, queueItem.Title);
if (!_upgradableSpecification.CutoffNotMet(subject.Artist.QualityProfile, if (!_upgradableSpecification.CutoffNotMet(qualityProfile,
subject.Artist.LanguageProfile, languageProfile,
remoteAlbum.ParsedAlbumInfo.Quality, new List<QualityModel> { remoteAlbum.ParsedAlbumInfo.Quality },
remoteAlbum.ParsedAlbumInfo.Language, new List<Language> { remoteAlbum.ParsedAlbumInfo.Language },
queuedItemPreferredWordScore, queuedItemPreferredWordScore,
subject.ParsedAlbumInfo.Quality, subject.ParsedAlbumInfo.Quality,
subject.PreferredWordScore)) 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, if (!_upgradableSpecification.IsUpgradable(qualityProfile,
subject.Artist.LanguageProfile, languageProfile,
remoteAlbum.ParsedAlbumInfo.Quality, new List<QualityModel> { remoteAlbum.ParsedAlbumInfo.Quality },
remoteAlbum.ParsedAlbumInfo.Language, new List<Language> { remoteAlbum.ParsedAlbumInfo.Language },
queuedItemPreferredWordScore, queuedItemPreferredWordScore,
subject.ParsedAlbumInfo.Quality, subject.ParsedAlbumInfo.Quality,
subject.ParsedAlbumInfo.Language, subject.ParsedAlbumInfo.Language,
subject.PreferredWordScore)) 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");
} }
} }

@ -69,11 +69,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
if (trackFiles.Any()) 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, var upgradable = _upgradableSpecification.IsUpgradable(qualityProfile,
languageProfile, languageProfile,
lowestQuality, currentQualities,
trackFiles[0].Language, currentLanguages,
_preferredWordServiceCalculator.Calculate(subject.Artist, trackFiles[0].GetSceneOrFileName()), _preferredWordServiceCalculator.Calculate(subject.Artist, trackFiles[0].GetSceneOrFileName()),
subject.ParsedAlbumInfo.Quality, subject.ParsedAlbumInfo.Quality,
subject.ParsedAlbumInfo.Language, subject.ParsedAlbumInfo.Language,

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

@ -3,16 +3,18 @@ using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Languages;
using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using System.Collections.Generic;
namespace NzbDrone.Core.DecisionEngine.Specifications namespace NzbDrone.Core.DecisionEngine.Specifications
{ {
public interface IUpgradableSpecification 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 QualityCutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null);
bool LanguageCutoffNotMet(LanguageProfile languageProfile, Language currentLanguage); 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 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 public class UpgradableSpecification : IUpgradableSpecification
@ -24,32 +26,60 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
_logger = logger; _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) if (newLanguage != null)
{ {
var compare = new LanguageComparer(profile).Compare(newLanguage, currentLanguage); var totalCompare = 0;
if (compare <= 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;
} }
} }
return true;
// Not upgradable if new language is equal to all current languages
if (totalCompare == 0)
{
return ProfileComparisonResult.Equal;
}
}
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) 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"); var compare = new QualityModelComparer(profile).Compare(newQuality, quality);
return false;
totalCompare += compare;
if (compare < 0)
{
// Not upgradable if new quality is a downgrade for any current quality
return ProfileComparisonResult.Downgrade;
} }
} }
return true;
// Not upgradable if new quality is equal to all current qualities
if (totalCompare == 0) {
return ProfileComparisonResult.Equal;
}
}
return ProfileComparisonResult.Upgrade;
} }
private bool IsPreferredWordUpgradable(int currentScore, int newScore) private bool IsPreferredWordUpgradable(int currentScore, int newScore)
@ -57,25 +87,30 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return newScore > currentScore; 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; return true;
} }
if (new QualityModelComparer(qualityProfile).Compare(newQuality, currentQuality) < 0) if (qualityUpgrade == ProfileComparisonResult.Downgrade)
{ {
_logger.Debug("Existing item has better quality, skipping"); _logger.Debug("Existing item has better quality, skipping");
return false; return false;
} }
if (IsLanguageUpgradable(languageProfile, currentLanguage, newLanguage) && languageProfile.UpgradeAllowed) var languageUpgrade = IsLanguageUpgradable(languageProfile, currentLanguages, newLanguage);
if (languageUpgrade == ProfileComparisonResult.Upgrade)
{ {
return true; return true;
} }
if (new LanguageComparer(languageProfile).Compare(newLanguage, currentLanguage) < 0) if (languageUpgrade == ProfileComparisonResult.Downgrade)
{ {
_logger.Debug("Existing item has better language, skipping"); _logger.Debug("Existing item has better language, skipping");
return false; return false;
@ -87,12 +122,6 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return false; return false;
} }
if (!IsPreferredWordUpgradable(currentScore, newScore))
{
_logger.Debug("Existing item has a better preferred word score, skipping");
return false;
}
return true; return true;
} }
@ -120,19 +149,25 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return languageCompare < 0; 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 // 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. // matter as we can always get same quality with prefered language.
if (LanguageCutoffNotMet(languageProfile, currentLanguage)) foreach (var language in currentLanguages)
{
if (LanguageCutoffNotMet(languageProfile, language))
{ {
return true; return true;
} }
}
if (QualityCutoffNotMet(profile, currentQuality, newQuality)) foreach (var quality in currentQualities)
{
if (QualityCutoffNotMet(profile, quality, newQuality))
{ {
return true; return true;
} }
}
if (IsPreferredWordUpgradable(currentScore, newScore)) if (IsPreferredWordUpgradable(currentScore, newScore))
{ {
@ -157,5 +192,44 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return false; 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
}
} }
} }

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

@ -7,6 +7,7 @@ using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Music; using NzbDrone.Core.Music;
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
using NzbDrone.Core.Profiles.Releases; using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.DecisionEngine.Specifications namespace NzbDrone.Core.DecisionEngine.Specifications
{ {
@ -48,18 +49,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
if (!tracksMissing && trackFiles.Any()) 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, if (!_upgradableSpecification.IsUpgradable(subject.Artist.QualityProfile,
subject.Artist.LanguageProfile, subject.Artist.LanguageProfile,
lowestQuality, currentQualities,
trackFiles[0].Language, currentLanguages,
_preferredWordServiceCalculator.Calculate(subject.Artist, trackFiles[0].GetSceneOrFileName()), _preferredWordServiceCalculator.Calculate(subject.Artist, trackFiles[0].GetSceneOrFileName()),
subject.ParsedAlbumInfo.Quality, subject.ParsedAlbumInfo.Quality,
subject.ParsedAlbumInfo.Language, subject.ParsedAlbumInfo.Language,
subject.PreferredWordScore)) 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());
} }
} }

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

Loading…
Cancel
Save