Fixed: Correctly handle Repack Releases

pull/864/head
Qstick 5 years ago
parent 23316329ed
commit 2f1290d488

@ -25,6 +25,12 @@ const allowFingerprintingOptions = [
{ key: 'never', value: 'Never' } { key: 'never', value: 'Never' }
]; ];
const downloadPropersAndRepacksOptions = [
{ key: 'preferAndUpgrade', value: 'Prefer and Upgrade' },
{ key: 'doNotUpgrade', value: 'Do not Upgrade Automatically' },
{ key: 'doNotPrefer', value: 'Do not Prefer' }
];
const fileDateOptions = [ const fileDateOptions = [
{ key: 'none', value: 'None' }, { key: 'none', value: 'None' },
{ key: 'albumReleaseDate', value: 'Album Release Date' } { key: 'albumReleaseDate', value: 'Album Release Date' }
@ -209,14 +215,23 @@ class MediaManagement extends Component {
isAdvanced={true} isAdvanced={true}
size={sizes.MEDIUM} size={sizes.MEDIUM}
> >
<FormLabel>Download Propers</FormLabel> <FormLabel>Propers and Repacks</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.SELECT}
name="autoDownloadPropers" name="downloadPropersAndRepacks"
helpText="Should Lidarr automatically upgrade to propers when available?" helpTexts={[
'Whether or not to automatically upgrade to Propers/Repacks',
'Use \'Do not Prefer\' to sort by preferred word score over propers/repacks'
]}
helpTextWarning={
settings.downloadPropersAndRepacks.value === 'doNotPrefer' ?
'Use preferred words for automatic upgrades to propers/repacks' :
undefined
}
values={downloadPropersAndRepacksOptions}
onChange={onInputChange} onChange={onInputChange}
{...settings.autoDownloadPropers} {...settings.downloadPropersAndRepacks}
/> />
</FormGroup> </FormGroup>

@ -1,6 +1,7 @@
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using Lidarr.Http.REST; using Lidarr.Http.REST;
using NzbDrone.Core.Qualities;
namespace Lidarr.Api.V1.Config namespace Lidarr.Api.V1.Config
{ {
@ -8,7 +9,7 @@ namespace Lidarr.Api.V1.Config
{ {
public bool AutoUnmonitorPreviouslyDownloadedTracks { get; set; } public bool AutoUnmonitorPreviouslyDownloadedTracks { get; set; }
public string RecycleBin { get; set; } public string RecycleBin { get; set; }
public bool AutoDownloadPropers { get; set; } public ProperDownloadTypes DownloadPropersAndRepacks { get; set; }
public bool CreateEmptyArtistFolders { get; set; } public bool CreateEmptyArtistFolders { get; set; }
public bool DeleteEmptyFolders { get; set; } public bool DeleteEmptyFolders { get; set; }
public FileDateType FileDate { get; set; } public FileDateType FileDate { get; set; }
@ -35,7 +36,7 @@ namespace Lidarr.Api.V1.Config
{ {
AutoUnmonitorPreviouslyDownloadedTracks = model.AutoUnmonitorPreviouslyDownloadedTracks, AutoUnmonitorPreviouslyDownloadedTracks = model.AutoUnmonitorPreviouslyDownloadedTracks,
RecycleBin = model.RecycleBin, RecycleBin = model.RecycleBin,
AutoDownloadPropers = model.AutoDownloadPropers, DownloadPropersAndRepacks = model.DownloadPropersAndRepacks,
CreateEmptyArtistFolders = model.CreateEmptyArtistFolders, CreateEmptyArtistFolders = model.CreateEmptyArtistFolders,
DeleteEmptyFolders = model.DeleteEmptyFolders, DeleteEmptyFolders = model.DeleteEmptyFolders,
FileDate = model.FileDate, FileDate = model.FileDate,

@ -17,6 +17,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 NzbDrone.Core.Configuration;
namespace NzbDrone.Core.Test.DecisionEngineTests namespace NzbDrone.Core.Test.DecisionEngineTests
{ {
@ -411,7 +412,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
} }
[Test] [Test]
public void should_put_higher_quality_before_lower_allways() public void should_put_higher_quality_before_lower_always()
{ {
var remoteAlbum1 = GivenRemoteAlbum(new List<Album> { GivenAlbum(1) }, new QualityModel(Quality.MP3_256), Language.French); var remoteAlbum1 = GivenRemoteAlbum(new List<Album> { GivenAlbum(1) }, new QualityModel(Quality.MP3_256), Language.French);
var remoteAlbum2 = GivenRemoteAlbum(new List<Album> { GivenAlbum(1) }, new QualityModel(Quality.MP3_320), Language.German); var remoteAlbum2 = GivenRemoteAlbum(new List<Album> { GivenAlbum(1) }, new QualityModel(Quality.MP3_320), Language.German);
@ -423,5 +424,88 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
var qualifiedReports = Subject.PrioritizeDecisions(decisions); var qualifiedReports = Subject.PrioritizeDecisions(decisions);
qualifiedReports.First().RemoteAlbum.ParsedAlbumInfo.Quality.Quality.Should().Be(Quality.MP3_320); qualifiedReports.First().RemoteAlbum.ParsedAlbumInfo.Quality.Quality.Should().Be(Quality.MP3_320);
} }
[Test]
public void should_prefer_higher_score_over_lower_score()
{
var remoteAlbum1 = GivenRemoteAlbum(new List<Album> { GivenAlbum(1) }, new QualityModel(Quality.FLAC), Language.English);
var remoteAlbum2 = GivenRemoteAlbum(new List<Album> { GivenAlbum(1) }, new QualityModel(Quality.FLAC), Language.English);
remoteAlbum1.PreferredWordScore = 10;
remoteAlbum2.PreferredWordScore = 0;
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteAlbum1));
decisions.Add(new DownloadDecision(remoteAlbum2));
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
qualifiedReports.First().RemoteAlbum.PreferredWordScore.Should().Be(10);
}
[Test]
public void should_prefer_proper_over_score_when_download_propers_is_prefer_and_upgrade()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.PreferAndUpgrade);
var remoteAlbum1 = GivenRemoteAlbum(new List<Album> { GivenAlbum(1) }, new QualityModel(Quality.FLAC, new Revision(1)), Language.English);
var remoteAlbum2 = GivenRemoteAlbum(new List<Album> { GivenAlbum(1) }, new QualityModel(Quality.FLAC, new Revision(2)), Language.English);
remoteAlbum1.PreferredWordScore = 10;
remoteAlbum2.PreferredWordScore = 0;
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteAlbum1));
decisions.Add(new DownloadDecision(remoteAlbum2));
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
qualifiedReports.First().RemoteAlbum.ParsedAlbumInfo.Quality.Revision.Version.Should().Be(2);
}
[Test]
public void should_prefer_proper_over_score_when_download_propers_is_do_not_upgrade()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.DoNotUpgrade);
var remoteAlbum1 = GivenRemoteAlbum(new List<Album> { GivenAlbum(1) }, new QualityModel(Quality.FLAC, new Revision(1)), Language.English);
var remoteAlbum2 = GivenRemoteAlbum(new List<Album> { GivenAlbum(1) }, new QualityModel(Quality.FLAC, new Revision(2)), Language.English);
remoteAlbum1.PreferredWordScore = 10;
remoteAlbum2.PreferredWordScore = 0;
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteAlbum1));
decisions.Add(new DownloadDecision(remoteAlbum2));
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
qualifiedReports.First().RemoteAlbum.ParsedAlbumInfo.Quality.Revision.Version.Should().Be(2);
}
[Test]
public void should_prefer_score_over_proper_when_download_propers_is_do_not_prefer()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.DoNotPrefer);
var remoteAlbum1 = GivenRemoteAlbum(new List<Album> { GivenAlbum(1) }, new QualityModel(Quality.FLAC, new Revision(1)), Language.English);
var remoteAlbum2 = GivenRemoteAlbum(new List<Album> { GivenAlbum(1) }, new QualityModel(Quality.FLAC, new Revision(2)), Language.English);
remoteAlbum1.PreferredWordScore = 10;
remoteAlbum2.PreferredWordScore = 0;
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteAlbum1));
decisions.Add(new DownloadDecision(remoteAlbum2));
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
qualifiedReports.First().RemoteAlbum.ParsedAlbumInfo.Quality.Quality.Should().Be(Quality.FLAC);
qualifiedReports.First().RemoteAlbum.ParsedAlbumInfo.Quality.Revision.Version.Should().Be(1);
qualifiedReports.First().RemoteAlbum.PreferredWordScore.Should().Be(10);
}
} }
} }

@ -0,0 +1,203 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Music;
using Moq;
namespace NzbDrone.Core.Test.DecisionEngineTests
{
[TestFixture]
public class RepackSpecificationFixture : CoreTest<RepackSpecification>
{
private ParsedAlbumInfo _parsedAlbumInfo;
private List<Album> _albums;
private List<TrackFile> _trackFiles;
[SetUp]
public void Setup()
{
Mocker.Resolve<UpgradableSpecification>();
_parsedAlbumInfo = Builder<ParsedAlbumInfo>.CreateNew()
.With(p => p.Quality = new QualityModel(Quality.FLAC,
new Revision(2, 0, false)))
.With(p => p.ReleaseGroup = "Lidarr")
.Build();
_albums = Builder<Album>.CreateListOfSize(1)
.All()
.BuildList();
_trackFiles = Builder<TrackFile>.CreateListOfSize(3)
.All()
.With(t => t.AlbumId = _albums.First().Id)
.BuildList();
Mocker.GetMock<IMediaFileService>()
.Setup(c => c.GetFilesByAlbum(It.IsAny<int>()))
.Returns(_trackFiles);
}
[Test]
public void should_return_true_if_it_is_not_a_repack()
{
var remoteAlbum = Builder<RemoteAlbum>.CreateNew()
.With(e => e.ParsedAlbumInfo = _parsedAlbumInfo)
.With(e => e.Albums = _albums)
.Build();
Subject.IsSatisfiedBy(remoteAlbum, null)
.Accepted
.Should()
.BeTrue();
}
[Test]
public void should_return_true_if_there_are_is_no_track_files()
{
Mocker.GetMock<IMediaFileService>()
.Setup(c => c.GetFilesByAlbum(It.IsAny<int>()))
.Returns(new List<TrackFile>());
_parsedAlbumInfo.Quality.Revision.IsRepack = true;
var remoteAlbum = Builder<RemoteAlbum>.CreateNew()
.With(e => e.ParsedAlbumInfo = _parsedAlbumInfo)
.With(e => e.Albums = _albums)
.Build();
Subject.IsSatisfiedBy(remoteAlbum, null)
.Accepted
.Should()
.BeTrue();
}
[Test]
public void should_return_true_if_is_a_repack_for_a_different_quality()
{
_parsedAlbumInfo.Quality.Revision.IsRepack = true;
_trackFiles.Select(c => { c.ReleaseGroup = "Lidarr"; return c; }).ToList();
_trackFiles.Select(c => { c.Quality = new QualityModel(Quality.MP3_256); return c; }).ToList();
var remoteAlbum = Builder<RemoteAlbum>.CreateNew()
.With(e => e.ParsedAlbumInfo = _parsedAlbumInfo)
.With(e => e.Albums = _albums)
.Build();
Subject.IsSatisfiedBy(remoteAlbum, null)
.Accepted
.Should()
.BeTrue();
}
[Test]
public void should_return_true_if_is_a_repack_for_all_existing_files()
{
_parsedAlbumInfo.Quality.Revision.IsRepack = true;
_trackFiles.Select(c => { c.ReleaseGroup = "Lidarr"; return c; }).ToList();
_trackFiles.Select(c => { c.Quality = new QualityModel(Quality.FLAC); return c; }).ToList();
var remoteAlbum = Builder<RemoteAlbum>.CreateNew()
.With(e => e.ParsedAlbumInfo = _parsedAlbumInfo)
.With(e => e.Albums = _albums)
.Build();
Subject.IsSatisfiedBy(remoteAlbum, null)
.Accepted
.Should()
.BeTrue();
}
[Test]
public void should_return_false_if_is_a_repack_for_some_but_not_all_trackfiles()
{
_parsedAlbumInfo.Quality.Revision.IsRepack = true;
_trackFiles.Select(c => { c.ReleaseGroup = "Lidarr"; return c; }).ToList();
_trackFiles.Select(c => { c.Quality = new QualityModel(Quality.FLAC); return c; }).ToList();
_trackFiles.First().ReleaseGroup = "NotLidarr";
var remoteAlbum = Builder<RemoteAlbum>.CreateNew()
.With(e => e.ParsedAlbumInfo = _parsedAlbumInfo)
.With(e => e.Albums = _albums)
.Build();
Subject.IsSatisfiedBy(remoteAlbum, null)
.Accepted
.Should()
.BeFalse();
}
[Test]
public void should_return_false_if_is_a_repack_for_different_group()
{
_parsedAlbumInfo.Quality.Revision.IsRepack = true;
_trackFiles.Select(c => { c.ReleaseGroup = "NotLidarr"; return c; }).ToList();
_trackFiles.Select(c => { c.Quality = new QualityModel(Quality.FLAC); return c; }).ToList();
var remoteAlbum = Builder<RemoteAlbum>.CreateNew()
.With(e => e.ParsedAlbumInfo = _parsedAlbumInfo)
.With(e => e.Albums = _albums)
.Build();
Subject.IsSatisfiedBy(remoteAlbum, null)
.Accepted
.Should()
.BeFalse();
}
[Test]
public void should_return_false_if_release_group_for_existing_file_is_unknown()
{
_parsedAlbumInfo.Quality.Revision.IsRepack = true;
_trackFiles.Select(c => { c.ReleaseGroup = ""; return c; }).ToList();
_trackFiles.Select(c => { c.Quality = new QualityModel(Quality.FLAC); return c; }).ToList();
var remoteAlbum = Builder<RemoteAlbum>.CreateNew()
.With(e => e.ParsedAlbumInfo = _parsedAlbumInfo)
.With(e => e.Albums = _albums)
.Build();
Subject.IsSatisfiedBy(remoteAlbum, null)
.Accepted
.Should()
.BeFalse();
}
[Test]
public void should_return_false_if_release_group_for_release_is_unknown()
{
_parsedAlbumInfo.Quality.Revision.IsRepack = true;
_parsedAlbumInfo.ReleaseGroup = null;
_trackFiles.Select(c => { c.ReleaseGroup = "Lidarr"; return c; }).ToList();
_trackFiles.Select(c => { c.Quality = new QualityModel(Quality.FLAC); return c; }).ToList();
var remoteAlbum = Builder<RemoteAlbum>.CreateNew()
.With(e => e.ParsedAlbumInfo = _parsedAlbumInfo)
.With(e => e.Albums = _albums)
.Build();
Subject.IsSatisfiedBy(remoteAlbum, null)
.Accepted
.Should()
.BeFalse();
}
}
}

@ -35,8 +35,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
_firstFile = new TrackFile { Quality = new QualityModel(Quality.FLAC, new Revision(version: 1)), DateAdded = DateTime.Now }; _firstFile = new TrackFile { Quality = new QualityModel(Quality.FLAC, new Revision(version: 1)), DateAdded = DateTime.Now };
_secondFile = new TrackFile { Quality = new QualityModel(Quality.FLAC, new Revision(version: 1)), DateAdded = DateTime.Now }; _secondFile = new TrackFile { Quality = new QualityModel(Quality.FLAC, new Revision(version: 1)), DateAdded = DateTime.Now };
var singleEpisodeList = new List<Album> { new Album {}, new Album {} }; var singleAlbumList = new List<Album> { new 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 fakeArtist = Builder<Artist>.CreateNew() var fakeArtist = Builder<Artist>.CreateNew()
@ -51,14 +51,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
{ {
Artist = fakeArtist, Artist = fakeArtist,
ParsedAlbumInfo = new ParsedAlbumInfo { Quality = new QualityModel(Quality.MP3_256, new Revision(version: 2)) }, ParsedAlbumInfo = new ParsedAlbumInfo { Quality = new QualityModel(Quality.MP3_256, new Revision(version: 2)) },
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)) }, ParsedAlbumInfo = new ParsedAlbumInfo { Quality = new QualityModel(Quality.MP3_256, new Revision(version: 2)) },
Albums = singleEpisodeList Albums = singleAlbumList
}; };
} }
@ -67,13 +67,6 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
_firstFile.Quality = new QualityModel(Quality.MP3_192); _firstFile.Quality = new QualityModel(Quality.MP3_192);
} }
private void GivenAutoDownloadPropers()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.AutoDownloadPropers)
.Returns(true);
}
[Test] [Test]
public void should_return_false_when_trackFile_was_added_more_than_7_days_ago() public void should_return_false_when_trackFile_was_added_more_than_7_days_ago()
{ {
@ -124,6 +117,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
[Test] [Test]
public void should_return_false_when_proper_but_auto_download_propers_is_false() public void should_return_false_when_proper_but_auto_download_propers_is_false()
{ {
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.DoNotUpgrade);
_firstFile.Quality.Quality = Quality.MP3_256; _firstFile.Quality.Quality = Quality.MP3_256;
_firstFile.DateAdded = DateTime.Today; _firstFile.DateAdded = DateTime.Today;
@ -133,7 +130,22 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
[Test] [Test]
public void should_return_true_when_trackFile_was_added_today() public void should_return_true_when_trackFile_was_added_today()
{ {
GivenAutoDownloadPropers(); Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.PreferAndUpgrade);
_firstFile.Quality.Quality = Quality.MP3_256;
_firstFile.DateAdded = DateTime.Today;
Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_true_when_propers_are_not_preferred()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.DoNotPrefer);
_firstFile.Quality.Quality = Quality.MP3_256; _firstFile.Quality.Quality = Quality.MP3_256;

@ -38,17 +38,17 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private static readonly int NoPreferredWordScore = 0; private static readonly int NoPreferredWordScore = 0;
private void GivenAutoDownloadPropers(bool autoDownloadPropers) private void GivenAutoDownloadPropers(ProperDownloadTypes type)
{ {
Mocker.GetMock<IConfigService>() Mocker.GetMock<IConfigService>()
.SetupGet(s => s.AutoDownloadPropers) .SetupGet(s => s.DownloadPropersAndRepacks)
.Returns(autoDownloadPropers); .Returns(type);
} }
[Test, TestCaseSource(nameof(IsUpgradeTestCases))] [Test, TestCaseSource(nameof(IsUpgradeTestCases))]
public void IsUpgradeTest(Quality current, int currentVersion, Quality newQuality, int newVersion, Quality cutoff, bool expected) public void IsUpgradeTest(Quality current, int currentVersion, Quality newQuality, int newVersion, Quality cutoff, bool expected)
{ {
GivenAutoDownloadPropers(true); GivenAutoDownloadPropers(ProperDownloadTypes.PreferAndUpgrade);
var profile = new QualityProfile var profile = new QualityProfile
{ {
@ -78,7 +78,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[Test, TestCaseSource(nameof(IsUpgradeTestCasesLanguages))] [Test, TestCaseSource(nameof(IsUpgradeTestCasesLanguages))]
public void IsUpgradeTestLanguage(Quality current, int currentVersion, Language currentLanguage, Quality newQuality, int newVersion, Language newLanguage, Quality cutoff, Language languageCutoff, bool expected) public void IsUpgradeTestLanguage(Quality current, int currentVersion, Language currentLanguage, Quality newQuality, int newVersion, Language newLanguage, Quality cutoff, Language languageCutoff, bool expected)
{ {
GivenAutoDownloadPropers(true); GivenAutoDownloadPropers(ProperDownloadTypes.PreferAndUpgrade);
var profile = new QualityProfile var profile = new QualityProfile
{ {
@ -107,9 +107,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
} }
[Test] [Test]
public void should_return_false_if_proper_and_autoDownloadPropers_is_false() public void should_return_true_if_proper_and_download_propers_is_do_not_download()
{ {
GivenAutoDownloadPropers(false); GivenAutoDownloadPropers(ProperDownloadTypes.DoNotUpgrade);
var profile = new QualityProfile var profile = new QualityProfile
{ {
@ -126,13 +126,41 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.IsUpgradable( Subject.IsUpgradable(
profile, profile,
langProfile, langProfile,
new List<QualityModel> { new QualityModel(Quality.MP3_256, new Revision(version: 2)) }, new List<QualityModel> { new QualityModel(Quality.MP3_256, new Revision(version: 1)) },
new List<Language> { 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: 2)),
Language.English, Language.English,
NoPreferredWordScore) NoPreferredWordScore)
.Should().BeFalse(); .Should().BeTrue();
}
[Test]
public void should_return_false_if_proper_and_autoDownloadPropers_is_do_not_prefer()
{
GivenAutoDownloadPropers(ProperDownloadTypes.DoNotPrefer);
var profile = new QualityProfile
{
Items = Qualities.QualityFixture.GetDefaultQualities(),
};
var langProfile = new LanguageProfile
{
Languages = LanguageFixture.GetDefaultLanguages(),
Cutoff = Language.English
};
Subject.IsUpgradable(
profile,
langProfile,
new List<QualityModel> { new QualityModel(Quality.MP3_256, new Revision(version: 1)) },
new List<Language> { Language.English },
NoPreferredWordScore,
new QualityModel(Quality.MP3_256, new Revision(version: 2)),
Language.English,
NoPreferredWordScore)
.Should().BeFalse();
} }
} }
} }

@ -12,6 +12,7 @@ using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Music; using NzbDrone.Core.Music;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Languages;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Specifications namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Specifications
{ {
@ -19,6 +20,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Specifications
public class UpgradeSpecificationFixture : CoreTest<UpgradeSpecification> public class UpgradeSpecificationFixture : CoreTest<UpgradeSpecification>
{ {
private Artist _artist; private Artist _artist;
private Album _album;
private LocalTrack _localTrack; private LocalTrack _localTrack;
[SetUp] [SetUp]
@ -35,12 +37,15 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Specifications
Cutoff = Language.Spanish, Cutoff = Language.Spanish,
}).Build(); }).Build();
_album = Builder<Album>.CreateNew().Build();
_localTrack = new LocalTrack _localTrack = new LocalTrack
{ {
Path = @"C:\Test\Imagine Dragons\Imagine.Dragons.Song.1.mp3", Path = @"C:\Test\Imagine Dragons\Imagine.Dragons.Song.1.mp3",
Quality = new QualityModel(Quality.MP3_256, new Revision(version: 1)), Quality = new QualityModel(Quality.MP3_256, new Revision(version: 1)),
Language = Language.Spanish, Language = Language.Spanish,
Artist = _artist Artist = _artist,
Album = _album
}; };
} }
@ -215,5 +220,71 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Specifications
Subject.IsSatisfiedBy(_localTrack).Accepted.Should().BeFalse(); Subject.IsSatisfiedBy(_localTrack).Accepted.Should().BeFalse();
} }
[Test]
public void should_return_false_if_not_a_revision_upgrade_and_prefers_propers()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.PreferAndUpgrade);
_localTrack.Tracks = Builder<Track>.CreateListOfSize(1)
.All()
.With(e => e.TrackFileId = 1)
.With(e => e.TrackFile = new LazyLoaded<TrackFile>(
new TrackFile
{
Quality = new QualityModel(Quality.MP3_256, new Revision(version: 2))
}))
.Build()
.ToList();
Subject.IsSatisfiedBy(_localTrack).Accepted.Should().BeFalse();
}
[Test]
public void should_return_true_if_not_a_revision_upgrade_and_does_not_prefer_propers()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.DoNotPrefer);
_localTrack.Tracks = Builder<Track>.CreateListOfSize(1)
.All()
.With(e => e.TrackFileId = 1)
.With(e => e.TrackFile = new LazyLoaded<TrackFile>(
new TrackFile
{
Quality = new QualityModel(Quality.MP3_256, new Revision(version: 2))
}))
.Build()
.ToList();
Subject.IsSatisfiedBy(_localTrack).Accepted.Should().BeTrue();
}
[Test]
public void should_return_true_when_comparing_to_a_lower_quality_proper()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.DoNotPrefer);
_localTrack.Quality = new QualityModel(Quality.FLAC);
_localTrack.Tracks = Builder<Track>.CreateListOfSize(1)
.All()
.With(e => e.TrackFileId = 1)
.With(e => e.TrackFile = new LazyLoaded<TrackFile>(
new TrackFile
{
Quality = new QualityModel(Quality.FLAC, new Revision(version: 2))
}))
.Build()
.ToList();
Subject.IsSatisfiedBy(_localTrack).Accepted.Should().BeTrue();
}
} }
} }

@ -103,6 +103,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\RepackSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\UpgradeAllowedSpecificationFixture.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" />
@ -644,4 +645,4 @@
</ItemGroup> </ItemGroup>
<Copy SourceFiles="@(IdentificationTestCases)" DestinationFolder="$(OutputPath)\Files\Identification\" SkipUnchangedFiles="true" /> <Copy SourceFiles="@(IdentificationTestCases)" DestinationFolder="$(OutputPath)\Files\Identification\" SkipUnchangedFiles="true" />
</Target> </Target>
</Project> </Project>

@ -298,6 +298,16 @@ namespace NzbDrone.Core.Test.ParserTests
QualityParser.ParseCodec(null, null).Should().Be(Codec.Unknown); QualityParser.ParseCodec(null, null).Should().Be(Codec.Unknown);
} }
[TestCase("Artist Title - Album Title 2017 REPACK FLAC aAF", true)]
[TestCase("Artist Title - Album Title 2017 RERIP FLAC aAF", true)]
[TestCase("Artist Title - Album Title 2017 PROPER FLAC aAF", false)]
public void should_be_able_to_parse_repack(string title, bool isRepack)
{
var result = QualityParser.ParseQuality(title, null, 0);
result.Revision.Version.Should().Be(2);
result.Revision.IsRepack.Should().Be(isRepack);
}
private void ParseAndVerifyQuality(string name, string desc, int bitrate, Quality quality, int sampleSize = 0) private void ParseAndVerifyQuality(string name, string desc, int bitrate, Quality quality, int sampleSize = 0)
{ {
var result = QualityParser.ParseQuality(name, desc, bitrate, sampleSize); var result = QualityParser.ParseQuality(name, desc, bitrate, sampleSize);

@ -8,6 +8,7 @@ using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Common.Http.Proxy; using NzbDrone.Common.Http.Proxy;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Configuration namespace NzbDrone.Core.Configuration
{ {
@ -112,11 +113,11 @@ namespace NzbDrone.Core.Configuration
set { SetValue("MinimumAge", value); } set { SetValue("MinimumAge", value); }
} }
public bool AutoDownloadPropers public ProperDownloadTypes DownloadPropersAndRepacks
{ {
get { return GetValueBoolean("AutoDownloadPropers", true); } get { return GetValueEnum("DownloadPropersAndRepacks", ProperDownloadTypes.PreferAndUpgrade); }
set { SetValue("AutoDownloadPropers", value); } set { SetValue("DownloadPropersAndRepacks", value); }
} }
public bool EnableCompletedDownloadHandling public bool EnableCompletedDownloadHandling

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Common.Http.Proxy; using NzbDrone.Common.Http.Proxy;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Configuration namespace NzbDrone.Core.Configuration
{ {
@ -24,7 +25,7 @@ namespace NzbDrone.Core.Configuration
//Media Management //Media Management
bool AutoUnmonitorPreviouslyDownloadedTracks { get; set; } bool AutoUnmonitorPreviouslyDownloadedTracks { get; set; }
string RecycleBin { get; set; } string RecycleBin { get; set; }
bool AutoDownloadPropers { get; set; } ProperDownloadTypes DownloadPropersAndRepacks { get; set; }
bool CreateEmptyArtistFolders { get; set; } bool CreateEmptyArtistFolders { get; set; }
bool DeleteEmptyFolders { get; set; } bool DeleteEmptyFolders { get; set; }
FileDateType FileDate { get; set; } FileDateType FileDate { get; set; }

@ -0,0 +1,43 @@
using System.Data;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(033)]
public class download_propers_config : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.WithConnection(SetConfigValue);
Execute.Sql("DELETE FROM Config WHERE Key = 'autodownloadpropers'");
}
private void SetConfigValue(IDbConnection conn, IDbTransaction tran)
{
using (var cmd = conn.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandText = "SELECT Value FROM Config WHERE Key = 'autodownloadpropers'";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var value = reader.GetString(0);
var newValue = bool.Parse(value) ? "PreferAndUpgrade" : "DoNotUpgrade";
using (var updateCmd = conn.CreateCommand())
{
updateCmd.Transaction = tran;
updateCmd.CommandText = "INSERT INTO Config (key, value) VALUES ('downloadpropersandrepacks', ?)";
updateCmd.AddParameter(newValue);
updateCmd.ExecuteNonQuery();
}
}
}
}
}
}
}

@ -1,20 +1,25 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Delay; using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.DecisionEngine namespace NzbDrone.Core.DecisionEngine
{ {
public class DownloadDecisionComparer : IComparer<DownloadDecision> public class DownloadDecisionComparer : IComparer<DownloadDecision>
{ {
private readonly IConfigService _configService;
private readonly IDelayProfileService _delayProfileService; private readonly IDelayProfileService _delayProfileService;
public delegate int CompareDelegate(DownloadDecision x, DownloadDecision y); public delegate int CompareDelegate(DownloadDecision x, DownloadDecision y);
public delegate int CompareDelegate<TSubject, TValue>(DownloadDecision x, DownloadDecision y); public delegate int CompareDelegate<TSubject, TValue>(DownloadDecision x, DownloadDecision y);
public DownloadDecisionComparer(IDelayProfileService delayProfileService) public DownloadDecisionComparer(IConfigService configService, IDelayProfileService delayProfileService)
{ {
_configService = configService;
_delayProfileService = delayProfileService; _delayProfileService = delayProfileService;
} }
@ -57,6 +62,12 @@ namespace NzbDrone.Core.DecisionEngine
private int CompareQuality(DownloadDecision x, DownloadDecision y) private int CompareQuality(DownloadDecision x, DownloadDecision y)
{ {
if (_configService.DownloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer)
{
return CompareAll(CompareBy(x.RemoteAlbum, y.RemoteAlbum, remoteAlbum => remoteAlbum.Artist.QualityProfile.Value.GetIndex(remoteAlbum.ParsedAlbumInfo.Quality.Quality)),
CompareBy(x.RemoteAlbum, y.RemoteAlbum, remoteAlbum => remoteAlbum.ParsedAlbumInfo.Quality.Revision.Real));
}
return CompareAll(CompareBy(x.RemoteAlbum, y.RemoteAlbum, remoteAlbum => remoteAlbum.Artist.QualityProfile.Value.GetIndex(remoteAlbum.ParsedAlbumInfo.Quality.Quality)), return CompareAll(CompareBy(x.RemoteAlbum, y.RemoteAlbum, remoteAlbum => remoteAlbum.Artist.QualityProfile.Value.GetIndex(remoteAlbum.ParsedAlbumInfo.Quality.Quality)),
CompareBy(x.RemoteAlbum, y.RemoteAlbum, remoteAlbum => remoteAlbum.ParsedAlbumInfo.Quality.Revision.Real), CompareBy(x.RemoteAlbum, y.RemoteAlbum, remoteAlbum => remoteAlbum.ParsedAlbumInfo.Quality.Revision.Real),
CompareBy(x.RemoteAlbum, y.RemoteAlbum, remoteAlbum => remoteAlbum.ParsedAlbumInfo.Quality.Revision.Version)); CompareBy(x.RemoteAlbum, y.RemoteAlbum, remoteAlbum => remoteAlbum.ParsedAlbumInfo.Quality.Revision.Version));

@ -1,7 +1,7 @@
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Profiles.Delay; using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Languages;
namespace NzbDrone.Core.DecisionEngine namespace NzbDrone.Core.DecisionEngine
{ {
@ -12,10 +12,12 @@ namespace NzbDrone.Core.DecisionEngine
public class DownloadDecisionPriorizationService : IPrioritizeDownloadDecision public class DownloadDecisionPriorizationService : IPrioritizeDownloadDecision
{ {
private readonly IConfigService _configService;
private readonly IDelayProfileService _delayProfileService; private readonly IDelayProfileService _delayProfileService;
public DownloadDecisionPriorizationService(IDelayProfileService delayProfileService) public DownloadDecisionPriorizationService(IConfigService configService, IDelayProfileService delayProfileService)
{ {
_configService = configService;
_delayProfileService = delayProfileService; _delayProfileService = delayProfileService;
} }
@ -24,7 +26,7 @@ namespace NzbDrone.Core.DecisionEngine
return decisions.Where(c => c.RemoteAlbum.DownloadAllowed) return decisions.Where(c => c.RemoteAlbum.DownloadAllowed)
.GroupBy(c => c.RemoteAlbum.Artist.Id, (artistId, downloadDecisions) => .GroupBy(c => c.RemoteAlbum.Artist.Id, (artistId, downloadDecisions) =>
{ {
return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService)); return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_configService, _delayProfileService));
}) })
.SelectMany(c => c) .SelectMany(c => c)
.Union(decisions.Where(c => !c.RemoteAlbum.DownloadAllowed)) .Union(decisions.Where(c => !c.RemoteAlbum.DownloadAllowed))

@ -0,0 +1,67 @@
using System;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class RepackSpecification : IDecisionEngineSpecification
{
private readonly IMediaFileService _mediaFileService;
private readonly UpgradableSpecification _upgradableSpecification;
private readonly Logger _logger;
public RepackSpecification(IMediaFileService mediaFileService, UpgradableSpecification upgradableSpecification, Logger logger)
{
_mediaFileService = mediaFileService;
_upgradableSpecification = upgradableSpecification;
_logger = logger;
}
public SpecificationPriority Priority => SpecificationPriority.Database;
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
{
if (!subject.ParsedAlbumInfo.Quality.Revision.IsRepack)
{
return Decision.Accept();
}
foreach (var album in subject.Albums)
{
var releaseGroup = subject.ParsedAlbumInfo.ReleaseGroup;
var trackFiles = _mediaFileService.GetFilesByAlbum(album.Id);
foreach (var file in trackFiles)
{
if (_upgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedAlbumInfo.Quality))
{
var fileReleaseGroup = file.ReleaseGroup;
if (fileReleaseGroup.IsNullOrWhiteSpace())
{
return Decision.Reject("Unable to determine release group for the existing file");
}
if (releaseGroup.IsNullOrWhiteSpace())
{
return Decision.Reject("Unable to determine release group for this release");
}
if (!fileReleaseGroup.Equals(releaseGroup, StringComparison.InvariantCultureIgnoreCase))
{
_logger.Debug("Release is a repack for a different release group. Release Group: {0}. File release group: {0}", releaseGroup, fileReleaseGroup);
return Decision.Reject("Release is a repack for a different release group. Release Group: {0}. File release group: {0}", releaseGroup, fileReleaseGroup);
}
}
}
}
return Decision.Accept();
}
}
}

@ -5,6 +5,7 @@ using NzbDrone.Core.Configuration;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
{ {
@ -33,32 +34,34 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
return Decision.Accept(); return Decision.Accept();
} }
var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks;
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer)
{
_logger.Debug("Propers are not preferred, skipping check");
return Decision.Accept();
}
foreach (var album in subject.Albums) foreach (var album in subject.Albums)
{ {
var trackFiles = _mediaFileService.GetFilesByAlbum(album.Id); var trackFiles = _mediaFileService.GetFilesByAlbum(album.Id);
if (trackFiles.Any()) foreach (var file in trackFiles)
{ {
var lowestQuality = trackFiles.Select(c => c.Quality).OrderBy(c => c.Quality.Id).First(); if (_qualityUpgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedAlbumInfo.Quality))
var dateAdded = trackFiles[0].DateAdded;
_logger.Debug("Comparing file quality with report. Existing file is {0}", lowestQuality);
if (_qualityUpgradableSpecification.IsRevisionUpgrade(lowestQuality, subject.ParsedAlbumInfo.Quality))
{ {
if (dateAdded < DateTime.Today.AddDays(-7)) if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotUpgrade)
{ {
_logger.Debug("Proper for old file, rejecting: {0}", subject); _logger.Debug("Auto downloading of propers is disabled");
return Decision.Reject("Proper for old file"); return Decision.Reject("Proper downloading is disabled");
} }
if (!_configService.AutoDownloadPropers) if (file.DateAdded < DateTime.Today.AddDays(-7))
{ {
_logger.Debug("Auto downloading of propers is disabled"); _logger.Debug("Proper for old file, rejecting: {0}", subject);
return Decision.Reject("Proper downloading is disabled"); return Decision.Reject("Proper for old file");
} }
} }
} }
} }

@ -1,9 +1,11 @@
using NLog; using NLog;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Languages; 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; using System.Collections.Generic;
using System.Linq;
namespace NzbDrone.Core.DecisionEngine.Specifications namespace NzbDrone.Core.DecisionEngine.Specifications
{ {
@ -19,10 +21,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public class UpgradableSpecification : IUpgradableSpecification public class UpgradableSpecification : IUpgradableSpecification
{ {
private readonly IConfigService _configService;
private readonly Logger _logger; private readonly Logger _logger;
public UpgradableSpecification(Logger logger) public UpgradableSpecification(IConfigService configService, Logger logger)
{ {
_configService = configService;
_logger = logger; _logger = logger;
} }
@ -77,6 +81,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
if (totalCompare == 0) { if (totalCompare == 0) {
return ProfileComparisonResult.Equal; return ProfileComparisonResult.Equal;
} }
// Quality Treated as Equal if Propers are not Prefered
if (_configService.DownloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer &&
newQuality.Revision.CompareTo(currentQualities.Min(q => q.Revision)) > 0)
{
return ProfileComparisonResult.Equal;
}
} }
return ProfileComparisonResult.Upgrade; return ProfileComparisonResult.Upgrade;

@ -5,35 +5,50 @@ using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.MediaFiles.TrackImport.Specifications namespace NzbDrone.Core.MediaFiles.TrackImport.Specifications
{ {
public class UpgradeSpecification : IImportDecisionEngineSpecification<LocalTrack> public class UpgradeSpecification : IImportDecisionEngineSpecification<LocalTrack>
{ {
private readonly IConfigService _configService;
private readonly Logger _logger; private readonly Logger _logger;
public UpgradeSpecification(Logger logger) public UpgradeSpecification(IConfigService configService, Logger logger)
{ {
_configService = configService;
_logger = logger; _logger = logger;
} }
public Decision IsSatisfiedBy(LocalTrack localTrack) public Decision IsSatisfiedBy(LocalTrack localTrack)
{ {
var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks;
var qualityComparer = new QualityModelComparer(localTrack.Artist.QualityProfile); var qualityComparer = new QualityModelComparer(localTrack.Artist.QualityProfile);
var languageComparer = new LanguageComparer(localTrack.Artist.LanguageProfile); var languageComparer = new LanguageComparer(localTrack.Artist.LanguageProfile);
if (localTrack.Tracks.Any(e => e.TrackFileId != 0 && qualityComparer.Compare(e.TrackFile.Value.Quality, localTrack.Quality) > 0)) foreach (var track in localTrack.Tracks.Where(e => e.TrackFileId > 0))
{ {
_logger.Debug("This file isn't a quality upgrade for all tracks. Skipping {0}", localTrack.Path); var trackFile = track.TrackFile.Value;
return Decision.Reject("Not an upgrade for existing track file(s)"); var qualityCompare = qualityComparer.Compare(localTrack.Quality.Quality, trackFile.Quality.Quality);
}
if (localTrack.Tracks.Any(e => e.TrackFileId != 0 && if (qualityCompare < 0)
languageComparer.Compare(e.TrackFile.Value.Language, localTrack.Language) > 0 && {
qualityComparer.Compare(e.TrackFile.Value.Quality, localTrack.Quality) == 0)) _logger.Debug("This file isn't a quality upgrade for all tracks. Skipping {0}", localTrack.Path);
{ return Decision.Reject("Not an upgrade for existing track file(s)");
_logger.Debug("This file isn't a language upgrade for all tracks. Skipping {0}", localTrack.Path); }
return Decision.Reject("Not an upgrade for existing track file(s)");
if (qualityCompare == 0 && downloadPropersAndRepacks != ProperDownloadTypes.DoNotPrefer &&
localTrack.Quality.Revision.CompareTo(trackFile.Quality.Revision) < 0)
{
_logger.Debug("This file isn't a quality upgrade for all tracks. Skipping {0}", localTrack.Path);
return Decision.Reject("Not an upgrade for existing track file(s)");
}
if (languageComparer.Compare(localTrack.Language, trackFile.Language) < 0 && qualityCompare == 0)
{
_logger.Debug("This file isn't a language upgrade for all tracks. Skipping {0}", localTrack.Path);
return Decision.Reject("Not an upgrade for existing track file(s)");
}
} }
return Decision.Accept(); return Decision.Accept();

@ -173,6 +173,7 @@
<Compile Include="Datastore\Migration\030_add_mediafilerepository_mtime.cs" /> <Compile Include="Datastore\Migration\030_add_mediafilerepository_mtime.cs" />
<Compile Include="Datastore\Migration\031_add_artistmetadataid_constraint.cs" /> <Compile Include="Datastore\Migration\031_add_artistmetadataid_constraint.cs" />
<Compile Include="Datastore\Migration\032_old_ids_and_artist_alias.cs" /> <Compile Include="Datastore\Migration\032_old_ids_and_artist_alias.cs" />
<Compile Include="Datastore\Migration\033_download_propers_config.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationDbFactory.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationDbFactory.cs" />
@ -200,6 +201,7 @@
<Compile Include="DecisionEngine\IRejectWithReason.cs" /> <Compile Include="DecisionEngine\IRejectWithReason.cs" />
<Compile Include="DecisionEngine\Rejection.cs" /> <Compile Include="DecisionEngine\Rejection.cs" />
<Compile Include="DecisionEngine\RejectionType.cs" /> <Compile Include="DecisionEngine\RejectionType.cs" />
<Compile Include="DecisionEngine\Specifications\RepackSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\SameTracksSpecification.cs" /> <Compile Include="DecisionEngine\Specifications\SameTracksSpecification.cs" />
<Compile Include="DecisionEngine\SpecificationPriority.cs" /> <Compile Include="DecisionEngine\SpecificationPriority.cs" />
<Compile Include="DecisionEngine\Specifications\AcceptableSizeSpecification.cs" /> <Compile Include="DecisionEngine\Specifications\AcceptableSizeSpecification.cs" />
@ -1034,6 +1036,7 @@
<Compile Include="Profiles\Releases\TermMatchers\ITermMatcher.cs" /> <Compile Include="Profiles\Releases\TermMatchers\ITermMatcher.cs" />
<Compile Include="Profiles\Releases\TermMatchers\RegexTermMatcher.cs" /> <Compile Include="Profiles\Releases\TermMatchers\RegexTermMatcher.cs" />
<Compile Include="ProgressMessaging\ProgressMessageContext.cs" /> <Compile Include="ProgressMessaging\ProgressMessageContext.cs" />
<Compile Include="Qualities\ProperDownloadTypes.cs" />
<Compile Include="Qualities\QualityDetectionSource.cs" /> <Compile Include="Qualities\QualityDetectionSource.cs" />
<Compile Include="Qualities\Revision.cs" /> <Compile Include="Qualities\Revision.cs" />
<Compile Include="Queue\EstimatedCompletionTimeComparer.cs" /> <Compile Include="Queue\EstimatedCompletionTimeComparer.cs" />
@ -1343,4 +1346,4 @@
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
</Project> </Project>

@ -13,7 +13,10 @@ namespace NzbDrone.Core.Parser
{ {
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(QualityParser)); private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(QualityParser));
private static readonly Regex ProperRegex = new Regex(@"\b(?<proper>proper|repack|rerip)\b", private static readonly Regex ProperRegex = new Regex(@"\b(?<proper>proper)\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex RepackRegex = new Regex(@"\b(?<repack>repack|rerip)\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase); RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex VersionRegex = new Regex(@"\dv(?<version>\d)\b|\[v(?<version>\d)\]", private static readonly Regex VersionRegex = new Regex(@"\dv(?<version>\d)\b|\[v(?<version>\d)\]",
@ -286,6 +289,12 @@ namespace NzbDrone.Core.Parser
result.Revision.Version = 2; result.Revision.Version = 2;
} }
if (RepackRegex.IsMatch(normalizedName))
{
result.Revision.Version = 2;
result.Revision.IsRepack = true;
}
Match versionRegexResult = VersionRegex.Match(normalizedName); Match versionRegexResult = VersionRegex.Match(normalizedName);
if (versionRegexResult.Success) if (versionRegexResult.Success)
@ -294,7 +303,6 @@ namespace NzbDrone.Core.Parser
} }
//TODO: re-enable this when we have a reliable way to determine real //TODO: re-enable this when we have a reliable way to determine real
//TODO: Only treat it as a real if it comes AFTER the season/epsiode number
MatchCollection realRegexResult = RealRegex.Matches(name); MatchCollection realRegexResult = RealRegex.Matches(name);
if (realRegexResult.Count > 0) if (realRegexResult.Count > 0)

@ -0,0 +1,9 @@
namespace NzbDrone.Core.Qualities
{
public enum ProperDownloadTypes
{
PreferAndUpgrade,
DoNotUpgrade,
DoNotPrefer
}
}

@ -1,4 +1,4 @@
using System; using System;
using System.Text; using System.Text;
namespace NzbDrone.Core.Qualities namespace NzbDrone.Core.Qualities
@ -9,14 +9,16 @@ namespace NzbDrone.Core.Qualities
{ {
} }
public Revision(int version = 1, int real = 0) public Revision(int version = 1, int real = 0, bool isRepack = false)
{ {
Version = version; Version = version;
Real = real; Real = real;
IsRepack = isRepack;
} }
public int Version { get; set; } public int Version { get; set; }
public int Real { get; set; } public int Real { get; set; }
public bool IsRepack { get; set; }
public bool Equals(Revision other) public bool Equals(Revision other)
{ {

Loading…
Cancel
Save