diff --git a/.gitignore b/.gitignore index ceb71fe1a..a5d6bb7c8 100644 --- a/.gitignore +++ b/.gitignore @@ -158,34 +158,12 @@ Thumbs.db /tools/Addins/* packages.config.md5sum - -# Common IntelliJ Platform excludes - -# User specific -**/.idea/**/workspace.xml -**/.idea/**/tasks.xml -**/.idea/shelf/* -**/.idea/dictionaries -**/.idea/.idea.Radarr.Posix -**/.idea/.idea.Radarr.Windows - -# Sensitive or high-churn files -**/.idea/**/dataSources/ -**/.idea/**/dataSources.ids -**/.idea/**/dataSources.xml -**/.idea/**/dataSources.local.xml -**/.idea/**/sqlDataSources.xml -**/.idea/**/dynamic.xml - -# Rider -# Rider auto-generates .iml files, and contentModel.xml -**/.idea/**/*.iml -**/.idea/**/contentModel.xml -**/.idea/**/modules.xml - # ignore node_modules symlink node_modules node_modules.nosync # API doc generation .config/ + +# Ignore Jetbrains IntelliJ Workspace Directories +.idea/ diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 280cb4dc4..ba0800fee 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,7 +9,7 @@ variables: testsFolder: './_tests' yarnCacheFolder: $(Pipeline.Workspace)/.yarn nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages - majorVersion: '2.12.1' + majorVersion: '2.13.1' minorVersion: $[counter('minorVersion', 1076)] lidarrVersion: '$(majorVersion).$(minorVersion)' buildName: '$(Build.SourceBranchName).$(lidarrVersion)' diff --git a/frontend/src/Settings/MediaManagement/RootFolder/RootFolder.js b/frontend/src/Settings/MediaManagement/RootFolder/RootFolder.js index 47e5dfcf1..dc91e4622 100644 --- a/frontend/src/Settings/MediaManagement/RootFolder/RootFolder.js +++ b/frontend/src/Settings/MediaManagement/RootFolder/RootFolder.js @@ -94,9 +94,9 @@ class RootFolder extends Component { diff --git a/frontend/src/Settings/Quality/Definition/QualityDefinition.css b/frontend/src/Settings/Quality/Definition/QualityDefinition.css index e090428a1..860333725 100644 --- a/frontend/src/Settings/Quality/Definition/QualityDefinition.css +++ b/frontend/src/Settings/Quality/Definition/QualityDefinition.css @@ -24,19 +24,19 @@ height: 20px; } -.bar { +.track { top: 9px; margin: 0 5px; height: 3px; background-color: var(--sliderAccentColor); box-shadow: 0 0 0 #000; - &:nth-child(3n+1) { + &:nth-child(3n + 1) { background-color: #ddd; } } -.handle { +.thumb { top: 1px; z-index: 0 !important; width: 18px; diff --git a/frontend/src/Settings/Quality/Definition/QualityDefinition.css.d.ts b/frontend/src/Settings/Quality/Definition/QualityDefinition.css.d.ts index 2b92fb212..9c9e8393a 100644 --- a/frontend/src/Settings/Quality/Definition/QualityDefinition.css.d.ts +++ b/frontend/src/Settings/Quality/Definition/QualityDefinition.css.d.ts @@ -1,8 +1,6 @@ // This file is automatically generated. // Please do not change this file! interface CssExports { - 'bar': string; - 'handle': string; 'kilobitsPerSecond': string; 'quality': string; 'qualityDefinition': string; @@ -10,7 +8,9 @@ interface CssExports { 'sizeLimit': string; 'sizes': string; 'slider': string; + 'thumb': string; 'title': string; + 'track': string; } export const cssExports: CssExports; export default cssExports; diff --git a/frontend/src/Settings/Quality/Definition/QualityDefinition.js b/frontend/src/Settings/Quality/Definition/QualityDefinition.js index 7d8a78737..48251abfb 100644 --- a/frontend/src/Settings/Quality/Definition/QualityDefinition.js +++ b/frontend/src/Settings/Quality/Definition/QualityDefinition.js @@ -55,6 +55,27 @@ class QualityDefinition extends Component { }; } + // + // Control + + trackRenderer(props, state) { + return ( +
+ ); + } + + thumbRenderer(props, state) { + return ( +
+ ); + } + // // Listeners @@ -174,6 +195,7 @@ class QualityDefinition extends Component {
diff --git a/frontend/src/Store/Actions/artistIndexActions.js b/frontend/src/Store/Actions/artistIndexActions.js index 055e92662..736502460 100644 --- a/frontend/src/Store/Actions/artistIndexActions.js +++ b/frontend/src/Store/Actions/artistIndexActions.js @@ -151,7 +151,7 @@ export const defaultState = { { name: 'genres', label: () => translate('Genres'), - isSortable: false, + isSortable: true, isVisible: false }, { diff --git a/frontend/src/System/Logs/Files/LogFiles.js b/frontend/src/System/Logs/Files/LogFiles.js index 83736c617..5339a8590 100644 --- a/frontend/src/System/Logs/Files/LogFiles.js +++ b/frontend/src/System/Logs/Files/LogFiles.js @@ -1,8 +1,8 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import Alert from 'Components/Alert'; -import Link from 'Components/Link/Link'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; +import InlineMarkdown from 'Components/Markdown/InlineMarkdown'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; import PageToolbar from 'Components/Page/Toolbar/PageToolbar'; @@ -77,15 +77,16 @@ class LogFiles extends Component {
- Log files are located in: {location} + {translate('LogFilesLocation', { + location + })}
- { - currentLogView === 'Log Files' && -
- The log level defaults to 'Info' and can be changed in General Settings -
- } + {currentLogView === 'Log Files' ? ( +
+ +
+ ) : null}
{ diff --git a/frontend/src/System/Updates/Updates.tsx b/frontend/src/System/Updates/Updates.tsx index ef3d20288..300ab1f99 100644 --- a/frontend/src/System/Updates/Updates.tsx +++ b/frontend/src/System/Updates/Updates.tsx @@ -270,7 +270,7 @@ function Updates() { {generalSettingsError ? ( - {translate('FailedToUpdateSettings')} + {translate('FailedToFetchSettings')} ) : null} diff --git a/src/Lidarr.Api.V1/RemotePathMappings/RemotePathMappingController.cs b/src/Lidarr.Api.V1/RemotePathMappings/RemotePathMappingController.cs index 33edddff3..fae5b2388 100644 --- a/src/Lidarr.Api.V1/RemotePathMappings/RemotePathMappingController.cs +++ b/src/Lidarr.Api.V1/RemotePathMappings/RemotePathMappingController.cs @@ -4,6 +4,7 @@ using Lidarr.Http; using Lidarr.Http.REST; using Lidarr.Http.REST.Attributes; using Microsoft.AspNetCore.Mvc; +using NzbDrone.Common.Extensions; using NzbDrone.Core.RemotePathMappings; using NzbDrone.Core.Validation.Paths; @@ -21,11 +22,19 @@ namespace Lidarr.Api.V1.RemotePathMappings _remotePathMappingService = remotePathMappingService; SharedValidator.RuleFor(c => c.Host) - .NotEmpty(); + .NotEmpty(); // We cannot use IsValidPath here, because it's a remote path, possibly other OS. SharedValidator.RuleFor(c => c.RemotePath) - .NotEmpty(); + .NotEmpty(); + + SharedValidator.RuleFor(c => c.RemotePath) + .Must(remotePath => remotePath.IsNotNullOrWhiteSpace() && !remotePath.StartsWith(" ")) + .WithMessage("Remote Path '{PropertyValue}' must not start with a space"); + + SharedValidator.RuleFor(c => c.RemotePath) + .Must(remotePath => remotePath.IsNotNullOrWhiteSpace() && !remotePath.EndsWith(" ")) + .WithMessage("Remote Path '{PropertyValue}' must not end with a space"); SharedValidator.RuleFor(c => c.LocalPath) .Cascade(CascadeMode.Stop) diff --git a/src/Lidarr.Api.V1/System/Backup/BackupController.cs b/src/Lidarr.Api.V1/System/Backup/BackupController.cs index 22f017d03..350ada72b 100644 --- a/src/Lidarr.Api.V1/System/Backup/BackupController.cs +++ b/src/Lidarr.Api.V1/System/Backup/BackupController.cs @@ -92,7 +92,7 @@ namespace Lidarr.Api.V1.System.Backup } [HttpPost("restore/upload")] - [RequestFormLimits(MultipartBodyLengthLimit = 1000000000)] + [RequestFormLimits(MultipartBodyLengthLimit = 5000000000)] public object UploadAndRestore() { var files = Request.Form.Files; diff --git a/src/NzbDrone.Automation.Test/AutomationTest.cs b/src/NzbDrone.Automation.Test/AutomationTest.cs index bcf777431..51c79539e 100644 --- a/src/NzbDrone.Automation.Test/AutomationTest.cs +++ b/src/NzbDrone.Automation.Test/AutomationTest.cs @@ -40,15 +40,16 @@ namespace NzbDrone.Automation.Test var service = ChromeDriverService.CreateDefaultService(); // Timeout as windows automation tests seem to take alot longer to get going - driver = new ChromeDriver(service, options, new TimeSpan(0, 3, 0)); + driver = new ChromeDriver(service, options, TimeSpan.FromMinutes(3)); driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080); + driver.Manage().Window.FullScreen(); _runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null); _runner.KillAll(); _runner.Start(true); - driver.Url = "http://localhost:8686"; + driver.Navigate().GoToUrl("http://localhost:8686"); var page = new PageBase(driver); page.WaitForNoSpinner(); @@ -68,7 +69,7 @@ namespace NzbDrone.Automation.Test { try { - var image = ((ITakesScreenshot)driver).GetScreenshot(); + var image = (driver as ITakesScreenshot).GetScreenshot(); image.SaveAsFile($"./{name}_test_screenshot.png", ScreenshotImageFormat.Png); } catch (Exception ex) diff --git a/src/NzbDrone.Automation.Test/PageModel/PageBase.cs b/src/NzbDrone.Automation.Test/PageModel/PageBase.cs index c9a7e8891..664ec7258 100644 --- a/src/NzbDrone.Automation.Test/PageModel/PageBase.cs +++ b/src/NzbDrone.Automation.Test/PageModel/PageBase.cs @@ -1,19 +1,17 @@ using System; using System.Threading; using OpenQA.Selenium; -using OpenQA.Selenium.Remote; using OpenQA.Selenium.Support.UI; namespace NzbDrone.Automation.Test.PageModel { public class PageBase { - private readonly RemoteWebDriver _driver; + private readonly IWebDriver _driver; - public PageBase(RemoteWebDriver driver) + public PageBase(IWebDriver driver) { _driver = driver; - driver.Manage().Window.Maximize(); } public IWebElement FindByClass(string className, int timeout = 5) diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs index 8ca01f6ec..9d896d15c 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs @@ -141,7 +141,7 @@ namespace NzbDrone.Common.Http.Dispatchers } catch (OperationCanceledException ex) when (cts.IsCancellationRequested) { - throw new WebException("Http request timed out", ex.InnerException, WebExceptionStatus.Timeout, null); + throw new WebException("Http request timed out", ex, WebExceptionStatus.Timeout, null); } } diff --git a/src/NzbDrone.Common/Lidarr.Common.csproj b/src/NzbDrone.Common/Lidarr.Common.csproj index 6e16836fc..2e5bacde4 100644 --- a/src/NzbDrone.Common/Lidarr.Common.csproj +++ b/src/NzbDrone.Common/Lidarr.Common.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs b/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs index 948ab3a54..dd501374c 100644 --- a/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs +++ b/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs @@ -103,6 +103,7 @@ namespace NzbDrone.Core.Test.DiskSpace [TestCase("/var/lib/docker")] [TestCase("/some/place/docker/aufs")] [TestCase("/etc/network")] + [TestCase("/Volumes/.timemachine/ABC123456-A1BC-12A3B45678C9/2025-05-13-181401.backup")] public void should_not_check_diskspace_for_irrelevant_mounts(string path) { var mount = new Mock(); diff --git a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs index 933eb1009..acac75e97 100644 --- a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs +++ b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs @@ -14,6 +14,7 @@ using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.MetadataSource.SkyHook { [TestFixture] + [Ignore("Waiting for metadata to be back again", Until = "2025-07-01 00:00:00Z")] public class SkyHookProxyFixture : CoreTest { private MetadataProfile _metadataProfile; diff --git a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs index ebe3a88a5..2e44d67a1 100644 --- a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs +++ b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs @@ -12,6 +12,7 @@ using NzbDrone.Test.Common; namespace NzbDrone.Core.Test.MetadataSource.SkyHook { [TestFixture] + [Ignore("Waiting for metadata to be back again", Until = "2025-07-01 00:00:00Z")] public class SkyHookProxySearchFixture : CoreTest { [SetUp] diff --git a/src/NzbDrone.Core/CustomFormats/Specifications/SizeSpecification.cs b/src/NzbDrone.Core/CustomFormats/Specifications/SizeSpecification.cs index fe873f9ec..9e2fe766e 100644 --- a/src/NzbDrone.Core/CustomFormats/Specifications/SizeSpecification.cs +++ b/src/NzbDrone.Core/CustomFormats/Specifications/SizeSpecification.cs @@ -11,6 +11,7 @@ namespace NzbDrone.Core.CustomFormats { RuleFor(c => c.Min).GreaterThanOrEqualTo(0); RuleFor(c => c.Max).GreaterThan(c => c.Min); + RuleFor(c => c.Max).LessThanOrEqualTo(double.MaxValue); } } diff --git a/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs b/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs index 4f685c560..cb8bd50f0 100644 --- a/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs +++ b/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs @@ -21,7 +21,7 @@ namespace NzbDrone.Core.DiskSpace private readonly IRootFolderService _rootFolderService; private readonly Logger _logger; - private static readonly Regex _regexSpecialDrive = new Regex("^/var/lib/(docker|rancher|kubelet)(/|$)|^/(boot|etc)(/|$)|/docker(/var)?/aufs(/|$)", RegexOptions.Compiled); + private static readonly Regex _regexSpecialDrive = new Regex(@"^/var/lib/(docker|rancher|kubelet)(/|$)|^/(boot|etc)(/|$)|/docker(/var)?/aufs(/|$)|/\.timemachine", RegexOptions.Compiled); public DiskSpaceService(IDiskProvider diskProvider, IRootFolderService rootFolderService, @@ -38,7 +38,10 @@ namespace NzbDrone.Core.DiskSpace var optionalRootFolders = GetFixedDisksRootPaths().Except(importantRootFolders).Distinct().ToList(); - var diskSpace = GetDiskSpace(importantRootFolders).Concat(GetDiskSpace(optionalRootFolders, true)).ToList(); + var diskSpace = GetDiskSpace(importantRootFolders) + .Concat(GetDiskSpace(optionalRootFolders, true)) + .OrderBy(d => d.Path, StringComparer.OrdinalIgnoreCase) + .ToList(); return diskSpace; } @@ -54,7 +57,7 @@ namespace NzbDrone.Core.DiskSpace private IEnumerable GetFixedDisksRootPaths() { return _diskProvider.GetMounts() - .Where(d => d.DriveType == DriveType.Fixed) + .Where(d => d.DriveType is DriveType.Fixed or DriveType.Network) .Where(d => !_regexSpecialDrive.IsMatch(d.RootDirectory)) .Select(d => d.RootDirectory); } diff --git a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs index 0e13b49af..2a045f788 100644 --- a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs +++ b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs @@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -28,9 +29,10 @@ namespace NzbDrone.Core.Download.Clients.Aria2 IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, + ILocalizationService localizationService, IBlocklistService blocklistService, Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxy = proxy; } diff --git a/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs b/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs index 27e2560ec..a9f8c445f 100644 --- a/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs +++ b/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs @@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; @@ -30,9 +31,10 @@ namespace NzbDrone.Core.Download.Clients.Blackhole IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, + ILocalizationService localizationService, IBlocklistService blocklistService, Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _scanWatchFolder = scanWatchFolder; diff --git a/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs b/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs index 279ef6fcd..71f7fc828 100644 --- a/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs +++ b/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs @@ -7,6 +7,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -25,8 +26,9 @@ namespace NzbDrone.Core.Download.Clients.Blackhole IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, IValidateNzbs nzbValidationService, + ILocalizationService localizationService, Logger logger) - : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger) + : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, localizationService, logger) { _scanWatchFolder = scanWatchFolder; diff --git a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs index 5ad0f2387..e9ad75d37 100644 --- a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs +++ b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs @@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -27,9 +28,10 @@ namespace NzbDrone.Core.Download.Clients.Deluge IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, + ILocalizationService localizationService, IBlocklistService blocklistService, Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxy = proxy; } diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs index 1c31bbd9a..0774d5d6a 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs @@ -11,6 +11,7 @@ using NzbDrone.Common.Http; using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.DownloadStation.Proxies; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -37,9 +38,10 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, + ILocalizationService localizationService, IBlocklistService blocklistService, Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _dsInfoProxy = dsInfoProxy; _dsTaskProxySelector = dsTaskProxySelector; diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs index 1add52b29..fc14629f8 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs @@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.DownloadStation.Proxies; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; using NzbDrone.Core.ThingiProvider; @@ -34,8 +35,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, IValidateNzbs nzbValidationService, + ILocalizationService localizationService, Logger logger) - : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger) + : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, localizationService, logger) { _dsInfoProxy = dsInfoProxy; _dsTaskProxySelector = dsTaskProxySelector; diff --git a/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs b/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs index f8cfeeed0..0c8802859 100644 --- a/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs +++ b/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs @@ -9,6 +9,7 @@ using NzbDrone.Common.Http; using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.Flood.Models; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -28,9 +29,10 @@ namespace NzbDrone.Core.Download.Clients.Flood IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, + ILocalizationService localizationService, IBlocklistService blocklistService, Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxy = proxy; _downloadSeedConfigProvider = downloadSeedConfigProvider; diff --git a/src/NzbDrone.Core/Download/Clients/FreeboxDownload/TorrentFreeboxDownload.cs b/src/NzbDrone.Core/Download/Clients/FreeboxDownload/TorrentFreeboxDownload.cs index b83615c18..34afe472f 100644 --- a/src/NzbDrone.Core/Download/Clients/FreeboxDownload/TorrentFreeboxDownload.cs +++ b/src/NzbDrone.Core/Download/Clients/FreeboxDownload/TorrentFreeboxDownload.cs @@ -9,6 +9,7 @@ using NzbDrone.Common.Http; using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -25,9 +26,10 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, + ILocalizationService localizationService, IBlocklistService blocklistService, Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxy = proxy; } diff --git a/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs b/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs index d97645b3c..12336c986 100644 --- a/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs +++ b/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs @@ -8,6 +8,7 @@ using NzbDrone.Common.Http; using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.Hadouken.Models; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -25,9 +26,10 @@ namespace NzbDrone.Core.Download.Clients.Hadouken IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, + ILocalizationService localizationService, IBlocklistService blocklistService, Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxy = proxy; } diff --git a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs index dba7b3ffb..c71e6977f 100644 --- a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs +++ b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs @@ -8,6 +8,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; using NzbDrone.Core.Validation; @@ -24,8 +25,9 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, IValidateNzbs nzbValidationService, + ILocalizationService localizationService, Logger logger) - : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger) + : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, localizationService, logger) { _proxy = proxy; } diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs index df3e86411..29ee3718e 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs @@ -10,6 +10,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Exceptions; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; using NzbDrone.Core.Validation; @@ -28,8 +29,9 @@ namespace NzbDrone.Core.Download.Clients.Nzbget IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, IValidateNzbs nzbValidationService, + ILocalizationService localizationService, Logger logger) - : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger) + : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, localizationService, logger) { _proxy = proxy; } diff --git a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs index becc142d2..28866080e 100644 --- a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs +++ b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs @@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Localization; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -23,8 +24,9 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, + ILocalizationService localizationService, Logger logger) - : base(configService, diskProvider, remotePathMappingService, logger) + : base(configService, diskProvider, remotePathMappingService, localizationService, logger) { _httpClient = httpClient; } diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs index 75587513a..9ecf3d471 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs @@ -10,6 +10,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -35,9 +36,10 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, ICacheManager cacheManager, + ILocalizationService localizationService, IBlocklistService blocklistService, Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxySelector = proxySelector; diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs index 05d565718..7f36ae891 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs @@ -10,6 +10,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Exceptions; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; using NzbDrone.Core.Validation; @@ -26,8 +27,9 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, IValidateNzbs nzbValidationService, + ILocalizationService localizationService, Logger logger) - : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger) + : base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, localizationService, logger) { _proxy = proxy; } diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs b/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs index 88fdb0f41..d3963e571 100644 --- a/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs +++ b/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs @@ -8,6 +8,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.RemotePathMappings; @@ -24,9 +25,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, + ILocalizationService localizationService, IBlocklistService blocklistService, Logger logger) - : base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger) + : base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { } diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs index 2dc9dd14e..e797ae48a 100644 --- a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs +++ b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs @@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -28,9 +29,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, + ILocalizationService localizationService, IBlocklistService blocklistService, Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxy = proxy; } @@ -101,7 +103,11 @@ namespace NzbDrone.Core.Download.Clients.Transmission if (!torrent.ErrorString.IsNullOrWhiteSpace()) { item.Status = DownloadItemStatus.Warning; - item.Message = torrent.ErrorString; + item.Message = _localizationService.GetLocalizedString("DownloadClientItemErrorMessage", new Dictionary + { + { "clientName", Name }, + { "message", torrent.ErrorString } + }); } else if (torrent.TotalSize == 0) { diff --git a/src/NzbDrone.Core/Download/Clients/Vuze/Vuze.cs b/src/NzbDrone.Core/Download/Clients/Vuze/Vuze.cs index 9f5897495..c10d5d3ba 100644 --- a/src/NzbDrone.Core/Download/Clients/Vuze/Vuze.cs +++ b/src/NzbDrone.Core/Download/Clients/Vuze/Vuze.cs @@ -5,6 +5,7 @@ using NzbDrone.Common.Http; using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.Transmission; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.RemotePathMappings; @@ -23,9 +24,10 @@ namespace NzbDrone.Core.Download.Clients.Vuze IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, + ILocalizationService localizationService, IBlocklistService blocklistService, Logger logger) - : base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger) + : base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { } diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs index c68e1c15d..ff89db95c 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs @@ -12,6 +12,7 @@ using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.rTorrent; using NzbDrone.Core.Exceptions; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -35,9 +36,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent IRemotePathMappingService remotePathMappingService, IDownloadSeedConfigProvider downloadSeedConfigProvider, IRTorrentDirectoryValidator rTorrentDirectoryValidator, + ILocalizationService localizationService, IBlocklistService blocklistService, Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxy = proxy; _rTorrentDirectoryValidator = rTorrentDirectoryValidator; diff --git a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs index 72c7ec827..c44b908bd 100644 --- a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs @@ -10,6 +10,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -29,9 +30,10 @@ namespace NzbDrone.Core.Download.Clients.UTorrent IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, + ILocalizationService localizationService, IBlocklistService blocklistService, Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger) { _proxy = proxy; diff --git a/src/NzbDrone.Core/Download/DownloadClientBase.cs b/src/NzbDrone.Core/Download/DownloadClientBase.cs index 63ccf629e..69f0a025e 100644 --- a/src/NzbDrone.Core/Download/DownloadClientBase.cs +++ b/src/NzbDrone.Core/Download/DownloadClientBase.cs @@ -8,6 +8,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Localization; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; using NzbDrone.Core.ThingiProvider; @@ -23,6 +24,7 @@ namespace NzbDrone.Core.Download protected readonly IConfigService _configService; protected readonly IDiskProvider _diskProvider; protected readonly IRemotePathMappingService _remotePathMappingService; + protected readonly ILocalizationService _localizationService; protected readonly Logger _logger; protected ResiliencePipeline RetryStrategy => new ResiliencePipelineBuilder() @@ -77,11 +79,13 @@ namespace NzbDrone.Core.Download protected DownloadClientBase(IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, + ILocalizationService localizationService, Logger logger) { _configService = configService; _diskProvider = diskProvider; _remotePathMappingService = remotePathMappingService; + _localizationService = localizationService; _logger = logger; } diff --git a/src/NzbDrone.Core/Download/NzbValidationService.cs b/src/NzbDrone.Core/Download/NzbValidationService.cs index e3cbff710..ee5eae100 100644 --- a/src/NzbDrone.Core/Download/NzbValidationService.cs +++ b/src/NzbDrone.Core/Download/NzbValidationService.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.Linq; using System.Xml; @@ -15,39 +16,53 @@ namespace NzbDrone.Core.Download { public void Validate(string filename, byte[] fileContent) { - var reader = new StreamReader(new MemoryStream(fileContent)); - - using (var xmlTextReader = XmlReader.Create(reader, new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, IgnoreComments = true })) + try { - var xDoc = XDocument.Load(xmlTextReader); - var nzb = xDoc.Root; + var reader = new StreamReader(new MemoryStream(fileContent)); - if (nzb == null) + using (var xmlTextReader = XmlReader.Create(reader, + new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, IgnoreComments = true })) { - throw new InvalidNzbException("Invalid NZB: No Root element [{0}]", filename); - } + var xDoc = XDocument.Load(xmlTextReader); + var nzb = xDoc.Root; - // nZEDb has an bug in their error reporting code spitting out invalid http status codes - if (nzb.Name.LocalName.Equals("error") && - nzb.TryGetAttributeValue("code", out var code) && - nzb.TryGetAttributeValue("description", out var description)) - { - throw new InvalidNzbException("Invalid NZB: Contains indexer error: {0} - {1}", code, description); - } + if (nzb == null) + { + throw new InvalidNzbException("Invalid NZB: No Root element [{0}]", filename); + } - if (!nzb.Name.LocalName.Equals("nzb")) - { - throw new InvalidNzbException("Invalid NZB: Unexpected root element. Expected 'nzb' found '{0}' [{1}]", nzb.Name.LocalName, filename); - } + // nZEDb has an bug in their error reporting code spitting out invalid http status codes + if (nzb.Name.LocalName.Equals("error") && + nzb.TryGetAttributeValue("code", out var code) && + nzb.TryGetAttributeValue("description", out var description)) + { + throw new InvalidNzbException("Invalid NZB: Contains indexer error: {0} - {1}", code, description); + } - var ns = nzb.Name.Namespace; - var files = nzb.Elements(ns + "file").ToList(); + if (!nzb.Name.LocalName.Equals("nzb")) + { + throw new InvalidNzbException( + "Invalid NZB: Unexpected root element. Expected 'nzb' found '{0}' [{1}]", nzb.Name.LocalName, filename); + } - if (files.Empty()) - { - throw new InvalidNzbException("Invalid NZB: No files [{0}]", filename); + var ns = nzb.Name.Namespace; + var files = nzb.Elements(ns + "file").ToList(); + + if (files.Empty()) + { + throw new InvalidNzbException("Invalid NZB: No files [{0}]", filename); + } } } + catch (InvalidNzbException) + { + // Throw the original exception + throw; + } + catch (Exception ex) + { + throw new InvalidNzbException("Invalid NZB: Unable to parse [{0}]", ex, filename); + } } } } diff --git a/src/NzbDrone.Core/Download/TorrentClientBase.cs b/src/NzbDrone.Core/Download/TorrentClientBase.cs index 4e3ec11ab..cdee0e799 100644 --- a/src/NzbDrone.Core/Download/TorrentClientBase.cs +++ b/src/NzbDrone.Core/Download/TorrentClientBase.cs @@ -10,6 +10,7 @@ using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Configuration; using NzbDrone.Core.Exceptions; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Localization; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; @@ -30,9 +31,10 @@ namespace NzbDrone.Core.Download IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, + ILocalizationService localizationService, IBlocklistService blocklistService, Logger logger) - : base(configService, diskProvider, remotePathMappingService, logger) + : base(configService, diskProvider, remotePathMappingService, localizationService, logger) { _httpClient = httpClient; _blocklistService = blocklistService; @@ -170,7 +172,7 @@ namespace NzbDrone.Core.Download } catch (HttpException ex) { - if (ex.Response.StatusCode == HttpStatusCode.NotFound) + if (ex.Response.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.Gone) { _logger.Error(ex, "Downloading torrent file for album '{0}' failed since it no longer exists ({1})", remoteAlbum.Release.Title, torrentUrl); throw new ReleaseUnavailableException(remoteAlbum.Release, "Downloading torrent failed", ex); diff --git a/src/NzbDrone.Core/Download/UsenetClientBase.cs b/src/NzbDrone.Core/Download/UsenetClientBase.cs index e14ac5a87..d92363abf 100644 --- a/src/NzbDrone.Core/Download/UsenetClientBase.cs +++ b/src/NzbDrone.Core/Download/UsenetClientBase.cs @@ -6,6 +6,7 @@ using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Exceptions; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Localization; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; @@ -24,8 +25,9 @@ namespace NzbDrone.Core.Download IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, IValidateNzbs nzbValidationService, + ILocalizationService localizationService, Logger logger) - : base(configService, diskProvider, remotePathMappingService, logger) + : base(configService, diskProvider, remotePathMappingService, localizationService, logger) { _httpClient = httpClient; _nzbValidationService = nzbValidationService; @@ -46,6 +48,7 @@ namespace NzbDrone.Core.Download { var request = indexer?.GetDownloadRequest(url) ?? new HttpRequest(url); request.RateLimitKey = remoteAlbum?.Release?.IndexerId.ToString(); + request.AllowAutoRedirect = true; var response = await RetryStrategy .ExecuteAsync(static async (state, _) => await state._httpClient.GetAsync(state.request), (_httpClient, request)) @@ -57,7 +60,7 @@ namespace NzbDrone.Core.Download } catch (HttpException ex) { - if (ex.Response.StatusCode == HttpStatusCode.NotFound) + if (ex.Response.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.Gone) { _logger.Error(ex, "Downloading nzb file for album '{0}' failed since it no longer exists ({1})", remoteAlbum.Release.Title, url); throw new ReleaseUnavailableException(remoteAlbum.Release, "Downloading nzb failed", ex); diff --git a/src/NzbDrone.Core/History/EntityHistoryService.cs b/src/NzbDrone.Core/History/EntityHistoryService.cs index d88358ddb..4b342995a 100644 --- a/src/NzbDrone.Core/History/EntityHistoryService.cs +++ b/src/NzbDrone.Core/History/EntityHistoryService.cs @@ -157,7 +157,7 @@ namespace NzbDrone.Core.History history.Data.Add("Age", message.Album.Release.Age.ToString()); history.Data.Add("AgeHours", message.Album.Release.AgeHours.ToString()); history.Data.Add("AgeMinutes", message.Album.Release.AgeMinutes.ToString()); - history.Data.Add("PublishedDate", message.Album.Release.PublishDate.ToString("s") + "Z"); + history.Data.Add("PublishedDate", message.Album.Release.PublishDate.ToUniversalTime().ToString("s") + "Z"); history.Data.Add("DownloadClient", message.DownloadClient); history.Data.Add("Size", message.Album.Release.Size.ToString()); history.Data.Add("DownloadUrl", message.Album.Release.DownloadUrl); diff --git a/src/NzbDrone.Core/Indexers/FileList/FileListRequestGenerator.cs b/src/NzbDrone.Core/Indexers/FileList/FileListRequestGenerator.cs index 37c421456..653a94f9c 100644 --- a/src/NzbDrone.Core/Indexers/FileList/FileListRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/FileList/FileListRequestGenerator.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.IndexerSearch.Definitions; @@ -44,6 +45,11 @@ namespace NzbDrone.Core.Indexers.FileList private IEnumerable GetRequest(string searchType, IEnumerable categories, string parameters) { + if (categories.Empty()) + { + yield break; + } + var categoriesQuery = string.Join(",", categories.Distinct()); var baseUrl = string.Format("{0}/api.php?action={1}&category={2}{3}", Settings.BaseUrl.TrimEnd('/'), searchType, categoriesQuery, parameters); diff --git a/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs b/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs index 2f587e6ef..398cebd38 100644 --- a/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs +++ b/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs @@ -13,6 +13,8 @@ namespace NzbDrone.Core.Indexers.FileList RuleFor(c => c.Username).NotEmpty(); RuleFor(c => c.Passkey).NotEmpty(); + RuleFor(c => c.Categories).NotEmpty(); + RuleFor(c => c.SeedCriteria).SetValidator(_ => new SeedCriteriaSettingsValidator()); } } diff --git a/src/NzbDrone.Core/Lidarr.Core.csproj b/src/NzbDrone.Core/Lidarr.Core.csproj index 9c5d16036..14b90c62a 100644 --- a/src/NzbDrone.Core/Lidarr.Core.csproj +++ b/src/NzbDrone.Core/Lidarr.Core.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 5f9c76b0c..c9271275e 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -256,6 +256,7 @@ "CreateEmptyArtistFolders": "Create empty artist folders", "CreateEmptyArtistFoldersHelpText": "Create missing artist folders during disk scan", "CreateGroup": "Create group", + "CurrentlyInstalled": "Currently Installed", "Custom": "Custom", "CustomFilter": "Custom Filter", "CustomFilters": "Custom Filters", @@ -334,8 +335,6 @@ "DeleteReleaseProfileMessageText": "Are you sure you want to delete this release profile?", "DeleteRemotePathMapping": "Delete Remote Path Mapping", "DeleteRemotePathMappingMessageText": "Are you sure you want to delete this remote path mapping?", - "DeleteRootFolder": "Delete Root Folder", - "DeleteRootFolderMessageText": "Are you sure you want to delete the root folder '{name}'?", "DeleteSelected": "Delete Selected", "DeleteSelectedArtists": "Delete Selected Artists", "DeleteSelectedCustomFormats": "Delete Custom Format(s)", @@ -383,6 +382,7 @@ "DownloadClientDelugeSettingsDirectoryCompleted": "Move When Completed Directory", "DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Optional location to move completed downloads to, leave blank to use the default Deluge location", "DownloadClientDelugeSettingsDirectoryHelpText": "Optional location to put downloads in, leave blank to use the default Deluge location", + "DownloadClientItemErrorMessage": "{clientName} is reporting an error: {message}", "DownloadClientPriorityHelpText": "Download Client Priority from 1 (Highest) to 50 (Lowest). Default: 1. Round-Robin is used for clients with the same priority.", "DownloadClientQbittorrentSettingsContentLayout": "Content Layout", "DownloadClientQbittorrentSettingsContentLayoutHelpText": "Whether to use qBittorrent's configured content layout, the original layout from the torrent or always create a subfolder (qBittorrent 4.3.2+)", @@ -486,6 +486,8 @@ "ExtraFileExtensionsHelpTextsExamples": "Examples: '.sub, .nfo' or 'sub,nfo'", "FailedDownloadHandling": "Failed Download Handling", "FailedLoadingSearchResults": "Failed to load search results, please try again.", + "FailedToFetchSettings": "Failed to fetch settings", + "FailedToFetchUpdates": "Failed to fetch updates", "FailedToLoadQueue": "Failed to load Queue", "False": "False", "FileDateHelpText": "Change file date on import/rescan", @@ -688,6 +690,7 @@ "LocalPathHelpText": "Path that {appName} should use to access the remote path locally", "Location": "Location", "LogFiles": "Log Files", + "LogFilesLocation": "Log files are located in: {location}", "LogLevel": "Log Level", "LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "Trace logging should only be enabled temporarily", "LogSizeLimit": "Log Size Limit", @@ -1024,6 +1027,8 @@ "RemoveQueueItemRemovalMethod": "Removal Method", "RemoveQueueItemRemovalMethodHelpTextWarning": "'Remove from Download Client' will remove the download and the file(s) from the download client.", "RemoveQueueItemsRemovalMethodHelpTextWarning": "'Remove from Download Client' will remove the downloads and the files from the download client.", + "RemoveRootFolder": "Remove Root Folder", + "RemoveRootFolderArtistsMessageText": "Are you sure you want to remove the root folder '{name}'? Files and folders will not be deleted from disk, and artists in this root folder will not be removed from {appName}.", "RemoveSelected": "Remove Selected", "RemoveSelectedItem": "Remove Selected Item", "RemoveSelectedItemBlocklistMessageText": "Are you sure you want to remove the selected items from the blocklist?", @@ -1218,6 +1223,7 @@ "TestParsing": "Test Parsing", "TheAlbumsFilesWillBeDeleted": "The album's files will be deleted.", "TheArtistFolderStrongpathstrongAndAllOfItsContentWillBeDeleted": "The artist folder '{0}' and all of its content will be deleted.", + "TheLogLevelDefault": "The log level defaults to 'Debug' and can be changed in [General Settings](/settings/general)", "Theme": "Theme", "ThemeHelpText": "Change Application UI Theme, 'Auto' Theme will use your OS Theme to set Light or Dark mode. Inspired by Theme.Park", "ThereWasAnErrorLoadingThisItem": "There was an error loading this item", diff --git a/src/NzbDrone.Core/Localization/Core/fi.json b/src/NzbDrone.Core/Localization/Core/fi.json index 9eab9f71b..b61d52f69 100644 --- a/src/NzbDrone.Core/Localization/Core/fi.json +++ b/src/NzbDrone.Core/Localization/Core/fi.json @@ -963,7 +963,7 @@ "Small": "Pieni", "RemoveSelectedItems": "Poista valitut kohteet", "ResetTitles": "Palauta nimet", - "AddNewArtistRootFolderHelpText": "\"{folder}\" -alikansio luodaan automaattisesti.", + "AddNewArtistRootFolderHelpText": "Alikansio \"{folder}\" luodaan automaattisesti.", "AuthenticationRequiredUsernameHelpTextWarning": "Syötä uusi käyttäjätunnus", "AutoAdd": "Automaattilisäys", "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Latauspalvelu {0} on määritetty poistamaan valmistuneet lataukset, jonka seuraksena ne saatetaan poistaa ennen kuin {1} ehtii tuoda niitä.", @@ -1058,7 +1058,7 @@ "SomeResultsAreHiddenByTheAppliedFilter": "Aktiivinen suodatin piilottaa joitakin tuloksia.", "RemotePathMappingCheckFileRemoved": "Tiedosto \"{0}\" poistettiin kesken käsittelyn.", "AddListExclusionHelpText": "Estä {appName}ia lisäämästä esittäjää listoilta.", - "ArtistsEditRootFolderHelpText": "Siirtämällä esittäjät samaan juurikansioon voidaan niiden kansioiden nimet päivittää vastaamaan päivittynyttä nimikettä tai nimeämiskaavaa.", + "ArtistsEditRootFolderHelpText": "Siirtämällä esittäjät niiden nykyiseen juurikansioon voidaan niiden kansioiden nimet päivittää vastaamaan päivittynyttä nimikettä tai nimeämiskaavaa.", "DownloadClientAriaSettingsDirectoryHelpText": "Vaihtoehtoinen latausten tallennussijainti. Käytä Aria2:n oletusta jättämällä tyhjäksi.", "DeleteArtistFoldersHelpText": "Poista esittäjäkansiot ja niiden kaikki sisältö.", "ChangeCategoryHint": "Vaihtaa latauksen kategoriaksi latauspalvelun \"Tuonnin jälkeinen kategoria\" -asetuksen kategorian.", diff --git a/src/NzbDrone.Core/Localization/Core/it.json b/src/NzbDrone.Core/Localization/Core/it.json index ea9515178..2af304152 100644 --- a/src/NzbDrone.Core/Localization/Core/it.json +++ b/src/NzbDrone.Core/Localization/Core/it.json @@ -1061,5 +1061,6 @@ "ImportFailed": "Importazione fallita: {sourceTitle}", "Paused": "In Pausa", "Pending": "In Attesa", - "UnableToImportAutomatically": "Impossibile Importare Automaticamente" + "UnableToImportAutomatically": "Impossibile Importare Automaticamente", + "AlbumCount": "Numero album" } diff --git a/src/NzbDrone.Core/Localization/Core/nb_NO.json b/src/NzbDrone.Core/Localization/Core/nb_NO.json index e694f772b..7b294c88f 100644 --- a/src/NzbDrone.Core/Localization/Core/nb_NO.json +++ b/src/NzbDrone.Core/Localization/Core/nb_NO.json @@ -286,5 +286,7 @@ "IgnoredPlaceHolder": "Legg til ny begrensning", "AddImportList": "Ny Importliste", "AddNewArtistRootFolderHelpText": "Undermappa \"{folder}\" vil bli automatisk laget", - "CheckDownloadClientForDetails": "sjekk nedlastningsklienten for mer informasjon" + "CheckDownloadClientForDetails": "sjekk nedlastningsklienten for mer informasjon", + "TBA": "Venter", + "History": "Historikk" } diff --git a/src/NzbDrone.Core/Localization/Core/nl.json b/src/NzbDrone.Core/Localization/Core/nl.json index 8c24bdac1..cb686c7ce 100644 --- a/src/NzbDrone.Core/Localization/Core/nl.json +++ b/src/NzbDrone.Core/Localization/Core/nl.json @@ -595,7 +595,7 @@ "CustomFormatSettings": "Eigen Formaten Instellingen", "CustomFormats": "Eigen Formaten", "Customformat": "Eigen Formaat", - "CutoffFormatScoreHelpText": "Wanneer deze eigen formaat score is behaald, zal {appName} niet langer films downloaden", + "CutoffFormatScoreHelpText": "Wanneer deze aangepaste formaatscore is behaald, zal {appName} niet langer albumuitgaven downloaden", "DeleteCustomFormat": "Verwijder Eigen Formaat", "DeleteCustomFormatMessageText": "Bent u zeker dat u de indexeerder '{0}' wilt verwijderen?", "DeleteFormatMessageText": "Weet je zeker dat je formaat tag {0} wilt verwijderen?", @@ -879,7 +879,7 @@ "BlocklistOnly": "Alleen bloklijst", "ChangeCategoryHint": "Verandert download naar de 'Post-Import Categorie' van Downloadclient", "ClearBlocklist": "Blokkeerlijst wissen", - "Clone": "Kloon", + "Clone": "Dupliceren", "CustomFormatsSpecificationRegularExpression": "Reguliere expressie", "CustomFormatsSpecificationRegularExpressionHelpText": "Aangepaste opmaak RegEx is hoofdletterongevoelig", "CustomFormatsSettingsTriggerInfo": "Een Aangepast Formaat wordt toegepast op een uitgave of bestand als het overeenkomt met ten minste één van de verschillende condities die zijn gekozen.", diff --git a/src/NzbDrone.Core/Localization/Core/pt.json b/src/NzbDrone.Core/Localization/Core/pt.json index 762f82806..063c9d179 100644 --- a/src/NzbDrone.Core/Localization/Core/pt.json +++ b/src/NzbDrone.Core/Localization/Core/pt.json @@ -1031,5 +1031,9 @@ "CheckDownloadClientForDetails": "verifique o cliente de transferências para obter mais detalhes", "DownloadWarning": "Alerta de transferência: {warningMessage}", "Pending": "Pendente", - "WaitingToImport": "Aguardando para importar" + "WaitingToImport": "Aguardando para importar", + "TBA": "TBA", + "ThereWasAnErrorLoadingThisItem": "Houve um erro ao carregar este item", + "ThereWasAnErrorLoadingThisPage": "Houve um erro ao carregar esta página", + "EpisodeDoesNotHaveAnAbsoluteEpisodeNumber": "Episódio não tem um número de episódio absoluto" } diff --git a/src/NzbDrone.Core/Localization/Core/uk.json b/src/NzbDrone.Core/Localization/Core/uk.json index cfd4334cb..f6db56e21 100644 --- a/src/NzbDrone.Core/Localization/Core/uk.json +++ b/src/NzbDrone.Core/Localization/Core/uk.json @@ -8,7 +8,7 @@ "BackupRetentionHelpText": "Автоматичні резервні копії, старіші за період зберігання, очищаються автоматично", "ChmodFolderHelpText": "Восьмеричний, застосовується при імпорті/перейменуванні до медіа-папок та файлів (без бітів виконання)", "CompletedDownloadHandling": "Обробка завершених завантажень", - "CopyUsingHardlinksHelpText": "Використання жорстких посилань, коли намагаєтеся скопіювати файли з торентів, які все ще завантажуються", + "CopyUsingHardlinksHelpText": "Жорсткі посилання дозволяють {appName} імпортувати торренти, що роздаються, до папки виконавця без зайвого місця на диску або копіювання всього вмісту файлу. Жорсткі посилання працюватимуть лише якщо джерело та призначення знаходяться на одному томі", "DeleteBackupMessageText": "Ви впевнені, що хочете видалити резервну копію \"{name}\"?", "DeleteDownloadClientMessageText": "Ви впевнені, що хочете видалити клієнт завантаження '{name}'?", "AlreadyInYourLibrary": "Вже у вашій бібліотеці", @@ -55,12 +55,12 @@ "ResetAPIKeyMessageText": "Ви впевнені, що хочете скинути свій ключ API?", "ShowQualityProfile": "Додати профіль якості", "AnalyticsEnabledHelpText": "Надсилайте анонімну інформацію про використання та помилки на сервери {appName}. Це включає інформацію про ваш веб-переглядач, які сторінки {appName} WebUI ви використовуєте, звіти про помилки, а також версію ОС і часу виконання. Ми будемо використовувати цю інформацію, щоб визначити пріоритети функцій і виправлення помилок.", - "DeleteMetadataProfileMessageText": "Ви впевнені, що хочете видалити цей профіль затримки?", + "DeleteMetadataProfileMessageText": "Ви впевнені, що хочете видалити профіль метаданих '{name}'", "DeleteNotificationMessageText": "Ви впевнені, що хочете видалити сповіщення '{name}'?", "DeleteQualityProfileMessageText": "Ви впевнені, що хочете видалити профіль якості '{name}'?", "DeleteReleaseProfile": "Видалити профіль випуску", - "DeleteReleaseProfileMessageText": "Ви впевнені, що хочете видалити цей профіль затримки?", - "DeleteRootFolderMessageText": "Ви впевнені, що хочете видалити тег {0} ?", + "DeleteReleaseProfileMessageText": "Ви впевнені, що хочете видалити цей профіль випуску?", + "DeleteRootFolderMessageText": "Ви впевнені, що хочете видалити кореневу папку '{name}'?", "DeleteTagMessageText": "Ви впевнені, що хочете видалити тег '{label}'?", "IsCutoffCutoff": "Припинення", "CertificateValidationHelpText": "Змініть суворість перевірки сертифікації HTTPS. Не змінюйте, якщо не розумієте ризики.", @@ -470,7 +470,7 @@ "AddImportListExclusion": "Додати виняток до списку імпорту", "AddConnection": "Додати Підключення", "AddConnectionImplementation": "Додати Підключення - {implementationName}", - "Absolute": "Абсолютний", + "Absolute": "Загальний", "AddAutoTag": "Додати Авто Тег", "AddAutoTagError": "Не вдалося додати новий авто тег, спробуйте ще раз.", "AddConditionError": "Не вдалося додати нову умову, спробуйте ще раз.", @@ -620,7 +620,7 @@ "UnmonitoredHelpText": "Включайте неконтрольовані фільми в канал iCal", "Posters": "Плакати", "Priority": "Пріоритет", - "RemotePathMappingCheckImportFailed": "{appName} не вдалося імпортувати фільм. Подробиці перевірте у своїх журналах.", + "RemotePathMappingCheckImportFailed": "{appName} не вдалося імпортувати музику. Перегляньте журнали для деталей", "SslPortHelpTextWarning": "Щоб набуло чинності, потрібно перезапустити", "ApiKeyValidationHealthCheckMessage": "Будь ласка оновіть ключ API, щоб він містив принаймні {length} символів. Ви можете зробити це в налаштуваннях або в файлі конфігурації", "CustomFilter": "Користувацькі фільтри", @@ -694,7 +694,7 @@ "LongDateFormat": "Довгий формат дати", "MaintenanceRelease": "Випуск для обслуговування: виправлення помилок та інші покращення. Щоб отримати докладнішу інформацію, перегляньте історію фіксації Github", "ReleaseDate": "Дати випуску", - "RemotePathMappingCheckDownloadPermissions": "{appName} може бачити, але не має доступу до завантаженого фільму {path}. Ймовірна помилка дозволів.", + "RemotePathMappingCheckDownloadPermissions": "{appName} бачить, але не має доступу до завантаженої музики{0}. Ймовірно, помилка дозволів.", "UnableToLoadCustomFormats": "Не вдалося завантажити спеціальні формати", "ShownAboveEachColumnWhenWeekIsTheActiveView": "Відображається над кожним стовпцем, коли тиждень є активним переглядом", "Table": "Таблиця", @@ -945,5 +945,267 @@ "NotificationsEmbySettingsUpdateLibraryHelpText": "Оновити бібліотеку при імпорті, перейменуванні або видаленні", "NotificationsSettingsUpdateMapPathsFromHelpText": "Шлях {appName}, який використовується для зміни шляхів до серіалів, коли {serviceName} бачить шлях до бібліотеки інакше, ніж {appName} (необхідно 'Оновити бібліотеку')", "NotificationsSettingsUpdateMapPathsToHelpText": "Шлях {serviceName}, що використовується для зміни шляхів до серіалів, коли {serviceName} бачить шлях до бібліотеки інакше, ніж {appName} (потрібно 'Оновити бібліотеку')", - "Select...": "Вибрати..." + "Select...": "Вибрати...", + "DeleteSelectedDownloadClients": "Видалити вибрані клієнти завантаження", + "DownloadImported": "Завантажено імпортовано", + "DownloadedWaitingToImport": "'Завантажено - Очікує імпорту'", + "FirstAlbum": "Перший альбом", + "FutureAlbumsData": "Відстежувати альбоми, які ще не вийшли", + "IsExpandedHideAlbums": "Приховати альбоми", + "ManualDownload": "Завантажити вручну", + "ArtistIsUnmonitored": "Виконавець не відстежується", + "ForeignId": "Зовнішній ідентифікатор", + "IndexerIdHelpTextWarning": "Використання певного індексатора з бажаними словами може призвести до завантаження дублікатів релізів", + "ArtistsEditRootFolderHelpText": "Переміщення виконавців до однієї кореневої папки може використовуватися для перейменування папок виконавців відповідно до оновленого імені або формату найменування", + "AllowFingerprintingHelpText": "Використовувати створення аудіовідбитків для покращення точності зіставлення треків", + "CollapseMultipleAlbumsHelpText": "Згорнути кілька альбомів, що виходять в один день", + "ContinuingNoAdditionalAlbumsAreExpected": "Додаткових альбомів не очікується", + "DownloadClientSortingCheckMessage": "Для клієнта завантаження {0} увімкнено сортування для категорії {appName}. Вам слід вимкнути сортування у вашому клієнті завантаження, щоб уникнути проблем з імпортом", + "AnchorTooltip": "Цей файл вже є у вашій бібліотеці для релізу, який ви зараз імпортуєте", + "CollapseMultipleAlbums": "Згорнути кілька альбомів", + "ExpandEPByDefaultHelpText": "EP (міні-альбоми)", + "ForNewImportsOnly": "Лише для нових імпортів", + "MetadataProfile": "Профіль метаданих", + "EditMetadataProfile": "Редагувати профіль метаданих", + "EmbedCoverArtHelpText": "Вбудовувати обкладинку альбому Lidarr у аудіофайли під час запису тегів", + "AreYouSure": "Ви впевнені?", + "DelayProfileArtistTagsHelpText": "Застосовується до виконавців, які мають хоча б один відповідний тег", + "FilterArtistPlaceholder": "Фільтрувати виконавця", + "HasMonitoredAlbumsNoMonitoredAlbumsForThisArtist": "Для цього виконавця немає жодних альбомів, що відстежуються", + "IsExpandedHideFileInfo": "Приховати інформацію про файл", + "ArtistIsMonitored": "Виконавець відстежується", + "CustomFormatRequiredHelpText": "Ця {0}-а умова повинна збігатися, щоб застосувався власний формат. Інакше достатньо одного {0}-го збігу", + "ICalTagsArtistHelpText": "Стрічка міститиме лише виконавців, які мають хоча б один відповідний тег", + "MetadataConsumers": "Споживачі метаданих", + "ArtistNameHelpText": "Назва виконавця/альбому, який потрібно виключити (може бути будь-якою значущою)", + "DefaultMonitorOptionHelpText": "Які альбоми слід відстежувати при початковому додаванні для виконавців, виявлених у цій папці", + "ExistingAlbums": "Існуючі альбоми", + "IfYouDontAddAnImportListExclusionAndTheArtistHasAMetadataProfileOtherThanNoneThenThisAlbumMayBeReaddedDuringTheNextArtistRefresh": "Якщо ви не додасте виключення зі списку імпорту, і виконавець матиме профіль метаданих, відмінний від \"Немає\", цей альбом може бути повторно додано під час наступного оновлення виконавця", + "IsInUseCantDeleteAQualityProfileThatIsAttachedToAnArtistOrImportList": "Неможливо видалити профіль якості, який пов'язаний з виконавцем або списком імпорту", + "DeleteMetadataProfile": "Видалити профіль метаданих", + "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Для клієнта завантаження {0} налаштовано видалення завершених завантажень. Це може призвести до видалення завантажень з вашого клієнта до того, як {1} зможе їх імпортувати", + "ForeignIdHelpText": "Ідентифікатор MusicBrainz виконавця/альбому, який потрібно виключити", + "MassAlbumsCutoffUnmetWarning": "Ви впевнені, що хочете виконати пошук для всіх альбомів, де не досягнуто порогового значення '{0}'?", + "IsExpandedShowAlbums": "Показати альбоми", + "IsInUseCantDeleteAMetadataProfileThatIsAttachedToAnArtistOrImportList": "Неможливо видалити профіль метаданих, який пов'язаний з виконавцем або списком імпорту", + "MetadataSettingsArtistSummary": "Створювати файли метаданих під час імпорту треків або оновлення інформації про виконавця", + "MissingAlbumsData": "Відстежувати альбоми, які не мають файлів або ще не вийшли", + "MissingTracksArtistNotMonitored": "Відсутні треки (виконавець не відстежується)", + "MonitorAlbumExistingOnlyWarning": "Це одноразове коригування налаштування відстеження для кожного альбому. Використовуйте опцію в розділі \"Виконавець/Редагувати\", щоб контролювати, що відбуватиметься з новими доданими альбомами", + "FutureDaysHelpText": "Днів для перегляду майбутніх подій у стрічці iCal", + "CountImportListsSelected": "Вибрано {selectedCount} списків імпорту", + "DateAdded": "Дата додавання", + "MissingAlbums": "Відсутні альбоми", + "DeleteTrackFile": "Видалити файл треку", + "MonitorFutureAlbums": "Майбутні альбоми", + "MonitorLastestAlbum": "Останній альбом", + "MonitorMissingAlbums": "Відсутні альбоми", + "MonitorNewAlbums": "Нові альбоми", + "MonitorNewItemsHelpText": "Які нові альбоми слід відстежувати", + "MultiDiscTrackFormat": "Формат треків на кількох дисках", + "CombineWithExistingFiles": "Об'єднати з існуючими файлами", + "ContinuingAllTracksDownloaded": "Продовжити (Усі треки завантажено)", + "ContinuingMoreAlbumsAreExpected": "Очікуються інші альбоми", + "CountAlbums": "{albumCount} альбомів", + "CountIndexersSelected": "Вибрано {selectedCount} індексаторів", + "Country": "Країна", + "Deceased": "Помер(ла)", + "DefaultDelayProfileArtist": "Це профіль за замовчуванням. Він застосовується до всіх виконавців, які не мають явного профілю.", + "DefaultLidarrTags": "Теги {appName} за замовчуванням", + "DefaultMetadataProfileIdHelpText": "Профіль метаданих за замовчуванням для виконавців, виявлених у цій папці", + "DefaultQualityProfileIdHelpText": "Профіль якості за замовчуванням для виконавців, виявлених у цій папці", + "DefaultTagsHelpText": "Теги {appName} за замовчуванням для виконавців, виявлених у цій папці", + "DeleteArtist": "Видалити вибраного виконавця", + "DeleteArtistFolder": "Видалити папку виконавця", + "DeleteArtistFolders": "Видалити папки виконавців", + "DeleteFilesHelpText": "Видалити файли треків та папку виконавця", + "DeleteFormat": "Видалити формат", + "DeleteSelectedArtists": "Видалити вибраних виконавців", + "Discography": "Дискографія", + "DownloadClientSettingsRecentPriorityAlbumHelpText": "Пріоритет, який використовуватиметься при завантаженні альбомів, випущених протягом останніх 14 днів", + "DownloadPropersAndRepacksHelpTexts2": "Використовуйте \"Не надавати перевагу\", щоб сортувати за оцінкою бажаного слова, а не за належними назвами/перепакуваннями", + "DownloadedImporting": "'Завантажено - Імпортується'", + "DownloadedUnableToImportCheckLogsForDetails": "'Завантажено - Неможливо імпортувати: деталі дивіться в журналах'", + "EditArtist": "Редагувати виконавця", + "EditMetadata": "Редагувати метадані", + "EditSelectedArtists": "Редагувати вибраних виконавців", + "EmbedCoverArtInAudioFiles": "Вбудувати обкладинку в аудіофайли", + "EnableAutomaticAddHelpText": "Додавати виконавців/альбоми до {appName} під час синхронізації через інтерфейс користувача або {appName}", + "EndedAllTracksDownloaded": "Закінчено (Усі треки завантажено)", + "EntityName": "Назва сутності", + "ExistingAlbumsData": "Відстежувати альбоми, які мають файли або ще не вийшли", + "ExistingTagsScrubbed": "Наявні теги очищено", + "ExpandBroadcastByDefaultHelpText": "Трансляція", + "ExpandItemsByDefault": "Розгорнути елементи за замовчуванням", + "ExpandSingleByDefaultHelpText": "Сингли", + "FilterAlbumPlaceholder": "Фільтрувати альбом", + "FirstAlbumData": "Відстежувати перші альбоми. Усі інші альбоми буде проігноровано", + "FutureAlbums": "Майбутні альбоми", + "FutureDays": "Майбутні дні", + "GoToArtistListing": "Перейти до списку виконавців", + "GroupInformation": "Інформація про групу", + "HideAlbums": "Приховати альбоми", + "HideTracks": "Приховати треки", + "ImportCompleteFailed": "Імпорт не вдався", + "ImportFailures": "Збої імпорту", + "ImportListSettings": "Загальні налаштування списку імпорту", + "ImportListSpecificSettings": "Специфічні налаштування списку імпорту", + "Inactive": "Неактивний", + "IndexerDownloadClientHealthCheckMessage": "Індексатори з недійсними клієнтами завантаження: {0}.", + "IsExpandedShowFileInfo": "Показати інформацію про файл", + "IsExpandedShowTracks": "Показати треки", + "LastAlbum": "Останній альбом", + "LatestAlbum": "Найновіший альбом", + "LatestAlbumData": "Відстежувати останні та майбутні альбоми", + "LidarrSupportsMultipleListsForImportingAlbumsAndArtistsIntoTheDatabase": "{appName} підтримує кілька списків для імпорту альбомів та виконавців до бази даних", + "ListWillRefreshEveryInterp": "Список оновлюватиметься кожні {0}", + "MatchedToAlbums": "Збіги з альбомами", + "MatchedToArtist": "Збіги з виконавцем", + "MediaCount": "Кількість медіафайлів", + "MediumFormat": "Формат носія", + "MetadataProfileIdHelpText": "Елементи списку профілю метаданих слід додавати з", + "MetadataProfiles": "Профілі метаданих", + "MonitorAlbum": "Відстежувати альбом", + "MonitorArtist": "Відстежувати виконавця", + "MonitorArtists": "Відстежувати виконавців", + "MonitorExistingAlbums": "Наявні альбоми", + "MonitorFirstAlbum": "Перший альбом", + "MonitorNoNewAlbums": "Немає нових альбомів", + "MonitoredHelpText": "Завантажити відстежувані альбоми цього виконавця", + "MonitoringOptionsHelpText": "Які альбоми слід відстежувати після додавання виконавця (одноразове налаштування)", + "DownloadClientSettingsOlderPriorityAlbumHelpText": "Пріоритет, який використовуватиметься при завантаженні альбомів, випущених понад 14 днів тому", + "MissingTracks": "Відсутні треки", + "MissingTracksArtistMonitored": "Відсутні треки (виконавець відстежується)", + "AddMetadataProfile": "Додати профіль метаданих", + "AddedArtistSettings": "Додано налаштування артиста", + "AlbumCount": "Кількість альбомів", + "AlbumHasNotAired": "Альбом не був випущений", + "AlbumInfo": "Інформація про альбом", + "AlbumIsNotMonitored": "Альбом не моніториться", + "AlbumRelease": "Випуск альбому", + "AlbumStudio": "Студійний альбом", + "AllArtistAlbums": "Усі альбоми виконавця", + "AllMonitoringOptionHelpText": "Відстежувати виконавців та всі альбоми кожного виконавця, включеного до списку імпорту", + "AllowArtistChangeClickToChangeArtist": "Натисніть, щоб змінити виконавця", + "AllowFingerprinting": "Дозволити створення аудіовідбитків", + "AllowFingerprintingHelpTextWarning": "Для цього програмі 1 {appName} потрібно зчитати частини файлу, що сповільнить сканування та може спричинити високу активність диска або мережі", + "AnyReleaseOkHelpText": "{appName} автоматично перемкнеться на реліз, який найкраще відповідає завантаженим трекам", + "ArtistClickToChangeAlbum": "Натисніть, щоб змінити альбом", + "ArtistEditor": "Редактор виконавця", + "ArtistFolderFormat": "Формат папки виконавця", + "ArtistProgressBarText": "Завантажено файлів: {trackFileCount} / Всього треків у файлах: {trackCount} (Всього треків у релізі: {totalTrackCount}, Завантажується треків: {downloadingCount})", + "AutomaticallySwitchRelease": "Автоматично вибирати реліз", + "BannerOptions": "Параметри банера", + "Banners": "Банери", + "CatalogNumber": "Каталожний номер", + "Disambiguation": "Розрізнення", + "DiscCount": "Кількість дисків", + "DiscNumber": "Номер диску", + "IsExpandedHideTracks": "Приховати треки", + "ManageTracks": "Керувати треками", + "ScrubExistingTags": "Очистити існуючі теги", + "PathHelpText": "Коренева папка, що містить вашу музичну бібліотеку", + "RecycleBinUnableToWriteHealthCheck": "Не вдається записати до налаштованої папки кошика: {0}. Переконайтеся, що цей шлях існує і доступний для запису користувачем, який запустив {appName}", + "SelectArtist": "Вибрати виконавця", + "ShowNextAlbumHelpText": "Показувати наступний альбом під постером", + "ShouldMonitorExistingHelpText": "Автоматично відстежувати альбоми зі цього списку, які вже є в {appName}", + "UnableToLoadInteractiveSearch": "Не вдалося завантажити результати для цього пошуку альбому. Спробуйте пізніше", + "SpecificMonitoringOptionHelpText": "Відстежувати виконавців, але відстежувати лише альбоми, явно включені до списку", + "SearchForAllMissingAlbumsConfirmationCount": "Ви впевнені, що хочете шукати всі {totalRecords} відсутніх альбомів?", + "NoHistoryBlocklist": "Немає історії заблокованих елементів", + "QualityProfileIdHelpText": "Елементи списку профілів якості слід додавати за допомогою", + "ShouldSearchHelpText": "Пошук в індексаторах нових доданих елементів. Обережно використовуйте для великих списків.", + "NotificationsEmbySettingsSendNotificationsHelpText": "Відправляти сповіщення MediaBrowser на налаштовані провайдери", + "TrackFileRenamedTooltip": "Файл треку перейменовано", + "TrackMissingFromDisk": "Трек відсутній на диску", + "WatchLibraryForChangesHelpText": "Автоматично сканувати при зміні файлів у кореневій папці", + "MonitorNewItems": "Відстежувати нові альбоми", + "ReleaseProfileTagArtistHelpText": "Профілі випуску застосовуватимуться до виконавців, які мають хоча б один відповідний тег. Залиште порожнім, щоб застосувати до всіх виконавців", + "ReplaceExistingFiles": "Замінити існуючі файли", + "Retag": "Перетегувати", + "Retagged": "Перетеговано", + "TotalTrackCountTracksTotalTrackFileCountTracksWithFilesInterp": "Всього {0} треків. {1} треків з файлами.", + "UnableToLoadMetadataProviderSettings": "Не вдалося завантажити налаштування постачальника метаданих", + "RenameTracks": "Перейменувати треки", + "NoMediumInformation": "Інформація про носій недоступна", + "NotificationsTagsArtistHelpText": "Надсилати сповіщення лише для виконавців, які мають хоча б один відповідний тег", + "OnArtistAdd": "При додаванні виконавця", + "OnArtistDelete": "При видаленні виконавця", + "OnImportFailure": "При помилці імпорту", + "OneAlbum": "1 альбом", + "PastDays": "Минулі дні", + "PastDaysHelpText": "Кількість днів для перегляду минулих подій у фіді iCa", + "Playlist": "Плейлист", + "ProfilesSettingsArtistSummary": "Якість, метадані, затримка та профілі випуску", + "RetagSelectedArtists": "Перетегувати вибраних виконавців", + "SearchBoxPlaceHolder": "напр., Breaking Benjamin, lidarr:854a1807-025b-42a8-ba8c-2a39717f1d25", + "SearchForAllCutoffUnmetAlbums": "Пошук усіх альбомів, які не відповідають критерію відсікання", + "SecondaryAlbumTypes": "Другорядні типи альбомів", + "SecondaryTypes": "Другорядні типи", + "ShouldMonitorExisting": "Відстежувати існуючі альбоми", + "ShowBannersHelpText": "Показувати банери замість назв", + "SkipRedownloadHelpText": "Запобігає спробам {appName} завантажувати альтернативні випуски для видалених елементів.", + "ReleasesHelpText": "Змінити випуск для цього альбому", + "MusicBrainzAlbumID": "MusicBrainz Альбом ID", + "MusicBrainzArtistID": "MusicBrainz викаонавець ID", + "NoTracksInThisMedium": "На цьому носії немає треків", + "OnReleaseImport": "При імпорті релізу", + "SearchForAllCutoffUnmetAlbumsConfirmationCount": "Ви впевнені, що хочете шукати всі {totalRecords} альбомів, які не відповідають критерію відсікання?", + "SelectTracks": "Вибрати треки", + "TrackArtist": "Виконавець треку", + "TrackCount": "Кількість треків", + "TrackDownloaded": "Трек завантажено", + "TrackFileCounttotalTrackCountTracksDownloadedInterp": "Завантажено {0} з {1} треків", + "WriteMetadataToAudioFiles": "Записувати метадані до аудіофайлів", + "WriteAudioTagsHelpTextWarning": "Вибір \"Усі файли\" змінить існуючі файли під час їх імпорту.", + "WriteMetadataTags": "Записати теги метаданих", + "MusicBrainzRecordingID": "MusicBrainz запису ID", + "MusicBrainzReleaseID": "MusicBrainz релізу ID", + "MusicBrainzTrackID": "MusicBrainz Track ID", + "MusicbrainzId": "Musicbrainz Id", + "NewAlbums": "Нові альбоми", + "NextAlbum": "Наступний альбом", + "NoAlbums": "Немає альбомів", + "NoneData": "Жоден альбом не буде відстежуватися", + "NoneMonitoringOptionHelpText": "Не відстежувати виконавців або альбоми", + "NotDiscography": "Не дискографія", + "OnAlbumDelete": "При видаленні альбому", + "OnDownloadFailure": "При помилці завантаження", + "OnTrackRetag": "При перетегуванні треку", + "PathHelpTextWarning": "Це має відрізнятися від каталогу, куди ваш клієнт завантажує файли", + "PreviewRetag": "Попередній перегляд перетегування", + "PrimaryAlbumTypes": "Основні типи альбомів", + "PrimaryTypes": "Основні типи", + "Proceed": "Продовжити", + "RefreshArtist": "Оновити виконавця", + "ScrubAudioTagsHelpText": "Видалити існуючі теги з файлів, залишивши лише ті, що додані {appName}.", + "SearchAlbum": "Пошук альбому", + "SearchForAllMissingAlbums": "Пошук усіх відсутніх альбомів", + "SearchForMonitoredAlbums": "Пошук відстежуваних альбомів", + "SelectAlbum": "Вибрати альбом", + "SelectAlbumRelease": "Вибрати випуск альбому", + "SelectedCountArtistsSelectedInterp": "Вибрано {selectedCount} виконавця(ів)", + "SetAppTags": "Встановити теги {appName}.", + "ShouldMonitorHelpText": "Відстежувати виконавців та альбоми, додані з цього списку", + "ShouldSearch": "Пошук нових елементів", + "ShowAlbumCount": "Показати кількість альбомів", + "ShowLastAlbum": "Показати останній альбом", + "ShowName": "Показати назву", + "ShowNextAlbum": "Показати наступний альбом", + "ShowTitleHelpText": "Показувати ім'я виконавця під постером", + "SpecificAlbum": "Конкретний альбом", + "TagAudioFilesWithMetadata": "Тегувати аудіофайли метаданими", + "TheAlbumsFilesWillBeDeleted": "Файли альбому буде видалено", + "TrackFileDeletedTooltip": "Файл треку видалено", + "TrackFileMissingTooltip": "Файл треку відсутній", + "TrackFileTagsUpdatedTooltip": "Теги файлу треку оновлено", + "TrackFiles": "Файли треків", + "TrackFilesLoadError": "Не вдалося завантажити файли треків", + "TrackImported": "Трек імпортовано", + "TrackNaming": "Іменування треків", + "TrackProgress": "Прогрес треку", + "TrackStatus": "Статус треку", + "TracksLoadError": "Не вдалося завантажити треки", + "UpdatingIsDisabledInsideADockerContainerUpdateTheContainerImageInstead": "Оновлення вимкнено всередині контейнера Docker. Оновіть образ контейнера.", + "WatchRootFoldersForFileChanges": "Слідкувати за змінами файлів у кореневих папках" } diff --git a/src/NzbDrone.Core/Localization/Core/zh_CN.json b/src/NzbDrone.Core/Localization/Core/zh_CN.json index 93e286e22..54d7c5a4f 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_CN.json +++ b/src/NzbDrone.Core/Localization/Core/zh_CN.json @@ -1349,5 +1349,7 @@ "WaitingToImport": "等待导入", "WaitingToProcess": "等待处理", "DelayProfileArtistTagsHelpText": "应用到至少有一个标签匹配的艺术家", - "AlbumInfo": "专辑 信息" + "AlbumInfo": "专辑 信息", + "DownloadClientSettingsOlderPriorityAlbumHelpText": "优先使用14天前发布的专辑", + "DownloadClientSettingsRecentPriorityAlbumHelpText": "优先使用过去14天内发布的专辑" } diff --git a/src/NzbDrone.Core/Localization/Core/zh_Hans.json b/src/NzbDrone.Core/Localization/Core/zh_Hans.json index 032c8c738..69720c484 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_Hans.json +++ b/src/NzbDrone.Core/Localization/Core/zh_Hans.json @@ -4,5 +4,9 @@ "Always": "总是", "Analytics": "分析", "Username": "用户名", - "Activity": "活动" + "Activity": "活动", + "UseProxy": "使用代理", + "Uptime": "运行时间", + "Warn": "警告", + "Updates": "更新" } diff --git a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs index 0dd3c3e62..1dddb8de2 100644 --- a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs @@ -65,6 +65,10 @@ namespace NzbDrone.Core.MediaFiles _logger.Debug("Removing existing track file: {0}", file); _recycleBinProvider.DeleteFile(trackFilePath, subfolder); } + else + { + _logger.Warn("Existing track file missing from disk: {0}", trackFilePath); + } moveFileResult.OldFiles.Add(file); _mediaFileService.Delete(file, DeleteMediaFileReason.Upgrade); diff --git a/src/NzbDrone.Core/Music/Utilities/ShouldRefreshAlbum.cs b/src/NzbDrone.Core/Music/Utilities/ShouldRefreshAlbum.cs index 5dac303ce..f21c6d70c 100644 --- a/src/NzbDrone.Core/Music/Utilities/ShouldRefreshAlbum.cs +++ b/src/NzbDrone.Core/Music/Utilities/ShouldRefreshAlbum.cs @@ -19,26 +19,34 @@ namespace NzbDrone.Core.Music public bool ShouldRefresh(Album album) { - if (album.LastInfoSync < DateTime.UtcNow.AddDays(-60)) + try { - _logger.Trace("Album {0} last updated more than 60 days ago, should refresh.", album.Title); - return true; - } + if (album.LastInfoSync < DateTime.UtcNow.AddDays(-60)) + { + _logger.Trace("Album {0} last updated more than 60 days ago, should refresh.", album.Title); + return true; + } - if (album.LastInfoSync >= DateTime.UtcNow.AddHours(-12)) - { - _logger.Trace("Album {0} last updated less than 12 hours ago, should not be refreshed.", album.Title); + if (album.LastInfoSync >= DateTime.UtcNow.AddHours(-12)) + { + _logger.Trace("Album {0} last updated less than 12 hours ago, should not be refreshed.", album.Title); + return false; + } + + if (album.ReleaseDate > DateTime.UtcNow.AddDays(-30)) + { + _logger.Trace("album {0} released less than 30 days ago, should refresh.", album.Title); + return true; + } + + _logger.Trace("Album {0} released long ago and recently refreshed, should not be refreshed.", album.Title); return false; } - - if (album.ReleaseDate > DateTime.UtcNow.AddDays(-30)) + catch (Exception e) { - _logger.Trace("album {0} released less than 30 days ago, should refresh.", album.Title); + _logger.Error(e, "Unable to determine if album should refresh, will try to refresh."); return true; } - - _logger.Trace("Album {0} released long ago and recently refreshed, should not be refreshed.", album.Title); - return false; } } } diff --git a/src/NzbDrone.Core/Music/Utilities/ShouldRefreshArtist.cs b/src/NzbDrone.Core/Music/Utilities/ShouldRefreshArtist.cs index 495b937af..26548d757 100644 --- a/src/NzbDrone.Core/Music/Utilities/ShouldRefreshArtist.cs +++ b/src/NzbDrone.Core/Music/Utilities/ShouldRefreshArtist.cs @@ -22,40 +22,48 @@ namespace NzbDrone.Core.Music public bool ShouldRefresh(Artist artist) { - if (artist.LastInfoSync == null) + try { - _logger.Trace("Artist {0} was just added, should refresh.", artist.Name); - return true; - } + if (artist.LastInfoSync == null) + { + _logger.Trace("Artist {0} was just added, should refresh.", artist.Name); + return true; + } - if (artist.LastInfoSync < DateTime.UtcNow.AddDays(-30)) - { - _logger.Trace("Artist {0} last updated more than 30 days ago, should refresh.", artist.Name); - return true; - } + if (artist.LastInfoSync < DateTime.UtcNow.AddDays(-30)) + { + _logger.Trace("Artist {0} last updated more than 30 days ago, should refresh.", artist.Name); + return true; + } - if (artist.LastInfoSync >= DateTime.UtcNow.AddHours(-12)) - { - _logger.Trace("Artist {0} last updated less than 12 hours ago, should not be refreshed.", artist.Name); + if (artist.LastInfoSync >= DateTime.UtcNow.AddHours(-12)) + { + _logger.Trace("Artist {0} last updated less than 12 hours ago, should not be refreshed.", artist.Name); + return false; + } + + if (artist.Metadata.Value.Status == ArtistStatusType.Continuing && artist.LastInfoSync < DateTime.UtcNow.AddDays(-2)) + { + _logger.Trace("Artist {0} is continuing and has not been refreshed in 2 days, should refresh.", artist.Name); + return true; + } + + var lastAlbum = _albumService.GetAlbumsByArtist(artist.Id).MaxBy(e => e.ReleaseDate); + + if (lastAlbum != null && lastAlbum.ReleaseDate > DateTime.UtcNow.AddDays(-30)) + { + _logger.Trace("Last album in {0} aired less than 30 days ago, should refresh.", artist.Name); + return true; + } + + _logger.Trace("Artist {0} ended long ago, should not be refreshed.", artist.Name); return false; } - - if (artist.Metadata.Value.Status == ArtistStatusType.Continuing && artist.LastInfoSync < DateTime.UtcNow.AddDays(-2)) + catch (Exception e) { - _logger.Trace("Artist {0} is continuing and has not been refreshed in 2 days, should refresh.", artist.Name); + _logger.Error(e, "Unable to determine if artist should refresh, will try to refresh."); return true; } - - var lastAlbum = _albumService.GetAlbumsByArtist(artist.Id).MaxBy(e => e.ReleaseDate); - - if (lastAlbum != null && lastAlbum.ReleaseDate > DateTime.UtcNow.AddDays(-30)) - { - _logger.Trace("Last album in {0} aired less than 30 days ago, should refresh.", artist.Name); - return true; - } - - _logger.Trace("Artist {0} ended long ago, should not be refreshed.", artist.Name); - return false; } } } diff --git a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs index 9610ef324..c657f8b16 100644 --- a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs +++ b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs @@ -347,7 +347,7 @@ namespace NzbDrone.Core.Notifications.CustomScript { if (artist == null) { - return null; + return new List(); } return _tagRepository.GetTags(artist.Tags) diff --git a/src/NzbDrone.Core/Notifications/Discord/Discord.cs b/src/NzbDrone.Core/Notifications/Discord/Discord.cs index 94714a4f0..9889a8ace 100644 --- a/src/NzbDrone.Core/Notifications/Discord/Discord.cs +++ b/src/NzbDrone.Core/Notifications/Discord/Discord.cs @@ -514,9 +514,9 @@ namespace NzbDrone.Core.Notifications.Discord { var albumTitles = string.Join(" + ", albums.Select(e => e.Title)); - var title = $"{artist.Name} - {albumTitles}"; + var title = $"{artist.Name} - {albumTitles}".Replace("`", "\\`"); - return title.Length > 256 ? $"{title.AsSpan(0, 253)}..." : title; + return title.Length > 256 ? $"{title.AsSpan(0, 253).TrimEnd('\\')}..." : title; } } } diff --git a/src/NzbDrone.Core/RemotePathMappings/RemotePathMappingService.cs b/src/NzbDrone.Core/RemotePathMappings/RemotePathMappingService.cs index b757db3f3..9ee8a0ad0 100644 --- a/src/NzbDrone.Core/RemotePathMappings/RemotePathMappingService.cs +++ b/src/NzbDrone.Core/RemotePathMappings/RemotePathMappingService.cs @@ -96,6 +96,11 @@ namespace NzbDrone.Core.RemotePathMappings throw new ArgumentException("Invalid Host"); } + if (mapping.RemotePath.StartsWith(" ")) + { + throw new ArgumentException("Remote Path must not start with a space"); + } + var remotePath = new OsPath(mapping.RemotePath); var localPath = new OsPath(mapping.LocalPath); diff --git a/src/NzbDrone.Integration.Test/ApiTests/ArtistEditorFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/ArtistEditorFixture.cs index ca5e2743f..07f106f5e 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/ArtistEditorFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/ArtistEditorFixture.cs @@ -7,6 +7,7 @@ using NzbDrone.Test.Common; namespace NzbDrone.Integration.Test.ApiTests { [TestFixture] + [Ignore("Waiting for metadata to be back again", Until = "2025-07-01 00:00:00Z")] public class ArtistEditorFixture : IntegrationTest { private void GivenExistingArtist() diff --git a/src/NzbDrone.Integration.Test/ApiTests/ArtistFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/ArtistFixture.cs index 61279a695..3cb30f462 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/ArtistFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/ArtistFixture.cs @@ -7,6 +7,7 @@ using NUnit.Framework; namespace NzbDrone.Integration.Test.ApiTests { [TestFixture] + [Ignore("Waiting for metadata to be back again", Until = "2025-07-01 00:00:00Z")] public class ArtistFixture : IntegrationTest { [Test] diff --git a/src/NzbDrone.Integration.Test/ApiTests/ArtistLookupFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/ArtistLookupFixture.cs index af78cd1b5..7a6dec7f4 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/ArtistLookupFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/ArtistLookupFixture.cs @@ -4,6 +4,7 @@ using NUnit.Framework; namespace NzbDrone.Integration.Test.ApiTests { [TestFixture] + [Ignore("Waiting for metadata to be back again", Until = "2025-07-01 00:00:00Z")] public class ArtistLookupFixture : IntegrationTest { [TestCase("Kiss", "Kiss")] diff --git a/src/NzbDrone.Integration.Test/ApiTests/BlocklistFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/BlocklistFixture.cs index e727e4608..67a448b04 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/BlocklistFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/BlocklistFixture.cs @@ -6,6 +6,7 @@ using NUnit.Framework; namespace NzbDrone.Integration.Test.ApiTests { [TestFixture] + [Ignore("Waiting for metadata to be back again", Until = "2025-07-01 00:00:00Z")] public class BlocklistFixture : IntegrationTest { private ArtistResource _artist; diff --git a/src/NzbDrone.Integration.Test/ApiTests/CalendarFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/CalendarFixture.cs index 240bc9553..8b13f9c37 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/CalendarFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/CalendarFixture.cs @@ -9,6 +9,7 @@ using NzbDrone.Integration.Test.Client; namespace NzbDrone.Integration.Test.ApiTests { [TestFixture] + [Ignore("Waiting for metadata to be back again", Until = "2025-07-01 00:00:00Z")] public class CalendarFixture : IntegrationTest { public ClientBase Calendar; diff --git a/src/NzbDrone.Integration.Test/ApiTests/TrackFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/TrackFixture.cs index 91a86091d..19301d36b 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/TrackFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/TrackFixture.cs @@ -7,6 +7,7 @@ using NUnit.Framework; namespace NzbDrone.Integration.Test.ApiTests { [TestFixture] + [Ignore("Waiting for metadata to be back again", Until = "2025-07-01 00:00:00Z")] public class TrackFixture : IntegrationTest { private ArtistResource _artist; diff --git a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs index 2a859aefb..ef1f1edc2 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs @@ -8,6 +8,7 @@ using NzbDrone.Core.Qualities; namespace NzbDrone.Integration.Test.ApiTests.WantedTests { [TestFixture] + [Ignore("Waiting for metadata to be back again", Until = "2025-07-01 00:00:00Z")] public class CutoffUnmetFixture : IntegrationTest { [SetUp] diff --git a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs index 934543499..237953a0e 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs @@ -7,6 +7,7 @@ using NzbDrone.Core.Music; namespace NzbDrone.Integration.Test.ApiTests.WantedTests { [TestFixture] + [Ignore("Waiting for metadata to be back again", Until = "2025-07-01 00:00:00Z")] public class MissingFixture : IntegrationTest { [SetUp]