mirror of
https://github.com/Sonarr/Sonarr.git
synced 2025-01-27 11:21:43 +02:00
New: Option to not prefer repacks/propers (for use with Preferred Words)
Closes #3084
This commit is contained in:
parent
a06cbc44cd
commit
7321075631
@ -25,6 +25,12 @@ const rescanAfterRefreshOptions = [
|
|||||||
{ 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: 'localAirDate', value: 'Local Air Date' },
|
{ key: 'localAirDate', value: 'Local Air Date' },
|
||||||
@ -227,14 +233,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 Sonarr 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 Sonarr.Http.REST;
|
using Sonarr.Http.REST;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
|
||||||
namespace NzbDrone.Api.Config
|
namespace NzbDrone.Api.Config
|
||||||
{
|
{
|
||||||
@ -8,7 +9,7 @@ namespace NzbDrone.Api.Config
|
|||||||
{
|
{
|
||||||
public bool AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; }
|
public bool AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; }
|
||||||
public string RecycleBin { get; set; }
|
public string RecycleBin { get; set; }
|
||||||
public bool AutoDownloadPropers { get; set; }
|
public ProperDownloadTypes DownloadPropersAndRepacks { get; set; }
|
||||||
public bool CreateEmptySeriesFolders { get; set; }
|
public bool CreateEmptySeriesFolders { get; set; }
|
||||||
public bool DeleteEmptyFolders { get; set; }
|
public bool DeleteEmptyFolders { get; set; }
|
||||||
public FileDateType FileDate { get; set; }
|
public FileDateType FileDate { get; set; }
|
||||||
@ -34,7 +35,7 @@ namespace NzbDrone.Api.Config
|
|||||||
{
|
{
|
||||||
AutoUnmonitorPreviouslyDownloadedEpisodes = model.AutoUnmonitorPreviouslyDownloadedEpisodes,
|
AutoUnmonitorPreviouslyDownloadedEpisodes = model.AutoUnmonitorPreviouslyDownloadedEpisodes,
|
||||||
RecycleBin = model.RecycleBin,
|
RecycleBin = model.RecycleBin,
|
||||||
AutoDownloadPropers = model.AutoDownloadPropers,
|
DownloadPropersAndRepacks = model.DownloadPropersAndRepacks,
|
||||||
CreateEmptySeriesFolders = model.CreateEmptySeriesFolders,
|
CreateEmptySeriesFolders = model.CreateEmptySeriesFolders,
|
||||||
DeleteEmptyFolders = model.DeleteEmptyFolders,
|
DeleteEmptyFolders = model.DeleteEmptyFolders,
|
||||||
FileDate = model.FileDate,
|
FileDate = model.FileDate,
|
||||||
|
@ -13,6 +13,7 @@ using NUnit.Framework;
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Core.Languages;
|
using NzbDrone.Core.Languages;
|
||||||
using NzbDrone.Core.Profiles.Languages;
|
using NzbDrone.Core.Profiles.Languages;
|
||||||
@ -440,7 +441,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 remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.SDTV), Language.French);
|
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.SDTV), Language.French);
|
||||||
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.German);
|
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.German);
|
||||||
@ -452,5 +453,87 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
|||||||
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
|
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
|
||||||
qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Quality.Should().Be(Quality.HDTV720p);
|
qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Quality.Should().Be(Quality.HDTV720p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_prefer_higher_score_over_lower_score()
|
||||||
|
{
|
||||||
|
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.WEBDL1080p), Language.English);
|
||||||
|
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.WEBDL1080p), Language.English);
|
||||||
|
|
||||||
|
remoteEpisode1.PreferredWordScore = 10;
|
||||||
|
remoteEpisode2.PreferredWordScore = 0;
|
||||||
|
|
||||||
|
var decisions = new List<DownloadDecision>();
|
||||||
|
decisions.Add(new DownloadDecision(remoteEpisode1));
|
||||||
|
decisions.Add(new DownloadDecision(remoteEpisode2));
|
||||||
|
|
||||||
|
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
|
||||||
|
qualifiedReports.First().RemoteEpisode.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 remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.WEBDL1080p, new Revision(1)), Language.English);
|
||||||
|
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.WEBDL1080p, new Revision(2)), Language.English);
|
||||||
|
|
||||||
|
remoteEpisode1.PreferredWordScore = 10;
|
||||||
|
remoteEpisode2.PreferredWordScore = 0;
|
||||||
|
|
||||||
|
var decisions = new List<DownloadDecision>();
|
||||||
|
decisions.Add(new DownloadDecision(remoteEpisode1));
|
||||||
|
decisions.Add(new DownloadDecision(remoteEpisode2));
|
||||||
|
|
||||||
|
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
|
||||||
|
qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.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 remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.WEBDL1080p, new Revision(1)), Language.English);
|
||||||
|
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.WEBDL1080p, new Revision(2)), Language.English);
|
||||||
|
|
||||||
|
remoteEpisode1.PreferredWordScore = 10;
|
||||||
|
remoteEpisode2.PreferredWordScore = 0;
|
||||||
|
|
||||||
|
var decisions = new List<DownloadDecision>();
|
||||||
|
decisions.Add(new DownloadDecision(remoteEpisode1));
|
||||||
|
decisions.Add(new DownloadDecision(remoteEpisode2));
|
||||||
|
|
||||||
|
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
|
||||||
|
qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.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 remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.WEBDL1080p, new Revision(1)), Language.English);
|
||||||
|
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.WEBDL1080p, new Revision(2)), Language.English);
|
||||||
|
|
||||||
|
remoteEpisode1.PreferredWordScore = 10;
|
||||||
|
remoteEpisode2.PreferredWordScore = 0;
|
||||||
|
|
||||||
|
var decisions = new List<DownloadDecision>();
|
||||||
|
decisions.Add(new DownloadDecision(remoteEpisode1));
|
||||||
|
decisions.Add(new DownloadDecision(remoteEpisode2));
|
||||||
|
|
||||||
|
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
|
||||||
|
qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Quality.Should().Be(Quality.WEBDL1080p);
|
||||||
|
qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Revision.Version.Should().Be(1);
|
||||||
|
qualifiedReports.First().RemoteEpisode.PreferredWordScore.Should().Be(10);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ using NzbDrone.Core.Parser.Model;
|
|||||||
using NzbDrone.Core.Profiles.Qualities;
|
using NzbDrone.Core.Profiles.Qualities;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Core.DecisionEngine;
|
|
||||||
using NzbDrone.Core.DecisionEngine.Specifications;
|
using NzbDrone.Core.DecisionEngine.Specifications;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
@ -61,13 +60,6 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
|
|||||||
_firstFile.Quality = new QualityModel(Quality.SDTV);
|
_firstFile.Quality = new QualityModel(Quality.SDTV);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenAutoDownloadPropers()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IConfigService>()
|
|
||||||
.Setup(s => s.AutoDownloadPropers)
|
|
||||||
.Returns(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_false_when_episodeFile_was_added_more_than_7_days_ago()
|
public void should_return_false_when_episodeFile_was_added_more_than_7_days_ago()
|
||||||
{
|
{
|
||||||
@ -118,6 +110,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.DVD;
|
_firstFile.Quality.Quality = Quality.DVD;
|
||||||
|
|
||||||
_firstFile.DateAdded = DateTime.Today;
|
_firstFile.DateAdded = DateTime.Today;
|
||||||
@ -127,7 +123,22 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
|
|||||||
[Test]
|
[Test]
|
||||||
public void should_return_true_when_episodeFile_was_added_today()
|
public void should_return_true_when_episodeFile_was_added_today()
|
||||||
{
|
{
|
||||||
GivenAutoDownloadPropers();
|
Mocker.GetMock<IConfigService>()
|
||||||
|
.Setup(s => s.DownloadPropersAndRepacks)
|
||||||
|
.Returns(ProperDownloadTypes.PreferAndUpgrade);
|
||||||
|
|
||||||
|
_firstFile.Quality.Quality = Quality.DVD;
|
||||||
|
|
||||||
|
_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.DVD;
|
_firstFile.Quality.Quality = Quality.DVD;
|
||||||
|
|
||||||
|
@ -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
|
||||||
@ -79,7 +79,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
|||||||
[Test, TestCaseSource("IsUpgradeTestCasesLanguages")]
|
[Test, TestCaseSource("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
|
||||||
{
|
{
|
||||||
@ -108,9 +108,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
|
||||||
{
|
{
|
||||||
@ -127,13 +127,42 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
|||||||
Subject.IsUpgradable(
|
Subject.IsUpgradable(
|
||||||
profile,
|
profile,
|
||||||
langProfile,
|
langProfile,
|
||||||
new QualityModel(Quality.DVD, new Revision(version: 2)),
|
|
||||||
Language.English,
|
|
||||||
NoPreferredWordScore,
|
|
||||||
new QualityModel(Quality.DVD, new Revision(version: 1)),
|
new QualityModel(Quality.DVD, new Revision(version: 1)),
|
||||||
Language.English,
|
Language.English,
|
||||||
|
NoPreferredWordScore,
|
||||||
|
new QualityModel(Quality.DVD, new Revision(version: 2)),
|
||||||
|
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 QualityModel(Quality.DVD, new Revision(version: 1)),
|
||||||
|
Language.English,
|
||||||
|
NoPreferredWordScore,
|
||||||
|
new QualityModel(Quality.DVD, new Revision(version: 2)),
|
||||||
|
Language.English,
|
||||||
|
NoPreferredWordScore)
|
||||||
|
.Should().BeFalse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ 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.MediaFiles.EpisodeImport;
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Core.Security;
|
using NzbDrone.Core.Security;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Configuration
|
namespace NzbDrone.Core.Configuration
|
||||||
@ -113,11 +114,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
|
||||||
|
@ -2,6 +2,7 @@ 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.MediaFiles.EpisodeImport;
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Core.Security;
|
using NzbDrone.Core.Security;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Configuration
|
namespace NzbDrone.Core.Configuration
|
||||||
@ -26,7 +27,7 @@ namespace NzbDrone.Core.Configuration
|
|||||||
//Media Management
|
//Media Management
|
||||||
bool AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; }
|
bool AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; }
|
||||||
string RecycleBin { get; set; }
|
string RecycleBin { get; set; }
|
||||||
bool AutoDownloadPropers { get; set; }
|
ProperDownloadTypes DownloadPropersAndRepacks { get; set; }
|
||||||
bool CreateEmptySeriesFolders { get; set; }
|
bool CreateEmptySeriesFolders { 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(131)]
|
||||||
|
public class download_propers_config : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Execute.WithConnection(SetMetadataFileExtension);
|
||||||
|
Execute.Sql("DELETE FROM Config WHERE Key = 'autodownloadpropers'");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetMetadataFileExtension(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,21 +1,26 @@
|
|||||||
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;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +64,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.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.Series.QualityProfile.Value.GetIndex(remoteEpisode.ParsedEpisodeInfo.Quality.Quality)),
|
||||||
|
CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.ParsedEpisodeInfo.Quality.Revision.Real));
|
||||||
|
}
|
||||||
|
|
||||||
return CompareAll(CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.Series.QualityProfile.Value.GetIndex(remoteEpisode.ParsedEpisodeInfo.Quality.Quality)),
|
return CompareAll(CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.Series.QualityProfile.Value.GetIndex(remoteEpisode.ParsedEpisodeInfo.Quality.Quality)),
|
||||||
CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.ParsedEpisodeInfo.Quality.Revision.Real),
|
CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.ParsedEpisodeInfo.Quality.Revision.Real),
|
||||||
CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.ParsedEpisodeInfo.Quality.Revision.Version));
|
CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.ParsedEpisodeInfo.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.RemoteEpisode.Series != null)
|
return decisions.Where(c => c.RemoteEpisode.Series != null)
|
||||||
.GroupBy(c => c.RemoteEpisode.Series.Id, (seriesId, downloadDecisions) =>
|
.GroupBy(c => c.RemoteEpisode.Series.Id, (seriesId, 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.RemoteEpisode.Series == null))
|
.Union(decisions.Where(c => c.RemoteEpisode.Series == null))
|
||||||
|
@ -4,6 +4,7 @@ using NLog;
|
|||||||
using NzbDrone.Core.Configuration;
|
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.Qualities;
|
||||||
|
|
||||||
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||||
{
|
{
|
||||||
@ -30,21 +31,29 @@ 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 file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
|
foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
|
||||||
{
|
{
|
||||||
if (_upgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedEpisodeInfo.Quality))
|
if (_upgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedEpisodeInfo.Quality))
|
||||||
{
|
{
|
||||||
|
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotUpgrade)
|
||||||
|
{
|
||||||
|
_logger.Debug("Auto downloading of propers is disabled");
|
||||||
|
return Decision.Reject("Proper downloading is disabled");
|
||||||
|
}
|
||||||
|
|
||||||
if (file.DateAdded < DateTime.Today.AddDays(-7))
|
if (file.DateAdded < DateTime.Today.AddDays(-7))
|
||||||
{
|
{
|
||||||
_logger.Debug("Proper for old file, rejecting: {0}", subject);
|
_logger.Debug("Proper for old file, rejecting: {0}", subject);
|
||||||
return Decision.Reject("Proper for old file");
|
return Decision.Reject("Proper for old file");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_configService.AutoDownloadPropers)
|
|
||||||
{
|
|
||||||
_logger.Debug("Auto downloading of propers is disabled");
|
|
||||||
return Decision.Reject("Proper downloading is disabled");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
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;
|
||||||
@ -18,41 +19,15 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsLanguageUpgradable(LanguageProfile profile, Language currentLanguage, Language newLanguage = null)
|
|
||||||
{
|
|
||||||
if (newLanguage != null)
|
|
||||||
{
|
|
||||||
var compare = new LanguageComparer(profile).Compare(newLanguage, currentLanguage);
|
|
||||||
if (compare <= 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsQualityUpgradable(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null)
|
|
||||||
{
|
|
||||||
if (newQuality != null)
|
|
||||||
{
|
|
||||||
var compare = new QualityModelComparer(profile).Compare(newQuality, currentQuality);
|
|
||||||
|
|
||||||
if (compare <= 0)
|
|
||||||
{
|
|
||||||
_logger.Debug("Existing item has better quality, skipping");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsPreferredWordUpgradable(int currentScore, int newScore)
|
private bool IsPreferredWordUpgradable(int currentScore, int newScore)
|
||||||
{
|
{
|
||||||
return newScore > currentScore;
|
return newScore > currentScore;
|
||||||
@ -60,23 +35,36 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
|||||||
|
|
||||||
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, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality, Language newLanguage, int newScore)
|
||||||
{
|
{
|
||||||
if (IsQualityUpgradable(qualityProfile, currentQuality, newQuality))
|
var qualityComparer = new QualityModelComparer(qualityProfile);
|
||||||
|
var qualityCompare = qualityComparer.Compare(newQuality?.Quality, currentQuality.Quality);
|
||||||
|
|
||||||
|
if (qualityCompare > 0)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (new QualityModelComparer(qualityProfile).Compare(newQuality, currentQuality) < 0)
|
if (qualityCompare < 0)
|
||||||
{
|
{
|
||||||
_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))
|
// Accept unless the user doesn't want to prefer propers, optionally they can
|
||||||
|
// use preferred words to prefer propers/repacks over non-propers/repacks.
|
||||||
|
if (_configService.DownloadPropersAndRepacks != ProperDownloadTypes.DoNotPrefer &&
|
||||||
|
newQuality?.Revision.CompareTo(currentQuality.Revision) > 0)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (new LanguageComparer(languageProfile).Compare(newLanguage, currentLanguage) < 0)
|
var languageCompare = new LanguageComparer(languageProfile).Compare(newLanguage, currentLanguage);
|
||||||
|
|
||||||
|
if (languageCompare > 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (languageCompare < 0)
|
||||||
{
|
{
|
||||||
_logger.Debug("Existing item has better language, skipping");
|
_logger.Debug("Existing item has better language, skipping");
|
||||||
return false;
|
return false;
|
||||||
|
@ -136,6 +136,7 @@
|
|||||||
<Compile Include="Configuration\InvalidConfigFileException.cs" />
|
<Compile Include="Configuration\InvalidConfigFileException.cs" />
|
||||||
<Compile Include="Configuration\RescanAfterRefreshType.cs" />
|
<Compile Include="Configuration\RescanAfterRefreshType.cs" />
|
||||||
<Compile Include="Configuration\ResetApiKeyCommand.cs" />
|
<Compile Include="Configuration\ResetApiKeyCommand.cs" />
|
||||||
|
<Compile Include="Datastore\Migration\131_download_propers_config.cs" />
|
||||||
<Compile Include="Datastore\Migration\130_episode_last_searched_time.cs" />
|
<Compile Include="Datastore\Migration\130_episode_last_searched_time.cs" />
|
||||||
<Compile Include="Datastore\Migration\129_add_relative_original_path_to_episode_file.cs" />
|
<Compile Include="Datastore\Migration\129_add_relative_original_path_to_episode_file.cs" />
|
||||||
<Compile Include="Datastore\Migration\128_rename_quality_profiles_add_upgrade_allowed.cs" />
|
<Compile Include="Datastore\Migration\128_rename_quality_profiles_add_upgrade_allowed.cs" />
|
||||||
@ -1034,6 +1035,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\QualityFinder.cs" />
|
<Compile Include="Qualities\QualityFinder.cs" />
|
||||||
<Compile Include="Qualities\QualitySource.cs" />
|
<Compile Include="Qualities\QualitySource.cs" />
|
||||||
|
9
src/NzbDrone.Core/Qualities/ProperDownloadTypes.cs
Normal file
9
src/NzbDrone.Core/Qualities/ProperDownloadTypes.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace NzbDrone.Core.Qualities
|
||||||
|
{
|
||||||
|
public enum ProperDownloadTypes
|
||||||
|
{
|
||||||
|
PreferAndUpgrade,
|
||||||
|
DoNotUpgrade,
|
||||||
|
DoNotPrefer
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
using Sonarr.Http.REST;
|
using Sonarr.Http.REST;
|
||||||
|
|
||||||
namespace Sonarr.Api.V3.Config
|
namespace Sonarr.Api.V3.Config
|
||||||
@ -9,7 +10,7 @@ namespace Sonarr.Api.V3.Config
|
|||||||
{
|
{
|
||||||
public bool AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; }
|
public bool AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; }
|
||||||
public string RecycleBin { get; set; }
|
public string RecycleBin { get; set; }
|
||||||
public bool AutoDownloadPropers { get; set; }
|
public ProperDownloadTypes DownloadPropersAndRepacks { get; set; }
|
||||||
public bool CreateEmptySeriesFolders { get; set; }
|
public bool CreateEmptySeriesFolders { get; set; }
|
||||||
public bool DeleteEmptyFolders { get; set; }
|
public bool DeleteEmptyFolders { get; set; }
|
||||||
public FileDateType FileDate { get; set; }
|
public FileDateType FileDate { get; set; }
|
||||||
@ -37,7 +38,7 @@ namespace Sonarr.Api.V3.Config
|
|||||||
{
|
{
|
||||||
AutoUnmonitorPreviouslyDownloadedEpisodes = model.AutoUnmonitorPreviouslyDownloadedEpisodes,
|
AutoUnmonitorPreviouslyDownloadedEpisodes = model.AutoUnmonitorPreviouslyDownloadedEpisodes,
|
||||||
RecycleBin = model.RecycleBin,
|
RecycleBin = model.RecycleBin,
|
||||||
AutoDownloadPropers = model.AutoDownloadPropers,
|
DownloadPropersAndRepacks = model.DownloadPropersAndRepacks,
|
||||||
CreateEmptySeriesFolders = model.CreateEmptySeriesFolders,
|
CreateEmptySeriesFolders = model.CreateEmptySeriesFolders,
|
||||||
DeleteEmptyFolders = model.DeleteEmptyFolders,
|
DeleteEmptyFolders = model.DeleteEmptyFolders,
|
||||||
FileDate = model.FileDate,
|
FileDate = model.FileDate,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user