Compare commits

..

13 Commits

Author SHA1 Message Date
Qstick
7d02c00ca8 Bump version to 0.1.2 2023-01-21 13:45:45 -06:00
Qstick
b83ccc19b0 Bump MonoTorrent to 2.0.7 2023-01-21 13:41:04 -06:00
Mark McDowall
93086abf58 Fixed: Multiple pushed releases will be processed sequentially
(cherry picked from commit 1f8e1cf582f59fe1e8dcc0fad15afeed6d9cd9d1)
2023-01-21 13:39:06 -06:00
Qstick
f68dc04273 Fixed: RemotePathMappingCheck Improvements 2023-01-21 13:25:12 -06:00
Qstick
5664054f95 Fixed: DownloadClientRootFolderCheck Improvements 2023-01-21 13:25:11 -06:00
Qstick
f16bd435db Fixed: Catch InvalidDataException during initial config to prevent boot loop
[Common]
2023-01-21 13:25:11 -06:00
Qstick
2bde9d13dd Fixed: Restore old Sqlite version compatibility 2023-01-16 19:46:15 -06:00
ta264
f448481460 New: Respect 429 Retry-After responses from BookInfo 2023-01-13 13:07:21 +00:00
ta264
70856c217c Fixed: Adding book by edition id 2023-01-13 13:07:21 +00:00
Weblate
4db158e0c9 Translated using Weblate (Swedish) [skip ci]
Currently translated at 89.5% (789 of 881 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 66.9% (590 of 881 strings)

Translated using Weblate (Ukrainian) [skip ci]

Currently translated at 53.0% (467 of 881 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 66.8% (589 of 881 strings)

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

Currently translated at 100.0% (881 of 881 strings)

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

Currently translated at 100.0% (881 of 881 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 72.4% (638 of 881 strings)

Translated using Weblate (Greek) [skip ci]

Currently translated at 62.6% (552 of 881 strings)

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

Currently translated at 100.0% (881 of 881 strings)

Translated using Weblate (Ukrainian) [skip ci]

Currently translated at 50.3% (444 of 881 strings)

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

Currently translated at 100.0% (881 of 881 strings)

Translated using Weblate (Bengali) [skip ci]

Currently translated at 0.2% (2 of 881 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 85.9% (757 of 881 strings)

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

Currently translated at 100.0% (881 of 881 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (881 of 881 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 72.4% (638 of 881 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 80.0% (705 of 881 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 99.7% (879 of 881 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 70.8% (624 of 881 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 70.0% (617 of 881 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 66.6% (587 of 881 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (881 of 881 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 66.0% (582 of 881 strings)

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

Currently translated at 100.0% (881 of 881 strings)

Co-authored-by: AlexR-sf <omg.portal.supp@gmail.com>
Co-authored-by: Anthony Veaudry <anthonyveaudry@gmail.com>
Co-authored-by: Casselluu <jack10193@163.com>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Davide Palma <github@davidepalma.it>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Iagocds <cdsiago@gmail.com>
Co-authored-by: Luc Timmerman <timmerman.luc1999@gmail.com>
Co-authored-by: Mipiaceanutella <remix-polity-0l@icloud.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Robin Flikkema <robin@robinflikkema.nl>
Co-authored-by: Tanreon <tanreon@outlook.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: andrey4korop <andrey999@i.ua>
Co-authored-by: benniblot <ben2004engler@gmail.com>
Co-authored-by: deepserket <deepserket@gmail.com>
Co-authored-by: hidaba <nag@hidaba.com>
Co-authored-by: lhquark <lhquark@gmail.com>
Co-authored-by: oskhel <oskar.hellgren@gmail.com>
Co-authored-by: qing zhang <fzeehom@126.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: saambd <me@salimrahman.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/bn/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-01-10 22:22:38 -06:00
Qstick
55ab909fde Cleanup translate.js 2022-12-17 12:50:14 -06:00
Qstick
61c9779022 Fixed: Correct Attribute compare for Id validation
(cherry picked from commit 7e48ea0231272ae56c30f5f43339f0dca7a27fb3)
2022-12-17 12:13:56 -06:00
Qstick
8299c8e13e Update README for DigitalOcean attribution
(cherry picked from commit d3517532a4e28eb716bd949dc7fdbd85da316a0e)
2022-12-17 11:33:34 -06:00
40 changed files with 697 additions and 1349 deletions

View File

@@ -62,6 +62,15 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
[![Mega Sponsors List](https://opencollective.com/Readarr/tiers/mega-sponsor.svg?width=890)](https://opencollective.com/readarr#mega-sponsor)
## 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)

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '0.1.1'
majorVersion: '0.1.2'
minorVersion: $[counter('minorVersion', 1)]
readarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(readarrVersion)'

View File

@@ -1,22 +1,18 @@
import $ from 'jquery';
import createAjaxRequest from 'Utilities/createAjaxRequest';
function getTranslations() {
let localization = null;
const ajaxOptions = {
async: false,
type: 'GET',
global: false,
dataType: 'json',
url: `${window.Readarr.apiRoot}/localization`,
url: '/localization',
success: function(data) {
localization = data.Strings;
}
};
ajaxOptions.headers = ajaxOptions.headers || {};
ajaxOptions.headers['X-Api-Key'] = window.Readarr.apiKey;
createAjaxRequest(ajaxOptions);
$.ajax(ajaxOptions);
return localization;
}

View File

@@ -26,7 +26,7 @@
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageVersion Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr22" />
<PackageVersion Include="Moq" Version="4.17.2" />
<PackageVersion Include="MonoTorrent" Version="2.0.6" />
<PackageVersion Include="MonoTorrent" Version="2.0.7" />
<PackageVersion Include="NBuilder" Version="6.1.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
<PackageVersion Include="NLog.Extensions.Logging" Version="5.1.0" />

View File

@@ -1,371 +0,0 @@
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.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 RemoteEpisode CreateRemoteEpisode()
{
var episode = base.CreateRemoteEpisode();
episode.Release.DownloadUrl = _downloadURL;
return episode;
}
[Test]
public void Download_with_DestinationDirectory_should_force_directory()
{
GivenDestinationDirectory();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
Subject.Download(remoteEpisode);
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 remoteEpisode = CreateRemoteEpisode();
Subject.Download(remoteEpisode);
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 remoteEpisode = CreateRemoteEpisode();
Subject.Download(remoteEpisode);
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 remoteEpisode = CreateRemoteEpisode();
Subject.Download(remoteEpisode);
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(15, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.First, true)]
[TestCase(15, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.First, false)]
[TestCase(15, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.Last, true)]
[TestCase(15, (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 remoteEpisode = CreateRemoteEpisode();
var episode = new Tv.Episode()
{
AirDateUtc = DateTime.UtcNow.Date.AddDays(-ageDay)
};
remoteEpisode.Episodes.Add(episode);
Subject.Download(remoteEpisode);
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 remoteEpisode = CreateRemoteEpisode();
remoteEpisode.SeedConfiguration = new TorrentSeedConfiguration();
remoteEpisode.SeedConfiguration.Ratio = providerSeedRatio;
Subject.Download(remoteEpisode);
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);
}
}
}

View File

@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Test.Download
_downloadClients = new List<IDownloadClient>();
Mocker.GetMock<IProvideDownloadClient>()
.Setup(v => v.GetDownloadClients())
.Setup(v => v.GetDownloadClients(It.IsAny<bool>()))
.Returns(_downloadClients);
Mocker.GetMock<IProvideDownloadClient>()

View File

@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
public void should_return_warning_when_download_client_has_not_been_configured()
{
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients())
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(Array.Empty<IDownloadClient>());
Subject.Check().ShouldBeWarning();
@@ -40,7 +40,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Throws<Exception>();
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients())
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(new IDownloadClient[] { downloadClient.Object });
Subject.Check().ShouldBeError();
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Returns(new List<DownloadClientItem>());
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients())
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(new IDownloadClient[] { downloadClient.Object });
Subject.Check().ShouldBeOk();

View File

@@ -49,7 +49,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Returns(_clientStatus);
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients())
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(new IDownloadClient[] { _downloadClient.Object });
Mocker.GetMock<IDiskProvider>()

View File

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

View File

@@ -75,7 +75,7 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
[Test]
public void getting_details_of_invalid_book()
{
Assert.Throws<BookNotFoundException>(() => Subject.GetBookInfo("99999999"));
Assert.Throws<BookNotFoundException>(() => Subject.GetBookInfo("1"));
}
private void ValidateAuthor(Author author)

View File

@@ -45,13 +45,17 @@ namespace NzbDrone.Core.AuthorStats
}
}
private SqlBuilder Builder() => new SqlBuilder(_database.DatabaseType)
.Select(@"""Authors"".""Id"" AS ""AuthorId"",
private SqlBuilder Builder()
{
var trueIndicator = _database.DatabaseType == DatabaseType.PostgreSQL ? "true" : "1";
return new SqlBuilder(_database.DatabaseType)
.Select($@"""Authors"".""Id"" AS ""AuthorId"",
""Books"".""Id"" AS ""BookId"",
SUM(COALESCE(""BookFiles"".""Size"", 0)) AS ""SizeOnDisk"",
1 AS ""TotalBookCount"",
CASE WHEN MIN(""BookFiles"".""Id"") IS NULL THEN 0 ELSE 1 END AS ""AvailableBookCount"",
CASE WHEN (""Books"".""Monitored"" = true AND (""Books"".""ReleaseDate"" < @currentDate) OR ""Books"".""ReleaseDate"" IS NULL) OR MIN(""BookFiles"".""Id"") IS NOT NULL THEN 1 ELSE 0 END AS ""BookCount"",
CASE WHEN (""Books"".""Monitored"" = {trueIndicator} AND (""Books"".""ReleaseDate"" < @currentDate) OR ""Books"".""ReleaseDate"" IS NULL) OR MIN(""BookFiles"".""Id"") IS NOT NULL THEN 1 ELSE 0 END AS ""BookCount"",
CASE WHEN MIN(""BookFiles"".""Id"") IS NULL THEN 0 ELSE COUNT(""BookFiles"".""Id"") END AS ""BookFileCount""")
.Join<Edition, Book>((e, b) => e.BookId == b.Id)
.Join<Book, Author>((book, author) => book.AuthorMetadataId == author.AuthorMetadataId)
@@ -60,5 +64,6 @@ namespace NzbDrone.Core.AuthorStats
.GroupBy<Author>(x => x.Id)
.GroupBy<Book>(x => x.Id)
.AddParameters(new Dictionary<string, object> { { "currentDate", DateTime.UtcNow } });
}
}
}

View File

@@ -1,27 +0,0 @@
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);
}
}
}

View File

@@ -1,10 +0,0 @@
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
{
public class FreeboxDownloadException : DownloadClientException
{
public FreeboxDownloadException(string message)
: base(message)
{
}
}
}

View File

@@ -1,8 +0,0 @@
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
{
public enum FreeboxDownloadPriority
{
Last = 0,
First = 1
}
}

View File

@@ -1,277 +0,0 @@
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.");
}
}
}
}

View File

@@ -1,87 +0,0 @@
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));
}
}
}

View File

@@ -1,21 +0,0 @@
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();
}
}
}
}

View File

@@ -1,137 +0,0 @@
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";
}
}
}

View File

@@ -1,18 +0,0 @@
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; }
}
}

View File

@@ -1,69 +0,0 @@
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";
}
}
}

View File

@@ -1,227 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentValidation.Results;
using NLog;
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.MediaFiles.TorrentInfo;
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,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, 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(RemoteEpisode remoteEpisode, string hash, string magnetLink)
{
return _proxy.AddTaskFromUrl(magnetLink,
GetDownloadDirectory().EncodeBase64(),
ToBePaused(),
ToBeQueuedFirst(remoteEpisode),
GetSeedRatio(remoteEpisode),
Settings);
}
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
{
return _proxy.AddTaskFromFile(filename,
fileContent,
GetDownloadDirectory().EncodeBase64(),
ToBePaused(),
ToBeQueuedFirst(remoteEpisode),
GetSeedRatio(remoteEpisode),
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(RemoteEpisode remoteEpisode)
{
if ((remoteEpisode.IsRecentEpisode() && Settings.RecentPriority == (int)FreeboxDownloadPriority.First) ||
(!remoteEpisode.IsRecentEpisode() && Settings.OlderPriority == (int)FreeboxDownloadPriority.First))
{
return true;
}
return false;
}
private double? GetSeedRatio(RemoteEpisode remoteEpisode)
{
if (remoteEpisode.SeedConfiguration == null || remoteEpisode.SeedConfiguration.Ratio == null)
{
return null;
}
return remoteEpisode.SeedConfiguration.Ratio.Value * 100;
}
}
}

View File

@@ -9,7 +9,7 @@ namespace NzbDrone.Core.Download
public interface IProvideDownloadClient
{
IDownloadClient GetDownloadClient(DownloadProtocol downloadProtocol);
IEnumerable<IDownloadClient> GetDownloadClients();
IEnumerable<IDownloadClient> GetDownloadClients(bool filterBlockedClients = false);
IDownloadClient Get(int id);
}
@@ -67,14 +67,39 @@ namespace NzbDrone.Core.Download
return provider;
}
public IEnumerable<IDownloadClient> GetDownloadClients()
public IEnumerable<IDownloadClient> GetDownloadClients(bool filterBlockedClients = false)
{
return _downloadClientFactory.GetAvailableProviders();
var enabledClients = _downloadClientFactory.GetAvailableProviders();
if (filterBlockedClients)
{
return FilterBlockedIndexers(enabledClients).ToList();
}
return enabledClients;
}
public IDownloadClient Get(int id)
{
return _downloadClientFactory.GetAvailableProviders().Single(d => d.Definition.Id == id);
}
private IEnumerable<IDownloadClient> FilterBlockedIndexers(IEnumerable<IDownloadClient> clients)
{
var blockedClients = _downloadClientStatusService.GetBlockedProviders().ToDictionary(v => v.ProviderId, v => v);
foreach (var client in clients)
{
DownloadClientStatus blockedClientStatus;
if (blockedClients.TryGetValue(client.Definition.Id, out blockedClientStatus))
{
_logger.Debug("Temporarily ignoring client {0} till {1} due to recent failures.", client.Definition.Name, blockedClientStatus.DisabledTill.Value.ToLocalTime());
continue;
}
yield return client;
}
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Net.Http;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events;
@@ -37,7 +38,9 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check()
{
var clients = _downloadClientProvider.GetDownloadClients();
// Only check clients not in failure status, those get another message
var clients = _downloadClientProvider.GetDownloadClients(true);
var rootFolders = _rootFolderService.All();
foreach (var client in clients)
@@ -58,6 +61,10 @@ namespace NzbDrone.Core.HealthCheck.Checks
{
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
}
catch (HttpRequestException ex)
{
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
}
catch (Exception ex)
{
_logger.Error(ex, "Unknown error occured in DownloadClientRootFolderCheck HealthCheck");

View File

@@ -53,7 +53,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)
{

View File

@@ -1 +1,4 @@
{}
{
"About": "সম্পর্কিত",
"Actions": "ক্রিয়াকাণ্ড"
}

View File

@@ -349,7 +349,7 @@
"ShownAboveEachColumnWhenWeekIsTheActiveView": "Wird in der Wochenansicht über jeder Spalte angezeigt",
"Size": " Größe",
"SkipFreeSpaceCheck": "Pürfung des freien Speichers überspringen",
"SkipFreeSpaceCheckWhenImportingHelpText": "Aktiviere diese Option, wenn es Readarr nicht möglich ist, den freien Speicherplatz des Stammverzeichnises für Autoren zu erkennen",
"SkipFreeSpaceCheckWhenImportingHelpText": "Aktiviere diese Option, wenn es Readarr nicht möglich ist, den freien Speicherplatz des Stammverzeichnisses für Autoren zu erkennen",
"SorryThatAuthorCannotBeFound": "Schade, dieser Film kann nicht gefunden werden.",
"SorryThatBookCannotBeFound": "Schade, dieser Film kann nicht gefunden werden.",
"Source": "Quelle",
@@ -426,7 +426,7 @@
"UnmonitoredHelpText": "Nicht beobachtete Filme im iCal-Feed einschließen",
"UpdateAll": "Alle aktualisieren",
"UpdateAutomaticallyHelpText": "Updates automatisch herunteraden und installieren. Es kann weiterhin unter \"System -> Updates\" ein manuelles Update angestoßen werden",
"UpdateMechanismHelpText": "Benutze Readarr's Built-In Updater oder ein Script",
"UpdateMechanismHelpText": "Readarr's Built-In Updater oder ein Script verwenden",
"UpdateScriptPathHelpText": "Pfad zu einem benutzerdefinierten Skript, das ein extrahiertes Update-Paket übernimmt und den Rest des Update-Prozesses abwickelt",
"Updates": "Updates",
"UpgradeAllowedHelpText": "Wenn deaktiviert wird die Qualität nicht verbessert",
@@ -543,7 +543,7 @@
"DeleteMetadataProfile": "Metadaten Profil löschen",
"ImportListExclusions": "Ausschlüsse der Importliste",
"ImportLists": "Importlisten",
"ImportListSettings": "Allgemeine Einstellungen der Importliste",
"ImportListSettings": "Allgemeine Importlisten-Einstellungen",
"ImportListSpecificSettings": "Listenspezifische Einstellungen importieren",
"IndexerLongTermStatusCheckSingleClientMessage": "Indexer wegen über 6 Stunden langen bestehenden Fehlern nicht verfügbar: {0}",
"IndexerPriorityHelpText": "Indexer Priorität von 1 (höchste) bis 50 (niedrigste). Standard: 25. Wird beim Holen von Veröffentlichungen als Tiebreaker für ansonsten gleiche Veröffentlichungen verwendet, Readarr benützt weiterhin alle aktivierten Indexer für RSS-Synchronisierung und -Suche.",
@@ -702,7 +702,7 @@
"ImportListStatusCheckSingleClientMessage": "Listen aufgrund von Fehlern nicht verfügbar: {0}",
"ImportMechanismHealthCheckMessage": "Aktiviere die Verarbeitung der abgeschlossenen Downloads",
"IndexerRssHealthCheckNoIndexers": "Da keine Indexer mit aktivierter RSS-Synchronisierung aktiviert sind, erfasst Readarr neue Erscheinungen nicht automatisch",
"IndexerSearchCheckNoInteractiveMessage": "Keine Indexer mit interaktiver Suche verfügbar, Readarr liefert keine interaktiven Suchergebnisse",
"IndexerSearchCheckNoInteractiveMessage": "Keine Indexer mit aktivierter interaktiver Suche verfügbar, Readarr liefert keine interaktiven Suchergebnisse",
"IndexerStatusCheckSingleClientMessage": "Indexer aufgrund von Fehlern nicht verfügbar: {0}",
"ChownGroup": "chown Gruppe",
"AllowFingerprintingHelpText": "Benutze Fingerabdrücke um die Genauigkeit der Buch Übereinstimmungen zu verbessern",
@@ -877,5 +877,7 @@
"ShowBookCount": "Zeige Anzahl an Büchern",
"SkipPartBooksAndSets": "Überspringe Teilbücher und Sets",
"TagsHelpText": "Gilt für Autoren mit mindestens einem passenden Tag. Leer lassen, um auf alle Autoren anzuwenden",
"TagsSettingsSummary": "Verwalten von Autoren-, Profil-, Beschränkungs- und Benachrichtigungs-Tags"
"TagsSettingsSummary": "Verwalten von Autoren-, Profil-, Beschränkungs- und Benachrichtigungs-Tags",
"ApplicationURL": "Anwendungs-URL",
"ApplicationUrlHelpText": "Die externe URL der Anwendung inklusive http(s)://, Port und URL-Basis"
}

View File

@@ -6,8 +6,8 @@
"Size": " Μέγεθος",
"Source": "Πηγή",
"Uptime": "Ώρα",
"20MinutesTwenty": "60 λεπτά: {0}",
"45MinutesFourtyFive": "60 λεπτά: {0}",
"20MinutesTwenty": "20 λεπτά: {0}",
"45MinutesFourtyFive": "45 λεπτά: {0}",
"60MinutesSixty": "60 λεπτά: {0}",
"APIKey": "Κλειδί API",
"About": "Σχετικά",
@@ -557,5 +557,9 @@
"Test": "Δοκιμή",
"AddList": "Προσθήκη Λίστας",
"RenameFiles": "Μετονομασία αρχείων",
"ManualImportSelectEdition": "Μη αυτόματη εισαγωγή - Επιλέξτε ταινία"
"ManualImportSelectEdition": "Μη αυτόματη εισαγωγή - Επιλέξτε ταινία",
"AllExpandedCollapseAll": "Σύμπτυξη Όλων",
"AllExpandedExpandAll": "Ανάπτυξη Όλων",
"AddNewItem": "Προσθήκη Νέου",
"AddMissing": "Προσθήκη Στοιχείου Που Λείπει"
}

View File

@@ -448,7 +448,7 @@
"LoadingBooksFailed": "Elokuvatiedostojen lataaminen epäonnistui",
"ProxyPasswordHelpText": "Käyttäjätunnus ja salasana tulee syöttää vain tarvittaessa. Muussa tapauksessa jätä kentät tyhjiksi.",
"SslCertPathHelpTextWarning": "Käyttöönotto vaatii uudelleenkäynnistyksen.",
"UnableToLoadMetadataProfiles": "Metatietoprofiilien lataus epäonnistui.",
"UnableToLoadMetadataProfiles": "Metatietoprofiileja ei voida ladata.",
"DownloadClientCheckDownloadingToRoot": "Lataustyökalu '{0}' sijoittaa lataukset juurikansioon '{1}' ja näin ei pitäisi tehdä, vaan lataukset tulee tallentaa erilliseen sijaintiin.",
"ReplaceIllegalCharactersHelpText": "Korvaa laittomat merkit. Jos ei käytössä, laittomat merkit poistetaan.",
"OutputPath": "Tallennussijainti",
@@ -624,7 +624,7 @@
"UpdateAvailable": "Uusi päivitys on saatavilla",
"Filters": "Suodattimet",
"General": "Yleiset",
"ImportMechanismHealthCheckMessage": "Ota valmiiden latausten käsittely käyttöön",
"ImportMechanismHealthCheckMessage": "Käytä valmiiden latausten käsittelyä",
"IndexerJackettAll": "Jackettin ei-tuettua 'all'-päätettä käyttävät tietolähteet: {0}",
"IndexersSettingsSummary": "Sisältölähteet ja julkaisurajoitukset.",
"IndexerStatusCheckAllClientMessage": "Tietolähteet eivät ole käytettävissä virheiden vuoksi",
@@ -637,7 +637,7 @@
"RemotePathMappingCheckFilesGenericPermissions": "Lataustyökalu '{0}' ilmoitti tiedostosijainniksi '{1}', mutta Radarr ei näe sitä. Saata joutua muokkaamaan kansion käyttöoikeuksia.",
"RemotePathMappingCheckImportFailed": "Radarr ei voinut tuoda elokuvaa. Tarkista loki saadaksesi lisätietoja.",
"CouldntFindAnyResultsForTerm": "Haku '{0}' ei tuottanut tuloksia.",
"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.",
"RemotePathMappingCheckDockerFolderMissing": "Käytät Dockeria ja lataustyökalu '{0}' sijoittaa lataukset kohteeseen '{1}', mutta kansiota ei näytä olevan olemassa containerissa. Tarkista etäsijaintiesi kartoitukset ja containerin tallennusmedia-asetukset.",
"RemotePathMappingCheckDownloadPermissions": "Radarr näkee, muttei voi käyttää ladattua elokuvaa '{0}'. Todennäköinen syy on sijainnin käyttöoikeusvirhe.",
@@ -663,7 +663,7 @@
"FileWasDeletedByUpgrade": "Tiedosto poistettiin päivityksen tuontia varten.",
"HealthNoIssues": "Kokoonpanossasi ei ole ongelmia.",
"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.",
"IndexerStatusCheckSingleClientMessage": "Tietolähteet eivät ole käytettävissä virheiden vuoksi: {0}",
"MissingFromDisk": "Radarr ei löytänyt tiedostoa levyltä, joten sen kytkös kirjaston elokuvaan poistettiin",
"Monitor": "Valvo",
@@ -676,7 +676,7 @@
"OnRename": "Kun elokuva nimetään uudelleen",
"OnUpgrade": "Kun elokuva päivitetään",
"ProxyCheckResolveIpMessage": "Määritetyn välityspalvelimen '{0}' IP-osoitteen selvitys epäonnistui.",
"QualitySettingsSummary": "Laatumääritykset erilaisia sisältömuotoja ja teidostokokoja varten.",
"QualitySettingsSummary": "Laatumääritykset erilaisia sisältömuotoja ja tiedostokokoja varten.",
"QueueIsEmpty": "Jono on tyhjä",
"ReadarrSupportsAnyDownloadClient": "Radarr tukee kaikkien Newznab-yhteensopivien lataustyökalujen ohella myös monia muita alla listattuja torrent- ja Usenet-lataustyökaluja.",
"RefreshAndScan": "Päivitä ja tarkista",
@@ -687,7 +687,7 @@
"SettingsRemotePathMappingRemotePath": "Etäsijainti",
"SettingsRemotePathMappingRemotePathHelpText": "Lataustyökalun käyttämän hakemiston juurisijainti",
"SizeLimit": "Kokorajoitus",
"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.",
"UISettingsSummary": "Kalenterin, päiväyksen ja kellonajan sekä kielen ja heikentyneelle värinäölle sopivan tilan asetukset.",
"UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent-tiedon ilmoitti sovellus, joka kommunikoi API:n kanssa",
"WatchLibraryForChangesHelpText": "Suorita automaattinen uudelleentutkinta, kun juurikansiossa havaitaan tiedostomuutoksia.",
@@ -699,8 +699,61 @@
"MonitorNewBooks": "Valvo uusia kirjoja",
"RenameFiles": "Uudelleennimeä tiedostot",
"Test": "Kokeile",
"AllowFingerprintingHelpText": "Käytä piiloleimausta kirjojen täsmäyksen tarkennukseen",
"AllowFingerprintingHelpText": "Tarkenna kirjojen täsmäystarkkuutta piiloleimauksen avulla.",
"AllowFingerprinting": "Salli piiloleimaus",
"Database": "Tietokanta",
"ManualImportSelectEdition": "Manuaalinen tuonti - Valitse versio"
"ManualImportSelectEdition": "Manuaalinen tuonti - Valitse versio",
"ApplicationURL": "Sovelluksen URL-osoite",
"MusicBrainzReleaseID": "MusicBrainz-julkaisutunniste",
"MusicBrainzTrackID": "MusicBrainz-kappaletunniste",
"ShowBanners": "Näytä bannerit",
"CatalogNumber": "Luettelonumero",
"Continuing": "Jatkuva",
"DeleteRootFolder": "Poista juurikansio",
"DiscCount": "Levyjen määrä",
"EditList": "Muokkaa listaa",
"EnableProfile": "Käytä profiilia",
"FutureDays": "Tulevat päivät",
"ImportFailures": "Tuontivirheet",
"ImportLists": "Tuotilistat",
"ImportListSettings": "Tuontilistojen yleiset asetukset",
"ImportListSpecificSettings": "Tuotilistakohtaiset asetukset",
"IsShowingMonitoredMonitorSelected": "Valvo valittuja",
"IsShowingMonitoredUnmonitorSelected": "Lopeta valittujen valvonta",
"PreferredHelpTexts2": "Positiivisia tuloksia suositaan enemmän",
"MusicbrainzId": "MusicBrainz-tunniste",
"MusicBrainzRecordingID": "MusicBrainz-tallennetunniste",
"ShouldSearchHelpText": "Etsi tietolähteistä hiljattain lisättyjä kohteita. Käytä suurien listojen kanssa varoen.",
"OnDownloadFailure": "Latauksen epäonnistuessa",
"OnDownloadFailureHelpText": "Latauksen epäonnistuessa",
"OnImportFailure": "Tuonnin epäonistuessa",
"OnImportFailureHelpText": "Tuonnin epäonistuessa",
"OnReleaseImport": "Tuotaessa julkaisu",
"OnReleaseImportHelpText": "Tuotaessa julkaisu",
"PreferredHelpTexts3": "Negatiivisia tuloksia suositaan vähemmän",
"ReleaseProfiles": "Julkaisuprofiilit",
"ShowBannersHelpText": "Näytä nimien sijaan bannerit.",
"StatusEndedContinuing": "Jatkuu",
"UnableToLoadMetadataProviderSettings": "Metatietolähteen asetuksia ei voitu ladata",
"UnmappedFiles": "Kartoittamattomat tiedostot",
"UpdatingIsDisabledInsideADockerContainerUpdateTheContainerImageInstead": "Päivittäminen ei ole Docker-säiliössä käytettävissä. Päivitä sen sijaan säiliön kuva.",
"WatchRootFoldersForFileChanges": "Seuraa juurikansioiden tiedostomuutoksia",
"WriteMetadataTags": "Tallenna metatietotagit",
"Country": "Maa",
"MonitoringOptions": "Valvonta-asetukset",
"PastDays": "Menneet päivät",
"SearchMonitored": "Etsi valvottuja",
"SkipRedownload": "Ohita uudelleenlataus",
"TrackNumber": "Kappaleiden numero",
"TrackTitle": "Kappaleiden nimi",
"IsExpandedHideFileInfo": "Piilota tiedostotiedot",
"MetadataConsumers": "Metatietojen kuluttajat",
"MetadataProviderSource": "Metatietotoimittajan lähde",
"MetadataSource": "Metatietojen lähde",
"FutureDaysHelpText": "Päivien määrä, jonka verran tulevaisuuteen iCal-syötettä seurataan.",
"ManualDownload": "Manuaalinen lataus",
"PastDaysHelpText": "Päivien määrä, jonka verran menneisyyteen iCal-syötettä seurataan.",
"DiscNumber": "Levyn numero",
"ForeignIdHelpText": "Ohitettavan kirjailijan/kirjan MusicBrainz-tunniste.",
"IsExpandedShowFileInfo": "Näytä tiedostotiedot"
}

View File

@@ -877,5 +877,7 @@
"InstanceNameHelpText": "Példánynév a böngésző lapon és a syslog alkalmazás neve",
"LoadingEditionsFailed": "A kiadások betöltése nem sikerült",
"ManualImportSelectEdition": "Kézi importálás Válaszd ki a Kiadást",
"Database": "Adatbázis"
"Database": "Adatbázis",
"ApplicationURL": "Alkalmazás URL-je",
"ApplicationUrlHelpText": "Az alkalmazás külső URL-címe, beleértve a http(s)://-t, a portot és az URL-alapot"
}

View File

@@ -4,7 +4,7 @@
"45MinutesFourtyFive": "45 Minuti: {0}",
"60MinutesSixty": "60 Minuti: {0}",
"APIKey": "Chiave API",
"About": "Informazioni",
"About": "Info",
"AddListExclusion": "Aggiungi Lista Esclusioni",
"AddingTag": "Aggiungi etichetta",
"AdvancedSettingsHiddenClickToShow": "Nascosto, clicca per mostrare",
@@ -44,11 +44,11 @@
"AutoRedownloadFailedHelpText": "Cerca e prova a scaricare automaticamente un'altra versione",
"Automatic": "Automatico",
"BackupFolderHelpText": "I percorsi relativi saranno nella cartella AppData di Readarr",
"BackupNow": "Effettua il Backup adesso",
"BackupNow": "Esegui backup ora",
"BackupRetentionHelpText": "I backup automatici più vecchi del periodo di conservazione verranno eliminati automaticamente",
"Backups": "I Backup",
"BindAddress": "Indirizzo di Bind",
"BindAddressHelpText": "Indirizzo IPv4 valido o '*' per tutte le interfacce",
"BindAddressHelpText": "Indirizzo IPV4 valido o '*' per tutte le interfacce di rete",
"BindAddressHelpTextWarning": "Richiede il riavvio per avere effetto",
"BookIsDownloading": "Libro in download",
"BookIsDownloadingInterp": "Libro in download - {0}% {1}",
@@ -59,7 +59,7 @@
"Cancel": "Annulla",
"CancelMessageText": "Sei sicuro di voler cancellare questa operazione in sospeso?",
"CertificateValidation": "Convalida del Certificato",
"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.",
"ChangeFileDate": "Cambiare la Data del File",
"ChangeHasNotBeenSavedYet": "Il cambio non è stato ancora salvato",
"ChmodFolder": "Permessi Cartella",
@@ -381,9 +381,9 @@
"SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Verrà usato durante la ricerca interattiva",
"TagIsNotUsedAndCanBeDeleted": "L'etichetta non è in uso e può essere eliminata",
"Tasks": "Attività",
"TestAll": "Testa Tutti",
"TestAllClients": "Testa Tutti i Client",
"TestAllIndexers": "Testa tutti gli Indicizzatori",
"TestAll": "Prova Tutti",
"TestAllClients": "Testa tutti i client",
"TestAllIndexers": "Prova tutti gli indicizzatori",
"TestAllLists": "Testa tutte le liste",
"ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "Questo verrà applicato a tutti gli indexer, segui le regole impostate da loro",
"TimeFormat": "Formato orario",
@@ -544,7 +544,7 @@
"ProxyCheckBadRequestMessage": "Il test del proxy è fallito. Codice Stato: {0}",
"ProxyCheckFailedToTestMessage": "Test del proxy fallito: {0}",
"QualitySettingsSummary": "Dimensioni delle qualità e denominazione",
"Queued": "Messo in coda",
"Queued": "In coda",
"RefreshAndScan": "Aggiorna e Scansiona",
"RemotePathMappingCheckBadDockerPath": "Stai utilizzando docker; Il client di download {0} mette i download in {1} ma questo non è un percorso valido {2}. Controlla la mappa dei percorsi remoti e le impostazioni del client di download.",
"RemotePathMappingCheckDockerFolderMissing": "Stai utilizzando docker; il download client {0} riporta files in {1} ma questa directory non sembra esistere nel contenitore. Controlla la mappa dei percorsi remoti e le impostazioni dei volumi del container.",
@@ -617,9 +617,34 @@
"Series": "Serie",
"Test": "Test",
"InstanceName": "Nome Istanza",
"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",
"LogRotateHelpText": "Numero massimo di file di log da tenere salvati nella cartella log",
"LogRotation": "Rotazione Log",
"ManualImportSelectEdition": "Importazione manuale: seleziona Film",
"Database": "Database"
"Database": "Database",
"ApplicationURL": "URL Applicazione",
"ApplicationUrlHelpText": "L'URL esterno di questa applicazione, incluso http(s)://, porta e URL base",
"CatalogNumber": "Numero Catalogo",
"Country": "Nazione",
"Continuing": "Continuando",
"EditList": "Modifica Lista",
"EnableProfile": "Abilita Profilo",
"ChownGroup": "chown Group",
"DiscNumber": "Numero Disco",
"DeleteImportList": "Cancella la lista di importazione",
"DeleteRootFolder": "Cancella la cartella principale",
"CalibreSettings": "Impostazioni di Calibre",
"Started": "Iniziato",
"CalibreContentServer": "Server di Contenuto Calibre",
"BooksTotal": "Libri ({0})",
"BookTitle": "Titolo del Libro",
"CalibrePassword": "Password di Calibre",
"CalibrePort": "Porta di Calibre",
"CalibreUrlBase": "URL di Base di Calibre",
"CalibreUsername": "Nome Utente di Calibre",
"CalibreHost": "Host di Calibre",
"CalibreLibrary": "Libreria di Calibre",
"CalibreMetadata": "Metadati di Calibre",
"CalibreOutputFormat": "Formato di output di Calibre",
"CalibreOutputProfile": "Profilo di output di Calibre"
}

View File

@@ -42,7 +42,7 @@
"BackupRetentionHelpText": "Automatische veiligheidskopieën ouder dan de retentie periode zullen worden opgeruimd",
"Backups": "Veiligheidskopieën",
"BindAddress": "Gebonden Adres",
"BindAddressHelpText": "Geldig IPv4 adres of '*' voor alle interfaces",
"BindAddressHelpText": "Geldig IPv4-adres of '*' voor alle interfaces",
"BindAddressHelpTextWarning": "Herstarten vereist om in werking te treden",
"BookIsDownloading": "Film is aan het downloaden",
"BookIsDownloadingInterp": "Film is aan het downloaden - {0}% {1}",
@@ -53,7 +53,7 @@
"Cancel": "Annuleer",
"CancelMessageText": "Bent u zeker dat u deze taak in afwachting wilt annuleren?",
"CertificateValidation": "Certificaat Validatie",
"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.",
"ChangeFileDate": "Wijzig Bestandsdatum",
"ChangeHasNotBeenSavedYet": "Wijziging is nog niet opgeslagen",
"ChmodFolder": "chmod Map",
@@ -591,5 +591,12 @@
"RenameFiles": "Hernoem Bestanden",
"Test": "Test",
"ManualImportSelectEdition": "Manuele import - Selecteer Film",
"Database": "Databasis"
"Database": "Databasis",
"AddNewItem": "Voeg nieuwe toe",
"AddImportListExclusionHelpText": "Voorkom dat Readarr het boek toevoegt met Import Lijsten of Auteur Verversing",
"AllAuthorBooks": "Alle boeken van deze auteur",
"AllExpandedCollapseAll": "Klap alles in",
"AllExpandedExpandAll": "Klap alles uit",
"AllowAuthorChangeClickToChangeAuthor": "Klik om auteur aan te passen",
"AllowedLanguages": "Toegestane talen"
}

View File

@@ -224,7 +224,7 @@
"MinimumFreeSpace": "Mínimo de espaço livre",
"MinimumFreeSpaceWhenImportingHelpText": "Impedir a importação se deixar menos do que esta quantidade de espaço em disco disponível",
"MinimumLimits": "Limites mínimos",
"MonoVersion": "Versão do Mono",
"MonoVersion": "Versão Mono",
"MoreInfo": "Mais informações",
"MustNotContain": "Não deve conter",
"Name": "Nome",
@@ -260,7 +260,7 @@
"Preferred": "Preferido",
"PreviewRename": "Visualizar renomeação",
"Profiles": "Perfis",
"Proper": "Proper",
"Proper": "Apropriado",
"PropersAndRepacks": "Propers e repacks",
"Protocol": "Protocolo",
"ProtocolHelpText": "Escolha que protocolo(s) utilizar e qual o preferido ao escolher entre versões iguais",
@@ -761,7 +761,7 @@
"OnRename": "Ao Renomear",
"OnUpgrade": "Ao Atualizar",
"AppDataLocationHealthCheckMessage": "A atualização não será possível para evitar a exclusão de AppData na atualização",
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa Interativa habilitada, Readarr não irá prover quaisquer resultados para pesquisa interativa",
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa interativa habilitada, o Readarr não fornecerá nenhum resultado de pesquisa interativo",
"ConnectSettingsSummary": "Notificações, conexões com servidores/tocadores de mídia e scripts personalizados",
"DownloadClientStatusCheckAllClientMessage": "Todos os clientes download não estão disponíveis devido a falhas",
"DownloadClientsSettingsSummary": "Clientes de download, gerenciamento do download e mapeamento remoto de caminhos",
@@ -821,7 +821,7 @@
"ReadarrSupportsAnyDownloadClient": "Readarr suporta muitos clientes populares de download de torrent e usenet.",
"RemotePathMappingCheckDockerFolderMissing": "Você está usando o docker; cliente de download {0} coloca downloads em {1}, mas esse diretório parece não existir dentro do contêiner. Revise seus mapeamentos de caminho remoto e configurações de volume de contêiner.",
"RemotePathMappingCheckFilesLocalWrongOSPath": "O cliente de download local {0} relatou arquivos em {1}, mas este não é um caminho {2} válido. Revise as configurações do seu cliente de download.",
"RemotePathMappingCheckGenericPermissions": "O cliente de download {0} coloca os downloads em {1}, mas o Readarr 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 Readarr não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.",
"RemotePathMappingCheckRemoteDownloadClient": "O cliente de download remoto {0} relatou arquivos em {1}, mas este diretório parece não existir. Provavelmente faltando mapeamento de caminho remoto.",
"SettingsRemotePathMappingLocalPathHelpText": "Caminho que Readarr deve usar para acessar o caminho remoto localmente",
"RootFolderCheckSingleMessage": "Pasta raiz ausente: {0}",
@@ -851,7 +851,7 @@
"UpdateCheckStartupTranslocationMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' está em uma pasta App Translocation.",
"TagsSettingsSummary": "Gerenciar etiquetas de autor, perfil, restrição e notificação",
"InstanceNameHelpText": "Nome da instância na guia e para o nome do aplicativo Syslog",
"AddList": "Adicionar à Lista",
"AddList": "Adicionar Lista",
"DataExistingBooks": "Monitorar livros que você tem arquivos ou que não foram lançados ainda",
"DataListMonitorAll": "Monitorar autores e todos os livros para cada autor incluído na lista de importação",
"DataListMonitorNone": "Não monitorar autores ou livros",
@@ -861,7 +861,7 @@
"RenameFiles": "Renomear Arquivos",
"Test": "Testar",
"WriteMetadataTags": "Salvar Etiquetas de Metadados",
"RestartRequiredHelpTextWarning": "Requer reinicio para fazer efeito",
"RestartRequiredHelpTextWarning": "Requer reinicialização para ter efeito",
"InstanceName": "Nome da instância",
"ConvertToFormat": "Converter para o Formato",
"DataAllBooks": "Monitorar todos os livros",
@@ -877,5 +877,7 @@
"SetReadarrTags": "Configurar Etiquetas do Readarr",
"Database": "Banco de dados",
"LoadingEditionsFailed": "Falha ao carregar edições",
"ManualImportSelectEdition": "Importação Manual - Selecionar Edição"
"ManualImportSelectEdition": "Importação Manual - Selecionar Edição",
"ApplicationURL": "URL do Aplicativo",
"ApplicationUrlHelpText": "A URL externa deste aplicativo, incluindo http(s)://, porta e base da URL"
}

View File

@@ -592,5 +592,8 @@
"Started": "Запущено",
"Database": "База данных",
"InstanceName": "Имя экземпляра",
"InstanceNameHelpText": "Имя экземпляра на вкладке и для имени приложения системного журнала"
"InstanceNameHelpText": "Имя экземпляра на вкладке и для имени приложения системного журнала",
"AllowedLanguages": "Разрешенные языки",
"ApplicationURL": "URL-адрес приложения",
"ApplicationUrlHelpText": "Внешний URL-адрес этого приложения, включая http(s)://, порт и базовый URL-адрес"
}

View File

@@ -803,5 +803,7 @@
"DataExistingBooks": "Bevaka album som har filer eller inte har släppts än",
"DataFirstBook": "Bevaka första album. Alla andra album kommer bli ignorerade",
"DataFuturebooks": "Bevaka albums som inte har släppts än",
"DataMissingBooks": "Bevaka album som har filer eller inte har släppts än"
"DataMissingBooks": "Bevaka album som har filer eller inte har släppts än",
"ApplicationURL": "Applikations-URL",
"ApplicationUrlHelpText": "Denna applikations externa URL inklusive http(s)://, port och URL-bas"
}

View File

@@ -95,5 +95,375 @@
"BackupFolderHelpText": "Відносні шляхи будуть у каталозі AppData Radarr",
"BlocklistHelpText": "Забороняє Radarr знову автоматично захопити цей випуск",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Гілка, що використовується зовнішнім механізмом оновлення",
"AddList": "Додати список"
"AddList": "Додати список",
"ShowDateAdded": "Показати дату додавання",
"UnableToLoadBlocklist": "Не вдалося завантажити список блокувань",
"UnableToLoadQualityProfiles": "Не вдалося завантажити профілі якості",
"YesCancel": "Так, скасувати",
"Yesterday": "Вчора",
"ImportFailedInterp": "Помилка імпорту: {0}",
"NoLogFiles": "Немає файлів журналу",
"RSSSync": "Синхронізація RSS",
"RSSSyncInterval": "Інтервал синхронізації RSS",
"DeleteTag": "Видалити тег",
"DownloadClientCheckDownloadingToRoot": "Клієнт завантаження {0} розміщує завантаження в кореневій папці {1}. Ви не повинні завантажувати в кореневу папку.",
"DownloadClientCheckNoneAvailableMessage": "Немає доступного клієнта для завантаження",
"DownloadClients": "Клієнти завантажувачів",
"Enable": "Увімкнути",
"EnableAutomaticSearch": "Увімкнути автоматичний пошук",
"EnableColorImpairedMode": "Увімкнути режим із порушенням кольору",
"EnableColorImpairedModeHelpText": "Змінений стиль, щоб користувачі з вадами кольору могли краще розрізняти кольорову кодовану інформацію",
"Interval": "Інтервал",
"ApplicationURL": "URL програми",
"ApplicationUrlHelpText": "Зовнішня URL-адреса цієї програми, включаючи http(s)://, порт і базу URL-адрес",
"DownloadClientsSettingsSummary": "Клієнти завантаження, обробка завантажень і віддалені відображення шляхів",
"DownloadClientStatusCheckAllClientMessage": "Усі клієнти завантаження недоступні через збої",
"DownloadFailedCheckDownloadClientForMoreDetails": "Помилка завантаження: перевірте клієнт завантаження, щоб дізнатися більше",
"DownloadFailedInterp": "Помилка завантаження: {0}",
"DownloadWarningCheckDownloadClientForMoreDetails": "Попередження про завантаження: перевірте клієнт завантаження, щоб дізнатися більше",
"Edition": "Видання",
"EnableCompletedDownloadHandlingHelpText": "Автоматично імпортувати завершені завантаження з клієнта завантажень",
"EnableHelpText": "Увімкнути створення файлу метаданих для цього типу метаданих",
"EnableSslHelpText": " Щоб набуло чинності, потрібно перезапустити роботу від імені адміністратора",
"ImportListStatusCheckAllClientMessage": "Усі списки недоступні через помилки",
"ImportMechanismHealthCheckMessage": "Увімкнути обробку завершених завантажень",
"IncludeHealthWarningsHelpText": "Включайте попередження про здоров’я",
"Path": "Шлях",
"RetryingDownloadInterp": "Повторна спроба завантажити {0} о {1}",
"UnableToLoadBackups": "Не вдалося завантажити резервні копії",
"UpdateScriptPathHelpText": "Шлях до спеціального сценарію, який приймає витягнутий пакет оновлення та обробляє решту процесу оновлення",
"Message": "Повідомлення",
"DownloadClientSettings": "Налаштування клієнта завантажувача",
"MIA": "MIA",
"MinimumAgeHelpText": "Тільки Usenet: мінімальний вік NZB у хвилинах до їх захоплення. Використовуйте це, щоб дати новим випускам час для поширення до вашого провайдера usenet.",
"MinimumFreeSpace": "Мінімальний вільний простір",
"MinimumFreeSpaceWhenImportingHelpText": "Заборонити імпорт, якщо він залишить менше, ніж цей обсяг доступного дискового простору",
"EnableInteractiveSearch": "Увімкнути інтерактивний пошук",
"ErrorLoadingContents": "Помилка завантаження вмісту",
"ExtraFileExtensionsHelpTexts1": "Розділений комами список додаткових файлів для імпорту (.nfo буде імпортовано як .nfo-orig)",
"FileWasDeletedByUpgrade": "Файл видалено, щоб імпортувати оновлення",
"FirstDayOfWeek": "Перший день тижня",
"IndexerSearchCheckNoAvailableIndexersMessage": "Усі індексатори з можливістю пошуку тимчасово недоступні через нещодавні помилки індексатора",
"None": "Жодного",
"GeneralSettings": "Загальні налаштування",
"NotificationTriggers": "Тригери сповіщень",
"GeneralSettingsSummary": "Порт, SSL, ім’я користувача/пароль, проксі, аналітика та оновлення",
"NotAvailable": "Недоступний",
"GoToInterp": "Перейти до {0}",
"NotMonitored": "Не контролюється",
"NoUpdatesAreAvailable": "Немає оновлень",
"HealthNoIssues": "Немає проблем із вашою конфігурацією",
"ICalHttpUrlHelpText": "Скопіюйте цю URL-адресу до своїх клієнтів або натисніть, щоб підписатися, якщо ваш браузер підтримує веб-канал",
"OnHealthIssue": "Про питання здоров'я",
"OnHealthIssueHelpText": "Про питання здоров'я",
"OnRename": "При перейменуванні",
"OnRenameHelpText": "При перейменуванні",
"OnUpgrade": "При оновленні",
"OpenBrowserOnStart": "Відкрийте браузер при запуску",
"OutputPath": "Вихідний шлях",
"Overview": "Огляд",
"PackageVersion": "Версія пакета",
"PageSize": "Розмір сторінки",
"PageSizeHelpText": "Кількість елементів для показу на кожній сторінці",
"Password": "Пароль",
"IndexerPriority": "Пріоритет індексатора",
"IndexerRssHealthCheckNoAvailableIndexers": "Усі індексатори з підтримкою rss тимчасово недоступні через нещодавні помилки індексатора",
"DiskSpace": "Дисковий простір",
"Permissions": "Дозволи",
"Port": "Порт",
"PortNumber": "Номер порту",
"IndexerStatusCheckSingleClientMessage": "Індексатори недоступні через помилки: {0}",
"PosterSize": "Розмір плаката",
"PreviewRename": "Попередній перегляд Перейменування",
"Profiles": "Профілі",
"Proxy": "Проксі",
"ProtocolHelpText": "Виберіть протокол(и) для використання та який із них є кращим під час вибору між однаковими випусками",
"ProxyPasswordHelpText": "Вам потрібно лише ввести ім’я користувача та пароль, якщо вони потрібні. В іншому випадку залиште їх порожніми.",
"ProxyType": "Тип проксі",
"ProxyUsernameHelpText": "Вам потрібно лише ввести ім’я користувача та пароль, якщо вони потрібні. В іншому випадку залиште їх порожніми.",
"PublishedDate": "Дата публікації",
"Quality": "Якість",
"QualityDefinitions": "Визначення якості",
"QualityProfile": "Профіль якості",
"RemotePathMappingCheckLocalFolderMissing": "Клієнт віддаленого завантаження {0} розміщує завантаження в {1}, але цей каталог не існує. Ймовірно, віддалений шлях відсутній або неправильний.",
"RemotePathMappingCheckLocalWrongOSPath": "Локальний клієнт завантаження {0} розміщує завантаження в {1}, але це недійсний шлях {2}. Перегляньте налаштування клієнта завантаження.",
"RemoveSelected": "Видалити вибране",
"RemotePathMappingCheckWrongOSPath": "Клієнт віддаленого завантаження {0} розміщує завантаження в {1}, але це недійсний шлях {2}. Перегляньте свої віддалені відображення шляхів і завантажте налаштування клієнта.",
"MaximumLimits": "Максимальні обмеження",
"Reset": "Скинути",
"Restart": "Перезавантажити",
"MinimumLimits": "Мінімальні обмеження",
"MoreInfo": "Більше інформації",
"RootFolders": "Кореневі папки",
"SearchFiltered": "Пошук відфільтровано",
"NoBackupsAreAvailable": "Немає резервних копій",
"SourcePath": "Вихідний шлях",
"StartupDirectory": "Каталог запуску",
"Status": "Статус",
"SSLPort": "Порт SSL",
"Started": "Розпочато",
"StartTypingOrSelectAPathBelow": "Почніть вводити текст або виберіть шлях нижче",
"TagIsNotUsedAndCanBeDeleted": "Тег не використовується і може бути видалений",
"Tags": "Теги",
"SystemTimeCheckMessage": "Системний час вимкнено більш ніж на 1 день. Заплановані завдання можуть не працювати належним чином, доки час не буде виправлено",
"TestAll": "Перевірити все",
"Test": "Тест",
"TestAllIndexers": "Перевірити всі індексатори",
"ProxyBypassFilterHelpText": "Використовуйте «,» як роздільник і «*». як символ підстановки для субдоменів",
"ThisCannotBeCancelled": "Це не можна скасувати після запуску без вимкнення всіх ваших індексаторів.",
"Time": "Час",
"UnableToAddANewListPleaseTryAgain": "Не вдалося додати новий список, спробуйте ще раз.",
"UnableToAddANewQualityProfilePleaseTryAgain": "Не вдалося додати новий профіль якості, спробуйте ще раз.",
"UnableToAddANewRemotePathMappingPleaseTryAgain": "Не вдалося додати нове зіставлення віддаленого шляху, спробуйте ще раз.",
"UnableToAddANewNotificationPleaseTryAgain": "Не вдалося додати нове сповіщення, спробуйте ще раз.",
"UnableToLoadDownloadClients": "Не вдалося завантажити клієнти для завантаження",
"UnableToLoadGeneralSettings": "Не вдалося завантажити загальні налаштування",
"UnableToLoadIndexers": "Не вдалося завантажити індексатори",
"UnableToLoadMediaManagementSettings": "Не вдалося завантажити налаштування керування медіафайлами",
"UnableToLoadMetadata": "Не вдалося завантажити метадані",
"UnableToLoadNotifications": "Не вдалося завантажити сповіщення",
"UnableToLoadQualities": "Неможливо завантажити якості",
"UnableToLoadNamingSettings": "Не вдалося завантажити налаштування імен",
"UnableToLoadUISettings": "Не вдалося завантажити налаштування інтерфейсу користувача",
"Ungroup": "Розгрупувати",
"UnableToLoadRootFolders": "Не вдалося завантажити кореневі папки",
"UnableToLoadTags": "Не вдалося завантажити теги",
"UnableToLoadTheCalendar": "Неможливо завантажити календар",
"RemotePathMappingCheckFilesLocalWrongOSPath": "Локальний клієнт завантаження {0} повідомив про файли в {1}, але це недійсний шлях {2}. Перегляньте налаштування клієнта завантаження.",
"RemotePathMappingCheckFilesWrongOSPath": "Клієнт віддаленого завантаження {0} повідомив про файли в {1}, але це недійсний шлях {2}. Перегляньте свої віддалені відображення шляхів і завантажте налаштування клієнта.",
"Unmonitored": "Неконтрольований",
"Year": "Рік",
"RemoveHelpTextWarning": "Видалення видалить завантаження та файл(и) із клієнта завантаження.",
"Search": "Пошук",
"SendAnonymousUsageData": "Надсилати анонімні дані про використання",
"SetPermissionsLinuxHelpText": "Чи слід запускати chmod, коли файли імпортуються/перейменовуються?",
"ShowMonitoredHelpText": "Показати відстежуваний статус під плакатом",
"ShowReleaseDate": "Показати дату випуску",
"SkipFreeSpaceCheck": "Пропустити перевірку вільного місця",
"Tomorrow": "Завтра",
"UILanguageHelpTextWarning": "Потрібно перезавантажити браузер",
"UISettings": "Налаштування інтерфейсу користувача",
"UISettingsSummary": "Параметри календаря, дати та кольору",
"UnableToAddANewIndexerPleaseTryAgain": "Не вдалося додати новий індексатор, спробуйте ще раз.",
"UnableToLoadIndexerOptions": "Не вдалося завантажити параметри індексатора",
"UnableToLoadRemotePathMappings": "Неможливо завантажити віддалені відображення шляхів",
"UpdateAutomaticallyHelpText": "Автоматичне завантаження та встановлення оновлень. Ви все ще зможете встановити з System: Updates",
"UpdateCheckStartupTranslocationMessage": "Неможливо встановити оновлення, оскільки папка запуску \"{0}\" знаходиться в папці переміщення програми.",
"Version": "Версія",
"Original": "Оригінал",
"Options": "Опції",
"RefreshInformationAndScanDisk": "Оновити інформацію та сканувати диск",
"ManualImport": "Імпорт вручну",
"ProxyCheckBadRequestMessage": "Не вдалося перевірити проксі. Код стану: {0}",
"Reason": "Причина",
"RemoveFromQueue": "Видалити з черги",
"Restore": "Відновлення",
"SelectAll": "Вибрати все",
"TorrentDelayHelpText": "Затримка в хвилинах, щоб зачекати, перш ніж захопити торрент",
"Fixed": "Виправлено",
"DownloadClient": "Клієнт завантажувача",
"DownloadClientCheckUnableToCommunicateMessage": "Неможливо зв'язатися з {0}.",
"DownloadClientStatusCheckSingleClientMessage": "Завантаження клієнтів недоступне через помилки: {0}",
"Edit": "Редагувати",
"EnableAutomaticAdd": "Увімкнути автоматичне додавання",
"DestinationPath": "Шлях призначення",
"DetailedProgressBar": "Детальний індикатор прогресу",
"DetailedProgressBarHelpText": "Показати текст на панелі виконання",
"Disabled": "Вимкнено",
"Duration": "Тривалість",
"EnableRSS": "Увімкнути RSS",
"EnableSSL": "Увімкнути SSL",
"Ended": "Завершено",
"ErrorLoadingPreviews": "Помилка завантаження попереднього перегляду",
"Exception": "Виняток",
"FailedDownloadHandling": "Помилка обробки завантаження",
"FailedToLoadQueue": "Не вдалося завантажити чергу",
"FileDateHelpText": "Змінити дату файлу під час імпорту/повторного сканування",
"FileManagement": "Керування файлами",
"Filename": "Ім'я файлу",
"FileNames": "Імена файлів",
"Files": "Файли",
"FileWasDeletedByViaUI": "Файл видалено через інтерфейс користувача",
"Folder": "Папка",
"Folders": "Папки",
"General": "Загальний",
"Global": "Глобальний",
"IllRestartLater": "Я перезапущу пізніше",
"ImportedTo": "Імпортовано в",
"ImportExtraFiles": "Імпорт додаткових файлів",
"Filters": "Фільтри",
"Importing": "Імпорт",
"Indexers": "Індексатори",
"ImportListStatusCheckSingleClientMessage": "Списки недоступні через помилки: {0}",
"Indexer": "Індексатор",
"IndexerLongTermStatusCheckAllClientMessage": "Усі індексатори недоступні через збої більше 6 годин",
"IndexerLongTermStatusCheckSingleClientMessage": "Індексатори недоступні через збої більше 6 годин: {0}",
"IndexerJackettAll": "Індексатори, які використовують непідтримувану кінцеву точку Jackett 'all': {0}",
"IndexerSettings": "Налаштування індексатора",
"IndexersSettingsSummary": "Індексатори та обмеження випуску",
"IndexerStatusCheckAllClientMessage": "Усі індексатори недоступні через збої",
"InstanceName": "Ім'я екземпляра",
"InstanceNameHelpText": "Ім’я екземпляра на вкладці та ім’я програми Syslog",
"Language": "Мова",
"Level": "Рівень",
"Lists": "Списки",
"MaximumSize": "Максимальний розмір",
"Mechanism": "Механізм",
"MediaInfo": "Медіа інформація",
"MinimumAge": "Мінімальний вік",
"Mode": "Режим",
"MustContain": "Має містити",
"MustNotContain": "Не повинен містити",
"Name": "Ім'я",
"NamingSettings": "Налаштування імен",
"New": "Новий",
"NoLeaveIt": "Ні, залиште це",
"NoLimitForAnyRuntime": "Немає обмежень для будь-якого часу виконання",
"NoMinimumForAnyRuntime": "Немає мінімуму для будь-якого часу виконання",
"OnUpgradeHelpText": "При оновленні",
"Progress": "Прогрес",
"Proper": "Належний",
"Protocol": "Протокол",
"ProxyCheckFailedToTestMessage": "Не вдалося перевірити проксі: {0}",
"ProxyCheckResolveIpMessage": "Не вдалося визначити IP-адресу для налаштованого проксі-сервера {0}",
"RemotePathMappingCheckRemoteDownloadClient": "Клієнт віддаленого завантаження {0} повідомив про файли в {1}, але цей каталог, здається, не існує. Ймовірно, відсутнє відображення віддаленого шляху.",
"RemoveFromDownloadClient": "Видалити з клієнта завантаження",
"Reorder": "Змінити порядок",
"ReplaceIllegalCharacters": "Замінити неприпустимі символи",
"ResetAPIKey": "Скинути ключ API",
"RestartNow": "Перезавантажити зараз",
"RestartRequiredHelpTextWarning": "Щоб набуло чинності, потрібно перезапустити",
"Save": "Зберегти",
"Scheduled": "За розкладом",
"Score": "Оцінка",
"ScriptPath": "Шлях сценарію",
"SearchAll": "Пошук у всіх",
"SearchForMissing": "Розшук зниклих",
"SearchSelected": "Пошук вибрано",
"Security": "Безпека",
"SetPermissions": "Встановити дозволи",
"SetPermissionsLinuxHelpTextWarning": "Якщо ви не впевнені, що ці налаштування роблять, не змінюйте їх.",
"Settings": "Налаштування",
"SettingsRemotePathMappingLocalPath": "Місцевий шлях",
"SettingsRemotePathMappingRemotePath": "Віддалений шлях",
"SettingsRemotePathMappingRemotePathHelpText": "Кореневий шлях до каталогу, до якого має доступ клієнт завантаження",
"ShowCutoffUnmetIconHelpText": "Показувати піктограму для файлів, коли обмеження не досягнуто",
"ShowMonitored": "Показати Моніторинг",
"ShowPath": "Показати шлях",
"ShowQualityProfileHelpText": "Покажіть якісний профіль під плакатом",
"ShowSearch": "Показати пошук",
"ShowSizeOnDisk": "Показати розмір на диску",
"ShowTitle": "Показати назву",
"SizeLimit": "Обмеження розміру",
"Source": "Джерело",
"Style": "Стиль",
"Tasks": "Задачі",
"TestAllClients": "Перевірте всіх клієнтів",
"TestAllLists": "Перевірити всі списки",
"TimeFormat": "Формат часу",
"Title": "Назва",
"Today": "Сьогодні",
"TotalFileSize": "Загальний розмір файлу",
"UILanguage": "Мова інтерфейсу користувача",
"UnableToAddANewDownloadClientPleaseTryAgain": "Не вдається додати новий клієнт для завантаження, повторіть спробу.",
"UnableToLoadDelayProfiles": "Неможливо завантажити профілі затримки",
"UnableToLoadDownloadClientOptions": "Не вдалося завантажити параметри клієнта для завантаження",
"UnableToLoadLists": "Не вдалося завантажити списки",
"UnableToLoadQualityDefinitions": "Не вдалося завантажити визначення якості",
"UnselectAll": "Скасувати вибір усіх",
"UpdateAvailable": "Доступне нове оновлення",
"UpdateCheckStartupNotWritableMessage": "Неможливо встановити оновлення, оскільки папка запуску \"{0}\" не може бути записана користувачем \"{1}\".",
"UpdateCheckUINotWritableMessage": "Неможливо встановити оновлення, оскільки папка інтерфейсу користувача \"{0}\" не може бути записана користувачем \"{1}\".",
"Updates": "Оновлення",
"Uptime": "Час роботи",
"URLBase": "URL-адреса",
"UseHardlinksInsteadOfCopy": "Використовуйте жорсткі посилання замість копіювати",
"UsenetDelay": "Затримка Usenet",
"UseProxy": "Використовуйте проксі",
"Username": "Ім'я користувача",
"Wanted": "Розшукується",
"WeekColumnHeader": "Заголовок стовпця тижня",
"Local": "Місцевий",
"LogFiles": "Файли журналів",
"LogLevel": "Рівень журналу",
"Logs": "Журнали",
"MarkAsFailed": "Позначити як помилку",
"MarkAsFailedMessageText": "Ви впевнені, що бажаєте позначити \"{0}\" як невдале?",
"QualityProfiles": "Профілі якості",
"QualitySettings": "Налаштування якості",
"QualitySettingsSummary": "Якісні розміри та найменування",
"Queue": "Черга",
"Queued": "У черзі",
"QueueIsEmpty": "Черга порожня",
"ReadTheWikiForMoreInformation": "Читайте Wiki для отримання додаткової інформації",
"Real": "Справжня",
"RecycleBinCleanupDaysHelpText": "Встановіть значення 0, щоб вимкнути автоматичне очищення",
"RecycleBinCleanupDaysHelpTextWarning": "Файли в кошику, старші за вибрану кількість днів, будуть очищені автоматично",
"RecyclingBin": "Сміттєвий кошик",
"RecyclingBinCleanup": "Очищення сміттєвого кошика",
"Redownload": "Повторне завантаження",
"Refresh": "Оновити",
"RefreshAndScan": "Оновити та сканувати",
"UsenetDelayHelpText": "Затримка в хвилинах, щоб зачекати, перш ніж отримати випуск від Usenet",
"DeleteImportListExclusion": "Видалити виключення зі списку імпорту",
"DeleteIndexer": "Видалити індексатор",
"DeleteNotification": "Видалити сповіщення",
"DeleteQualityProfile": "Видалити профіль якості",
"Docker": "Docker",
"Group": "Група",
"History": "Історія",
"Host": "Хост",
"Hostname": "Ім'я хоста",
"ICalFeed": "Канал iCal",
"IgnoredAddresses": "Ігноровані адреси",
"IgnoredPlaceHolder": "Додайте нове обмеження",
"Missing": "Відсутня",
"Preferred": "Бажано",
"RemotePathMappingCheckDockerFolderMissing": "Ви використовуєте docker; клієнт завантаження {0} розміщує завантаження в {1}, але цей каталог не існує всередині контейнера. Перегляньте свої віддалені відображення шляхів і налаштування обсягу контейнера.",
"RemotePathMappingCheckFileRemoved": "Файл {0} видалено під час обробки.",
"RemotePathMappingCheckFilesBadDockerPath": "Ви використовуєте docker; завантажити клієнтські {0} звітні файли в {1}, але це недійсний {2} шлях. Перегляньте свої віддалені відображення шляхів і завантажте налаштування клієнта.",
"ReleaseGroup": "Група випуску",
"ReleaseRejected": "Реліз відхилено",
"ReleaseTitle": "Назва випуску",
"ReleaseWillBeProcessedInterp": "Випуск буде оброблено {0}",
"Reload": "Перезавантажити",
"RemotePathMappingCheckBadDockerPath": "Ви використовуєте docker; клієнт завантаження {0} розміщує завантаження в {1}, але це недійсний шлях {2}. Перегляньте свої віддалені відображення шляхів і завантажте налаштування клієнта.",
"RenameFiles": "Перейменування файлів",
"RestoreBackup": "Відновлення резервної копії",
"Result": "Результат",
"Retention": "Утримання",
"RetentionHelpText": "Лише Usenet: встановіть нуль, щоб налаштувати необмежену утримку",
"RootFolder": "Коренева папка",
"RemotePathMappings": "Віддалені відображення шляхів",
"Remove": "Видалити",
"RemoveCompletedDownloadsHelpText": "Видалити імпортовані завантаження з історії клієнта завантажень",
"RemovedFromTaskQueue": "Видалено з черги завдань",
"RemoveFailedDownloadsHelpText": "Видаліть невдалі завантаження з історії завантажень клієнта",
"RemoveFilter": "Видалити фільтр",
"RemoveFromBlocklist": "Видалити зі списку блокувань",
"RootFolderCheckMultipleMessage": "Відсутні кілька кореневих папок: {0}",
"RootFolderCheckSingleMessage": "Відсутня коренева папка: {0}",
"TorrentDelay": "Затримка торрента",
"Torrents": "Торренти",
"UpgradeAllowedHelpText": "Якщо відключені якості не будуть покращені",
"MediaManagementSettings": "Налаштування Управління медіа",
"OnGrab": "При захопленні",
"OnGrabHelpText": "При захопленні",
"SSLCertPassword": "Пароль SSL сертифіката",
"SSLCertPath": "Шлях сертифіката SSL",
"IncludeUnmonitored": "Включити неконтрольований",
"MediaManagement": "Управління медіа",
"MetadataSettings": "Налаштування метаданих",
"Monitor": "Контрольований",
"Monitored": "Відстежується",
"UI": "Інтерфейс користувача",
"Usenet": "Usenet",
"Logging": "Журналування",
"MaintenanceRelease": "Випуск для обслуговування: виправлення помилок та інші покращення. Щоб отримати докладнішу інформацію, перегляньте історію фіксації Github",
"Metadata": "Метадані",
"GrabRelease": "Захопити реліз",
"GrabSelected": "Захопити вибране",
"IconForCutoffUnmet": "Значок \"Не виконано відсікання\"",
"Grab": "Захопити",
"GrabID": "Захопити ID"
}

View File

@@ -83,7 +83,7 @@
"DeleteImportListMessageText": "您确定要删除列表 '{0}'",
"DeleteIndexer": "删除索引",
"DeleteIndexerMessageText": "您确定要删除索引 '{0}'吗?",
"DeleteMetadataProfileMessageText": "确定要删除影片质量配置“{0}",
"DeleteMetadataProfileMessageText": "确定要删除元数据配置吗‘{0}",
"DeleteNotification": "删除消息推送",
"DeleteNotificationMessageText": "您确定要删除推送 '{0}' 吗?",
"DeleteQualityProfile": "删除电影质量配置",
@@ -433,7 +433,7 @@
"WeekColumnHeader": "日期格式",
"Year": "年",
"YesCancel": "是,取消",
"20MinutesTwenty": "20分钟: {0}",
"20MinutesTwenty": "20 分钟{0}",
"45MinutesFourtyFive": "45分钟: {0}",
"60MinutesSixty": "60分钟: {0}",
"APIKey": "API Key",
@@ -877,5 +877,7 @@
"CollapseMultipleBooksHelpText": "折叠在同日发行的多本书籍",
"ASIN": "亚马逊标准识别码",
"NoTagsHaveBeenAddedYet": "尚未添加标签,添加标签以链接具有延迟配置文件、限制或通知的作者。单击{0}以了解有关Readarr中标签的更多信息。",
"BindAddressHelpText": "有效的 IPv4 地址或以'*'代表所有接口"
"BindAddressHelpText": "有效的 IPv4 地址或以'*'代表所有接口",
"ApplicationURL": "程序URL",
"ApplicationUrlHelpText": "此应用的外部URL包含 http(s)://、端口和基本URL"
}

View File

@@ -125,7 +125,7 @@ namespace NzbDrone.Core.MetadataSource.BookInfo
catch (BookInfoException e)
{
_logger.Warn(e, "Unexpected error getting book info");
throw new AuthorNotFoundException(foreignBookId);
throw new BookNotFoundException(foreignBookId);
}
}
@@ -378,14 +378,29 @@ namespace NzbDrone.Core.MetadataSource.BookInfo
private Book GetEditionInfo(int id, bool getAllEditions)
{
var httpRequest = _requestBuilder.GetRequestBuilder().Create()
.SetSegment("route", $"book/{id}")
.Build();
HttpRequest httpRequest;
HttpResponse httpResponse;
httpRequest.SuppressHttpError = true;
while (true)
{
httpRequest = _requestBuilder.GetRequestBuilder().Create()
.SetSegment("route", $"book/{id}")
.Build();
// we expect a redirect
var httpResponse = _httpClient.Get(httpRequest);
httpRequest.SuppressHttpError = true;
// we expect a redirect
httpResponse = _httpClient.Get(httpRequest);
if (httpResponse.StatusCode == HttpStatusCode.TooManyRequests)
{
WaitUntilRetry(httpResponse);
}
else
{
break;
}
}
if (httpResponse.StatusCode == HttpStatusCode.NotFound)
{
@@ -398,9 +413,9 @@ namespace NzbDrone.Core.MetadataSource.BookInfo
}
var location = httpResponse.Headers.GetSingleValue("Location");
var split = location.Split('/');
var type = split[0];
var newId = split[1];
var split = location.Split('/').Reverse().ToList();
var newId = split[0];
var type = split[1];
Book book;
List<AuthorMetadata> authors;
@@ -437,6 +452,10 @@ namespace NzbDrone.Core.MetadataSource.BookInfo
trimmed.AuthorMetadata = book.AuthorMetadata.Value;
trimmed.SeriesLinks = book.SeriesLinks;
var edition = book.Editions.Value.SingleOrDefault(e => e.ForeignEditionId == id.ToString());
if (edition != null)
{
edition.Monitored = true;
}
trimmed.Editions = new List<Edition> { edition };
book = trimmed;
@@ -450,16 +469,32 @@ namespace NzbDrone.Core.MetadataSource.BookInfo
private List<Book> MapSearchResult(List<int> ids)
{
var httpRequest = _requestBuilder.GetRequestBuilder().Create()
.SetSegment("route", $"book/bulk")
.SetHeader("Content-Type", "application/json")
.Build();
HttpRequest httpRequest;
HttpResponse<BulkBookResource> httpResponse;
httpRequest.SetContent(ids.ToJson());
while (true)
{
httpRequest = _requestBuilder.GetRequestBuilder().Create()
.SetSegment("route", $"book/bulk")
.SetHeader("Content-Type", "application/json")
.Build();
httpRequest.AllowAutoRedirect = true;
httpRequest.SetContent(ids.ToJson());
var httpResponse = _httpClient.Post<BulkBookResource>(httpRequest);
httpRequest.AllowAutoRedirect = true;
httpRequest.SuppressHttpError = true;
httpResponse = _httpClient.Post<BulkBookResource>(httpRequest);
if (httpResponse.StatusCode == HttpStatusCode.TooManyRequests)
{
WaitUntilRetry(httpResponse);
}
else
{
break;
}
}
var mapped = MapBulkBook(httpResponse.Resource);
@@ -574,7 +609,12 @@ namespace NzbDrone.Core.MetadataSource.BookInfo
if (httpResponse.HasHttpError)
{
if (httpResponse.StatusCode == HttpStatusCode.NotFound)
if (httpResponse.StatusCode == HttpStatusCode.TooManyRequests)
{
WaitUntilRetry(httpResponse);
continue;
}
else if (httpResponse.StatusCode == HttpStatusCode.NotFound)
{
throw new AuthorNotFoundException(foreignAuthorId);
}
@@ -624,6 +664,12 @@ namespace NzbDrone.Core.MetadataSource.BookInfo
// this may redirect to an author
var httpResponse = _httpClient.Get(httpRequest);
if (httpResponse.StatusCode == HttpStatusCode.TooManyRequests)
{
WaitUntilRetry(httpResponse);
continue;
}
if (httpResponse.StatusCode == HttpStatusCode.NotFound)
{
throw new BookNotFoundException(foreignBookId);
@@ -632,9 +678,9 @@ namespace NzbDrone.Core.MetadataSource.BookInfo
if (httpResponse.HasHttpRedirect)
{
var location = httpResponse.Headers.GetSingleValue("Location");
var split = location.Split('/');
var type = split[0];
var newId = split[1];
var split = location.Split('/').Reverse().ToList();
var newId = split[0];
var type = split[1];
if (type == "author")
{
@@ -693,6 +739,25 @@ namespace NzbDrone.Core.MetadataSource.BookInfo
return Tuple.Create(authorId, book, metadata);
}
private void WaitUntilRetry(HttpResponse response)
{
var seconds = 5;
if (response.Headers.ContainsKey("Retry-After"))
{
var retryAfter = response.Headers["Retry-After"];
if (!int.TryParse(retryAfter, out seconds))
{
seconds = 5;
}
}
_logger.Info("BookInfo returned 429, backing off for {0}s", seconds);
Thread.Sleep(TimeSpan.FromSeconds(seconds));
}
private static AuthorMetadata MapAuthorMetadata(AuthorResource resource)
{
var metadata = new AuthorMetadata

View File

@@ -215,11 +215,20 @@ namespace NzbDrone.Host
private static IConfiguration GetConfiguration(StartupContext context)
{
var appFolder = new AppFolderInfo(context);
return new ConfigurationBuilder()
.AddXmlFile(appFolder.GetConfigPath(), optional: true, reloadOnChange: false)
.AddInMemoryCollection(new List<KeyValuePair<string, string>> { new ("dataProtectionFolder", appFolder.GetDataProtectionPath()) })
.AddEnvironmentVariables()
.Build();
var configPath = appFolder.GetConfigPath();
try
{
return new ConfigurationBuilder()
.AddXmlFile(configPath, optional: true, reloadOnChange: false)
.AddInMemoryCollection(new List<KeyValuePair<string, string>> { new ("dataProtectionFolder", appFolder.GetDataProtectionPath()) })
.AddEnvironmentVariables()
.Build();
}
catch (InvalidDataException ex)
{
throw new InvalidConfigFileException($"{configPath} is corrupt or invalid. Please delete the config file and Readarr will recreate it.", ex);
}
}
private static string BuildUrl(string scheme, string bindAddress, int port)

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Mvc;
@@ -22,6 +23,8 @@ namespace Readarr.Api.V1.Indexers
private readonly IIndexerFactory _indexerFactory;
private readonly Logger _logger;
private static readonly object PushLock = new object();
public ReleasePushController(IMakeDownloadDecision downloadDecisionMaker,
IProcessDownloadDecisions downloadDecisionProcessor,
IIndexerFactory indexerFactory,
@@ -51,8 +54,13 @@ namespace Readarr.Api.V1.Indexers
ResolveIndexer(info);
var decisions = _downloadDecisionMaker.GetRssDecision(new List<ReleaseInfo> { info });
_downloadDecisionProcessor.ProcessDecisions(decisions);
List<DownloadDecision> decisions;
lock (PushLock)
{
decisions = _downloadDecisionMaker.GetRssDecision(new List<ReleaseInfo> { info });
_downloadDecisionProcessor.ProcessDecisions(decisions);
}
var firstDecision = decisions.FirstOrDefault();

View File

@@ -74,7 +74,7 @@ namespace Readarr.Http.REST
}
var attributes = descriptor.MethodInfo.CustomAttributes;
if (attributes.Any(x => VALIDATE_ID_ATTRIBUTES.Contains(x.GetType())) && !skipValidate)
if (attributes.Any(x => VALIDATE_ID_ATTRIBUTES.Contains(x.AttributeType)) && !skipValidate)
{
if (context.ActionArguments.TryGetValue("id", out var idObj))
{