mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-15 15:54:47 -04:00
Compare commits
54 Commits
collection
...
sonarr-pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb764832ed | ||
|
|
685a24e476 | ||
|
|
cae4faae61 | ||
|
|
5dac6badf2 | ||
|
|
5948f56482 | ||
|
|
98ddd0386b | ||
|
|
2947b244e4 | ||
|
|
72552b8084 | ||
|
|
09642444d7 | ||
|
|
d1080b825c | ||
|
|
001421de10 | ||
|
|
bab9b8b36a | ||
|
|
0fb738aa2e | ||
|
|
4963920a46 | ||
|
|
f0d10fe1cd | ||
|
|
386b33b624 | ||
|
|
98201508f2 | ||
|
|
9723c569a1 | ||
|
|
0584d7676c | ||
|
|
09c42530ec | ||
|
|
0697d694e0 | ||
|
|
e085f6af8a | ||
|
|
7feda1c446 | ||
|
|
e1f83c205d | ||
|
|
db00edd266 | ||
|
|
d699f61f5d | ||
|
|
dc1b478f2c | ||
|
|
0ca665c903 | ||
|
|
111c6a743f | ||
|
|
d3517532a4 | ||
|
|
5790ebc558 | ||
|
|
c11f72c098 | ||
|
|
3617bef54b | ||
|
|
a5fb01f1e6 | ||
|
|
fa6acb7497 | ||
|
|
904259df92 | ||
|
|
65c316bd6d | ||
|
|
3b46a08606 | ||
|
|
6ad49373d4 | ||
|
|
2a1f57c085 | ||
|
|
9d9065fbcd | ||
|
|
694940452c | ||
|
|
f5d6a79998 | ||
|
|
4cc98a10a0 | ||
|
|
1751bd1a58 | ||
|
|
c0caf65b69 | ||
|
|
cd889872de | ||
|
|
6366e335bc | ||
|
|
41f10d098e | ||
|
|
b2c1698097 | ||
|
|
ed20487f30 | ||
|
|
d1235adfc4 | ||
|
|
561993e30c | ||
|
|
14f8f89634 |
@@ -76,6 +76,15 @@ Thank you to [<img src="/Logo/jetbrains.svg" alt="JetBrains" width="32"> JetBrai
|
||||
* [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
|
||||
* [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
|
||||
|
||||
## DigitalOcean
|
||||
|
||||
This project is also supported by DigitalOcean
|
||||
<p>
|
||||
<a href="https://www.digitalocean.com/">
|
||||
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
### License
|
||||
|
||||
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
||||
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '4.3.1'
|
||||
majorVersion: '4.4.0'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
|
||||
@@ -75,13 +75,23 @@ class Queue extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextState = {};
|
||||
|
||||
if (prevProps.items !== items) {
|
||||
nextState.items = items;
|
||||
}
|
||||
|
||||
const selectedIds = this.getSelectedIds();
|
||||
const isPendingSelected = _.some(this.props.items, (item) => {
|
||||
return selectedIds.indexOf(item.id) > -1 && item.status === 'delay';
|
||||
});
|
||||
|
||||
if (isPendingSelected !== this.state.isPendingSelected) {
|
||||
this.setState({ isPendingSelected });
|
||||
nextState.isPendingSelected = isPendingSelected;
|
||||
}
|
||||
|
||||
if (!_.isEmpty(nextState)) {
|
||||
this.setState(nextState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,26 +224,29 @@ class Queue extends Component {
|
||||
|
||||
<PageContentBody>
|
||||
{
|
||||
isRefreshing && !isAllPopulated &&
|
||||
<LoadingIndicator />
|
||||
isRefreshing && !isAllPopulated ?
|
||||
<LoadingIndicator /> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!isRefreshing && hasError &&
|
||||
!isRefreshing && hasError ?
|
||||
<div>
|
||||
{translate('FailedToLoadQueue')}
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
isAllPopulated && !hasError && !items.length &&
|
||||
isAllPopulated && !hasError && !items.length ?
|
||||
<div>
|
||||
{translate('QueueIsEmpty')}
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
isAllPopulated && !hasError && !!items.length &&
|
||||
isAllPopulated && !hasError && !!items.length ?
|
||||
<div>
|
||||
<Table
|
||||
columns={columns}
|
||||
@@ -268,7 +281,8 @@ class Queue extends Component {
|
||||
isFetching={isRefreshing}
|
||||
{...otherProps}
|
||||
/>
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
</PageContentBody>
|
||||
|
||||
|
||||
@@ -225,13 +225,19 @@ class ImportMovieFooter extends Component {
|
||||
body={
|
||||
<ul>
|
||||
{
|
||||
importError.responseJSON.map((error, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
{error.errorMessage}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
Array.isArray(importError.responseJSON) ?
|
||||
importError.responseJSON.map((error, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
{error.errorMessage}
|
||||
</li>
|
||||
);
|
||||
}) :
|
||||
<li>
|
||||
{
|
||||
JSON.stringify(importError.responseJSON)
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@@ -152,13 +152,19 @@ class ImportMovieSelectFolder extends Component {
|
||||
|
||||
<ul>
|
||||
{
|
||||
saveError.responseJSON.map((e, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
{e.errorMessage}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
Array.isArray(saveError.responseJSON) ?
|
||||
saveError.responseJSON.map((e, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
{e.errorMessage}
|
||||
</li>
|
||||
);
|
||||
}) :
|
||||
<li>
|
||||
{
|
||||
JSON.stringify(saveError.responseJSON)
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</Alert> :
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import * as dark from './dark';
|
||||
import * as light from './light';
|
||||
|
||||
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const auto = defaultDark ? { ...dark } : { ...light };
|
||||
|
||||
export default {
|
||||
auto,
|
||||
light,
|
||||
dark
|
||||
};
|
||||
|
||||
@@ -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,25 @@
|
||||
using System.Globalization;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Test.ExtensionTests.StringExtensionTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class IsValidIPAddressFixture
|
||||
{
|
||||
[TestCase("192.168.0.1")]
|
||||
[TestCase("::1")]
|
||||
[TestCase("2001:db8:4006:812::200e")]
|
||||
public void should_validate_ip_address(string input)
|
||||
{
|
||||
input.IsValidIpAddress().Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("sonarr.tv")]
|
||||
public void should_not_parse_non_ip_address(string input)
|
||||
{
|
||||
input.IsValidIpAddress().Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using FluentAssertions;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Test.Common;
|
||||
@@ -10,6 +10,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[TestCase("abc://my_host.com:8080/root/api/")]
|
||||
[TestCase("abc://my_host.com:8080//root/api/")]
|
||||
[TestCase("abc://my_host.com:8080/root//api/")]
|
||||
[TestCase("abc://[::1]:8080/root//api/")]
|
||||
public void should_parse(string uri)
|
||||
{
|
||||
var newUri = new HttpUri(uri);
|
||||
|
||||
@@ -7,34 +7,50 @@ namespace NzbDrone.Common.Extensions
|
||||
{
|
||||
public static bool IsLocalAddress(this IPAddress ipAddress)
|
||||
{
|
||||
if (ipAddress.IsIPv6LinkLocal)
|
||||
// Map back to IPv4 if mapped to IPv6, for example "::ffff:1.2.3.4" to "1.2.3.4".
|
||||
if (ipAddress.IsIPv4MappedToIPv6)
|
||||
{
|
||||
return true;
|
||||
ipAddress = ipAddress.MapToIPv4();
|
||||
}
|
||||
|
||||
// Checks loopback ranges for both IPv4 and IPv6.
|
||||
if (IPAddress.IsLoopback(ipAddress))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// IPv4
|
||||
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
byte[] bytes = ipAddress.GetAddressBytes();
|
||||
switch (bytes[0])
|
||||
{
|
||||
case 10:
|
||||
case 127:
|
||||
return true;
|
||||
case 172:
|
||||
return bytes[1] < 32 && bytes[1] >= 16;
|
||||
case 192:
|
||||
return bytes[1] == 168;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return IsLocalIPv4(ipAddress.GetAddressBytes());
|
||||
}
|
||||
|
||||
// IPv6
|
||||
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
return ipAddress.IsIPv6LinkLocal ||
|
||||
ipAddress.IsIPv6UniqueLocal ||
|
||||
ipAddress.IsIPv6SiteLocal;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsLocalIPv4(byte[] ipv4Bytes)
|
||||
{
|
||||
// Link local (no IP assigned by DHCP): 169.254.0.0 to 169.254.255.255 (169.254.0.0/16)
|
||||
bool IsLinkLocal() => ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;
|
||||
|
||||
// Class A private range: 10.0.0.0 – 10.255.255.255 (10.0.0.0/8)
|
||||
bool IsClassA() => ipv4Bytes[0] == 10;
|
||||
|
||||
// Class B private range: 172.16.0.0 – 172.31.255.255 (172.16.0.0/12)
|
||||
bool IsClassB() => ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;
|
||||
|
||||
// Class C private range: 192.168.0.0 – 192.168.255.255 (192.168.0.0/16)
|
||||
bool IsClassC() => ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;
|
||||
|
||||
return IsLinkLocal() || IsClassA() || IsClassC() || IsClassB();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
@@ -192,5 +194,30 @@ namespace NzbDrone.Common.Extensions
|
||||
.Replace("'", "%27")
|
||||
.Replace("%7E", "~");
|
||||
}
|
||||
|
||||
public static bool IsValidIpAddress(this string value)
|
||||
{
|
||||
if (!IPAddress.TryParse(value, out var parsedAddress))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsedAddress.Equals(IPAddress.Parse("255.255.255.255")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsedAddress.IsIPv6Multicast)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return parsedAddress.AddressFamily == AddressFamily.InterNetwork || parsedAddress.AddressFamily == AddressFamily.InterNetworkV6;
|
||||
}
|
||||
|
||||
public static string ToUrlHost(this string input)
|
||||
{
|
||||
return input.Contains(":") ? $"[{input}]" : input;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Common.Http
|
||||
{
|
||||
public class HttpUri : IEquatable<HttpUri>
|
||||
{
|
||||
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+)(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+|\[[[A-F0-9:]+\])(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private readonly string _uri;
|
||||
public string FullUri => _uri;
|
||||
@@ -70,6 +70,8 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
private void Parse()
|
||||
{
|
||||
var parseSuccess = Uri.TryCreate(_uri, UriKind.RelativeOrAbsolute, out var uri);
|
||||
|
||||
var match = RegexUri.Match(_uri);
|
||||
|
||||
var scheme = match.Groups["scheme"];
|
||||
@@ -79,7 +81,7 @@ namespace NzbDrone.Common.Http
|
||||
var query = match.Groups["query"];
|
||||
var fragment = match.Groups["fragment"];
|
||||
|
||||
if (!match.Success || (scheme.Success && !host.Success && path.Success))
|
||||
if (!parseSuccess || (scheme.Success && !host.Success && path.Success))
|
||||
{
|
||||
throw new ArgumentException("Uri didn't match expected pattern: " + _uri);
|
||||
}
|
||||
|
||||
@@ -11,26 +11,41 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
{
|
||||
try
|
||||
{
|
||||
sentryEvent.Message.Message = CleanseLogMessage.Cleanse(sentryEvent.Message.Message);
|
||||
if (sentryEvent.Message is not null)
|
||||
{
|
||||
sentryEvent.Message.Formatted = CleanseLogMessage.Cleanse(sentryEvent.Message.Formatted);
|
||||
sentryEvent.Message.Message = CleanseLogMessage.Cleanse(sentryEvent.Message.Message);
|
||||
sentryEvent.Message.Params = sentryEvent.Message.Params?.Select(x => CleanseLogMessage.Cleanse(x switch
|
||||
{
|
||||
string str => str,
|
||||
_ => x.ToString()
|
||||
})).ToList();
|
||||
}
|
||||
|
||||
if (sentryEvent.Fingerprint != null)
|
||||
if (sentryEvent.Fingerprint.Any())
|
||||
{
|
||||
var fingerprint = sentryEvent.Fingerprint.Select(x => CleanseLogMessage.Cleanse(x)).ToList();
|
||||
sentryEvent.SetFingerprint(fingerprint);
|
||||
}
|
||||
|
||||
if (sentryEvent.Extra != null)
|
||||
if (sentryEvent.Extra.Any())
|
||||
{
|
||||
var extras = sentryEvent.Extra.ToDictionary(x => x.Key, y => (object)CleanseLogMessage.Cleanse((string)y.Value));
|
||||
var extras = sentryEvent.Extra.ToDictionary(x => x.Key, y => (object)CleanseLogMessage.Cleanse(y.Value as string));
|
||||
sentryEvent.SetExtras(extras);
|
||||
}
|
||||
|
||||
foreach (var exception in sentryEvent.SentryExceptions)
|
||||
if (sentryEvent.SentryExceptions is not null)
|
||||
{
|
||||
exception.Value = CleanseLogMessage.Cleanse(exception.Value);
|
||||
foreach (var frame in exception.Stacktrace.Frames)
|
||||
foreach (var exception in sentryEvent.SentryExceptions)
|
||||
{
|
||||
frame.FileName = ShortenPath(frame.FileName);
|
||||
exception.Value = CleanseLogMessage.Cleanse(exception.Value);
|
||||
if (exception.Stacktrace is not null)
|
||||
{
|
||||
foreach (var frame in exception.Stacktrace.Frames)
|
||||
{
|
||||
frame.FileName = ShortenPath(frame.FileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,9 +99,6 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
o.Dsn = dsn;
|
||||
o.AttachStacktrace = true;
|
||||
o.MaxBreadcrumbs = 200;
|
||||
o.SendDefaultPii = false;
|
||||
o.Debug = false;
|
||||
o.DiagnosticLevel = SentryLevel.Debug;
|
||||
o.Release = BuildInfo.Release;
|
||||
o.BeforeSend = x => SentryCleanser.CleanseEvent(x);
|
||||
o.BeforeBreadcrumb = x => SentryCleanser.CleanseBreadcrumb(x);
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
<PackageReference Include="DryIoc.dll" Version="5.3.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
|
||||
<PackageReference Include="Sentry" Version="3.20.1" />
|
||||
<PackageReference Include="Sentry" Version="3.23.1" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.5" />
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.Clients;
|
||||
using NzbDrone.Core.Download.Clients.FreeboxDownload;
|
||||
using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Test.Download.DownloadClientTests.FreeboxDownloadTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TorrentFreeboxDownloadFixture : DownloadClientFixtureBase<TorrentFreeboxDownload>
|
||||
{
|
||||
protected FreeboxDownloadSettings _settings;
|
||||
|
||||
protected FreeboxDownloadConfiguration _downloadConfiguration;
|
||||
|
||||
protected FreeboxDownloadTask _task;
|
||||
|
||||
protected string _defaultDestination = @"/some/path";
|
||||
protected string _encodedDefaultDestination = "L3NvbWUvcGF0aA==";
|
||||
protected string _category = "somecat";
|
||||
protected string _encodedDefaultDestinationAndCategory = "L3NvbWUvcGF0aC9zb21lY2F0";
|
||||
protected string _destinationDirectory = @"/path/to/media";
|
||||
protected string _encodedDestinationDirectory = "L3BhdGgvdG8vbWVkaWE=";
|
||||
protected OsPath _physicalPath = new OsPath("/mnt/sdb1/mydata");
|
||||
protected string _downloadURL => "magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcad53426&dn=download";
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Subject.Definition = new DownloadClientDefinition();
|
||||
|
||||
_settings = new FreeboxDownloadSettings()
|
||||
{
|
||||
Host = "127.0.0.1",
|
||||
Port = 443,
|
||||
ApiUrl = "/api/v1/",
|
||||
AppId = "someid",
|
||||
AppToken = "S0mEv3RY1oN9T0k3n"
|
||||
};
|
||||
|
||||
Subject.Definition.Settings = _settings;
|
||||
|
||||
_downloadConfiguration = new FreeboxDownloadConfiguration()
|
||||
{
|
||||
DownloadDirectory = _encodedDefaultDestination
|
||||
};
|
||||
|
||||
_task = new FreeboxDownloadTask()
|
||||
{
|
||||
Id = "id0",
|
||||
Name = "name",
|
||||
DownloadDirectory = "L3NvbWUvcGF0aA==",
|
||||
InfoHash = "HASH",
|
||||
QueuePosition = 1,
|
||||
Status = FreeboxDownloadTaskStatus.Unknown,
|
||||
Eta = 0,
|
||||
Error = "none",
|
||||
Type = FreeboxDownloadTaskType.Bt.ToString(),
|
||||
IoPriority = FreeboxDownloadTaskIoPriority.Normal.ToString(),
|
||||
StopRatio = 150,
|
||||
PieceLength = 125,
|
||||
CreatedTimestamp = 1665261599,
|
||||
Size = 1000,
|
||||
ReceivedPrct = 0,
|
||||
ReceivedBytes = 0,
|
||||
ReceivedRate = 0,
|
||||
TransmittedPrct = 0,
|
||||
TransmittedBytes = 0,
|
||||
TransmittedRate = 0,
|
||||
};
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0]));
|
||||
}
|
||||
|
||||
protected void GivenCategory()
|
||||
{
|
||||
_settings.Category = _category;
|
||||
}
|
||||
|
||||
protected void GivenDestinationDirectory()
|
||||
{
|
||||
_settings.DestinationDirectory = _destinationDirectory;
|
||||
}
|
||||
|
||||
protected virtual void GivenDownloadConfiguration()
|
||||
{
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Setup(s => s.GetDownloadConfiguration(It.IsAny<FreeboxDownloadSettings>()))
|
||||
.Returns(_downloadConfiguration);
|
||||
}
|
||||
|
||||
protected virtual void GivenTasks(List<FreeboxDownloadTask> torrents)
|
||||
{
|
||||
if (torrents == null)
|
||||
{
|
||||
torrents = new List<FreeboxDownloadTask>();
|
||||
}
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Setup(s => s.GetTasks(It.IsAny<FreeboxDownloadSettings>()))
|
||||
.Returns(torrents);
|
||||
}
|
||||
|
||||
protected void PrepareClientToReturnQueuedItem()
|
||||
{
|
||||
_task.Status = FreeboxDownloadTaskStatus.Queued;
|
||||
|
||||
GivenTasks(new List<FreeboxDownloadTask>
|
||||
{
|
||||
_task
|
||||
});
|
||||
}
|
||||
|
||||
protected void GivenSuccessfulDownload()
|
||||
{
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000]));
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Setup(s => s.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()))
|
||||
.Callback(PrepareClientToReturnQueuedItem);
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Setup(s => s.AddTaskFromFile(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()))
|
||||
.Callback(PrepareClientToReturnQueuedItem);
|
||||
}
|
||||
|
||||
protected override RemoteMovie CreateRemoteMovie()
|
||||
{
|
||||
var movie = base.CreateRemoteMovie();
|
||||
|
||||
movie.Release.DownloadUrl = _downloadURL;
|
||||
|
||||
return movie;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_with_DestinationDirectory_should_force_directory()
|
||||
{
|
||||
GivenDestinationDirectory();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteMovie = CreateRemoteMovie();
|
||||
|
||||
Subject.Download(remoteMovie);
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), _encodedDestinationDirectory, It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_with_Category_should_force_directory()
|
||||
{
|
||||
GivenDownloadConfiguration();
|
||||
GivenCategory();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteMovie = CreateRemoteMovie();
|
||||
|
||||
Subject.Download(remoteMovie);
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), _encodedDefaultDestinationAndCategory, It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_without_DestinationDirectory_and_Category_should_use_default()
|
||||
{
|
||||
GivenDownloadConfiguration();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteMovie = CreateRemoteMovie();
|
||||
|
||||
Subject.Download(remoteMovie);
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), _encodedDefaultDestination, It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[TestCase(false, false)]
|
||||
[TestCase(true, true)]
|
||||
public void Download_should_pause_torrent_as_expected(bool addPausedSetting, bool toBePausedFlag)
|
||||
{
|
||||
_settings.AddPaused = addPausedSetting;
|
||||
|
||||
GivenDownloadConfiguration();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteMovie = CreateRemoteMovie();
|
||||
|
||||
Subject.Download(remoteMovie);
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), toBePausedFlag, It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[TestCase(0, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.First, true)]
|
||||
[TestCase(0, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.First, true)]
|
||||
[TestCase(0, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.Last, false)]
|
||||
[TestCase(0, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.Last, false)]
|
||||
[TestCase(22, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.First, true)]
|
||||
[TestCase(22, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.First, false)]
|
||||
[TestCase(22, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.Last, true)]
|
||||
[TestCase(22, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.Last, false)]
|
||||
public void Download_should_queue_torrent_first_as_expected(int ageDay, int olderPriority, int recentPriority, bool toBeQueuedFirstFlag)
|
||||
{
|
||||
_settings.OlderPriority = olderPriority;
|
||||
_settings.RecentPriority = recentPriority;
|
||||
|
||||
GivenDownloadConfiguration();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteMovie = CreateRemoteMovie();
|
||||
|
||||
remoteMovie.Movie.MovieMetadata.Value.PhysicalRelease = DateTime.UtcNow.AddDays(-ageDay);
|
||||
|
||||
Subject.Download(remoteMovie);
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), toBeQueuedFirstFlag, It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[TestCase(0, 0)]
|
||||
[TestCase(1.5, 150)]
|
||||
public void Download_should_define_seed_ratio_as_expected(double? providerSeedRatio, double? expectedSeedRatio)
|
||||
{
|
||||
GivenDownloadConfiguration();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteMovie = CreateRemoteMovie();
|
||||
|
||||
remoteMovie.SeedConfiguration = new TorrentSeedConfiguration();
|
||||
remoteMovie.SeedConfiguration.Ratio = providerSeedRatio;
|
||||
|
||||
Subject.Download(remoteMovie);
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>(), expectedSeedRatio, It.IsAny<FreeboxDownloadSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_should_return_empty_list_if_no_tasks_available()
|
||||
{
|
||||
GivenTasks(new List<FreeboxDownloadTask>());
|
||||
|
||||
Subject.GetItems().Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_should_return_ignore_tasks_of_unknown_type()
|
||||
{
|
||||
_task.Status = FreeboxDownloadTaskStatus.Done;
|
||||
_task.Type = "toto";
|
||||
|
||||
GivenTasks(new List<FreeboxDownloadTask> { _task });
|
||||
|
||||
Subject.GetItems().Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_when_destinationdirectory_is_set_should_ignore_downloads_in_wrong_folder()
|
||||
{
|
||||
_settings.DestinationDirectory = @"/some/path/that/will/not/match";
|
||||
|
||||
_task.Status = FreeboxDownloadTaskStatus.Done;
|
||||
|
||||
GivenTasks(new List<FreeboxDownloadTask> { _task });
|
||||
|
||||
Subject.GetItems().Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_when_category_is_set_should_ignore_downloads_in_wrong_folder()
|
||||
{
|
||||
_settings.Category = "somecategory";
|
||||
|
||||
_task.Status = FreeboxDownloadTaskStatus.Done;
|
||||
|
||||
GivenTasks(new List<FreeboxDownloadTask> { _task });
|
||||
|
||||
Subject.GetItems().Should().BeEmpty();
|
||||
}
|
||||
|
||||
[TestCase(FreeboxDownloadTaskStatus.Downloading, false, false)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Done, true, true)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Seeding, false, false)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Stopped, false, false)]
|
||||
public void GetItems_should_return_canBeMoved_and_canBeDeleted_as_expected(FreeboxDownloadTaskStatus apiStatus, bool canMoveFilesExpected, bool canBeRemovedExpected)
|
||||
{
|
||||
_task.Status = apiStatus;
|
||||
|
||||
GivenTasks(new List<FreeboxDownloadTask>() { _task });
|
||||
|
||||
var items = Subject.GetItems();
|
||||
|
||||
items.Should().HaveCount(1);
|
||||
items.First().CanBeRemoved.Should().Be(canBeRemovedExpected);
|
||||
items.First().CanMoveFiles.Should().Be(canMoveFilesExpected);
|
||||
}
|
||||
|
||||
[TestCase(FreeboxDownloadTaskStatus.Stopped, DownloadItemStatus.Paused)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Stopping, DownloadItemStatus.Paused)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Queued, DownloadItemStatus.Queued)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Starting, DownloadItemStatus.Downloading)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Downloading, DownloadItemStatus.Downloading)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Retry, DownloadItemStatus.Downloading)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Checking, DownloadItemStatus.Downloading)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Error, DownloadItemStatus.Warning)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Seeding, DownloadItemStatus.Completed)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Done, DownloadItemStatus.Completed)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Unknown, DownloadItemStatus.Downloading)]
|
||||
public void GetItems_should_return_item_as_downloadItemStatus(FreeboxDownloadTaskStatus apiStatus, DownloadItemStatus expectedItemStatus)
|
||||
{
|
||||
_task.Status = apiStatus;
|
||||
|
||||
GivenTasks(new List<FreeboxDownloadTask>() { _task });
|
||||
|
||||
var items = Subject.GetItems();
|
||||
|
||||
items.Should().HaveCount(1);
|
||||
items.First().Status.Should().Be(expectedItemStatus);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_should_return_decoded_destination_directory()
|
||||
{
|
||||
var decodedDownloadDirectory = "/that/the/path";
|
||||
|
||||
_task.Status = FreeboxDownloadTaskStatus.Done;
|
||||
_task.DownloadDirectory = "L3RoYXQvdGhlL3BhdGg=";
|
||||
|
||||
GivenTasks(new List<FreeboxDownloadTask> { _task });
|
||||
|
||||
var items = Subject.GetItems();
|
||||
|
||||
items.Should().HaveCount(1);
|
||||
items.First().OutputPath.Should().Be(decodedDownloadDirectory);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_should_return_message_if_tasks_in_error()
|
||||
{
|
||||
_task.Status = FreeboxDownloadTaskStatus.Error;
|
||||
_task.Error = "internal";
|
||||
|
||||
GivenTasks(new List<FreeboxDownloadTask> { _task });
|
||||
|
||||
var items = Subject.GetItems();
|
||||
|
||||
items.Should().HaveCount(1);
|
||||
items.First().Message.Should().Be("Internal error.");
|
||||
items.First().Status.Should().Be(DownloadItemStatus.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>()
|
||||
|
||||
@@ -89,6 +89,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
.With(h => h.Title = title)
|
||||
.With(h => h.Release = release)
|
||||
.With(h => h.Reason = reason)
|
||||
.With(h => h.ParsedMovieInfo = _parsedMovieInfo)
|
||||
.Build();
|
||||
|
||||
_heldReleases.AddRange(heldReleases);
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
_pending.Add(new PendingRelease
|
||||
{
|
||||
Id = id,
|
||||
Title = "Movie.Title.2020.720p-Radarr",
|
||||
ParsedMovieInfo = new ParsedMovieInfo { MovieTitles = new List<string> { title }, Year = year },
|
||||
MovieId = _movie.Id
|
||||
});
|
||||
|
||||
@@ -93,6 +93,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
.With(h => h.MovieId = _movie.Id)
|
||||
.With(h => h.Title = title)
|
||||
.With(h => h.Release = release)
|
||||
.With(h => h.ParsedMovieInfo = _parsedMovieInfo)
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<IPendingReleaseRepository>()
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie.Title.1990.Ultimate.Rekall.Edition.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA5.1-TWA", "Ultimate Rekall Edition")]
|
||||
[TestCase("Movie.Title.1971.Signature.Edition.1080p.BluRay.FLAC.2.0.x264-TDD", "Signature Edition")]
|
||||
[TestCase("Movie.1979.The.Imperial.Edition.BluRay.720p.DTS.x264-CtrlHD", "Imperial Edition")]
|
||||
[TestCase("Movie.1997.Open.Matte.1080p.BluRay.x264.DTS-FGT", "Open Matte")]
|
||||
public void should_parse_edition(string postTitle, string edition)
|
||||
{
|
||||
var parsed = Parser.Parser.ParseMovieTitle(postTitle);
|
||||
|
||||
@@ -455,6 +455,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie.Title.2016.1080p.KORSUBS.WEBRip.x264.AAC2.0-RADARR", "KORSUBS")]
|
||||
[TestCase("Movie Title 2017 HC 720p HDRiP DD5 1 x264-LEGi0N", "Generic Hardcoded Subs")]
|
||||
[TestCase("Movie.Title.2017.720p.SUBBED.HDRip.V2.XViD-26k.avi", "Generic Hardcoded Subs")]
|
||||
[TestCase("Movie.Title.2000.1080p.BlueRay.x264.DTS.RoSubbed-playHD", null)]
|
||||
[TestCase("Movie Title! 2018 [Web][MKV][h264][480p][AAC 2.0][Softsubs]", null)]
|
||||
[TestCase("Movie Title! 2019 [HorribleSubs][Web][MKV][h264][848x480][AAC 2.0][Softsubs(HorribleSubs)]", null)]
|
||||
public void should_parse_hardcoded_subs(string postTitle, string sub)
|
||||
|
||||
@@ -198,7 +198,7 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
public string LogLevel => GetValue("LogLevel", "info").ToLowerInvariant();
|
||||
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
|
||||
public string Theme => GetValue("Theme", "light", persist: false);
|
||||
public string Theme => GetValue("Theme", "auto", persist: false);
|
||||
public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
|
||||
public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false);
|
||||
public string PostgresPassword => _postgresOptions?.Password ?? GetValue("PostgresPassword", string.Empty, persist: false);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,22 +132,22 @@ namespace NzbDrone.Core.Download.Clients.Flood
|
||||
item.RemainingTime = TimeSpan.FromSeconds(properties.Eta);
|
||||
}
|
||||
|
||||
if (properties.Status.Contains("error"))
|
||||
{
|
||||
item.Status = DownloadItemStatus.Warning;
|
||||
}
|
||||
else if (properties.Status.Contains("seeding") || properties.Status.Contains("complete"))
|
||||
if (properties.Status.Contains("seeding") || properties.Status.Contains("complete"))
|
||||
{
|
||||
item.Status = DownloadItemStatus.Completed;
|
||||
}
|
||||
else if (properties.Status.Contains("downloading"))
|
||||
{
|
||||
item.Status = DownloadItemStatus.Downloading;
|
||||
}
|
||||
else if (properties.Status.Contains("stopped"))
|
||||
{
|
||||
item.Status = DownloadItemStatus.Paused;
|
||||
}
|
||||
else if (properties.Status.Contains("error"))
|
||||
{
|
||||
item.Status = DownloadItemStatus.Warning;
|
||||
}
|
||||
else if (properties.Status.Contains("downloading"))
|
||||
{
|
||||
item.Status = DownloadItemStatus.Downloading;
|
||||
}
|
||||
|
||||
if (item.Status == DownloadItemStatus.Completed)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
{
|
||||
public static class EncodingForBase64
|
||||
{
|
||||
public static string EncodeBase64(this string text)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] textAsBytes = System.Text.Encoding.UTF8.GetBytes(text);
|
||||
return System.Convert.ToBase64String(textAsBytes);
|
||||
}
|
||||
|
||||
public static string DecodeBase64(this string encodedText)
|
||||
{
|
||||
if (encodedText == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] textAsBytes = System.Convert.FromBase64String(encodedText);
|
||||
return System.Text.Encoding.UTF8.GetString(textAsBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
{
|
||||
public class FreeboxDownloadException : DownloadClientException
|
||||
{
|
||||
public FreeboxDownloadException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
{
|
||||
public enum FreeboxDownloadPriority
|
||||
{
|
||||
Last = 0,
|
||||
First = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
{
|
||||
public interface IFreeboxDownloadProxy
|
||||
{
|
||||
void Authenticate(FreeboxDownloadSettings settings);
|
||||
string AddTaskFromUrl(string url, string directory, bool addPaused, bool addFirst, double? seedRatio, FreeboxDownloadSettings settings);
|
||||
string AddTaskFromFile(string fileName, byte[] fileContent, string directory, bool addPaused, bool addFirst, double? seedRatio, FreeboxDownloadSettings settings);
|
||||
void DeleteTask(string id, bool deleteData, FreeboxDownloadSettings settings);
|
||||
FreeboxDownloadConfiguration GetDownloadConfiguration(FreeboxDownloadSettings settings);
|
||||
List<FreeboxDownloadTask> GetTasks(FreeboxDownloadSettings settings);
|
||||
}
|
||||
|
||||
public class FreeboxDownloadProxy : IFreeboxDownloadProxy
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly Logger _logger;
|
||||
private ICached<string> _authSessionTokenCache;
|
||||
|
||||
public FreeboxDownloadProxy(ICacheManager cacheManager, IHttpClient httpClient, Logger logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
_authSessionTokenCache = cacheManager.GetCache<string>(GetType(), "authSessionToken");
|
||||
}
|
||||
|
||||
public void Authenticate(FreeboxDownloadSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/login").Build();
|
||||
|
||||
var response = ProcessRequest<FreeboxLogin>(request, settings);
|
||||
|
||||
if (response.Result.LoggedIn == false)
|
||||
{
|
||||
throw new DownloadClientAuthenticationException("Not logged");
|
||||
}
|
||||
}
|
||||
|
||||
public string AddTaskFromUrl(string url, string directory, bool addPaused, bool addFirst, double? seedRatio, FreeboxDownloadSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/downloads/add").Post();
|
||||
request.Headers.ContentType = "application/x-www-form-urlencoded";
|
||||
|
||||
request.AddFormParameter("download_url", System.Web.HttpUtility.UrlPathEncode(url));
|
||||
|
||||
if (!directory.IsNullOrWhiteSpace())
|
||||
{
|
||||
request.AddFormParameter("download_dir", directory);
|
||||
}
|
||||
|
||||
var response = ProcessRequest<FreeboxDownloadTask>(request.Build(), settings);
|
||||
|
||||
SetTorrentSettings(response.Result.Id, addPaused, addFirst, seedRatio, settings);
|
||||
|
||||
return response.Result.Id;
|
||||
}
|
||||
|
||||
public string AddTaskFromFile(string fileName, byte[] fileContent, string directory, bool addPaused, bool addFirst, double? seedRatio, FreeboxDownloadSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/downloads/add").Post();
|
||||
|
||||
request.AddFormUpload("download_file", fileName, fileContent, "multipart/form-data");
|
||||
|
||||
if (directory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.AddFormParameter("download_dir", directory);
|
||||
}
|
||||
|
||||
var response = ProcessRequest<FreeboxDownloadTask>(request.Build(), settings);
|
||||
|
||||
SetTorrentSettings(response.Result.Id, addPaused, addFirst, seedRatio, settings);
|
||||
|
||||
return response.Result.Id;
|
||||
}
|
||||
|
||||
public void DeleteTask(string id, bool deleteData, FreeboxDownloadSettings settings)
|
||||
{
|
||||
var uri = "/downloads/" + id;
|
||||
|
||||
if (deleteData == true)
|
||||
{
|
||||
uri += "/erase";
|
||||
}
|
||||
|
||||
var request = BuildRequest(settings).Resource(uri).Build();
|
||||
|
||||
request.Method = HttpMethod.Delete;
|
||||
|
||||
ProcessRequest<string>(request, settings);
|
||||
}
|
||||
|
||||
public FreeboxDownloadConfiguration GetDownloadConfiguration(FreeboxDownloadSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/downloads/config/").Build();
|
||||
|
||||
return ProcessRequest<FreeboxDownloadConfiguration>(request, settings).Result;
|
||||
}
|
||||
|
||||
public List<FreeboxDownloadTask> GetTasks(FreeboxDownloadSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/downloads/").Build();
|
||||
|
||||
return ProcessRequest<List<FreeboxDownloadTask>>(request, settings).Result;
|
||||
}
|
||||
|
||||
private static string BuildCachedHeaderKey(FreeboxDownloadSettings settings)
|
||||
{
|
||||
return $"{settings.Host}:{settings.AppId}:{settings.AppToken}";
|
||||
}
|
||||
|
||||
private void SetTorrentSettings(string id, bool addPaused, bool addFirst, double? seedRatio, FreeboxDownloadSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/downloads/" + id).Build();
|
||||
|
||||
request.Method = HttpMethod.Put;
|
||||
|
||||
var body = new Dictionary<string, object> { };
|
||||
|
||||
if (addPaused)
|
||||
{
|
||||
body.Add("status", FreeboxDownloadTaskStatus.Stopped.ToString().ToLower());
|
||||
}
|
||||
|
||||
if (addFirst)
|
||||
{
|
||||
body.Add("queue_pos", "1");
|
||||
}
|
||||
|
||||
if (seedRatio != null)
|
||||
{
|
||||
// 0 means unlimited seeding
|
||||
body.Add("stop_ratio", seedRatio);
|
||||
}
|
||||
|
||||
if (body.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
request.SetContent(body.ToJson());
|
||||
|
||||
ProcessRequest<FreeboxDownloadTask>(request, settings);
|
||||
}
|
||||
|
||||
private string GetSessionToken(HttpRequestBuilder requestBuilder, FreeboxDownloadSettings settings, bool force = false)
|
||||
{
|
||||
var sessionToken = _authSessionTokenCache.Find(BuildCachedHeaderKey(settings));
|
||||
|
||||
if (sessionToken == null || force)
|
||||
{
|
||||
_authSessionTokenCache.Remove(BuildCachedHeaderKey(settings));
|
||||
|
||||
_logger.Debug($"Client needs a new Session Token to reach the API with App ID '{settings.AppId}'");
|
||||
|
||||
// Obtaining a Session Token (from official documentation):
|
||||
// To protect the app_token secret, it will never be used directly to authenticate the
|
||||
// application, instead the API will provide a challenge the app will combine to its
|
||||
// app_token to open a session and get a session_token.
|
||||
// The validity of the session_token is limited in time and the app will have to renew
|
||||
// this session_token once in a while.
|
||||
|
||||
// Retrieving the 'challenge' value (it changes frequently and have a limited time validity)
|
||||
// needed to build password
|
||||
var challengeRequest = requestBuilder.Resource("/login").Build();
|
||||
challengeRequest.Method = HttpMethod.Get;
|
||||
|
||||
var challenge = ProcessRequest<FreeboxLogin>(challengeRequest, settings).Result.Challenge;
|
||||
|
||||
// The password is computed using the 'challenge' value and the 'app_token' ('App Token' setting)
|
||||
var enc = System.Text.Encoding.ASCII;
|
||||
var hmac = new HMACSHA1(enc.GetBytes(settings.AppToken));
|
||||
hmac.Initialize();
|
||||
var buffer = enc.GetBytes(challenge);
|
||||
var password = System.BitConverter.ToString(hmac.ComputeHash(buffer)).Replace("-", "").ToLower();
|
||||
|
||||
// Both 'app_id' ('App ID' setting) and computed password are set to get a Session Token
|
||||
var sessionRequest = requestBuilder.Resource("/login/session").Post().Build();
|
||||
var body = new Dictionary<string, object>
|
||||
{
|
||||
{ "app_id", settings.AppId },
|
||||
{ "password", password }
|
||||
};
|
||||
sessionRequest.SetContent(body.ToJson());
|
||||
|
||||
sessionToken = ProcessRequest<FreeboxLogin>(sessionRequest, settings).Result.SessionToken;
|
||||
|
||||
_authSessionTokenCache.Set(BuildCachedHeaderKey(settings), sessionToken);
|
||||
|
||||
_logger.Debug($"New Session Token stored in cache for App ID '{settings.AppId}', ready to reach API");
|
||||
}
|
||||
|
||||
return sessionToken;
|
||||
}
|
||||
|
||||
private HttpRequestBuilder BuildRequest(FreeboxDownloadSettings settings, bool authentication = true)
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port, settings.ApiUrl)
|
||||
{
|
||||
LogResponseContent = true
|
||||
};
|
||||
|
||||
requestBuilder.Headers.ContentType = "application/json";
|
||||
|
||||
if (authentication == true)
|
||||
{
|
||||
requestBuilder.SetHeader("X-Fbx-App-Auth", GetSessionToken(requestBuilder, settings));
|
||||
}
|
||||
|
||||
return requestBuilder;
|
||||
}
|
||||
|
||||
private FreeboxResponse<T> ProcessRequest<T>(HttpRequest request, FreeboxDownloadSettings settings)
|
||||
{
|
||||
request.LogResponseContent = true;
|
||||
request.SuppressHttpError = true;
|
||||
|
||||
HttpResponse response;
|
||||
|
||||
try
|
||||
{
|
||||
response = _httpClient.Execute(request);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
throw new DownloadClientUnavailableException($"Unable to reach Freebox API. Verify 'Host', 'Port' or 'Use SSL' settings. (Error: {ex.Message})", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
throw new DownloadClientUnavailableException("Unable to connect to Freebox API, please check your settings", ex);
|
||||
}
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Forbidden || response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
_authSessionTokenCache.Remove(BuildCachedHeaderKey(settings));
|
||||
|
||||
var responseContent = Json.Deserialize<FreeboxResponse<FreeboxLogin>>(response.Content);
|
||||
|
||||
var msg = $"Authentication to Freebox API failed. Reason: {responseContent.GetErrorDescription()}";
|
||||
_logger.Error(msg);
|
||||
throw new DownloadClientAuthenticationException(msg);
|
||||
}
|
||||
else if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
throw new FreeboxDownloadException("Unable to reach Freebox API. Verify 'API URL' setting for base URL and version.");
|
||||
}
|
||||
else if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
var responseContent = Json.Deserialize<FreeboxResponse<T>>(response.Content);
|
||||
|
||||
if (responseContent.Success)
|
||||
{
|
||||
return responseContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
var msg = $"Freebox API returned error: {responseContent.GetErrorDescription()}";
|
||||
_logger.Error(msg);
|
||||
throw new DownloadClientException(msg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DownloadClientException("Unable to connect to Freebox, please check your settings.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
{
|
||||
public class FreeboxDownloadSettingsValidator : AbstractValidator<FreeboxDownloadSettings>
|
||||
{
|
||||
public FreeboxDownloadSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.Host).ValidHost();
|
||||
RuleFor(c => c.Port).InclusiveBetween(1, 65535);
|
||||
RuleFor(c => c.ApiUrl).NotEmpty()
|
||||
.WithMessage("'API URL' must not be empty.");
|
||||
RuleFor(c => c.ApiUrl).ValidUrlBase();
|
||||
RuleFor(c => c.AppId).NotEmpty()
|
||||
.WithMessage("'App ID' must not be empty.");
|
||||
RuleFor(c => c.AppToken).NotEmpty()
|
||||
.WithMessage("'App Token' must not be empty.");
|
||||
RuleFor(c => c.Category).Matches(@"^\.?[-a-z]*$", RegexOptions.IgnoreCase)
|
||||
.WithMessage("Allowed characters a-z and -");
|
||||
RuleFor(c => c.DestinationDirectory).IsValidPath()
|
||||
.When(c => c.DestinationDirectory.IsNotNullOrWhiteSpace());
|
||||
RuleFor(c => c.DestinationDirectory).Empty()
|
||||
.When(c => c.Category.IsNotNullOrWhiteSpace())
|
||||
.WithMessage("Cannot use 'Category' and 'Destination Directory' at the same time.");
|
||||
RuleFor(c => c.Category).Empty()
|
||||
.When(c => c.DestinationDirectory.IsNotNullOrWhiteSpace())
|
||||
.WithMessage("Cannot use 'Category' and 'Destination Directory' at the same time.");
|
||||
}
|
||||
}
|
||||
|
||||
public class FreeboxDownloadSettings : IProviderConfig
|
||||
{
|
||||
private static readonly FreeboxDownloadSettingsValidator Validator = new FreeboxDownloadSettingsValidator();
|
||||
|
||||
public FreeboxDownloadSettings()
|
||||
{
|
||||
Host = "mafreebox.freebox.fr";
|
||||
Port = 443;
|
||||
UseSsl = true;
|
||||
ApiUrl = "/api/v1/";
|
||||
}
|
||||
|
||||
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox, HelpText = "Hostname or host IP address of the Freebox, defaults to 'mafreebox.freebox.fr' (will only work if on same network)")]
|
||||
public string Host { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox, HelpText = "Port used to access Freebox interface, defaults to '443'")]
|
||||
public int Port { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secured connection when connecting to Freebox API")]
|
||||
public bool UseSsl { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "API URL", Type = FieldType.Textbox, Advanced = true, HelpText = "Define Freebox API base URL with API version, eg http://[host]:[port]/[api_base_url]/[api_version]/, defaults to '/api/v1/'")]
|
||||
public string ApiUrl { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "App ID", Type = FieldType.Textbox, HelpText = "App ID given when creating access to Freebox API (ie 'app_id')")]
|
||||
public string AppId { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "App Token", Type = FieldType.Password, Privacy = PrivacyLevel.Password, HelpText = "App token retrieved when creating access to Freebox API (ie 'app_token')")]
|
||||
public string AppToken { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Destination Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Freebox download location")]
|
||||
public string DestinationDirectory { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated non-Sonarr downloads (will create a [category] subdirectory in the output directory)")]
|
||||
public string Category { get; set; }
|
||||
|
||||
[FieldDefinition(8, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
||||
public int RecentPriority { get; set; }
|
||||
|
||||
[FieldDefinition(9, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
||||
public int OlderPriority { get; set; }
|
||||
|
||||
[FieldDefinition(10, Label = "Add Paused", Type = FieldType.Checkbox)]
|
||||
public bool AddPaused { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
|
||||
{
|
||||
public class FreeboxDownloadConfiguration
|
||||
{
|
||||
[JsonProperty(PropertyName = "download_dir")]
|
||||
public string DownloadDirectory { get; set; }
|
||||
public string DecodedDownloadDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
return DownloadDirectory.DecodeBase64();
|
||||
}
|
||||
set
|
||||
{
|
||||
DownloadDirectory = value.EncodeBase64();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Serializer;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
|
||||
{
|
||||
public enum FreeboxDownloadTaskType
|
||||
{
|
||||
Bt,
|
||||
Nzb,
|
||||
Http,
|
||||
Ftp
|
||||
}
|
||||
|
||||
public enum FreeboxDownloadTaskStatus
|
||||
{
|
||||
Unknown,
|
||||
Stopped,
|
||||
Queued,
|
||||
Starting,
|
||||
Downloading,
|
||||
Stopping,
|
||||
Error,
|
||||
Done,
|
||||
Checking,
|
||||
Repairing,
|
||||
Extracting,
|
||||
Seeding,
|
||||
Retry
|
||||
}
|
||||
|
||||
public enum FreeboxDownloadTaskIoPriority
|
||||
{
|
||||
Low,
|
||||
Normal,
|
||||
High
|
||||
}
|
||||
|
||||
public class FreeboxDownloadTask
|
||||
{
|
||||
private static readonly Dictionary<string, string> Descriptions;
|
||||
|
||||
[JsonProperty(PropertyName = "id")]
|
||||
public string Id { get; set; }
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
public string Name { get; set; }
|
||||
[JsonProperty(PropertyName = "download_dir")]
|
||||
public string DownloadDirectory { get; set; }
|
||||
public string DecodedDownloadDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
return DownloadDirectory.DecodeBase64();
|
||||
}
|
||||
set
|
||||
{
|
||||
DownloadDirectory = value.EncodeBase64();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "info_hash")]
|
||||
public string InfoHash { get; set; }
|
||||
[JsonProperty(PropertyName = "queue_pos")]
|
||||
public int QueuePosition { get; set; }
|
||||
[JsonConverter(typeof(UnderscoreStringEnumConverter), FreeboxDownloadTaskStatus.Unknown)]
|
||||
public FreeboxDownloadTaskStatus Status { get; set; }
|
||||
[JsonProperty(PropertyName = "eta")]
|
||||
public long Eta { get; set; }
|
||||
[JsonProperty(PropertyName = "error")]
|
||||
public string Error { get; set; }
|
||||
[JsonProperty(PropertyName = "type")]
|
||||
public string Type { get; set; }
|
||||
[JsonProperty(PropertyName = "io_priority")]
|
||||
public string IoPriority { get; set; }
|
||||
[JsonProperty(PropertyName = "stop_ratio")]
|
||||
public long StopRatio { get; set; }
|
||||
[JsonProperty(PropertyName = "piece_length")]
|
||||
public long PieceLength { get; set; }
|
||||
[JsonProperty(PropertyName = "created_ts")]
|
||||
public long CreatedTimestamp { get; set; }
|
||||
[JsonProperty(PropertyName = "size")]
|
||||
public long Size { get; set; }
|
||||
[JsonProperty(PropertyName = "rx_pct")]
|
||||
public long ReceivedPrct { get; set; }
|
||||
[JsonProperty(PropertyName = "rx_bytes")]
|
||||
public long ReceivedBytes { get; set; }
|
||||
[JsonProperty(PropertyName = "rx_rate")]
|
||||
public long ReceivedRate { get; set; }
|
||||
[JsonProperty(PropertyName = "tx_pct")]
|
||||
public long TransmittedPrct { get; set; }
|
||||
[JsonProperty(PropertyName = "tx_bytes")]
|
||||
public long TransmittedBytes { get; set; }
|
||||
[JsonProperty(PropertyName = "tx_rate")]
|
||||
public long TransmittedRate { get; set; }
|
||||
|
||||
static FreeboxDownloadTask()
|
||||
{
|
||||
Descriptions = new Dictionary<string, string>
|
||||
{
|
||||
{ "internal", "Internal error." },
|
||||
{ "disk_full", "The disk is full." },
|
||||
{ "unknown", "Unknown error." },
|
||||
{ "parse_error", "Parse error." },
|
||||
{ "unknown_host", "Unknown host." },
|
||||
{ "timeout", "Timeout." },
|
||||
{ "bad_authentication", "Invalid credentials." },
|
||||
{ "connection_refused", "Remote host refused connection." },
|
||||
{ "bt_tracker_error", "Unable to announce on tracker." },
|
||||
{ "bt_missing_files", "Missing torrent files." },
|
||||
{ "bt_file_error", "Error accessing torrent files." },
|
||||
{ "missing_ctx_file", "Error accessing task context file." },
|
||||
{ "nzb_no_group", "Cannot find the requested group on server." },
|
||||
{ "nzb_not_found", "Article not fount on the server." },
|
||||
{ "nzb_invalid_crc", "Invalid article CRC." },
|
||||
{ "nzb_invalid_size", "Invalid article size." },
|
||||
{ "nzb_invalid_filename", "Invalid filename." },
|
||||
{ "nzb_open_failed", "Error opening." },
|
||||
{ "nzb_write_failed", "Error writing." },
|
||||
{ "nzb_missing_size", "Missing article size." },
|
||||
{ "nzb_decode_error", "Article decoding error." },
|
||||
{ "nzb_missing_segments", "Missing article segments." },
|
||||
{ "nzb_error", "Other nzb error." },
|
||||
{ "nzb_authentication_required", "Nzb server need authentication." }
|
||||
};
|
||||
}
|
||||
|
||||
public string GetErrorDescription()
|
||||
{
|
||||
if (Descriptions.ContainsKey(Error))
|
||||
{
|
||||
return Descriptions[Error];
|
||||
}
|
||||
|
||||
return $"{Error} - Unknown error";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
|
||||
{
|
||||
public class FreeboxLogin
|
||||
{
|
||||
[JsonProperty(PropertyName = "logged_in")]
|
||||
public bool LoggedIn { get; set; }
|
||||
[JsonProperty(PropertyName = "challenge")]
|
||||
public string Challenge { get; set; }
|
||||
[JsonProperty(PropertyName = "password_salt")]
|
||||
public string PasswordSalt { get; set; }
|
||||
[JsonProperty(PropertyName = "password_set")]
|
||||
public bool PasswordSet { get; set; }
|
||||
[JsonProperty(PropertyName = "session_token")]
|
||||
public string SessionToken { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
|
||||
{
|
||||
public class FreeboxResponse<T>
|
||||
{
|
||||
private static readonly Dictionary<string, string> Descriptions;
|
||||
|
||||
[JsonProperty(PropertyName = "success")]
|
||||
public bool Success { get; set; }
|
||||
[JsonProperty(PropertyName = "msg")]
|
||||
public string Message { get; set; }
|
||||
[JsonProperty(PropertyName = "error_code")]
|
||||
public string ErrorCode { get; set; }
|
||||
[JsonProperty(PropertyName = "result")]
|
||||
public T Result { get; set; }
|
||||
|
||||
static FreeboxResponse()
|
||||
{
|
||||
Descriptions = new Dictionary<string, string>
|
||||
{
|
||||
// Common errors
|
||||
{ "invalid_request", "Your request is invalid." },
|
||||
{ "invalid_api_version", "Invalid API base url or unknown API version." },
|
||||
{ "internal_error", "Internal error." },
|
||||
|
||||
// Login API errors
|
||||
{ "auth_required", "Invalid session token, or no session token sent." },
|
||||
{ "invalid_token", "The app token you are trying to use is invalid or has been revoked." },
|
||||
{ "pending_token", "The app token you are trying to use has not been validated by user yet." },
|
||||
{ "insufficient_rights", "Your app permissions does not allow accessing this API." },
|
||||
{ "denied_from_external_ip", "You are trying to get an app_token from a remote IP." },
|
||||
{ "ratelimited", "Too many auth error have been made from your IP." },
|
||||
{ "new_apps_denied", "New application token request has been disabled." },
|
||||
{ "apps_denied", "API access from apps has been disabled." },
|
||||
|
||||
// Download API errors
|
||||
{ "task_not_found", "No task was found with the given id." },
|
||||
{ "invalid_operation", "Attempt to perform an invalid operation." },
|
||||
{ "invalid_file", "Error with the download file (invalid format ?)." },
|
||||
{ "invalid_url", "URL is invalid." },
|
||||
{ "not_implemented", "Method not implemented." },
|
||||
{ "out_of_memory", "No more memory available to perform the requested action." },
|
||||
{ "invalid_task_type", "The task type is invalid." },
|
||||
{ "hibernating", "The downloader is hibernating." },
|
||||
{ "need_bt_stopped_done", "This action is only valid for Bittorrent task in stopped or done state." },
|
||||
{ "bt_tracker_not_found", "Attempt to access an invalid tracker object." },
|
||||
{ "too_many_tasks", "Too many tasks." },
|
||||
{ "invalid_address", "Invalid peer address." },
|
||||
{ "port_conflict", "Port conflict when setting config." },
|
||||
{ "invalid_priority", "Invalid priority." },
|
||||
{ "ctx_file_error", "Failed to initialize task context file (need to check disk)." },
|
||||
{ "exists", "Same task already exists." },
|
||||
{ "port_outside_range", "Incoming port is not available for this customer." }
|
||||
};
|
||||
}
|
||||
|
||||
public string GetErrorDescription()
|
||||
{
|
||||
if (Descriptions.ContainsKey(ErrorCode))
|
||||
{
|
||||
return Descriptions[ErrorCode];
|
||||
}
|
||||
|
||||
return $"{ErrorCode} - Unknown error";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses;
|
||||
using NzbDrone.Core.Download.Clients.QBittorrent;
|
||||
using NzbDrone.Core.MediaFiles.TorrentInfo;
|
||||
using NzbDrone.Core.Organizer;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.RemotePathMappings;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
{
|
||||
public class TorrentFreeboxDownload : TorrentClientBase<FreeboxDownloadSettings>
|
||||
{
|
||||
private readonly IFreeboxDownloadProxy _proxy;
|
||||
|
||||
public TorrentFreeboxDownload(IFreeboxDownloadProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
IConfigService configService,
|
||||
INamingConfigService namingConfigService,
|
||||
IDiskProvider diskProvider,
|
||||
IRemotePathMappingService remotePathMappingService,
|
||||
ICacheManager cacheManager,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
public override string Name => "Freebox Download";
|
||||
|
||||
protected IEnumerable<FreeboxDownloadTask> GetTorrents()
|
||||
{
|
||||
return _proxy.GetTasks(Settings).Where(v => v.Type.ToLower() == FreeboxDownloadTaskType.Bt.ToString().ToLower());
|
||||
}
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
{
|
||||
var torrents = GetTorrents();
|
||||
|
||||
var queueItems = new List<DownloadClientItem>();
|
||||
|
||||
foreach (var torrent in torrents)
|
||||
{
|
||||
var outputPath = new OsPath(torrent.DecodedDownloadDirectory);
|
||||
|
||||
if (Settings.DestinationDirectory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
if (!new OsPath(Settings.DestinationDirectory).Contains(outputPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (Settings.Category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var directories = outputPath.FullPath.Split('\\', '/');
|
||||
|
||||
if (!directories.Contains(Settings.Category))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var item = new DownloadClientItem()
|
||||
{
|
||||
DownloadId = torrent.Id,
|
||||
Category = Settings.Category,
|
||||
Title = torrent.Name,
|
||||
TotalSize = torrent.Size,
|
||||
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
|
||||
RemainingSize = (long)(torrent.Size * (double)(1 - ((double)torrent.ReceivedPrct / 10000))),
|
||||
RemainingTime = torrent.Eta <= 0 ? null : TimeSpan.FromSeconds(torrent.Eta),
|
||||
SeedRatio = torrent.StopRatio <= 0 ? 0 : torrent.StopRatio / 100,
|
||||
OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, outputPath)
|
||||
};
|
||||
|
||||
switch (torrent.Status)
|
||||
{
|
||||
case FreeboxDownloadTaskStatus.Stopped: // task is stopped, can be resumed by setting the status to downloading
|
||||
case FreeboxDownloadTaskStatus.Stopping: // task is gracefully stopping
|
||||
item.Status = DownloadItemStatus.Paused;
|
||||
break;
|
||||
|
||||
case FreeboxDownloadTaskStatus.Queued: // task will start when a new download slot is available the queue position is stored in queue_pos attribute
|
||||
item.Status = DownloadItemStatus.Queued;
|
||||
break;
|
||||
|
||||
case FreeboxDownloadTaskStatus.Starting: // task is preparing to start download
|
||||
case FreeboxDownloadTaskStatus.Downloading:
|
||||
case FreeboxDownloadTaskStatus.Retry: // you can set a task status to ‘retry’ to restart the download task.
|
||||
case FreeboxDownloadTaskStatus.Checking: // checking data before lauching download.
|
||||
item.Status = DownloadItemStatus.Downloading;
|
||||
break;
|
||||
|
||||
case FreeboxDownloadTaskStatus.Error: // there was a problem with the download, you can get an error code in the error field
|
||||
item.Status = DownloadItemStatus.Warning;
|
||||
item.Message = torrent.GetErrorDescription();
|
||||
break;
|
||||
|
||||
case FreeboxDownloadTaskStatus.Done: // the download is over. For bt you can resume seeding setting the status to seeding if the ratio is not reached yet
|
||||
case FreeboxDownloadTaskStatus.Seeding: // download is over, the content is Change to being shared to other users. The task will automatically stop once the seed ratio has been reached
|
||||
item.Status = DownloadItemStatus.Completed;
|
||||
break;
|
||||
|
||||
case FreeboxDownloadTaskStatus.Unknown:
|
||||
default: // new status in API? default to downloading
|
||||
item.Message = "Unknown download state: " + torrent.Status;
|
||||
_logger.Info(item.Message);
|
||||
item.Status = DownloadItemStatus.Downloading;
|
||||
break;
|
||||
}
|
||||
|
||||
item.CanBeRemoved = item.CanMoveFiles = torrent.Status == FreeboxDownloadTaskStatus.Done;
|
||||
|
||||
queueItems.Add(item);
|
||||
}
|
||||
|
||||
return queueItems;
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
|
||||
{
|
||||
return _proxy.AddTaskFromUrl(magnetLink,
|
||||
GetDownloadDirectory().EncodeBase64(),
|
||||
ToBePaused(),
|
||||
ToBeQueuedFirst(remoteMovie),
|
||||
GetSeedRatio(remoteMovie),
|
||||
Settings);
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
return _proxy.AddTaskFromFile(filename,
|
||||
fileContent,
|
||||
GetDownloadDirectory().EncodeBase64(),
|
||||
ToBePaused(),
|
||||
ToBeQueuedFirst(remoteMovie),
|
||||
GetSeedRatio(remoteMovie),
|
||||
Settings);
|
||||
}
|
||||
|
||||
public override void RemoveItem(DownloadClientItem item, bool deleteData)
|
||||
{
|
||||
_proxy.DeleteTask(item.DownloadId, deleteData, Settings);
|
||||
}
|
||||
|
||||
public override DownloadClientInfo GetStatus()
|
||||
{
|
||||
var destDir = GetDownloadDirectory();
|
||||
|
||||
return new DownloadClientInfo
|
||||
{
|
||||
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "::1" || Settings.Host == "localhost",
|
||||
OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(destDir)) }
|
||||
};
|
||||
}
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
{
|
||||
try
|
||||
{
|
||||
_proxy.Authenticate(Settings);
|
||||
}
|
||||
catch (DownloadClientUnavailableException ex)
|
||||
{
|
||||
failures.Add(new ValidationFailure("Host", ex.Message));
|
||||
failures.Add(new ValidationFailure("Port", ex.Message));
|
||||
}
|
||||
catch (DownloadClientAuthenticationException ex)
|
||||
{
|
||||
failures.Add(new ValidationFailure("AppId", ex.Message));
|
||||
failures.Add(new ValidationFailure("AppToken", ex.Message));
|
||||
}
|
||||
catch (FreeboxDownloadException ex)
|
||||
{
|
||||
failures.Add(new ValidationFailure("ApiUrl", ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
private string GetDownloadDirectory()
|
||||
{
|
||||
if (Settings.DestinationDirectory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return Settings.DestinationDirectory.TrimEnd('/');
|
||||
}
|
||||
|
||||
var destDir = _proxy.GetDownloadConfiguration(Settings).DecodedDownloadDirectory.TrimEnd('/');
|
||||
|
||||
if (Settings.Category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
destDir = $"{destDir}/{Settings.Category}";
|
||||
}
|
||||
|
||||
return destDir;
|
||||
}
|
||||
|
||||
private bool ToBePaused()
|
||||
{
|
||||
return Settings.AddPaused;
|
||||
}
|
||||
|
||||
private bool ToBeQueuedFirst(RemoteMovie remoteMovie)
|
||||
{
|
||||
var isRecentMovie = remoteMovie.Movie.MovieMetadata.Value.IsRecentMovie;
|
||||
|
||||
if ((isRecentMovie && Settings.RecentPriority == (int)FreeboxDownloadPriority.First) ||
|
||||
(!isRecentMovie && Settings.OlderPriority == (int)FreeboxDownloadPriority.First))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private double? GetSeedRatio(RemoteMovie remoteMovie)
|
||||
{
|
||||
if (remoteMovie.SeedConfiguration == null || remoteMovie.SeedConfiguration.Ratio == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return remoteMovie.SeedConfiguration.Ratio.Value * 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,11 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
return true;
|
||||
}
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
throw new DownloadClientException("Failed to connect to qBittorrent. Check your settings and qBittorrent configuration.", new HttpException(response));
|
||||
}
|
||||
|
||||
if (response.HasHttpError)
|
||||
{
|
||||
throw new DownloadClientException("Failed to connect to qBittorrent, check your settings.", new HttpException(response));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,6 +283,12 @@ namespace NzbDrone.Core.Download.Pending
|
||||
return null;
|
||||
}
|
||||
|
||||
// Languages will be empty if added before upgrading to v4, reparsing the languages if they're empty will set it to Unknown or better.
|
||||
if (release.ParsedMovieInfo.Languages.Empty())
|
||||
{
|
||||
release.ParsedMovieInfo.Languages = LanguageParser.ParseLanguages(release.Title);
|
||||
}
|
||||
|
||||
release.RemoteMovie = new RemoteMovie
|
||||
{
|
||||
Movie = movie,
|
||||
|
||||
@@ -5,5 +5,7 @@ namespace NzbDrone.Core.Download
|
||||
public class ProcessMonitoredDownloadsCommand : Command
|
||||
{
|
||||
public override bool RequiresDiskAccess => true;
|
||||
|
||||
public override bool IsLongRunning => true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
public class RssSyncCommand : Command
|
||||
{
|
||||
public override bool SendUpdatesToClient => true;
|
||||
|
||||
public override bool IsLongRunning => true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
{}
|
||||
{
|
||||
"About": "সম্পর্কিত",
|
||||
"AcceptConfirmationModal": "নিশ্চিতকরণ মডেল গ্রহণ করুন",
|
||||
"Actions": "ক্রিয়াকাণ্ড",
|
||||
"Activity": "কার্যকলাপ",
|
||||
"Add": "যোগ করুন"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"EditDelayProfile": "Upravit profil zpoždění",
|
||||
"AcceptConfirmationModal": "Přijměte potvrzení Modal",
|
||||
"AcceptConfirmationModal": "Přijměte potvrzovací modální okno",
|
||||
"Automatic": "Automatický",
|
||||
"CertificateValidation": "Ověření certifikátu",
|
||||
"CertificateValidationHelpText": "Změňte, jak přísné je ověření certifikace HTTPS",
|
||||
"CertificateValidationHelpText": "Změňte přísnost ověřování certifikátů HTTPS. Neměňte, pokud nerozumíte rizikům.",
|
||||
"Cast": "Obsazení",
|
||||
"CheckDownloadClientForDetails": "zkontrolujte stahování klienta pro více informací",
|
||||
"CheckForFinishedDownloadsInterval": "Zkontrolujte interval dokončených stahování",
|
||||
@@ -137,7 +137,7 @@
|
||||
"Ungroup": "Oddělit skupinu",
|
||||
"Unlimited": "Neomezený",
|
||||
"UnsavedChanges": "Neuložené změny",
|
||||
"UpdateMechanismHelpText": "Použijte vestavěný aktualizátor Radarr nebo skript",
|
||||
"UpdateMechanismHelpText": "Použijte vestavěný aktualizační program Radarr nebo skript",
|
||||
"UpgradeUntilQuality": "Upgradujte až do kvality",
|
||||
"UseHardlinksInsteadOfCopy": "Místo kopírování použijte pevné odkazy",
|
||||
"UsenetDelayTime": "Usenet Zpoždění: {0}",
|
||||
@@ -157,12 +157,12 @@
|
||||
"Announced": "Oznámeno",
|
||||
"AvailabilityDelayHelpText": "Množství času před nebo po dostupném datu pro vyhledání filmu",
|
||||
"ImportExistingMovies": "Importovat existující filmy",
|
||||
"AddedToDownloadQueue": "Přidáno do stažené fronty",
|
||||
"AddedToDownloadQueue": "Přidáno do fronty ke stažení",
|
||||
"AddNotification": "Přidat oznámení",
|
||||
"Add": "Přidat",
|
||||
"AddCustomFormat": "Přidat vlastní formát",
|
||||
"AddDelayProfile": "Přidat profil zpoždění",
|
||||
"AddDownloadClient": "Přidat staženého klienta",
|
||||
"AddDownloadClient": "Přidat klienta pro stahování",
|
||||
"AddRootFolder": "Přidat kořenovou složku",
|
||||
"Always": "Vždy",
|
||||
"AnalyticsEnabledHelpText": "Odesílejte anonymní informace o použití a chybách na servery Radarru. To zahrnuje informace o vašem prohlížeči, které stránky Radarr WebUI používáte, hlášení chyb a také verzi operačního systému a běhového prostředí. Tyto informace použijeme k upřednostnění funkcí a oprav chyb.",
|
||||
@@ -677,7 +677,7 @@
|
||||
"IMDb": "IMDb",
|
||||
"ImportCustomFormat": "Importujte vlastní formát",
|
||||
"ImportedTo": "Importováno do",
|
||||
"ImportRootPath": "Namiřte Radarr na složku obsahující všechny vaše filmy, nikoli konkrétní film. např. {0} a ne {1}. Každý film musí být navíc ve své vlastní složce v kořenové složce / složce knihovny.",
|
||||
"ImportRootPath": "Nasměrujte Radarr na složku obsahující všechny vaše filmy, ne na konkrétní film. Např. {0} a ne {1}. Kromě toho musí být každý film ve vlastní složce v rámci kořenové složky nebo knihovny.",
|
||||
"ImportTipsMessage": "Několik tipů, jak zajistit bezproblémový import:",
|
||||
"InCinemasDate": "V kinech",
|
||||
"InCinemasMsg": "Film je v kinech",
|
||||
@@ -687,13 +687,13 @@
|
||||
"IncludeUnmonitored": "Zahrnout Nesledováno",
|
||||
"ImportMovies": "Importovat filmy",
|
||||
"IndexerPriority": "Priorita indexování",
|
||||
"IndexerPriorityHelpText": "Priorita indexování od 1 (nejvyšší) do 50 (nejnižší). Výchozí: 25.",
|
||||
"IndexerPriorityHelpText": "Priorita indexovacího modulu od 1 (nejvyšší) do 50 (nejnižší). Výchozí: 25. Používá se při získávání verzí jako rozhodující prvek pro jinak stejné verze, Radarr bude stále používat všechny povolené indexovací moduly pro Synchronizaci RSS a vyhledávání",
|
||||
"IndexerRssHealthCheckNoAvailableIndexers": "Všechny indexery podporující rss jsou dočasně nedostupné kvůli nedávným chybám indexeru",
|
||||
"IndexerRssHealthCheckNoIndexers": "Nejsou k dispozici žádné indexery se zapnutou synchronizací RSS, Radarr nové verze automaticky nezachytí",
|
||||
"Indexers": "Indexery",
|
||||
"IndexerSearchCheckNoAutomaticMessage": "Nejsou k dispozici žádné indexery se zapnutým automatickým vyhledáváním, Radarr neposkytne žádné automatické výsledky hledání",
|
||||
"IndexerSearchCheckNoAvailableIndexersMessage": "Všechny indexery podporující vyhledávání jsou dočasně nedostupné kvůli nedávným chybám indexeru",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Pokud je povoleno interaktivní vyhledávání, nejsou k dispozici žádné indexery, Radarr neposkytne žádné interaktivní výsledky hledání",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Při povoleném interaktivním vyhledávání, nejsou dostupné žádné indexovací moduly, Radarr neposkytne žádné interaktivní výsledky hledání",
|
||||
"IndexerSettings": "Nastavení indexeru",
|
||||
"IndexerStatusCheckSingleClientMessage": "Indexery nedostupné z důvodu selhání: {0}",
|
||||
"InstallLatest": "Nainstalujte nejnovější",
|
||||
@@ -1061,6 +1061,6 @@
|
||||
"AllCollectionsHiddenDueToFilter": "Všechny filmy jsou skryty kvůli použitému filtru.",
|
||||
"Collections": "Sbírka",
|
||||
"MonitorMovies": "Monitorujte film",
|
||||
"NoCollections": "Nebyly nalezeny žádné filmy. Chcete-li začít, budete chtít přidat nový film nebo importovat některé stávající.",
|
||||
"NoCollections": "Nebyly nalezeny žádné kolekce, pro začátek budete chtít přidat nový film nebo importovat některé stávající",
|
||||
"RssSyncHelpText": "Interval v minutách. Nastavením na nulu deaktivujete (tím se zastaví veškeré automatické uvolnění uvolnění)"
|
||||
}
|
||||
|
||||
@@ -329,17 +329,17 @@
|
||||
"BackupRetentionHelpText": "Automatische Backups, die älter als die Aufbewahrungsfrist sind, werden automatisch gelöscht",
|
||||
"Backups": "Backups",
|
||||
"BindAddress": "Adresse binden",
|
||||
"BindAddressHelpText": "Gültige IPv4 Adresse oder \"*\" für alle Netzwerke",
|
||||
"BindAddressHelpText": "Gültige IP Adresse oder \"*\" für alle Netzwerke",
|
||||
"Branch": "Git-Branch",
|
||||
"BypassProxyForLocalAddresses": "Proxy für lokale Adressen umgehen",
|
||||
"CertificateValidation": "Zertifikat Validierung",
|
||||
"CertificateValidationHelpText": "Ändere wie streng die Validierung der HTTPS-Zertifizierung ist. Nicht anpassen, außer du kennst das Risiko.",
|
||||
"CertificateValidationHelpText": "Ändere wie streng die Validierung der HTTPS-Zertifizierung ist. Ändern Sie nicht wenn Ihnen die Risiken nicht bewusst sind.",
|
||||
"CertificationCountry": "Zertifizierungs Land",
|
||||
"ChangeFileDate": "Erstelldatum der Datei anpassen",
|
||||
"ChangeHasNotBeenSavedYet": "Änderung wurde noch nicht gespeichert",
|
||||
"CheckForFinishedDownloadsInterval": "Intervall zum prüfen von fertigen Downloads",
|
||||
"CleanLibraryLevel": "Mediathek aufräumen",
|
||||
"ClickToChangeLanguage": "Sprache ändern ...",
|
||||
"ClickToChangeLanguage": "Sprache ändern",
|
||||
"ClickToChangeQuality": "Hier klicken um die Qualität zu ändern",
|
||||
"ClientPriority": "Priorität",
|
||||
"CloneFormatTag": "Format Tag kopieren",
|
||||
@@ -516,7 +516,7 @@
|
||||
"ShowTitleHelpText": "Filmtitel unter dem Plakat anzeigen",
|
||||
"ShowUnknownMovieItems": "Unzugeordente Filmeinträge anzeigen",
|
||||
"SkipFreeSpaceCheck": "Pürfung des freien Speichers überspringen",
|
||||
"SkipFreeSpaceCheckWhenImportingHelpText": "Aktiviere diese Option, wenn es nicht möglich ist, den freien Speicherplatz des Stammverzeichnisses für Filme zu erkennen",
|
||||
"SkipFreeSpaceCheckWhenImportingHelpText": "Aktiviere diese Option, wenn es Radarr nicht möglich ist, den freien Speicherplatz des Stammverzeichnisses für Filme zu erkennen",
|
||||
"SorryThatMovieCannotBeFound": "Schade, dieser Film kann nicht gefunden werden.",
|
||||
"SourcePath": "Quellpfad",
|
||||
"SourceRelativePath": "Relativer Quellpfad",
|
||||
@@ -531,7 +531,7 @@
|
||||
"TestAllIndexers": "Alle testen",
|
||||
"TestAllLists": "Alle testen",
|
||||
"TimeFormat": "Zeitformat",
|
||||
"UpdateMechanismHelpText": "Benutze den Build-In Updater oder ein Script",
|
||||
"UpdateMechanismHelpText": "Benutze Radarr's Build-In Updater oder ein Script",
|
||||
"UpdateScriptPathHelpText": "Pfad zu einem benutzerdefinierten Skript, das ein extrahiertes Update-Paket übernimmt und den Rest des Update-Prozesses abwickelt",
|
||||
"UpgradeAllowedHelpText": "Wenn deaktiviert wird die Qualität nicht verbessert",
|
||||
"Uptime": "Laufzeit",
|
||||
@@ -979,7 +979,7 @@
|
||||
"EditCustomFormat": "Eigenes Format bearbeiten",
|
||||
"DoNotUpgradeAutomatically": "Nicht automatisch upgraden",
|
||||
"DoNotPrefer": "Nicht bevorzugen",
|
||||
"DoneEditingGroups": "Gruppen bearbeiten abgechlossen",
|
||||
"DoneEditingGroups": "Editieren der Gruppen abschließen",
|
||||
"Donations": "Spenden",
|
||||
"DockerUpdater": "aktualisiere den Docker Container um das Update zu erhalten",
|
||||
"Discord": "Discord",
|
||||
@@ -1063,7 +1063,7 @@
|
||||
"ImportListMissingRoot": "Fehlendes Stammverzeichnis für Importlist(en): {0}",
|
||||
"BypassDelayIfHighestQualityHelpText": "Verzögerung ignorieren wenn das Release die höchste aktivierte Qualität des Qualitätsprofils mit dem bevorzugtem Protokoll ist",
|
||||
"BypassDelayIfHighestQuality": "Ignoriere wenn höchste Qualität",
|
||||
"TaskUserAgentTooltip": "UserAgent von der App die die API aufgerufen hat",
|
||||
"TaskUserAgentTooltip": "UserAgent von der App welche die API aufgerufen hat",
|
||||
"Letterboxd": "Letterboxd",
|
||||
"From": "von",
|
||||
"NotificationTriggersHelpText": "Auslöser für diese Benachrichtigung auswählen",
|
||||
@@ -1146,5 +1146,12 @@
|
||||
"TotalMovies": "Filme insgesamt",
|
||||
"ApplicationURL": "Anwendungs-URL",
|
||||
"ApplicationUrlHelpText": "Die externe URL der Anwendung inklusive http(s)://, Port und URL-Basis",
|
||||
"PreferredProtocol": "Bevorzugtes Protokoll"
|
||||
"PreferredProtocol": "Bevorzugtes Protokoll",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Sicher, dass die Qualitätsdefinitionen zurückgesetzt werden sollen?",
|
||||
"ResetDefinitions": "Definitionen zurücksetzen",
|
||||
"ResetQualityDefinitions": "Qualitätsdefinitionen zurücksetzen",
|
||||
"SettingsThemeHelpText": "Anwendungsdesign ändern, das 'Auto' Design passt sich an den Light/Dark-Mode deines Systems an. Inspiriert von Theme.Park",
|
||||
"ResetTitles": "Titel zurücksetzen",
|
||||
"ResetTitlesHelpText": "Definitionstitel und Werte zurücksetzen",
|
||||
"SettingsTheme": "Design"
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
"Backups": "Backups",
|
||||
"BeforeUpdate": "Before update",
|
||||
"BindAddress": "Bind Address",
|
||||
"BindAddressHelpText": "Valid IPv4 address or '*' for all interfaces",
|
||||
"BindAddressHelpText": "Valid IP address, localhost or '*' for all interfaces",
|
||||
"Blocklist": "Blocklist",
|
||||
"Blocklisted": "Blocklisted",
|
||||
"BlocklistHelpText": "Prevents Radarr from automatically grabbing this release again",
|
||||
@@ -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)",
|
||||
@@ -928,7 +930,7 @@
|
||||
"SettingsShowRelativeDates": "Show Relative Dates",
|
||||
"SettingsShowRelativeDatesHelpText": "Show relative (Today/Yesterday/etc) or absolute dates",
|
||||
"SettingsTheme": "Theme",
|
||||
"SettingsThemeHelpText": "Change Application UI Theme, Inspired by Theme.Park",
|
||||
"SettingsThemeHelpText": "Change Application UI Theme, 'Auto' Theme will use your OS Theme to set Light or Dark mode. Inspired by Theme.Park",
|
||||
"SettingsTimeFormat": "Time Format",
|
||||
"SettingsWeekColumnHeader": "Week Column Header",
|
||||
"SettingsWeekColumnHeaderHelpText": "Shown above each column when week is the active view",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"BackupIntervalHelpText": "Prowlarrin tietokannan ja asetusten automaattisen varmuuskopioinnin suoritusaikaväli.",
|
||||
"AppDataDirectory": "AppData-kansio",
|
||||
"BackupNow": "Varmuuskopioi nyt",
|
||||
"BindAddressHelpText": "Toimiva IPv4-osoite tai '*' (tähti) kaikille yhteyksille.",
|
||||
"BindAddressHelpText": "Toimiva IP-osoite, localhost tai '*' (tähti) kaikille yhteyksille.",
|
||||
"Branch": "Kehityshaara",
|
||||
"BuiltIn": "Sisäänrakennettu",
|
||||
"CalendarOptions": "Kalenterin asetukset",
|
||||
@@ -252,7 +252,7 @@
|
||||
"Profiles": "Profiilit",
|
||||
"ProxyType": "Välityspalvelimen tyyppi",
|
||||
"PtpOldSettingsCheckMessage": "Seuraavat PassThePopcorn-tietolähteet sisältävät vanhentuneita asetuksia, jotka olisi syytä päivittää: {0}",
|
||||
"QualitiesHelpText": "Listalla ylempänä olevia laatuja painotetaan. Saman ryhmän laadut ovat tasaveroisia. Valitse vain haluamasi laadut.",
|
||||
"QualitiesHelpText": "Listalla ylempänä olevia laatuja painotetaan enemmän vaikkei niitä ole valittu. Samoissa ryhmissä olevat laadut ovat tasaveroisia. Valitse vain halutut laadut.",
|
||||
"QualityProfileInUse": "Elokuvaan, listaan tai kokelmaan liitettyä laatuprofiilia ei voida poistaa.",
|
||||
"QueueIsEmpty": "Jono on tyhjä",
|
||||
"RadarrCalendarFeed": "Radarr-kalenterisyöte",
|
||||
@@ -333,7 +333,7 @@
|
||||
"CreateEmptyMovieFoldersHelpText": "Luo puuttuvat elokuvakansiot levyn tarkistuksen yhteydessä.",
|
||||
"DeleteDownloadClient": "Poista lataustyökalu",
|
||||
"ImportHeader": "Lisää Radarriin elokuvia tuomalla olemassa oleva, järjestetty kirjasto.",
|
||||
"ImportMechanismHealthCheckMessage": "Ota valmiiden latausten käsittely käyttöön",
|
||||
"ImportMechanismHealthCheckMessage": "Käytä valmiiden latausten käsittelyä",
|
||||
"MinAvailability": "Pienin saatavuus",
|
||||
"MovieIsUnmonitored": "Elokuvaa ei valvota",
|
||||
"MovieNaming": "Elokuvien nimeäminen",
|
||||
@@ -435,7 +435,7 @@
|
||||
"StartTypingOrSelectAPathBelow": "Aloita kirjoitus tai valitse sijainti alta",
|
||||
"StartupDirectory": "Käynnistyskansio",
|
||||
"System": "Järjestelmä",
|
||||
"SystemTimeCheckMessage": "Järjestelmän aika on heittä yli vuorokauden verran. Ajoitetut tehtävät eivät luultavasti toimi oikein ennen ajan korjausta.",
|
||||
"SystemTimeCheckMessage": "Järjestelmän aika on pielessä yli vuorokauden. Ajoitetut tehtävät eivät luultavasti toimi oikein ennen sen korjausta.",
|
||||
"Posters": "Julisteet",
|
||||
"PosterSize": "Julisteen koko",
|
||||
"TagCannotBeDeletedWhileInUse": "Tunnistetta ei voi poistaa, koska se on käytössä",
|
||||
@@ -653,9 +653,9 @@
|
||||
"IndexerPriority": "Tietolähteiden painotus",
|
||||
"IndexerPriorityHelpText": "Tietolähteen painotus: 1 (korkein) - 50 (matalin). Oletusarvo on 25. Käytetään muutoin tasaveroisten julkaisujen sieppauspäätökseen. Kaikkia käytössä olevia tietolähteitä käytetään edelleen RSS-synkronointiin ja hakuun.",
|
||||
"IndexerRssHealthCheckNoAvailableIndexers": "Kaikki RSS-tietolähteet ovat tilapaisesti tavoittamattomissa viimeaikaisten tietolähdevirheiden vuoksi.",
|
||||
"IndexerRssHealthCheckNoIndexers": "Yhtään RSS-synkronointia käyttävää tietolähdettä ei ole käytettävissä, eikä Radarr tämän vuoksi sieppaa uusia julkaisuja automaattisesti.",
|
||||
"IndexerRssHealthCheckNoIndexers": "Yhtään RSS-synkronointia käyttävää tietolähdettä ei ole käytettävissä, eikä uusia julkaisuja sen vuoksi siepata automaattisesti.",
|
||||
"Indexers": "Tietolähteet",
|
||||
"IndexerSearchCheckNoAutomaticMessage": "Ei hakemistoja, joissa automaattinen haku on käytössä, Radarr ei tarjoa automaattisia hakutuloksia",
|
||||
"IndexerSearchCheckNoAutomaticMessage": "Automaattihaussa käytettäviä tietolähteitä ei ole käytettävissä, eikä automaattisia hakutuloksia ole tämän vuoksi saatavilla.",
|
||||
"IndexerSearchCheckNoAvailableIndexersMessage": "Kaikki hakukelpoiset tietolähteet ovat tilapaisesti tavoittamattomissa viimeaikaisten tietolähdevirheiden vuoksi.",
|
||||
"IndexerSettings": "Tietolähteiden asetukset",
|
||||
"IndexerStatusCheckSingleClientMessage": "Tietolähteet eivät ole käytettävissä virheiden vuoksi: {0}",
|
||||
@@ -750,7 +750,7 @@
|
||||
"QualityDefinitions": "Laatumääritykset",
|
||||
"QualityLimitsHelpText": "Rajoitukset suhteutetaan automaattisesti elokuvan kestoon.",
|
||||
"QualityProfileDeleteConfirm": "Haluatko varmasti poistaa laatuprofiilin {0}",
|
||||
"QualitySettingsSummary": "Laatumääritykset erilaisia sisältömuotoja ja teidostokokoja varten.",
|
||||
"QualitySettingsSummary": "Laatumääritykset erilaisia sisältömuotoja ja tiedostokokoja varten.",
|
||||
"RadarrSupportsAnyIndexer": "Radarr tukee Newznab- ja Torznab-yhteensopivien tietolähteiden ohella myös monia muita alla lueteltuja tietolähteitä.",
|
||||
"RadarrSupportsCustomConditionsAgainstTheReleasePropertiesBelow": "Radarr tukee mukautettuja ehtoja perustuen julkaisujen alla oleviin ominaisuuksiin.",
|
||||
"Ratings": "Arviot",
|
||||
@@ -1146,5 +1146,13 @@
|
||||
"TotalMovies": "Elokuvia yhteensä",
|
||||
"ApplicationURL": "Sovelluksen URL-osoite",
|
||||
"ApplicationUrlHelpText": "Sovelluksen ulkoinen URL-osoite, johon sisältyy http(s)://, portti ja URL-perusta.",
|
||||
"PreferredProtocol": "Ensisijainen protokolla"
|
||||
"PreferredProtocol": "Ensisijainen protokolla",
|
||||
"ResetQualityDefinitions": "Palauta laatumääritykset",
|
||||
"ResetTitles": "Palauta nimet",
|
||||
"SettingsTheme": "Teema",
|
||||
"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.",
|
||||
"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.",
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
"BranchUpdateMechanism": "A külső frissítési mechanizmus által használt ágazat",
|
||||
"BranchUpdate": "Ágazattípus a Radarr frissítéseihez",
|
||||
"Branch": "Ágazat",
|
||||
"BindAddressHelpText": "Érvényes IPv4-cím, vagy „*” minden interfészhez",
|
||||
"BindAddressHelpText": "Érvényes IP-cím, localhost vagy '*' minden interfészhez",
|
||||
"BindAddress": "Kapcsolási Cím",
|
||||
"BeforeUpdate": "Alkalmazásfrissítés előtt",
|
||||
"Backups": "Biztonsági Mentés",
|
||||
@@ -838,7 +838,7 @@
|
||||
"EditCustomFormat": "Egyéni Formátumok szerkesztése",
|
||||
"DoNotUpgradeAutomatically": "Ne frissítsen automatikusan",
|
||||
"DoNotPrefer": "Nem preferált",
|
||||
"DoneEditingGroups": "Kész szerkesztő csoportok",
|
||||
"DoneEditingGroups": "A csoportok szerkesztése kész",
|
||||
"Donations": "Adományok",
|
||||
"DockerUpdater": "A Frissítéshez frissítenie kell a Docker tárolót",
|
||||
"Discord": "Discord",
|
||||
@@ -965,7 +965,7 @@
|
||||
"QueueIsEmpty": "A várakozási sor üres",
|
||||
"QualityProfileInUse": "A filmhez, listához vagy gyűjteményhez csatolt minőségi profil nem törölhető",
|
||||
"QualityLimitsHelpText": "A korlátozások automatikusan beállítódnak a film hossza szerint.",
|
||||
"QualitiesHelpText": "A jobb minőség a listában jobban preferált. Ugyanazon minőségek a csoportban egyenlőek. Csak a bejelölt minőségek a kívánt minőségek",
|
||||
"QualitiesHelpText": "A listán magasabb minőségek előnyösebbek, még akkor is, ha nincs bejelölve. Ugyanazon csoporton belül a tulajdonságok egyenlőek. Csak ellenőrzött minőségek szükségesek",
|
||||
"Qualities": "Minőségek",
|
||||
"PrioritySettings": "Prioritás: {0}",
|
||||
"PreviewRenameHelpText": "Tipp: Átnevezés előnézetéhez... válassza a 'Visszavonást' majd kattintson a film címére és használja a",
|
||||
@@ -1144,7 +1144,15 @@
|
||||
"InstanceNameHelpText": "Példánynév a böngésző lapon és a syslog alkalmazás neve",
|
||||
"RottenTomatoesRating": "Tomato Értékelés",
|
||||
"TotalMovies": "Összes film",
|
||||
"ApplicationUrlHelpText": "Az alkalmazás külső URL-címe, beleértve a \"http(s)://\"-t, a \"Portot\" és az \"URL-alapot\" is",
|
||||
"ApplicationUrlHelpText": "Az alkalmazás külső URL-címe, beleértve a http(s)://-t, a portot és az URL-alapot",
|
||||
"ApplicationURL": "Alkalmazás URL-je",
|
||||
"PreferredProtocol": "Preferált protokoll"
|
||||
"PreferredProtocol": "Preferált protokoll",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Biztos visszaállítod a minőségi definíciókat?",
|
||||
"SettingsTheme": "Téma",
|
||||
"SettingsThemeHelpText": "Változtasd meg az alkalmazás felhasználói felület témáját, az „Auto” téma az operációs rendszer témáját használja a Világos vagy Sötét mód beállításához. A Theme.Park ihlette",
|
||||
"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",
|
||||
"RSSHelpText": "Akkor használatos, amikor a Radarr rendszeresen keres kiadásokat az RSS Sync segítségével"
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
"AppDataLocationHealthCheckMessage": "L'aggiornamento non sarà possibile per evitare la cancellazione di AppData durante l'aggiornamento",
|
||||
"Analytics": "Analitica",
|
||||
"Added": "Aggiunto",
|
||||
"About": "Informazioni",
|
||||
"About": "Info",
|
||||
"Year": "Anno",
|
||||
"Week": "Settimana",
|
||||
"Updates": "Aggiornamenti",
|
||||
@@ -189,7 +189,7 @@
|
||||
"ChooseAnotherFolder": "Scegli un'altra Cartella",
|
||||
"Cast": "Cast",
|
||||
"Calendar": "Calendario",
|
||||
"BackupNow": "Effettua il Backup adesso",
|
||||
"BackupNow": "Esegui backup ora",
|
||||
"Backup": "Backup",
|
||||
"All": "Tutti",
|
||||
"Agenda": "Agenda",
|
||||
@@ -220,12 +220,12 @@
|
||||
"ChangeFileDate": "Cambiare la Data del File",
|
||||
"CertificationCountryHelpText": "Seleziona il Paese per le Certificazioni dei Film",
|
||||
"CertificationCountry": "Paese di Certificazione",
|
||||
"CertificateValidationHelpText": "Cambia quanto è rigorosa la convalida del certificato HTTPS. Non cambiare a meno che tu non comprenda i rischi.",
|
||||
"CertificateValidationHelpText": "Cambia quanto rigorosamente vengono validati i certificati HTTPS. Non cambiare senza conoscerne i rischi.",
|
||||
"CertificateValidation": "Convalida del Certificato",
|
||||
"Cancel": "Annulla",
|
||||
"BypassProxyForLocalAddresses": "Evita il Proxy per gli Indirizzi Locali",
|
||||
"Branch": "Ramo",
|
||||
"BindAddressHelpText": "Indirizzo IPv4 valido o '*' per tutte le interfacce",
|
||||
"BindAddressHelpText": "Indirizzo IPV4 valido o '*' per tutte le interfacce di rete",
|
||||
"BindAddress": "Indirizzo di Bind",
|
||||
"Backups": "I Backup",
|
||||
"BackupRetentionHelpText": "I backup automatici più vecchi del periodo di conservazione verranno eliminati automaticamente",
|
||||
@@ -261,7 +261,7 @@
|
||||
"TotalSpace": "Spazio Totale",
|
||||
"Title": "Titolo",
|
||||
"Time": "Ora",
|
||||
"TestAll": "Testa Tutti",
|
||||
"TestAll": "Prova Tutti",
|
||||
"Test": "Test",
|
||||
"TableOptionsColumnsMessage": "Scegli quali colonne rendere visibili ed il loro ordine",
|
||||
"TableOptions": "Opzioni della tabella",
|
||||
@@ -452,7 +452,7 @@
|
||||
"RadarrSupportsAnyRSSMovieListsAsWellAsTheOneStatedBelow": "Radarr supporta qualunque Lista di film RSS, cosi come le altre sotto.",
|
||||
"RadarrSupportsAnyDownloadClient": "Radarr supporta qualunque client di download che usi gli standard Newznab, cosi come gli altri client sotto.",
|
||||
"QuickImport": "Sposta automaticamente",
|
||||
"Queued": "Messo in coda",
|
||||
"Queued": "In coda",
|
||||
"QualitySettings": "Impostazione di Qualità",
|
||||
"QualityProfileDeleteConfirm": "Sicuro di voler cancellare il profilo di qualità {0}",
|
||||
"QualityCutoffHasNotBeenMet": "Il qualità di taglio non è stata raggiunta",
|
||||
@@ -713,8 +713,8 @@
|
||||
"TimeFormat": "Formato orario",
|
||||
"ThisConditionMatchesUsingRegularExpressions": "Questa condizione si applica usando espressione regolari. Nota che i caratteri {0} hanno significati speciali e devono essere evitati con un {1}",
|
||||
"TestAllLists": "Testa tutte le liste",
|
||||
"TestAllIndexers": "Testa tutti gli Indicizzatori",
|
||||
"TestAllClients": "Testa Tutti i Client",
|
||||
"TestAllIndexers": "Prova tutti gli indicizzatori",
|
||||
"TestAllClients": "Testa tutti i client",
|
||||
"TagsHelpText": "Si applica ai film con almeno un tag corrispondente",
|
||||
"TagIsNotUsedAndCanBeDeleted": "L'etichetta non è in uso e può essere eliminata",
|
||||
"TagCannotBeDeletedWhileInUse": "Non può essere cancellato mentre è in uso",
|
||||
@@ -793,7 +793,7 @@
|
||||
"MovieDetailsPreviousMovie": "Dettagli del film: film precedente",
|
||||
"FocusSearchBox": "Evidenzia casella di ricerca",
|
||||
"CloseCurrentModal": "Chiudi la Modale Attuale",
|
||||
"AcceptConfirmationModal": "Acetta Conferma Modale",
|
||||
"AcceptConfirmationModal": "Accetta Conferma Modale",
|
||||
"StartSearchForMissingMovie": "Avvia ricerca film mancanti",
|
||||
"StartProcessing": "Avvia Lavorazione",
|
||||
"StartImport": "Avvia Importazione",
|
||||
@@ -842,7 +842,7 @@
|
||||
"AddQualityProfile": "Aggiungi Profilo Qualità",
|
||||
"AddNotification": "Aggiungi Notifica",
|
||||
"AddedToDownloadQueue": "Aggiunto alla coda di download",
|
||||
"AddDownloadClient": "Aggiungi Client di Download",
|
||||
"AddDownloadClient": "Aggiungi Downloader",
|
||||
"AddCustomFormat": "Aggiungi Formato Personalizzato",
|
||||
"Add": "Aggiungi",
|
||||
"ImportLibrary": "Importazione della libreria",
|
||||
@@ -1091,11 +1091,18 @@
|
||||
"ClickToChangeReleaseGroup": "Clicca per cambiare gruppo di rilascio",
|
||||
"DiscordUrlInSlackNotification": "Hai una notifica Discord configurata come notifica Slack. Configurala come notifica Discord per una miglior funzionalità. Le notifiche interessate sono: {0}",
|
||||
"Duration": "Durata",
|
||||
"InstanceNameHelpText": "Nome dell'istanza nella scheda e per il nome dell'applicazione Syslog",
|
||||
"InstanceNameHelpText": "Nome istanza nella scheda e per il nome dell'app nel Syslog",
|
||||
"InstanceName": "Nome Istanza",
|
||||
"AllCollectionsHiddenDueToFilter": "Tutti i film sono nascosti a causa del filtro applicato.",
|
||||
"Collections": "Collezione",
|
||||
"MonitorMovies": "Monitora Film",
|
||||
"NoCollections": "Nessun film trovato, per iniziare ti consigliamo di aggiungere un nuovo film o importarne alcuni esistenti.",
|
||||
"RssSyncHelpText": "Intervallo in minuti. Imposta zero per disabilitarlo (ciò fermerà il recupero automatico di tutte le release)"
|
||||
"RssSyncHelpText": "Intervallo in minuti. Imposta zero per disabilitarlo (ciò fermerà il recupero automatico di tutte le release)",
|
||||
"ApplicationURL": "URL Applicazione",
|
||||
"ApplicationUrlHelpText": "L'URL esterno di questa applicazione, incluso http(s)://, porta e URL base",
|
||||
"CollectionOptions": "Opzioni della Collezione",
|
||||
"CollectionShowDetailsHelpText": "Mostra lo stato e le proprietà della collezione",
|
||||
"Started": "Iniziato",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Sei sicuro di voler ripristinare le definizioni della qualità?",
|
||||
"ChooseImportMode": "Selezionare Metodo di Importazione"
|
||||
}
|
||||
|
||||
@@ -368,11 +368,11 @@
|
||||
"ChangeFileDate": "Wijzig Bestandsdatum",
|
||||
"CertificationCountryHelpText": "Selecteer Land voor Film Certificatie",
|
||||
"CertificationCountry": "Certificatie Land",
|
||||
"CertificateValidationHelpText": "Wijzig hoe strikt HTTPS certificaat validatie is",
|
||||
"CertificateValidationHelpText": "Wijzig hoe strict HTTPS certificaat validatie is. Wijzig dit niet behalve als je de risico's begrijpt.",
|
||||
"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}'",
|
||||
@@ -806,10 +806,10 @@
|
||||
"AllMoviesInPathHaveBeenImported": "Alle films in {0} zijn geïmporteerd",
|
||||
"AllFiles": "Alle bestanden",
|
||||
"AfterManualRefresh": "Na handmatig verversen",
|
||||
"AddToDownloadQueue": "Toegevoegd aan download wachtrij",
|
||||
"AddToDownloadQueue": "Toegevoegd aan downloadwachtrij",
|
||||
"AddQualityProfile": "Kwaliteitsprofiel toevoegen",
|
||||
"AddNotification": "Notificatie toevoegen",
|
||||
"AddedToDownloadQueue": "Aan de download wachtrij toegevoegd",
|
||||
"AddedToDownloadQueue": "Aan de download wachtrijtoegevoegd",
|
||||
"AddDownloadClient": "Download Client Toevoegen",
|
||||
"Add": "Toevoegen",
|
||||
"ChmodFolderHelpTextWarning": "Dit werkt alleen als de gebruiker die Radarr draait de eigenaar is van het bestand. Het is beter om zeker te zijn dat de downloader de juiste rechten zet.",
|
||||
@@ -1045,7 +1045,7 @@
|
||||
"DeleteFileLabel": "Verwijder {0} Film Bestanden",
|
||||
"UnableToAddRootFolder": "Kon hoofdmappen niet inladen",
|
||||
"UpdateAvailable": "Nieuwe update is beschikbaar",
|
||||
"From": "Van",
|
||||
"From": "van",
|
||||
"RemotePathMappingCheckDownloadPermissions": "Radarr kan gedownloade film {0} zien, maar niet openen. Waarschijnlijk fout met machtigingen.",
|
||||
"RemotePathMappingCheckFileRemoved": "Bestand {0} is halverwege de verwerking verwijderd.",
|
||||
"RemotePathMappingCheckFilesBadDockerPath": "U gebruikt docker; download client {0} gerapporteerde bestanden in {1} maar dit is geen geldig {2} pad. Controleer uw externe padtoewijzingen en download clientinstellingen.",
|
||||
@@ -1107,5 +1107,14 @@
|
||||
"Collections": "Collectie",
|
||||
"MonitorMovies": "Bewaak Film",
|
||||
"ApplicationURL": "Applicatie URL",
|
||||
"ApplicationUrlHelpText": "De externe URL van deze applicatie inclusief http(s)://,Port en URL base"
|
||||
"ApplicationUrlHelpText": "De externe URL van deze applicatie inclusief http(s)://,Port en URL base",
|
||||
"CollectionsSelectedInterp": "{0} Collectie(s) geselecteerd",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Weet je zeker dat je de kwaliteitsdefinities wilt resetten?",
|
||||
"ChooseImportMode": "Kies Importmodus",
|
||||
"CollectionOptions": "Collectieopties",
|
||||
"CollectionShowDetailsHelpText": "Collectie status en details weergeven",
|
||||
"CollectionShowOverviewsHelpText": "Collectieoverzicht weergeven",
|
||||
"EditCollection": "Bewerk collectie",
|
||||
"BypassDelayIfHighestQualityHelpText": "Vertraging negeren wanneer een release de hoogst mogelijke kwaliteit heeft in het kwaliteitsprofiel van het geprefereerde protocol",
|
||||
"CollectionShowPostersHelpText": "Posters van de collectie tonen"
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"IndexerStatusCheckAllClientMessage": "Todos os indexadores estão indisponíveis devido a falhas",
|
||||
"IndexersSettingsSummary": "Indexadores e restrições de lançamento",
|
||||
"IndexerSettings": "Configurações do indexador",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa interativa ativada, o Radarr não fornecerá nenhum resultado de pesquisa interativa",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa interativa habilitada, o Radarr não fornecerá nenhum resultado de pesquisa interativo",
|
||||
"IndexerSearchCheckNoAvailableIndexersMessage": "Todos os indexadores com capacidade de pesquisa estão temporariamente indisponíveis devido a erros recentes do indexador",
|
||||
"IndexerSearchCheckNoAutomaticMessage": "Nenhum indexador disponível com a Pesquisa automática habilitada, o Radarr não fornecerá nenhum resultado de pesquisa automática",
|
||||
"Indexers": "Indexadores",
|
||||
@@ -84,7 +84,7 @@
|
||||
"HealthNoIssues": "Nenhum problema com sua configuração",
|
||||
"Health": "Integridade",
|
||||
"HaveNotAddedMovies": "Você ainda não adicionou nenhum filme, deseja importar alguns ou todos os seus filmes primeiro?",
|
||||
"HardlinkCopyFiles": "Vínculo real/Copiar arquivos",
|
||||
"HardlinkCopyFiles": "Hardlink/Copiar arquivos",
|
||||
"Group": "Grupo",
|
||||
"GrabSelected": "Obter selecionado",
|
||||
"GrabReleaseMessageText": "O Radarr não conseguiu determinar a qual filme este lançamento está relacionado. O Radarr pode não conseguir importar automaticamente este lançamento. Quer obter \"{0}\"?",
|
||||
@@ -174,7 +174,7 @@
|
||||
"EditListExclusion": "Editar exclusão da lista",
|
||||
"Edition": "Edição",
|
||||
"EditIndexer": "Editar indexador",
|
||||
"EditGroups": "Editar grupos",
|
||||
"EditGroups": "Editar Grupos",
|
||||
"EditDelayProfile": "Editar perfil de atraso",
|
||||
"EditCustomFormat": "Editar formato personalizado",
|
||||
"Edit": "Editar",
|
||||
@@ -202,7 +202,7 @@
|
||||
"DownloadClient": "Cliente de download",
|
||||
"DoNotUpgradeAutomatically": "Não atualizar automaticamente",
|
||||
"DoNotPrefer": "Não preferir",
|
||||
"DoneEditingGroups": "Edição de grupos concluída",
|
||||
"DoneEditingGroups": "Concluído a Edição de Grupos",
|
||||
"Donations": "Doações",
|
||||
"DockerUpdater": "atualizar o contêiner do Docker para receber a atualização",
|
||||
"Docker": "Docker",
|
||||
@@ -340,7 +340,7 @@
|
||||
"BranchUpdateMechanism": "Ramificação usada pelo mecanismo de atualização externo",
|
||||
"BranchUpdate": "Ramificação para atualização do Radarr",
|
||||
"Branch": "Ramo",
|
||||
"BindAddressHelpText": "Endereço IPv4 Válido ou '*' para todas as interfaces",
|
||||
"BindAddressHelpText": "Endereço IP válido, localhost ou '*' para todas as interfaces",
|
||||
"BindAddress": "Fixar Endereço",
|
||||
"BeforeUpdate": "Antes da atualização",
|
||||
"Backups": "Backups",
|
||||
@@ -484,7 +484,7 @@
|
||||
"AddMovies": "Adicionar filmes",
|
||||
"AddMovie": "Adicionar filme",
|
||||
"AddListExclusion": "Adicionar exclusão à lista",
|
||||
"AddList": "Adicionar à Lista",
|
||||
"AddList": "Adicionar Lista",
|
||||
"AddingTag": "Adicionando etiqueta",
|
||||
"AddIndexer": "Adicionar indexador",
|
||||
"AddImportExclusionHelpText": "Impedir a adição do filme ao Radarr por listas",
|
||||
@@ -752,7 +752,7 @@
|
||||
"Restrictions": "Restrições",
|
||||
"RestoreBackup": "Restaurar backup",
|
||||
"Restore": "Restaurar",
|
||||
"RestartRequiredHelpTextWarning": "Requer reinicio para fazer efeito",
|
||||
"RestartRequiredHelpTextWarning": "Requer reinicialização para ter efeito",
|
||||
"RestartRadarr": "Reiniciar o Radarr",
|
||||
"RestartNow": "Reiniciar agora",
|
||||
"Restart": "Reiniciar",
|
||||
@@ -854,7 +854,7 @@
|
||||
"QualityDefinitions": "Definições de qualidade",
|
||||
"QualityCutoffHasNotBeenMet": "Limite de qualidade não atingido",
|
||||
"Quality": "Qualidade",
|
||||
"QualitiesHelpText": "As qualidades mais altas na lista são mais preferidas, mesmo que não estejam marcadas. As qualidades dentro do mesmo grupo são iguais. Apenas qualidades marcadas são desejadas",
|
||||
"QualitiesHelpText": "As qualidades mais altas na lista são mais preferidas, mesmo que não sejam verificadas. As qualidades dentro do mesmo grupo são iguais. Somente qualidades verificadas são desejadas",
|
||||
"Qualities": "Qualidades",
|
||||
"PublishedDate": "Data de publicação",
|
||||
"PtpOldSettingsCheckMessage": "As configurações dos seguintes indexadores do PassThePopcorn são obsoletas e devem ser atualizadas: {0}",
|
||||
@@ -868,7 +868,7 @@
|
||||
"Proxy": "Proxy",
|
||||
"ProtocolHelpText": "Escolha que protocolo(s) utilizar e qual o preferido ao escolher entre versões iguais",
|
||||
"Protocol": "Protocolo",
|
||||
"Proper": "Proper",
|
||||
"Proper": "Apropriado",
|
||||
"Progress": "Progresso",
|
||||
"ProfilesSettingsSummary": "Perfis de qualidade, idioma e atraso",
|
||||
"Profiles": "Perfis",
|
||||
@@ -923,7 +923,7 @@
|
||||
"OnRenameHelpText": "Ao renomear",
|
||||
"OnRename": "Ao Renomear",
|
||||
"OnlyUsenet": "Apenas Usenet",
|
||||
"OnlyTorrent": "Apenas torrents",
|
||||
"OnlyTorrent": "Apenas Torrents",
|
||||
"OnLatestVersion": "A versão mais recente do Radarr já está instalada",
|
||||
"OnImport": "Ao importar",
|
||||
"OnHealthIssueHelpText": "Ao ter problema de integridade",
|
||||
@@ -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}'.",
|
||||
@@ -1053,7 +1053,7 @@
|
||||
"RemotePathMappingCheckImportFailed": "O Radarr não conseguiu importar um filme. Verifique os logs para saber mais.",
|
||||
"RemotePathMappingCheckFileRemoved": "O arquivo {0} foi removido no meio do processamento.",
|
||||
"RemotePathMappingCheckDownloadPermissions": "O Radarr pode ver, mas não pode acessar o filme baixado {0}. Provável erro de permissões.",
|
||||
"RemotePathMappingCheckGenericPermissions": "O cliente de download {0} coloca downloads em {1} mas o Radarr não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.",
|
||||
"RemotePathMappingCheckGenericPermissions": "O cliente de download {0} coloca os downloads em {1}, mas o Radarr não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.",
|
||||
"RemotePathMappingCheckWrongOSPath": "O cliente de download remoto {0} coloca downloads em {1}, mas este não é um caminho {2} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.",
|
||||
"RemotePathMappingCheckLocalWrongOSPath": "O cliente de download local {0} coloca downloads em {1}, mas este não é um caminho {2} válido. Revise as configurações do seu cliente de download.",
|
||||
"RemotePathMappingCheckLocalFolderMissing": "O cliente de download remoto {0} coloca downloads em {1}, mas esse diretório parece não existir. Mapeamento de caminho remoto provavelmente ausente ou incorreto.",
|
||||
@@ -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",
|
||||
@@ -1087,7 +1087,7 @@
|
||||
"OnApplicationUpdateHelpText": "Na Atualização do Aplicativo",
|
||||
"DiscordUrlInSlackNotification": "Você tem uma notificação do Discord configurado como uma notificação do Slack. Definir isso como uma notificação do Discord para melhor funcionalidade. Com efeito, notificações são: {0}",
|
||||
"AnnouncedMsg": "Filme foi anunciado",
|
||||
"IndexerDownloadClientHelpText": "Especificar em que cliente de download é usado para baixar deste indexador",
|
||||
"IndexerDownloadClientHelpText": "Especificar qual cliente de download é utilizado para baixar a partir deste indexador",
|
||||
"LocalPath": "Caminho Local",
|
||||
"ManualImportSetReleaseGroup": "Importar Manual - Definir Grupo de Lançamento",
|
||||
"SelectLanguages": "Selecionar Idiomas",
|
||||
@@ -1144,7 +1144,15 @@
|
||||
"CollectionShowPostersHelpText": "Mostrar pôsteres de itens da coleção",
|
||||
"RottenTomatoesRating": "Avaliação Tomato",
|
||||
"TotalMovies": "Total de Filmes",
|
||||
"ApplicationURL": "URL da Aplicação",
|
||||
"ApplicationUrlHelpText": "O URL externa deste aplicativo, incluindo http(s)://, porta e base de URL",
|
||||
"PreferredProtocol": "Protocolo Preferido"
|
||||
"ApplicationURL": "URL do Aplicativo",
|
||||
"ApplicationUrlHelpText": "A URL externa deste aplicativo, incluindo http(s)://, porta e base da URL",
|
||||
"PreferredProtocol": "Protocolo Preferido",
|
||||
"SettingsThemeHelpText": "Alterar o tema da interface do usuário do aplicativo, o tema 'Auto' usará o tema do sistema operacional para definir o modo Claro ou Escuro. Inspirado por Theme.Park",
|
||||
"ResetDefinitions": "Redefinir definições",
|
||||
"ResetQualityDefinitions": "Redefinir Definições de Qualidade",
|
||||
"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?",
|
||||
"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": "Колонки",
|
||||
@@ -218,5 +218,941 @@
|
||||
"ImportCustomFormat": "Додати свій формат",
|
||||
"DeleteRestrictionHelpText": "Ви впевнені, що хочете видалити цей профіль затримки?",
|
||||
"AllCollectionsHiddenDueToFilter": "Всі фільми заховані відповідно до фільтра.",
|
||||
"Collections": "Колекція"
|
||||
"Collections": "Колекція",
|
||||
"CollectionOptions": "Параметри колекції",
|
||||
"CollectionShowDetailsHelpText": "Показати статус і властивості колекції",
|
||||
"CollectionShowOverviewsHelpText": "Показати огляд колекції",
|
||||
"CollectionShowPostersHelpText": "Показати плакати предметів колекції",
|
||||
"DeleteList": "Видалити список",
|
||||
"DeleteMovieFolderHelpText": "Видалити папку з фільмами та її вміст",
|
||||
"DeleteImportListExclusion": "Видалити виключення зі списку імпорту",
|
||||
"DeleteIndexer": "Видалити індексатор",
|
||||
"DeleteMovieFolderLabel": "Видалити папку з фільмами",
|
||||
"DeleteSelectedMovie": "Видалити вибрані фільм(и)",
|
||||
"Enable": "Увімкнути",
|
||||
"EnableAutomaticAdd": "Увімкнути автоматичне додавання",
|
||||
"EnableAutomaticSearchHelpText": "Використовуватиметься, коли автоматичний пошук виконується через інтерфейс користувача або Radarr",
|
||||
"Ended": "Завершено",
|
||||
"Fixed": "Виправлено",
|
||||
"FocusSearchBox": "Перейти до вікна пошуку",
|
||||
"Filters": "Фільтри",
|
||||
"FirstDayOfWeek": "Перший день тижня",
|
||||
"IgnoreDeletedMovies": "Відключити моніторинг видалених фільмів",
|
||||
"IgnoredPlaceHolder": "Додайте нове обмеження",
|
||||
"ImportedTo": "Імпортовано в",
|
||||
"ImportErrors": "Помилки імпорту",
|
||||
"Imported": "Імпортні",
|
||||
"IndexersSettingsSummary": "Індексатори та обмеження випуску",
|
||||
"LogLevelTraceHelpTextWarning": "Журнал трасування слід увімкнути лише тимчасово",
|
||||
"LogLevel": "Рівень журналу",
|
||||
"Monday": "Понеділок",
|
||||
"MonitoredHelpText": "Завантажте фільм, якщо є",
|
||||
"MoreDetails": "Детальніше",
|
||||
"MountCheckMessage": "Монтування, що містить шлях до фільму, монтується лише для читання: ",
|
||||
"MovieIsRecommend": "Фільм рекомендовано на основі останнього додавання",
|
||||
"MovieIsOnImportExclusionList": "Фільм у списку винятків для імпорту",
|
||||
"MultiLanguage": "Багатомовність",
|
||||
"NoLeaveIt": "Ні, залиште це",
|
||||
"NoLimitForAnyRuntime": "Немає обмежень для будь-якого часу виконання",
|
||||
"NoCollections": "Колекції не знайдено. Щоб почати, ви захочете додати новий фільм або імпортувати кілька наявних",
|
||||
"NoEventsFound": "Подій не знайдено",
|
||||
"NoHistory": "Без історії",
|
||||
"None": "Жодного",
|
||||
"OnHealthIssue": "Про питання здоров'я",
|
||||
"RemotePathMappingCheckDownloadPermissions": "Radarr може бачити, але не має доступу до завантаженого фільму {0}. Ймовірна помилка дозволів.",
|
||||
"Remove": "Видалити",
|
||||
"RemotePathMappingCheckWrongOSPath": "Клієнт віддаленого завантаження {0} розміщує завантаження в {1}, але це недійсний шлях {2}. Перегляньте свої віддалені відображення шляхів і завантажте налаштування клієнта.",
|
||||
"ReplaceWithSpaceDashSpace": "Замінити на Пробіл Тире Пробіл",
|
||||
"RequiredHelpText": "Ця умова {0} має збігатися, щоб користувацький формат застосовувався. В іншому випадку достатньо одного збігу {1}.",
|
||||
"RequiredRestrictionHelpText": "Реліз має містити принаймні один із цих термінів (незалежно від регістру)",
|
||||
"RequiredRestrictionPlaceHolder": "Додайте нове обмеження",
|
||||
"Required": "Обов'язковий",
|
||||
"SelectLanguage": "Оберіть мову",
|
||||
"UnableToLoadDownloadClients": "Не вдалося завантажити клієнти для завантаження",
|
||||
"OnApplicationUpdate": "Оновлення програми",
|
||||
"OutputPath": "Вихідний шлях",
|
||||
"Overview": "Огляд",
|
||||
"ProxyType": "Тип проксі",
|
||||
"RecyclingBin": "Сміттєвий кошик",
|
||||
"Result": "Результат",
|
||||
"Retention": "Утримання",
|
||||
"RetryingDownloadInterp": "Повторна спроба завантажити {0} о {1}",
|
||||
"Settings": "Налаштування",
|
||||
"ShowSizeOnDisk": "Показати розмір на диску",
|
||||
"ShowTitle": "Показати назву",
|
||||
"ShowStudio": "Показати Студію",
|
||||
"UnableToLoadCustomFormats": "Не вдалося завантажити спеціальні формати",
|
||||
"Auto": "Авто",
|
||||
"Downloading": "Завантаження",
|
||||
"EnableColorImpairedMode": "Увімкнути режим із порушенням кольору",
|
||||
"Duration": "Тривалість",
|
||||
"EnableHelpText": "Увімкнути створення файлу метаданих для цього типу метаданих",
|
||||
"Error": "Помилка",
|
||||
"ErrorLoadingContents": "Помилка завантаження вмісту",
|
||||
"ErrorLoadingPreviews": "Помилка завантаження попереднього перегляду",
|
||||
"ErrorRestoringBackup": "Помилка відновлення резервної копії",
|
||||
"Events": "Події",
|
||||
"FailedLoadingSearchResults": "Не вдалося завантажити результати пошуку, повторіть спробу.",
|
||||
"FailedToLoadMovieFromAPI": "Не вдалося завантажити фільм із API",
|
||||
"FileDateHelpText": "Змінити дату файлу під час імпорту/повторного сканування",
|
||||
"Files": "Файли",
|
||||
"Filter": "Фільтр",
|
||||
"Folders": "Папки",
|
||||
"FollowPerson": "Слідкуйте за особою",
|
||||
"From": "від",
|
||||
"HideAdvanced": "Сховати додаткові",
|
||||
"History": "Історія",
|
||||
"MinimumAge": "Мінімальний вік",
|
||||
"MinimumAvailability": "Мінімальна доступність",
|
||||
"MinimumCustomFormatScore": "Мінімальна оцінка спеціального формату",
|
||||
"MovieDetailsPreviousMovie": "Відомості про фільм: попередній фільм",
|
||||
"MovieEditor": "Редактор фільмів",
|
||||
"PtpOldSettingsCheckMessage": "Наведені нижче індексатори PassThePopcorn мають застарілі налаштування та їх потрібно оновити: {0}",
|
||||
"ProxyPasswordHelpText": "Вам потрібно лише ввести ім’я користувача та пароль, якщо вони потрібні. В іншому випадку залиште їх порожніми.",
|
||||
"PublishedDate": "Дата публікації",
|
||||
"QualityDefinitions": "Визначення якості",
|
||||
"QualityLimitsHelpText": "Обмеження автоматично регулюються для тривалості фільму.",
|
||||
"RemotePathMappingCheckFilesGenericPermissions": "Завантажте файли звітів клієнта {0} в {1}, але Radarr не бачить цей каталог. Можливо, вам знадобиться налаштувати дозволи для папки.",
|
||||
"RescanMovieFolderAfterRefresh": "Перескануйте папку фільму після оновлення",
|
||||
"Reset": "Скинути",
|
||||
"ResetAPIKey": "Скинути ключ API",
|
||||
"Restart": "Перезавантажити",
|
||||
"RestartRadarr": "Перезавантажити Radarr",
|
||||
"RestartRequiredHelpTextWarning": "Щоб набуло чинності, потрібно перезапустити",
|
||||
"RestoreBackup": "Відновлення резервної копії",
|
||||
"Restrictions": "Обмеження",
|
||||
"RootFolder": "Коренева папка",
|
||||
"RootFolderCheckMultipleMessage": "Відсутні кілька кореневих папок: {0}",
|
||||
"Runtime": "Час виконання",
|
||||
"Search": "Пошук",
|
||||
"SearchAll": "Пошук у всіх",
|
||||
"UsenetDelay": "Затримка Usenet",
|
||||
"Downloaded": "Завантажено",
|
||||
"Existing": "Існуючий",
|
||||
"ImportFailed": "Помилка імпорту: {0}",
|
||||
"Importing": "Імпорт",
|
||||
"InCinemas": "У кінотеатрах",
|
||||
"IncludeRadarrRecommendations": "Включити рекомендації Radarr",
|
||||
"Indexer": "Індексатор",
|
||||
"Language": "Мова",
|
||||
"MetadataSettingsSummary": "Створюйте файли метаданих, коли фільми імпортуються або оновлюються",
|
||||
"Month": "Місяць",
|
||||
"NoMoveFilesSelf": " Ні, я сам перенесу файли",
|
||||
"Sunday": "Неділя",
|
||||
"Tasks": "Задачі",
|
||||
"Today": "Сьогодні",
|
||||
"Tomorrow": "Завтра",
|
||||
"TorrentDelay": "Затримка торрента",
|
||||
"OnMovieAddedHelpText": "Фільм додано",
|
||||
"OnMovieAdded": "У фільмі додано",
|
||||
"OnMovieFileDelete": "Видаленні відеофайлу",
|
||||
"OnMovieFileDeleteForUpgrade": "Видалити відеофайл для оновлення",
|
||||
"OnMovieDelete": "Фільм видалено",
|
||||
"OnRename": "При перейменуванні",
|
||||
"OnMovieFileDeleteHelpText": "Видаленні відеофайлу",
|
||||
"OnRenameHelpText": "При перейменуванні",
|
||||
"OpenThisModal": "Відкрийте цей модальний вікно",
|
||||
"PageSize": "Розмір сторінки",
|
||||
"PageSizeHelpText": "Кількість елементів для показу на кожній сторінці",
|
||||
"Password": "Пароль",
|
||||
"Path": "Шлях",
|
||||
"OriginalTitle": "Оригінальна назва",
|
||||
"Presets": "Предустановки",
|
||||
"PreviewRename": "Попередній перегляд Перейменування",
|
||||
"PreviewRenameHelpText": "Порада: щоб переглянути перейменування... виберіть «Скасувати», потім клацніть назву будь-якого фільму та скористайтеся",
|
||||
"Priority": "Пріоритет",
|
||||
"Profiles": "Профілі",
|
||||
"ProcessingFolders": "Обробка папок",
|
||||
"Proxy": "Проксі",
|
||||
"ProxyBypassFilterHelpText": "Використовуйте «,» як роздільник і «*». як символ підстановки для субдоменів",
|
||||
"ProtocolHelpText": "Виберіть протокол(и) для використання та який із них є кращим під час вибору між однаковими випусками",
|
||||
"ProxyUsernameHelpText": "Вам потрібно лише ввести ім’я користувача та пароль, якщо вони потрібні. В іншому випадку залиште їх порожніми.",
|
||||
"QualitySettings": "Налаштування якості",
|
||||
"QualitySettingsSummary": "Якісні розміри та найменування",
|
||||
"QualityProfiles": "Профілі якості",
|
||||
"Queue": "Черга",
|
||||
"Queued": "У черзі",
|
||||
"RadarrSupportsAnyIndexer": "Radarr підтримує будь-який індексатор, який використовує стандарт Newznab, а також інші індексатори, перелічені нижче.",
|
||||
"RadarrSupportsAnyDownloadClient": "Radarr підтримує багато популярних торрент-клієнтів і клієнтів для завантаження через Usenet.",
|
||||
"RadarrSupportsAnyRSSMovieListsAsWellAsTheOneStatedBelow": "Radarr підтримує будь-які списки фільмів RSS, а також наведений нижче.",
|
||||
"RecentChanges": "Останні зміни",
|
||||
"Reason": "Причина",
|
||||
"RecentFolders": "Останні папки",
|
||||
"RecycleBinCleanupDaysHelpText": "Встановіть значення 0, щоб вимкнути автоматичне очищення",
|
||||
"RecyclingBinCleanup": "Очищення сміттєвого кошика",
|
||||
"RefreshInformationAndScanDisk": "Оновити інформацію та сканувати диск",
|
||||
"RefreshCollections": "Оновити колекції",
|
||||
"RefreshLists": "Оновити списки",
|
||||
"RefreshMonitoredIntervalHelpText": "Як часто оновлювати відстежувані завантаження з клієнтів завантаження, мінімум 1 хвилина",
|
||||
"ReleaseDates": "Дати випуску",
|
||||
"ReleasedMsg": "Фільм виходить",
|
||||
"ReleaseGroup": "Група випуску",
|
||||
"ReleaseRejected": "Реліз відхилено",
|
||||
"RemoveFailed": "Не вдалося видалити",
|
||||
"RemoveDownloadsAlert": "Параметри видалення переміщено до окремих налаштувань клієнта завантаження в таблиці вище.",
|
||||
"RetentionHelpText": "Лише Usenet: встановіть нуль, щоб налаштувати необмежену утримку",
|
||||
"Seconds": "Секунди",
|
||||
"Security": "Безпека",
|
||||
"SelectMovie": "Виберіть Фільм",
|
||||
"SetPermissionsLinuxHelpTextWarning": "Якщо ви не впевнені, що ці налаштування роблять, не змінюйте їх.",
|
||||
"SetTags": "Встановити теги",
|
||||
"SettingsFirstDayOfWeek": "Перший день тижня",
|
||||
"SettingsRemotePathMappingRemotePathHelpText": "Кореневий шлях до каталогу, до якого має доступ клієнт завантаження",
|
||||
"SettingsRuntimeFormat": "Формат виконання",
|
||||
"SettingsWeekColumnHeader": "Заголовок стовпця тижня",
|
||||
"SettingsShowRelativeDatesHelpText": "Показати відносні (сьогодні/вчора/тощо) або абсолютні дати",
|
||||
"ShowMovieInformation": "Показати інформацію про фільм",
|
||||
"ShowMovieInformationHelpText": "Показати жанри фільмів і сертифікати",
|
||||
"ShownClickToHide": "Показано, натисніть, щоб приховати",
|
||||
"ShowOverview": "Показати огляд",
|
||||
"ShowMonitoredHelpText": "Показати відстежуваний статус під плакатом",
|
||||
"ShowSearchHelpText": "Показувати кнопку пошуку при наведенні",
|
||||
"Size": "Розмір",
|
||||
"StartupDirectory": "Каталог запуску",
|
||||
"ThisConditionMatchesUsingRegularExpressions": "Ця умова відповідає використанню регулярних виразів. Зауважте, що символи {0} мають особливі значення та потребують екранування за допомогою {1}",
|
||||
"UILanguage": "Мова інтерфейсу користувача",
|
||||
"UnableToAddANewCustomFormatPleaseTryAgain": "Не вдалося додати новий спеціальний формат, спробуйте ще раз.",
|
||||
"UnableToAddANewDownloadClientPleaseTryAgain": "Не вдається додати новий клієнт для завантаження, повторіть спробу.",
|
||||
"UnableToAddANewIndexerPleaseTryAgain": "Не вдалося додати новий індексатор, спробуйте ще раз.",
|
||||
"UnableToAddANewListExclusionPleaseTryAgain": "Не вдається додати нове виключення зі списку, повторіть спробу.",
|
||||
"UnableToAddANewListPleaseTryAgain": "Не вдалося додати новий список, спробуйте ще раз.",
|
||||
"UnableToLoadIndexers": "Не вдалося завантажити індексатори",
|
||||
"UnableToLoadQualities": "Неможливо завантажити якості",
|
||||
"UpperCase": "Великі літери",
|
||||
"TotalMovies": "Всього фільмів",
|
||||
"TotalSpace": "Загальний простір",
|
||||
"UISettings": "Налаштування інтерфейсу користувача",
|
||||
"UILanguageHelpText": "Мова, яку Radarr використовуватиме для інтерфейсу користувача",
|
||||
"UILanguageHelpTextWarning": "Потрібно перезавантажити браузер",
|
||||
"UnableToLoadBackups": "Не вдалося завантажити резервні копії",
|
||||
"UnableToLoadBlocklist": "Не вдалося завантажити список блокувань",
|
||||
"UnableToLoadCollections": "Не вдалося завантажити колекції",
|
||||
"UnableToLoadAltTitle": "Не вдалося завантажити альтернативні назви.",
|
||||
"UnableToLoadNamingSettings": "Не вдалося завантажити налаштування імен",
|
||||
"UnableToLoadRestrictions": "Не вдалося завантажити обмеження",
|
||||
"UnableToLoadTheCalendar": "Неможливо завантажити календар",
|
||||
"UnableToLoadTags": "Не вдалося завантажити теги",
|
||||
"Unreleased": "Недоступний",
|
||||
"UnsavedChanges": "Незбережені зміни",
|
||||
"Indexers": "Індексатори",
|
||||
"ListTagsHelpText": "Буде додано елементи списку тегів",
|
||||
"ListUpdateInterval": "Інтервал оновлення списку",
|
||||
"LoadingMovieCreditsFailed": "Не вдалося завантажити титри фільму",
|
||||
"MustContain": "Має містити",
|
||||
"PreferUsenet": "Віддавайте перевагу Usenet",
|
||||
"SSLCertPathHelpText": "Шлях до файлу pfx",
|
||||
"StartProcessing": "Почати обробку",
|
||||
"Waiting": "Очікування",
|
||||
"WaitingToImport": "Очікування імпорту",
|
||||
"WaitingToProcess": "Очікування обробки",
|
||||
"VisitGithubCustomFormatsAphrodite": "Відвідайте вікі для отримання додаткової інформації: ",
|
||||
"EnableAutoHelpText": "Якщо ввімкнено, фільми автоматично додаватимуться до Radarr із цього списку",
|
||||
"Enabled": "Увімкнено",
|
||||
"EnabledHelpText": "Увімкнути цей список для використання в Radarr",
|
||||
"EnableMediaInfoHelpText": "Отримайте з файлів інформацію про відео, таку як роздільна здатність, час виконання та кодек. Це вимагає, щоб Radarr читав частини файлу, що може спричинити високу дискову або мережеву активність під час сканування.",
|
||||
"ExtraFileExtensionsHelpTexts1": "Розділений комами список додаткових файлів для імпорту (.nfo буде імпортовано як .nfo-orig)",
|
||||
"FileNameTokens": "Маркери імен файлів",
|
||||
"FolderMoveRenameWarning": "Це також перейменує папку з фільмами відповідно до формату папки з фільмами в налаштуваннях.",
|
||||
"IndexerSearchCheckNoAutomaticMessage": "Немає доступних індексаторів із увімкненим автоматичним пошуком, Radarr не надаватиме автоматичних результатів пошуку",
|
||||
"HiddenClickToShow": "Приховано, натисніть, щоб показати",
|
||||
"HomePage": "Домашня сторінка",
|
||||
"IgnoredHelpText": "Випуск буде відхилено, якщо він містить один або кілька термінів (незалежно від регістру)",
|
||||
"ImportExtraFilesHelpText": "Імпортуйте відповідні додаткові файли (субтитри, nfo тощо) після імпортування файлу фільму",
|
||||
"ImportListMissingRoot": "Відсутня коренева папка для списків імпорту: {0}",
|
||||
"IncludeRecommendationsHelpText": "Включіть рекомендовані Radarr фільми в режим Discovery",
|
||||
"IndexerLongTermStatusCheckAllClientMessage": "Усі індексатори недоступні через збої більше 6 годин",
|
||||
"DestinationRelativePath": "Відносний шлях призначення",
|
||||
"EditCollection": "Редагувати колекцію",
|
||||
"ForMoreInformationOnTheIndividualDownloadClients": "Щоб отримати додаткові відомості про окремі клієнти для завантаження, натисніть кнопки додаткових відомостей.",
|
||||
"IndexerTagHelpText": "Використовуйте цей індексатор лише для фільмів із принаймні одним відповідним тегом. Залиште поле порожнім для використання з усіма фільмами.",
|
||||
"Info": "Інформація",
|
||||
"InstanceName": "Ім'я екземпляра",
|
||||
"InstanceNameHelpText": "Ім’я екземпляра на вкладці та ім’я програми Syslog",
|
||||
"KeepAndUnmonitorMovie": "Зберегти та скасувати моніторинг фільму",
|
||||
"LaunchBrowserHelpText": " Відкрийте веб-браузер і перейдіть на домашню сторінку Radarr під час запуску програми.",
|
||||
"ListSyncLevelHelpText": "Фільми з бібліотеки буде видалено або відстежуватись не буде, якщо їх немає у вашому списку",
|
||||
"LocalPath": "Місцевий шлях",
|
||||
"LowerCase": "Малі літери",
|
||||
"MaximumLimits": "Максимальні обмеження",
|
||||
"MinimumAgeHelpText": "Тільки Usenet: мінімальний вік NZB у хвилинах до їх захоплення. Використовуйте це, щоб дати новим випускам час для поширення до вашого провайдера usenet.",
|
||||
"MinimumFreeSpace": "Мінімальний вільний простір",
|
||||
"MinimumLimits": "Мінімальні обмеження",
|
||||
"MovieCollectionMissingRoot": "Відсутня коренева папка для колекції фільмів: {0}",
|
||||
"MoveFolders1": "Бажаєте перемістити папки з фільмами до \"{0}\"?",
|
||||
"MoveFolders2": "Бажаєте перемістити файли фільму з \"{0}\" до \"{1}\"?",
|
||||
"MovieFolderFormat": "Формат папки фільму",
|
||||
"MovieIndexScrollTop": "Індекс фільму: прокрутка вгору",
|
||||
"MovieInfoLanguageHelpText": "Мова, яку Radarr використовуватиме для інформації про фільм в інтерфейсі користувача",
|
||||
"MovieIsDownloadingInterp": "Фільм завантажується - {0}% {1}",
|
||||
"NextExecution": "Наступне виконання",
|
||||
"OnMovieFileDeleteForUpgradeHelpText": "Видалити відеофайл для оновлення",
|
||||
"OrganizeModalDisabled": "Перейменування вимкнено, перейменовувати нічого",
|
||||
"OrganizeModalSuccess": "Успіх! Мою роботу виконано, немає файлів для перейменування.",
|
||||
"OrganizeSelectedMovies": "Упорядкуйте вибрані фільми",
|
||||
"PendingChangesMessage": "У вас є незбережені зміни. Ви впевнені, що бажаєте залишити цю сторінку?",
|
||||
"PosterSize": "Розмір плаката",
|
||||
"PreferredProtocol": "Переважний протокол",
|
||||
"PriorityHelpText": "Надайте пріоритет кільком клієнтам завантаження. Круговий алгоритм використовується для клієнтів з таким же пріоритетом.",
|
||||
"ProxyCheckBadRequestMessage": "Не вдалося перевірити проксі. Код стану: {0}",
|
||||
"ProxyCheckResolveIpMessage": "Не вдалося визначити IP-адресу для налаштованого проксі-сервера {0}",
|
||||
"QualitiesHelpText": "Якості, які стоять вище в списку, є кращими, навіть якщо не позначено. Якості в одній групі рівні. Потрібні тільки перевірені якості",
|
||||
"QualityProfileInUse": "Неможливо видалити профіль якості, доданий до фільму, списку або колекції",
|
||||
"RemotePathMappingCheckBadDockerPath": "Ви використовуєте docker; клієнт завантаження {0} розміщує завантаження в {1}, але це недійсний шлях {2}. Перегляньте свої віддалені відображення шляхів і завантажте налаштування клієнта.",
|
||||
"RemotePathMappingCheckRemoteDownloadClient": "Клієнт віддаленого завантаження {0} повідомив про файли в {1}, але цей каталог, здається, не існує. Ймовірно, відсутнє відображення віддаленого шляху.",
|
||||
"RemoveSelected": "Видалити вибране",
|
||||
"ReplaceIllegalCharactersHelpText": "Замінити недопустимі символи. Якщо не позначено, Radarr видалить їх",
|
||||
"RescanAfterRefreshHelpTextWarning": "Radarr не визначатиме автоматично зміни файлів, якщо не встановлено значення «Завжди»",
|
||||
"RestartReloadNote": "Примітка: Radarr автоматично перезапуститься та перезавантажить інтерфейс під час процесу відновлення.",
|
||||
"RottenTomatoesRating": "Рейтинг томатів",
|
||||
"RssSyncHelpText": "Інтервал у хвилинах. Встановіть нуль, щоб вимкнути (це зупинить автоматичне захоплення звільнення)",
|
||||
"ScriptPath": "Шлях сценарію",
|
||||
"SearchFailedPleaseTryAgainLater": "Помилка пошуку, спробуйте пізніше.",
|
||||
"SearchOnAddCollectionHelpText": "Шукайте фільми в цій колекції після додавання в бібліотеку",
|
||||
"SelectReleaseGroup": "Виберіть Release Group",
|
||||
"SendAnonymousUsageData": "Надсилати анонімні дані про використання",
|
||||
"SettingsEnableColorImpairedMode": "Увімкнути режим із порушенням кольору",
|
||||
"SettingsEnableColorImpairedModeHelpText": "Змінений стиль, щоб користувачі з вадами кольору могли краще розрізняти кольорову кодовану інформацію",
|
||||
"SettingsThemeHelpText": "Змініть тему інтерфейсу додатка, тема «Авто» використовуватиме вашу тему ОС, щоб установити світлий або темний режим. Натхненний Theme.Park",
|
||||
"SettingsWeekColumnHeaderHelpText": "Відображається над кожним стовпцем, коли тиждень є активним переглядом",
|
||||
"ShowCertification": "Показати сертифікат",
|
||||
"ShowCollectionDetails": "Показати статус колекції",
|
||||
"ShowPath": "Показати шлях",
|
||||
"ShowQualityProfileHelpText": "Покажіть якісний профіль під плакатом",
|
||||
"ShowTitleHelpText": "Показати назву фільму під постером",
|
||||
"SkipFreeSpaceCheck": "Пропустити перевірку вільного місця",
|
||||
"SkipFreeSpaceCheckWhenImportingHelpText": "Використовуйте, коли Radarr не може виявити вільне місце в кореневій папці фільму",
|
||||
"SqliteVersionCheckUpgradeRequiredMessage": "Наразі встановлена версія SQLite {0} більше не підтримується. Оновіть SQLite принаймні до версії {1}.",
|
||||
"TheLogLevelDefault": "Рівень журналу за замовчуванням має значення «Інформація», і його можна змінити",
|
||||
"ThisCannotBeCancelled": "Це не можна скасувати після запуску без вимкнення всіх ваших індексаторів.",
|
||||
"TorrentDelayHelpText": "Затримка в хвилинах, щоб зачекати, перш ніж захопити торрент",
|
||||
"UISettingsSummary": "Параметри календаря, дати та кольору",
|
||||
"UnableToAddANewNotificationPleaseTryAgain": "Не вдалося додати нове сповіщення, спробуйте ще раз.",
|
||||
"UnableToAddANewRemotePathMappingPleaseTryAgain": "Не вдалося додати нове зіставлення віддаленого шляху, спробуйте ще раз.",
|
||||
"UnableToLoadDownloadClientOptions": "Не вдалося завантажити параметри клієнта для завантаження",
|
||||
"UnableToLoadListExclusions": "Неможливо завантажити список винятків",
|
||||
"UnableToLoadManualImportItems": "Не вдалося завантажити елементи ручного імпорту",
|
||||
"UnableToLoadMovies": "Неможливо завантажити фільми",
|
||||
"UnableToLoadQualityProfiles": "Не вдалося завантажити профілі якості",
|
||||
"UnableToLoadResultsIntSearch": "Неможливо завантажити результати для цього пошуку фільмів. Спробуйте ще раз пізніше",
|
||||
"Unmonitored": "Неконтрольований",
|
||||
"UnmonitoredHelpText": "Включайте неконтрольовані фільми в канал iCal",
|
||||
"IndexerJackettAll": "Індексатори, які використовують непідтримувану кінцеву точку Jackett 'all': {0}",
|
||||
"UpdateAutomaticallyHelpText": "Автоматичне завантаження та встановлення оновлень. Ви все ще зможете встановити з System: Updates",
|
||||
"UpdateCheckStartupNotWritableMessage": "Неможливо встановити оновлення, оскільки папка запуску \"{0}\" не може бути записана користувачем \"{1}\".",
|
||||
"UpdateCheckUINotWritableMessage": "Неможливо встановити оновлення, оскільки папка інтерфейсу користувача \"{0}\" не може бути записана користувачем \"{1}\".",
|
||||
"UpgradeUntilCustomFormatScore": "Оновлення до оцінки спеціального формату",
|
||||
"UpgradeUntilThisQualityIsMetOrExceeded": "Оновлюйте, поки ця якість не буде досягнута або перевищена",
|
||||
"UsenetDelayHelpText": "Затримка в хвилинах, щоб зачекати, перш ніж отримати випуск від Usenet",
|
||||
"VersionUpdateText": "Встановлено версію Radarr {0}. Щоб отримати останні зміни, потрібно перезавантажити Radarr.",
|
||||
"WhitelistedHardcodedSubsHelpText": "Встановлені тут теги субтитрів не вважатимуться жорстко закодованими",
|
||||
"DockerUpdater": "оновіть контейнер docker, щоб отримати оновлення",
|
||||
"EnableAutomaticSearch": "Увімкнути автоматичний пошук",
|
||||
"Images": "Зображення",
|
||||
"IMDb": "IMDb",
|
||||
"Import": "Імпорт",
|
||||
"LastUsed": "Останнє використання",
|
||||
"Lists": "Списки",
|
||||
"NoVideoFilesFoundSelectedFolder": "У вибраній папці не знайдено відеофайлів",
|
||||
"Released": "Випуск",
|
||||
"SourcePath": "Вихідний шлях",
|
||||
"UnableToLoadUISettings": "Не вдалося завантажити налаштування інтерфейсу користувача",
|
||||
"WouldYouLikeToRestoreBackup": "Бажаєте відновити резервну копію {0}?",
|
||||
"IndexerFlags": "Прапори індексатора",
|
||||
"OnMovieDeleteHelpText": "Фільм видалено",
|
||||
"TagDetails": "Деталі тегу - {0}",
|
||||
"DiscordUrlInSlackNotification": "Ви налаштували сповіщення Discord як сповіщення Slack. Налаштуйте це як сповіщення Discord для кращої роботи. Сповіщення, на які впливає: {0}",
|
||||
"Missing": "Відсутня",
|
||||
"SettingsTimeFormat": "Формат часу",
|
||||
"iCalLink": "Посилання iCal",
|
||||
"InCinemasDate": "У кінотеатрах Дата",
|
||||
"IndexerSettings": "Налаштування індексатора",
|
||||
"List": "Список",
|
||||
"ListExclusions": "Список виключень",
|
||||
"ListSettings": "Параметри списку",
|
||||
"LinkHere": "тут",
|
||||
"Links": "Посилання",
|
||||
"ListsSettingsSummary": "Імпорт списків, список виключень",
|
||||
"OrganizeConfirm": "Ви впевнені, що бажаєте впорядкувати всі файли у {0} вибраних фільмах?",
|
||||
"OrganizeModalAllPathsRelative": "Усі шляхи відносяться до:",
|
||||
"Options": "Опції",
|
||||
"Organize": "Організувати",
|
||||
"OrganizeAndRename": "Організувати та перейменувати",
|
||||
"OverviewOptions": "Параметри огляду",
|
||||
"PackageVersion": "Версія пакета",
|
||||
"Quality": "Якість",
|
||||
"Time": "Час",
|
||||
"UsenetDelayTime": "Затримка Usenet: {0}",
|
||||
"ChooseImportMode": "Виберіть режим імпорту",
|
||||
"DeleteHeader": "Видалити - {0}",
|
||||
"IncludeCustomFormatWhenRenaming": "Включати спеціальний формат під час перейменування",
|
||||
"IncludeCustomFormatWhenRenamingHelpText": "Включіть у формат перейменування {Custom Formats}",
|
||||
"MaximumSizeHelpText": "Максимальний розмір випуску для захоплення в МБ. Встановіть нуль, щоб встановити необмежений",
|
||||
"Ungroup": "Розгрупувати",
|
||||
"InteractiveImport": "Інтерактивний імпорт",
|
||||
"Large": "Великий",
|
||||
"LastDuration": "Остання тривалість",
|
||||
"Scheduled": "За розкладом",
|
||||
"UpdateSelected": "Оновити вибране",
|
||||
"PreferTorrent": "Віддаю перевагу Torrent",
|
||||
"PrioritySettings": "Пріоритет: {0}",
|
||||
"ProxyCheckFailedToTestMessage": "Не вдалося перевірити проксі: {0}",
|
||||
"Qualities": "Якості",
|
||||
"QualityCutoffHasNotBeenMet": "Порогова якість не досягнута",
|
||||
"QualityOrLangCutoffHasNotBeenMet": "Обрізання якості чи мови не виконано",
|
||||
"QualityProfile": "Профіль якості",
|
||||
"QueueIsEmpty": "Черга порожня",
|
||||
"QuickImport": "Рухатися автоматично",
|
||||
"RadarrCalendarFeed": "Стрічка календаря Radarr",
|
||||
"RadarrSupportsCustomConditionsAgainstTheReleasePropertiesBelow": "Radarr підтримує спеціальні умови щодо наведених нижче властивостей випуску.",
|
||||
"RadarrTags": "Теги Radar",
|
||||
"RadarrUpdated": "Radarr оновлено",
|
||||
"Rating": "Рейтинг",
|
||||
"Ratings": "Рейтинги",
|
||||
"Real": "Справжня",
|
||||
"Refresh": "Оновити",
|
||||
"RefreshAndScan": "Оновити та сканувати",
|
||||
"RelativePath": "Відносний шлях",
|
||||
"SearchForMissing": "Розшук зниклих",
|
||||
"SearchForMovie": "Пошук фільму",
|
||||
"SearchMissing": "Пошук відсутній",
|
||||
"StartImport": "Розпочати імпорт",
|
||||
"System": "Система",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Ви впевнені, що бажаєте скинути визначення якості?",
|
||||
"CollectionsSelectedInterp": "Вибрано колекцій: {0}",
|
||||
"DeleteNotification": "Видалити сповіщення",
|
||||
"DeleteFileLabel": "Видалити файл фільму {0}",
|
||||
"DeleteFilesHelpText": "Видалити файли фільмів і папку фільмів",
|
||||
"DeleteFilesLabel": "Видалити файли фільмів {0}",
|
||||
"DeleteQualityProfile": "Видалити профіль якості",
|
||||
"DeleteRestriction": "Видалити обмеження",
|
||||
"DownloadedAndMonitored": "Завантажено (Відстежується)",
|
||||
"DownloadClientUnavailable": "Клієнт завантажувача недоступний",
|
||||
"DownloadedButNotMonitored": "Завантажено (Не відстежується)",
|
||||
"DownloadFailed": "Помилка завантаження",
|
||||
"DownloadFailedCheckDownloadClientForMoreDetails": "Помилка завантаження: перевірте клієнт завантаження, щоб дізнатися більше",
|
||||
"DownloadFailedInterp": "Помилка завантаження: {0}",
|
||||
"DownloadWarning": "Попередження про завантаження: {0}",
|
||||
"DownloadWarningCheckDownloadClientForMoreDetails": "Попередження про завантаження: перевірте клієнт завантаження, щоб дізнатися більше",
|
||||
"Edit": "Редагувати",
|
||||
"EditGroups": "Редагувати групи",
|
||||
"EditIndexer": "Редагувати індексатор",
|
||||
"Edition": "Видання",
|
||||
"EditMovie": "Редагувати фільм",
|
||||
"EditMovieFile": "Редагувати файл фільму",
|
||||
"EditPerson": "Редагувати особу",
|
||||
"DeleteSelectedMovieFiles": "Видалити вибрані файли фільмів",
|
||||
"DeleteSelectedMovieFilesMessage": "Ви впевнені, що бажаєте видалити вибрані файли фільмів?",
|
||||
"DeleteTag": "Видалити тег",
|
||||
"DeleteTheMovieFolder": "Папку фільму \"{0}\" і весь її вміст буде видалено.",
|
||||
"DestinationPath": "Шлях призначення",
|
||||
"DetailedProgressBar": "Детальний індикатор прогресу",
|
||||
"DetailedProgressBarHelpText": "Показати текст на панелі виконання",
|
||||
"Details": "Подробиці",
|
||||
"Donations": "Пожертви",
|
||||
"EnableCompletedDownloadHandlingHelpText": "Автоматично імпортувати завершені завантаження з клієнта завантажень",
|
||||
"EnableAutomaticSearchHelpTextWarning": "Буде використано, коли використовується інтерактивний пошук",
|
||||
"EnableColorImpairedModeHelpText": "Змінений стиль, щоб користувачі з вадами кольору могли краще розрізняти кольорову кодовану інформацію",
|
||||
"EnableInteractiveSearch": "Увімкнути інтерактивний пошук",
|
||||
"EnableInteractiveSearchHelpText": "Буде використано, коли використовується інтерактивний пошук",
|
||||
"EnableInteractiveSearchHelpTextWarning": "Пошук не підтримується цим індексатором",
|
||||
"EnableRSS": "Увімкнути RSS",
|
||||
"EnableSSL": "Увімкнути SSL",
|
||||
"EnableSslHelpText": " Щоб набуло чинності, потрібно перезапустити роботу від імені адміністратора",
|
||||
"EventType": "Тип події",
|
||||
"Exception": "Виняток",
|
||||
"Excluded": "Виключено",
|
||||
"ExcludeMovie": "Виключити фільм",
|
||||
"ExcludeTitle": "Виключити {0}? Це не дозволить Radarr автоматично додавати через синхронізацію списку.",
|
||||
"ExistingMovies": "Існуючі фільми",
|
||||
"ExistingTag": "Існуючий тег",
|
||||
"Extension": "Розширення",
|
||||
"ExternalUpdater": "Radarr налаштовано на використання зовнішнього механізму оновлення",
|
||||
"ExtraFileExtensionsHelpTexts2": "Приклади: '.sub, .nfo' або 'sub,nfo'",
|
||||
"Failed": "Не вдалося",
|
||||
"FailedDownloadHandling": "Помилка обробки завантаження",
|
||||
"FailedToLoadQueue": "Не вдалося завантажити чергу",
|
||||
"FeatureRequests": "Майбутні запити",
|
||||
"FileManagement": "Керування файлами",
|
||||
"Filename": "Ім'я файлу",
|
||||
"FileNames": "Імена файлів",
|
||||
"FileWasDeletedByUpgrade": "Файл видалено, щоб імпортувати оновлення",
|
||||
"FileWasDeletedByViaUI": "Файл видалено через інтерфейс користувача",
|
||||
"FilterPlaceHolder": "Пошук фільмів",
|
||||
"Folder": "Папка",
|
||||
"Forecast": "Прогноз",
|
||||
"Formats": "Формати",
|
||||
"ForMoreInformationOnTheIndividualImportListsClinkOnTheInfoButtons": "Щоб отримати додаткову інформацію про окремі списки імпорту, натисніть інформаційні кнопки.",
|
||||
"ForMoreInformationOnTheIndividualIndexers": "Щоб отримати додаткову інформацію про окремих індексаторів, натисніть кнопки інформації.",
|
||||
"FreeSpace": "Вільний простір",
|
||||
"General": "Загальний",
|
||||
"Host": "Хост",
|
||||
"Hostname": "Ім'я хоста",
|
||||
"Hours": "Години",
|
||||
"HttpHttps": "HTTP(S)",
|
||||
"ICalFeed": "Канал iCal",
|
||||
"ICalHttpUrlHelpText": "Скопіюйте цю URL-адресу до своїх клієнтів або натисніть, щоб підписатися, якщо ваш браузер підтримує веб-канал",
|
||||
"Ignored": "Ігнорується",
|
||||
"IgnoredAddresses": "Ігноровані адреси",
|
||||
"IllRestartLater": "Я перезапущу пізніше",
|
||||
"ImportExistingMovies": "Імпорт наявних фільмів",
|
||||
"ImportExtraFiles": "Імпорт додаткових файлів",
|
||||
"ImportFailedInterp": "Помилка імпорту: {0}",
|
||||
"ImportHeader": "Імпортуйте існуючу організовану бібліотеку, щоб додати фільми до Radarr",
|
||||
"ImportIncludeQuality": "Переконайтеся, що назви ваших файлів містять якість. напр. {0}",
|
||||
"ImportLibrary": "Імпорт бібліотеки",
|
||||
"ImportListMultipleMissingRoots": "Кілька кореневих папок відсутні для списків імпорту: {0}",
|
||||
"IndexerLongTermStatusCheckSingleClientMessage": "Індексатори недоступні через збої більше 6 годин: {0}",
|
||||
"ImdbRating": "Рейтинг IMDb",
|
||||
"ImdbVotes": "Голоси IMDb",
|
||||
"IndexerDownloadClientHelpText": "Укажіть, який клієнт завантаження використовується для захоплення з цього індексатора",
|
||||
"IndexerRssHealthCheckNoAvailableIndexers": "Усі індексатори з підтримкою rss тимчасово недоступні через нещодавні помилки індексатора",
|
||||
"IndexerPriorityHelpText": "Пріоритет індексатора від 1 (найвищий) до 50 (найнижчий). За замовчуванням: 25. Використовується під час захоплення випусків як тай-брейку для інших однакових випусків, Radarr все одно використовуватиме всі ввімкнені індексатори для RSS-синхронізації та пошуку",
|
||||
"IndexerRssHealthCheckNoIndexers": "Немає доступних індексаторів із увімкненою синхронізацією RSS, Radarr не збиратиме нові випуски автоматично",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Немає доступних індексаторів, коли ввімкнено інтерактивний пошук, Radarr не надаватиме жодних інтерактивних результатів пошуку",
|
||||
"IndexerStatusCheckAllClientMessage": "Усі індексатори недоступні через збої",
|
||||
"IndexerStatusCheckSingleClientMessage": "Індексатори недоступні через помилки: {0}",
|
||||
"InteractiveImportErrLanguage": "Для кожного вибраного файлу необхідно вибрати мову",
|
||||
"InteractiveImportErrMovie": "Фільм потрібно вибрати для кожного вибраного файлу",
|
||||
"InteractiveImportErrQuality": "Для кожного вибраного файлу необхідно вибрати якість",
|
||||
"InteractiveSearch": "Інтерактивний пошук",
|
||||
"Interval": "Інтервал",
|
||||
"InvalidFormat": "Недійсний формат",
|
||||
"KeyboardShortcuts": "Гарячі клавіши",
|
||||
"LanguageHelpText": "Мова для релізів",
|
||||
"Languages": "Мови",
|
||||
"LastExecution": "Останнє виконання",
|
||||
"LastWriteTime": "Час останнього запису",
|
||||
"Level": "Рівень",
|
||||
"ListSyncLevelHelpTextWarning": "Файли фільмів буде остаточно видалено, це може призвести до видалення вашої бібліотеки, якщо ваші списки порожні",
|
||||
"LoadingMovieExtraFilesFailed": "Не вдалося завантажити додаткові файли фільму",
|
||||
"LoadingMovieFilesFailed": "Не вдалося завантажити файли фільму",
|
||||
"Local": "Місцевий",
|
||||
"Location": "Місцезнаходження",
|
||||
"LogFiles": "Файли журналів",
|
||||
"LogOnly": "Лише журнал",
|
||||
"Logs": "Журнали",
|
||||
"Manual": "Інструкція",
|
||||
"ManualImport": "Імпорт вручну",
|
||||
"ManualImportSelectLanguage": "Імпорт вручну - виберіть мову",
|
||||
"ManualImportSelectMovie": "Імпорт вручну - виберіть фільм",
|
||||
"ManualImportSelectQuality": " Імпорт вручну - Виберіть якість",
|
||||
"ManualImportSetReleaseGroup": "Імпорт вручну - встановити групу випуску",
|
||||
"MappedDrivesRunningAsService": "Підключені мережеві диски недоступні під час роботи як служби Windows. Щоб отримати додаткову інформацію, перегляньте FAQ",
|
||||
"MarkAsFailed": "Позначити як помилку",
|
||||
"MarkAsFailedMessageText": "Ви впевнені, що бажаєте позначити \"{0}\" як невдале?",
|
||||
"MassMovieSearch": "Масовий пошук фільмів",
|
||||
"Max": "Максимальний",
|
||||
"MaximumSize": "Максимальний розмір",
|
||||
"Mechanism": "Механізм",
|
||||
"MediaInfo": "Медіа інформація",
|
||||
"MediaManagementSettingsSummary": "Налаштування іменування та керування файлами",
|
||||
"Medium": "Середній",
|
||||
"MIA": "MIA",
|
||||
"MinAvailability": "Мінімальна доступність",
|
||||
"MinFormatScoreHelpText": "Мінімальна оцінка спеціального формату, дозволена для завантаження",
|
||||
"MinimumFreeSpaceWhenImportingHelpText": "Заборонити імпорт, якщо він залишить менше, ніж цей обсяг доступного дискового простору",
|
||||
"Minutes": "Хвилин",
|
||||
"MinutesHundredTwenty": "120 хвилин: {0}",
|
||||
"MinutesNinety": "90 хвилин: {0}",
|
||||
"MinutesSixty": "60 хвилин: {0}",
|
||||
"MissingFromDisk": "Radarr не зміг знайти файл на диску, тому файл було від’єднано від фільму в базі даних",
|
||||
"MissingMonitoredAndConsideredAvailable": "Відсутній (відстежується)",
|
||||
"Mode": "Режим",
|
||||
"Months": "Місяці",
|
||||
"More": "Більше",
|
||||
"MoveFiles": "Перемістити файли",
|
||||
"MovieChat": "Кіночат",
|
||||
"MovieCollectionMultipleMissingRoots": "Кілька кореневих папок відсутні для колекцій фільмів: {0}",
|
||||
"MovieDetailsNextMovie": "Подробиці про фільм: наступний фільм",
|
||||
"MovieAlreadyExcluded": "Фільм уже виключено",
|
||||
"MovieAndCollection": "Фільм і колекція",
|
||||
"MovieExcludedFromAutomaticAdd": "Фільм виключено з автоматичного додавання",
|
||||
"MovieFiles": "Файли фільмів",
|
||||
"MovieFilesTotaling": "Підсумок файлів фільмів",
|
||||
"MovieID": "ID фільму",
|
||||
"MovieIndex": "Індекс фільму",
|
||||
"MovieIndexScrollBottom": "Індекс фільму: прокрутка внизу",
|
||||
"MovieInfoLanguage": "Мова інформації про фільм",
|
||||
"MovieInfoLanguageHelpTextWarning": "Потрібно перезавантажити браузер",
|
||||
"MovieInvalidFormat": "Фільм: недійсний формат",
|
||||
"MovieIsDownloading": "Фільм завантажується",
|
||||
"MovieIsMonitored": "Фільм відстежується",
|
||||
"MovieIsUnmonitored": "Фільм без моніторингу",
|
||||
"MovieNaming": "Назва фільму",
|
||||
"MovieOnly": "Тільки кіно",
|
||||
"MoviesSelectedInterp": "Вибрано фільмів: {0}",
|
||||
"MovieTitle": "Назва фільму",
|
||||
"MustNotContain": "Не повинен містити",
|
||||
"Name": "Ім'я",
|
||||
"NamingSettings": "Налаштування імен",
|
||||
"Negate": "Заперечувати",
|
||||
"Negated": "Заперечено",
|
||||
"NoLogFiles": "Немає файлів журналу",
|
||||
"NoMatchFound": "Збігів не знайдено!",
|
||||
"NoResultsFound": "Нічого не знайдено",
|
||||
"NoTagsHaveBeenAddedYet": "Теги ще не додано",
|
||||
"NotAvailable": "Недоступний",
|
||||
"NotificationTriggers": "Тригери сповіщень",
|
||||
"NotificationTriggersHelpText": "Виберіть, які події мають викликати це сповіщення",
|
||||
"NotMonitored": "Не контролюється",
|
||||
"OAuthPopupMessage": "Ваш браузер блокує спливаючі вікна",
|
||||
"Ok": "Гаразд",
|
||||
"OnApplicationUpdateHelpText": "Оновлення програми",
|
||||
"OnDownloadHelpText": "При імпорті",
|
||||
"OnHealthIssueHelpText": "Про питання здоров'я",
|
||||
"OnImport": "При імпорті",
|
||||
"OnLatestVersion": "Остання версія Radarr вже встановлена",
|
||||
"OnlyTorrent": "Тільки торрент",
|
||||
"OnlyUsenet": "Тільки Usenet",
|
||||
"OnUpgrade": "При оновленні",
|
||||
"OnUpgradeHelpText": "При оновленні",
|
||||
"OpenBrowserOnStart": "Відкрийте браузер при запуску",
|
||||
"OrganizeModalNamingPattern": "Шаблон іменування:",
|
||||
"Original": "Оригінал",
|
||||
"OriginalLanguage": "Мова оригіналу",
|
||||
"Paused": "Призупинено",
|
||||
"Pending": "В очікуванні",
|
||||
"PendingChangesDiscardChanges": "Відкинути зміни та залишити",
|
||||
"PendingChangesStayReview": "Залишайтеся та переглядайте зміни",
|
||||
"Permissions": "Дозволи",
|
||||
"PhysicalRelease": "Фізичний реліз",
|
||||
"PhysicalReleaseDate": "Дата фізичного випуску",
|
||||
"Port": "Порт",
|
||||
"PortNumber": "Номер порту",
|
||||
"PosterOptions": "Параметри плаката",
|
||||
"Posters": "Плакати",
|
||||
"PreferAndUpgrade": "Віддайте перевагу та оновіть",
|
||||
"PreferIndexerFlags": "Віддавати перевагу прапорцям індексатора",
|
||||
"PreferIndexerFlagsHelpText": "Пріоритезуйте випуски за допомогою спеціальних прапорців",
|
||||
"Preferred": "Бажано",
|
||||
"PreferredSize": "Бажаний розмір",
|
||||
"ProfilesSettingsSummary": "Профілі якості, мови та затримки",
|
||||
"Progress": "Прогрес",
|
||||
"Proper": "Належний",
|
||||
"Protocol": "Протокол",
|
||||
"ReadTheWikiForMoreInformation": "Читайте Wiki для отримання додаткової інформації",
|
||||
"RecycleBinCleanupDaysHelpTextWarning": "Файли в кошику, старші за вибрану кількість днів, будуть очищені автоматично",
|
||||
"RecycleBinHelpText": "Файли фільмів потраплять сюди після видалення, а не назавжди",
|
||||
"Redownload": "Повторне завантаження",
|
||||
"RejectionCount": "Кількість відмов",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "Гілка {0} не є дійсною гілкою випуску Radarr, ви не отримуватимете оновлення",
|
||||
"ReleaseStatus": "Статус випуску",
|
||||
"ReleaseTitle": "Назва випуску",
|
||||
"ReleaseWillBeProcessedInterp": "Випуск буде оброблено {0}",
|
||||
"Reload": "Перезавантажити",
|
||||
"RemotePath": "Віддалений шлях",
|
||||
"RemotePathMappingCheckDockerFolderMissing": "Ви використовуєте docker; клієнт завантаження {0} розміщує завантаження в {1}, але цей каталог не існує всередині контейнера. Перегляньте свої віддалені відображення шляхів і налаштування обсягу контейнера.",
|
||||
"RemotePathMappingCheckFileRemoved": "Файл {0} видалено під час обробки.",
|
||||
"RemotePathMappingCheckFilesBadDockerPath": "Ви використовуєте docker; завантажити клієнтські {0} звітні файли в {1}, але це недійсний {2} шлях. Перегляньте свої віддалені відображення шляхів і завантажте налаштування клієнта.",
|
||||
"RemotePathMappingCheckFilesLocalWrongOSPath": "Локальний клієнт завантаження {0} повідомив про файли в {1}, але це недійсний шлях {2}. Перегляньте налаштування клієнта завантаження.",
|
||||
"RemotePathMappingCheckFilesWrongOSPath": "Клієнт віддаленого завантаження {0} повідомив про файли в {1}, але це недійсний шлях {2}. Перегляньте свої віддалені відображення шляхів і завантажте налаштування клієнта.",
|
||||
"RemotePathMappingCheckFolderPermissions": "Radarr може бачити, але не має доступу до каталогу завантажень {0}. Ймовірна помилка дозволів.",
|
||||
"RemotePathMappingCheckGenericPermissions": "Клієнт завантаження {0} розміщує завантаження в {1}, але Radarr не бачить цей каталог. Можливо, вам знадобиться налаштувати дозволи для папки.",
|
||||
"RemotePathMappingCheckImportFailed": "Radarr не вдалося імпортувати фільм. Подробиці перевірте у своїх журналах.",
|
||||
"RemotePathMappingCheckLocalFolderMissing": "Клієнт віддаленого завантаження {0} розміщує завантаження в {1}, але цей каталог не існує. Ймовірно, віддалений шлях відсутній або неправильний.",
|
||||
"RemotePathMappingCheckLocalWrongOSPath": "Локальний клієнт завантаження {0} розміщує завантаження в {1}, але це недійсний шлях {2}. Перегляньте налаштування клієнта завантаження.",
|
||||
"RemotePathMappings": "Віддалені відображення шляхів",
|
||||
"RemoveCompleted": "Видалення завершено",
|
||||
"RemoveCompletedDownloadsHelpText": "Видалити імпортовані завантаження з історії клієнта завантажень",
|
||||
"RemovedFromTaskQueue": "Видалено з черги завдань",
|
||||
"RemovedMovieCheckMultipleMessage": "Фільми {0} видалено з TMDb",
|
||||
"RemovedMovieCheckSingleMessage": "Фільм {0} видалено з TMDb",
|
||||
"RemoveFromDownloadClient": "Видалити з клієнта завантаження",
|
||||
"Reorder": "Змінити порядок",
|
||||
"Replace": "Замінити",
|
||||
"ReplaceIllegalCharacters": "Замінити неприпустимі символи",
|
||||
"ReplaceWithDash": "Замінити тире",
|
||||
"ReplaceWithSpaceDash": "Замінити пробілом",
|
||||
"RescanAfterRefreshHelpText": "Перескануйте папку фільму після оновлення фільму",
|
||||
"ResetDefinitions": "Скинути визначення",
|
||||
"ResetQualityDefinitions": "Скинути визначення якості",
|
||||
"ResetTitles": "Скинути заголовки",
|
||||
"ResetTitlesHelpText": "Скинути заголовки визначень, а також значення",
|
||||
"RestartNow": "Перезавантажити зараз",
|
||||
"Restore": "Відновлення",
|
||||
"RootFolderCheckSingleMessage": "Відсутня коренева папка: {0}",
|
||||
"RootFolders": "Кореневі папки",
|
||||
"RSSIsNotSupportedWithThisIndexer": "Цей індексатор не підтримує RSS",
|
||||
"RSSSync": "Синхронізація RSS",
|
||||
"RSSSyncInterval": "Інтервал синхронізації RSS",
|
||||
"RSSSyncIntervalHelpTextWarning": "Це стосується всіх індексаторів, будь ласка, дотримуйтеся встановлених ними правил",
|
||||
"Save": "Зберегти",
|
||||
"SaveChanges": "Зберегти зміни",
|
||||
"SaveSettings": "Зберегти зміни",
|
||||
"Score": "Оцінка",
|
||||
"Script": "Сценарій",
|
||||
"ScrollMovies": "Прокрутка фільмів",
|
||||
"SearchCutoffUnmet": "Обрізка пошуку не виконана",
|
||||
"SearchFiltered": "Пошук відфільтровано",
|
||||
"SearchMovie": "Пошук фільму",
|
||||
"SearchOnAdd": "Шукати при додаванні",
|
||||
"SearchOnAddHelpText": "Шукайте фільми в цьому списку після додавання в бібліотеку",
|
||||
"SearchSelected": "Пошук вибрано",
|
||||
"SelectAll": "Вибрати все",
|
||||
"SelectDotDot": "Виберіть...",
|
||||
"SelectFolder": "Виберіть папку",
|
||||
"SelectLanguages": "Виберіть Мови",
|
||||
"SelectQuality": "Виберіть Якість",
|
||||
"SetPermissions": "Встановити дозволи",
|
||||
"SetPermissionsLinuxHelpText": "Чи слід запускати chmod, коли файли імпортуються/перейменовуються?",
|
||||
"SetReleaseGroup": "Встановити групу випуску",
|
||||
"SettingsLongDateFormat": "Довгий формат дати",
|
||||
"SettingsRemotePathMappingLocalPathHelpText": "Шлях, який має використовувати Radarr для локального доступу до віддаленого шляху",
|
||||
"SettingsRemotePathMappingRemotePath": "Віддалений шлях",
|
||||
"SettingsShortDateFormat": "Короткий формат дати",
|
||||
"SettingsShowRelativeDates": "Показати відносні дати",
|
||||
"SettingsTheme": "Тема",
|
||||
"ShouldMonitorHelpText": "Чи слід додавати фільми чи колекції, додані до цього списку, як контрольовані",
|
||||
"ShowAdvanced": "Показати Додатково",
|
||||
"ShowAsAllDayEvents": "Показати як події на весь день",
|
||||
"ShowCinemaRelease": "Показати дату виходу в кіно",
|
||||
"showCinemaReleaseHelpText": "Покажіть дату виходу в кіно під афішею",
|
||||
"ShowCutoffUnmetIconHelpText": "Показувати піктограму для файлів, коли обмеження не досягнуто",
|
||||
"ShowDateAdded": "Показати дату додавання",
|
||||
"ShowMonitored": "Показати Моніторинг",
|
||||
"ShowPosters": "Показати плакати",
|
||||
"ShowRatings": "Показати рейтинги",
|
||||
"ShowReleaseDate": "Показати дату випуску",
|
||||
"ShowReleaseDateHelpText": "Показати дату випуску під плакатом",
|
||||
"ShowSearch": "Показати пошук",
|
||||
"ShowUnknownMovieItems": "Показати невідомі елементи фільму",
|
||||
"ShowYear": "Показати рік",
|
||||
"Shutdown": "Вимкнення",
|
||||
"SizeLimit": "Обмеження розміру",
|
||||
"SizeOnDisk": "Розмір на диску",
|
||||
"Small": "Маленький",
|
||||
"SorryThatMovieCannotBeFound": "Вибачте, цей фільм неможливо знайти.",
|
||||
"Sort": "Сортування",
|
||||
"Source": "Джерело",
|
||||
"SourceRelativePath": "Відносний шлях джерела",
|
||||
"SourceTitle": "Назва джерела",
|
||||
"SSLCertPasswordHelpText": "Пароль для файлу pfx",
|
||||
"SSLPort": "Порт SSL",
|
||||
"StandardMovieFormat": "Стандартний формат фільму",
|
||||
"Started": "Розпочато",
|
||||
"StartSearchForMissingMovie": "Почніть пошук зниклого фільму",
|
||||
"StartTypingOrSelectAPathBelow": "Почніть вводити текст або виберіть шлях нижче",
|
||||
"Status": "Статус",
|
||||
"Studio": "Студія",
|
||||
"Style": "Стиль",
|
||||
"SubfolderWillBeCreatedAutomaticallyInterp": "Вкладена папка \"{0}\" буде створена автоматично",
|
||||
"SuggestTranslationChange": "Запропонуйте зміну перекладу",
|
||||
"SystemTimeCheckMessage": "Системний час вимкнено більш ніж на 1 день. Заплановані завдання можуть не працювати належним чином, доки час не буде виправлено",
|
||||
"Table": "Таблиця",
|
||||
"TableOptions": "Параметри таблиці",
|
||||
"TableOptionsColumnsMessage": "Виберіть, які стовпці відображаються та в якому порядку вони відображаються",
|
||||
"TagCannotBeDeletedWhileInUse": "Неможливо видалити під час використання",
|
||||
"TagIsNotUsedAndCanBeDeleted": "Тег не використовується і може бути видалений",
|
||||
"Tags": "Теги",
|
||||
"TagsHelpText": "Застосовується до фільмів із принаймні одним відповідним тегом",
|
||||
"TagsSettingsSummary": "Перегляньте всі теги та те, як вони використовуються. Невикористані теги можна видалити",
|
||||
"TestAll": "Перевірити все",
|
||||
"TestAllClients": "Перевірте всіх клієнтів",
|
||||
"TestAllIndexers": "Перевірити всі індексатори",
|
||||
"TestAllLists": "Перевірити всі списки",
|
||||
"TimeFormat": "Формат часу",
|
||||
"Timeleft": "Час залишився",
|
||||
"Title": "Назва",
|
||||
"Titles": "Назви",
|
||||
"TmdbIdHelpText": "Ідентифікатор TMDb фільму, який потрібно виключити",
|
||||
"TmdbRating": "Рейтинг TMDb",
|
||||
"TmdbVotes": "Голоси TMDb",
|
||||
"TorrentDelayTime": "Затримка торрента: {0}",
|
||||
"Torrents": "Торренти",
|
||||
"Trace": "Трасувати",
|
||||
"Trailer": "Трейлер",
|
||||
"Trigger": "Тригер",
|
||||
"Type": "Тип",
|
||||
"UnableToAddANewConditionPleaseTryAgain": "Не вдалося додати нову умову, спробуйте ще раз.",
|
||||
"UnableToAddANewQualityProfilePleaseTryAgain": "Не вдалося додати новий профіль якості, спробуйте ще раз.",
|
||||
"UnableToAddRootFolder": "Не вдалося додати кореневу папку",
|
||||
"UnableToImportCheckLogs": "Завантажено – неможливо імпортувати: подробиці перевірте в журналах",
|
||||
"UnableToLoadDelayProfiles": "Неможливо завантажити профілі затримки",
|
||||
"UnableToLoadGeneralSettings": "Не вдалося завантажити загальні налаштування",
|
||||
"UnableToLoadHistory": "Не вдалося завантажити історію",
|
||||
"UnableToLoadIndexerOptions": "Не вдалося завантажити параметри індексатора",
|
||||
"UnableToLoadLanguages": "Не вдалося завантажити мови",
|
||||
"UnableToLoadListOptions": "Не вдалося завантажити параметри списку",
|
||||
"UnableToLoadLists": "Не вдалося завантажити списки",
|
||||
"UnableToLoadMediaManagementSettings": "Не вдалося завантажити налаштування керування медіафайлами",
|
||||
"UnableToLoadMetadata": "Не вдалося завантажити метадані",
|
||||
"UnableToLoadNotifications": "Не вдалося завантажити сповіщення",
|
||||
"UnableToLoadQualityDefinitions": "Не вдалося завантажити визначення якості",
|
||||
"UnableToLoadRemotePathMappings": "Неможливо завантажити віддалені відображення шляхів",
|
||||
"UnableToLoadRootFolders": "Не вдалося завантажити кореневі папки",
|
||||
"UnableToUpdateRadarrDirectly": "Неможливо оновити Radarr безпосередньо,",
|
||||
"Unavailable": "Недоступний",
|
||||
"Unlimited": "Необмежений",
|
||||
"UnmappedFilesOnly": "Лише незіставлені файли",
|
||||
"UnmappedFolders": "Невідповідні папки",
|
||||
"UnselectAll": "Скасувати вибір усіх",
|
||||
"UpdateAll": "Оновити все",
|
||||
"UpdateCheckStartupTranslocationMessage": "Неможливо встановити оновлення, оскільки папка запуску \"{0}\" знаходиться в папці переміщення програми.",
|
||||
"UpdateMechanismHelpText": "Використовуйте вбудований засіб оновлення Radarr або скрипт",
|
||||
"UpdateScriptPathHelpText": "Шлях до спеціального сценарію, який приймає витягнутий пакет оновлення та обробляє решту процесу оновлення",
|
||||
"UpgradeAllowedHelpText": "Якщо відключені якості не будуть покращені",
|
||||
"UpgradesAllowed": "Якщо відключені якості не будуть покращені",
|
||||
"UpgradeUntilQuality": "Оновлення до якості",
|
||||
"Uptime": "Час роботи",
|
||||
"URLBase": "URL-адреса",
|
||||
"UrlBaseHelpText": "Для підтримки зворотного проксі-сервера значення за умовчанням порожнє",
|
||||
"UseHardlinksInsteadOfCopy": "Використовуйте жорсткі посилання замість копіювати",
|
||||
"UsenetDisabled": "Usenet вимкнено",
|
||||
"UseProxy": "Використовуйте проксі",
|
||||
"Username": "Ім'я користувача",
|
||||
"Version": "Версія",
|
||||
"VideoCodec": "Відеокодек",
|
||||
"View": "Переглянути",
|
||||
"Wanted": "Розшукується",
|
||||
"Warn": "Попередити",
|
||||
"Week": "Тиждень",
|
||||
"WeekColumnHeader": "Заголовок стовпця тижня",
|
||||
"Weeks": "Тижнів",
|
||||
"WhatsNew": "Що нового?",
|
||||
"WhitelistedSubtitleTags": "Теги субтитрів із білого списку",
|
||||
"Year": "Рік",
|
||||
"YesCancel": "Так, скасувати",
|
||||
"YesMoveFiles": "Так, перемістити файли",
|
||||
"Yesterday": "Вчора",
|
||||
"ApplicationURL": "URL програми",
|
||||
"ApplicationUrlHelpText": "Зовнішня URL-адреса цієї програми, включаючи http(s)://, порт і базу URL-адрес",
|
||||
"ImportRootPath": "Наведіть Radarr на папку з усіма вашими фільмами, а не на певний фільм. напр. {0}, а не {1}. Крім того, кожен фільм має бути у власній папці в кореневій папці/теці бібліотеки.",
|
||||
"ImportTipsMessage": "Кілька порад, щоб забезпечити безперешкодне імпортування:",
|
||||
"InCinemasMsg": "Фільм у кінотеатрах",
|
||||
"IncludeHealthWarningsHelpText": "Включайте попередження про здоров’я",
|
||||
"IncludeUnknownMovieItemsHelpText": "Показати елементи без фільму в черзі. Це може включати видалені фільми чи будь-що інше в категорії Radarr",
|
||||
"IndexerPriority": "Пріоритет індексатора",
|
||||
"ImportListStatusCheckAllClientMessage": "Усі списки недоступні через помилки",
|
||||
"ImportListStatusCheckSingleClientMessage": "Списки недоступні через помилки: {0}",
|
||||
"ImportListSyncIntervalHelpText": "Як часто Radarr синхронізує ваші списки. Мінімальне значення 6 годин",
|
||||
"ImportMechanismHealthCheckMessage": "Увімкнути обробку завершених завантажень",
|
||||
"ImportMovies": "Імпорт фільмів",
|
||||
"ImportNotForDownloads": "Не використовуйте для імпортування завантажень із вашого клієнта завантаження, це лише для існуючих упорядкованих бібліотек, а не для несортованих файлів.",
|
||||
"IndexerSearchCheckNoAvailableIndexersMessage": "Усі індексатори з можливістю пошуку тимчасово недоступні через нещодавні помилки індексатора",
|
||||
"InstallLatest": "Встановити останній",
|
||||
"MegabytesPerMinute": "Мегабайт за хвилину",
|
||||
"Message": "Повідомлення",
|
||||
"MovieTitleHelpText": "Назва фільму, який потрібно виключити (може бути будь-яким значущим)",
|
||||
"MovieYear": "Фільм рік",
|
||||
"MovieYearHelpText": "Рік фільму виключити",
|
||||
"NegateHelpText": "Якщо позначено, настроюваний формат не застосовуватиметься, якщо ця умова {0} збігається.",
|
||||
"Never": "Ніколи",
|
||||
"New": "Новий",
|
||||
"NoAltTitle": "Без альтернативних назв.",
|
||||
"NoBackupsAreAvailable": "Немає резервних копій",
|
||||
"NoChange": "Без змін",
|
||||
"NoChanges": "Жодних змін",
|
||||
"NoLinks": "Немає посилань",
|
||||
"NoListRecommendations": "Елементів списку чи рекомендацій не знайдено. Щоб почати, ви захочете додати новий фільм, імпортувати кілька наявних або додати список.",
|
||||
"NoMinimumForAnyRuntime": "Немає мінімуму для будь-якого часу виконання",
|
||||
"NoMoviesExist": "Фільми не знайдено. Щоб почати, ви захочете додати новий фільм або імпортувати кілька наявних.",
|
||||
"NoUpdatesAreAvailable": "Немає оновлень",
|
||||
"Test": "Тест",
|
||||
"TorrentsDisabled": "Торренти вимкнено",
|
||||
"TotalFileSize": "Загальний розмір файлу",
|
||||
"DownloadClientCheckDownloadingToRoot": "Клієнт завантаження {0} розміщує завантаження в кореневій папці {1}. Ви не повинні завантажувати в кореневу папку.",
|
||||
"DigitalRelease": "Цифровий випуск",
|
||||
"Disabled": "Вимкнено",
|
||||
"DeleteEmptyFoldersHelpText": "Видаляти порожні папки фільмів під час сканування диска та під час видалення файлів фільмів",
|
||||
"DeleteFile": "Видалити файл",
|
||||
"Discord": "Discord",
|
||||
"Docker": "Docker",
|
||||
"DownloadClientCheckNoneAvailableMessage": "Немає доступного клієнта для завантаження",
|
||||
"DownloadClientCheckUnableToCommunicateMessage": "Неможливо зв'язатися з {0}.",
|
||||
"DiskSpace": "Дисковий простір",
|
||||
"DoneEditingGroups": "Редагування груп завершено",
|
||||
"DoNotPrefer": "Не віддавати перевагу",
|
||||
"DoNotUpgradeAutomatically": "Не оновлювати автоматично",
|
||||
"Download": "Завантажити",
|
||||
"DownloadClient": "Клієнт завантажувача",
|
||||
"DownloadClients": "Клієнти завантажувачів",
|
||||
"DownloadClientSettings": "Налаштування клієнта завантажувача",
|
||||
"DownloadClientsSettingsSummary": "Клієнти завантаження, обробка завантажень і віддалені відображення шляхів",
|
||||
"DownloadClientStatusCheckAllClientMessage": "Усі клієнти завантаження недоступні через збої",
|
||||
"DownloadClientStatusCheckSingleClientMessage": "Завантаження клієнтів недоступне через помилки: {0}",
|
||||
"Genres": "Жанри",
|
||||
"Global": "Глобальний",
|
||||
"GoToInterp": "Перейти до {0}",
|
||||
"Group": "Група",
|
||||
"HardlinkCopyFiles": "Жорстке посилання/Копіювати файли",
|
||||
"HaveNotAddedMovies": "Ви ще не додали жодного фільму. Бажаєте спершу імпортувати деякі або всі свої фільми?",
|
||||
"Health": "Здоров'я",
|
||||
"HealthNoIssues": "Немає проблем із вашою конфігурацією",
|
||||
"GeneralSettings": "Загальні налаштування",
|
||||
"GeneralSettingsSummary": "Порт, SSL, ім’я користувача/пароль, проксі, аналітика та оновлення",
|
||||
"MoreInfo": "Більше інформації",
|
||||
"RefreshMovie": "Оновити фільм",
|
||||
"RegularExpressionsCanBeTested": "Регулярні вирази можна перевірити ",
|
||||
"RemoveSelectedItems": "Видалити вибрані елементи",
|
||||
"RemovingTag": "Видалення мітки",
|
||||
"Renamed": "Перейменовано",
|
||||
"RenameFiles": "Перейменування файлів",
|
||||
"RenameMovies": "Перейменувати фільми",
|
||||
"RenameMoviesHelpText": "Radarr використовуватиме існуючу назву файлу, якщо перейменування вимкнено",
|
||||
"RemoveFailedDownloadsHelpText": "Видаліть невдалі завантаження з історії завантажень клієнта",
|
||||
"RemoveFilter": "Видалити фільтр",
|
||||
"RemoveFromBlocklist": "Видалити зі списку блокувань",
|
||||
"RemoveFromQueue": "Видалити з черги",
|
||||
"RemoveHelpTextWarning": "Видалення видалить завантаження та файл(и) із клієнта завантаження.",
|
||||
"RemoveMovieAndDeleteFiles": "Видалити фільм і видалити файли",
|
||||
"RemoveMovieAndKeepFiles": "Видалити фільм і зберегти файли",
|
||||
"RemoveRootFolder": "Видалити кореневу папку",
|
||||
"RemoveSelectedItem": "Видалити вибраний елемент",
|
||||
"SettingsRemotePathMappingHostHelpText": "Той самий хост, який ви вказали для віддаленого клієнта завантаження",
|
||||
"SettingsRemotePathMappingLocalPath": "Місцевий шлях",
|
||||
"ShowGenres": "Показати жанри",
|
||||
"UpdateAvailable": "Доступне нове оновлення",
|
||||
"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": "Захопити вибране"
|
||||
}
|
||||
|
||||
@@ -636,7 +636,7 @@
|
||||
"InstallLatest": "安装最新版",
|
||||
"IndexerStatusCheckSingleClientMessage": "搜刮器因错误不可用:{0}",
|
||||
"IndexerStatusCheckAllClientMessage": "所有搜刮器都因错误不可用",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "没有任何索引器开启了手动搜索,因此Radarr 不会提供任何手动搜索的结果",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "没有任何索引器开启了手动搜索,因此 Radarr 不会提供任何手动搜索的结果",
|
||||
"IndexerRssHealthCheckNoIndexers": "没有任何索引器开启了RSS同步,Radarr不会自动抓取新发布的影片",
|
||||
"IndexerLongTermStatusCheckSingleClientMessage": "由于故障6小时,下列索引器都已不可用:{0}",
|
||||
"IndexerLongTermStatusCheckAllClientMessage": "由于故障超过6小时,所有索引器均不可用",
|
||||
@@ -1016,7 +1016,7 @@
|
||||
"ChmodGroupHelpText": "组名称或GID。对于远程文件系统请使用GID。",
|
||||
"MinAvailability": "最小可用性",
|
||||
"RequiredHelpText": "此{0}条件必须匹配才能应用自定义格式。否则,单个{1}匹配就足够了。",
|
||||
"QualitiesHelpText": "列表中较高的质量更可取。同一组内的质量相同。只需要检查质量",
|
||||
"QualitiesHelpText": "即使未选中,列表中的质量排序越高优先级也越高。同组内的质量优先级相同。质量只有选中才标记为需要",
|
||||
"DownloadPropersAndRepacksHelpText2": "使用“不喜欢”按Propers / Repacks中的自定义格式分数排序",
|
||||
"LookingForReleaseProfiles1": "寻找发行档案?尝试",
|
||||
"QualityLimitsHelpText": "影片运行时会自动调整限制。",
|
||||
@@ -1072,7 +1072,7 @@
|
||||
"IndexerTagHelpText": "仅对至少有一个匹配标记的电影使用此索引器。留空则适用于所有电影。",
|
||||
"RemotePathMappingCheckFileRemoved": "文件{0} 在处理的过程中被部分删除。",
|
||||
"RemotePathMappingCheckFilesGenericPermissions": "下载{1}中客户端{0}报告的文件,但Radarr无法看到此目录。您可能需要调整文件夹的权限。",
|
||||
"RemotePathMappingCheckGenericPermissions": "下载客户端{0}将下载放置在{1}中,但Radarr无法看到此目录。您可能需要调整文件夹的权限。",
|
||||
"RemotePathMappingCheckGenericPermissions": "下载客户端{0}将下载放置在{1}中,但 Radarr 无法看到此目录。您可能需要调整文件夹的权限。",
|
||||
"UpdateAvailable": "有新的更新可用",
|
||||
"Letterboxd": "Letterboxd",
|
||||
"RemoveSelectedItem": "删除所选项目",
|
||||
@@ -1143,8 +1143,16 @@
|
||||
"OnMovieAdded": "电影添加时",
|
||||
"OnMovieAddedHelpText": "电影添加时",
|
||||
"TotalMovies": "电影总数",
|
||||
"ApplicationUrlHelpText": "此应用的外部URL包含 http(s)://,端口和基本URL",
|
||||
"ApplicationUrlHelpText": "此应用的外部URL,包含 http(s)://、端口和基本URL",
|
||||
"ApplicationURL": "程序URL",
|
||||
"BindAddressHelpText": "有效的 IPv4 地址或以'*'代表所有接口",
|
||||
"PreferredProtocol": "首选协议"
|
||||
"BindAddressHelpText": "有效的 IP 地址,localhost,或以'*'代表所有地址",
|
||||
"PreferredProtocol": "首选协议",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "确定要重置质量定义吗?",
|
||||
"SettingsThemeHelpText": "更改程序界面主题,“自动”主题将根据您的操作系统主题来设置浅色或深色模式。灵感来自Theme.Park",
|
||||
"ResetDefinitions": "重置定义",
|
||||
"ResetQualityDefinitions": "重置质量定义",
|
||||
"ResetTitles": "重置标题",
|
||||
"ResetTitlesHelpText": "重置定义标题与参数值",
|
||||
"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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace NzbDrone.Core.Messaging.Commands
|
||||
public virtual bool RequiresDiskAccess => false;
|
||||
public virtual bool IsExclusive => false;
|
||||
public virtual bool IsTypeExclusive => false;
|
||||
public virtual bool IsLongRunning => false;
|
||||
|
||||
public string Name { get; private set; }
|
||||
public DateTime? LastExecutionTime { get; set; }
|
||||
|
||||
@@ -176,6 +176,11 @@ namespace NzbDrone.Core.Messaging.Commands
|
||||
queuedCommands = queuedCommands.Where(c => !exclusiveTypes.Any(x => x == c.Body.Name));
|
||||
}
|
||||
|
||||
if (startedCommands.Any(x => x.Body.IsLongRunning))
|
||||
{
|
||||
queuedCommands = queuedCommands.Where(c => !c.Body.IsExclusive);
|
||||
}
|
||||
|
||||
var localItem = queuedCommands.OrderByDescending(c => c.Priority)
|
||||
.ThenBy(c => c.QueuedAt)
|
||||
.FirstOrDefault();
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace NzbDrone.Core.Notifications.Emby
|
||||
}
|
||||
|
||||
public override string Link => "https://emby.media/";
|
||||
public override string Name => "Emby";
|
||||
public override string Name => "Emby / Jellyfin";
|
||||
|
||||
public override void OnGrab(GrabMessage grabMessage)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
@@ -43,7 +44,7 @@ namespace NzbDrone.Core.Notifications.Emby
|
||||
public bool UpdateLibrary { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string Address => $"{Host}:{Port}";
|
||||
public string Address => $"{Host.ToUrlHost()}:{Port}";
|
||||
|
||||
public bool IsValid => !string.IsNullOrWhiteSpace(Host) && Port > 0;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ namespace NzbDrone.Core.Notifications.Plex.Server
|
||||
{
|
||||
var scheme = settings.UseSsl ? "https" : "http";
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder($"{scheme}://{settings.Host}:{settings.Port}")
|
||||
var requestBuilder = new HttpRequestBuilder($"{scheme}://{settings.Host.ToUrlHost()}:{settings.Port}")
|
||||
.Accept(HttpAccept.Json)
|
||||
.AddQueryParam("X-Plex-Client-Identifier", _configService.PlexClientIdentifier)
|
||||
.AddQueryParam("X-Plex-Product", BuildInfo.AppName)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,11 @@ namespace NzbDrone.Core.Notifications.Webhook
|
||||
IndexerFlags = movieFile.IndexerFlags.ToString();
|
||||
Size = movieFile.Size;
|
||||
DateAdded = movieFile.DateAdded;
|
||||
MediaInfo = new WebhookMovieFileMediaInfo(movieFile);
|
||||
|
||||
if (movieFile.MediaInfo != null)
|
||||
{
|
||||
MediaInfo = new WebhookMovieFileMediaInfo(movieFile);
|
||||
}
|
||||
}
|
||||
|
||||
public int Id { get; set; }
|
||||
|
||||
@@ -86,7 +86,6 @@ namespace NzbDrone.Core.Notifications.Xbmc
|
||||
|
||||
if (moviePath != null)
|
||||
{
|
||||
moviePath = new OsPath(moviePath).Directory.FullPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
_logger.Debug("Updating movie {0} (Path: {1}) on XBMC host: {2}", movie, moviePath, settings.Address);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.ComponentModel;
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
@@ -58,7 +59,7 @@ namespace NzbDrone.Core.Notifications.Xbmc
|
||||
public bool AlwaysUpdate { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string Address => string.Format("{0}:{1}", Host, Port);
|
||||
public string Address => $"{Host.ToUrlHost()}:{Port}";
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.FileSystemGlobbing;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
@@ -197,31 +198,34 @@ namespace NzbDrone.Core.Parser
|
||||
}
|
||||
|
||||
// Case sensitive
|
||||
var caseSensitiveMatch = CaseSensitiveLanguageRegex.Match(title);
|
||||
var caseSensitiveMatchs = CaseSensitiveLanguageRegex.Matches(title);
|
||||
|
||||
if (caseSensitiveMatch.Groups["lithuanian"].Captures.Cast<Capture>().Any())
|
||||
foreach (Match match in caseSensitiveMatchs)
|
||||
{
|
||||
languages.Add(Language.Lithuanian);
|
||||
}
|
||||
if (match.Groups["lithuanian"].Captures.Cast<Capture>().Any())
|
||||
{
|
||||
languages.Add(Language.Lithuanian);
|
||||
}
|
||||
|
||||
if (caseSensitiveMatch.Groups["czech"].Captures.Cast<Capture>().Any())
|
||||
{
|
||||
languages.Add(Language.Czech);
|
||||
}
|
||||
if (match.Groups["czech"].Captures.Cast<Capture>().Any())
|
||||
{
|
||||
languages.Add(Language.Czech);
|
||||
}
|
||||
|
||||
if (caseSensitiveMatch.Groups["polish"].Captures.Cast<Capture>().Any())
|
||||
{
|
||||
languages.Add(Language.Polish);
|
||||
}
|
||||
if (match.Groups["polish"].Captures.Cast<Capture>().Any())
|
||||
{
|
||||
languages.Add(Language.Polish);
|
||||
}
|
||||
|
||||
if (caseSensitiveMatch.Groups["bulgarian"].Captures.Cast<Capture>().Any())
|
||||
{
|
||||
languages.Add(Language.Bulgarian);
|
||||
}
|
||||
if (match.Groups["bulgarian"].Captures.Cast<Capture>().Any())
|
||||
{
|
||||
languages.Add(Language.Bulgarian);
|
||||
}
|
||||
|
||||
if (caseSensitiveMatch.Groups["slovak"].Captures.Cast<Capture>().Any())
|
||||
{
|
||||
languages.Add(Language.Slovak);
|
||||
if (match.Groups["slovak"].Captures.Cast<Capture>().Any())
|
||||
{
|
||||
languages.Add(Language.Slovak);
|
||||
}
|
||||
}
|
||||
|
||||
var matches = LanguageRegex.Matches(title);
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Parser
|
||||
{
|
||||
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(Parser));
|
||||
|
||||
private static readonly Regex EditionRegex = new Regex(@"\(?\b(?<edition>(((Recut.|Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Extended|Despecialized|(Special|Rouge|Final|Assembly|Imperial|Diamond|Signature|Hunter|Rekall)(?=(.(Cut|Edition|Version)))|\d{2,3}(th)?.Anniversary)(?:.(Cut|Edition|Version))?(.(Extended|Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit))?|((Uncensored|Remastered|Unrated|Uncut|IMAX|Fan.?Edit|Restored|((2|3|4)in1))))))\b\)?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex EditionRegex = new Regex(@"\(?\b(?<edition>(((Recut.|Extended.|Ultimate.)?(Director.?s|Collector.?s|Theatrical|Ultimate|Extended|Despecialized|(Special|Rouge|Final|Assembly|Imperial|Diamond|Signature|Hunter|Rekall)(?=(.(Cut|Edition|Version)))|\d{2,3}(th)?.Anniversary)(?:.(Cut|Edition|Version))?(.(Extended|Uncensored|Remastered|Unrated|Uncut|Open.?Matte|IMAX|Fan.?Edit))?|((Uncensored|Remastered|Unrated|Uncut|Open?.Matte|IMAX|Fan.?Edit|Restored|((2|3|4)in1))))))\b\)?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex ReportEditionRegex = new Regex(@"^.+?" + EditionRegex, RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace NzbDrone.Core.Parser
|
||||
private static readonly Regex RealRegex = new Regex(@"\b(?<real>REAL)\b",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex ResolutionRegex = new Regex(@"\b(?:(?<R360p>360p)|(?<R480p>480p|640x480|848x480)|(?<R540p>540p)|(?<R576p>576p)|(?<R720p>720p|1280x720|960p)|(?<R1080p>1080p|1920x1080|1440p|FHD|1080i|4kto1080p)|(?<R2160p>2160p|3840x2160|4k[-_. ](?:UHD|HEVC|BD|H265)|(?:UHD|HEVC|BD|H265)[-_. ]4k))\b",
|
||||
private static readonly Regex ResolutionRegex = new Regex(@"\b(?:(?<R360p>360p)|(?<R480p>480p|640x480|848x480)|(?<R540p>540p)|(?<R576p>576p)|(?<R720p>720p|1280x720|960p)|(?<R1080p>1080p|1920x1080|1440p|FHD|1080i|4kto1080p)|(?<R2160p>2160p|3840x2160|4k[-_. ](?:UHD|HEVC|BD|H\.?265)|(?:UHD|HEVC|BD|H\.?265)[-_. ]4k))\b",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
// Handle cases where no resolution is in the release name; assume if UHD then 4k
|
||||
@@ -75,7 +75,7 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
private static readonly Regex RemuxRegex = new Regex(@"(?:[_. \[]|\d{4}p-)(?<remux>(?:(BD|UHD)[-_. ]?)?Remux)\b|(?<remux>(?:(BD|UHD)[-_. ]?)?Remux[_. ]\d{4}p)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex HardcodedSubsRegex = new Regex(@"\b(?<hcsub>(\w+(?<!(SOFT|HORRIBLE))SUBS?)\b)|(?<hc>(HC|SUBBED))\b",
|
||||
private static readonly Regex HardcodedSubsRegex = new Regex(@"\b((?<hcsub>(\w+(?<!SOFT|HORRIBLE)SUBS?))|(?<hc>(HC|SUBBED)))\b",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
|
||||
public static QualityModel ParseQuality(string name)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<PackageReference Include="MailKit" Version="2.15.0" />
|
||||
<PackageReference Include="Npgsql" Version="6.0.3" />
|
||||
<PackageReference Include="Servarr.FFMpegCore" Version="4.7.0-26" />
|
||||
<PackageReference Include="Servarr.FFprobe" Version="5.0.1.93" />
|
||||
<PackageReference Include="Servarr.FFprobe" Version="5.1.2.106" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||
@@ -18,10 +18,10 @@
|
||||
<PackageReference Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
|
||||
<PackageReference Include="FluentValidation" Version="8.6.2" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
<PackageReference Include="MonoTorrent" Version="2.0.5" />
|
||||
<PackageReference Include="MonoTorrent" Version="2.0.7" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.5" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user