1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-25 17:35:35 -04:00

Compare commits

...

18 Commits

Author SHA1 Message Date
PearsonFlyer
eb764832ed Fix notifiation in Ntfy on test from Radarr to Sonarr
(cherry picked from commit 1b599c7e765b10ce043cbf6b127143cc737b378a)
2023-01-20 00:24:26 +00:00
Qstick
685a24e476 Fixed: RemotePathMappingCheck Improvements 2023-01-16 22:45:55 -06:00
Qstick
cae4faae61 Fixed: DownloadClientRootFolderCheck Improvements 2023-01-16 22:38:05 -06:00
Qstick
5dac6badf2 Fixed: Ignore movie add errors during collection sync
Fixes #7982
2023-01-11 23:34:21 -06:00
Weblate
5948f56482 Translated using Weblate (Ukrainian) [skip ci]
Currently translated at 100.0% (1156 of 1156 strings)

Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]

Currently translated at 100.0% (1156 of 1156 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (1156 of 1156 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 100.0% (1156 of 1156 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 95.5% (1105 of 1156 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1156 of 1156 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1156 of 1156 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 97.5% (1128 of 1156 strings)

Co-authored-by: AlexR-sf <omg.portal.supp@gmail.com>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: aenron <1414004038@qq.com>
Co-authored-by: andrey4korop <andrey999@i.ua>
Co-authored-by: jjTogo228 <juniorbiam@gmail.com>
Co-authored-by: verhese <sean.verheyen1@telenet.be>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2023-01-08 22:28:28 -06:00
Mark McDowall
98ddd0386b Fixed: Trakt connection auth tokens not being refreshed
Closes #7873

(cherry picked from commit d09e5d8eb4097cbba1ee0a668dbb27f941cc4f68)
2023-01-08 22:22:01 -06:00
Mark McDowall
2947b244e4 Fixed: Quality cutoff updating in UI when adding/removing qualities
Closes #7879

(cherry picked from commit fea66cb7bccc7e94523614db38b157fa38f55ea5)
2023-01-08 21:43:55 -06:00
Mark McDowall
72552b8084 New: Option to include movie image for Gotify notifications
Closes #7920

(cherry picked from commit e57e68c97a9d24f8344623ac8f731c2da220686b)
2023-01-08 21:41:08 -06:00
Qstick
09642444d7 Switch Trakt to STJson
Fixes #7913
2023-01-08 21:11:59 -06:00
Qstick
d1080b825c Fixed: Truncate custom format card tags
Fixes #7725
Fixes #7973

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2023-01-08 21:00:43 -06:00
Qstick
001421de10 New: Improve messaging for rejected quality upgrades
Fixes #7461

Co-Authored-By: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2023-01-08 20:52:47 -06:00
Qstick
bab9b8b36a Add Volta node config
Fixes #7600
Fixes #7747

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2023-01-08 20:52:47 -06:00
Qstick
0fb738aa2e Fixed: Kodi Metadata Subtitle Language
Fixes #7962

Co-Authored-By: Stevie Robinson <stevie.robinson@gmail.com>
2023-01-08 20:52:47 -06:00
Mark McDowall
4963920a46 New: Added health check warning if SABnzbd sorting is enabled
(cherry picked from commit 61fa1e5e3f00072f0d5f59cc883fac74fe12ee9d)
2023-01-08 20:38:26 -06:00
Qstick
f0d10fe1cd Fixed: Correct messaging when release is not upgrade
Fixes #7963
2023-01-08 20:24:13 -06:00
James Hu
386b33b624 New: Include movie title and year when logging report
* Include movie title and year when logging report

* Change verbage

Co-authored-by: Robin Dadswell <19610103+RobinDadswell@users.noreply.github.com>
2023-01-05 21:07:59 +00:00
Mark McDowall
98201508f2 New: Description for indexer RSS setting
(cherry picked from commit 396406b2174c4876057175e7537a4718eee2abca)
2023-01-04 10:19:57 +00:00
Qstick
9723c569a1 Bump version to 4.4.0 2023-01-03 18:48:07 -06:00
49 changed files with 1020 additions and 423 deletions

View File

@@ -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)'

View File

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

View File

@@ -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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 =>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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");

View File

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

View File

@@ -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");

View File

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

View File

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

View File

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

View File

@@ -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)

View File

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

View File

@@ -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)",

View File

@@ -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."
}

View File

@@ -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.",

View File

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

View File

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

View File

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

View File

@@ -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": "Прокрутите фильмы"
}

View File

@@ -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": "Захопити вибране"
}

View File

@@ -1148,10 +1148,11 @@
"BindAddressHelpText": "有效的 IP 地址localhost或以'*'代表所有地址",
"PreferredProtocol": "首选协议",
"AreYouSureYouWantToResetQualityDefinitions": "确定要重置质量定义吗?",
"SettingsThemeHelpText": "更改程序界面主题,“自动”主题将根据您的操作系统主题来设置浅色或深色模式。",
"SettingsThemeHelpText": "更改程序界面主题,“自动”主题将根据您的操作系统主题来设置浅色或深色模式。灵感来自Theme.Park",
"ResetDefinitions": "重置定义",
"ResetQualityDefinitions": "重置质量定义",
"ResetTitles": "重置标题",
"ResetTitlesHelpText": "重置定义标题与参数值",
"SettingsTheme": "主题"
"SettingsTheme": "主题",
"RSSHelpText": "当Radarr定期通过RSS同步查找发布时使用"
}

View File

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

View File

@@ -140,7 +140,7 @@ namespace NzbDrone.Core.Movies
AddMethod = AddMovieMethod.Collection
},
Monitored = true
}).ToList());
}).ToList(), true);
}
}
}

View File

@@ -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![](https://raw.githubusercontent.com/Radarr/Radarr/develop/Logo/128.png)");
}
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![]({poster})");
}
}
var payload = new GotifyMessage
{
Title = title,
Message = sb.ToString(),
Priority = Settings.Priority
};
payload.SetContentType(isMarkdown);
_proxy.SendNotification(payload, Settings);
}
}
}

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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