mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-25 17:35:35 -04:00
Compare commits
18 Commits
v4.3.2.685
...
sonarr-pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb764832ed | ||
|
|
685a24e476 | ||
|
|
cae4faae61 | ||
|
|
5dac6badf2 | ||
|
|
5948f56482 | ||
|
|
98ddd0386b | ||
|
|
2947b244e4 | ||
|
|
72552b8084 | ||
|
|
09642444d7 | ||
|
|
d1080b825c | ||
|
|
001421de10 | ||
|
|
bab9b8b36a | ||
|
|
0fb738aa2e | ||
|
|
4963920a46 | ||
|
|
f0d10fe1cd | ||
|
|
386b33b624 | ||
|
|
98201508f2 | ||
|
|
9723c569a1 |
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '4.3.2'
|
||||
majorVersion: '4.4.0'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
|
||||
@@ -113,10 +113,12 @@ class EnhancedSelectInput extends Component {
|
||||
this._scheduleUpdate();
|
||||
}
|
||||
|
||||
if (!Array.isArray(this.props.value) && prevProps.value !== this.props.value) {
|
||||
this.setState({
|
||||
selectedIndex: getSelectedIndex(this.props)
|
||||
});
|
||||
if (!Array.isArray(this.props.value)) {
|
||||
if (prevProps.value !== this.props.value || prevProps.values !== this.props.values) {
|
||||
this.setState({
|
||||
selectedIndex: getSelectedIndex(this.props)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,6 +334,11 @@ class EnhancedSelectInput extends Component {
|
||||
|
||||
const isMultiSelect = Array.isArray(value);
|
||||
const selectedOption = getSelectedOption(selectedIndex, values);
|
||||
let selectedValue = value;
|
||||
|
||||
if (!values.length) {
|
||||
selectedValue = isMultiSelect ? [] : '';
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -372,15 +379,17 @@ class EnhancedSelectInput extends Component {
|
||||
onPress={this.onPress}
|
||||
>
|
||||
{
|
||||
isFetching &&
|
||||
isFetching ?
|
||||
<LoadingIndicator
|
||||
className={styles.loading}
|
||||
size={20}
|
||||
/>
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching &&
|
||||
isFetching ?
|
||||
null :
|
||||
<Icon
|
||||
name={icons.CARET_DOWN}
|
||||
/>
|
||||
@@ -400,7 +409,7 @@ class EnhancedSelectInput extends Component {
|
||||
onPress={this.onPress}
|
||||
>
|
||||
<SelectedValueComponent
|
||||
value={value}
|
||||
value={selectedValue}
|
||||
values={values}
|
||||
{...selectedValueOptions}
|
||||
{...selectedOption}
|
||||
@@ -418,15 +427,17 @@ class EnhancedSelectInput extends Component {
|
||||
>
|
||||
|
||||
{
|
||||
isFetching &&
|
||||
isFetching ?
|
||||
<LoadingIndicator
|
||||
className={styles.loading}
|
||||
size={20}
|
||||
/>
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching &&
|
||||
isFetching ?
|
||||
null :
|
||||
<Icon
|
||||
name={icons.CARET_DOWN}
|
||||
/>
|
||||
@@ -506,7 +517,7 @@ class EnhancedSelectInput extends Component {
|
||||
</Manager>
|
||||
|
||||
{
|
||||
isMobile &&
|
||||
isMobile ?
|
||||
<Modal
|
||||
className={styles.optionsModal}
|
||||
size={sizes.EXTRA_SMALL}
|
||||
@@ -557,7 +568,8 @@ class EnhancedSelectInput extends Component {
|
||||
}
|
||||
</Scroller>
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
</Modal> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ function HintedSelectInputSelectedValue(props) {
|
||||
>
|
||||
<div className={styles.valueText}>
|
||||
{
|
||||
isMultiSelect &&
|
||||
isMultiSelect ?
|
||||
value.map((key, index) => {
|
||||
const v = valuesMap[key];
|
||||
return (
|
||||
@@ -32,26 +32,28 @@ function HintedSelectInputSelectedValue(props) {
|
||||
{v ? v.value : key}
|
||||
</Label>
|
||||
);
|
||||
})
|
||||
}) :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!isMultiSelect && value
|
||||
isMultiSelect ? null : value
|
||||
}
|
||||
</div>
|
||||
|
||||
{
|
||||
hint != null && includeHint &&
|
||||
hint != null && includeHint ?
|
||||
<div className={styles.hintText}>
|
||||
{hint}
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
</EnhancedSelectInputSelectedValue>
|
||||
);
|
||||
}
|
||||
|
||||
HintedSelectInputSelectedValue.propTypes = {
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired,
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
hint: PropTypes.string,
|
||||
isMultiSelect: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -68,7 +68,7 @@ RootFolderSelectInputOption.propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
freeSpace: PropTypes.number,
|
||||
movieFolder: PropTypes.string,
|
||||
isMissing: PropTypes.boolean,
|
||||
isMissing: PropTypes.bool,
|
||||
isMobile: PropTypes.bool.isRequired,
|
||||
isWindows: PropTypes.bool
|
||||
};
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.cloneButton {
|
||||
composes: button from '~Components/Link/IconButton.css';
|
||||
|
||||
@@ -36,3 +40,10 @@
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
@add-mixin truncate;
|
||||
composes: label from '~Components/Label.css';
|
||||
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ class CustomFormat extends Component {
|
||||
{name}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className={styles.buttons}>
|
||||
<IconButton
|
||||
className={styles.cloneButton}
|
||||
title={translate('CloneCustomFormat')}
|
||||
@@ -124,6 +124,7 @@ class CustomFormat extends Component {
|
||||
|
||||
return (
|
||||
<Label
|
||||
className={styles.label}
|
||||
key={index}
|
||||
kind={kind}
|
||||
>
|
||||
|
||||
@@ -89,6 +89,7 @@ function EditIndexerModalContent(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="enableRss"
|
||||
helpText={supportsRss.value ? translate('RSSHelpText') : undefined}
|
||||
helpTextWarning={supportsRss.value ? undefined : translate('RSSIsNotSupportedWithThisIndexer')}
|
||||
isDisabled={!supportsRss.value}
|
||||
{...enableRss}
|
||||
|
||||
@@ -134,5 +134,9 @@
|
||||
"webpack-cli": "4.9.1",
|
||||
"webpack-livereload-plugin": "3.0.2",
|
||||
"worker-loader": "3.0.8"
|
||||
},
|
||||
"volta": {
|
||||
"node": "16.17.0",
|
||||
"yarn": "1.22.19"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.CustomFormats;
|
||||
using NzbDrone.Core.DecisionEngine.Specifications;
|
||||
using NzbDrone.Core.Profiles;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class UpgradeAllowedSpecificationFixture : CoreTest<UpgradableSpecification>
|
||||
{
|
||||
private CustomFormat _customFormatOne;
|
||||
private CustomFormat _customFormatTwo;
|
||||
private Profile _qualityProfile;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_customFormatOne = new CustomFormat
|
||||
{
|
||||
Id = 1,
|
||||
Name = "One"
|
||||
};
|
||||
_customFormatTwo = new CustomFormat
|
||||
{
|
||||
Id = 2,
|
||||
Name = "Two"
|
||||
};
|
||||
|
||||
_qualityProfile = new Profile
|
||||
{
|
||||
Cutoff = Quality.Bluray1080p.Id,
|
||||
Items = Qualities.QualityFixture.GetDefaultQualities(),
|
||||
UpgradeAllowed = false,
|
||||
CutoffFormatScore = 100,
|
||||
FormatItems = new List<ProfileFormatItem>
|
||||
{
|
||||
new ProfileFormatItem
|
||||
{
|
||||
Id = 1,
|
||||
Format = _customFormatOne,
|
||||
Score = 50
|
||||
},
|
||||
new ProfileFormatItem
|
||||
{
|
||||
Id = 1,
|
||||
Format = _customFormatTwo,
|
||||
Score = 100
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_false_when_quality_is_better_custom_formats_are_the_same_and_upgrading_is_not_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = false;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat>(),
|
||||
new QualityModel(Quality.Bluray1080p),
|
||||
new List<CustomFormat>())
|
||||
.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_false_when_quality_is_same_and_custom_format_is_upgrade_and_upgrading_is_not_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = false;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatOne },
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatTwo })
|
||||
.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_custom_format_upgrade_when_upgrading_is_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = true;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatOne },
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatTwo })
|
||||
.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_same_custom_format_score_when_upgrading_is_not_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = false;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatOne },
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatOne })
|
||||
.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_lower_custom_format_score_when_upgrading_is_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = true;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatTwo },
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatOne })
|
||||
.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_lower_language_when_upgrading_is_not_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = false;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatTwo },
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatOne })
|
||||
.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_quality_upgrade_when_upgrading_is_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = true;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat>(),
|
||||
new QualityModel(Quality.Bluray1080p),
|
||||
new List<CustomFormat>())
|
||||
.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_same_quality_when_upgrading_is_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = true;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat>(),
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat>())
|
||||
.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_same_quality_when_upgrading_is_not_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = false;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat>(),
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat>())
|
||||
.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_lower_quality_when_upgrading_is_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = true;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat>(),
|
||||
new QualityModel(Quality.SDTV),
|
||||
new List<CustomFormat>())
|
||||
.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_lower_quality_when_upgrading_is_not_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = false;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat>(),
|
||||
new QualityModel(Quality.SDTV),
|
||||
new List<CustomFormat>())
|
||||
.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
_downloadClients = new List<IDownloadClient>();
|
||||
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(v => v.GetDownloadClients())
|
||||
.Setup(v => v.GetDownloadClients(It.IsAny<bool>()))
|
||||
.Returns(_downloadClients);
|
||||
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
public void should_return_warning_when_download_client_has_not_been_configured()
|
||||
{
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(s => s.GetDownloadClients())
|
||||
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
|
||||
.Returns(Array.Empty<IDownloadClient>());
|
||||
|
||||
Subject.Check().ShouldBeWarning();
|
||||
@@ -40,7 +40,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
.Throws<Exception>();
|
||||
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(s => s.GetDownloadClients())
|
||||
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
|
||||
.Returns(new IDownloadClient[] { downloadClient.Object });
|
||||
|
||||
Subject.Check().ShouldBeError();
|
||||
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
.Returns(new List<DownloadClientItem>());
|
||||
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(s => s.GetDownloadClients())
|
||||
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
|
||||
.Returns(new IDownloadClient[] { downloadClient.Object });
|
||||
|
||||
Subject.Check().ShouldBeOk();
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
.Returns(_clientStatus);
|
||||
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(s => s.GetDownloadClients())
|
||||
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
|
||||
.Returns(new IDownloadClient[] { _downloadClient.Object });
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.Clients;
|
||||
using NzbDrone.Core.HealthCheck.Checks;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
{
|
||||
[TestFixture]
|
||||
public class DownloadClientFolderCheckFixture : CoreTest<DownloadClientSortingCheck>
|
||||
{
|
||||
private DownloadClientInfo _clientStatus;
|
||||
private Mock<IDownloadClient> _downloadClient;
|
||||
|
||||
private static Exception[] DownloadClientExceptions =
|
||||
{
|
||||
new DownloadClientUnavailableException("error"),
|
||||
new DownloadClientAuthenticationException("error"),
|
||||
new DownloadClientException("error")
|
||||
};
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Mocker.GetMock<ILocalizationService>()
|
||||
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
|
||||
.Returns("Some Warning Message");
|
||||
|
||||
_clientStatus = new DownloadClientInfo
|
||||
{
|
||||
IsLocalhost = true,
|
||||
SortingMode = null
|
||||
};
|
||||
|
||||
_downloadClient = Mocker.GetMock<IDownloadClient>();
|
||||
_downloadClient.Setup(s => s.Definition)
|
||||
.Returns(new DownloadClientDefinition { Name = "Test" });
|
||||
|
||||
_downloadClient.Setup(s => s.GetStatus())
|
||||
.Returns(_clientStatus);
|
||||
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
|
||||
.Returns(new IDownloadClient[] { _downloadClient.Object });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_ok_if_sorting_is_not_enabled()
|
||||
{
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_warning_if_sorting_is_enabled()
|
||||
{
|
||||
_clientStatus.SortingMode = "TV";
|
||||
|
||||
Subject.Check().ShouldBeWarning();
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("DownloadClientExceptions")]
|
||||
public void should_return_ok_if_client_throws_downloadclientexception(Exception ex)
|
||||
{
|
||||
_downloadClient.Setup(s => s.GetStatus())
|
||||
.Throws(ex);
|
||||
|
||||
Subject.Check().ShouldBeOk();
|
||||
|
||||
ExceptionVerification.ExpectedErrors(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
.Returns(_clientStatus);
|
||||
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(s => s.GetDownloadClients())
|
||||
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
|
||||
.Returns(new IDownloadClient[] { _downloadClient.Object });
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -13,10 +14,10 @@ using NzbDrone.Core.Test.Framework;
|
||||
namespace NzbDrone.Core.Test.NotificationTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TraktServiceFixture : CoreTest<TraktService>
|
||||
public class TraktServiceFixture : CoreTest<Trakt>
|
||||
{
|
||||
private DownloadMessage _downloadMessage;
|
||||
private TraktSettings _traktSettings;
|
||||
private NotificationDefinition _traktDefinition;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
@@ -34,11 +35,17 @@ namespace NzbDrone.Core.Test.NotificationTests
|
||||
}
|
||||
};
|
||||
|
||||
_traktSettings = new TraktSettings
|
||||
_traktDefinition = new NotificationDefinition
|
||||
{
|
||||
AccessToken = "",
|
||||
RefreshToken = ""
|
||||
Settings = new TraktSettings
|
||||
{
|
||||
AccessToken = "",
|
||||
RefreshToken = "",
|
||||
Expires = DateTime.Now.AddDays(1)
|
||||
}
|
||||
};
|
||||
|
||||
Subject.Definition = _traktDefinition;
|
||||
}
|
||||
|
||||
private void GiventValidMediaInfo(Quality quality, string audioChannels, string audioFormat, string scanType)
|
||||
@@ -56,7 +63,7 @@ namespace NzbDrone.Core.Test.NotificationTests
|
||||
[Test]
|
||||
public void should_add_collection_movie_if_null_mediainfo()
|
||||
{
|
||||
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
|
||||
Subject.OnDownload(_downloadMessage);
|
||||
|
||||
Mocker.GetMock<ITraktProxy>()
|
||||
.Verify(v => v.AddToCollection(It.IsAny<TraktCollectMoviesResource>(), It.IsAny<string>()), Times.Once());
|
||||
@@ -67,7 +74,7 @@ namespace NzbDrone.Core.Test.NotificationTests
|
||||
{
|
||||
GiventValidMediaInfo(Quality.Bluray1080p, "5.1", "DTS", "Progressive");
|
||||
|
||||
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
|
||||
Subject.OnDownload(_downloadMessage);
|
||||
|
||||
Mocker.GetMock<ITraktProxy>()
|
||||
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>
|
||||
@@ -83,7 +90,7 @@ namespace NzbDrone.Core.Test.NotificationTests
|
||||
{
|
||||
GiventValidMediaInfo(Quality.Bluray1080p, "2.0", "DTS", "Progressive");
|
||||
|
||||
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
|
||||
Subject.OnDownload(_downloadMessage);
|
||||
|
||||
Mocker.GetMock<ITraktProxy>()
|
||||
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>
|
||||
|
||||
@@ -144,7 +144,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
if ((isQualityUpgrade || isCustomFormatUpgrade) && !qualityProfile.UpgradeAllowed)
|
||||
{
|
||||
_logger.Debug("Quality profile does not allow upgrades, skipping");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,6 +262,19 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
|
||||
if (category != null)
|
||||
{
|
||||
if (config.Misc.enable_tv_sorting && ContainsCategory(config.Misc.tv_categories, Settings.MovieCategory))
|
||||
{
|
||||
status.SortingMode = "TV";
|
||||
}
|
||||
else if (config.Misc.enable_movie_sorting && ContainsCategory(config.Misc.movie_categories, Settings.MovieCategory))
|
||||
{
|
||||
status.SortingMode = "Movie";
|
||||
}
|
||||
else if (config.Misc.enable_date_sorting && ContainsCategory(config.Misc.date_categories, Settings.MovieCategory))
|
||||
{
|
||||
status.SortingMode = "Date";
|
||||
}
|
||||
|
||||
status.OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.FullPath) };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Disk;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
@@ -11,6 +11,7 @@ namespace NzbDrone.Core.Download
|
||||
}
|
||||
|
||||
public bool IsLocalhost { get; set; }
|
||||
public string SortingMode { get; set; }
|
||||
public List<OsPath> OutputRootFolders { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Download
|
||||
public interface IProvideDownloadClient
|
||||
{
|
||||
IDownloadClient GetDownloadClient(DownloadProtocol downloadProtocol, int indexerId = 0);
|
||||
IEnumerable<IDownloadClient> GetDownloadClients();
|
||||
IEnumerable<IDownloadClient> GetDownloadClients(bool filterBlockedClients = false);
|
||||
IDownloadClient Get(int id);
|
||||
}
|
||||
|
||||
@@ -86,14 +86,39 @@ namespace NzbDrone.Core.Download
|
||||
return provider;
|
||||
}
|
||||
|
||||
public IEnumerable<IDownloadClient> GetDownloadClients()
|
||||
public IEnumerable<IDownloadClient> GetDownloadClients(bool filterBlockedClients = false)
|
||||
{
|
||||
return _downloadClientFactory.GetAvailableProviders();
|
||||
var enabledClients = _downloadClientFactory.GetAvailableProviders();
|
||||
|
||||
if (filterBlockedClients)
|
||||
{
|
||||
return FilterBlockedIndexers(enabledClients).ToList();
|
||||
}
|
||||
|
||||
return enabledClients;
|
||||
}
|
||||
|
||||
public IDownloadClient Get(int id)
|
||||
{
|
||||
return _downloadClientFactory.GetAvailableProviders().Single(d => d.Definition.Id == id);
|
||||
}
|
||||
|
||||
private IEnumerable<IDownloadClient> FilterBlockedIndexers(IEnumerable<IDownloadClient> clients)
|
||||
{
|
||||
var blockedClients = _downloadClientStatusService.GetBlockedProviders().ToDictionary(v => v.ProviderId, v => v);
|
||||
|
||||
foreach (var client in clients)
|
||||
{
|
||||
DownloadClientStatus blockedClientStatus;
|
||||
|
||||
if (blockedClients.TryGetValue(client.Definition.Id, out blockedClientStatus))
|
||||
{
|
||||
_logger.Debug("Temporarily ignoring client {0} till {1} due to recent failures.", client.Definition.Name, blockedClientStatus.DisabledTill.Value.ToLocalTime());
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return client;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace NzbDrone.Core.Download
|
||||
movieGrabbedEvent.DownloadId = downloadClientId;
|
||||
}
|
||||
|
||||
_logger.ProgressInfo("Report sent to {0} from indexer {1}. {2}", downloadClient.Definition.Name, remoteMovie.Release.Indexer, downloadTitle);
|
||||
_logger.ProgressInfo("Report for {0} ({1}) sent to {2} from indexer {3}. {4}", remoteMovie.Movie.Title, remoteMovie.Movie.Year, downloadClient.Definition.Name, remoteMovie.Release.Indexer, downloadTitle);
|
||||
_eventAggregator.PublishEvent(movieGrabbedEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,9 +343,12 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
||||
|
||||
if (movieFile.MediaInfo.Subtitles != null && movieFile.MediaInfo.Subtitles.Count > 0)
|
||||
{
|
||||
var subtitle = new XElement("subtitle");
|
||||
subtitle.Add(new XElement("language", movieFile.MediaInfo.Subtitles));
|
||||
streamDetails.Add(subtitle);
|
||||
foreach (var s in movieFile.MediaInfo.Subtitles)
|
||||
{
|
||||
var subtitle = new XElement("subtitle");
|
||||
subtitle.Add(new XElement("language", s));
|
||||
streamDetails.Add(subtitle);
|
||||
}
|
||||
}
|
||||
|
||||
fileInfo.Add(streamDetails);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
@@ -37,7 +38,9 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
var clients = _downloadClientProvider.GetDownloadClients();
|
||||
// Only check clients not in failure status, those get another message
|
||||
var clients = _downloadClientProvider.GetDownloadClients(true);
|
||||
|
||||
var rootFolders = _rootFolderService.All();
|
||||
|
||||
foreach (var client in clients)
|
||||
@@ -58,6 +61,10 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unknown error occured in DownloadClientRootFolderCheck HealthCheck");
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.Clients;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.RemotePathMappings;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Core.ThingiProvider.Events;
|
||||
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
[CheckOn(typeof(ProviderUpdatedEvent<IDownloadClient>))]
|
||||
[CheckOn(typeof(ProviderDeletedEvent<IDownloadClient>))]
|
||||
[CheckOn(typeof(ModelEvent<RootFolder>))]
|
||||
[CheckOn(typeof(ModelEvent<RemotePathMapping>))]
|
||||
|
||||
public class DownloadClientSortingCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IProvideDownloadClient _downloadClientProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DownloadClientSortingCheck(IProvideDownloadClient downloadClientProvider,
|
||||
Logger logger,
|
||||
ILocalizationService localizationService)
|
||||
: base(localizationService)
|
||||
{
|
||||
_downloadClientProvider = downloadClientProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
var clients = _downloadClientProvider.GetDownloadClients();
|
||||
|
||||
foreach (var client in clients)
|
||||
{
|
||||
try
|
||||
{
|
||||
var clientName = client.Definition.Name;
|
||||
var status = client.GetStatus();
|
||||
|
||||
if (status.SortingMode.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("DownloadClientSortingCheckMessage"), clientName, status.SortingMode), "#download-folder-and-library-folder-not-different-folders");
|
||||
}
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unknown error occurred in DownloadClientSortingCheck HealthCheck");
|
||||
}
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
@@ -53,7 +54,8 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
|
||||
var clients = _downloadClientProvider.GetDownloadClients();
|
||||
// Only check clients not in failure status, those get another message
|
||||
var clients = _downloadClientProvider.GetDownloadClients(true);
|
||||
|
||||
foreach (var client in clients)
|
||||
{
|
||||
@@ -100,6 +102,10 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unknown error occured in RemotePathMapping HealthCheck");
|
||||
@@ -139,7 +145,14 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
|
||||
// If the previous case did not match then the failure occured in DownloadedMovieImportService,
|
||||
// while trying to locate the files reported by the download client
|
||||
var client = _downloadClientProvider.GetDownloadClients().FirstOrDefault(x => x.Definition.Name == failureMessage.DownloadClientInfo.Name);
|
||||
// Only check clients not in failure status, those get another message
|
||||
var client = _downloadClientProvider.GetDownloadClients(true).FirstOrDefault(x => x.Definition.Name == failureMessage.DownloadClientInfo.Name);
|
||||
|
||||
if (client == null)
|
||||
{
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var status = client.GetStatus();
|
||||
@@ -192,6 +205,10 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unknown error occured in RemotePathMapping HealthCheck");
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.List
|
||||
var listName = Parser.Parser.ToUrlSlug(Settings.Listname.Trim(), true, "-", "-");
|
||||
link += $"users/{Settings.Username.Trim()}/lists/{listName}/items/movies?limit={Settings.Limit}";
|
||||
|
||||
var request = new ImportListRequest(_traktProxy.BuildTraktRequest(link, HttpMethod.Get, Settings.AccessToken));
|
||||
var request = new ImportListRequest(_traktProxy.BuildRequest(link, HttpMethod.Get, Settings.AccessToken));
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.ImportLists.ImportListMovies;
|
||||
using NzbDrone.Core.Notifications.Trakt.Resource;
|
||||
|
||||
@@ -31,11 +31,11 @@ namespace NzbDrone.Core.ImportLists.Trakt.Popular
|
||||
|
||||
if (_settings.TraktListType == (int)TraktPopularListType.Popular)
|
||||
{
|
||||
jsonResponse = JsonConvert.DeserializeObject<List<TraktMovieResource>>(_importResponse.Content);
|
||||
jsonResponse = STJson.Deserialize<List<TraktMovieResource>>(_importResponse.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonResponse = JsonConvert.DeserializeObject<List<TraktListResource>>(_importResponse.Content).SelectList(c => c.Movie);
|
||||
jsonResponse = STJson.Deserialize<List<TraktListResource>>(_importResponse.Content).SelectList(c => c.Movie);
|
||||
}
|
||||
|
||||
// no movies were return
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.Popular
|
||||
|
||||
link += filtersAndLimit;
|
||||
|
||||
var request = new ImportListRequest(_traktProxy.BuildTraktRequest(link, HttpMethod.Get, Settings.AccessToken));
|
||||
var request = new ImportListRequest(_traktProxy.BuildRequest(link, HttpMethod.Get, Settings.AccessToken));
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.ImportLists.Exceptions;
|
||||
using NzbDrone.Core.ImportLists.ImportListMovies;
|
||||
using NzbDrone.Core.Notifications.Trakt.Resource;
|
||||
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.ImportLists.Trakt
|
||||
return movies;
|
||||
}
|
||||
|
||||
var jsonResponse = JsonConvert.DeserializeObject<List<TraktListResource>>(_importResponse.Content);
|
||||
var jsonResponse = STJson.Deserialize<List<TraktListResource>>(_importResponse.Content);
|
||||
|
||||
// no movies were return
|
||||
if (jsonResponse == null)
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.User
|
||||
break;
|
||||
}
|
||||
|
||||
var request = new ImportListRequest(_traktProxy.BuildTraktRequest(link, HttpMethod.Get, Settings.AccessToken));
|
||||
var request = new ImportListRequest(_traktProxy.BuildRequest(link, HttpMethod.Get, Settings.AccessToken));
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
@@ -258,6 +258,7 @@
|
||||
"DownloadClients": "Download Clients",
|
||||
"DownloadClientSettings": "Download Client Settings",
|
||||
"DownloadClientsSettingsSummary": "Download clients, download handling and remote path mappings",
|
||||
"DownloadClientSortingCheckMessage": "Download client {0} has {1} sorting enabled for Radarr's category. You should disable sorting in your download client to avoid import issues.",
|
||||
"DownloadClientStatusCheckAllClientMessage": "All download clients are unavailable due to failures",
|
||||
"DownloadClientStatusCheckSingleClientMessage": "Download clients unavailable due to failures: {0}",
|
||||
"DownloadClientUnavailable": "Download client is unavailable",
|
||||
@@ -869,6 +870,7 @@
|
||||
"RootFolders": "Root Folders",
|
||||
"RottenTomatoesRating": "Tomato Rating",
|
||||
"RSS": "RSS",
|
||||
"RSSHelpText": "Will be used when Radarr periodically looks for releases via RSS Sync",
|
||||
"RSSIsNotSupportedWithThisIndexer": "RSS is not supported with this indexer",
|
||||
"RSSSync": "RSS Sync",
|
||||
"RssSyncHelpText": "Interval in minutes. Set to zero to disable (this will stop all automatic release grabbing)",
|
||||
|
||||
@@ -1153,5 +1153,6 @@
|
||||
"SettingsThemeHelpText": "Vaihda sovelluksen käyttöliittymän ulkoasua. \"Automaattinen\" vaihtaa vaalean ja tumman tilan käyttöjärjestelmäsi teemaa vastaavaksi. Innoittanut Theme.Park.",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Haluatko varmasti palauttaa laatumääritykset?",
|
||||
"ResetDefinitions": "Palauta määritykset",
|
||||
"ResetTitlesHelpText": "Palauta määritysten nimet ja arvot."
|
||||
"ResetTitlesHelpText": "Palauta määritysten nimet ja arvot.",
|
||||
"RSSHelpText": "Käytetään etsittäessä julkaisuja RSS-syötteistä ajoitetusti."
|
||||
}
|
||||
|
||||
@@ -1113,7 +1113,7 @@
|
||||
"SelectReleaseGroup": "Sélectionner le groupe de publication",
|
||||
"SetReleaseGroup": "Régler le groupe de publication",
|
||||
"RefreshMonitoredIntervalHelpText": "Intervalle en minutes entre chaque vérification des téléchargements, minimum 1 minute",
|
||||
"AllCollectionsHiddenDueToFilter": "Tous les films sont masqués en raison du filtre appliqué.",
|
||||
"AllCollectionsHiddenDueToFilter": "Toutes les collections sont masquées en raison du filtre appliqué.",
|
||||
"Collections": "Collections",
|
||||
"MonitorMovies": "Surveiller le film",
|
||||
"NoCollections": "Aucun film trouvé, pour commencer, vous voudrez ajouter un nouveau film ou importer des films existants.",
|
||||
|
||||
@@ -1153,5 +1153,6 @@
|
||||
"ResetDefinitions": "Definíciók visszaállítása",
|
||||
"ResetQualityDefinitions": "Állítsd vissza a minőségi meghatározásokat",
|
||||
"ResetTitles": "Címek visszaállítása",
|
||||
"ResetTitlesHelpText": "A definíciócímek és értékek visszaállítása"
|
||||
"ResetTitlesHelpText": "A definíciócímek és értékek visszaállítása",
|
||||
"RSSHelpText": "Akkor használatos, amikor a Radarr rendszeresen keres kiadásokat az RSS Sync segítségével"
|
||||
}
|
||||
|
||||
@@ -372,7 +372,7 @@
|
||||
"CertificateValidation": "Certificaat Validatie",
|
||||
"BypassProxyForLocalAddresses": "Omzeil Proxy voor Lokale Adressen",
|
||||
"Branch": "Branch",
|
||||
"BindAddressHelpText": "Geldig IPv4-adres of '*' voor alle interfaces",
|
||||
"BindAddressHelpText": "Geldig IP-adres, localhost of '*' voor alle interfaces",
|
||||
"DeleteBackup": "Verwijder Veiligheidskopie",
|
||||
"BackupIntervalHelpText": "Tussentijd voor automatische back-up",
|
||||
"Backups": "Veiligheidskopieën",
|
||||
@@ -723,7 +723,7 @@
|
||||
"HaveNotAddedMovies": "U heeft nog geen films toegevoegd, wilt u eerst enkele of al uw films importeren?",
|
||||
"ForMoreInformationOnTheIndividualIndexers": "Voor meer informatie over de individuele indexeerders, klik op de info knoppen.",
|
||||
"ForMoreInformationOnTheIndividualImportListsClinkOnTheInfoButtons": "Voor meer informatie over de individuele lijsten, klik op de info knoppen.",
|
||||
"ForMoreInformationOnTheIndividualDownloadClients": "Voor meer informatie over de individuele downloaders, klik op de info knoppen.",
|
||||
"ForMoreInformationOnTheIndividualDownloadClients": "Voor meer informatie over de individuele download applicaties, klik op de info knoppen.",
|
||||
"FailedLoadingSearchResults": "Laden van zoekresultaten is mislukt, gelieve opnieuw te proberen.",
|
||||
"ExtraFileExtensionsHelpTexts1": "Komma gescheiden lijst met extra bestanden om te importeren (.nfo zal als .nfo-orig worden geïmporteerd)",
|
||||
"CouldNotFindResults": "Kon geen resultaten vinden voor '{0}'",
|
||||
@@ -1115,6 +1115,6 @@
|
||||
"CollectionShowDetailsHelpText": "Collectie status en details weergeven",
|
||||
"CollectionShowOverviewsHelpText": "Collectieoverzicht weergeven",
|
||||
"EditCollection": "Bewerk collectie",
|
||||
"BypassDelayIfHighestQualityHelpText": "Vertraging ignoreren wanneer een release de hoogst ingeschakelde kwaliteit heeft in het kwialiteitsprofiel van het geprefereerde protocol",
|
||||
"BypassDelayIfHighestQualityHelpText": "Vertraging negeren wanneer een release de hoogst mogelijke kwaliteit heeft in het kwaliteitsprofiel van het geprefereerde protocol",
|
||||
"CollectionShowPostersHelpText": "Posters van de collectie tonen"
|
||||
}
|
||||
|
||||
@@ -961,7 +961,7 @@
|
||||
"UpdateSelected": "Atualizar selecionado(s)",
|
||||
"UpdateScriptPathHelpText": "Caminho para um script personalizado que usa um pacote de atualização extraído e lida com o restante do processo de atualização",
|
||||
"Updates": "Atualizações",
|
||||
"UpdateMechanismHelpText": "Use o atualizador integrado do Radarr ou um script",
|
||||
"UpdateMechanismHelpText": "Usar o atualizador integrado do Radarr ou um script",
|
||||
"UpdateCheckUINotWritableMessage": "Não é possível instalar a atualização porque a pasta de interface do usuário '{0}' não é gravável pelo usuário '{1}'.",
|
||||
"UpdateCheckStartupTranslocationMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' está em uma pasta App Translocation.",
|
||||
"UpdateCheckStartupNotWritableMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' não pode ser gravada pelo usuário '{1}'.",
|
||||
@@ -1078,7 +1078,7 @@
|
||||
"AreYouSureYouWantToRemoveSelectedItemFromQueue": "Você tem certeza de que deseja remover {0} item{1} da fila?",
|
||||
"BlocklistReleases": "Lançamento na lista de bloqueio",
|
||||
"RemoveSelectedItems": "Remover Itens Selecionados",
|
||||
"IndexerTagHelpText": "Só use este indexador para filmes com ao menos uma etiqueta correspondente. Deixe em branco para usar com todos os filmes.",
|
||||
"IndexerTagHelpText": "Use este indexador apenas para filmes com pelo menos uma etiqueta correspondente. Deixe em branco para usar com todos os filmes.",
|
||||
"RemoveSelectedItem": "Remover Item Selecionado",
|
||||
"RemoveFailed": "Falha na Remoção",
|
||||
"RemoveCompleted": "Remover Completos",
|
||||
@@ -1153,5 +1153,6 @@
|
||||
"ResetTitles": "Redefinir Títulos",
|
||||
"ResetTitlesHelpText": "Redefinir títulos de definição, bem como valores",
|
||||
"SettingsTheme": "Tema",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Tem certeza de que deseja redefinir as definições de qualidade?"
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Tem certeza de que deseja redefinir as definições de qualidade?",
|
||||
"RSSHelpText": "Será usado quando o Radarr procurar periodicamente lançamentos via RSS Sync"
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
"ExcludeTitle": "Исключить {0}? Radarr не будет автоматически добавлять сканируя лист.",
|
||||
"InCinemasMsg": "Фильмы в кинотеатрах",
|
||||
"IncludeRecommendationsHelpText": "Включить в отображении найденного фильмы рекомендованные Radar",
|
||||
"IndexerPriorityHelpText": "Приоритет индексатора от 1 (самый высокий) до 50 (самый низкий). По умолчанию: 25. Используется при захвате выпусков в качестве средства разрешения конфликтов для равных в остальном выпусков, Radarr по-прежнему будет использовать все включенные индексаторы для синхронизации и поиска RSS.",
|
||||
"IndexerPriorityHelpText": "Приоритет индексатора от 1 (самый высокий) до 50 (самый низкий). По умолчанию: 25. Используется при захвате выпусков в качестве средства разрешения конфликтов для равных в остальном выпусков, Radarr по-прежнему будет использовать все включенные индексаторы для синхронизации и поиска RSS",
|
||||
"IndexerSearchCheckNoAvailableIndexersMessage": "Все индексаторы с возможностью поиска временно выключены из-за ошибок",
|
||||
"KeyboardShortcuts": "Горячие клавиши",
|
||||
"CantFindMovie": "Почему я не могу найти фильм?",
|
||||
@@ -330,7 +330,7 @@
|
||||
"Connect": "Подключить",
|
||||
"Connections": "Соединения",
|
||||
"CompletedDownloadHandling": "Обработка завершенных скачиваний",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Нет доступных индексаторов с интерактивным поиском, Radarr не будет предоставлять результаты интерактивного поиска",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Нет доступных индексаторов с включенным интерактивным поиском, Radarr не будет предоставлять результаты интерактивного поиска",
|
||||
"IndexersSettingsSummary": "Ограничения для индексаторов и релизов",
|
||||
"Language": "Язык",
|
||||
"ImportListStatusCheckAllClientMessage": "Все листы недоступны из-за ошибок",
|
||||
@@ -421,7 +421,7 @@
|
||||
"QualitySettingsSummary": "Качественные размеры и наименования",
|
||||
"QualitySettings": "Настройки качества",
|
||||
"QualityProfiles": "Профили качества",
|
||||
"QualityProfileInUse": "Невозможно удалить профиль качества прикрепленный к фильму",
|
||||
"QualityProfileInUse": "Невозможно удалить профиль качества, прикрепленный к фильму, списку или коллекции",
|
||||
"QualityProfileDeleteConfirm": "Вы действительно хотите удалить профиль качества {0}",
|
||||
"QualityProfile": "Профиль качества",
|
||||
"QualityOrLangCutoffHasNotBeenMet": "Не соблюдено ограничение по качеству или языку",
|
||||
@@ -429,7 +429,7 @@
|
||||
"QualityDefinitions": "Определения качества",
|
||||
"QualityCutoffHasNotBeenMet": "Порог качества не соблюден",
|
||||
"Quality": "Качество",
|
||||
"QualitiesHelpText": "Качества, расположенные выше в списке, более предпочтительны. Качества внутри одной группы равны. Требуются только отмеченные качества",
|
||||
"QualitiesHelpText": "Качества, расположенные выше в списке, являются более предпочтительными, даже если они не отмечены. Качества внутри одной группы равны. Требуются только отмеченные качества",
|
||||
"Qualities": "Качества",
|
||||
"PublishedDate": "Дата публикации",
|
||||
"PtpOldSettingsCheckMessage": "Следующие индексаторы PassThePopcorn устарели и должны быть обновлены: {0}",
|
||||
@@ -747,7 +747,7 @@
|
||||
"BranchUpdateMechanism": "Ветвь, используемая внешним механизмом обновления",
|
||||
"BranchUpdate": "Ветвь для обновления Radarr",
|
||||
"Branch": "Ветка",
|
||||
"BindAddressHelpText": "Действительный IPv4-адрес или '*' для всех интерфейсов",
|
||||
"BindAddressHelpText": "Действительный IP-адрес, локальный адрес или '*' для всех интерфейсов",
|
||||
"BackupFolderHelpText": "Относительные пути будут в каталоге AppData Radarr",
|
||||
"AvailabilityDelayHelpText": "Время до или после доступной даты для поиска фильма",
|
||||
"AvailabilityDelay": "Задержка доступности",
|
||||
@@ -1047,7 +1047,7 @@
|
||||
"RemotePathMappingCheckRemoteDownloadClient": "Удалённый клиент загрузки {0} сообщил о файлах в {1}, но эта директория, похоже, не существует. Вероятно, отсутствует сопоставление удаленных путей.",
|
||||
"RemotePathMappingCheckLocalWrongOSPath": "Локальный клиент загрузки {0} загружает файлы в {1}, но это не правильный путь {2}. Проверьте настройки клиента загрузки.",
|
||||
"RemotePathMappingCheckLocalFolderMissing": "Удалённый клиент загрузки {0} загружает файлы в {1}, но эта директория, похоже, не существует. Вероятно, отсутствует или неправильно указан удаленный путь.",
|
||||
"RemotePathMappingCheckGenericPermissions": "Клиент загрузки {0} загружает файлы в {1}, но Radarr не может найти эту директорию. Возможно, вам нужно настроить права доступа к данной директории.",
|
||||
"RemotePathMappingCheckGenericPermissions": "Клиент загрузки {0} загружает файлы в {1}, но Radarr не может видеть этот каталог. Возможно, вам нужно настроить права доступа к данной директории.",
|
||||
"RemotePathMappingCheckFilesBadDockerPath": "Вы используете docker; клиент загрузки {0} сообщил о файлах в {1}, но это не корректный путь {2}. Проверьте правильность указанного пути и настройки клиента загрузки.",
|
||||
"RemotePathMappingCheckImportFailed": "Radarr не удалось импортировать фильм. Проверьте ваши логи для более подробной информации.",
|
||||
"RemotePathMappingCheckFolderPermissions": "Radarr видит директорию загрузки {0}, но не имеет доступа к ней. Возможно, ошибка в правах доступа.",
|
||||
@@ -1108,7 +1108,7 @@
|
||||
"AllCollectionsHiddenDueToFilter": "Все фильмы скрыты в соответствии с фильтром.",
|
||||
"Collections": "Коллекции",
|
||||
"MonitorMovies": "Отслеживать фильм",
|
||||
"NoCollections": "Коллекции не найдены. Для начала вам нужно добавить новый фильм или импортировать несколько существующих.",
|
||||
"NoCollections": "Коллекции не найдены. Для начала вам нужно добавить новый фильм или импортировать несколько существующих",
|
||||
"CollectionOptions": "Параметры коллекции",
|
||||
"CollectionShowDetailsHelpText": "Показать статус и свойства коллекции",
|
||||
"CollectionShowOverviewsHelpText": "Показать обзоры коллекций",
|
||||
@@ -1142,5 +1142,17 @@
|
||||
"RottenTomatoesRating": "Tomato рейтинг",
|
||||
"Started": "Запущено",
|
||||
"Database": "База данных",
|
||||
"From": "из"
|
||||
"From": "из",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Вы уверены, что хотите сбросить определения качества?",
|
||||
"ResetDefinitions": "Сбросить определения",
|
||||
"ResetQualityDefinitions": "Сбросить определения качества",
|
||||
"ResetTitles": "Сбросить заголовки",
|
||||
"ResetTitlesHelpText": "Сбросить заголовки определений, а также значения",
|
||||
"RSSHelpText": "Будет использоваться, когда Radarr будет периодически искать выпуски через RSS Sync",
|
||||
"SettingsTheme": "Тема",
|
||||
"SettingsThemeHelpText": "Измените тему пользовательского интерфейса приложения, тема «Авто» будет использовать тему вашей ОС для установки светлого или темного режима. Вдохновленный Theme.Park",
|
||||
"PreferredProtocol": "Предпочтительный протокол",
|
||||
"ApplicationURL": "URL-адрес приложения",
|
||||
"ApplicationUrlHelpText": "Внешний URL-адрес этого приложения, включая http(s)://, порт и базовый URL-адрес",
|
||||
"ScrollMovies": "Прокрутите фильмы"
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"Backups": "Резервні копії",
|
||||
"BeforeUpdate": "Перед оновленням",
|
||||
"BindAddress": "Прив'язувати адресу",
|
||||
"BindAddressHelpText": "Дійсна адреса IPv4 або '*' для всіх інтерфейсів",
|
||||
"BindAddressHelpText": "Дійсна адреса IP або '*' для всіх інтерфейсів",
|
||||
"Blocklist": "Чорний список",
|
||||
"Blocklisted": "Заблокований",
|
||||
"BlocklistRelease": "Реліз із чорного списку",
|
||||
@@ -72,7 +72,7 @@
|
||||
"CloneProfile": "Клонувати профіль",
|
||||
"Close": "Закрити",
|
||||
"CloseCurrentModal": "Закрити поточне вікно",
|
||||
"Collection": "Колекція",
|
||||
"Collection": "Колекції",
|
||||
"ColonReplacement": "Заміна двокрапок",
|
||||
"ColonReplacementFormatHelpText": "Змінити як Radarr обробляє заміну двокрапок",
|
||||
"Columns": "Колонки",
|
||||
@@ -492,7 +492,7 @@
|
||||
"PendingChangesMessage": "У вас є незбережені зміни. Ви впевнені, що бажаєте залишити цю сторінку?",
|
||||
"PosterSize": "Розмір плаката",
|
||||
"PreferredProtocol": "Переважний протокол",
|
||||
"PriorityHelpText": "Надайте пріоритет кільком клієнтам завантаження. Round-Robin використовується для клієнтів з однаковим пріоритетом.",
|
||||
"PriorityHelpText": "Надайте пріоритет кільком клієнтам завантаження. Круговий алгоритм використовується для клієнтів з таким же пріоритетом.",
|
||||
"ProxyCheckBadRequestMessage": "Не вдалося перевірити проксі. Код стану: {0}",
|
||||
"ProxyCheckResolveIpMessage": "Не вдалося визначити IP-адресу для налаштованого проксі-сервера {0}",
|
||||
"QualitiesHelpText": "Якості, які стоять вище в списку, є кращими, навіть якщо не позначено. Якості в одній групі рівні. Потрібні тільки перевірені якості",
|
||||
@@ -563,7 +563,7 @@
|
||||
"DiscordUrlInSlackNotification": "Ви налаштували сповіщення Discord як сповіщення Slack. Налаштуйте це як сповіщення Discord для кращої роботи. Сповіщення, на які впливає: {0}",
|
||||
"Missing": "Відсутня",
|
||||
"SettingsTimeFormat": "Формат часу",
|
||||
"iCalLink": "iCal Link",
|
||||
"iCalLink": "Посилання iCal",
|
||||
"InCinemasDate": "У кінотеатрах Дата",
|
||||
"IndexerSettings": "Налаштування індексатора",
|
||||
"List": "Список",
|
||||
@@ -671,7 +671,7 @@
|
||||
"Failed": "Не вдалося",
|
||||
"FailedDownloadHandling": "Помилка обробки завантаження",
|
||||
"FailedToLoadQueue": "Не вдалося завантажити чергу",
|
||||
"FeatureRequests": "Запити щодо функцій",
|
||||
"FeatureRequests": "Майбутні запити",
|
||||
"FileManagement": "Керування файлами",
|
||||
"Filename": "Ім'я файлу",
|
||||
"FileNames": "Імена файлів",
|
||||
@@ -885,7 +885,7 @@
|
||||
"SearchCutoffUnmet": "Обрізка пошуку не виконана",
|
||||
"SearchFiltered": "Пошук відфільтровано",
|
||||
"SearchMovie": "Пошук фільму",
|
||||
"SearchOnAdd": "Шукати на Додати",
|
||||
"SearchOnAdd": "Шукати при додаванні",
|
||||
"SearchOnAddHelpText": "Шукайте фільми в цьому списку після додавання в бібліотеку",
|
||||
"SearchSelected": "Пошук вибрано",
|
||||
"SelectAll": "Вибрати все",
|
||||
@@ -1102,5 +1102,57 @@
|
||||
"SettingsRemotePathMappingLocalPath": "Місцевий шлях",
|
||||
"ShowGenres": "Показати жанри",
|
||||
"UpdateAvailable": "Доступне нове оновлення",
|
||||
"Updates": "Оновлення"
|
||||
"Updates": "Оновлення",
|
||||
"Monitor": "Контрольований",
|
||||
"MonitorCollection": "Контрольовані Колекції",
|
||||
"Monitored": "Відстежується",
|
||||
"MonitoredCollectionHelpText": "Відстежуйте, щоб фільми з цієї колекції автоматично додавалися до бібліотеки",
|
||||
"MonitoredOnly": "Тільки під контролем",
|
||||
"MonitoredStatus": "Відстежується/Стан",
|
||||
"MoreControlCFText": "Хочете більше контролювати, яким завантаженням віддавати перевагу? Додайте",
|
||||
"DownloadPropersAndRepacks": "Пропери та Репаки",
|
||||
"LookingForReleaseProfiles2": "замість цього.",
|
||||
"Usenet": "Usenet",
|
||||
"Grab": "Захопити",
|
||||
"IncludeUnmonitored": "Включити неконтрольований",
|
||||
"Letterboxd": "Letterboxd",
|
||||
"Reddit": "Reddit",
|
||||
"Socks5": "Socks5 (Підтримка TOR)",
|
||||
"Socks4": "Socks4",
|
||||
"SSLCertPath": "Шлях сертифіката SSL",
|
||||
"Wiki": "Wiki",
|
||||
"RSSHelpText": "Використовуватиметься, коли Radarr періодично шукатиме випуски через RSS Sync",
|
||||
"DownloadPropersAndRepacksHelpText1": "Автоматичне оновлення до Propers/Repacks чи ні",
|
||||
"DownloadPropersAndRepacksHelpText2": "Використовуйте «Не віддавати перевагу», щоб відсортувати за користувальницьким форматом оцінки над Propers/Repacks",
|
||||
"DownloadPropersAndRepacksHelpTextWarning": "Використовуйте спеціальні формати для автоматичного оновлення до Propers/Repacks",
|
||||
"IconForCutoffUnmet": "Значок \"Не виконано відсікання\"",
|
||||
"Logging": "Журналування",
|
||||
"LookingForReleaseProfiles1": "Шукайте профілі релізів? Спробуйте",
|
||||
"MaintenanceRelease": "Випуск для обслуговування: виправлення помилок та інші покращення. Щоб отримати докладнішу інформацію, перегляньте історію фіксації Github",
|
||||
"MediaManagement": "Управління медіа",
|
||||
"MediaManagementSettings": "Налаштування Управління медіа",
|
||||
"MetadataSettings": "Налаштування метаданих",
|
||||
"Min": "Мінімум",
|
||||
"MissingNotMonitored": "Відсутній (неконтрольований)",
|
||||
"MonitorMovie": "Відстежувати фільм",
|
||||
"MonitorMovies": "Відстежувати фільми",
|
||||
"NetCore": ".NET",
|
||||
"OnGrabHelpText": "При захопленні",
|
||||
"Peers": "Піри",
|
||||
"RSS": "RSS",
|
||||
"Seeders": "Сиди",
|
||||
"SSLCertPassword": "Пароль SSL сертифіката",
|
||||
"TaskUserAgentTooltip": "Агент користувача, наданий програмою, яка викликала API",
|
||||
"TMDb": "TMDb",
|
||||
"TMDBId": "Ідентифікатор TMDb",
|
||||
"Trakt": "Trakt",
|
||||
"UI": "Інтерфейс користувача",
|
||||
"Metadata": "Метадані",
|
||||
"OnGrab": "При захопленні",
|
||||
"Discover": "Відкрийте для себе",
|
||||
"Grabbed": "Захоплений",
|
||||
"GrabID": "Захопити ID",
|
||||
"GrabRelease": "Захопити реліз",
|
||||
"GrabReleaseMessageText": "Radarr не зміг визначити, для якого фільму цей випуск. Можливо, Radarr не зможе автоматично імпортувати цей випуск. Ви хочете взяти '{0}'?",
|
||||
"GrabSelected": "Захопити вибране"
|
||||
}
|
||||
|
||||
@@ -1148,10 +1148,11 @@
|
||||
"BindAddressHelpText": "有效的 IP 地址,localhost,或以'*'代表所有地址",
|
||||
"PreferredProtocol": "首选协议",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "确定要重置质量定义吗?",
|
||||
"SettingsThemeHelpText": "更改程序界面主题,“自动”主题将根据您的操作系统主题来设置浅色或深色模式。",
|
||||
"SettingsThemeHelpText": "更改程序界面主题,“自动”主题将根据您的操作系统主题来设置浅色或深色模式。灵感来自Theme.Park",
|
||||
"ResetDefinitions": "重置定义",
|
||||
"ResetQualityDefinitions": "重置质量定义",
|
||||
"ResetTitles": "重置标题",
|
||||
"ResetTitlesHelpText": "重置定义标题与参数值",
|
||||
"SettingsTheme": "主题"
|
||||
"SettingsTheme": "主题",
|
||||
"RSSHelpText": "当Radarr定期通过RSS同步查找发布时使用"
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Specifications
|
||||
|
||||
if (qualityCompare < 0)
|
||||
{
|
||||
_logger.Debug("This file isn't a quality upgrade for movie. Skipping {0}", localMovie.Path);
|
||||
return Decision.Reject("Not a quality upgrade for existing movie file(s)");
|
||||
_logger.Debug("This file isn't a quality upgrade for movie. New Quality is {0}. Skipping {1}", localMovie.Quality.Quality, localMovie.Path);
|
||||
return Decision.Reject("Not an upgrade for existing movie file. New Quality is {0}", localMovie.Quality.Quality);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace NzbDrone.Core.Movies
|
||||
AddMethod = AddMovieMethod.Collection
|
||||
},
|
||||
Monitored = true
|
||||
}).ToList());
|
||||
}).ToList(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.Movies;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Gotify
|
||||
@@ -20,39 +23,39 @@ namespace NzbDrone.Core.Notifications.Gotify
|
||||
public override string Name => "Gotify";
|
||||
public override string Link => "https://gotify.net/";
|
||||
|
||||
public override void OnGrab(GrabMessage grabMessage)
|
||||
public override void OnGrab(GrabMessage message)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_GRABBED_TITLE, grabMessage.Message, Settings);
|
||||
SendNotification(MOVIE_GRABBED_TITLE, message.Message, message.Movie);
|
||||
}
|
||||
|
||||
public override void OnDownload(DownloadMessage message)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_DOWNLOADED_TITLE, message.Message, Settings);
|
||||
SendNotification(MOVIE_DOWNLOADED_TITLE, message.Message, message.Movie);
|
||||
}
|
||||
|
||||
public override void OnMovieAdded(Movie movie)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_ADDED_TITLE, $"{movie.Title} added to library", Settings);
|
||||
SendNotification(MOVIE_ADDED_TITLE, $"{movie.Title} added to library", movie);
|
||||
}
|
||||
|
||||
public override void OnMovieFileDelete(MovieFileDeleteMessage deleteMessage)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_FILE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||
SendNotification(MOVIE_FILE_DELETED_TITLE, deleteMessage.Message, deleteMessage.Movie);
|
||||
}
|
||||
|
||||
public override void OnMovieDelete(MovieDeleteMessage deleteMessage)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||
SendNotification(MOVIE_DELETED_TITLE, deleteMessage.Message, deleteMessage.Movie);
|
||||
}
|
||||
|
||||
public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck)
|
||||
{
|
||||
_proxy.SendNotification(HEALTH_ISSUE_TITLE, healthCheck.Message, Settings);
|
||||
SendNotification(HEALTH_ISSUE_TITLE, healthCheck.Message, null);
|
||||
}
|
||||
|
||||
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
|
||||
public override void OnApplicationUpdate(ApplicationUpdateMessage message)
|
||||
{
|
||||
_proxy.SendNotification(APPLICATION_UPDATE_TITLE, updateMessage.Message, Settings);
|
||||
SendNotification(APPLICATION_UPDATE_TITLE, message.Message, null);
|
||||
}
|
||||
|
||||
public override ValidationResult Test()
|
||||
@@ -61,10 +64,29 @@ namespace NzbDrone.Core.Notifications.Gotify
|
||||
|
||||
try
|
||||
{
|
||||
var isMarkdown = false;
|
||||
const string title = "Test Notification";
|
||||
const string body = "This is a test message from Radarr";
|
||||
|
||||
_proxy.SendNotification(title, body, Settings);
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("This is a test message from Radarr");
|
||||
|
||||
if (Settings.IncludeMoviePoster)
|
||||
{
|
||||
isMarkdown = true;
|
||||
|
||||
sb.AppendLine("\r");
|
||||
}
|
||||
|
||||
var payload = new GotifyMessage
|
||||
{
|
||||
Title = title,
|
||||
Message = sb.ToString(),
|
||||
Priority = Settings.Priority
|
||||
};
|
||||
|
||||
payload.SetContentType(isMarkdown);
|
||||
|
||||
_proxy.SendNotification(payload, Settings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -74,5 +96,35 @@ namespace NzbDrone.Core.Notifications.Gotify
|
||||
|
||||
return new ValidationResult(failures);
|
||||
}
|
||||
|
||||
private void SendNotification(string title, string message, Movie movie)
|
||||
{
|
||||
var isMarkdown = false;
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine(message);
|
||||
|
||||
if (Settings.IncludeMoviePoster && movie != null)
|
||||
{
|
||||
var poster = movie.MovieMetadata.Value.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Poster)?.Url;
|
||||
|
||||
if (poster != null)
|
||||
{
|
||||
isMarkdown = true;
|
||||
sb.AppendLine($"\r");
|
||||
}
|
||||
}
|
||||
|
||||
var payload = new GotifyMessage
|
||||
{
|
||||
Title = title,
|
||||
Message = sb.ToString(),
|
||||
Priority = Settings.Priority
|
||||
};
|
||||
|
||||
payload.SetContentType(isMarkdown);
|
||||
|
||||
_proxy.SendNotification(payload, Settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
40
src/NzbDrone.Core/Notifications/Gotify/GotifyMessage.cs
Normal file
40
src/NzbDrone.Core/Notifications/Gotify/GotifyMessage.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Gotify
|
||||
{
|
||||
public class GotifyMessage
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Message { get; set; }
|
||||
public int Priority { get; set; }
|
||||
public GotifyExtras Extras { get; set; }
|
||||
|
||||
public GotifyMessage()
|
||||
{
|
||||
Extras = new GotifyExtras();
|
||||
}
|
||||
|
||||
public void SetContentType(bool isMarkdown)
|
||||
{
|
||||
var contentType = isMarkdown ? "text/markdown" : "text/plain";
|
||||
|
||||
Extras.ClientDisplay = new GotifyClientDisplay(contentType);
|
||||
}
|
||||
}
|
||||
|
||||
public class GotifyExtras
|
||||
{
|
||||
[JsonProperty("client::display")]
|
||||
public GotifyClientDisplay ClientDisplay { get; set; }
|
||||
}
|
||||
|
||||
public class GotifyClientDisplay
|
||||
{
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public GotifyClientDisplay(string contentType)
|
||||
{
|
||||
ContentType = contentType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
using System.Net;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Gotify
|
||||
{
|
||||
public interface IGotifyProxy
|
||||
{
|
||||
void SendNotification(string title, string message, GotifySettings settings);
|
||||
void SendNotification(GotifyMessage payload, GotifySettings settings);
|
||||
}
|
||||
|
||||
public class GotifyProxy : IGotifyProxy
|
||||
@@ -17,16 +18,20 @@ namespace NzbDrone.Core.Notifications.Gotify
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public void SendNotification(string title, string message, GotifySettings settings)
|
||||
public void SendNotification(GotifyMessage payload, GotifySettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new HttpRequestBuilder(settings.Server).Resource("message").Post()
|
||||
.AddQueryParam("token", settings.AppToken)
|
||||
.AddFormParameter("title", title)
|
||||
.AddFormParameter("message", message)
|
||||
.AddFormParameter("priority", settings.Priority)
|
||||
.Build();
|
||||
var request = new HttpRequestBuilder(settings.Server)
|
||||
.Resource("message")
|
||||
.Post()
|
||||
.AddQueryParam("token", settings.AppToken)
|
||||
.Build();
|
||||
|
||||
request.Headers.ContentType = "application/json";
|
||||
|
||||
var json = payload.ToJson();
|
||||
request.SetContent(payload.ToJson());
|
||||
|
||||
_httpClient.Execute(request);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@ namespace NzbDrone.Core.Notifications.Gotify
|
||||
[FieldDefinition(2, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(GotifyPriority), HelpText = "Priority of the notification")]
|
||||
public int Priority { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Include Movie Poster", Type = FieldType.Checkbox, HelpText = "Include movie poster in message")]
|
||||
public bool IncludeMoviePoster { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -70,9 +70,9 @@ namespace NzbDrone.Core.Notifications.Ntfy
|
||||
{
|
||||
try
|
||||
{
|
||||
const string title = "Radarr - Test Notification";
|
||||
const string title = "Sonarr - Test Notification";
|
||||
|
||||
const string body = "This is a test message from Radarr";
|
||||
const string body = "This is a test message from Sonarr";
|
||||
|
||||
SendNotification(title, body, settings);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Notifications.Trakt.Resource;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Trakt
|
||||
{
|
||||
public class Trakt : NotificationBase<TraktSettings>
|
||||
{
|
||||
private readonly ITraktService _traktService;
|
||||
private readonly ITraktProxy _proxy;
|
||||
private readonly INotificationRepository _notificationRepository;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public Trakt(ITraktService traktService, INotificationRepository notificationRepository, Logger logger)
|
||||
public Trakt(ITraktProxy proxy, INotificationRepository notificationRepository, Logger logger)
|
||||
{
|
||||
_traktService = traktService;
|
||||
_proxy = proxy;
|
||||
_notificationRepository = notificationRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
@@ -26,27 +32,53 @@ namespace NzbDrone.Core.Notifications.Trakt
|
||||
|
||||
public override void OnDownload(DownloadMessage message)
|
||||
{
|
||||
_traktService.AddMovieToCollection(Settings, message.Movie, message.MovieFile);
|
||||
RefreshTokenIfNecessary();
|
||||
AddMovieToCollection(Settings, message.Movie, message.MovieFile);
|
||||
}
|
||||
|
||||
public override void OnMovieFileDelete(MovieFileDeleteMessage deleteMessage)
|
||||
{
|
||||
_traktService.RemoveMovieFromCollection(Settings, deleteMessage.Movie);
|
||||
RefreshTokenIfNecessary();
|
||||
RemoveMovieFromCollection(Settings, deleteMessage.Movie);
|
||||
}
|
||||
|
||||
public override void OnMovieDelete(MovieDeleteMessage deleteMessage)
|
||||
{
|
||||
if (deleteMessage.DeletedFiles)
|
||||
{
|
||||
_traktService.RemoveMovieFromCollection(Settings, deleteMessage.Movie);
|
||||
}
|
||||
RefreshTokenIfNecessary();
|
||||
RemoveMovieFromCollection(Settings, deleteMessage.Movie);
|
||||
}
|
||||
|
||||
public override ValidationResult Test()
|
||||
{
|
||||
var failures = new List<ValidationFailure>();
|
||||
|
||||
failures.AddIfNotNull(_traktService.Test(Settings));
|
||||
RefreshTokenIfNecessary();
|
||||
|
||||
try
|
||||
{
|
||||
_proxy.GetUserName(Settings.AccessToken);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
_logger.Error(ex, "Access Token is invalid: " + ex.Message);
|
||||
|
||||
failures.Add(new ValidationFailure("Token", "Access Token is invalid"));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test message: " + ex.Message);
|
||||
|
||||
failures.Add(new ValidationFailure("Token", "Unable to send test message"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test message: " + ex.Message);
|
||||
|
||||
failures.Add(new ValidationFailure("", "Unable to send test message"));
|
||||
}
|
||||
|
||||
return new ValidationResult(failures);
|
||||
}
|
||||
@@ -55,7 +87,7 @@ namespace NzbDrone.Core.Notifications.Trakt
|
||||
{
|
||||
if (action == "startOAuth")
|
||||
{
|
||||
var request = _traktService.GetOAuthRequest(query["callbackUrl"]);
|
||||
var request = _proxy.GetOAuthRequest(query["callbackUrl"]);
|
||||
|
||||
return new
|
||||
{
|
||||
@@ -69,14 +101,22 @@ namespace NzbDrone.Core.Notifications.Trakt
|
||||
accessToken = query["access_token"],
|
||||
expires = DateTime.UtcNow.AddSeconds(int.Parse(query["expires_in"])),
|
||||
refreshToken = query["refresh_token"],
|
||||
authUser = _traktService.GetUserName(query["access_token"])
|
||||
authUser = _proxy.GetUserName(query["access_token"])
|
||||
};
|
||||
}
|
||||
|
||||
return new { };
|
||||
}
|
||||
|
||||
public void RefreshToken()
|
||||
private void RefreshTokenIfNecessary()
|
||||
{
|
||||
if (Settings.Expires < DateTime.UtcNow.AddMinutes(5))
|
||||
{
|
||||
RefreshToken();
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshToken()
|
||||
{
|
||||
_logger.Trace("Refreshing Token");
|
||||
|
||||
@@ -84,11 +124,12 @@ namespace NzbDrone.Core.Notifications.Trakt
|
||||
|
||||
try
|
||||
{
|
||||
var response = _traktService.RefreshAuthToken(Settings.RefreshToken);
|
||||
var response = _proxy.RefreshAuthToken(Settings.RefreshToken);
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
var token = response;
|
||||
|
||||
Settings.AccessToken = token.AccessToken;
|
||||
Settings.Expires = DateTime.UtcNow.AddSeconds(token.ExpiresIn);
|
||||
Settings.RefreshToken = token.RefreshToken ?? Settings.RefreshToken;
|
||||
@@ -99,10 +140,193 @@ namespace NzbDrone.Core.Notifications.Trakt
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (HttpException)
|
||||
catch (HttpException ex)
|
||||
{
|
||||
_logger.Warn($"Error refreshing trakt access token");
|
||||
_logger.Warn(ex, "Error refreshing trakt access token");
|
||||
}
|
||||
}
|
||||
|
||||
private void AddMovieToCollection(TraktSettings settings, Movie movie, MovieFile movieFile)
|
||||
{
|
||||
var payload = new TraktCollectMoviesResource
|
||||
{
|
||||
Movies = new List<TraktCollectMovie>()
|
||||
};
|
||||
|
||||
var traktResolution = MapResolution(movieFile.Quality.Quality.Resolution, movieFile.MediaInfo?.ScanType);
|
||||
var mediaType = MapMediaType(movieFile.Quality.Quality.Source);
|
||||
var audio = MapAudio(movieFile);
|
||||
var audioChannels = MapAudioChannels(movieFile);
|
||||
|
||||
payload.Movies.Add(new TraktCollectMovie
|
||||
{
|
||||
Title = movie.Title,
|
||||
Year = movie.Year,
|
||||
CollectedAt = DateTime.Now,
|
||||
Resolution = traktResolution,
|
||||
MediaType = mediaType,
|
||||
AudioChannels = audioChannels,
|
||||
Audio = audio,
|
||||
Ids = new TraktMovieIdsResource
|
||||
{
|
||||
Tmdb = movie.MovieMetadata.Value.TmdbId,
|
||||
Imdb = movie.MovieMetadata.Value.ImdbId ?? "",
|
||||
}
|
||||
});
|
||||
|
||||
_proxy.AddToCollection(payload, settings.AccessToken);
|
||||
}
|
||||
|
||||
private void RemoveMovieFromCollection(TraktSettings settings, Movie movie)
|
||||
{
|
||||
var payload = new TraktCollectMoviesResource
|
||||
{
|
||||
Movies = new List<TraktCollectMovie>()
|
||||
};
|
||||
|
||||
payload.Movies.Add(new TraktCollectMovie
|
||||
{
|
||||
Title = movie.Title,
|
||||
Year = movie.Year,
|
||||
Ids = new TraktMovieIdsResource
|
||||
{
|
||||
Tmdb = movie.MovieMetadata.Value.TmdbId,
|
||||
Imdb = movie.MovieMetadata.Value.ImdbId ?? "",
|
||||
}
|
||||
});
|
||||
|
||||
_proxy.RemoveFromCollection(payload, settings.AccessToken);
|
||||
}
|
||||
|
||||
private string MapMediaType(Source source)
|
||||
{
|
||||
var traktSource = string.Empty;
|
||||
|
||||
switch (source)
|
||||
{
|
||||
case Source.BLURAY:
|
||||
traktSource = "bluray";
|
||||
break;
|
||||
case Source.WEBDL:
|
||||
traktSource = "digital";
|
||||
break;
|
||||
case Source.WEBRIP:
|
||||
traktSource = "digital";
|
||||
break;
|
||||
case Source.DVD:
|
||||
traktSource = "dvd";
|
||||
break;
|
||||
case Source.TV:
|
||||
traktSource = "dvd";
|
||||
break;
|
||||
}
|
||||
|
||||
return traktSource;
|
||||
}
|
||||
|
||||
private string MapResolution(int resolution, string scanType)
|
||||
{
|
||||
var traktResolution = string.Empty;
|
||||
|
||||
var scanIdentifier = scanType.IsNotNullOrWhiteSpace() && TraktInterlacedTypes.interlacedTypes.Contains(scanType) ? "i" : "p";
|
||||
|
||||
switch (resolution)
|
||||
{
|
||||
case 2160:
|
||||
traktResolution = "uhd_4k";
|
||||
break;
|
||||
case 1080:
|
||||
traktResolution = $"hd_1080{scanIdentifier}";
|
||||
break;
|
||||
case 720:
|
||||
traktResolution = "hd_720p";
|
||||
break;
|
||||
case 576:
|
||||
traktResolution = $"sd_576{scanIdentifier}";
|
||||
break;
|
||||
case 480:
|
||||
traktResolution = $"sd_480{scanIdentifier}";
|
||||
break;
|
||||
}
|
||||
|
||||
return traktResolution;
|
||||
}
|
||||
|
||||
private string MapAudio(MovieFile movieFile)
|
||||
{
|
||||
var traktAudioFormat = string.Empty;
|
||||
|
||||
var audioCodec = movieFile.MediaInfo != null ? MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, movieFile.SceneName) : string.Empty;
|
||||
|
||||
switch (audioCodec)
|
||||
{
|
||||
case "AC3":
|
||||
traktAudioFormat = "dolby_digital";
|
||||
break;
|
||||
case "EAC3":
|
||||
traktAudioFormat = "dolby_digital_plus";
|
||||
break;
|
||||
case "TrueHD":
|
||||
traktAudioFormat = "dolby_truehd";
|
||||
break;
|
||||
case "EAC3 Atmos":
|
||||
traktAudioFormat = "dolby_digital_plus_atmos";
|
||||
break;
|
||||
case "TrueHD Atmos":
|
||||
traktAudioFormat = "dolby_atmos";
|
||||
break;
|
||||
case "DTS":
|
||||
case "DTS-ES":
|
||||
traktAudioFormat = "dts";
|
||||
break;
|
||||
case "DTS-HD MA":
|
||||
traktAudioFormat = "dts_ma";
|
||||
break;
|
||||
case "DTS-HD HRA":
|
||||
traktAudioFormat = "dts_hr";
|
||||
break;
|
||||
case "DTS-X":
|
||||
traktAudioFormat = "dts_x";
|
||||
break;
|
||||
case "MP3":
|
||||
traktAudioFormat = "mp3";
|
||||
break;
|
||||
case "MP2":
|
||||
traktAudioFormat = "mp2";
|
||||
break;
|
||||
case "Vorbis":
|
||||
traktAudioFormat = "ogg";
|
||||
break;
|
||||
case "WMA":
|
||||
traktAudioFormat = "wma";
|
||||
break;
|
||||
case "AAC":
|
||||
traktAudioFormat = "aac";
|
||||
break;
|
||||
case "PCM":
|
||||
traktAudioFormat = "lpcm";
|
||||
break;
|
||||
case "FLAC":
|
||||
traktAudioFormat = "flac";
|
||||
break;
|
||||
case "Opus":
|
||||
traktAudioFormat = "ogg_opus";
|
||||
break;
|
||||
}
|
||||
|
||||
return traktAudioFormat;
|
||||
}
|
||||
|
||||
private string MapAudioChannels(MovieFile movieFile)
|
||||
{
|
||||
var audioChannels = movieFile.MediaInfo != null ? MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo).ToString("0.0") : string.Empty;
|
||||
|
||||
if (audioChannels == "0.0")
|
||||
{
|
||||
audioChannels = string.Empty;
|
||||
}
|
||||
|
||||
return audioChannels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Trakt
|
||||
{
|
||||
public static class TraktInterlacedTypes
|
||||
{
|
||||
private static HashSet<string> _interlacedTypes;
|
||||
|
||||
static TraktInterlacedTypes()
|
||||
{
|
||||
_interlacedTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"Interlaced", "MBAFF", "PAFF"
|
||||
};
|
||||
}
|
||||
|
||||
public static HashSet<string> interlacedTypes => _interlacedTypes;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Notifications.Trakt
|
||||
TraktAuthRefreshResource RefreshAuthToken(string refreshToken);
|
||||
void AddToCollection(TraktCollectMoviesResource payload, string accessToken);
|
||||
void RemoveFromCollection(TraktCollectMoviesResource payload, string accessToken);
|
||||
HttpRequest BuildTraktRequest(string resource, HttpMethod method, string accessToken);
|
||||
HttpRequest BuildRequest(string resource, HttpMethod method, string accessToken);
|
||||
}
|
||||
|
||||
public class TraktProxy : ITraktProxy
|
||||
@@ -36,59 +36,30 @@ namespace NzbDrone.Core.Notifications.Trakt
|
||||
|
||||
public void AddToCollection(TraktCollectMoviesResource payload, string accessToken)
|
||||
{
|
||||
var request = BuildTraktRequest("sync/collection", HttpMethod.Post, accessToken);
|
||||
var request = BuildRequest("sync/collection", HttpMethod.Post, accessToken);
|
||||
|
||||
request.Headers.ContentType = "application/json";
|
||||
request.SetContent(payload.ToJson());
|
||||
|
||||
try
|
||||
{
|
||||
_httpClient.Execute(request);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to post payload {0}", payload);
|
||||
throw new TraktException("Unable to post payload", ex);
|
||||
}
|
||||
MakeRequest(request);
|
||||
}
|
||||
|
||||
public void RemoveFromCollection(TraktCollectMoviesResource payload, string accessToken)
|
||||
{
|
||||
var request = BuildTraktRequest("sync/collection/remove", HttpMethod.Post, accessToken);
|
||||
var request = BuildRequest("sync/collection/remove", HttpMethod.Post, accessToken);
|
||||
|
||||
request.Headers.ContentType = "application/json";
|
||||
request.SetContent(payload.ToJson());
|
||||
|
||||
try
|
||||
{
|
||||
_httpClient.Execute(request);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to post payload {0}", payload);
|
||||
throw new TraktException("Unable to post payload", ex);
|
||||
}
|
||||
MakeRequest(request);
|
||||
}
|
||||
|
||||
public string GetUserName(string accessToken)
|
||||
{
|
||||
var request = BuildTraktRequest("users/settings", HttpMethod.Get, accessToken);
|
||||
var request = BuildRequest("users/settings", HttpMethod.Get, accessToken);
|
||||
var response = _httpClient.Get<TraktUserSettingsResource>(request);
|
||||
|
||||
try
|
||||
{
|
||||
var response = _httpClient.Get<TraktUserSettingsResource>(request);
|
||||
|
||||
if (response != null && response.Resource != null)
|
||||
{
|
||||
return response.Resource.User.Ids.Slug;
|
||||
}
|
||||
}
|
||||
catch (HttpException)
|
||||
{
|
||||
_logger.Warn($"Error refreshing trakt access token");
|
||||
}
|
||||
|
||||
return null;
|
||||
return response?.Resource?.User?.Ids?.Slug;
|
||||
}
|
||||
|
||||
public HttpRequest GetOAuthRequest(string callbackUrl)
|
||||
@@ -110,7 +81,7 @@ namespace NzbDrone.Core.Notifications.Trakt
|
||||
return _httpClient.Get<TraktAuthRefreshResource>(request)?.Resource ?? null;
|
||||
}
|
||||
|
||||
public HttpRequest BuildTraktRequest(string resource, HttpMethod method, string accessToken)
|
||||
public HttpRequest BuildRequest(string resource, HttpMethod method, string accessToken)
|
||||
{
|
||||
var request = new HttpRequestBuilder(URL).Resource(resource).Build();
|
||||
|
||||
@@ -127,5 +98,17 @@ namespace NzbDrone.Core.Notifications.Trakt
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private void MakeRequest(HttpRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
_httpClient.Execute(request);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
throw new TraktException("Unable to send payload", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,263 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Notifications.Trakt.Resource;
|
||||
using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Trakt
|
||||
{
|
||||
public interface ITraktService
|
||||
{
|
||||
HttpRequest GetOAuthRequest(string callbackUrl);
|
||||
TraktAuthRefreshResource RefreshAuthToken(string refreshToken);
|
||||
void AddMovieToCollection(TraktSettings settings, Movie movie, MovieFile movieFile);
|
||||
void RemoveMovieFromCollection(TraktSettings settings, Movie movie);
|
||||
string GetUserName(string accessToken);
|
||||
ValidationFailure Test(TraktSettings settings);
|
||||
}
|
||||
|
||||
public class TraktService : ITraktService
|
||||
{
|
||||
private readonly ITraktProxy _proxy;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public TraktService(ITraktProxy proxy,
|
||||
Logger logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string GetUserName(string accessToken)
|
||||
{
|
||||
return _proxy.GetUserName(accessToken);
|
||||
}
|
||||
|
||||
public HttpRequest GetOAuthRequest(string callbackUrl)
|
||||
{
|
||||
return _proxy.GetOAuthRequest(callbackUrl);
|
||||
}
|
||||
|
||||
public TraktAuthRefreshResource RefreshAuthToken(string refreshToken)
|
||||
{
|
||||
return _proxy.RefreshAuthToken(refreshToken);
|
||||
}
|
||||
|
||||
public ValidationFailure Test(TraktSettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
GetUserName(settings.AccessToken);
|
||||
return null;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
_logger.Error(ex, "Access Token is invalid: " + ex.Message);
|
||||
return new ValidationFailure("Token", "Access Token is invalid");
|
||||
}
|
||||
|
||||
_logger.Error(ex, "Unable to send test message: " + ex.Message);
|
||||
return new ValidationFailure("Token", "Unable to send test message");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test message: " + ex.Message);
|
||||
return new ValidationFailure("", "Unable to send test message");
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveMovieFromCollection(TraktSettings settings, Movie movie)
|
||||
{
|
||||
var payload = new TraktCollectMoviesResource
|
||||
{
|
||||
Movies = new List<TraktCollectMovie>()
|
||||
};
|
||||
|
||||
payload.Movies.Add(new TraktCollectMovie
|
||||
{
|
||||
Title = movie.Title,
|
||||
Year = movie.Year,
|
||||
Ids = new TraktMovieIdsResource
|
||||
{
|
||||
Tmdb = movie.MovieMetadata.Value.TmdbId,
|
||||
Imdb = movie.MovieMetadata.Value.ImdbId ?? "",
|
||||
}
|
||||
});
|
||||
|
||||
_proxy.RemoveFromCollection(payload, settings.AccessToken);
|
||||
}
|
||||
|
||||
public void AddMovieToCollection(TraktSettings settings, Movie movie, MovieFile movieFile)
|
||||
{
|
||||
var payload = new TraktCollectMoviesResource
|
||||
{
|
||||
Movies = new List<TraktCollectMovie>()
|
||||
};
|
||||
|
||||
var traktResolution = MapResolution(movieFile.Quality.Quality.Resolution, movieFile.MediaInfo?.ScanType);
|
||||
var mediaType = MapMediaType(movieFile.Quality.Quality.Source);
|
||||
var audio = MapAudio(movieFile);
|
||||
var audioChannels = MapAudioChannels(movieFile, audio);
|
||||
|
||||
payload.Movies.Add(new TraktCollectMovie
|
||||
{
|
||||
Title = movie.Title,
|
||||
Year = movie.Year,
|
||||
CollectedAt = DateTime.Now,
|
||||
Resolution = traktResolution,
|
||||
MediaType = mediaType,
|
||||
AudioChannels = audioChannels,
|
||||
Audio = audio,
|
||||
Ids = new TraktMovieIdsResource
|
||||
{
|
||||
Tmdb = movie.MovieMetadata.Value.TmdbId,
|
||||
Imdb = movie.MovieMetadata.Value.ImdbId ?? "",
|
||||
}
|
||||
});
|
||||
|
||||
_proxy.AddToCollection(payload, settings.AccessToken);
|
||||
}
|
||||
|
||||
private string MapMediaType(Source source)
|
||||
{
|
||||
var traktSource = string.Empty;
|
||||
|
||||
switch (source)
|
||||
{
|
||||
case Source.BLURAY:
|
||||
traktSource = "bluray";
|
||||
break;
|
||||
case Source.WEBDL:
|
||||
traktSource = "digital";
|
||||
break;
|
||||
case Source.WEBRIP:
|
||||
traktSource = "digital";
|
||||
break;
|
||||
case Source.DVD:
|
||||
traktSource = "dvd";
|
||||
break;
|
||||
case Source.TV:
|
||||
traktSource = "dvd";
|
||||
break;
|
||||
}
|
||||
|
||||
return traktSource;
|
||||
}
|
||||
|
||||
private string MapResolution(int resolution, string scanType)
|
||||
{
|
||||
var traktResolution = string.Empty;
|
||||
var interlacedTypes = new string[] { "Interlaced", "MBAFF", "PAFF" };
|
||||
|
||||
var scanIdentifier = scanType.IsNotNullOrWhiteSpace() && interlacedTypes.Contains(scanType) ? "i" : "p";
|
||||
|
||||
switch (resolution)
|
||||
{
|
||||
case 2160:
|
||||
traktResolution = "uhd_4k";
|
||||
break;
|
||||
case 1080:
|
||||
traktResolution = string.Format("hd_1080{0}", scanIdentifier);
|
||||
break;
|
||||
case 720:
|
||||
traktResolution = "hd_720p";
|
||||
break;
|
||||
case 576:
|
||||
traktResolution = string.Format("sd_576{0}", scanIdentifier);
|
||||
break;
|
||||
case 480:
|
||||
traktResolution = string.Format("sd_480{0}", scanIdentifier);
|
||||
break;
|
||||
}
|
||||
|
||||
return traktResolution;
|
||||
}
|
||||
|
||||
private string MapAudio(MovieFile movieFile)
|
||||
{
|
||||
var traktAudioFormat = string.Empty;
|
||||
|
||||
var audioCodec = movieFile.MediaInfo != null ? MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, movieFile.SceneName) : string.Empty;
|
||||
|
||||
switch (audioCodec)
|
||||
{
|
||||
case "AC3":
|
||||
traktAudioFormat = "dolby_digital";
|
||||
break;
|
||||
case "EAC3":
|
||||
traktAudioFormat = "dolby_digital_plus";
|
||||
break;
|
||||
case "TrueHD":
|
||||
traktAudioFormat = "dolby_truehd";
|
||||
break;
|
||||
case "EAC3 Atmos":
|
||||
traktAudioFormat = "dolby_digital_plus_atmos";
|
||||
break;
|
||||
case "TrueHD Atmos":
|
||||
traktAudioFormat = "dolby_atmos";
|
||||
break;
|
||||
case "DTS":
|
||||
case "DTS-ES":
|
||||
traktAudioFormat = "dts";
|
||||
break;
|
||||
case "DTS-HD MA":
|
||||
traktAudioFormat = "dts_ma";
|
||||
break;
|
||||
case "DTS-HD HRA":
|
||||
traktAudioFormat = "dts_hr";
|
||||
break;
|
||||
case "DTS-X":
|
||||
traktAudioFormat = "dts_x";
|
||||
break;
|
||||
case "MP3":
|
||||
traktAudioFormat = "mp3";
|
||||
break;
|
||||
case "MP2":
|
||||
traktAudioFormat = "mp2";
|
||||
break;
|
||||
case "Vorbis":
|
||||
traktAudioFormat = "ogg";
|
||||
break;
|
||||
case "WMA":
|
||||
traktAudioFormat = "wma";
|
||||
break;
|
||||
case "AAC":
|
||||
traktAudioFormat = "aac";
|
||||
break;
|
||||
case "PCM":
|
||||
traktAudioFormat = "lpcm";
|
||||
break;
|
||||
case "FLAC":
|
||||
traktAudioFormat = "flac";
|
||||
break;
|
||||
case "Opus":
|
||||
traktAudioFormat = "ogg_opus";
|
||||
break;
|
||||
}
|
||||
|
||||
return traktAudioFormat;
|
||||
}
|
||||
|
||||
private string MapAudioChannels(MovieFile movieFile, string audioFormat)
|
||||
{
|
||||
var audioChannels = movieFile.MediaInfo != null ? MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo).ToString("0.0") : string.Empty;
|
||||
|
||||
if (audioChannels == "0.0")
|
||||
{
|
||||
audioChannels = string.Empty;
|
||||
}
|
||||
|
||||
return audioChannels;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user