From 463ead657946d0f84fcafb5379ebf42cf6a18686 Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 15 Aug 2022 22:45:37 -0500 Subject: [PATCH 0001/1478] Bump version to 1.1.1 --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3d02269f2..5457f61b9 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: '1.1.0' + majorVersion: '1.1.1' minorVersion: $[counter('minorVersion', 1076)] lidarrVersion: '$(majorVersion).$(minorVersion)' buildName: '$(Build.SourceBranchName).$(lidarrVersion)' From b3b93db6432550164b1b9219e1d6f6f4e374f7a7 Mon Sep 17 00:00:00 2001 From: Mark Trolley Date: Fri, 19 Aug 2022 09:08:57 -0400 Subject: [PATCH 0002/1478] Fix typo: loseless to lossless --- .../DownloadClientTests/Blackhole/TorrentBlackholeFixture.cs | 2 +- .../Indexers/Newznab/NewznabCategoryFieldOptionsConverter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/TorrentBlackholeFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/TorrentBlackholeFixture.cs index b22477237..b5cb79956 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/TorrentBlackholeFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/TorrentBlackholeFixture.cs @@ -104,7 +104,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole torrentInfo.Title = remoteAlbum.Release.Title; torrentInfo.DownloadUrl = remoteAlbum.Release.DownloadUrl; torrentInfo.DownloadProtocol = remoteAlbum.Release.DownloadProtocol; - torrentInfo.MagnetUrl = "magnet:?xt=urn:btih:755248817d32b00cc853e633ecdc48e4c21bff15&dn=Artist.Album.FLAC.loseless-DEFiNE%5Brartv%5D&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710&tr=udp%3A%2F%2F9.rarbg.to%3A2710"; + torrentInfo.MagnetUrl = "magnet:?xt=urn:btih:755248817d32b00cc853e633ecdc48e4c21bff15&dn=Artist.Album.FLAC.lossless-DEFiNE%5Brartv%5D&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710&tr=udp%3A%2F%2F9.rarbg.to%3A2710"; remoteAlbum.Release = torrentInfo; diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabCategoryFieldOptionsConverter.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabCategoryFieldOptionsConverter.cs index 922b08659..1d1498a82 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabCategoryFieldOptionsConverter.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabCategoryFieldOptionsConverter.cs @@ -27,7 +27,7 @@ namespace NzbDrone.Core.Indexers.Newznab Name = "Music", Subcategories = new List { - new NewznabCategory { Id = 3040, Name = "Loseless" }, + new NewznabCategory { Id = 3040, Name = "Lossless" }, new NewznabCategory { Id = 3010, Name = "MP3" }, new NewznabCategory { Id = 3050, Name = "Other" }, new NewznabCategory { Id = 3030, Name = "Audiobook" } From d27d982120f08c51972bea1009ce7a1d97e0b203 Mon Sep 17 00:00:00 2001 From: "servarr[bot]" <68984020+servarr[bot]@users.noreply.github.com> Date: Sat, 20 Aug 2022 00:01:15 -0500 Subject: [PATCH 0003/1478] Fixed: Don't process files that don't have a supported media file extension (#2938) * Fixed: Don't process files that don't have a supported media file extension (cherry picked from commit 1977f4aa3c55cc0a06e1958e611f366d56cc346e) * fixup! * fixup! Co-authored-by: Mark McDowall Co-authored-by: Qstick --- .../MediaFiles/DownloadedTracksImportService.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedTracksImportService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedTracksImportService.cs index 32c00b908..d26cf0525 100644 --- a/src/NzbDrone.Core/MediaFiles/DownloadedTracksImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/DownloadedTracksImportService.cs @@ -6,6 +6,7 @@ using System.Linq; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Extensions; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download; using NzbDrone.Core.MediaFiles.Events; @@ -276,6 +277,20 @@ namespace NzbDrone.Core.MediaFiles }; } + var extension = Path.GetExtension(fileInfo.Name); + + if (extension.IsNullOrWhiteSpace() || !MediaFileExtensions.Extensions.Contains(extension)) + { + _logger.Debug("[{0}] has an unsupported extension: '{1}'", fileInfo.FullName, extension); + + return new List + { + new ImportResult(new ImportDecision(new LocalTrack { Path = fileInfo.FullName }, + new Rejection($"Invalid audio file, unsupported extension: '{extension}'")), + $"Invalid audio file, unsupported extension: '{extension}'") + }; + } + if (downloadClientItem == null) { if (_diskProvider.IsFileLocked(fileInfo.FullName)) From 4985024dd6166972f42205d3408f28b3bdd78641 Mon Sep 17 00:00:00 2001 From: Stevie Robinson Date: Thu, 22 Sep 2022 18:48:29 +0200 Subject: [PATCH 0004/1478] New: Torrent Seed Ratio no longer advance settings (cherry picked from commit c98fac65ed8e669c5e97ed3255c424b61fe0e8b3) --- src/NzbDrone.Core/Indexers/SeedCriteriaSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/Indexers/SeedCriteriaSettings.cs b/src/NzbDrone.Core/Indexers/SeedCriteriaSettings.cs index d9b5410c1..b2ffed74a 100644 --- a/src/NzbDrone.Core/Indexers/SeedCriteriaSettings.cs +++ b/src/NzbDrone.Core/Indexers/SeedCriteriaSettings.cs @@ -50,7 +50,7 @@ namespace NzbDrone.Core.Indexers { private static readonly SeedCriteriaSettingsValidator Validator = new SeedCriteriaSettingsValidator(); - [FieldDefinition(0, Type = FieldType.Textbox, Label = "Seed Ratio", HelpText = "The ratio a torrent should reach before stopping, empty is download client's default", Advanced = true)] + [FieldDefinition(0, Type = FieldType.Textbox, Label = "Seed Ratio", HelpText = "The ratio a torrent should reach before stopping, empty is download client's default. Ratio should be at least 1.0 and follow the indexers rules")] public double? SeedRatio { get; set; } [FieldDefinition(1, Type = FieldType.Textbox, Label = "Seed Time", Unit = "minutes", HelpText = "The time a torrent should be seeded before stopping, empty is download client's default", Advanced = true)] From d580bcb79c9fe2427e4c6d646593610b72d074a4 Mon Sep 17 00:00:00 2001 From: Robin Dadswell <19610103+RobinDadswell@users.noreply.github.com> Date: Thu, 8 Sep 2022 11:14:57 +0100 Subject: [PATCH 0005/1478] Update Bug Report Template [skip ci] (cherry picked from commit b9185574f3761e125151907e3d31511689a4513e) --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 32b32499a..31914dc75 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -42,12 +42,14 @@ body: - **Docker Install**: Yes - **Using Reverse Proxy**: No - **Browser**: Firefox 90 (If UI related) + - **Database**: Sqlite 3.36.0 value: | - OS: - Lidarr: - Docker Install: - Using Reverse Proxy: - Browser: + - Database: render: markdown validations: required: true From 9677b41cebe0505c98a214182ec3697992f9a7dd Mon Sep 17 00:00:00 2001 From: bakerboy448 <55419169+bakerboy448@users.noreply.github.com> Date: Wed, 7 Sep 2022 18:28:29 -0500 Subject: [PATCH 0006/1478] Update Bug Report Template [skip ci] (cherry picked from commit 99e0d42b717b279be0f9005039025cd730f90f6c) --- .github/ISSUE_TEMPLATE/bug_report.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 31914dc75..e12afbd39 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -5,9 +5,9 @@ body: - type: checkboxes attributes: label: Is there an existing issue for this? - description: Please search to see if an issue already exists for the bug you encountered. + description: Please search to see if an open or closed issue already exists for the bug you encountered. If a bug exists and is closed note that it may only be fixed in an unstable branch. options: - - label: I have searched the existing issues + - label: I have searched the existing open and closed issues required: true - type: textarea attributes: From 77041a5401ae2a4af7aaf6706ec605765b45c4ee Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sat, 17 Sep 2022 17:40:12 -0700 Subject: [PATCH 0007/1478] Fixed: Series list jump bar click issues (cherry picked from commit 9c7378625112088d022239fdbdb90c0dc941d61d) --- frontend/src/Components/Page/PageJumpBar.css | 1 + frontend/src/Styles/Variables/zIndexes.js | 1 + 2 files changed, 2 insertions(+) diff --git a/frontend/src/Components/Page/PageJumpBar.css b/frontend/src/Components/Page/PageJumpBar.css index 9a116fb54..f5ae7a729 100644 --- a/frontend/src/Components/Page/PageJumpBar.css +++ b/frontend/src/Components/Page/PageJumpBar.css @@ -1,4 +1,5 @@ .jumpBar { + z-index: $pageJumpBarZIndex; display: flex; align-content: stretch; align-items: stretch; diff --git a/frontend/src/Styles/Variables/zIndexes.js b/frontend/src/Styles/Variables/zIndexes.js index 986ceb548..4d10253a7 100644 --- a/frontend/src/Styles/Variables/zIndexes.js +++ b/frontend/src/Styles/Variables/zIndexes.js @@ -1,4 +1,5 @@ module.exports = { + pageJumpBarZIndex: 10, modalZIndex: 1000, popperZIndex: 2000 }; From a26cbdf61f23004214396377fb3a324f362054c3 Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 26 Sep 2022 14:20:20 -0500 Subject: [PATCH 0008/1478] New: Add tag support to indexers Co-Authored-By: Mark McDowall --- .../Indexers/EditIndexerModalContent.js | 17 ++- .../src/Settings/Indexers/Indexers/Indexer.js | 10 ++ .../Settings/Indexers/Indexers/Indexers.js | 3 + .../Indexers/Indexers/IndexersConnector.js | 9 +- .../Tags/Details/TagDetailsModalContent.js | 43 ++++-- .../TagDetailsModalContentConnector.js | 14 +- frontend/src/Settings/Tags/Tag.js | 37 +++-- frontend/src/Settings/Tags/TagsConnector.js | 12 +- src/Lidarr.Api.V1/Tags/TagDetailsResource.cs | 2 + .../RssSync/IndexerTagSpecificationFixture.cs | 136 ++++++++++++++++++ .../IndexerTests/IndexerServiceFixture.cs | 6 +- .../OmgwtfnzbsTests/OmgwtfnzbsFixture.cs | 55 ------- .../WafflesTests/WafflesFixture.cs | 56 -------- .../Datastore/Migration/059_indexer_tags.cs | 17 +++ src/NzbDrone.Core/Datastore/TableMapping.cs | 3 +- .../RssSync/IndexerTagSpecification.cs | 56 ++++++++ .../IndexerSearch/NzbSearchService.cs | 3 + src/NzbDrone.Core/Indexers/Newznab/Newznab.cs | 1 - .../Indexers/Omgwtfnzbs/Omgwtfnzbs.cs | 29 ---- .../Omgwtfnzbs/OmgwtfnzbsRequestGenerator.cs | 65 --------- .../Omgwtfnzbs/OmgwtfnzbsRssParser.cs | 50 ------- .../Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs | 46 ------ src/NzbDrone.Core/Indexers/Waffles/Waffles.cs | 30 ---- .../Waffles/WafflesRequestGenerator.cs | 63 -------- .../Indexers/Waffles/WafflesRssParser.cs | 93 ------------ .../Indexers/Waffles/WafflesSettings.cs | 50 ------- src/NzbDrone.Core/Localization/Core/en.json | 6 +- src/NzbDrone.Core/Tags/TagDetails.cs | 3 +- src/NzbDrone.Core/Tags/TagService.cs | 14 +- 29 files changed, 351 insertions(+), 578 deletions(-) create mode 100644 src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/IndexerTagSpecificationFixture.cs delete mode 100644 src/NzbDrone.Core.Test/IndexerTests/OmgwtfnzbsTests/OmgwtfnzbsFixture.cs delete mode 100644 src/NzbDrone.Core.Test/IndexerTests/WafflesTests/WafflesFixture.cs create mode 100644 src/NzbDrone.Core/Datastore/Migration/059_indexer_tags.cs create mode 100644 src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/IndexerTagSpecification.cs delete mode 100644 src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs delete mode 100644 src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRequestGenerator.cs delete mode 100644 src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRssParser.cs delete mode 100644 src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs delete mode 100644 src/NzbDrone.Core/Indexers/Waffles/Waffles.cs delete mode 100644 src/NzbDrone.Core/Indexers/Waffles/WafflesRequestGenerator.cs delete mode 100644 src/NzbDrone.Core/Indexers/Waffles/WafflesRssParser.cs delete mode 100644 src/NzbDrone.Core/Indexers/Waffles/WafflesSettings.cs diff --git a/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js b/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js index 112c06948..3ddd0223c 100644 --- a/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js +++ b/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js @@ -43,6 +43,7 @@ function EditIndexerModalContent(props) { enableInteractiveSearch, supportsRss, supportsSearch, + tags, fields, priority, protocol, @@ -168,18 +169,30 @@ function EditIndexerModalContent(props) { advancedSettings={advancedSettings} isAdvanced={true} > - DownloadClient + {translate('DownloadClient')} + + + {translate('Tags')} + + + } diff --git a/frontend/src/Settings/Indexers/Indexers/Indexer.js b/frontend/src/Settings/Indexers/Indexers/Indexer.js index 93e8d2d57..05198dcf8 100644 --- a/frontend/src/Settings/Indexers/Indexers/Indexer.js +++ b/frontend/src/Settings/Indexers/Indexers/Indexer.js @@ -4,6 +4,7 @@ import Card from 'Components/Card'; import Label from 'Components/Label'; import IconButton from 'Components/Link/IconButton'; import ConfirmModal from 'Components/Modal/ConfirmModal'; +import TagList from 'Components/TagList'; import { icons, kinds } from 'Helpers/Props'; import translate from 'Utilities/String/translate'; import EditIndexerModalConnector from './EditIndexerModalConnector'; @@ -68,6 +69,8 @@ class Indexer extends Component { enableRss, enableAutomaticSearch, enableInteractiveSearch, + tags, + tagList, supportsRss, supportsSearch, priority, @@ -133,6 +136,11 @@ class Indexer extends Component { } + + indexers + createTagsSelector(), + (indexers, tagList) => { + return { + ...indexers, + tagList + }; + } ); } diff --git a/frontend/src/Settings/Tags/Details/TagDetailsModalContent.js b/frontend/src/Settings/Tags/Details/TagDetailsModalContent.js index a8a08d719..0168d9ae3 100644 --- a/frontend/src/Settings/Tags/Details/TagDetailsModalContent.js +++ b/frontend/src/Settings/Tags/Details/TagDetailsModalContent.js @@ -22,6 +22,7 @@ function TagDetailsModalContent(props) { importLists, notifications, releaseProfiles, + indexers, onModalClose, onDeleteTagPress } = props; @@ -41,7 +42,7 @@ function TagDetailsModalContent(props) { } { - !!artist.length && + artist.length ?
{ artist.map((item) => { @@ -52,11 +53,12 @@ function TagDetailsModalContent(props) { ); }) } -
+ : + null } { - !!delayProfiles.length && + delayProfiles.length ?
{ delayProfiles.map((item) => { @@ -81,11 +83,12 @@ function TagDetailsModalContent(props) { ); }) } -
+ : + null } { - !!notifications.length && + notifications.length ?
{ notifications.map((item) => { @@ -96,11 +99,12 @@ function TagDetailsModalContent(props) { ); }) } -
+ : + null } { - !!importLists.length && + importLists.length ?
{ importLists.map((item) => { @@ -111,11 +115,12 @@ function TagDetailsModalContent(props) { ); }) } -
+ : + null } { - !!releaseProfiles.length && + releaseProfiles.length ?
{ releaseProfiles.map((item) => { @@ -157,7 +162,24 @@ function TagDetailsModalContent(props) { ); }) } -
+ : + null + } + + { + indexers.length ? +
+ { + indexers.map((item) => { + return ( +
+ {item.name} +
+ ); + }) + } +
: + null } @@ -192,6 +214,7 @@ TagDetailsModalContent.propTypes = { importLists: PropTypes.arrayOf(PropTypes.object).isRequired, notifications: PropTypes.arrayOf(PropTypes.object).isRequired, releaseProfiles: PropTypes.arrayOf(PropTypes.object).isRequired, + indexers: PropTypes.arrayOf(PropTypes.object).isRequired, onModalClose: PropTypes.func.isRequired, onDeleteTagPress: PropTypes.func.isRequired }; diff --git a/frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js b/frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js index 39c68d5f9..5a92c89ed 100644 --- a/frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js +++ b/frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js @@ -69,6 +69,14 @@ function createMatchingReleaseProfilesSelector() { ); } +function createMatchingIndexersSelector() { + return createSelector( + (state, { indexerIds }) => indexerIds, + (state) => state.settings.indexers.items, + findMatchingItems + ); +} + function createMapStateToProps() { return createSelector( createMatchingArtistSelector(), @@ -76,13 +84,15 @@ function createMapStateToProps() { createMatchingImportListsSelector(), createMatchingNotificationsSelector(), createMatchingReleaseProfilesSelector(), - (artist, delayProfiles, importLists, notifications, releaseProfiles) => { + createMatchingIndexersSelector(), + (artist, delayProfiles, importLists, notifications, releaseProfiles, indexers) => { return { artist, delayProfiles, importLists, notifications, - releaseProfiles + releaseProfiles, + indexers }; } ); diff --git a/frontend/src/Settings/Tags/Tag.js b/frontend/src/Settings/Tags/Tag.js index a73175ed2..983cb6637 100644 --- a/frontend/src/Settings/Tags/Tag.js +++ b/frontend/src/Settings/Tags/Tag.js @@ -57,6 +57,7 @@ class Tag extends Component { importListIds, notificationIds, restrictionIds, + indexerIds, artistIds } = this.props; @@ -70,6 +71,7 @@ class Tag extends Component { importListIds.length || notificationIds.length || restrictionIds.length || + indexerIds.length || artistIds.length ); @@ -87,38 +89,50 @@ class Tag extends Component { isTagUsed &&
{ - !!artistIds.length && + artistIds.length ?
{artistIds.length} artists -
+
: + null } { - !!delayProfileIds.length && + delayProfileIds.length ?
{delayProfileIds.length} delay profile{delayProfileIds.length > 1 && 's'} -
+ : + null } { - !!importListIds.length && + importListIds.length ?
{importListIds.length} import list{importListIds.length > 1 && 's'} -
+ : + null } { - !!notificationIds.length && + notificationIds.length ?
{notificationIds.length} connection{notificationIds.length > 1 && 's'} -
+ : + null } { - !!restrictionIds.length && + restrictionIds.length ?
{restrictionIds.length} restriction{restrictionIds.length > 1 && 's'} -
+ : + null + } + { + indexerIds.length ? +
+ {indexerIds.length} indexer{indexerIds.length > 1 && 's'} +
: + null } } @@ -138,6 +152,7 @@ class Tag extends Component { importListIds={importListIds} notificationIds={notificationIds} restrictionIds={restrictionIds} + indexerIds={indexerIds} isOpen={isDetailsModalOpen} onModalClose={this.onDetailsModalClose} onDeleteTagPress={this.onDeleteTagPress} @@ -164,6 +179,7 @@ Tag.propTypes = { importListIds: PropTypes.arrayOf(PropTypes.number).isRequired, notificationIds: PropTypes.arrayOf(PropTypes.number).isRequired, restrictionIds: PropTypes.arrayOf(PropTypes.number).isRequired, + indexerIds: PropTypes.arrayOf(PropTypes.number).isRequired, artistIds: PropTypes.arrayOf(PropTypes.number).isRequired, onConfirmDeleteTag: PropTypes.func.isRequired }; @@ -173,6 +189,7 @@ Tag.defaultProps = { importListIds: [], notificationIds: [], restrictionIds: [], + indexerIds: [], artistIds: [] }; diff --git a/frontend/src/Settings/Tags/TagsConnector.js b/frontend/src/Settings/Tags/TagsConnector.js index bbfa5d27e..241ee260a 100644 --- a/frontend/src/Settings/Tags/TagsConnector.js +++ b/frontend/src/Settings/Tags/TagsConnector.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { fetchDelayProfiles, fetchImportLists, fetchNotifications, fetchReleaseProfiles } from 'Store/Actions/settingsActions'; +import { fetchDelayProfiles, fetchImportLists, fetchIndexers, fetchNotifications, fetchReleaseProfiles } from 'Store/Actions/settingsActions'; import { fetchTagDetails } from 'Store/Actions/tagActions'; import Tags from './Tags'; @@ -29,7 +29,8 @@ const mapDispatchToProps = { dispatchFetchDelayProfiles: fetchDelayProfiles, dispatchFetchImportLists: fetchImportLists, dispatchFetchNotifications: fetchNotifications, - dispatchFetchReleaseProfiles: fetchReleaseProfiles + dispatchFetchReleaseProfiles: fetchReleaseProfiles, + dispatchFetchIndexers: fetchIndexers }; class MetadatasConnector extends Component { @@ -43,7 +44,8 @@ class MetadatasConnector extends Component { dispatchFetchDelayProfiles, dispatchFetchImportLists, dispatchFetchNotifications, - dispatchFetchReleaseProfiles + dispatchFetchReleaseProfiles, + dispatchFetchIndexers } = this.props; dispatchFetchTagDetails(); @@ -51,6 +53,7 @@ class MetadatasConnector extends Component { dispatchFetchImportLists(); dispatchFetchNotifications(); dispatchFetchReleaseProfiles(); + dispatchFetchIndexers(); } // @@ -70,7 +73,8 @@ MetadatasConnector.propTypes = { dispatchFetchDelayProfiles: PropTypes.func.isRequired, dispatchFetchImportLists: PropTypes.func.isRequired, dispatchFetchNotifications: PropTypes.func.isRequired, - dispatchFetchReleaseProfiles: PropTypes.func.isRequired + dispatchFetchReleaseProfiles: PropTypes.func.isRequired, + dispatchFetchIndexers: PropTypes.func.isRequired }; export default connect(createMapStateToProps, mapDispatchToProps)(MetadatasConnector); diff --git a/src/Lidarr.Api.V1/Tags/TagDetailsResource.cs b/src/Lidarr.Api.V1/Tags/TagDetailsResource.cs index c22b4e07e..1f59e47b0 100644 --- a/src/Lidarr.Api.V1/Tags/TagDetailsResource.cs +++ b/src/Lidarr.Api.V1/Tags/TagDetailsResource.cs @@ -12,6 +12,7 @@ namespace Lidarr.Api.V1.Tags public List ImportListIds { get; set; } public List NotificationIds { get; set; } public List RestrictionIds { get; set; } + public List IndexerIds { get; set; } public List ArtistIds { get; set; } } @@ -32,6 +33,7 @@ namespace Lidarr.Api.V1.Tags ImportListIds = model.ImportListIds, NotificationIds = model.NotificationIds, RestrictionIds = model.RestrictionIds, + IndexerIds = model.IndexerIds, ArtistIds = model.ArtistIds }; } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/IndexerTagSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/IndexerTagSpecificationFixture.cs new file mode 100644 index 000000000..135409b88 --- /dev/null +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/IndexerTagSpecificationFixture.cs @@ -0,0 +1,136 @@ +using System.Collections.Generic; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.DecisionEngine.Specifications.RssSync; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Music; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync +{ + [TestFixture] + public class IndexerTagSpecificationFixture : CoreTest + { + private IndexerTagSpecification _specification; + + private RemoteAlbum _parseResultMulti; + private IndexerDefinition _fakeIndexerDefinition; + private Artist _fakeArtist; + private Album _firstAlbum; + private Album _secondAlbum; + private ReleaseInfo _fakeRelease; + + [SetUp] + public void Setup() + { + _fakeIndexerDefinition = new IndexerDefinition + { + Tags = new HashSet() + }; + + Mocker + .GetMock() + .Setup(m => m.Get(It.IsAny())) + .Throws(new ModelNotFoundException(typeof(IndexerDefinition), -1)); + + Mocker + .GetMock() + .Setup(m => m.Get(1)) + .Returns(_fakeIndexerDefinition); + + _specification = Mocker.Resolve(); + + _fakeArtist = Builder.CreateNew() + .With(c => c.Monitored = true) + .With(c => c.Tags = new HashSet()) + .Build(); + + _fakeRelease = new ReleaseInfo + { + IndexerId = 1 + }; + + _firstAlbum = new Album { Monitored = true }; + _secondAlbum = new Album { Monitored = true }; + + var doubleEpisodeList = new List { _firstAlbum, _secondAlbum }; + + _parseResultMulti = new RemoteAlbum + { + Artist = _fakeArtist, + Albums = doubleEpisodeList, + Release = _fakeRelease + }; + } + + [Test] + public void indexer_and_series_without_tags_should_return_true() + { + _fakeIndexerDefinition.Tags = new HashSet(); + _fakeArtist.Tags = new HashSet(); + + _specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeTrue(); + } + + [Test] + public void indexer_with_tags_series_without_tags_should_return_false() + { + _fakeIndexerDefinition.Tags = new HashSet { 123 }; + _fakeArtist.Tags = new HashSet(); + + _specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeFalse(); + } + + [Test] + public void indexer_without_tags_series_with_tags_should_return_true() + { + _fakeIndexerDefinition.Tags = new HashSet(); + _fakeArtist.Tags = new HashSet { 123 }; + + _specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeTrue(); + } + + [Test] + public void indexer_with_tags_series_with_matching_tags_should_return_true() + { + _fakeIndexerDefinition.Tags = new HashSet { 123, 456 }; + _fakeArtist.Tags = new HashSet { 123, 789 }; + + _specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeTrue(); + } + + [Test] + public void indexer_with_tags_series_with_different_tags_should_return_false() + { + _fakeIndexerDefinition.Tags = new HashSet { 456 }; + _fakeArtist.Tags = new HashSet { 123, 789 }; + + _specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeFalse(); + } + + [Test] + public void release_without_indexerid_should_return_true() + { + _fakeIndexerDefinition.Tags = new HashSet { 456 }; + _fakeArtist.Tags = new HashSet { 123, 789 }; + _fakeRelease.IndexerId = 0; + + _specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeTrue(); + } + + [Test] + public void release_with_invalid_indexerid_should_return_true() + { + _fakeIndexerDefinition.Tags = new HashSet { 456 }; + _fakeArtist.Tags = new HashSet { 123, 789 }; + _fakeRelease.IndexerId = 2; + + _specification.IsSatisfiedBy(_parseResultMulti, new AlbumSearchCriteria { MonitoredEpisodesOnly = true }).Accepted.Should().BeTrue(); + } + } +} diff --git a/src/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs index 4cc12f77b..4ade676d9 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; +using System.Collections.Generic; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.FileList; using NzbDrone.Core.Indexers.Newznab; -using NzbDrone.Core.Indexers.Omgwtfnzbs; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Test.Framework; @@ -20,7 +20,7 @@ namespace NzbDrone.Core.Test.IndexerTests _indexers = new List(); _indexers.Add(Mocker.Resolve()); - _indexers.Add(Mocker.Resolve()); + _indexers.Add(Mocker.Resolve()); Mocker.SetConstant>(_indexers); } diff --git a/src/NzbDrone.Core.Test/IndexerTests/OmgwtfnzbsTests/OmgwtfnzbsFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/OmgwtfnzbsTests/OmgwtfnzbsFixture.cs deleted file mode 100644 index 1b19fd7f1..000000000 --- a/src/NzbDrone.Core.Test/IndexerTests/OmgwtfnzbsTests/OmgwtfnzbsFixture.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Linq; -using FluentAssertions; -using Moq; -using NUnit.Framework; -using NzbDrone.Common.Http; -using NzbDrone.Core.Indexers; -using NzbDrone.Core.Indexers.Omgwtfnzbs; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.IndexerTests.OmgwtfnzbsTests -{ - [TestFixture] - public class OmgwtfnzbsFixture : CoreTest - { - [SetUp] - public void Setup() - { - Subject.Definition = new IndexerDefinition() - { - Name = "Omgwtfnzbs", - Settings = new OmgwtfnzbsSettings() - { - ApiKey = "xxx", - Username = "me@my.domain" - } - }; - } - - [Test] - public void should_parse_recent_feed_from_omgwtfnzbs() - { - var recentFeed = ReadAllText(@"Files/Indexers/Omgwtfnzbs/Omgwtfnzbs.xml"); - - Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) - .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); - - var releases = Subject.FetchRecent(); - - releases.Should().HaveCount(100); - - var releaseInfo = releases.First(); - - releaseInfo.Title.Should().Be("Stephen.Fry.Gadget.Man.S01E05.HDTV.x264-C4TV"); - releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Usenet); - releaseInfo.DownloadUrl.Should().Be("http://api.omgwtfnzbs.org/sn.php?id=OAl4g&user=nzbdrone&api=nzbdrone"); - releaseInfo.InfoUrl.Should().Be("http://omgwtfnzbs.org/details.php?id=OAl4g"); - releaseInfo.CommentUrl.Should().BeNullOrEmpty(); - releaseInfo.Indexer.Should().Be(Subject.Definition.Name); - releaseInfo.PublishDate.Should().Be(DateTime.Parse("2012/12/17 23:30:13")); - releaseInfo.Size.Should().Be(236822906); - } - } -} diff --git a/src/NzbDrone.Core.Test/IndexerTests/WafflesTests/WafflesFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/WafflesTests/WafflesFixture.cs deleted file mode 100644 index eb313b1ef..000000000 --- a/src/NzbDrone.Core.Test/IndexerTests/WafflesTests/WafflesFixture.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Linq; -using FluentAssertions; -using Moq; -using NUnit.Framework; -using NzbDrone.Common.Http; -using NzbDrone.Core.Indexers; -using NzbDrone.Core.Indexers.Waffles; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.IndexerTests.WafflesTests -{ - [TestFixture] - public class WafflesFixture : CoreTest - { - [SetUp] - public void Setup() - { - Subject.Definition = new IndexerDefinition() - { - Name = "Waffles", - Settings = new WafflesSettings() - { - UserId = "xxx", - RssPasskey = "123456789" - } - }; - } - - [Test] - public void should_parse_recent_feed_from_waffles() - { - var recentFeed = ReadAllText(@"Files/Indexers/Waffles/waffles.xml"); - - Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) - .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); - - var releases = Subject.FetchRecent(); - - releases.Should().HaveCount(15); - - var releaseInfo = releases.First(); - - releaseInfo.Title.Should().Be("Coldplay - Kaleidoscope EP (FLAC HD) [2017-Web-FLAC-Lossless]"); - releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent); - releaseInfo.DownloadUrl.Should().Be("https://waffles.ch/download.php/xxx/1166992/" + - "Coldplay%20-%20Kaleidoscope%20EP%20%28FLAC%20HD%29%20%5B2017-Web-FLAC-Lossless%5D.torrent?passkey=123456789&uid=xxx&rss=1"); - releaseInfo.InfoUrl.Should().Be("https://waffles.ch/details.php?id=1166992&hit=1"); - releaseInfo.CommentUrl.Should().Be("https://waffles.ch/details.php?id=1166992&hit=1"); - releaseInfo.Indexer.Should().Be(Subject.Definition.Name); - releaseInfo.PublishDate.Should().Be(DateTime.Parse("2017-07-16 09:51:54")); - releaseInfo.Size.Should().Be(552668227); - } - } -} diff --git a/src/NzbDrone.Core/Datastore/Migration/059_indexer_tags.cs b/src/NzbDrone.Core/Datastore/Migration/059_indexer_tags.cs new file mode 100644 index 000000000..e45340a3c --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/059_indexer_tags.cs @@ -0,0 +1,17 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(059)] + public class add_indexer_tags : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Execute.Sql("DELETE FROM Indexers WHERE Implementation = 'Omgwtfnzbs'"); + Execute.Sql("DELETE FROM Indexers WHERE Implementation = 'Waffles'"); + + Alter.Table("Indexers").AddColumn("Tags").AsString().Nullable(); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index f1554484d..096ec53b1 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -68,8 +68,7 @@ namespace NzbDrone.Core.Datastore .Ignore(i => i.Enable) .Ignore(i => i.Protocol) .Ignore(i => i.SupportsRss) - .Ignore(i => i.SupportsSearch) - .Ignore(d => d.Tags); + .Ignore(i => i.SupportsSearch); Mapper.Entity("ImportLists").RegisterModel() .Ignore(x => x.ImplementationName) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/IndexerTagSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/IndexerTagSpecification.cs new file mode 100644 index 000000000..68d6b0277 --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/IndexerTagSpecification.cs @@ -0,0 +1,56 @@ +using System.Linq; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync +{ + public class IndexerTagSpecification : IDecisionEngineSpecification + { + private readonly Logger _logger; + private readonly IIndexerFactory _indexerFactory; + + public IndexerTagSpecification(Logger logger, IIndexerFactory indexerFactory) + { + _logger = logger; + _indexerFactory = indexerFactory; + } + + public SpecificationPriority Priority => SpecificationPriority.Default; + public RejectionType Type => RejectionType.Permanent; + + public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) + { + if (subject.Release == null || subject.Artist?.Tags == null || subject.Release.IndexerId == 0) + { + return Decision.Accept(); + } + + IndexerDefinition indexer; + try + { + indexer = _indexerFactory.Get(subject.Release.IndexerId); + } + catch (ModelNotFoundException) + { + _logger.Debug("Indexer with id {0} does not exist, skipping indexer tags check", subject.Release.IndexerId); + return Decision.Accept(); + } + + // If indexer has tags, check that at least one of them is present on the series + var indexerTags = indexer.Tags; + + if (indexerTags.Any() && indexerTags.Intersect(subject.Artist.Tags).Empty()) + { + _logger.Debug("Indexer {0} has tags. None of these are present on artist {1}. Rejecting", subject.Release.Indexer, subject.Artist); + + return Decision.Reject("Artist tags do not match any of the indexer tags"); + } + + return Decision.Accept(); + } + } +} diff --git a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs index 89ff4e779..a3c3644af 100644 --- a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs @@ -125,6 +125,9 @@ namespace NzbDrone.Core.IndexerSearch _indexerFactory.InteractiveSearchEnabled() : _indexerFactory.AutomaticSearchEnabled(); + // Filter indexers to untagged indexers and indexers with intersecting tags + indexers = indexers.Where(i => i.Definition.Tags.Empty() || i.Definition.Tags.Intersect(criteriaBase.Artist.Tags).Any()).ToList(); + var reports = new List(); _logger.ProgressInfo("Searching indexers for {0}. {1} active indexers", criteriaBase, indexers.Count); diff --git a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs index a0109a5fe..5e49f5b18 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs @@ -47,7 +47,6 @@ namespace NzbDrone.Core.Indexers.Newznab yield return GetDefinition("NZBFinder.ws", GetSettings("https://nzbfinder.ws")); yield return GetDefinition("NZBgeek", GetSettings("https://api.nzbgeek.info")); yield return GetDefinition("nzbplanet.net", GetSettings("https://api.nzbplanet.net")); - yield return GetDefinition("omgwtfnzbs", GetSettings("https://api.omgwtfnzbs.me")); yield return GetDefinition("OZnzb.com", GetSettings("https://api.oznzb.com")); yield return GetDefinition("SimplyNZBs", GetSettings("https://simplynzbs.com")); yield return GetDefinition("Tabula Rasa", GetSettings("https://www.tabula-rasa.pw", apiPath: @"/api/v1/api")); diff --git a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs deleted file mode 100644 index e4f1bb56b..000000000 --- a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs +++ /dev/null @@ -1,29 +0,0 @@ -using NLog; -using NzbDrone.Common.Http; -using NzbDrone.Core.Configuration; -using NzbDrone.Core.Parser; - -namespace NzbDrone.Core.Indexers.Omgwtfnzbs -{ - public class Omgwtfnzbs : HttpIndexerBase - { - public override string Name => "omgwtfnzbs"; - - public override DownloadProtocol Protocol => DownloadProtocol.Usenet; - - public Omgwtfnzbs(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) - : base(httpClient, indexerStatusService, configService, parsingService, logger) - { - } - - public override IIndexerRequestGenerator GetRequestGenerator() - { - return new OmgwtfnzbsRequestGenerator() { Settings = Settings }; - } - - public override IParseIndexerResponse GetParser() - { - return new OmgwtfnzbsRssParser(); - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRequestGenerator.cs deleted file mode 100644 index 80e73e276..000000000 --- a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRequestGenerator.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Collections.Generic; -using System.Text; -using NzbDrone.Common.Extensions; -using NzbDrone.Common.Http; -using NzbDrone.Core.IndexerSearch.Definitions; - -namespace NzbDrone.Core.Indexers.Omgwtfnzbs -{ - public class OmgwtfnzbsRequestGenerator : IIndexerRequestGenerator - { - public string BaseUrl { get; set; } - public OmgwtfnzbsSettings Settings { get; set; } - - public OmgwtfnzbsRequestGenerator() - { - BaseUrl = "https://rss.omgwtfnzbs.me/rss-download.php"; - } - - public virtual IndexerPageableRequestChain GetRecentRequests() - { - var pageableRequests = new IndexerPageableRequestChain(); - - pageableRequests.Add(GetPagedRequests(null)); - - return pageableRequests; - } - - public virtual IndexerPageableRequestChain GetSearchRequests(AlbumSearchCriteria searchCriteria) - { - var pageableRequests = new IndexerPageableRequestChain(); - - pageableRequests.Add(GetPagedRequests(string.Format("{0}+{1}", - searchCriteria.ArtistQuery, - searchCriteria.AlbumQuery))); - - return pageableRequests; - } - - public virtual IndexerPageableRequestChain GetSearchRequests(ArtistSearchCriteria searchCriteria) - { - var pageableRequests = new IndexerPageableRequestChain(); - - pageableRequests.Add(GetPagedRequests(string.Format("{0}", - searchCriteria.ArtistQuery))); - - return pageableRequests; - } - - private IEnumerable GetPagedRequests(string query) - { - var url = new StringBuilder(); - - // Category 22 is Music-FLAC, category 7 is Music-MP3 - url.AppendFormat("{0}?catid=22,7&user={1}&api={2}&eng=1&delay={3}", BaseUrl, Settings.Username, Settings.ApiKey, Settings.Delay); - - if (query.IsNotNullOrWhiteSpace()) - { - url = url.Replace("rss-download.php", "rss-search.php"); - url.AppendFormat("&search={0}", query); - } - - yield return new IndexerRequest(url.ToString(), HttpAccept.Rss); - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRssParser.cs b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRssParser.cs deleted file mode 100644 index 5ff9174e3..000000000 --- a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRssParser.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Linq; -using System.Text.RegularExpressions; -using System.Xml.Linq; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.Indexers.Exceptions; - -namespace NzbDrone.Core.Indexers.Omgwtfnzbs -{ - public class OmgwtfnzbsRssParser : RssParser - { - public OmgwtfnzbsRssParser() - { - UseEnclosureUrl = true; - UseEnclosureLength = true; - } - - protected override bool PreProcess(IndexerResponse indexerResponse) - { - var xdoc = LoadXmlDocument(indexerResponse); - var notice = xdoc.Descendants("notice").FirstOrDefault(); - - if (notice == null) - { - return true; - } - - if (!notice.Value.ContainsIgnoreCase("api")) - { - return true; - } - - throw new ApiKeyException(notice.Value); - } - - protected override string GetInfoUrl(XElement item) - { - //Todo: Me thinks I need to parse details to get this... - var match = Regex.Match(item.Description(), - @"(?:\View NZB\:\<\/b\>\s\.+)(?:\""\starget)", - RegexOptions.IgnoreCase | RegexOptions.Compiled); - - if (match.Success) - { - return match.Groups["URL"].Value; - } - - return string.Empty; - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs deleted file mode 100644 index ff5eeb02e..000000000 --- a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs +++ /dev/null @@ -1,46 +0,0 @@ -using FluentValidation; -using NzbDrone.Core.Annotations; -using NzbDrone.Core.Validation; - -namespace NzbDrone.Core.Indexers.Omgwtfnzbs -{ - public class OmgwtfnzbsSettingsValidator : AbstractValidator - { - public OmgwtfnzbsSettingsValidator() - { - RuleFor(c => c.Username).NotEmpty(); - RuleFor(c => c.ApiKey).NotEmpty(); - RuleFor(c => c.Delay).GreaterThanOrEqualTo(0); - } - } - - public class OmgwtfnzbsSettings : IIndexerSettings - { - private static readonly OmgwtfnzbsSettingsValidator Validator = new OmgwtfnzbsSettingsValidator(); - - public OmgwtfnzbsSettings() - { - Delay = 30; - } - - // Unused since Omg has a hardcoded url. - public string BaseUrl { get; set; } - - [FieldDefinition(0, Label = "Username", Privacy = PrivacyLevel.UserName)] - public string Username { get; set; } - - [FieldDefinition(1, Label = "API Key", Privacy = PrivacyLevel.ApiKey)] - public string ApiKey { get; set; } - - [FieldDefinition(2, Label = "Delay", HelpText = "Time in minutes to delay new nzbs before they appear on the RSS feed", Advanced = true)] - public int Delay { get; set; } - - [FieldDefinition(3, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)] - public int? EarlyReleaseLimit { get; set; } - - public NzbDroneValidationResult Validate() - { - return new NzbDroneValidationResult(Validator.Validate(this)); - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Waffles/Waffles.cs b/src/NzbDrone.Core/Indexers/Waffles/Waffles.cs deleted file mode 100644 index 9f2377fb2..000000000 --- a/src/NzbDrone.Core/Indexers/Waffles/Waffles.cs +++ /dev/null @@ -1,30 +0,0 @@ -using NLog; -using NzbDrone.Common.Http; -using NzbDrone.Core.Configuration; -using NzbDrone.Core.Parser; - -namespace NzbDrone.Core.Indexers.Waffles -{ - public class Waffles : HttpIndexerBase - { - public override string Name => "Waffles"; - - public override DownloadProtocol Protocol => DownloadProtocol.Torrent; - public override int PageSize => 15; - - public Waffles(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) - : base(httpClient, indexerStatusService, configService, parsingService, logger) - { - } - - public override IIndexerRequestGenerator GetRequestGenerator() - { - return new WafflesRequestGenerator() { Settings = Settings }; - } - - public override IParseIndexerResponse GetParser() - { - return new WafflesRssParser() { ParseSizeInDescription = true, ParseSeedersInDescription = true }; - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Waffles/WafflesRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Waffles/WafflesRequestGenerator.cs deleted file mode 100644 index fa39d562b..000000000 --- a/src/NzbDrone.Core/Indexers/Waffles/WafflesRequestGenerator.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Collections.Generic; -using System.Text; -using NzbDrone.Common.Extensions; -using NzbDrone.Common.Http; -using NzbDrone.Core.IndexerSearch.Definitions; - -namespace NzbDrone.Core.Indexers.Waffles -{ - public class WafflesRequestGenerator : IIndexerRequestGenerator - { - public WafflesSettings Settings { get; set; } - public int MaxPages { get; set; } - - public WafflesRequestGenerator() - { - MaxPages = 5; - } - - public virtual IndexerPageableRequestChain GetRecentRequests() - { - var pageableRequests = new IndexerPageableRequestChain(); - - pageableRequests.Add(GetPagedRequests(MaxPages, null)); - - return pageableRequests; - } - - public IndexerPageableRequestChain GetSearchRequests(AlbumSearchCriteria searchCriteria) - { - var pageableRequests = new IndexerPageableRequestChain(); - - pageableRequests.Add(GetPagedRequests(MaxPages, string.Format("&q=artist:{0} album:{1}", searchCriteria.ArtistQuery, searchCriteria.AlbumQuery))); - - return pageableRequests; - } - - public IndexerPageableRequestChain GetSearchRequests(ArtistSearchCriteria searchCriteria) - { - var pageableRequests = new IndexerPageableRequestChain(); - - pageableRequests.Add(GetPagedRequests(MaxPages, string.Format("&q=artist:{0}", searchCriteria.ArtistQuery))); - - return pageableRequests; - } - - private IEnumerable GetPagedRequests(int maxPages, string query) - { - var url = new StringBuilder(); - - url.AppendFormat("{0}/browse.php?rss=1&c0=1&uid={1}&passkey={2}", Settings.BaseUrl.Trim().TrimEnd('/'), Settings.UserId, Settings.RssPasskey); - - if (query.IsNotNullOrWhiteSpace()) - { - url.AppendFormat(query); - } - - for (var page = 0; page < maxPages; page++) - { - yield return new IndexerRequest(string.Format("{0}&p={1}", url, page), HttpAccept.Rss); - } - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Waffles/WafflesRssParser.cs b/src/NzbDrone.Core/Indexers/Waffles/WafflesRssParser.cs deleted file mode 100644 index a5744d141..000000000 --- a/src/NzbDrone.Core/Indexers/Waffles/WafflesRssParser.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Text.RegularExpressions; -using System.Xml.Linq; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.Indexers.Exceptions; -using NzbDrone.Core.Parser.Model; - -namespace NzbDrone.Core.Indexers.Waffles -{ - public class WafflesRssParser : TorrentRssParser - { - public const string ns = "{http://purl.org/rss/1.0/}"; - public const string dc = "{http://purl.org/dc/elements/1.1/}"; - - protected override bool PreProcess(IndexerResponse indexerResponse) - { - var xdoc = LoadXmlDocument(indexerResponse); - var error = xdoc.Descendants("error").FirstOrDefault(); - - if (error == null) - { - return true; - } - - var code = Convert.ToInt32(error.Attribute("code").Value); - var errorMessage = error.Attribute("description").Value; - - if (code >= 100 && code <= 199) - { - throw new ApiKeyException("Invalid Pass key"); - } - - if (!indexerResponse.Request.Url.FullUri.Contains("passkey=") && errorMessage == "Missing parameter") - { - throw new ApiKeyException("Indexer requires an Pass key"); - } - - if (errorMessage == "Request limit reached") - { - throw new RequestLimitReachedException("API limit reached"); - } - - throw new IndexerException(indexerResponse, errorMessage); - } - - protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo) - { - var torrentInfo = base.ProcessItem(item, releaseInfo) as TorrentInfo; - - return torrentInfo; - } - - protected override string GetInfoUrl(XElement item) - { - return ParseUrl(item.TryGetValue("comments").TrimEnd("#comments")); - } - - protected override string GetCommentUrl(XElement item) - { - return ParseUrl(item.TryGetValue("comments")); - } - - private static readonly Regex ParseSizeRegex = new Regex(@"(?:Size: )(?\d+)<", - RegexOptions.IgnoreCase | RegexOptions.Compiled); - - protected override long GetSize(XElement item) - { - var match = ParseSizeRegex.Matches(item.Element("description").Value); - - if (match.Count != 0) - { - var value = decimal.Parse(Regex.Replace(match[0].Groups["value"].Value, "\\,", ""), CultureInfo.InvariantCulture); - return (long)value; - } - - return 0; - } - - protected override DateTime GetPublishDate(XElement item) - { - var dateString = item.TryGetValue(dc + "date"); - - if (dateString.IsNullOrWhiteSpace()) - { - throw new UnsupportedFeedException("Rss feed must have a pubDate element with a valid publish date."); - } - - return XElementExtensions.ParseDate(dateString); - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Waffles/WafflesSettings.cs b/src/NzbDrone.Core/Indexers/Waffles/WafflesSettings.cs deleted file mode 100644 index 410cc204c..000000000 --- a/src/NzbDrone.Core/Indexers/Waffles/WafflesSettings.cs +++ /dev/null @@ -1,50 +0,0 @@ -using FluentValidation; -using NzbDrone.Core.Annotations; -using NzbDrone.Core.Validation; - -namespace NzbDrone.Core.Indexers.Waffles -{ - public class WafflesSettingsValidator : AbstractValidator - { - public WafflesSettingsValidator() - { - RuleFor(c => c.BaseUrl).ValidRootUrl(); - RuleFor(c => c.UserId).NotEmpty(); - RuleFor(c => c.RssPasskey).NotEmpty(); - } - } - - public class WafflesSettings : ITorrentIndexerSettings - { - private static readonly WafflesSettingsValidator Validator = new WafflesSettingsValidator(); - - public WafflesSettings() - { - BaseUrl = "https://www.waffles.ch"; - MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; - } - - [FieldDefinition(0, Label = "Website URL")] - public string BaseUrl { get; set; } - - [FieldDefinition(1, Label = "UserId", Privacy = PrivacyLevel.UserName)] - public string UserId { get; set; } - - [FieldDefinition(2, Label = "RSS Passkey", Privacy = PrivacyLevel.ApiKey)] - public string RssPasskey { get; set; } - - [FieldDefinition(3, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)] - public int MinimumSeeders { get; set; } - - [FieldDefinition(4)] - public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings(); - - [FieldDefinition(5, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)] - public int? EarlyReleaseLimit { get; set; } - - public NzbDroneValidationResult Validate() - { - return new NzbDroneValidationResult(Validator.Validate(this)); - } - } -} diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index ca43a4485..230ce6453 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -269,6 +269,7 @@ "IncludeUnknownArtistItemsHelpText": "Show items without a artist in the queue, this could include removed artists, movies or anything else in Lidarr's category", "IncludeUnmonitored": "Include Unmonitored", "Indexer": "Indexer", + "IndexerDownloadClientHelpText": "Specify which download client is used for grabs from this indexer", "IndexerIdHelpText": "Specify what indexer the profile applies to", "IndexerIdHelpTextWarning": "Using a specific indexer with preferred words can lead to duplicate releases being grabbed", "IndexerIdvalue0IncludeInPreferredWordsRenamingFormat": "Include in {Preferred Words} renaming format", @@ -276,6 +277,7 @@ "IndexerPriority": "Indexer Priority", "Indexers": "Indexers", "IndexerSettings": "Indexer Settings", + "IndexerTagHelpText": "Only use this indexer for artist with at least one matching tag. Leave blank to use with all artists.", "InstanceName": "Instance Name", "InstanceNameHelpText": "Instance name in tab and for Syslog app name", "InteractiveSearch": "Interactive Search", @@ -321,8 +323,8 @@ "ManualImport": "Manual Import", "MarkAsFailed": "Mark as Failed", "MarkAsFailedMessageText": "Are you sure you want to mark '{0}' as failed?", - "MassAlbumsSearchWarning": "Are you sure you want to search for all '{0}' missing albums?", "MassAlbumsCutoffUnmetWarning": "Are you sure you want to search for all '{0}' Cutoff Unmet albums?", + "MassAlbumsSearchWarning": "Are you sure you want to search for all '{0}' missing albums?", "MaximumLimits": "Maximum Limits", "MaximumSize": "Maximum Size", "MaximumSizeHelpText": "Maximum size for a release to be grabbed in MB. Set to zero to set to unlimited.", @@ -550,9 +552,9 @@ "SetPermissionsLinuxHelpTextWarning": "If you're unsure what these settings do, do not alter them.", "Settings": "Settings", "ShortDateFormat": "Short Date Format", - "ShouldMonitorHelpText": "Monitor artists and albums added from this list", "ShouldMonitorExisting": "Monitor existing albums", "ShouldMonitorExistingHelpText": "Automatically monitor albums on this list which are already in Lidarr", + "ShouldMonitorHelpText": "Monitor artists and albums added from this list", "ShouldSearch": "Search for New Items", "ShouldSearchHelpText": "Search indexers for newly added items. Use with caution for large lists.", "ShowAlbumCount": "Show Album Count", diff --git a/src/NzbDrone.Core/Tags/TagDetails.cs b/src/NzbDrone.Core/Tags/TagDetails.cs index 1730ce760..f11fc802e 100644 --- a/src/NzbDrone.Core/Tags/TagDetails.cs +++ b/src/NzbDrone.Core/Tags/TagDetails.cs @@ -13,12 +13,13 @@ namespace NzbDrone.Core.Tags public List DelayProfileIds { get; set; } public List ImportListIds { get; set; } public List RootFolderIds { get; set; } + public List IndexerIds { get; set; } public bool InUse { get { - return ArtistIds.Any() || NotificationIds.Any() || RestrictionIds.Any() || DelayProfileIds.Any() || ImportListIds.Any() || RootFolderIds.Any(); + return ArtistIds.Any() || NotificationIds.Any() || RestrictionIds.Any() || DelayProfileIds.Any() || ImportListIds.Any() || RootFolderIds.Any() || IndexerIds.Any(); } } } diff --git a/src/NzbDrone.Core/Tags/TagService.cs b/src/NzbDrone.Core/Tags/TagService.cs index 7ef08df3c..328e3f251 100644 --- a/src/NzbDrone.Core/Tags/TagService.cs +++ b/src/NzbDrone.Core/Tags/TagService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NzbDrone.Core.Datastore; using NzbDrone.Core.ImportLists; +using NzbDrone.Core.Indexers; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Music; using NzbDrone.Core.Notifications; @@ -33,6 +34,7 @@ namespace NzbDrone.Core.Tags private readonly IReleaseProfileService _releaseProfileService; private readonly IArtistService _artistService; private readonly IRootFolderService _rootFolderService; + private readonly IIndexerFactory _indexerService; public TagService(ITagRepository repo, IEventAggregator eventAggregator, @@ -41,7 +43,8 @@ namespace NzbDrone.Core.Tags INotificationFactory notificationFactory, IReleaseProfileService releaseProfileService, IArtistService artistService, - IRootFolderService rootFolderService) + IRootFolderService rootFolderService, + IIndexerFactory indexerService) { _repo = repo; _eventAggregator = eventAggregator; @@ -51,6 +54,7 @@ namespace NzbDrone.Core.Tags _releaseProfileService = releaseProfileService; _artistService = artistService; _rootFolderService = rootFolderService; + _indexerService = indexerService; } public Tag GetTag(int tagId) @@ -79,6 +83,7 @@ namespace NzbDrone.Core.Tags var restrictions = _releaseProfileService.AllForTag(tagId); var artist = _artistService.AllForTag(tagId); var rootFolders = _rootFolderService.AllForTag(tagId); + var indexers = _indexerService.AllForTag(tagId); return new TagDetails { @@ -89,7 +94,8 @@ namespace NzbDrone.Core.Tags NotificationIds = notifications.Select(c => c.Id).ToList(), RestrictionIds = restrictions.Select(c => c.Id).ToList(), ArtistIds = artist.Select(c => c.Id).ToList(), - RootFolderIds = rootFolders.Select(c => c.Id).ToList() + RootFolderIds = rootFolders.Select(c => c.Id).ToList(), + IndexerIds = indexers.Select(c => c.Id).ToList() }; } @@ -102,6 +108,7 @@ namespace NzbDrone.Core.Tags var restrictions = _releaseProfileService.All(); var artists = _artistService.GetAllArtists(); var rootFolders = _rootFolderService.All(); + var indexers = _indexerService.All(); var details = new List(); @@ -116,7 +123,8 @@ namespace NzbDrone.Core.Tags NotificationIds = notifications.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList(), RestrictionIds = restrictions.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList(), ArtistIds = artists.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList(), - RootFolderIds = rootFolders.Where(c => c.DefaultTags.Contains(tag.Id)).Select(c => c.Id).ToList() + RootFolderIds = rootFolders.Where(c => c.DefaultTags.Contains(tag.Id)).Select(c => c.Id).ToList(), + IndexerIds = indexers.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList() }); } From e6fb6b5033632fd93e2df8e202f4cc9fcc510dd1 Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 26 Sep 2022 14:25:46 -0500 Subject: [PATCH 0009/1478] Rename NzbSearchService to ReleaseSearchService Co-Authored-By: Mark McDowall --- src/Lidarr.Api.V1/Indexers/ReleaseController.cs | 10 +++++----- .../IndexerSearchTests/ArtistSearchServiceFixture.cs | 4 ++-- src/NzbDrone.Core/IndexerSearch/AlbumSearchService.cs | 10 +++++----- src/NzbDrone.Core/IndexerSearch/ArtistSearchService.cs | 8 ++++---- .../{NzbSearchService.cs => ReleaseSearchService.cs} | 6 +++--- 5 files changed, 19 insertions(+), 19 deletions(-) rename src/NzbDrone.Core/IndexerSearch/{NzbSearchService.cs => ReleaseSearchService.cs} (97%) diff --git a/src/Lidarr.Api.V1/Indexers/ReleaseController.cs b/src/Lidarr.Api.V1/Indexers/ReleaseController.cs index fd0ca6831..0a6428b08 100644 --- a/src/Lidarr.Api.V1/Indexers/ReleaseController.cs +++ b/src/Lidarr.Api.V1/Indexers/ReleaseController.cs @@ -26,7 +26,7 @@ namespace Lidarr.Api.V1.Indexers private readonly IAlbumService _albumService; private readonly IArtistService _artistService; private readonly IFetchAndParseRss _rssFetcherAndParser; - private readonly ISearchForNzb _nzbSearchService; + private readonly ISearchForReleases _releaseSearchService; private readonly IMakeDownloadDecision _downloadDecisionMaker; private readonly IPrioritizeDownloadDecision _prioritizeDownloadDecision; private readonly IParsingService _parsingService; @@ -38,7 +38,7 @@ namespace Lidarr.Api.V1.Indexers public ReleaseController(IAlbumService albumService, IArtistService artistService, IFetchAndParseRss rssFetcherAndParser, - ISearchForNzb nzbSearchService, + ISearchForReleases nzbSearchService, IMakeDownloadDecision downloadDecisionMaker, IPrioritizeDownloadDecision prioritizeDownloadDecision, IParsingService parsingService, @@ -51,7 +51,7 @@ namespace Lidarr.Api.V1.Indexers _albumService = albumService; _artistService = artistService; _rssFetcherAndParser = rssFetcherAndParser; - _nzbSearchService = nzbSearchService; + _releaseSearchService = nzbSearchService; _downloadDecisionMaker = downloadDecisionMaker; _prioritizeDownloadDecision = prioritizeDownloadDecision; _parsingService = parsingService; @@ -157,7 +157,7 @@ namespace Lidarr.Api.V1.Indexers { try { - var decisions = _nzbSearchService.AlbumSearch(albumId, true, true, true); + var decisions = _releaseSearchService.AlbumSearch(albumId, true, true, true); var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions); return MapDecisions(prioritizedDecisions); @@ -173,7 +173,7 @@ namespace Lidarr.Api.V1.Indexers { try { - var decisions = _nzbSearchService.ArtistSearch(artistId, false, true, true); + var decisions = _releaseSearchService.ArtistSearch(artistId, false, true, true); var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions); return MapDecisions(prioritizedDecisions); diff --git a/src/NzbDrone.Core.Test/IndexerSearchTests/ArtistSearchServiceFixture.cs b/src/NzbDrone.Core.Test/IndexerSearchTests/ArtistSearchServiceFixture.cs index bcad5015a..edbb71f42 100644 --- a/src/NzbDrone.Core.Test/IndexerSearchTests/ArtistSearchServiceFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerSearchTests/ArtistSearchServiceFixture.cs @@ -25,7 +25,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests .Setup(s => s.GetArtist(It.IsAny())) .Returns(_artist); - Mocker.GetMock() + Mocker.GetMock() .Setup(s => s.ArtistSearch(_artist.Id, false, true, false)) .Returns(new List()); @@ -45,7 +45,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests Subject.Execute(new ArtistSearchCommand { ArtistId = _artist.Id, Trigger = CommandTrigger.Manual }); - Mocker.GetMock() + Mocker.GetMock() .Verify(v => v.ArtistSearch(_artist.Id, false, true, false), Times.Exactly(_artist.Albums.Value.Count(s => s.Monitored))); } diff --git a/src/NzbDrone.Core/IndexerSearch/AlbumSearchService.cs b/src/NzbDrone.Core/IndexerSearch/AlbumSearchService.cs index 34ee40e29..498feb5b0 100644 --- a/src/NzbDrone.Core/IndexerSearch/AlbumSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/AlbumSearchService.cs @@ -17,21 +17,21 @@ namespace NzbDrone.Core.IndexerSearch IExecute, IExecute { - private readonly ISearchForNzb _nzbSearchService; + private readonly ISearchForReleases _releaseSearchService; private readonly IAlbumService _albumService; private readonly IAlbumCutoffService _albumCutoffService; private readonly IQueueService _queueService; private readonly IProcessDownloadDecisions _processDownloadDecisions; private readonly Logger _logger; - public AlbumSearchService(ISearchForNzb nzbSearchService, + public AlbumSearchService(ISearchForReleases nzbSearchService, IAlbumService albumService, IAlbumCutoffService albumCutoffService, IQueueService queueService, IProcessDownloadDecisions processDownloadDecisions, Logger logger) { - _nzbSearchService = nzbSearchService; + _releaseSearchService = nzbSearchService; _albumService = albumService; _albumCutoffService = albumCutoffService; _queueService = queueService; @@ -47,7 +47,7 @@ namespace NzbDrone.Core.IndexerSearch foreach (var album in albums) { List decisions; - decisions = _nzbSearchService.AlbumSearch(album.Id, false, userInvokedSearch, false); + decisions = _releaseSearchService.AlbumSearch(album.Id, false, userInvokedSearch, false); var processed = _processDownloadDecisions.ProcessDecisions(decisions); downloadedCount += processed.Grabbed.Count; @@ -61,7 +61,7 @@ namespace NzbDrone.Core.IndexerSearch foreach (var albumId in message.AlbumIds) { var decisions = - _nzbSearchService.AlbumSearch(albumId, false, message.Trigger == CommandTrigger.Manual, false); + _releaseSearchService.AlbumSearch(albumId, false, message.Trigger == CommandTrigger.Manual, false); var processed = _processDownloadDecisions.ProcessDecisions(decisions); _logger.ProgressInfo("Album search completed. {0} reports downloaded.", processed.Grabbed.Count); diff --git a/src/NzbDrone.Core/IndexerSearch/ArtistSearchService.cs b/src/NzbDrone.Core/IndexerSearch/ArtistSearchService.cs index 2e3cc5ccb..c4cb6a5ec 100644 --- a/src/NzbDrone.Core/IndexerSearch/ArtistSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/ArtistSearchService.cs @@ -7,22 +7,22 @@ namespace NzbDrone.Core.IndexerSearch { public class ArtistSearchService : IExecute { - private readonly ISearchForNzb _nzbSearchService; + private readonly ISearchForReleases _releaseSearchService; private readonly IProcessDownloadDecisions _processDownloadDecisions; private readonly Logger _logger; - public ArtistSearchService(ISearchForNzb nzbSearchService, + public ArtistSearchService(ISearchForReleases nzbSearchService, IProcessDownloadDecisions processDownloadDecisions, Logger logger) { - _nzbSearchService = nzbSearchService; + _releaseSearchService = nzbSearchService; _processDownloadDecisions = processDownloadDecisions; _logger = logger; } public void Execute(ArtistSearchCommand message) { - var decisions = _nzbSearchService.ArtistSearch(message.ArtistId, false, message.Trigger == CommandTrigger.Manual, false); + var decisions = _releaseSearchService.ArtistSearch(message.ArtistId, false, message.Trigger == CommandTrigger.Manual, false); var processed = _processDownloadDecisions.ProcessDecisions(decisions); _logger.ProgressInfo("Artist search completed. {0} reports downloaded.", processed.Grabbed.Count); diff --git a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs b/src/NzbDrone.Core/IndexerSearch/ReleaseSearchService.cs similarity index 97% rename from src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs rename to src/NzbDrone.Core/IndexerSearch/ReleaseSearchService.cs index a3c3644af..7b52bad32 100644 --- a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/ReleaseSearchService.cs @@ -14,13 +14,13 @@ using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.IndexerSearch { - public interface ISearchForNzb + public interface ISearchForReleases { List AlbumSearch(int albumId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch); List ArtistSearch(int artistId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch); } - public class NzbSearchService : ISearchForNzb + public class ReleaseSearchService : ISearchForReleases { private readonly IIndexerFactory _indexerFactory; private readonly IAlbumService _albumService; @@ -28,7 +28,7 @@ namespace NzbDrone.Core.IndexerSearch private readonly IMakeDownloadDecision _makeDownloadDecision; private readonly Logger _logger; - public NzbSearchService(IIndexerFactory indexerFactory, + public ReleaseSearchService(IIndexerFactory indexerFactory, IAlbumService albumService, IArtistService artistService, IMakeDownloadDecision makeDownloadDecision, From dfb2cf945f67808c95e51386b6b22f1373cccb6b Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 26 Sep 2022 14:44:12 -0500 Subject: [PATCH 0010/1478] New: Adds SSL option to Kodi connections Co-Authored-By: Zippy79 <1822598+zippy79@users.noreply.github.com> --- .../Notifications/Xbmc/XbmcJsonApiProxy.cs | 2 +- .../Notifications/Xbmc/XbmcSettings.cs | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs b/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs index bb2359fdc..9139350b5 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs @@ -75,7 +75,7 @@ namespace NzbDrone.Core.Notifications.Xbmc private string ProcessRequest(XbmcSettings settings, string method, params object[] parameters) { - var url = string.Format(@"http://{0}/jsonrpc", settings.Address); + var url = HttpRequestBuilder.BuildBaseUrl(settings.UseSsl, settings.Host, settings.Port, "jsonrpc"); var requestBuilder = new JsonRpcRequestBuilder(url, method, parameters); requestBuilder.LogResponseContent = true; diff --git a/src/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs b/src/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs index 7cc956440..99e4c6004 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs @@ -32,26 +32,29 @@ namespace NzbDrone.Core.Notifications.Xbmc [FieldDefinition(1, Label = "Port")] public int Port { get; set; } - [FieldDefinition(2, Label = "Username", Privacy = PrivacyLevel.UserName)] + [FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Connect to Kodi over HTTPS instead of HTTP")] + public bool UseSsl { get; set; } + + [FieldDefinition(3, Label = "Username", Privacy = PrivacyLevel.UserName)] public string Username { get; set; } - [FieldDefinition(3, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)] + [FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)] public string Password { get; set; } [DefaultValue(5)] - [FieldDefinition(4, Label = "Display Time", HelpText = "How long the notification will be displayed for (In seconds)")] + [FieldDefinition(5, Label = "Display Time", HelpText = "How long the notification will be displayed for (In seconds)")] public int DisplayTime { get; set; } - [FieldDefinition(5, Label = "GUI Notification", Type = FieldType.Checkbox)] + [FieldDefinition(6, Label = "GUI Notification", Type = FieldType.Checkbox)] public bool Notify { get; set; } - [FieldDefinition(6, Label = "Update Library", HelpText = "Update Library on Import & Rename?", Type = FieldType.Checkbox)] + [FieldDefinition(7, Label = "Update Library", HelpText = "Update Library on Import & Rename?", Type = FieldType.Checkbox)] public bool UpdateLibrary { get; set; } - [FieldDefinition(7, Label = "Clean Library", HelpText = "Clean Library after update?", Type = FieldType.Checkbox)] + [FieldDefinition(8, Label = "Clean Library", HelpText = "Clean Library after update?", Type = FieldType.Checkbox)] public bool CleanLibrary { get; set; } - [FieldDefinition(8, Label = "Always Update", HelpText = "Update Library even when a file is playing?", Type = FieldType.Checkbox)] + [FieldDefinition(9, Label = "Always Update", HelpText = "Update Library even when a file is playing?", Type = FieldType.Checkbox)] public bool AlwaysUpdate { get; set; } [JsonIgnore] From a34460459566f5b55823ca4ec8991e8c5023bc7e Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 26 Sep 2022 14:47:29 -0500 Subject: [PATCH 0011/1478] Sentry logging exceptions Co-Authored-By: Taloth --- src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs index 2c5e4eb71..f17eb467a 100644 --- a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs +++ b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs @@ -206,7 +206,11 @@ namespace NzbDrone.Common.Instrumentation.Sentry if (ex != null) { fingerPrint.Add(ex.GetType().FullName); - fingerPrint.Add(ex.TargetSite.ToString()); + if (ex.TargetSite != null) + { + fingerPrint.Add(ex.TargetSite.ToString()); + } + if (ex.InnerException != null) { fingerPrint.Add(ex.InnerException.GetType().FullName); From b8c84c8550d5faa134991675c45b97e44094afb5 Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 26 Sep 2022 14:50:21 -0500 Subject: [PATCH 0012/1478] Fixed: Parse endpoint response when title failed to parse Co-Authored-By: Taloth --- src/Lidarr.Api.V1/Parse/ParseController.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Lidarr.Api.V1/Parse/ParseController.cs b/src/Lidarr.Api.V1/Parse/ParseController.cs index 38e2b89f6..8cab2252b 100644 --- a/src/Lidarr.Api.V1/Parse/ParseController.cs +++ b/src/Lidarr.Api.V1/Parse/ParseController.cs @@ -23,7 +23,10 @@ namespace Lidarr.Api.V1.Parse if (parsedAlbumInfo == null) { - return null; + return new ParseResource + { + Title = title + }; } var remoteAlbum = _parsingService.Map(parsedAlbumInfo); From d70a9195ea895f6814e2b3c238365d3e6b607bb1 Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 26 Sep 2022 14:54:35 -0500 Subject: [PATCH 0013/1478] Fixed: Cleanup of unused tags for Import lists Co-Authored-By: Taloth --- .../Housekeeping/Housekeepers/CleanupUnusedTags.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupUnusedTags.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupUnusedTags.cs index 9b993a26d..e164325e4 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupUnusedTags.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupUnusedTags.cs @@ -19,7 +19,7 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - var usedTags = new[] { "Artists", "Notifications", "DelayProfiles", "ReleaseProfiles" } + var usedTags = new[] { "Artists", "Notifications", "DelayProfiles", "ReleaseProfiles", "ImportLists", "Indexers" } .SelectMany(v => GetUsedTags(v, mapper)) .Distinct() .ToArray(); @@ -32,7 +32,7 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers private int[] GetUsedTags(string table, IDbConnection mapper) { - return mapper.Query>($"SELECT DISTINCT Tags FROM {table} WHERE NOT Tags = '[]'") + return mapper.Query>($"SELECT DISTINCT Tags FROM {table} WHERE NOT Tags = '[]' AND NOT Tags IS NULL") .SelectMany(x => x) .Distinct() .ToArray(); From f834829fa6d6d4f227c89bd5e569e1cbb5df5207 Mon Sep 17 00:00:00 2001 From: Chris <1967906+psylenced@users.noreply.github.com> Date: Fri, 23 Sep 2022 02:46:26 +1000 Subject: [PATCH 0014/1478] Added: Ntfy provider for notifications --- src/NzbDrone.Core/Notifications/Ntfy/Ntfy.cs | 60 ++++++++ .../Notifications/Ntfy/NtfyException.cs | 18 +++ .../Notifications/Ntfy/NtfyPriority.cs | 11 ++ .../Notifications/Ntfy/NtfyProxy.cs | 138 ++++++++++++++++++ .../Notifications/Ntfy/NtfySettings.cs | 63 ++++++++ 5 files changed, 290 insertions(+) create mode 100644 src/NzbDrone.Core/Notifications/Ntfy/Ntfy.cs create mode 100644 src/NzbDrone.Core/Notifications/Ntfy/NtfyException.cs create mode 100644 src/NzbDrone.Core/Notifications/Ntfy/NtfyPriority.cs create mode 100644 src/NzbDrone.Core/Notifications/Ntfy/NtfyProxy.cs create mode 100644 src/NzbDrone.Core/Notifications/Ntfy/NtfySettings.cs diff --git a/src/NzbDrone.Core/Notifications/Ntfy/Ntfy.cs b/src/NzbDrone.Core/Notifications/Ntfy/Ntfy.cs new file mode 100644 index 000000000..2ebd8ccd8 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Ntfy/Ntfy.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; + +using FluentValidation.Results; +using NzbDrone.Common.Extensions; + +namespace NzbDrone.Core.Notifications.Ntfy +{ + public class Ntfy : NotificationBase + { + private readonly INtfyProxy _proxy; + + public Ntfy(INtfyProxy proxy) + { + _proxy = proxy; + } + + public override string Name => "ntfy.sh"; + + public override string Link => "https://ntfy.sh/"; + + public override void OnGrab(GrabMessage grabMessage) + { + _proxy.SendNotification(ALBUM_GRABBED_TITLE_BRANDED, grabMessage.Message, Settings); + } + + public override void OnReleaseImport(AlbumDownloadMessage message) + { + _proxy.SendNotification(ALBUM_DOWNLOADED_TITLE_BRANDED, message.Message, Settings); + } + + public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck) + { + _proxy.SendNotification(HEALTH_ISSUE_TITLE_BRANDED, healthCheck.Message, Settings); + } + + public override void OnDownloadFailure(DownloadFailedMessage message) + { + _proxy.SendNotification(DOWNLOAD_FAILURE_TITLE_BRANDED, message.Message, Settings); + } + + public override void OnImportFailure(AlbumDownloadMessage message) + { + _proxy.SendNotification(IMPORT_FAILURE_TITLE_BRANDED, message.Message, Settings); + } + + public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage) + { + _proxy.SendNotification(APPLICATION_UPDATE_TITLE_BRANDED, updateMessage.Message, Settings); + } + + public override ValidationResult Test() + { + var failures = new List(); + + failures.AddIfNotNull(_proxy.Test(Settings)); + + return new ValidationResult(failures); + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Ntfy/NtfyException.cs b/src/NzbDrone.Core/Notifications/Ntfy/NtfyException.cs new file mode 100644 index 000000000..a6e70080e --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Ntfy/NtfyException.cs @@ -0,0 +1,18 @@ +using System; +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.Notifications.Ntfy +{ + public class NtfyException : NzbDroneException + { + public NtfyException(string message) + : base(message) + { + } + + public NtfyException(string message, Exception innerException, params object[] args) + : base(message, innerException, args) + { + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Ntfy/NtfyPriority.cs b/src/NzbDrone.Core/Notifications/Ntfy/NtfyPriority.cs new file mode 100644 index 000000000..48c00ac7d --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Ntfy/NtfyPriority.cs @@ -0,0 +1,11 @@ +namespace NzbDrone.Core.Notifications.Ntfy +{ + public enum NtfyPriority + { + Min = 1, + Low = 2, + Default = 3, + High = 4, + Max = 5 + } +} diff --git a/src/NzbDrone.Core/Notifications/Ntfy/NtfyProxy.cs b/src/NzbDrone.Core/Notifications/Ntfy/NtfyProxy.cs new file mode 100644 index 000000000..d670cb724 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Ntfy/NtfyProxy.cs @@ -0,0 +1,138 @@ +using System; +using System.Linq; +using System.Net; + +using FluentValidation.Results; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; + +namespace NzbDrone.Core.Notifications.Ntfy +{ + public interface INtfyProxy + { + void SendNotification(string title, string message, NtfySettings settings); + + ValidationFailure Test(NtfySettings settings); + } + + public class NtfyProxy : INtfyProxy + { + private const string DEFAULT_PUSH_URL = "https://ntfy.sh"; + + private readonly IHttpClient _httpClient; + + private readonly Logger _logger; + + public NtfyProxy(IHttpClient httpClient, Logger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + public void SendNotification(string title, string message, NtfySettings settings) + { + var error = false; + + var serverUrl = settings.ServerUrl.IsNullOrWhiteSpace() ? NtfyProxy.DEFAULT_PUSH_URL : settings.ServerUrl; + + foreach (var topic in settings.Topics) + { + var request = BuildTopicRequest(serverUrl, topic); + + try + { + SendNotification(title, message, request, settings); + } + catch (NtfyException ex) + { + _logger.Error(ex, "Unable to send test message to {0}", topic); + error = true; + } + } + + if (error) + { + throw new NtfyException("Unable to send Ntfy notifications to all topics"); + } + } + + private HttpRequestBuilder BuildTopicRequest(string serverUrl, string topic) + { + var trimServerUrl = serverUrl.TrimEnd('/'); + + var requestBuilder = new HttpRequestBuilder($"{trimServerUrl}/{topic}").Post(); + + return requestBuilder; + } + + public ValidationFailure Test(NtfySettings settings) + { + try + { + const string title = "Lidarr - Test Notification"; + + const string body = "This is a test message from Lidarr"; + + SendNotification(title, body, settings); + } + catch (HttpException ex) + { + if (ex.Response.StatusCode == HttpStatusCode.Unauthorized || ex.Response.StatusCode == HttpStatusCode.Forbidden) + { + _logger.Error(ex, "Authorization is required"); + return new ValidationFailure("UserName", "Authorization is required"); + } + + _logger.Error(ex, "Unable to send test message"); + return new ValidationFailure("ServerUrl", "Unable to send test message"); + } + catch (Exception ex) + { + _logger.Error(ex, "Unable to send test message"); + return new ValidationFailure("", "Unable to send test message"); + } + + return null; + } + + private void SendNotification(string title, string message, HttpRequestBuilder requestBuilder, NtfySettings settings) + { + try + { + requestBuilder.Headers.Add("X-Title", title); + requestBuilder.Headers.Add("X-Message", message); + requestBuilder.Headers.Add("X-Priority", settings.Priority.ToString()); + + if (settings.Tags.Any()) + { + requestBuilder.Headers.Add("X-Tags", settings.Tags.Join(",")); + } + + if (!settings.ClickUrl.IsNullOrWhiteSpace()) + { + requestBuilder.Headers.Add("X-Click", settings.ClickUrl); + } + + var request = requestBuilder.Build(); + + if (!settings.UserName.IsNullOrWhiteSpace() && !settings.Password.IsNullOrWhiteSpace()) + { + request.AddBasicAuthentication(settings.UserName, settings.Password); + } + + _httpClient.Execute(request); + } + catch (HttpException ex) + { + if (ex.Response.StatusCode == HttpStatusCode.Unauthorized || ex.Response.StatusCode == HttpStatusCode.Forbidden) + { + _logger.Error(ex, "Authorization is required"); + throw; + } + + throw new NtfyException("Unable to send text message: {0}", ex, ex.Message); + } + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Ntfy/NtfySettings.cs b/src/NzbDrone.Core/Notifications/Ntfy/NtfySettings.cs new file mode 100644 index 000000000..94190458d --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Ntfy/NtfySettings.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using FluentValidation; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.Notifications.Ntfy +{ + public class NtfySettingsValidator : AbstractValidator + { + public NtfySettingsValidator() + { + RuleFor(c => c.Topics).NotEmpty(); + RuleFor(c => c.Priority).InclusiveBetween(1, 5); + RuleFor(c => c.ServerUrl).IsValidUrl().When(c => !c.ServerUrl.IsNullOrWhiteSpace()); + RuleFor(c => c.ClickUrl).IsValidUrl().When(c => !c.ClickUrl.IsNullOrWhiteSpace()); + RuleFor(c => c.UserName).NotEmpty().When(c => !c.Password.IsNullOrWhiteSpace()); + RuleFor(c => c.Password).NotEmpty().When(c => !c.UserName.IsNullOrWhiteSpace()); + RuleForEach(c => c.Topics).NotEmpty().Matches("[a-zA-Z0-9_-]+").Must(c => !InvalidTopics.Contains(c)).WithMessage("Invalid topic"); + } + + private static List InvalidTopics => new List { "announcements", "app", "docs", "settings", "stats", "mytopic-rw", "mytopic-ro", "mytopic-wo" }; + } + + public class NtfySettings : IProviderConfig + { + private static readonly NtfySettingsValidator Validator = new NtfySettingsValidator(); + + public NtfySettings() + { + Topics = Array.Empty(); + Priority = 3; + } + + [FieldDefinition(0, Label = "Server Url", Type = FieldType.Url, HelpLink = "https://ntfy.sh/docs/install/", HelpText = "Leave blank to use public server (https://ntfy.sh)")] + public string ServerUrl { get; set; } + + [FieldDefinition(1, Label = "User Name", HelpText = "Optional Authorization", Privacy = PrivacyLevel.UserName)] + public string UserName { get; set; } + + [FieldDefinition(2, Label = "Password", Type = FieldType.Password, HelpText = "Optional Password", Privacy = PrivacyLevel.Password)] + public string Password { get; set; } + + [FieldDefinition(3, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NtfyPriority))] + public int Priority { get; set; } + + [FieldDefinition(4, Label = "Topics", HelpText = "List of Topics to send notifications to", Type = FieldType.Tag)] + public IEnumerable Topics { get; set; } + + [FieldDefinition(5, Label = "Ntfy Tags and Emojis", Type = FieldType.Tag, HelpText = "Optional list of tags or emojis to use", HelpLink = "https://ntfy.sh/docs/emojis/")] + public IEnumerable Tags { get; set; } + + [FieldDefinition(6, Label = "Click Url", Type = FieldType.Url, HelpText = "Optional link when user clicks notification")] + public string ClickUrl { get; set; } + + public NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } + } +} From 9bacc78eb1619a926d6e2adc3f4c89ef8f12ea9c Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 26 Sep 2022 15:33:30 -0500 Subject: [PATCH 0015/1478] Handle redirects for 308 redirects Co-Authored-By: Mark McDowall --- src/NzbDrone.Common/Http/HttpResponse.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Common/Http/HttpResponse.cs b/src/NzbDrone.Common/Http/HttpResponse.cs index 359856f32..bc4bc2c74 100644 --- a/src/NzbDrone.Common/Http/HttpResponse.cs +++ b/src/NzbDrone.Common/Http/HttpResponse.cs @@ -52,10 +52,11 @@ namespace NzbDrone.Common.Http public bool HasHttpRedirect => StatusCode == HttpStatusCode.Moved || StatusCode == HttpStatusCode.MovedPermanently || + StatusCode == HttpStatusCode.Found || StatusCode == HttpStatusCode.TemporaryRedirect || - StatusCode == HttpStatusCode.SeeOther || StatusCode == HttpStatusCode.RedirectMethod || - StatusCode == HttpStatusCode.Found; + StatusCode == HttpStatusCode.SeeOther || + StatusCode == HttpStatusCode.PermanentRedirect; public string[] GetCookieHeaders() { From 35651df049a0fc6b94c5de4068f90db5edd38fce Mon Sep 17 00:00:00 2001 From: Qstick Date: Sat, 13 Nov 2021 15:10:42 -0600 Subject: [PATCH 0016/1478] Use modern HttpClient Co-Authored-By: ta264 (cherry picked from commit 4c0fe62dda7ba87eec08d628f79e4fae8fdb1a0f) --- .editorconfig | 4 +- .../Http/HttpClientFixture.cs | 140 +++++++- .../Extensions/StringExtensions.cs | 21 ++ .../Http/BasicNetworkCredential.cs | 12 + .../ICertificationValidationService.cs | 10 + .../Http/Dispatchers/IHttpDispatcher.cs | 1 - .../Http/Dispatchers/ManagedHttpDispatcher.cs | 332 +++++++++++------- src/NzbDrone.Common/Http/GZipWebClient.cs | 15 - src/NzbDrone.Common/Http/HttpAccept.cs | 1 + src/NzbDrone.Common/Http/HttpClient.cs | 104 ++++-- src/NzbDrone.Common/Http/HttpException.cs | 2 +- src/NzbDrone.Common/Http/HttpHeader.cs | 20 ++ src/NzbDrone.Common/Http/HttpMethod.cs | 14 - src/NzbDrone.Common/Http/HttpRequest.cs | 11 +- .../Http/HttpRequestBuilder.cs | 20 +- .../Http/JsonRpcRequestBuilder.cs | 7 +- .../Http/XmlRpcRequestBuilder.cs | 103 ++++++ src/NzbDrone.Core.Test/Framework/CoreTest.cs | 6 +- .../FileListTests/FileListFixture.cs | 3 +- .../GazelleTests/GazelleFixture.cs | 7 +- .../HeadphonesTests/HeadphonesFixture.cs | 3 +- .../IPTorrentsTests/IPTorrentsFixture.cs | 3 +- .../NewznabTests/NewznabFixture.cs | 3 +- .../IndexerTests/NyaaTests/NyaaFixture.cs | 5 +- .../IndexerTests/RarbgTests/RarbgFixture.cs | 9 +- .../RedactedTests/RedactedFixture.cs | 5 +- .../TorrentleechTests/TorrentleechFixture.cs | 5 +- .../TorznabTests/TorznabFixture.cs | 9 +- .../Download/Clients/Aria2/Aria2.cs | 8 +- .../Download/Clients/Aria2/Aria2Containers.cs | 220 +++++++----- .../Download/Clients/Aria2/Aria2Proxy.cs | 197 ++++------- .../Proxies/DiskStationProxyBase.cs | 11 +- .../Proxies/DownloadStationTaskProxyV1.cs | 5 +- .../Proxies/DownloadStationTaskProxyV2.cs | 3 +- .../Download/Clients/Flood/FloodProxy.cs | 11 +- .../Clients/Hadouken/HadoukenProxy.cs | 2 +- .../Download/Clients/Nzbget/NzbgetProxy.cs | 2 +- .../Clients/QBittorrent/QBittorrentProxyV1.cs | 2 +- .../Clients/QBittorrent/QBittorrentProxyV2.cs | 2 +- .../Clients/Transmission/TransmissionProxy.cs | 2 +- .../Download/Clients/rTorrent/RTorrent.cs | 6 + .../Clients/rTorrent/RTorrentFault.cs | 28 ++ .../Clients/rTorrent/RTorrentProxy.cs | 253 +++++-------- .../Clients/rTorrent/RTorrentTorrent.cs | 30 +- .../Clients/uTorrent/UTorrentProxy.cs | 2 +- .../Download/Extensions/XmlExtensions.cs | 55 +++ .../FileList/FileListRequestGenerator.cs | 2 +- .../Gazelle/GazelleRequestGenerator.cs | 5 +- .../HeadphonesCapabilitiesProvider.cs | 3 +- .../Headphones/HeadphonesRequestGenerator.cs | 4 +- .../Notifications/Discord/DiscordProxy.cs | 3 +- .../Notifications/Email/Email.cs | 42 ++- .../Notifications/Join/JoinProxy.cs | 3 +- .../Notifications/Mailgun/MailgunProxy.cs | 4 +- .../MediaBrowser/MediaBrowserProxy.cs | 7 +- .../Notifications/Ntfy/NtfyProxy.cs | 3 +- .../Plex/PlexTv/PlexTvService.cs | 4 +- .../Plex/Server/PlexServerProxy.cs | 13 +- .../PushBullet/PushBulletProxy.cs | 7 +- .../Notifications/SendGrid/SendGridProxy.cs | 5 +- .../Notifications/Slack/SlackProxy.cs | 3 +- .../Subsonic/SubsonicServerProxy.cs | 7 +- .../Notifications/Twitter/TwitterProxy.cs | 110 ++++++ .../Notifications/Twitter/TwitterService.cs | 48 +-- .../Notifications/Webhook/WebhookMethod.cs | 4 +- .../Notifications/Webhook/WebhookProxy.cs | 12 +- .../Notifications/Xbmc/XbmcJsonApiProxy.cs | 2 +- .../X509CertificateValidationService.cs | 40 ++- src/NzbDrone.Core/TinyTwitter.cs | 235 ------------- .../IndexHtmlFixture.cs | 24 +- 70 files changed, 1296 insertions(+), 1008 deletions(-) create mode 100644 src/NzbDrone.Common/Http/BasicNetworkCredential.cs create mode 100644 src/NzbDrone.Common/Http/Dispatchers/ICertificationValidationService.cs delete mode 100644 src/NzbDrone.Common/Http/GZipWebClient.cs delete mode 100644 src/NzbDrone.Common/Http/HttpMethod.cs create mode 100644 src/NzbDrone.Common/Http/XmlRpcRequestBuilder.cs create mode 100644 src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentFault.cs create mode 100644 src/NzbDrone.Core/Download/Extensions/XmlExtensions.cs create mode 100644 src/NzbDrone.Core/Notifications/Twitter/TwitterProxy.cs delete mode 100644 src/NzbDrone.Core/TinyTwitter.cs diff --git a/.editorconfig b/.editorconfig index 5a4666a97..1cdc7b36e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ root = true # NOTE: Requires **VS2019 16.3** or later # Stylecop.ruleset -# Description: Rules for Radarr +# Description: Rules for Lidarr # Code files [*.cs] @@ -264,7 +264,7 @@ dotnet_diagnostic.CA5392.severity = suggestion dotnet_diagnostic.CA5394.severity = suggestion dotnet_diagnostic.CA5397.severity = suggestion -dotnet_diagnostic.SYSLIB0014.severity = none +dotnet_diagnostic.SYSLIB0006.severity = none [*.{js,html,js,hbs,less,css}] charset = utf-8 diff --git a/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs index a79f40faa..5828c9fb2 100644 --- a/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs +++ b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading; using FluentAssertions; using Moq; @@ -15,8 +16,11 @@ using NzbDrone.Common.Http; using NzbDrone.Common.Http.Dispatchers; using NzbDrone.Common.Http.Proxy; using NzbDrone.Common.TPL; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Security; using NzbDrone.Test.Common; using NzbDrone.Test.Common.Categories; +using HttpClient = NzbDrone.Common.Http.HttpClient; namespace NzbDrone.Common.Test.Http { @@ -31,6 +35,8 @@ namespace NzbDrone.Common.Test.Http private string _httpBinHost; private string _httpBinHost2; + private System.Net.Http.HttpClient _httpClient = new (); + [OneTimeSetUp] public void FixtureSetUp() { @@ -38,7 +44,7 @@ namespace NzbDrone.Common.Test.Http var mainHost = "httpbin.servarr.com"; // Use mirrors for tests that use two hosts - var candidates = new[] { "eu.httpbin.org", /* "httpbin.org", */ "www.httpbin.org" }; + var candidates = new[] { "httpbin1.servarr.com" }; // httpbin.org is broken right now, occassionally redirecting to https if it's unavailable. _httpBinHost = mainHost; @@ -46,29 +52,21 @@ namespace NzbDrone.Common.Test.Http TestLogger.Info($"{candidates.Length} TestSites available."); - _httpBinSleep = _httpBinHosts.Length < 2 ? 100 : 10; + _httpBinSleep = 10; } private bool IsTestSiteAvailable(string site) { try { - var req = WebRequest.Create($"https://{site}/get") as HttpWebRequest; - var res = req.GetResponse() as HttpWebResponse; + var res = _httpClient.GetAsync($"https://{site}/get").GetAwaiter().GetResult(); + if (res.StatusCode != HttpStatusCode.OK) { return false; } - try - { - req = WebRequest.Create($"https://{site}/status/429") as HttpWebRequest; - res = req.GetResponse() as HttpWebResponse; - } - catch (WebException ex) - { - res = ex.Response as HttpWebResponse; - } + res = _httpClient.GetAsync($"https://{site}/status/429").GetAwaiter().GetResult(); if (res == null || res.StatusCode != (HttpStatusCode)429) { @@ -95,10 +93,14 @@ namespace NzbDrone.Common.Test.Http Mocker.GetMock().Setup(c => c.Name).Returns("TestOS"); Mocker.GetMock().Setup(c => c.Version).Returns("9.0.0"); + Mocker.GetMock().SetupGet(x => x.CertificateValidation).Returns(CertificateValidationType.Enabled); + Mocker.SetConstant(Mocker.Resolve()); Mocker.SetConstant(Mocker.Resolve()); Mocker.SetConstant(Mocker.Resolve()); + Mocker.SetConstant(new X509CertificateValidationService(Mocker.GetMock().Object, TestLogger)); + Mocker.SetConstant(Mocker.Resolve()); Mocker.SetConstant>(Array.Empty()); Mocker.SetConstant(Mocker.Resolve()); @@ -138,6 +140,28 @@ namespace NzbDrone.Common.Test.Http response.Content.Should().NotBeNullOrWhiteSpace(); } + [TestCase(CertificateValidationType.Enabled)] + [TestCase(CertificateValidationType.DisabledForLocalAddresses)] + public void bad_ssl_should_fail_when_remote_validation_enabled(CertificateValidationType validationType) + { + Mocker.GetMock().SetupGet(x => x.CertificateValidation).Returns(validationType); + var request = new HttpRequest($"https://expired.badssl.com"); + + Assert.Throws(() => Subject.Execute(request)); + ExceptionVerification.ExpectedErrors(2); + } + + [Test] + public void bad_ssl_should_pass_if_remote_validation_disabled() + { + Mocker.GetMock().SetupGet(x => x.CertificateValidation).Returns(CertificateValidationType.Disabled); + + var request = new HttpRequest($"https://expired.badssl.com"); + + Subject.Execute(request); + ExceptionVerification.ExpectedErrors(0); + } + [Test] public void should_execute_typed_get() { @@ -162,15 +186,42 @@ namespace NzbDrone.Common.Test.Http response.Resource.Data.Should().Be(message); } - [TestCase("gzip")] - public void should_execute_get_using_gzip(string compression) + [Test] + public void should_execute_post_with_content_type() { - var request = new HttpRequest($"https://{_httpBinHost}/{compression}"); + var message = "{ my: 1 }"; + var request = new HttpRequest($"https://{_httpBinHost}/post"); + request.SetContent(message); + request.Headers.ContentType = "application/json"; + + var response = Subject.Post(request); + + response.Resource.Data.Should().Be(message); + } + + [Test] + public void should_execute_get_using_gzip() + { + var request = new HttpRequest($"https://{_httpBinHost}/gzip"); var response = Subject.Get(request); - response.Resource.Headers["Accept-Encoding"].ToString().Should().Be(compression); + response.Resource.Headers["Accept-Encoding"].ToString().Should().Contain("gzip"); + response.Resource.Gzipped.Should().BeTrue(); + response.Resource.Brotli.Should().BeFalse(); + } + + [Test] + public void should_execute_get_using_brotli() + { + var request = new HttpRequest($"https://{_httpBinHost}/brotli"); + var response = Subject.Get(request); + + response.Resource.Headers["Accept-Encoding"].ToString().Should().Contain("br"); + + response.Resource.Gzipped.Should().BeFalse(); + response.Resource.Brotli.Should().BeTrue(); } [TestCase(HttpStatusCode.Unauthorized)] @@ -201,6 +252,16 @@ namespace NzbDrone.Common.Test.Http ExceptionVerification.IgnoreWarns(); } + [Test] + public void should_log_unsuccessful_status_codes() + { + var request = new HttpRequest($"https://{_httpBinHost}/status/{HttpStatusCode.NotFound}"); + + var exception = Assert.Throws(() => Subject.Get(request)); + + ExceptionVerification.ExpectedWarns(1); + } + [Test] public void should_not_log_unsuccessful_status_codes() { @@ -309,8 +370,11 @@ namespace NzbDrone.Common.Test.Http Subject.DownloadFile(url, file); + File.Exists(file).Should().BeTrue(); + File.Exists(file + ".part").Should().BeFalse(); + var fileInfo = new FileInfo(file); - fileInfo.Exists.Should().BeTrue(); + fileInfo.Length.Should().Be(146122); } @@ -337,13 +401,39 @@ namespace NzbDrone.Common.Test.Http { var file = GetTempFilePath(); - Assert.Throws(() => Subject.DownloadFile("https://download.lidarr.audio/wrongpath", file)); + Assert.Throws(() => Subject.DownloadFile("https://download.sonarr.tv/wrongpath", file)); File.Exists(file).Should().BeFalse(); + File.Exists(file + ".part").Should().BeFalse(); ExceptionVerification.ExpectedWarns(1); } + [Test] + public void should_not_write_redirect_content_to_stream() + { + var file = GetTempFilePath(); + + using (var fileStream = new FileStream(file, FileMode.Create)) + { + var request = new HttpRequest($"https://{_httpBinHost}/redirect/1"); + request.AllowAutoRedirect = false; + request.ResponseStream = fileStream; + + var response = Subject.Get(request); + + response.StatusCode.Should().Be(HttpStatusCode.Redirect); + } + + ExceptionVerification.ExpectedErrors(1); + + File.Exists(file).Should().BeTrue(); + + var fileInfo = new FileInfo(file); + + fileInfo.Length.Should().Be(0); + } + [Test] public void should_send_cookie() { @@ -763,6 +853,17 @@ namespace NzbDrone.Common.Test.Http { } } + + [Test] + public void should_correctly_use_basic_auth() + { + var request = new HttpRequest($"https://{_httpBinHost}/basic-auth/username/password"); + request.Credentials = new BasicNetworkCredential("username", "password"); + + var response = Subject.Execute(request); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + } } public class HttpBinResource @@ -773,6 +874,7 @@ namespace NzbDrone.Common.Test.Http public string Url { get; set; } public string Data { get; set; } public bool Gzipped { get; set; } + public bool Brotli { get; set; } } public class HttpCookieResource diff --git a/src/NzbDrone.Common/Extensions/StringExtensions.cs b/src/NzbDrone.Common/Extensions/StringExtensions.cs index 4a07aa7b8..686167d64 100644 --- a/src/NzbDrone.Common/Extensions/StringExtensions.cs +++ b/src/NzbDrone.Common/Extensions/StringExtensions.cs @@ -210,5 +210,26 @@ namespace NzbDrone.Common.Extensions { return 1.0 - ((double)a.LevenshteinDistance(b) / Math.Max(a.Length, b.Length)); } + + public static string EncodeRFC3986(this string value) + { + // From Twitterizer http://www.twitterizer.net/ + if (string.IsNullOrEmpty(value)) + { + return string.Empty; + } + + var encoded = Uri.EscapeDataString(value); + + return Regex + .Replace(encoded, "(%[0-9a-f][0-9a-f])", c => c.Value.ToUpper()) + .Replace("(", "%28") + .Replace(")", "%29") + .Replace("$", "%24") + .Replace("!", "%21") + .Replace("*", "%2A") + .Replace("'", "%27") + .Replace("%7E", "~"); + } } } diff --git a/src/NzbDrone.Common/Http/BasicNetworkCredential.cs b/src/NzbDrone.Common/Http/BasicNetworkCredential.cs new file mode 100644 index 000000000..26710f766 --- /dev/null +++ b/src/NzbDrone.Common/Http/BasicNetworkCredential.cs @@ -0,0 +1,12 @@ +using System.Net; + +namespace NzbDrone.Common.Http +{ + public class BasicNetworkCredential : NetworkCredential + { + public BasicNetworkCredential(string user, string pass) + : base(user, pass) + { + } + } +} diff --git a/src/NzbDrone.Common/Http/Dispatchers/ICertificationValidationService.cs b/src/NzbDrone.Common/Http/Dispatchers/ICertificationValidationService.cs new file mode 100644 index 000000000..54f3e2a8d --- /dev/null +++ b/src/NzbDrone.Common/Http/Dispatchers/ICertificationValidationService.cs @@ -0,0 +1,10 @@ +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; + +namespace NzbDrone.Common.Http.Dispatchers +{ + public interface ICertificateValidationService + { + bool ShouldByPassValidationError(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors); + } +} diff --git a/src/NzbDrone.Common/Http/Dispatchers/IHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/IHttpDispatcher.cs index b4cae7ff8..8e665ceed 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/IHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/IHttpDispatcher.cs @@ -5,6 +5,5 @@ namespace NzbDrone.Common.Http.Dispatchers public interface IHttpDispatcher { HttpResponse GetResponse(HttpRequest request, CookieContainer cookies); - void DownloadFile(string url, string fileName); } } diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs index c1179d4a2..c3d630a86 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs @@ -1,214 +1,231 @@ using System; using System.Diagnostics; using System.IO; -using System.IO.Compression; using System.Net; -using System.Reflection; +using System.Net.Http; +using System.Net.Security; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; using NLog; -using NLog.Fluent; -using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Cache; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http.Proxy; -using NzbDrone.Common.Instrumentation.Extensions; namespace NzbDrone.Common.Http.Dispatchers { public class ManagedHttpDispatcher : IHttpDispatcher { + private const string NO_PROXY_KEY = "no-proxy"; + + private const int connection_establish_timeout = 2000; + private static bool useIPv6 = Socket.OSSupportsIPv6; + private static bool hasResolvedIPv6Availability; + private readonly IHttpProxySettingsProvider _proxySettingsProvider; private readonly ICreateManagedWebProxy _createManagedWebProxy; + private readonly ICertificateValidationService _certificateValidationService; private readonly IUserAgentBuilder _userAgentBuilder; - private readonly IPlatformInfo _platformInfo; + private readonly ICached _httpClientCache; private readonly Logger _logger; + private readonly ICached _credentialCache; - public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, ICreateManagedWebProxy createManagedWebProxy, IUserAgentBuilder userAgentBuilder, IPlatformInfo platformInfo, Logger logger) + public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, + ICreateManagedWebProxy createManagedWebProxy, + ICertificateValidationService certificateValidationService, + IUserAgentBuilder userAgentBuilder, + ICacheManager cacheManager, + Logger logger) { _proxySettingsProvider = proxySettingsProvider; _createManagedWebProxy = createManagedWebProxy; + _certificateValidationService = certificateValidationService; _userAgentBuilder = userAgentBuilder; - _platformInfo = platformInfo; _logger = logger; + + _httpClientCache = cacheManager.GetCache(typeof(ManagedHttpDispatcher)); + _credentialCache = cacheManager.GetCache(typeof(ManagedHttpDispatcher), "credentialcache"); } public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies) { - var webRequest = (HttpWebRequest)WebRequest.Create((Uri)request.Url); + var requestMessage = new HttpRequestMessage(request.Method, (Uri)request.Url); + requestMessage.Headers.UserAgent.ParseAdd(_userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent)); + requestMessage.Headers.ConnectionClose = !request.ConnectionKeepAlive; - // Deflate is not a standard and could break depending on implementation. - // we should just stick with the more compatible Gzip - //http://stackoverflow.com/questions/8490718/how-to-decompress-stream-deflated-with-java-util-zip-deflater-in-net - webRequest.AutomaticDecompression = DecompressionMethods.GZip; - - webRequest.Method = request.Method.ToString(); - webRequest.UserAgent = _userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent); - webRequest.KeepAlive = request.ConnectionKeepAlive; - webRequest.AllowAutoRedirect = false; - webRequest.CookieContainer = cookies; - - if (request.RequestTimeout != TimeSpan.Zero) + var cookieHeader = cookies.GetCookieHeader((Uri)request.Url); + if (cookieHeader.IsNotNullOrWhiteSpace()) { - webRequest.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalMilliseconds); + requestMessage.Headers.Add("Cookie", cookieHeader); } - webRequest.Proxy = GetProxy(request.Url); + using var cts = new CancellationTokenSource(); + if (request.RequestTimeout != TimeSpan.Zero) + { + cts.CancelAfter(request.RequestTimeout); + } + else + { + // The default for System.Net.Http.HttpClient + cts.CancelAfter(TimeSpan.FromSeconds(100)); + } + + if (request.Credentials != null) + { + if (request.Credentials is BasicNetworkCredential bc) + { + // Manually set header to avoid initial challenge response + var authInfo = bc.UserName + ":" + bc.Password; + authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo)); + requestMessage.Headers.Add("Authorization", "Basic " + authInfo); + } + else if (request.Credentials is NetworkCredential nc) + { + var creds = GetCredentialCache(); + foreach (var authtype in new[] { "Basic", "Digest" }) + { + creds.Remove((Uri)request.Url, authtype); + creds.Add((Uri)request.Url, authtype, nc); + } + } + } + + if (request.ContentData != null) + { + requestMessage.Content = new ByteArrayContent(request.ContentData); + } if (request.Headers != null) { - AddRequestHeaders(webRequest, request.Headers); + AddRequestHeaders(requestMessage, request.Headers); } - HttpWebResponse httpWebResponse; + var httpClient = GetClient(request.Url); + + HttpResponseMessage responseMessage; try { - if (request.ContentData != null) - { - webRequest.ContentLength = request.ContentData.Length; - using (var writeStream = webRequest.GetRequestStream()) - { - writeStream.Write(request.ContentData, 0, request.ContentData.Length); - } - } - - httpWebResponse = (HttpWebResponse)webRequest.GetResponse(); + responseMessage = httpClient.Send(requestMessage, cts.Token); } - catch (WebException e) + catch (HttpRequestException e) { - httpWebResponse = (HttpWebResponse)e.Response; - - if (httpWebResponse == null) - { - // The default messages for WebException on mono are pretty horrible. - if (e.Status == WebExceptionStatus.NameResolutionFailure) - { - throw new WebException($"DNS Name Resolution Failure: '{webRequest.RequestUri.Host}'", e.Status); - } - else if (e.ToString().Contains("TLS Support not")) - { - throw new TlsFailureException(webRequest, e); - } - else if (e.ToString().Contains("The authentication or decryption has failed.")) - { - throw new TlsFailureException(webRequest, e); - } - else if (OsInfo.IsNotWindows) - { - throw new WebException($"{e.Message}: '{webRequest.RequestUri}'", e, e.Status, e.Response); - } - else - { - throw; - } - } + _logger.Error(e, "HttpClient error"); + throw; } byte[] data = null; - using (var responseStream = httpWebResponse.GetResponseStream()) + using (var responseStream = responseMessage.Content.ReadAsStream()) { if (responseStream != null && responseStream != Stream.Null) { try { - data = responseStream.ToBytes(); + if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK) + { + // A target ResponseStream was specified, write to that instead. + // But only on the OK status code, since we don't want to write failures and redirects. + responseStream.CopyTo(request.ResponseStream); + } + else + { + data = responseStream.ToBytes(); + } } catch (Exception ex) { - throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, httpWebResponse); + throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, null); } } } - return new HttpResponse(request, new HttpHeader(httpWebResponse.Headers), data, httpWebResponse.StatusCode); + var headers = responseMessage.Headers.ToNameValueCollection(); + + headers.Add(responseMessage.Content.Headers.ToNameValueCollection()); + + return new HttpResponse(request, new HttpHeader(responseMessage.Headers), data, responseMessage.StatusCode); } - public void DownloadFile(string url, string fileName) + protected virtual System.Net.Http.HttpClient GetClient(HttpUri uri) { - try - { - var fileInfo = new FileInfo(fileName); - if (fileInfo.Directory != null && !fileInfo.Directory.Exists) - { - fileInfo.Directory.Create(); - } - - _logger.Debug("Downloading [{0}] to [{1}]", url, fileName); - - var stopWatch = Stopwatch.StartNew(); - var uri = new HttpUri(url); - - using (var webClient = new GZipWebClient()) - { - webClient.Headers.Add(HttpRequestHeader.UserAgent, _userAgentBuilder.GetUserAgent()); - webClient.Proxy = GetProxy(uri); - webClient.DownloadFile(uri.FullUri, fileName); - stopWatch.Stop(); - _logger.Debug("Downloading Completed. took {0:0}s", stopWatch.Elapsed.Seconds); - } - } - catch (WebException e) - { - _logger.Warn("Failed to get response from: {0} {1}", url, e.Message); - throw; - } - catch (Exception e) - { - _logger.Warn(e, "Failed to get response from: " + url); - throw; - } - } - - protected virtual IWebProxy GetProxy(HttpUri uri) - { - IWebProxy proxy = null; - var proxySettings = _proxySettingsProvider.GetProxySettings(uri); + var key = proxySettings?.Key ?? NO_PROXY_KEY; + + return _httpClientCache.Get(key, () => CreateHttpClient(proxySettings)); + } + + protected virtual System.Net.Http.HttpClient CreateHttpClient(HttpProxySettings proxySettings) + { + var handler = new SocketsHttpHandler() + { + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Brotli, + UseCookies = false, // sic - we don't want to use a shared cookie container + AllowAutoRedirect = false, + Credentials = GetCredentialCache(), + PreAuthenticate = true, + MaxConnectionsPerServer = 12, + ConnectCallback = onConnect, + SslOptions = new SslClientAuthenticationOptions + { + RemoteCertificateValidationCallback = _certificateValidationService.ShouldByPassValidationError + } + }; + if (proxySettings != null) { - proxy = _createManagedWebProxy.GetWebProxy(proxySettings); + handler.Proxy = _createManagedWebProxy.GetWebProxy(proxySettings); } - return proxy; + var client = new System.Net.Http.HttpClient(handler) + { + Timeout = Timeout.InfiniteTimeSpan + }; + + return client; } - protected virtual void AddRequestHeaders(HttpWebRequest webRequest, HttpHeader headers) + protected virtual void AddRequestHeaders(HttpRequestMessage webRequest, HttpHeader headers) { foreach (var header in headers) { switch (header.Key) { case "Accept": - webRequest.Accept = header.Value; + webRequest.Headers.Accept.ParseAdd(header.Value); break; case "Connection": - webRequest.Connection = header.Value; + webRequest.Headers.Connection.Clear(); + webRequest.Headers.Connection.Add(header.Value); break; case "Content-Length": - webRequest.ContentLength = Convert.ToInt64(header.Value); + AddContentHeader(webRequest, "Content-Length", header.Value); break; case "Content-Type": - webRequest.ContentType = header.Value; + AddContentHeader(webRequest, "Content-Type", header.Value); break; case "Date": - webRequest.Date = HttpHeader.ParseDateTime(header.Value); + webRequest.Headers.Remove("Date"); + webRequest.Headers.Date = HttpHeader.ParseDateTime(header.Value); break; case "Expect": - webRequest.Expect = header.Value; + webRequest.Headers.Expect.ParseAdd(header.Value); break; case "Host": - webRequest.Host = header.Value; + webRequest.Headers.Host = header.Value; break; case "If-Modified-Since": - webRequest.IfModifiedSince = HttpHeader.ParseDateTime(header.Value); + webRequest.Headers.IfModifiedSince = HttpHeader.ParseDateTime(header.Value); break; case "Range": throw new NotImplementedException(); case "Referer": - webRequest.Referer = header.Value; + webRequest.Headers.Add("Referer", header.Value); break; case "Transfer-Encoding": - webRequest.TransferEncoding = header.Value; + webRequest.Headers.TransferEncoding.ParseAdd(header.Value); break; case "User-Agent": throw new NotSupportedException("User-Agent other than Lidarr not allowed."); @@ -220,5 +237,84 @@ namespace NzbDrone.Common.Http.Dispatchers } } } + + private void AddContentHeader(HttpRequestMessage request, string header, string value) + { + var headers = request.Content?.Headers; + if (headers == null) + { + return; + } + + headers.Remove(header); + headers.Add(header, value); + } + + private CredentialCache GetCredentialCache() + { + return _credentialCache.Get("credentialCache", () => new CredentialCache()); + } + + private static async ValueTask onConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken) + { + // Until .NET supports an implementation of Happy Eyeballs (https://tools.ietf.org/html/rfc8305#section-2), let's make IPv4 fallback work in a simple way. + // This issue is being tracked at https://github.com/dotnet/runtime/issues/26177 and expected to be fixed in .NET 6. + if (useIPv6) + { + try + { + var localToken = cancellationToken; + + if (!hasResolvedIPv6Availability) + { + // to make things move fast, use a very low timeout for the initial ipv6 attempt. + var quickFailCts = new CancellationTokenSource(connection_establish_timeout); + var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, quickFailCts.Token); + + localToken = linkedTokenSource.Token; + } + + return await attemptConnection(AddressFamily.InterNetworkV6, context, localToken); + } + catch + { + // very naively fallback to ipv4 permanently for this execution based on the response of the first connection attempt. + // note that this may cause users to eventually get switched to ipv4 (on a random failure when they are switching networks, for instance) + // but in the interest of keeping this implementation simple, this is acceptable. + useIPv6 = false; + } + finally + { + hasResolvedIPv6Availability = true; + } + } + + // fallback to IPv4. + return await attemptConnection(AddressFamily.InterNetwork, context, cancellationToken); + } + + private static async ValueTask attemptConnection(AddressFamily addressFamily, SocketsHttpConnectionContext context, CancellationToken cancellationToken) + { + // The following socket constructor will create a dual-mode socket on systems where IPV6 is available. + var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp) + { + // Turn off Nagle's algorithm since it degrades performance in most HttpClient scenarios. + NoDelay = true + }; + + try + { + await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false); + + // The stream should take the ownership of the underlying socket, + // closing it when it's disposed. + return new NetworkStream(socket, ownsSocket: true); + } + catch + { + socket.Dispose(); + throw; + } + } } } diff --git a/src/NzbDrone.Common/Http/GZipWebClient.cs b/src/NzbDrone.Common/Http/GZipWebClient.cs deleted file mode 100644 index 191bfb10b..000000000 --- a/src/NzbDrone.Common/Http/GZipWebClient.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Net; - -namespace NzbDrone.Common.Http -{ - public class GZipWebClient : WebClient - { - protected override WebRequest GetWebRequest(Uri address) - { - var request = (HttpWebRequest)base.GetWebRequest(address); - request.AutomaticDecompression = DecompressionMethods.GZip; - return request; - } - } -} diff --git a/src/NzbDrone.Common/Http/HttpAccept.cs b/src/NzbDrone.Common/Http/HttpAccept.cs index 8e76f87d3..21367b3a6 100644 --- a/src/NzbDrone.Common/Http/HttpAccept.cs +++ b/src/NzbDrone.Common/Http/HttpAccept.cs @@ -4,6 +4,7 @@ { public static readonly HttpAccept Rss = new HttpAccept("application/rss+xml, text/rss+xml, application/xml, text/xml"); public static readonly HttpAccept Json = new HttpAccept("application/json"); + public static readonly HttpAccept JsonCharset = new HttpAccept("application/json; charset=utf-8"); public static readonly HttpAccept Html = new HttpAccept("text/html"); public string Value { get; private set; } diff --git a/src/NzbDrone.Common/Http/HttpClient.cs b/src/NzbDrone.Common/Http/HttpClient.cs index 5c23af7c8..9ae684958 100644 --- a/src/NzbDrone.Common/Http/HttpClient.cs +++ b/src/NzbDrone.Common/Http/HttpClient.cs @@ -1,8 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Net; +using System.Net.Http; using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.EnvironmentInfo; @@ -119,8 +121,6 @@ namespace NzbDrone.Common.Http var stopWatch = Stopwatch.StartNew(); - PrepareRequestCookies(request, cookieContainer); - var response = _httpDispatcher.GetResponse(request, cookieContainer); HandleResponseCookies(response, cookieContainer); @@ -134,7 +134,7 @@ namespace NzbDrone.Common.Http response = interceptor.PostResponse(response); } - if (request.LogResponseContent) + if (request.LogResponseContent && response.ResponseData != null) { _logger.Trace("Response content ({0} bytes): {1}", response.ResponseData.Length, response.Content); } @@ -187,57 +187,97 @@ namespace NzbDrone.Common.Http } } - private void PrepareRequestCookies(HttpRequest request, CookieContainer cookieContainer) + private void HandleResponseCookies(HttpResponse response, CookieContainer container) { - // Don't collect persistnet cookies for intermediate/redirected urls. - /*lock (_cookieContainerCache) + foreach (Cookie cookie in container.GetAllCookies()) { - var presistentContainer = _cookieContainerCache.Get("container", () => new CookieContainer()); - var persistentCookies = presistentContainer.GetCookies((Uri)request.Url); - var existingCookies = cookieContainer.GetCookies((Uri)request.Url); + cookie.Expired = true; + } - cookieContainer.Add(persistentCookies); - cookieContainer.Add(existingCookies); - }*/ - } - - private void HandleResponseCookies(HttpResponse response, CookieContainer cookieContainer) - { var cookieHeaders = response.GetCookieHeaders(); + if (cookieHeaders.Empty()) { return; } + AddCookiesToContainer(response.Request.Url, cookieHeaders, container); + if (response.Request.StoreResponseCookie) { lock (_cookieContainerCache) { var persistentCookieContainer = _cookieContainerCache.Get("container", () => new CookieContainer()); - foreach (var cookieHeader in cookieHeaders) - { - try - { - persistentCookieContainer.SetCookies((Uri)response.Request.Url, cookieHeader); - } - catch (Exception ex) - { - _logger.Debug(ex, "Invalid cookie in {0}", response.Request.Url); - } - } + AddCookiesToContainer(response.Request.Url, cookieHeaders, persistentCookieContainer); + } + } + } + + private void AddCookiesToContainer(HttpUri url, string[] cookieHeaders, CookieContainer container) + { + foreach (var cookieHeader in cookieHeaders) + { + try + { + container.SetCookies((Uri)url, cookieHeader); + } + catch (Exception ex) + { + _logger.Debug(ex, "Invalid cookie in {0}", url); } } } public void DownloadFile(string url, string fileName) { - _httpDispatcher.DownloadFile(url, fileName); + var fileNamePart = fileName + ".part"; + + try + { + var fileInfo = new FileInfo(fileName); + if (fileInfo.Directory != null && !fileInfo.Directory.Exists) + { + fileInfo.Directory.Create(); + } + + _logger.Debug("Downloading [{0}] to [{1}]", url, fileName); + + var stopWatch = Stopwatch.StartNew(); + using (var fileStream = new FileStream(fileNamePart, FileMode.Create, FileAccess.ReadWrite)) + { + var request = new HttpRequest(url); + request.AllowAutoRedirect = true; + request.ResponseStream = fileStream; + var response = Get(request); + + if (response.Headers.ContentType != null && response.Headers.ContentType.Contains("text/html")) + { + throw new HttpException(request, response, "Site responded with html content."); + } + } + + stopWatch.Stop(); + if (File.Exists(fileName)) + { + File.Delete(fileName); + } + + File.Move(fileNamePart, fileName); + _logger.Debug("Downloading Completed. took {0:0}s", stopWatch.Elapsed.Seconds); + } + finally + { + if (File.Exists(fileNamePart)) + { + File.Delete(fileNamePart); + } + } } public HttpResponse Get(HttpRequest request) { - request.Method = HttpMethod.GET; + request.Method = HttpMethod.Get; return Execute(request); } @@ -251,13 +291,13 @@ namespace NzbDrone.Common.Http public HttpResponse Head(HttpRequest request) { - request.Method = HttpMethod.HEAD; + request.Method = HttpMethod.Head; return Execute(request); } public HttpResponse Post(HttpRequest request) { - request.Method = HttpMethod.POST; + request.Method = HttpMethod.Post; return Execute(request); } diff --git a/src/NzbDrone.Common/Http/HttpException.cs b/src/NzbDrone.Common/Http/HttpException.cs index d78dd5b3e..c3ad7a1fa 100644 --- a/src/NzbDrone.Common/Http/HttpException.cs +++ b/src/NzbDrone.Common/Http/HttpException.cs @@ -26,7 +26,7 @@ namespace NzbDrone.Common.Http public override string ToString() { - if (Response != null) + if (Response != null && Response.ResponseData != null) { return base.ToString() + Environment.NewLine + Response.Content; } diff --git a/src/NzbDrone.Common/Http/HttpHeader.cs b/src/NzbDrone.Common/Http/HttpHeader.cs index 4aee4092f..f4f1e8a84 100644 --- a/src/NzbDrone.Common/Http/HttpHeader.cs +++ b/src/NzbDrone.Common/Http/HttpHeader.cs @@ -4,11 +4,26 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; using System.Linq; +using System.Net.Http.Headers; using System.Text; using NzbDrone.Common.Extensions; namespace NzbDrone.Common.Http { + public static class WebHeaderCollectionExtensions + { + public static NameValueCollection ToNameValueCollection(this HttpHeaders headers) + { + var result = new NameValueCollection(); + foreach (var header in headers) + { + result.Add(header.Key, header.Value.ConcatToString(";")); + } + + return result; + } + } + public class HttpHeader : NameValueCollection, IEnumerable>, IEnumerable { public HttpHeader(NameValueCollection headers) @@ -16,6 +31,11 @@ namespace NzbDrone.Common.Http { } + public HttpHeader(HttpHeaders headers) + : base(headers.ToNameValueCollection()) + { + } + public HttpHeader() { } diff --git a/src/NzbDrone.Common/Http/HttpMethod.cs b/src/NzbDrone.Common/Http/HttpMethod.cs deleted file mode 100644 index 8964bbef6..000000000 --- a/src/NzbDrone.Common/Http/HttpMethod.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace NzbDrone.Common.Http -{ - public enum HttpMethod - { - GET, - POST, - PUT, - DELETE, - HEAD, - OPTIONS, - PATCH, - MERGE - } -} diff --git a/src/NzbDrone.Common/Http/HttpRequest.cs b/src/NzbDrone.Common/Http/HttpRequest.cs index 99bec5f8f..c9ef99f62 100644 --- a/src/NzbDrone.Common/Http/HttpRequest.cs +++ b/src/NzbDrone.Common/Http/HttpRequest.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Net; +using System.Net.Http; using System.Text; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; @@ -12,6 +13,7 @@ namespace NzbDrone.Common.Http { public HttpRequest(string url, HttpAccept httpAccept = null) { + Method = HttpMethod.Get; Url = new HttpUri(url); Headers = new HttpHeader(); AllowAutoRedirect = true; @@ -35,6 +37,7 @@ namespace NzbDrone.Common.Http public HttpHeader Headers { get; set; } public byte[] ContentData { get; set; } public string ContentSummary { get; set; } + public ICredentials Credentials { get; set; } public bool SuppressHttpError { get; set; } public IEnumerable SuppressHttpErrorStatusCodes { get; set; } public bool UseSimplifiedUserAgent { get; set; } @@ -48,6 +51,7 @@ namespace NzbDrone.Common.Http public TimeSpan RequestTimeout { get; set; } public TimeSpan RateLimit { get; set; } public string RateLimitKey { get; set; } + public Stream ResponseStream { get; set; } public override string ToString() { @@ -84,12 +88,5 @@ namespace NzbDrone.Common.Http var encoding = HttpHeader.GetEncodingFromContentType(Headers.ContentType); ContentData = encoding.GetBytes(data); } - - public void AddBasicAuthentication(string username, string password) - { - var authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes($"{username}:{password}")); - - Headers.Set("Authorization", "Basic " + authInfo); - } } } diff --git a/src/NzbDrone.Common/Http/HttpRequestBuilder.cs b/src/NzbDrone.Common/Http/HttpRequestBuilder.cs index a8405a15c..32fafd56f 100644 --- a/src/NzbDrone.Common/Http/HttpRequestBuilder.cs +++ b/src/NzbDrone.Common/Http/HttpRequestBuilder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; +using System.Net.Http; using System.Text; using NzbDrone.Common.Extensions; @@ -25,17 +26,16 @@ namespace NzbDrone.Common.Http public bool ConnectionKeepAlive { get; set; } public TimeSpan RateLimit { get; set; } public bool LogResponseContent { get; set; } - public NetworkCredential NetworkCredential { get; set; } + public ICredentials NetworkCredential { get; set; } public Dictionary Cookies { get; private set; } public List FormData { get; private set; } - public Action PostProcess { get; set; } public HttpRequestBuilder(string baseUrl) { BaseUrl = new HttpUri(baseUrl); ResourceUrl = string.Empty; - Method = HttpMethod.GET; + Method = HttpMethod.Get; QueryParams = new List>(); SuffixQueryParams = new List>(); Segments = new Dictionary(); @@ -108,13 +108,7 @@ namespace NzbDrone.Common.Http request.ConnectionKeepAlive = ConnectionKeepAlive; request.RateLimit = RateLimit; request.LogResponseContent = LogResponseContent; - - if (NetworkCredential != null) - { - var authInfo = NetworkCredential.UserName + ":" + NetworkCredential.Password; - authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo)); - request.Headers.Set("Authorization", "Basic " + authInfo); - } + request.Credentials = NetworkCredential; foreach (var header in Headers) { @@ -271,7 +265,7 @@ namespace NzbDrone.Common.Http public virtual HttpRequestBuilder Post() { - Method = HttpMethod.POST; + Method = HttpMethod.Post; return this; } @@ -362,7 +356,7 @@ namespace NzbDrone.Common.Http public virtual HttpRequestBuilder AddFormParameter(string key, object value) { - if (Method != HttpMethod.POST) + if (Method != HttpMethod.Post) { throw new NotSupportedException("HttpRequest Method must be POST to add FormParameter."); } @@ -378,7 +372,7 @@ namespace NzbDrone.Common.Http public virtual HttpRequestBuilder AddFormUpload(string name, string fileName, byte[] data, string contentType = "application/octet-stream") { - if (Method != HttpMethod.POST) + if (Method != HttpMethod.Post) { throw new NotSupportedException("HttpRequest Method must be POST to add FormUpload."); } diff --git a/src/NzbDrone.Common/Http/JsonRpcRequestBuilder.cs b/src/NzbDrone.Common/Http/JsonRpcRequestBuilder.cs index ae987a23d..06b113e54 100644 --- a/src/NzbDrone.Common/Http/JsonRpcRequestBuilder.cs +++ b/src/NzbDrone.Common/Http/JsonRpcRequestBuilder.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; +using System.Net.Http; using Newtonsoft.Json; using NzbDrone.Common.Serializer; @@ -17,14 +18,14 @@ namespace NzbDrone.Common.Http public JsonRpcRequestBuilder(string baseUrl) : base(baseUrl) { - Method = HttpMethod.POST; + Method = HttpMethod.Post; JsonParameters = new List(); } public JsonRpcRequestBuilder(string baseUrl, string method, IEnumerable parameters) : base(baseUrl) { - Method = HttpMethod.POST; + Method = HttpMethod.Post; JsonMethod = method; JsonParameters = parameters.ToList(); } diff --git a/src/NzbDrone.Common/Http/XmlRpcRequestBuilder.cs b/src/NzbDrone.Common/Http/XmlRpcRequestBuilder.cs new file mode 100644 index 000000000..e03161702 --- /dev/null +++ b/src/NzbDrone.Common/Http/XmlRpcRequestBuilder.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Xml.Linq; +using NLog; +using NzbDrone.Common.Instrumentation; + +namespace NzbDrone.Common.Http +{ + public class XmlRpcRequestBuilder : HttpRequestBuilder + { + public static string XmlRpcContentType = "text/xml"; + + private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(XmlRpcRequestBuilder)); + + public string XmlMethod { get; private set; } + public List XmlParameters { get; private set; } + + public XmlRpcRequestBuilder(string baseUrl) + : base(baseUrl) + { + Method = HttpMethod.Post; + XmlParameters = new List(); + } + + public XmlRpcRequestBuilder(bool useHttps, string host, int port, string urlBase = null) + : this(BuildBaseUrl(useHttps, host, port, urlBase)) + { + } + + public override HttpRequestBuilder Clone() + { + var clone = base.Clone() as XmlRpcRequestBuilder; + clone.XmlParameters = new List(XmlParameters); + return clone; + } + + public XmlRpcRequestBuilder Call(string method, params object[] parameters) + { + var clone = Clone() as XmlRpcRequestBuilder; + clone.XmlMethod = method; + clone.XmlParameters = parameters.ToList(); + return clone; + } + + protected override void Apply(HttpRequest request) + { + base.Apply(request); + + request.Headers.ContentType = XmlRpcContentType; + + var methodCallElements = new List { new XElement("methodName", XmlMethod) }; + + if (XmlParameters.Any()) + { + var argElements = XmlParameters.Select(x => new XElement("param", ConvertParameter(x))).ToList(); + var paramsElement = new XElement("params", argElements); + methodCallElements.Add(paramsElement); + } + + var message = new XDocument( + new XDeclaration("1.0", "utf-8", "yes"), + new XElement("methodCall", methodCallElements)); + + var body = message.ToString(); + + Logger.Debug($"Executing remote method: {XmlMethod}"); + + Logger.Trace($"methodCall {XmlMethod} body:\n{body}"); + + request.SetContent(body); + } + + private static XElement ConvertParameter(object value) + { + XElement data; + + if (value is string s) + { + data = new XElement("string", s); + } + else if (value is List l) + { + data = new XElement("array", new XElement("data", l.Select(x => new XElement("value", new XElement("string", x))))); + } + else if (value is int i) + { + data = new XElement("int", i); + } + else if (value is byte[] bytes) + { + data = new XElement("base64", Convert.ToBase64String(bytes)); + } + else + { + throw new InvalidOperationException($"Unhandled argument type {value.GetType().Name}"); + } + + return new XElement("value", data); + } + } +} diff --git a/src/NzbDrone.Core.Test/Framework/CoreTest.cs b/src/NzbDrone.Core.Test/Framework/CoreTest.cs index 86807e0db..550309a34 100644 --- a/src/NzbDrone.Core.Test/Framework/CoreTest.cs +++ b/src/NzbDrone.Core.Test/Framework/CoreTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using NUnit.Framework; using NzbDrone.Common.Cache; using NzbDrone.Common.Cloud; @@ -10,6 +10,7 @@ using NzbDrone.Common.TPL; using NzbDrone.Core.Configuration; using NzbDrone.Core.Http; using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.Security; using NzbDrone.Test.Common; namespace NzbDrone.Core.Test.Framework @@ -24,7 +25,8 @@ namespace NzbDrone.Core.Test.Framework Mocker.SetConstant(new HttpProxySettingsProvider(Mocker.Resolve())); Mocker.SetConstant(new ManagedWebProxyFactory(Mocker.Resolve())); - Mocker.SetConstant(new ManagedHttpDispatcher(Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), TestLogger)); + Mocker.SetConstant(new X509CertificateValidationService(Mocker.Resolve(), TestLogger)); + Mocker.SetConstant(new ManagedHttpDispatcher(Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), TestLogger)); Mocker.SetConstant(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), TestLogger)); Mocker.SetConstant(new LidarrCloudRequestBuilder()); Mocker.SetConstant(Mocker.Resolve()); diff --git a/src/NzbDrone.Core.Test/IndexerTests/FileListTests/FileListFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/FileListTests/FileListFixture.cs index cd4da25ee..732825188 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/FileListTests/FileListFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/FileListTests/FileListFixture.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Net.Http; using FluentAssertions; using Moq; using NUnit.Framework; @@ -30,7 +31,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests var recentFeed = ReadAllText(@"Files/Indexers/FileList/RecentFeed.json"); Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Get))) .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); var releases = Subject.FetchRecent(); diff --git a/src/NzbDrone.Core.Test/IndexerTests/GazelleTests/GazelleFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/GazelleTests/GazelleFixture.cs index c1d254b96..2bedec4a2 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/GazelleTests/GazelleFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/GazelleTests/GazelleFixture.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Net.Http; using FluentAssertions; using Moq; using NUnit.Framework; @@ -35,15 +36,15 @@ namespace NzbDrone.Core.Test.IndexerTests.GazelleTests var indexFeed = ReadAllText(@"Files/Indexers/Gazelle/GazelleIndex.json"); Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET && v.Url.FullUri.Contains("ajax.php?action=browse")))) + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Get && v.Url.FullUri.Contains("ajax.php?action=browse")))) .Returns(r => new HttpResponse(r, new HttpHeader { ContentType = "application/json" }, recentFeed)); Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.POST && v.Url.FullUri.Contains("ajax.php?action=index")))) + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Post && v.Url.FullUri.Contains("ajax.php?action=index")))) .Returns(r => new HttpResponse(r, new HttpHeader(), indexFeed)); Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.POST && v.Url.FullUri.Contains("login.php")))) + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Post && v.Url.FullUri.Contains("login.php")))) .Returns(r => new HttpResponse(r, new HttpHeader(), indexFeed)); var releases = Subject.FetchRecent(); diff --git a/src/NzbDrone.Core.Test/IndexerTests/HeadphonesTests/HeadphonesFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/HeadphonesTests/HeadphonesFixture.cs index 88678f923..418ad45fc 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/HeadphonesTests/HeadphonesFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/HeadphonesTests/HeadphonesFixture.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Net.Http; using FluentAssertions; using Moq; using NUnit.Framework; @@ -42,7 +43,7 @@ namespace NzbDrone.Core.Test.IndexerTests.HeadphonesTests var recentFeed = ReadAllText(@"Files/Indexers/Headphones/Headphones.xml"); Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Get))) .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); var releases = Subject.FetchRecent(); diff --git a/src/NzbDrone.Core.Test/IndexerTests/IPTorrentsTests/IPTorrentsFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/IPTorrentsTests/IPTorrentsFixture.cs index cabc82040..14238d93f 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/IPTorrentsTests/IPTorrentsFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/IPTorrentsTests/IPTorrentsFixture.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Net.Http; using FluentAssertions; using Moq; using NUnit.Framework; @@ -88,7 +89,7 @@ namespace NzbDrone.Core.Test.IndexerTests.IPTorrentsTests var recentFeed = ReadAllText(@"Files/Indexers/IPTorrents/IPTorrents.xml"); Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Get))) .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); var releases = Subject.FetchRecent(); diff --git a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs index 3d244f5fe..b0b8ecd8b 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Net; +using System.Net.Http; using FluentAssertions; using Moq; using NUnit.Framework; @@ -43,7 +44,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests var recentFeed = ReadAllText(@"Files/Indexers/Newznab/newznab_nzb_su.xml"); Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Get))) .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); var releases = Subject.FetchRecent(); diff --git a/src/NzbDrone.Core.Test/IndexerTests/NyaaTests/NyaaFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/NyaaTests/NyaaFixture.cs index f04f8a571..2d2d957d3 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/NyaaTests/NyaaFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/NyaaTests/NyaaFixture.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Linq; +using System.Net.Http; using FluentAssertions; using Moq; using NUnit.Framework; @@ -30,7 +31,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NyaaTests var recentFeed = ReadAllText(@"Files/Indexers/Nyaa/Nyaa.xml"); Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Get))) .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); var releases = Subject.FetchRecent(); diff --git a/src/NzbDrone.Core.Test/IndexerTests/RarbgTests/RarbgFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/RarbgTests/RarbgFixture.cs index c640c07e3..97ae0fcdd 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/RarbgTests/RarbgFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/RarbgTests/RarbgFixture.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Linq; +using System.Net.Http; using FluentAssertions; using Moq; using NUnit.Framework; @@ -35,7 +36,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests var recentFeed = ReadAllText(@"Files/Indexers/Rarbg/RecentFeed_v2.json"); Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Get))) .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); var releases = Subject.FetchRecent(); @@ -62,7 +63,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests public void should_parse_error_20_as_empty_results() { Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Get))) .Returns(r => new HttpResponse(r, new HttpHeader(), "{ error_code: 20, error: \"some message\" }")); var releases = Subject.FetchRecent(); @@ -74,7 +75,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests public void should_warn_on_unknown_error() { Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Get))) .Returns(r => new HttpResponse(r, new HttpHeader(), "{ error_code: 25, error: \"some message\" }")); var releases = Subject.FetchRecent(); diff --git a/src/NzbDrone.Core.Test/IndexerTests/RedactedTests/RedactedFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/RedactedTests/RedactedFixture.cs index 68da49f7c..b13a80974 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/RedactedTests/RedactedFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/RedactedTests/RedactedFixture.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Net.Http; using FluentAssertions; using Moq; using NUnit.Framework; @@ -33,13 +34,13 @@ namespace NzbDrone.Core.Test.IndexerTests.GazelleTests var indexFeed = ReadAllText(@"Files/Indexers/Gazelle/GazelleIndex.json"); Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET && + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Get && v.Url.FullUri.Contains("ajax.php?action=browse") && v.Headers.Get("Authorization") == ((RedactedSettings)Subject.Definition.Settings).ApiKey))) .Returns(r => new HttpResponse(r, new HttpHeader { ContentType = "application/json" }, recentFeed)); Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET && + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Get && v.Url.FullUri.Contains("ajax.php?action=index") && v.Headers.Get("Authorization") == ((RedactedSettings)Subject.Definition.Settings).ApiKey))) .Returns(r => new HttpResponse(r, new HttpHeader(), indexFeed)); diff --git a/src/NzbDrone.Core.Test/IndexerTests/TorrentleechTests/TorrentleechFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/TorrentleechTests/TorrentleechFixture.cs index 691abd0a4..59a284276 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/TorrentleechTests/TorrentleechFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/TorrentleechTests/TorrentleechFixture.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Linq; +using System.Net.Http; using FluentAssertions; using Moq; using NUnit.Framework; @@ -30,7 +31,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentleechTests var recentFeed = ReadAllText(@"Files/Indexers/Torrentleech/Torrentleech.xml"); Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Get))) .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); var releases = Subject.FetchRecent(); diff --git a/src/NzbDrone.Core.Test/IndexerTests/TorznabTests/TorznabFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/TorznabTests/TorznabFixture.cs index 6b17cb077..48715ef21 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/TorznabTests/TorznabFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/TorznabTests/TorznabFixture.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Net.Http; using FizzWare.NBuilder; using FluentAssertions; using Moq; @@ -48,7 +49,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_hdaccess_net.xml"); Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Get))) .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); var releases = Subject.FetchRecent(); @@ -77,7 +78,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_tpb.xml"); Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Get))) .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); var releases = Subject.FetchRecent(); @@ -130,7 +131,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests (Subject.Definition.Settings as TorznabSettings).BaseUrl = baseUrl; Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Get))) .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); var result = new NzbDroneValidationResult(Subject.Test()); @@ -145,7 +146,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_tpb.xml"); Mocker.GetMock() - .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.Get))) .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); (Subject.Definition.Settings as TorznabSettings).ApiPath = apiPath; diff --git a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs index e895f34de..0af3f4a95 100644 --- a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs +++ b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; -using CookComputing.XmlRpc; using FluentValidation.Results; using NLog; using NzbDrone.Common.Disk; @@ -90,12 +89,7 @@ namespace NzbDrone.Core.Download.Clients.Aria2 var downloadSpeed = long.Parse(torrent.DownloadSpeed); var status = DownloadItemStatus.Failed; - var title = ""; - - if (torrent.Bittorrent?.ContainsKey("info") == true && ((XmlRpcStruct)torrent.Bittorrent["info"]).ContainsKey("name")) - { - title = ((XmlRpcStruct)torrent.Bittorrent["info"])["name"].ToString(); - } + var title = torrent.Bittorrent?.Name ?? ""; switch (torrent.Status) { diff --git a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Containers.cs b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Containers.cs index d4ab5a49c..24122b65c 100644 --- a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Containers.cs +++ b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Containers.cs @@ -1,111 +1,161 @@ -using CookComputing.XmlRpc; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using System.Xml.XPath; +using NzbDrone.Core.Download.Extensions; namespace NzbDrone.Core.Download.Clients.Aria2 { - public class Aria2Version + public class Aria2Fault { - [XmlRpcMember("version")] - public string Version; + public Aria2Fault(XElement element) + { + foreach (var e in element.XPathSelectElements("./value/struct/member")) + { + var name = e.ElementAsString("name"); + if (name == "faultCode") + { + FaultCode = e.Element("value").ElementAsInt("int"); + } + else if (name == "faultString") + { + FaultString = e.Element("value").GetStringValue(); + } + } + } - [XmlRpcMember("enabledFeatures")] - public string[] EnabledFeatures; + public int FaultCode { get; set; } + public string FaultString { get; set; } } - public class Aria2Uri + public class Aria2Version { - [XmlRpcMember("status")] - public string Status; + public Aria2Version(XElement element) + { + foreach (var e in element.XPathSelectElements("./struct/member")) + { + if (e.ElementAsString("name") == "version") + { + Version = e.Element("value").GetStringValue(); + } + } + } - [XmlRpcMember("uri")] - public string Uri; + public string Version { get; set; } } public class Aria2File { - [XmlRpcMember("index")] - public string Index; + public Aria2File(XElement element) + { + foreach (var e in element.XPathSelectElements("./struct/member")) + { + var name = e.ElementAsString("name"); - [XmlRpcMember("length")] - public string Length; + if (name == "path") + { + Path = e.Element("value").GetStringValue(); + } + } + } - [XmlRpcMember("completedLength")] - public string CompletedLength; + public string Path { get; set; } + } - [XmlRpcMember("path")] - public string Path; + public class Aria2Dict + { + public Aria2Dict(XElement element) + { + Dict = new Dictionary(); - [XmlRpcMember("selected")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string Selected; + foreach (var e in element.XPathSelectElements("./struct/member")) + { + Dict.Add(e.ElementAsString("name"), e.Element("value").GetStringValue()); + } + } - [XmlRpcMember("uris")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public Aria2Uri[] Uris; + public Dictionary Dict { get; set; } + } + + public class Aria2Bittorrent + { + public Aria2Bittorrent(XElement element) + { + foreach (var e in element.Descendants("member")) + { + if (e.ElementAsString("name") == "name") + { + Name = e.Element("value").GetStringValue(); + } + } + } + + public string Name; } public class Aria2Status { - [XmlRpcMember("bittorrent")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public XmlRpcStruct Bittorrent; + public Aria2Status(XElement element) + { + foreach (var e in element.XPathSelectElements("./struct/member")) + { + var name = e.ElementAsString("name"); - [XmlRpcMember("bitfield")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string Bitfield; + if (name == "bittorrent") + { + Bittorrent = new Aria2Bittorrent(e.Element("value")); + } + else if (name == "infoHash") + { + InfoHash = e.Element("value").GetStringValue(); + } + else if (name == "completedLength") + { + CompletedLength = e.Element("value").GetStringValue(); + } + else if (name == "downloadSpeed") + { + DownloadSpeed = e.Element("value").GetStringValue(); + } + else if (name == "files") + { + Files = e.XPathSelectElement("./value/array/data") + .Elements() + .Select(x => new Aria2File(x)) + .ToArray(); + } + else if (name == "gid") + { + Gid = e.Element("value").GetStringValue(); + } + else if (name == "status") + { + Status = e.Element("value").GetStringValue(); + } + else if (name == "totalLength") + { + TotalLength = e.Element("value").GetStringValue(); + } + else if (name == "uploadLength") + { + UploadLength = e.Element("value").GetStringValue(); + } + else if (name == "errorMessage") + { + ErrorMessage = e.Element("value").GetStringValue(); + } + } + } - [XmlRpcMember("infoHash")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string InfoHash; - - [XmlRpcMember("completedLength")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string CompletedLength; - - [XmlRpcMember("connections")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string Connections; - - [XmlRpcMember("dir")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string Dir; - - [XmlRpcMember("downloadSpeed")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string DownloadSpeed; - - [XmlRpcMember("files")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public Aria2File[] Files; - - [XmlRpcMember("gid")] - public string Gid; - - [XmlRpcMember("numPieces")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string NumPieces; - - [XmlRpcMember("pieceLength")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string PieceLength; - - [XmlRpcMember("status")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string Status; - - [XmlRpcMember("totalLength")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string TotalLength; - - [XmlRpcMember("uploadLength")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string UploadLength; - - [XmlRpcMember("uploadSpeed")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string UploadSpeed; - - [XmlRpcMember("errorMessage")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string ErrorMessage; + public Aria2Bittorrent Bittorrent { get; set; } + public string InfoHash { get; set; } + public string CompletedLength { get; set; } + public string DownloadSpeed { get; set; } + public Aria2File[] Files { get; set; } + public string Gid { get; set; } + public string Status { get; set; } + public string TotalLength { get; set; } + public string UploadLength { get; set; } + public string ErrorMessage { get; set; } } } diff --git a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Proxy.cs b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Proxy.cs index 3e7b5a6be..f141ef7ce 100644 --- a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Proxy.cs +++ b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Proxy.cs @@ -1,9 +1,9 @@ -using System; -using System.Collections; using System.Collections.Generic; -using System.Net; -using CookComputing.XmlRpc; -using NLog; +using System.Linq; +using System.Xml.Linq; +using System.Xml.XPath; +using NzbDrone.Common.Http; +using NzbDrone.Core.Download.Extensions; namespace NzbDrone.Core.Download.Clients.Aria2 { @@ -19,103 +19,61 @@ namespace NzbDrone.Core.Download.Clients.Aria2 Aria2Status GetFromGID(Aria2Settings settings, string gid); } - public interface IAria2 : IXmlRpcProxy - { - [XmlRpcMethod("aria2.getVersion")] - Aria2Version GetVersion(string token); - - [XmlRpcMethod("aria2.addUri")] - string AddUri(string token, string[] uri); - - [XmlRpcMethod("aria2.addTorrent")] - string AddTorrent(string token, byte[] torrent); - - [XmlRpcMethod("aria2.forceRemove")] - string Remove(string token, string gid); - - [XmlRpcMethod("aria2.removeDownloadResult")] - string RemoveResult(string token, string gid); - - [XmlRpcMethod("aria2.tellStatus")] - Aria2Status GetFromGid(string token, string gid); - - [XmlRpcMethod("aria2.getGlobalOption")] - XmlRpcStruct GetGlobalOption(string token); - - [XmlRpcMethod("aria2.tellActive")] - Aria2Status[] GetActive(string token); - - [XmlRpcMethod("aria2.tellWaiting")] - Aria2Status[] GetWaiting(string token, int offset, int num); - - [XmlRpcMethod("aria2.tellStopped")] - Aria2Status[] GetStopped(string token, int offset, int num); - } - public class Aria2Proxy : IAria2Proxy { - private readonly Logger _logger; + private readonly IHttpClient _httpClient; - public Aria2Proxy(Logger logger) + public Aria2Proxy(IHttpClient httpClient) { - _logger = logger; - } - - private string GetToken(Aria2Settings settings) - { - return $"token:{settings?.SecretToken}"; - } - - private string GetURL(Aria2Settings settings) - { - return $"http{(settings.UseSsl ? "s" : "")}://{settings.Host}:{settings.Port}{settings.RpcPath}"; + _httpClient = httpClient; } public string GetVersion(Aria2Settings settings) { - _logger.Trace("> aria2.getVersion"); + var response = ExecuteRequest(settings, "aria2.getVersion", GetToken(settings)); - var client = BuildClient(settings); - var version = ExecuteRequest(() => client.GetVersion(GetToken(settings))); + var element = response.XPathSelectElement("./methodResponse/params/param/value"); - _logger.Trace("< aria2.getVersion"); + var version = new Aria2Version(element); return version.Version; } public Aria2Status GetFromGID(Aria2Settings settings, string gid) { - _logger.Trace("> aria2.tellStatus"); + var response = ExecuteRequest(settings, "aria2.tellStatus", GetToken(settings), gid); - var client = BuildClient(settings); - var found = ExecuteRequest(() => client.GetFromGid(GetToken(settings), gid)); + var element = response.XPathSelectElement("./methodResponse/params/param/value"); - _logger.Trace("< aria2.tellStatus"); + return new Aria2Status(element); + } - return found; + private List GetTorrentsMethod(Aria2Settings settings, string method, params object[] args) + { + var allArgs = new List { GetToken(settings) }; + if (args.Any()) + { + allArgs.AddRange(args); + } + + var response = ExecuteRequest(settings, method, allArgs.ToArray()); + + var element = response.XPathSelectElement("./methodResponse/params/param/value/array/data"); + + var torrents = element?.Elements() + .Select(x => new Aria2Status(x)) + .ToList() + ?? new List(); + return torrents; } public List GetTorrents(Aria2Settings settings) { - _logger.Trace("> aria2.tellActive"); + var active = GetTorrentsMethod(settings, "aria2.tellActive"); - var client = BuildClient(settings); + var waiting = GetTorrentsMethod(settings, "aria2.tellWaiting", 0, 10 * 1024); - var active = ExecuteRequest(() => client.GetActive(GetToken(settings))); - - _logger.Trace("< aria2.tellActive"); - - _logger.Trace("> aria2.tellWaiting"); - - var waiting = ExecuteRequest(() => client.GetWaiting(GetToken(settings), 0, 10 * 1024)); - - _logger.Trace("< aria2.tellWaiting"); - - _logger.Trace("> aria2.tellStopped"); - - var stopped = ExecuteRequest(() => client.GetStopped(GetToken(settings), 0, 10 * 1024)); - - _logger.Trace("< aria2.tellStopped"); + var stopped = GetTorrentsMethod(settings, "aria2.tellStopped", 0, 10 * 1024); var items = new List(); @@ -128,98 +86,79 @@ namespace NzbDrone.Core.Download.Clients.Aria2 public Dictionary GetGlobals(Aria2Settings settings) { - _logger.Trace("> aria2.getGlobalOption"); + var response = ExecuteRequest(settings, "aria2.getGlobalOption", GetToken(settings)); - var client = BuildClient(settings); - var options = ExecuteRequest(() => client.GetGlobalOption(GetToken(settings))); + var element = response.XPathSelectElement("./methodResponse/params/param/value"); - _logger.Trace("< aria2.getGlobalOption"); + var result = new Aria2Dict(element); - var ret = new Dictionary(); - - foreach (DictionaryEntry option in options) - { - ret.Add(option.Key.ToString(), option.Value?.ToString()); - } - - return ret; + return result.Dict; } public string AddMagnet(Aria2Settings settings, string magnet) { - _logger.Trace("> aria2.addUri"); + var response = ExecuteRequest(settings, "aria2.addUri", GetToken(settings), new List { magnet }); - var client = BuildClient(settings); - var gid = ExecuteRequest(() => client.AddUri(GetToken(settings), new[] { magnet })); - - _logger.Trace("< aria2.addUri"); + var gid = response.GetStringResponse(); return gid; } public string AddTorrent(Aria2Settings settings, byte[] torrent) { - _logger.Trace("> aria2.addTorrent"); + var response = ExecuteRequest(settings, "aria2.addTorrent", GetToken(settings), torrent); - var client = BuildClient(settings); - var gid = ExecuteRequest(() => client.AddTorrent(GetToken(settings), torrent)); - - _logger.Trace("< aria2.addTorrent"); + var gid = response.GetStringResponse(); return gid; } public bool RemoveTorrent(Aria2Settings settings, string gid) { - _logger.Trace("> aria2.forceRemove"); + var response = ExecuteRequest(settings, "aria2.forceRemove", GetToken(settings), gid); - var client = BuildClient(settings); - var gidres = ExecuteRequest(() => client.Remove(GetToken(settings), gid)); - - _logger.Trace("< aria2.forceRemove"); + var gidres = response.GetStringResponse(); return gid == gidres; } public bool RemoveCompletedTorrent(Aria2Settings settings, string gid) { - _logger.Trace("> aria2.removeDownloadResult"); + var response = ExecuteRequest(settings, "aria2.removeDownloadResult", GetToken(settings), gid); - var client = BuildClient(settings); - var result = ExecuteRequest(() => client.RemoveResult(GetToken(settings), gid)); - - _logger.Trace("< aria2.removeDownloadResult"); + var result = response.GetStringResponse(); return result == "OK"; } - private IAria2 BuildClient(Aria2Settings settings) + private string GetToken(Aria2Settings settings) { - var client = XmlRpcProxyGen.Create(); - client.Url = GetURL(settings); - - return client; + return $"token:{settings?.SecretToken}"; } - private T ExecuteRequest(Func task) + private XDocument ExecuteRequest(Aria2Settings settings, string methodName, params object[] args) { - try + var requestBuilder = new XmlRpcRequestBuilder(settings.UseSsl, settings.Host, settings.Port, settings.RpcPath) { - return task(); - } - catch (XmlRpcServerException ex) - { - throw new DownloadClientException("Unable to connect to aria2, please check your settings", ex); - } - catch (WebException ex) - { - if (ex.Status == WebExceptionStatus.TrustFailure) - { - throw new DownloadClientUnavailableException("Unable to connect to aria2, certificate validation failed.", ex); - } + LogResponseContent = true, + }; - throw new DownloadClientUnavailableException("Unable to connect to aria2, please check your settings", ex); + var request = requestBuilder.Call(methodName, args).Build(); + + var response = _httpClient.Execute(request); + + var doc = XDocument.Parse(response.Content); + + var faultElement = doc.XPathSelectElement("./methodResponse/fault"); + + if (faultElement != null) + { + var fault = new Aria2Fault(faultElement); + + throw new DownloadClientException($"Aria2 returned error code {fault.FaultCode}: {fault.FaultString}"); } + + return doc; } } } diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DiskStationProxyBase.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DiskStationProxyBase.cs index 5dfbea3ac..c1507aa2e 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DiskStationProxyBase.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DiskStationProxyBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net; +using System.Net.Http; using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.Http; @@ -142,15 +143,19 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies return authResponse.Data.SId; } - protected HttpRequestBuilder BuildRequest(DownloadStationSettings settings, string methodName, int apiVersion, HttpMethod httpVerb = HttpMethod.GET) + protected HttpRequestBuilder BuildRequest(DownloadStationSettings settings, string methodName, int apiVersion, HttpMethod httpVerb = null) { + httpVerb ??= HttpMethod.Get; + var info = GetApiInfo(_apiType, settings); return BuildRequest(settings, info, methodName, apiVersion, httpVerb); } - private HttpRequestBuilder BuildRequest(DownloadStationSettings settings, DiskStationApiInfo apiInfo, string methodName, int apiVersion, HttpMethod httpVerb = HttpMethod.GET) + private HttpRequestBuilder BuildRequest(DownloadStationSettings settings, DiskStationApiInfo apiInfo, string methodName, int apiVersion, HttpMethod httpVerb = null) { + httpVerb ??= HttpMethod.Get; + var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port).Resource($"webapi/{apiInfo.Path}"); requestBuilder.Method = httpVerb; requestBuilder.LogResponseContent = true; @@ -163,7 +168,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies throw new ArgumentOutOfRangeException(nameof(apiVersion)); } - if (httpVerb == HttpMethod.POST) + if (httpVerb == HttpMethod.Post) { if (apiInfo.NeedsAuthentication) { diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxyV1.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxyV1.cs index 8180a70ba..466a8c49c 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxyV1.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxyV1.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Generic; +using System.Net.Http; using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.Extensions; @@ -21,7 +22,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies public void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings) { - var requestBuilder = BuildRequest(settings, "create", 2, HttpMethod.POST); + var requestBuilder = BuildRequest(settings, "create", 2, HttpMethod.Post); if (downloadDirectory.IsNotNullOrWhiteSpace()) { diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxyV2.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxyV2.cs index 261f76e19..fa76c1d0d 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxyV2.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationTaskProxyV2.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Net.Http; using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.Extensions; @@ -25,7 +26,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies public void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings) { - var requestBuilder = BuildRequest(settings, "create", 2, HttpMethod.POST); + var requestBuilder = BuildRequest(settings, "create", 2, HttpMethod.Post); requestBuilder.AddFormParameter("type", "\"file\""); requestBuilder.AddFormParameter("file", "[\"fileData\"]"); diff --git a/src/NzbDrone.Core/Download/Clients/Flood/FloodProxy.cs b/src/NzbDrone.Core/Download/Clients/Flood/FloodProxy.cs index 4f27759f9..20a53da78 100644 --- a/src/NzbDrone.Core/Download/Clients/Flood/FloodProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/Flood/FloodProxy.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.Http; @@ -108,7 +109,7 @@ namespace NzbDrone.Core.Download.Clients.Flood { var verifyRequest = BuildRequest(settings).Resource("/auth/verify").Build(); - verifyRequest.Method = HttpMethod.GET; + verifyRequest.Method = HttpMethod.Get; HandleRequest(verifyRequest, settings); } @@ -181,7 +182,7 @@ namespace NzbDrone.Core.Download.Clients.Flood { var getTorrentsRequest = BuildRequest(settings).Resource("/torrents").Build(); - getTorrentsRequest.Method = HttpMethod.GET; + getTorrentsRequest.Method = HttpMethod.Get; return Json.Deserialize(HandleRequest(getTorrentsRequest, settings).Content).Torrents; } @@ -190,7 +191,7 @@ namespace NzbDrone.Core.Download.Clients.Flood { var contentsRequest = BuildRequest(settings).Resource($"/torrents/{hash}/contents").Build(); - contentsRequest.Method = HttpMethod.GET; + contentsRequest.Method = HttpMethod.Get; return Json.Deserialize>(HandleRequest(contentsRequest, settings).Content).ConvertAll(content => content.Path); } @@ -199,7 +200,7 @@ namespace NzbDrone.Core.Download.Clients.Flood { var tagsRequest = BuildRequest(settings).Resource("/torrents/tags").Build(); - tagsRequest.Method = HttpMethod.PATCH; + tagsRequest.Method = HttpMethod.Patch; var body = new Dictionary { @@ -215,7 +216,7 @@ namespace NzbDrone.Core.Download.Clients.Flood { var contentsRequest = BuildRequest(settings).Resource($"/client/settings").Build(); - contentsRequest.Method = HttpMethod.GET; + contentsRequest.Method = HttpMethod.Get; return Json.Deserialize(HandleRequest(contentsRequest, settings).Content); } diff --git a/src/NzbDrone.Core/Download/Clients/Hadouken/HadoukenProxy.cs b/src/NzbDrone.Core/Download/Clients/Hadouken/HadoukenProxy.cs index 42fcede8f..ec0dd1ffe 100644 --- a/src/NzbDrone.Core/Download/Clients/Hadouken/HadoukenProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/Hadouken/HadoukenProxy.cs @@ -74,7 +74,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken var requestBuilder = new JsonRpcRequestBuilder(baseUrl, method, parameters); requestBuilder.LogResponseContent = true; - requestBuilder.NetworkCredential = new NetworkCredential(settings.Username, settings.Password); + requestBuilder.NetworkCredential = new BasicNetworkCredential(settings.Username, settings.Password); requestBuilder.Headers.Add("Accept-Encoding", "gzip,deflate"); var httpRequest = requestBuilder.Build(); diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetProxy.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetProxy.cs index 6c7b5a53a..edd01faa3 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetProxy.cs @@ -229,7 +229,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget var requestBuilder = new JsonRpcRequestBuilder(baseUrl, method, parameters); requestBuilder.LogResponseContent = true; - requestBuilder.NetworkCredential = new NetworkCredential(settings.Username, settings.Password); + requestBuilder.NetworkCredential = new BasicNetworkCredential(settings.Username, settings.Password); var httpRequest = requestBuilder.Build(); diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV1.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV1.cs index d8549479d..b26e4929f 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV1.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV1.cs @@ -296,7 +296,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase) { LogResponseContent = true, - NetworkCredential = new NetworkCredential(settings.Username, settings.Password) + NetworkCredential = new BasicNetworkCredential(settings.Username, settings.Password) }; return requestBuilder; } diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV2.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV2.cs index 104a03d83..dd1a09e2f 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV2.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV2.cs @@ -338,7 +338,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase) { LogResponseContent = true, - NetworkCredential = new NetworkCredential(settings.Username, settings.Password) + NetworkCredential = new BasicNetworkCredential(settings.Username, settings.Password) }; return requestBuilder; } diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionProxy.cs b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionProxy.cs index 6f5c7296b..b9ae4605a 100644 --- a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionProxy.cs @@ -200,7 +200,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission .Accept(HttpAccept.Json); requestBuilder.LogResponseContent = true; - requestBuilder.NetworkCredential = new NetworkCredential(settings.Username, settings.Password); + requestBuilder.NetworkCredential = new BasicNetworkCredential(settings.Username, settings.Password); requestBuilder.AllowAutoRedirect = false; return requestBuilder; diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs index 6d440e57a..72c387396 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs @@ -129,6 +129,12 @@ namespace NzbDrone.Core.Download.Clients.RTorrent continue; } + // Ignore torrents with an empty path + if (torrent.Path.IsNullOrWhiteSpace()) + { + continue; + } + if (torrent.Path.StartsWith(".")) { throw new DownloadClientException("Download paths must be absolute. Please specify variable \"directory\" in rTorrent."); diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentFault.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentFault.cs new file mode 100644 index 000000000..ba1a23962 --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentFault.cs @@ -0,0 +1,28 @@ +using System.Xml.Linq; +using System.Xml.XPath; +using NzbDrone.Core.Download.Extensions; + +namespace NzbDrone.Core.Download.Clients.RTorrent +{ + public class RTorrentFault + { + public RTorrentFault(XElement element) + { + foreach (var e in element.XPathSelectElements("./value/struct/member")) + { + var name = e.ElementAsString("name"); + if (name == "faultCode") + { + FaultCode = e.Element("value").GetIntValue(); + } + else if (name == "faultString") + { + FaultString = e.Element("value").GetStringValue(); + } + } + } + + public int FaultCode { get; set; } + public string FaultString { get; set; } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs index 1e5191e54..fb32c0598 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net; -using CookComputing.XmlRpc; -using NLog; +using System.Xml.Linq; +using System.Xml.XPath; using NzbDrone.Common.Extensions; -using NzbDrone.Common.Serializer; +using NzbDrone.Common.Http; +using NzbDrone.Core.Download.Extensions; namespace NzbDrone.Core.Download.Clients.RTorrent { @@ -21,125 +23,67 @@ namespace NzbDrone.Core.Download.Clients.RTorrent void PushTorrentUniqueView(string hash, string view, RTorrentSettings settings); } - public interface IRTorrent : IXmlRpcProxy - { - [XmlRpcMethod("d.multicall2")] - object[] TorrentMulticall(params string[] parameters); - - [XmlRpcMethod("load.normal")] - int LoadNormal(string target, string data, params string[] commands); - - [XmlRpcMethod("load.start")] - int LoadStart(string target, string data, params string[] commands); - - [XmlRpcMethod("load.raw")] - int LoadRaw(string target, byte[] data, params string[] commands); - - [XmlRpcMethod("load.raw_start")] - int LoadRawStart(string target, byte[] data, params string[] commands); - - [XmlRpcMethod("d.erase")] - int Remove(string hash); - - [XmlRpcMethod("d.name")] - string GetName(string hash); - - [XmlRpcMethod("d.custom1.set")] - string SetLabel(string hash, string label); - - [XmlRpcMethod("d.views.push_back_unique")] - int PushUniqueView(string hash, string view); - - [XmlRpcMethod("system.client_version")] - string GetVersion(); - } - public class RTorrentProxy : IRTorrentProxy { - private readonly Logger _logger; + private readonly IHttpClient _httpClient; - public RTorrentProxy(Logger logger) + public RTorrentProxy(IHttpClient httpClient) { - _logger = logger; + _httpClient = httpClient; } public string GetVersion(RTorrentSettings settings) { - _logger.Debug("Executing remote method: system.client_version"); + var document = ExecuteRequest(settings, "system.client_version"); - var client = BuildClient(settings); - var version = ExecuteRequest(() => client.GetVersion()); - - return version; + return document.Descendants("string").FirstOrDefault()?.Value ?? "0.0.0"; } public List GetTorrents(RTorrentSettings settings) { - _logger.Debug("Executing remote method: d.multicall2"); + var document = ExecuteRequest(settings, + "d.multicall2", + "", + "", + "d.name=", // string + "d.hash=", // string + "d.base_path=", // string + "d.custom1=", // string (label) + "d.size_bytes=", // long + "d.left_bytes=", // long + "d.down.rate=", // long (in bytes / s) + "d.ratio=", // long + "d.is_open=", // long + "d.is_active=", // long + "d.complete=", //long + "d.timestamp.finished="); // long (unix timestamp) - var client = BuildClient(settings); - var ret = ExecuteRequest(() => client.TorrentMulticall( - "", - "", - "d.name=", // string - "d.hash=", // string - "d.base_path=", // string - "d.custom1=", // string (label) - "d.size_bytes=", // long - "d.left_bytes=", // long - "d.down.rate=", // long (in bytes / s) - "d.ratio=", // long - "d.is_open=", // long - "d.is_active=", // long - "d.complete=", //long - "d.timestamp.finished=")); // long (unix timestamp) + var torrents = document.XPathSelectElement("./methodResponse/params/param/value/array/data") + ?.Elements() + .Select(x => new RTorrentTorrent(x)) + .ToList() + ?? new List(); - _logger.Trace(ret.ToJson()); - - var items = new List(); - - foreach (object[] torrent in ret) - { - var labelDecoded = System.Web.HttpUtility.UrlDecode((string)torrent[3]); - - var item = new RTorrentTorrent(); - item.Name = (string)torrent[0]; - item.Hash = (string)torrent[1]; - item.Path = (string)torrent[2]; - item.Category = labelDecoded; - item.TotalSize = (long)torrent[4]; - item.RemainingSize = (long)torrent[5]; - item.DownRate = (long)torrent[6]; - item.Ratio = (long)torrent[7]; - item.IsOpen = Convert.ToBoolean((long)torrent[8]); - item.IsActive = Convert.ToBoolean((long)torrent[9]); - item.IsFinished = Convert.ToBoolean((long)torrent[10]); - item.FinishedTime = (long)torrent[11]; - - items.Add(item); - } - - return items; + return torrents; } public void AddTorrentFromUrl(string torrentUrl, string label, RTorrentPriority priority, string directory, RTorrentSettings settings) { - var client = BuildClient(settings); - var response = ExecuteRequest(() => - { - if (settings.AddStopped) - { - _logger.Debug("Executing remote method: load.normal"); - return client.LoadNormal("", torrentUrl, GetCommands(label, priority, directory)); - } - else - { - _logger.Debug("Executing remote method: load.start"); - return client.LoadStart("", torrentUrl, GetCommands(label, priority, directory)); - } - }); + var args = new List { "", torrentUrl }; + args.AddRange(GetCommands(label, priority, directory)); - if (response != 0) + XDocument response; + + if (settings.AddStopped) + { + response = ExecuteRequest(settings, "load.normal", args.ToArray()); + } + else + { + response = ExecuteRequest(settings, "load.start", args.ToArray()); + } + + if (response.GetIntResponse() != 0) { throw new DownloadClientException("Could not add torrent: {0}.", torrentUrl); } @@ -147,22 +91,21 @@ namespace NzbDrone.Core.Download.Clients.RTorrent public void AddTorrentFromFile(string fileName, byte[] fileContent, string label, RTorrentPriority priority, string directory, RTorrentSettings settings) { - var client = BuildClient(settings); - var response = ExecuteRequest(() => - { - if (settings.AddStopped) - { - _logger.Debug("Executing remote method: load.raw"); - return client.LoadRaw("", fileContent, GetCommands(label, priority, directory)); - } - else - { - _logger.Debug("Executing remote method: load.raw_start"); - return client.LoadRawStart("", fileContent, GetCommands(label, priority, directory)); - } - }); + var args = new List { "", fileContent }; + args.AddRange(GetCommands(label, priority, directory)); - if (response != 0) + XDocument response; + + if (settings.AddStopped) + { + response = ExecuteRequest(settings, "load.raw", args.ToArray()); + } + else + { + response = ExecuteRequest(settings, "load.raw_start", args.ToArray()); + } + + if (response.GetIntResponse() != 0) { throw new DownloadClientException("Could not add torrent: {0}.", fileName); } @@ -170,12 +113,9 @@ namespace NzbDrone.Core.Download.Clients.RTorrent public void SetTorrentLabel(string hash, string label, RTorrentSettings settings) { - _logger.Debug("Executing remote method: d.custom1.set"); + var response = ExecuteRequest(settings, "d.custom1.set", hash, label); - var client = BuildClient(settings); - var response = ExecuteRequest(() => client.SetLabel(hash, label)); - - if (response != label) + if (response.GetStringResponse() != label) { throw new DownloadClientException("Could not set label to {1} for torrent: {0}.", hash, label); } @@ -183,11 +123,9 @@ namespace NzbDrone.Core.Download.Clients.RTorrent public void PushTorrentUniqueView(string hash, string view, RTorrentSettings settings) { - _logger.Debug("Executing remote method: d.views.push_back_unique"); + var response = ExecuteRequest(settings, "d.views.push_back_unique", hash, view); - var client = BuildClient(settings); - var response = ExecuteRequest(() => client.PushUniqueView(hash, view)); - if (response != 0) + if (response.GetIntResponse() != 0) { throw new DownloadClientException("Could not push unique view {0} for torrent: {1}.", view, hash); } @@ -195,12 +133,9 @@ namespace NzbDrone.Core.Download.Clients.RTorrent public void RemoveTorrent(string hash, RTorrentSettings settings) { - _logger.Debug("Executing remote method: d.erase"); + var response = ExecuteRequest(settings, "d.erase", hash); - var client = BuildClient(settings); - var response = ExecuteRequest(() => client.Remove(hash)); - - if (response != 0) + if (response.GetIntResponse() != 0) { throw new DownloadClientException("Could not remove torrent: {0}.", hash); } @@ -208,13 +143,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent public bool HasHashTorrent(string hash, RTorrentSettings settings) { - _logger.Debug("Executing remote method: d.name"); - - var client = BuildClient(settings); - try { - var name = ExecuteRequest(() => client.GetName(hash)); + var response = ExecuteRequest(settings, "d.name", hash); + var name = response.GetStringResponse(); if (name.IsNullOrWhiteSpace()) { @@ -253,45 +185,34 @@ namespace NzbDrone.Core.Download.Clients.RTorrent return result.ToArray(); } - private IRTorrent BuildClient(RTorrentSettings settings) + private XDocument ExecuteRequest(RTorrentSettings settings, string methodName, params object[] args) { - var client = XmlRpcProxyGen.Create(); - - client.Url = string.Format(@"{0}://{1}:{2}/{3}", - settings.UseSsl ? "https" : "http", - settings.Host, - settings.Port, - settings.UrlBase); - - client.EnableCompression = true; + var requestBuilder = new XmlRpcRequestBuilder(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase) + { + LogResponseContent = true, + }; if (!settings.Username.IsNullOrWhiteSpace()) { - client.Credentials = new NetworkCredential(settings.Username, settings.Password); + requestBuilder.NetworkCredential = new NetworkCredential(settings.Username, settings.Password); } - return client; - } + var request = requestBuilder.Call(methodName, args).Build(); - private T ExecuteRequest(Func task) - { - try - { - return task(); - } - catch (XmlRpcServerException ex) - { - throw new DownloadClientException("Unable to connect to rTorrent, please check your settings", ex); - } - catch (WebException ex) - { - if (ex.Status == WebExceptionStatus.TrustFailure) - { - throw new DownloadClientUnavailableException("Unable to connect to rTorrent, certificate validation failed.", ex); - } + var response = _httpClient.Execute(request); - throw new DownloadClientUnavailableException("Unable to connect to rTorrent, please check your settings", ex); + var doc = XDocument.Parse(response.Content); + + var faultElement = doc.XPathSelectElement("./methodResponse/fault"); + + if (faultElement != null) + { + var fault = new RTorrentFault(faultElement); + + throw new DownloadClientException($"rTorrent returned error code {fault.FaultCode}: {fault.FaultString}"); } + + return doc; } } } diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentTorrent.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentTorrent.cs index 14cd0b346..75573b0e9 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentTorrent.cs @@ -1,7 +1,35 @@ -namespace NzbDrone.Core.Download.Clients.RTorrent +using System; +using System.Linq; +using System.Web; +using System.Xml.Linq; +using NzbDrone.Core.Download.Extensions; + +namespace NzbDrone.Core.Download.Clients.RTorrent { public class RTorrentTorrent { + public RTorrentTorrent() + { + } + + public RTorrentTorrent(XElement element) + { + var data = element.Descendants("value").ToList(); + + Name = data[0].GetStringValue(); + Hash = data[1].GetStringValue(); + Path = data[2].GetStringValue(); + Category = HttpUtility.UrlDecode(data[3].GetStringValue()); + TotalSize = data[4].GetLongValue(); + RemainingSize = data[5].GetLongValue(); + DownRate = data[6].GetLongValue(); + Ratio = data[7].GetLongValue(); + IsOpen = Convert.ToBoolean(data[8].GetLongValue()); + IsActive = Convert.ToBoolean(data[9].GetLongValue()); + IsFinished = Convert.ToBoolean(data[10].GetLongValue()); + FinishedTime = data[11].GetLongValue(); + } + public string Name { get; set; } public string Hash { get; set; } public string Path { get; set; } diff --git a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentProxy.cs b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentProxy.cs index 67aae7fd9..184608986 100644 --- a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentProxy.cs @@ -196,7 +196,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent .Accept(HttpAccept.Json); requestBuilder.LogResponseContent = true; - requestBuilder.NetworkCredential = new NetworkCredential(settings.Username, settings.Password); + requestBuilder.NetworkCredential = new BasicNetworkCredential(settings.Username, settings.Password); return requestBuilder; } diff --git a/src/NzbDrone.Core/Download/Extensions/XmlExtensions.cs b/src/NzbDrone.Core/Download/Extensions/XmlExtensions.cs new file mode 100644 index 000000000..1e9deec9f --- /dev/null +++ b/src/NzbDrone.Core/Download/Extensions/XmlExtensions.cs @@ -0,0 +1,55 @@ +using System.Linq; +using System.Xml.Linq; +using System.Xml.XPath; + +namespace NzbDrone.Core.Download.Extensions +{ + internal static class XmlExtensions + { + public static string GetStringValue(this XElement element) + { + return element.ElementAsString("string"); + } + + public static long GetLongValue(this XElement element) + { + return element.ElementAsLong("i8"); + } + + public static int GetIntValue(this XElement element) + { + return element.ElementAsInt("i4"); + } + + public static string ElementAsString(this XElement element, XName name, bool trim = false) + { + var el = element.Element(name); + + return string.IsNullOrWhiteSpace(el?.Value) + ? null + : (trim ? el.Value.Trim() : el.Value); + } + + public static long ElementAsLong(this XElement element, XName name) + { + var el = element.Element(name); + return long.TryParse(el?.Value, out long value) ? value : default; + } + + public static int ElementAsInt(this XElement element, XName name) + { + var el = element.Element(name); + return int.TryParse(el?.Value, out int value) ? value : default(int); + } + + public static int GetIntResponse(this XDocument document) + { + return document.XPathSelectElement("./methodResponse/params/param/value").GetIntValue(); + } + + public static string GetStringResponse(this XDocument document) + { + return document.XPathSelectElement("./methodResponse/params/param/value").GetStringValue(); + } + } +} diff --git a/src/NzbDrone.Core/Indexers/FileList/FileListRequestGenerator.cs b/src/NzbDrone.Core/Indexers/FileList/FileListRequestGenerator.cs index 5f58aa3d6..42748ff38 100644 --- a/src/NzbDrone.Core/Indexers/FileList/FileListRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/FileList/FileListRequestGenerator.cs @@ -44,7 +44,7 @@ namespace NzbDrone.Core.Indexers.FileList var baseUrl = string.Format("{0}/api.php?action={1}&category={2}{3}", Settings.BaseUrl.TrimEnd('/'), searchType, categoriesQuery, parameters); var request = new IndexerRequest(baseUrl, HttpAccept.Json); - request.HttpRequest.AddBasicAuthentication(Settings.Username.Trim(), Settings.Passkey.Trim()); + request.HttpRequest.Credentials = new BasicNetworkCredential(Settings.Username.Trim(), Settings.Passkey.Trim()); yield return request; } diff --git a/src/NzbDrone.Core/Indexers/Gazelle/GazelleRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Gazelle/GazelleRequestGenerator.cs index 0f15e1484..1353fcc5c 100644 --- a/src/NzbDrone.Core/Indexers/Gazelle/GazelleRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/Gazelle/GazelleRequestGenerator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net.Http; using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.Extensions; @@ -71,7 +72,7 @@ namespace NzbDrone.Core.Indexers.Gazelle }; indexRequestBuilder.SetCookies(cookies); - indexRequestBuilder.Method = HttpMethod.POST; + indexRequestBuilder.Method = HttpMethod.Post; indexRequestBuilder.Resource("ajax.php?action=index"); var authIndexRequest = indexRequestBuilder @@ -92,7 +93,7 @@ namespace NzbDrone.Core.Indexers.Gazelle LogResponseContent = true }; - requestBuilder.Method = HttpMethod.POST; + requestBuilder.Method = HttpMethod.Post; requestBuilder.Resource("login.php"); requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15); diff --git a/src/NzbDrone.Core/Indexers/Headphones/HeadphonesCapabilitiesProvider.cs b/src/NzbDrone.Core/Indexers/Headphones/HeadphonesCapabilitiesProvider.cs index 95155a710..6de2154a9 100644 --- a/src/NzbDrone.Core/Indexers/Headphones/HeadphonesCapabilitiesProvider.cs +++ b/src/NzbDrone.Core/Indexers/Headphones/HeadphonesCapabilitiesProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net; using System.Xml; using System.Xml.Linq; using NLog; @@ -50,7 +51,7 @@ namespace NzbDrone.Core.Indexers.Headphones var request = new HttpRequest(url, HttpAccept.Rss); - request.AddBasicAuthentication(indexerSettings.Username, indexerSettings.Password); + request.Credentials = new BasicNetworkCredential(indexerSettings.Username, indexerSettings.Password); HttpResponse response; diff --git a/src/NzbDrone.Core/Indexers/Headphones/HeadphonesRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Headphones/HeadphonesRequestGenerator.cs index 348c29243..882f33e5e 100644 --- a/src/NzbDrone.Core/Indexers/Headphones/HeadphonesRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/Headphones/HeadphonesRequestGenerator.cs @@ -78,7 +78,7 @@ namespace NzbDrone.Core.Indexers.Headphones if (PageSize == 0) { var request = new IndexerRequest($"{baseUrl}{parameters}", HttpAccept.Rss); - request.HttpRequest.AddBasicAuthentication(Settings.Username, Settings.Password); + request.HttpRequest.Credentials = new BasicNetworkCredential(Settings.Username, Settings.Password); yield return request; } @@ -87,7 +87,7 @@ namespace NzbDrone.Core.Indexers.Headphones for (var page = 0; page < maxPages; page++) { var request = new IndexerRequest($"{baseUrl}&offset={page * PageSize}&limit={PageSize}{parameters}", HttpAccept.Rss); - request.HttpRequest.AddBasicAuthentication(Settings.Username, Settings.Password); + request.HttpRequest.Credentials = new BasicNetworkCredential(Settings.Username, Settings.Password); yield return request; } diff --git a/src/NzbDrone.Core/Notifications/Discord/DiscordProxy.cs b/src/NzbDrone.Core/Notifications/Discord/DiscordProxy.cs index f3a6be3d2..c066da569 100644 --- a/src/NzbDrone.Core/Notifications/Discord/DiscordProxy.cs +++ b/src/NzbDrone.Core/Notifications/Discord/DiscordProxy.cs @@ -1,3 +1,4 @@ +using System.Net.Http; using NLog; using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; @@ -29,7 +30,7 @@ namespace NzbDrone.Core.Notifications.Discord .Accept(HttpAccept.Json) .Build(); - request.Method = HttpMethod.POST; + request.Method = HttpMethod.Post; request.Headers.ContentType = "application/json"; request.SetContent(payload.ToJson()); diff --git a/src/NzbDrone.Core/Notifications/Email/Email.cs b/src/NzbDrone.Core/Notifications/Email/Email.cs index 9eb5f0a08..80a2e5e55 100644 --- a/src/NzbDrone.Core/Notifications/Email/Email.cs +++ b/src/NzbDrone.Core/Notifications/Email/Email.cs @@ -7,17 +7,21 @@ using MailKit.Security; using MimeKit; using NLog; using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http.Dispatchers; +using NzbDrone.Core.Security; namespace NzbDrone.Core.Notifications.Email { public class Email : NotificationBase { + private readonly ICertificateValidationService _certificateValidationService; private readonly Logger _logger; public override string Name => "Email"; - public Email(Logger logger) + public Email(ICertificateValidationService certificateValidationService, Logger logger) { + _certificateValidationService = certificateValidationService; _logger = logger; } @@ -68,23 +72,6 @@ namespace NzbDrone.Core.Notifications.Email return new ValidationResult(failures); } - public ValidationFailure Test(EmailSettings settings) - { - const string body = "Success! You have properly configured your email notification settings"; - - try - { - SendEmail(settings, "Lidarr - Test Notification", body); - } - catch (Exception ex) - { - _logger.Error(ex, "Unable to send test email"); - return new ValidationFailure("Server", "Unable to send test email"); - } - - return null; - } - private void SendEmail(EmailSettings settings, string subject, string body, bool htmlBody = false) { var email = new MimeMessage(); @@ -137,6 +124,8 @@ namespace NzbDrone.Core.Notifications.Email } } + client.ServerCertificateValidationCallback = _certificateValidationService.ShouldByPassValidationError; + _logger.Debug("Connecting to mail server"); client.Connect(settings.Server, settings.Port, serverOption); @@ -160,6 +149,23 @@ namespace NzbDrone.Core.Notifications.Email } } + public ValidationFailure Test(EmailSettings settings) + { + const string body = "Success! You have properly configured your email notification settings"; + + try + { + SendEmail(settings, "Sonarr - Test Notification", body); + } + catch (Exception ex) + { + _logger.Error(ex, "Unable to send test email"); + return new ValidationFailure("Server", "Unable to send test email"); + } + + return null; + } + private MailboxAddress ParseAddress(string type, string address) { try diff --git a/src/NzbDrone.Core/Notifications/Join/JoinProxy.cs b/src/NzbDrone.Core/Notifications/Join/JoinProxy.cs index 7a98dfda4..167d64d67 100644 --- a/src/NzbDrone.Core/Notifications/Join/JoinProxy.cs +++ b/src/NzbDrone.Core/Notifications/Join/JoinProxy.cs @@ -1,4 +1,5 @@ using System; +using System.Net.Http; using FluentValidation.Results; using NLog; using NzbDrone.Common.Extensions; @@ -27,7 +28,7 @@ namespace NzbDrone.Core.Notifications.Join public void SendNotification(string title, string message, JoinSettings settings) { - var method = HttpMethod.GET; + var method = HttpMethod.Get; try { diff --git a/src/NzbDrone.Core/Notifications/Mailgun/MailgunProxy.cs b/src/NzbDrone.Core/Notifications/Mailgun/MailgunProxy.cs index dbbbf2af7..ba4bbe18b 100644 --- a/src/NzbDrone.Core/Notifications/Mailgun/MailgunProxy.cs +++ b/src/NzbDrone.Core/Notifications/Mailgun/MailgunProxy.cs @@ -1,7 +1,7 @@ using System.Net; +using System.Net.Http; using NLog; using NzbDrone.Common.Http; -using HttpMethod = NzbDrone.Common.Http.HttpMethod; namespace NzbDrone.Core.Notifications.Mailgun { @@ -27,7 +27,7 @@ namespace NzbDrone.Core.Notifications.Mailgun { try { - var request = BuildRequest(settings, $"{settings.SenderDomain}/messages", HttpMethod.POST, title, message).Build(); + var request = BuildRequest(settings, $"{settings.SenderDomain}/messages", HttpMethod.Post, title, message).Build(); _httpClient.Execute(request); } catch (HttpException ex) diff --git a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs index 9bfdf948f..f774140f9 100644 --- a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs +++ b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Net.Http; using NLog; using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; @@ -23,7 +24,7 @@ namespace NzbDrone.Core.Notifications.Emby var path = "/Notifications/Admin"; var request = BuildRequest(path, settings); request.Headers.ContentType = "application/json"; - request.Method = HttpMethod.POST; + request.Method = HttpMethod.Post; request.SetContent(new { @@ -68,7 +69,7 @@ namespace NzbDrone.Core.Notifications.Emby request = BuildRequest(path, settings); } - request.Method = HttpMethod.POST; + request.Method = HttpMethod.Post; ProcessRequest(request, settings); } @@ -105,7 +106,7 @@ namespace NzbDrone.Core.Notifications.Emby { var path = "/Library/MediaFolders"; var request = BuildRequest(path, settings); - request.Method = HttpMethod.GET; + request.Method = HttpMethod.Get; var response = ProcessRequest(request, settings); diff --git a/src/NzbDrone.Core/Notifications/Ntfy/NtfyProxy.cs b/src/NzbDrone.Core/Notifications/Ntfy/NtfyProxy.cs index d670cb724..bb594eeb6 100644 --- a/src/NzbDrone.Core/Notifications/Ntfy/NtfyProxy.cs +++ b/src/NzbDrone.Core/Notifications/Ntfy/NtfyProxy.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using System.Net; - using FluentValidation.Results; using NLog; using NzbDrone.Common.Extensions; @@ -118,7 +117,7 @@ namespace NzbDrone.Core.Notifications.Ntfy if (!settings.UserName.IsNullOrWhiteSpace() && !settings.Password.IsNullOrWhiteSpace()) { - request.AddBasicAuthentication(settings.UserName, settings.Password); + request.Credentials = new BasicNetworkCredential(settings.UserName, settings.Password); } _httpClient.Execute(request); diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvService.cs b/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvService.cs index 533f5d866..ea65e7175 100644 --- a/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvService.cs +++ b/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvService.cs @@ -1,4 +1,6 @@ using System.Linq; +using System.Net.Http; +using System.Text; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; @@ -37,7 +39,7 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv .AddQueryParam("X-Plex-Version", BuildInfo.Version.ToString()) .AddQueryParam("strong", true); - requestBuilder.Method = HttpMethod.POST; + requestBuilder.Method = HttpMethod.Post; var request = requestBuilder.Build(); diff --git a/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerProxy.cs b/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerProxy.cs index 9ae241580..effa10244 100644 --- a/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerProxy.cs +++ b/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerProxy.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using NLog; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; @@ -35,7 +36,7 @@ namespace NzbDrone.Core.Notifications.Plex.Server public List GetArtistSections(PlexServerSettings settings) { - var request = BuildRequest("library/sections", HttpMethod.GET, settings); + var request = BuildRequest("library/sections", HttpMethod.Get, settings); var response = ProcessRequest(request); CheckForError(response); @@ -65,7 +66,7 @@ namespace NzbDrone.Core.Notifications.Plex.Server public void Update(int sectionId, PlexServerSettings settings) { var resource = $"library/sections/{sectionId}/refresh"; - var request = BuildRequest(resource, HttpMethod.GET, settings); + var request = BuildRequest(resource, HttpMethod.Get, settings); var response = ProcessRequest(request); CheckForError(response); @@ -74,7 +75,7 @@ namespace NzbDrone.Core.Notifications.Plex.Server public void UpdateArtist(int metadataId, PlexServerSettings settings) { var resource = $"library/metadata/{metadataId}/refresh"; - var request = BuildRequest(resource, HttpMethod.PUT, settings); + var request = BuildRequest(resource, HttpMethod.Put, settings); var response = ProcessRequest(request); CheckForError(response); @@ -82,7 +83,7 @@ namespace NzbDrone.Core.Notifications.Plex.Server public string Version(PlexServerSettings settings) { - var request = BuildRequest("identity", HttpMethod.GET, settings); + var request = BuildRequest("identity", HttpMethod.Get, settings); var response = ProcessRequest(request); CheckForError(response); @@ -100,7 +101,7 @@ namespace NzbDrone.Core.Notifications.Plex.Server public List Preferences(PlexServerSettings settings) { - var request = BuildRequest(":/prefs", HttpMethod.GET, settings); + var request = BuildRequest(":/prefs", HttpMethod.Get, settings); var response = ProcessRequest(request); CheckForError(response); @@ -120,7 +121,7 @@ namespace NzbDrone.Core.Notifications.Plex.Server { var guid = string.Format("com.plexapp.agents.lastfm://{0}?lang={1}", mbId, language); // TODO Plex Route for MB? LastFM? var resource = $"library/sections/{sectionId}/all?guid={System.Web.HttpUtility.UrlEncode(guid)}"; - var request = BuildRequest(resource, HttpMethod.GET, settings); + var request = BuildRequest(resource, HttpMethod.Get, settings); var response = ProcessRequest(request); CheckForError(response); diff --git a/src/NzbDrone.Core/Notifications/PushBullet/PushBulletProxy.cs b/src/NzbDrone.Core/Notifications/PushBullet/PushBulletProxy.cs index 8ed8df0b7..6f5302e6c 100644 --- a/src/NzbDrone.Core/Notifications/PushBullet/PushBulletProxy.cs +++ b/src/NzbDrone.Core/Notifications/PushBullet/PushBulletProxy.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using FluentValidation.Results; using NLog; using NzbDrone.Common.Extensions; @@ -100,8 +101,8 @@ namespace NzbDrone.Core.Notifications.PushBullet var request = requestBuilder.Build(); - request.Method = HttpMethod.GET; - request.AddBasicAuthentication(settings.ApiKey, string.Empty); + request.Method = HttpMethod.Get; + request.Credentials = new BasicNetworkCredential(settings.ApiKey, string.Empty); var response = _httpClient.Execute(request); @@ -197,7 +198,7 @@ namespace NzbDrone.Core.Notifications.PushBullet var request = requestBuilder.Build(); - request.AddBasicAuthentication(settings.ApiKey, string.Empty); + request.Credentials = new BasicNetworkCredential(settings.ApiKey, string.Empty); _httpClient.Execute(request); } diff --git a/src/NzbDrone.Core/Notifications/SendGrid/SendGridProxy.cs b/src/NzbDrone.Core/Notifications/SendGrid/SendGridProxy.cs index 0db56b4c9..ec57c8636 100644 --- a/src/NzbDrone.Core/Notifications/SendGrid/SendGridProxy.cs +++ b/src/NzbDrone.Core/Notifications/SendGrid/SendGridProxy.cs @@ -1,4 +1,5 @@ -using System.Net; +using System.Net; +using System.Net.Http; using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; @@ -22,7 +23,7 @@ namespace NzbDrone.Core.Notifications.SendGrid { try { - var request = BuildRequest(settings, "mail/send", HttpMethod.POST); + var request = BuildRequest(settings, "mail/send", HttpMethod.Post); var payload = new SendGridPayload { diff --git a/src/NzbDrone.Core/Notifications/Slack/SlackProxy.cs b/src/NzbDrone.Core/Notifications/Slack/SlackProxy.cs index 02075dad9..c8dd12f53 100644 --- a/src/NzbDrone.Core/Notifications/Slack/SlackProxy.cs +++ b/src/NzbDrone.Core/Notifications/Slack/SlackProxy.cs @@ -1,3 +1,4 @@ +using System.Net.Http; using NLog; using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; @@ -29,7 +30,7 @@ namespace NzbDrone.Core.Notifications.Slack .Accept(HttpAccept.Json) .Build(); - request.Method = HttpMethod.POST; + request.Method = HttpMethod.Post; request.Headers.ContentType = "application/json"; request.SetContent(payload.ToJson()); diff --git a/src/NzbDrone.Core/Notifications/Subsonic/SubsonicServerProxy.cs b/src/NzbDrone.Core/Notifications/Subsonic/SubsonicServerProxy.cs index af2d58716..aa3c93a9b 100644 --- a/src/NzbDrone.Core/Notifications/Subsonic/SubsonicServerProxy.cs +++ b/src/NzbDrone.Core/Notifications/Subsonic/SubsonicServerProxy.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Net.Http; using System.Xml.Linq; using NLog; using NzbDrone.Common.Extensions; @@ -36,7 +37,7 @@ namespace NzbDrone.Core.Notifications.Subsonic public void Notify(SubsonicSettings settings, string message) { var resource = "addChatMessage"; - var request = GetSubsonicServerRequest(resource, HttpMethod.GET, settings); + var request = GetSubsonicServerRequest(resource, HttpMethod.Get, settings); request.AddQueryParam("message", message); var response = _httpClient.Execute(request.Build()); @@ -48,7 +49,7 @@ namespace NzbDrone.Core.Notifications.Subsonic public void Update(SubsonicSettings settings) { var resource = "startScan"; - var request = GetSubsonicServerRequest(resource, HttpMethod.GET, settings); + var request = GetSubsonicServerRequest(resource, HttpMethod.Get, settings); var response = _httpClient.Execute(request.Build()); _logger.Trace("Update response: {0}", response.Content); @@ -57,7 +58,7 @@ namespace NzbDrone.Core.Notifications.Subsonic public string Version(SubsonicSettings settings) { - var request = GetSubsonicServerRequest("ping", HttpMethod.GET, settings); + var request = GetSubsonicServerRequest("ping", HttpMethod.Get, settings); var response = _httpClient.Execute(request.Build()); _logger.Trace("Version response: {0}", response.Content); diff --git a/src/NzbDrone.Core/Notifications/Twitter/TwitterProxy.cs b/src/NzbDrone.Core/Notifications/Twitter/TwitterProxy.cs new file mode 100644 index 000000000..3330e4a07 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Twitter/TwitterProxy.cs @@ -0,0 +1,110 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Web; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; +using NzbDrone.Common.OAuth; + +namespace NzbDrone.Core.Notifications.Twitter +{ + public interface ITwitterProxy + { + NameValueCollection GetOAuthToken(string consumerKey, string consumerSecret, string oauthToken, string oauthVerifier); + string GetOAuthRedirect(string consumerKey, string consumerSecret, string callbackUrl); + void UpdateStatus(string message, TwitterSettings settings); + void DirectMessage(string message, TwitterSettings settings); + } + + public class TwitterProxy : ITwitterProxy + { + private readonly IHttpClient _httpClient; + + public TwitterProxy(IHttpClient httpClient) + { + _httpClient = httpClient; + } + + public string GetOAuthRedirect(string consumerKey, string consumerSecret, string callbackUrl) + { + // Creating a new instance with a helper method + var oAuthRequest = OAuthRequest.ForRequestToken(consumerKey, consumerSecret, callbackUrl); + oAuthRequest.RequestUrl = "https://api.twitter.com/oauth/request_token"; + var qscoll = HttpUtility.ParseQueryString(ExecuteRequest(GetRequest(oAuthRequest, new Dictionary())).Content); + + return string.Format("https://api.twitter.com/oauth/authorize?oauth_token={0}", qscoll["oauth_token"]); + } + + public NameValueCollection GetOAuthToken(string consumerKey, string consumerSecret, string oauthToken, string oauthVerifier) + { + // Creating a new instance with a helper method + var oAuthRequest = OAuthRequest.ForAccessToken(consumerKey, consumerSecret, oauthToken, "", oauthVerifier); + oAuthRequest.RequestUrl = "https://api.twitter.com/oauth/access_token"; + + return HttpUtility.ParseQueryString(ExecuteRequest(GetRequest(oAuthRequest, new Dictionary())).Content); + } + + public void UpdateStatus(string message, TwitterSettings settings) + { + var oAuthRequest = OAuthRequest.ForProtectedResource("POST", settings.ConsumerKey, settings.ConsumerSecret, settings.AccessToken, settings.AccessTokenSecret); + + oAuthRequest.RequestUrl = "https://api.twitter.com/1.1/statuses/update.json"; + + var customParams = new Dictionary + { + { "status", message.EncodeRFC3986() } + }; + + var request = GetRequest(oAuthRequest, customParams); + + request.Headers.ContentType = "application/x-www-form-urlencoded"; + request.SetContent(Encoding.ASCII.GetBytes(GetCustomParametersString(customParams))); + + ExecuteRequest(request); + } + + public void DirectMessage(string message, TwitterSettings settings) + { + var oAuthRequest = OAuthRequest.ForProtectedResource("POST", settings.ConsumerKey, settings.ConsumerSecret, settings.AccessToken, settings.AccessTokenSecret); + + oAuthRequest.RequestUrl = "https://api.twitter.com/1.1/direct_messages/new.json"; + + var customParams = new Dictionary + { + { "text", message.EncodeRFC3986() }, + { "screenname", settings.Mention.EncodeRFC3986() } + }; + + var request = GetRequest(oAuthRequest, customParams); + + request.Headers.ContentType = "application/x-www-form-urlencoded"; + request.SetContent(Encoding.ASCII.GetBytes(GetCustomParametersString(customParams))); + + ExecuteRequest(request); + } + + private string GetCustomParametersString(Dictionary customParams) + { + return customParams.Select(x => string.Format("{0}={1}", x.Key, x.Value)).Join("&"); + } + + private HttpRequest GetRequest(OAuthRequest oAuthRequest, Dictionary customParams) + { + var auth = oAuthRequest.GetAuthorizationHeader(customParams); + var request = new HttpRequest(oAuthRequest.RequestUrl); + + request.Headers.Add("Authorization", auth); + + request.Method = oAuthRequest.Method == "POST" ? HttpMethod.Post : HttpMethod.Get; + + return request; + } + + private HttpResponse ExecuteRequest(HttpRequest request) + { + return _httpClient.Execute(request); + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Twitter/TwitterService.cs b/src/NzbDrone.Core/Notifications/Twitter/TwitterService.cs index 261c194d1..e784b64a7 100644 --- a/src/NzbDrone.Core/Notifications/Twitter/TwitterService.cs +++ b/src/NzbDrone.Core/Notifications/Twitter/TwitterService.cs @@ -1,13 +1,9 @@ -using System; -using System.Collections.Specialized; +using System; using System.IO; using System.Net; -using System.Web; using FluentValidation.Results; using NLog; using NzbDrone.Common.Extensions; -using NzbDrone.Common.Http; -using NzbDrone.Common.OAuth; namespace NzbDrone.Core.Notifications.Twitter { @@ -21,31 +17,18 @@ namespace NzbDrone.Core.Notifications.Twitter public class TwitterService : ITwitterService { - private readonly IHttpClient _httpClient; + private readonly ITwitterProxy _twitterProxy; private readonly Logger _logger; - public TwitterService(IHttpClient httpClient, Logger logger) + public TwitterService(ITwitterProxy twitterProxy, Logger logger) { - _httpClient = httpClient; + _twitterProxy = twitterProxy; _logger = logger; } - private NameValueCollection OAuthQuery(OAuthRequest oAuthRequest) - { - var auth = oAuthRequest.GetAuthorizationHeader(); - var request = new Common.Http.HttpRequest(oAuthRequest.RequestUrl); - request.Headers.Add("Authorization", auth); - var response = _httpClient.Get(request); - - return HttpUtility.ParseQueryString(response.Content); - } - public OAuthToken GetOAuthToken(string consumerKey, string consumerSecret, string oauthToken, string oauthVerifier) { - // Creating a new instance with a helper method - var oAuthRequest = OAuthRequest.ForAccessToken(consumerKey, consumerSecret, oauthToken, "", oauthVerifier); - oAuthRequest.RequestUrl = "https://api.twitter.com/oauth/access_token"; - var qscoll = OAuthQuery(oAuthRequest); + var qscoll = _twitterProxy.GetOAuthToken(consumerKey, consumerSecret, oauthToken, oauthVerifier); return new OAuthToken { @@ -56,31 +39,16 @@ namespace NzbDrone.Core.Notifications.Twitter public string GetOAuthRedirect(string consumerKey, string consumerSecret, string callbackUrl) { - // Creating a new instance with a helper method - var oAuthRequest = OAuthRequest.ForRequestToken(consumerKey, consumerSecret, callbackUrl); - oAuthRequest.RequestUrl = "https://api.twitter.com/oauth/request_token"; - var qscoll = OAuthQuery(oAuthRequest); - - return string.Format("https://api.twitter.com/oauth/authorize?oauth_token={0}", qscoll["oauth_token"]); + return _twitterProxy.GetOAuthRedirect(consumerKey, consumerSecret, callbackUrl); } public void SendNotification(string message, TwitterSettings settings) { try { - var oAuth = new TinyTwitter.OAuthInfo - { - ConsumerKey = settings.ConsumerKey, - ConsumerSecret = settings.ConsumerSecret, - AccessToken = settings.AccessToken, - AccessSecret = settings.AccessTokenSecret - }; - - var twitter = new TinyTwitter.TinyTwitter(oAuth); - if (settings.DirectMessage) { - twitter.DirectMessage(message, settings.Mention); + _twitterProxy.DirectMessage(message, settings); } else { @@ -89,7 +57,7 @@ namespace NzbDrone.Core.Notifications.Twitter message += string.Format(" @{0}", settings.Mention); } - twitter.UpdateStatus(message); + _twitterProxy.UpdateStatus(message, settings); } } catch (WebException ex) diff --git a/src/NzbDrone.Core/Notifications/Webhook/WebhookMethod.cs b/src/NzbDrone.Core/Notifications/Webhook/WebhookMethod.cs index 5d6e859a6..e3705d69b 100644 --- a/src/NzbDrone.Core/Notifications/Webhook/WebhookMethod.cs +++ b/src/NzbDrone.Core/Notifications/Webhook/WebhookMethod.cs @@ -4,7 +4,7 @@ namespace NzbDrone.Core.Notifications.Webhook { public enum WebhookMethod { - POST = HttpMethod.POST, - PUT = HttpMethod.PUT + POST = 1, + PUT = 2 } } diff --git a/src/NzbDrone.Core/Notifications/Webhook/WebhookProxy.cs b/src/NzbDrone.Core/Notifications/Webhook/WebhookProxy.cs index 489eeb41f..23a7fbdc8 100644 --- a/src/NzbDrone.Core/Notifications/Webhook/WebhookProxy.cs +++ b/src/NzbDrone.Core/Notifications/Webhook/WebhookProxy.cs @@ -1,3 +1,5 @@ +using System; +using System.Net.Http; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; @@ -26,13 +28,19 @@ namespace NzbDrone.Core.Notifications.Webhook .Accept(HttpAccept.Json) .Build(); - request.Method = (HttpMethod)settings.Method; + request.Method = settings.Method switch + { + (int)WebhookMethod.POST => HttpMethod.Post, + (int)WebhookMethod.PUT => HttpMethod.Put, + _ => throw new ArgumentOutOfRangeException($"Invalid Webhook method {settings.Method}") + }; + request.Headers.ContentType = "application/json"; request.SetContent(body.ToJson()); if (settings.Username.IsNotNullOrWhiteSpace() || settings.Password.IsNotNullOrWhiteSpace()) { - request.AddBasicAuthentication(settings.Username, settings.Password); + request.Credentials = new BasicNetworkCredential(settings.Username, settings.Password); } _httpClient.Execute(request); diff --git a/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs b/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs index 9139350b5..f130199ba 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs @@ -84,7 +84,7 @@ namespace NzbDrone.Core.Notifications.Xbmc if (!settings.Username.IsNullOrWhiteSpace()) { - request.AddBasicAuthentication(settings.Username, settings.Password); + request.Credentials = new BasicNetworkCredential(settings.Username, settings.Password); } var response = _httpClient.Execute(request); diff --git a/src/NzbDrone.Core/Security/X509CertificateValidationService.cs b/src/NzbDrone.Core/Security/X509CertificateValidationService.cs index 3493cd20a..7efc877ad 100644 --- a/src/NzbDrone.Core/Security/X509CertificateValidationService.cs +++ b/src/NzbDrone.Core/Security/X509CertificateValidationService.cs @@ -1,16 +1,15 @@ -using System.Linq; +using System.Linq; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using NLog; using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http.Dispatchers; using NzbDrone.Core.Configuration; -using NzbDrone.Core.Lifecycle; -using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Security { - public class X509CertificateValidationService : IHandle + public class X509CertificateValidationService : ICertificateValidationService { private readonly IConfigService _configService; private readonly Logger _logger; @@ -21,19 +20,29 @@ namespace NzbDrone.Core.Security _logger = logger; } - private bool ShouldByPassValidationError(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + public bool ShouldByPassValidationError(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { - var request = sender as HttpWebRequest; + var targetHostName = string.Empty; - if (request == null) + if (sender is not SslStream && sender is not string) { return true; } - var cert2 = certificate as X509Certificate2; - if (cert2 != null && request != null && cert2.SignatureAlgorithm.FriendlyName == "md5RSA") + if (sender is SslStream request) { - _logger.Error("https://{0} uses the obsolete md5 hash in it's https certificate, if that is your certificate, please (re)create certificate with better algorithm as soon as possible.", request.RequestUri.Authority); + targetHostName = request.TargetHostName; + } + + // Mailkit passes host in sender as string + if (sender is string stringHost) + { + targetHostName = stringHost; + } + + if (certificate is X509Certificate2 cert2 && cert2.SignatureAlgorithm.FriendlyName == "md5RSA") + { + _logger.Error("https://{0} uses the obsolete md5 hash in it's https certificate, if that is your certificate, please (re)create certificate with better algorithm as soon as possible.", targetHostName); } if (sslPolicyErrors == SslPolicyErrors.None) @@ -41,12 +50,12 @@ namespace NzbDrone.Core.Security return true; } - if (request.RequestUri.Host == "localhost" || request.RequestUri.Host == "127.0.0.1") + if (targetHostName == "localhost" || targetHostName == "127.0.0.1") { return true; } - var ipAddresses = GetIPAddresses(request.RequestUri.Host); + var ipAddresses = GetIPAddresses(targetHostName); var certificateValidation = _configService.CertificateValidation; if (certificateValidation == CertificateValidationType.Disabled) @@ -60,7 +69,7 @@ namespace NzbDrone.Core.Security return true; } - _logger.Error("Certificate validation for {0} failed. {1}", request.Address, sslPolicyErrors); + _logger.Error("Certificate validation for {0} failed. {1}", targetHostName, sslPolicyErrors); return false; } @@ -74,10 +83,5 @@ namespace NzbDrone.Core.Security return Dns.GetHostEntry(host).AddressList; } - - public void Handle(ApplicationStartedEvent message) - { - ServicePointManager.ServerCertificateValidationCallback = ShouldByPassValidationError; - } } } diff --git a/src/NzbDrone.Core/TinyTwitter.cs b/src/NzbDrone.Core/TinyTwitter.cs deleted file mode 100644 index f7f77bb58..000000000 --- a/src/NzbDrone.Core/TinyTwitter.cs +++ /dev/null @@ -1,235 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Security.Cryptography; -using System.Text; -using System.Text.RegularExpressions; - -namespace TinyTwitter -{ - public class OAuthInfo - { - public string ConsumerKey { get; set; } - public string ConsumerSecret { get; set; } - public string AccessToken { get; set; } - public string AccessSecret { get; set; } - } - - public class Tweet - { - public long Id { get; set; } - public DateTime CreatedAt { get; set; } - public string UserName { get; set; } - public string ScreenName { get; set; } - public string Text { get; set; } - } - - public class TinyTwitter - { - private readonly OAuthInfo _oauth; - - public TinyTwitter(OAuthInfo oauth) - { - _oauth = oauth; - } - - public void UpdateStatus(string message) - { - new RequestBuilder(_oauth, "POST", "https://api.twitter.com/1.1/statuses/update.json") - .AddParameter("status", message) - .Execute(); - } - - /** - * - * As of June 26th 2015 Direct Messaging is not part of TinyTwitter. - * I have added it to Lidarr's copy to make our implementation easier - * and added this banner so it's not blindly updated. - * - **/ - public void DirectMessage(string message, string screenName) - { - new RequestBuilder(_oauth, "POST", "https://api.twitter.com/1.1/direct_messages/new.json") - .AddParameter("text", message) - .AddParameter("screen_name", screenName) - .Execute(); - } - - public class RequestBuilder - { - private const string VERSION = "1.0"; - private const string SIGNATURE_METHOD = "HMAC-SHA1"; - - private readonly OAuthInfo _oauth; - private readonly string _method; - private readonly IDictionary _customParameters; - private readonly string _url; - - public RequestBuilder(OAuthInfo oauth, string method, string url) - { - _oauth = oauth; - _method = method; - _url = url; - _customParameters = new Dictionary(); - } - - public RequestBuilder AddParameter(string name, string value) - { - _customParameters.Add(name, value.EncodeRFC3986()); - return this; - } - - public string Execute() - { - var timespan = GetTimestamp(); - var nonce = CreateNonce(); - - var parameters = new Dictionary(_customParameters); - AddOAuthParameters(parameters, timespan, nonce); - - var signature = GenerateSignature(parameters); - var headerValue = GenerateAuthorizationHeaderValue(parameters, signature); - - var request = (HttpWebRequest)WebRequest.Create(GetRequestUrl()); - request.Method = _method; - request.ContentType = "application/x-www-form-urlencoded"; - - request.Headers.Add("Authorization", headerValue); - - WriteRequestBody(request); - - // It looks like a bug in HttpWebRequest. It throws random TimeoutExceptions - // after some requests. Abort the request seems to work. More info: - // http://stackoverflow.com/questions/2252762/getrequeststream-throws-timeout-exception-randomly - var response = request.GetResponse(); - - string content; - - using (var stream = response.GetResponseStream()) - { - using (var reader = new StreamReader(stream)) - { - content = reader.ReadToEnd(); - } - } - - request.Abort(); - - return content; - } - - private void WriteRequestBody(HttpWebRequest request) - { - if (_method == "GET") - { - return; - } - - var requestBody = Encoding.ASCII.GetBytes(GetCustomParametersString()); - using (var stream = request.GetRequestStream()) - { - stream.Write(requestBody, 0, requestBody.Length); - } - } - - private string GetRequestUrl() - { - if (_method != "GET" || _customParameters.Count == 0) - { - return _url; - } - - return string.Format("{0}?{1}", _url, GetCustomParametersString()); - } - - private string GetCustomParametersString() - { - return _customParameters.Select(x => string.Format("{0}={1}", x.Key, x.Value)).Join("&"); - } - - private string GenerateAuthorizationHeaderValue(IEnumerable> parameters, string signature) - { - return new StringBuilder("OAuth ") - .Append(parameters.Concat(new KeyValuePair("oauth_signature", signature)) - .Where(x => x.Key.StartsWith("oauth_")) - .Select(x => string.Format("{0}=\"{1}\"", x.Key, x.Value.EncodeRFC3986())) - .Join(",")) - .ToString(); - } - - private string GenerateSignature(IEnumerable> parameters) - { - var dataToSign = new StringBuilder() - .Append(_method).Append("&") - .Append(_url.EncodeRFC3986()).Append("&") - .Append(parameters - .OrderBy(x => x.Key) - .Select(x => string.Format("{0}={1}", x.Key, x.Value)) - .Join("&") - .EncodeRFC3986()); - - var signatureKey = string.Format("{0}&{1}", _oauth.ConsumerSecret.EncodeRFC3986(), _oauth.AccessSecret.EncodeRFC3986()); - var sha1 = new HMACSHA1(Encoding.ASCII.GetBytes(signatureKey)); - - var signatureBytes = sha1.ComputeHash(Encoding.ASCII.GetBytes(dataToSign.ToString())); - return Convert.ToBase64String(signatureBytes); - } - - private void AddOAuthParameters(IDictionary parameters, string timestamp, string nonce) - { - parameters.Add("oauth_version", VERSION); - parameters.Add("oauth_consumer_key", _oauth.ConsumerKey); - parameters.Add("oauth_nonce", nonce); - parameters.Add("oauth_signature_method", SIGNATURE_METHOD); - parameters.Add("oauth_timestamp", timestamp); - parameters.Add("oauth_token", _oauth.AccessToken); - } - - private static string GetTimestamp() - { - return ((int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds).ToString(); - } - - private static string CreateNonce() - { - return new Random().Next(0x0000000, 0x7fffffff).ToString("X8"); - } - } - } - - public static class TinyTwitterHelperExtensions - { - public static string Join(this IEnumerable items, string separator) - { - return string.Join(separator, items.ToArray()); - } - - public static IEnumerable Concat(this IEnumerable items, T value) - { - return items.Concat(new[] { value }); - } - - public static string EncodeRFC3986(this string value) - { - // From Twitterizer http://www.twitterizer.net/ - if (string.IsNullOrEmpty(value)) - { - return string.Empty; - } - - var encoded = Uri.EscapeDataString(value); - - return Regex - .Replace(encoded, "(%[0-9a-f][0-9a-f])", c => c.Value.ToUpper()) - .Replace("(", "%28") - .Replace(")", "%29") - .Replace("$", "%24") - .Replace("!", "%21") - .Replace("*", "%2A") - .Replace("'", "%27") - .Replace("%7E", "~"); - } - } -} diff --git a/src/NzbDrone.Integration.Test/IndexHtmlFixture.cs b/src/NzbDrone.Integration.Test/IndexHtmlFixture.cs index ed732aee8..7534fcf1b 100644 --- a/src/NzbDrone.Integration.Test/IndexHtmlFixture.cs +++ b/src/NzbDrone.Integration.Test/IndexHtmlFixture.cs @@ -1,5 +1,8 @@ +using System; using System.Linq; using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; using FluentAssertions; using NUnit.Framework; @@ -8,25 +11,30 @@ namespace NzbDrone.Integration.Test [TestFixture] public class IndexHtmlFixture : IntegrationTest { + private HttpClient _httpClient = new HttpClient(); + [Test] public void should_get_index_html() { - var text = new WebClient().DownloadString(RootUrl); + var request = new HttpRequestMessage(HttpMethod.Get, RootUrl); + var response = _httpClient.Send(request); + var text = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); text.Should().NotBeNullOrWhiteSpace(); } [Test] public void index_should_not_be_cached() { - var client = new WebClient(); - _ = client.DownloadString(RootUrl); + var request = new HttpRequestMessage(HttpMethod.Get, RootUrl); + var response = _httpClient.Send(request); - var headers = client.ResponseHeaders; + var headers = response.Headers; - headers.Get("Cache-Control").Split(',').Select(x => x.Trim()) - .Should().BeEquivalentTo("no-store, no-cache".Split(',').Select(x => x.Trim())); - headers.Get("Pragma").Should().Be("no-cache"); - headers.Get("Expires").Should().Be("-1"); + headers.CacheControl.NoStore.Should().BeTrue(); + headers.CacheControl.NoCache.Should().BeTrue(); + headers.Pragma.Should().Contain(new NameValueHeaderValue("no-cache")); + + response.Content.Headers.Expires.Should().BeBefore(DateTime.UtcNow); } } } From f6f44f2fbddb2526ba81ddf016d2824a0fb38c7b Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 26 Sep 2022 16:46:58 -0500 Subject: [PATCH 0017/1478] Updated NLog Version Co-Authored-By: Robin Dadswell <19610103+RobinDadswell@users.noreply.github.com> --- src/Lidarr.Api.V1/Lidarr.Api.V1.csproj | 2 +- src/Lidarr.Http/Lidarr.Http.csproj | 2 +- .../Extensions/SentryLoggerExtensions.cs | 31 +++++++++---------- .../Instrumentation/NzbDroneFileTarget.cs | 8 +++-- .../Instrumentation/NzbDroneLogger.cs | 12 +++++++ src/NzbDrone.Common/Lidarr.Common.csproj | 2 +- .../Download/CompletedDownloadService.cs | 5 ++- .../Instrumentation/ReconfigureLogging.cs | 1 - src/NzbDrone.Core/Lidarr.Core.csproj | 6 ++-- src/NzbDrone.Core/MediaFiles/AudioTag.cs | 11 +++---- .../MediaFiles/AudioTagService.cs | 11 +++---- .../MediaFiles/MediaInfoFormatter.cs | 9 +++--- src/NzbDrone.Mono/Lidarr.Mono.csproj | 2 +- .../Lidarr.Test.Common.csproj | 2 +- src/NzbDrone.Update/Lidarr.Update.csproj | 2 +- src/NzbDrone.Windows/Lidarr.Windows.csproj | 2 +- 16 files changed, 58 insertions(+), 50 deletions(-) diff --git a/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj b/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj index 1cd62fe9e..16e1a8fc3 100644 --- a/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj +++ b/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Lidarr.Http/Lidarr.Http.csproj b/src/Lidarr.Http/Lidarr.Http.csproj index 9b78d4cb5..99ef46bc2 100644 --- a/src/Lidarr.Http/Lidarr.Http.csproj +++ b/src/Lidarr.Http/Lidarr.Http.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/NzbDrone.Common/Instrumentation/Extensions/SentryLoggerExtensions.cs b/src/NzbDrone.Common/Instrumentation/Extensions/SentryLoggerExtensions.cs index 2e17aad44..417ee4a59 100644 --- a/src/NzbDrone.Common/Instrumentation/Extensions/SentryLoggerExtensions.cs +++ b/src/NzbDrone.Common/Instrumentation/Extensions/SentryLoggerExtensions.cs @@ -1,6 +1,6 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using NLog; -using NLog.Fluent; namespace NzbDrone.Common.Instrumentation.Extensions { @@ -8,47 +8,46 @@ namespace NzbDrone.Common.Instrumentation.Extensions { public static readonly Logger SentryLogger = LogManager.GetLogger("Sentry"); - public static LogBuilder SentryFingerprint(this LogBuilder logBuilder, params string[] fingerprint) + public static LogEventBuilder SentryFingerprint(this LogEventBuilder logBuilder, params string[] fingerprint) { return logBuilder.Property("Sentry", fingerprint); } - public static LogBuilder WriteSentryDebug(this LogBuilder logBuilder, params string[] fingerprint) + public static LogEventBuilder WriteSentryDebug(this LogEventBuilder logBuilder, params string[] fingerprint) { return LogSentryMessage(logBuilder, LogLevel.Debug, fingerprint); } - public static LogBuilder WriteSentryInfo(this LogBuilder logBuilder, params string[] fingerprint) + public static LogEventBuilder WriteSentryInfo(this LogEventBuilder logBuilder, params string[] fingerprint) { return LogSentryMessage(logBuilder, LogLevel.Info, fingerprint); } - public static LogBuilder WriteSentryWarn(this LogBuilder logBuilder, params string[] fingerprint) + public static LogEventBuilder WriteSentryWarn(this LogEventBuilder logBuilder, params string[] fingerprint) { return LogSentryMessage(logBuilder, LogLevel.Warn, fingerprint); } - public static LogBuilder WriteSentryError(this LogBuilder logBuilder, params string[] fingerprint) + public static LogEventBuilder WriteSentryError(this LogEventBuilder logBuilder, params string[] fingerprint) { return LogSentryMessage(logBuilder, LogLevel.Error, fingerprint); } - private static LogBuilder LogSentryMessage(LogBuilder logBuilder, LogLevel level, string[] fingerprint) + private static LogEventBuilder LogSentryMessage(LogEventBuilder logBuilder, LogLevel level, string[] fingerprint) { - SentryLogger.Log(level) - .CopyLogEvent(logBuilder.LogEventInfo) + SentryLogger.ForLogEvent(level) + .CopyLogEvent(logBuilder.LogEvent) .SentryFingerprint(fingerprint) - .Write(); + .Log(); - return logBuilder.Property("Sentry", null); + return logBuilder.Property("Sentry", null); } - private static LogBuilder CopyLogEvent(this LogBuilder logBuilder, LogEventInfo logEvent) + private static LogEventBuilder CopyLogEvent(this LogEventBuilder logBuilder, LogEventInfo logEvent) { - return logBuilder.LoggerName(logEvent.LoggerName) - .TimeStamp(logEvent.TimeStamp) + return logBuilder.TimeStamp(logEvent.TimeStamp) .Message(logEvent.Message, logEvent.Parameters) - .Properties(logEvent.Properties.ToDictionary(v => v.Key, v => v.Value)) + .Properties(logEvent.Properties.Select(p => new KeyValuePair(p.Key.ToString(), p.Value))) .Exception(logEvent.Exception); } } diff --git a/src/NzbDrone.Common/Instrumentation/NzbDroneFileTarget.cs b/src/NzbDrone.Common/Instrumentation/NzbDroneFileTarget.cs index 62e41b0e0..26c98968b 100644 --- a/src/NzbDrone.Common/Instrumentation/NzbDroneFileTarget.cs +++ b/src/NzbDrone.Common/Instrumentation/NzbDroneFileTarget.cs @@ -1,13 +1,15 @@ -using NLog; +using System.Text; +using NLog; using NLog.Targets; namespace NzbDrone.Common.Instrumentation { public class NzbDroneFileTarget : FileTarget { - protected override string GetFormattedMessage(LogEventInfo logEvent) + protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target) { - return CleanseLogMessage.Cleanse(Layout.Render(logEvent)); + var result = CleanseLogMessage.Cleanse(Layout.Render(logEvent)); + target.Append(result); } } } diff --git a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs index c6108471f..436b65821 100644 --- a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs +++ b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs @@ -34,6 +34,8 @@ namespace NzbDrone.Common.Instrumentation var appFolderInfo = new AppFolderInfo(startupContext); + RegisterGlobalFilters(); + if (Debugger.IsAttached) { RegisterDebugger(); @@ -101,6 +103,16 @@ namespace NzbDrone.Common.Instrumentation LogManager.Configuration.LoggingRules.Add(loggingRule); } + private static void RegisterGlobalFilters() + { + LogManager.Setup().LoadConfiguration(c => + { + c.ForLogger("Microsoft.Hosting.Lifetime*").WriteToNil(LogLevel.Info); + c.ForLogger("System*").WriteToNil(LogLevel.Warn); + c.ForLogger("Microsoft*").WriteToNil(LogLevel.Warn); + }); + } + private static void RegisterConsole() { var level = LogLevel.Trace; diff --git a/src/NzbDrone.Common/Lidarr.Common.csproj b/src/NzbDrone.Common/Lidarr.Common.csproj index 1294f5fbe..9b6be37d9 100644 --- a/src/NzbDrone.Common/Lidarr.Common.csproj +++ b/src/NzbDrone.Common/Lidarr.Common.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/NzbDrone.Core/Download/CompletedDownloadService.cs b/src/NzbDrone.Core/Download/CompletedDownloadService.cs index d80af358e..9e0518cb1 100644 --- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs +++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using NLog; -using NLog.Fluent; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Common.Instrumentation.Extensions; @@ -206,14 +205,14 @@ namespace NzbDrone.Core.Download } else { - _logger.Debug() + _logger.ForDebugEvent() .Message("No albums were just imported, but all albums were previously imported, possible issue with download history.") .Property("ArtistId", trackedDownload.RemoteAlbum.Artist.Id) .Property("DownloadId", trackedDownload.DownloadItem.DownloadId) .Property("Title", trackedDownload.DownloadItem.Title) .Property("Path", trackedDownload.DownloadItem.OutputPath.ToString()) .WriteSentryWarn("DownloadHistoryIncomplete") - .Write(); + .Log(); } trackedDownload.State = TrackedDownloadState.Imported; diff --git a/src/NzbDrone.Core/Instrumentation/ReconfigureLogging.cs b/src/NzbDrone.Core/Instrumentation/ReconfigureLogging.cs index ee7d2f072..38b695ccc 100644 --- a/src/NzbDrone.Core/Instrumentation/ReconfigureLogging.cs +++ b/src/NzbDrone.Core/Instrumentation/ReconfigureLogging.cs @@ -117,7 +117,6 @@ namespace NzbDrone.Core.Instrumentation syslogTarget.MessageSend.Protocol = ProtocolType.Udp; syslogTarget.MessageSend.Udp.Port = syslogPort; syslogTarget.MessageSend.Udp.Server = syslogServer; - syslogTarget.MessageSend.Udp.ReconnectInterval = 500; syslogTarget.MessageCreation.Rfc = RfcNumber.Rfc5424; syslogTarget.MessageCreation.Rfc5424.AppName = _configFileProvider.InstanceName; diff --git a/src/NzbDrone.Core/Lidarr.Core.csproj b/src/NzbDrone.Core/Lidarr.Core.csproj index 2fcf79c1b..34b027680 100644 --- a/src/NzbDrone.Core/Lidarr.Core.csproj +++ b/src/NzbDrone.Core/Lidarr.Core.csproj @@ -14,9 +14,9 @@ - - - + + + diff --git a/src/NzbDrone.Core/MediaFiles/AudioTag.cs b/src/NzbDrone.Core/MediaFiles/AudioTag.cs index e4fb6d2a1..f5b226dc2 100644 --- a/src/NzbDrone.Core/MediaFiles/AudioTag.cs +++ b/src/NzbDrone.Core/MediaFiles/AudioTag.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using NLog; -using NLog.Fluent; using NzbDrone.Common.Extensions; using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Instrumentation.Extensions; @@ -441,11 +440,11 @@ namespace NzbDrone.Core.MediaFiles } catch (Exception ex) { - Logger.Warn() - .Exception(ex) - .Message($"Tag writing failed for {path}") - .WriteSentryWarn("Tag writing failed") - .Write(); + Logger.ForWarnEvent() + .Exception(ex) + .Message($"Tag writing failed for {path}") + .WriteSentryWarn("Tag writing failed") + .Log(); } finally { diff --git a/src/NzbDrone.Core/MediaFiles/AudioTagService.cs b/src/NzbDrone.Core/MediaFiles/AudioTagService.cs index 043531fcf..6f4692189 100644 --- a/src/NzbDrone.Core/MediaFiles/AudioTagService.cs +++ b/src/NzbDrone.Core/MediaFiles/AudioTagService.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using NLog; -using NLog.Fluent; using NzbDrone.Common.Disk; using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Core.Configuration; @@ -161,11 +160,11 @@ namespace NzbDrone.Core.MediaFiles } catch (Exception ex) { - _logger.Warn() - .Exception(ex) - .Message($"Tag removal failed for {path}") - .WriteSentryWarn("Tag removal failed") - .Write(); + _logger.ForWarnEvent() + .Exception(ex) + .Message($"Tag removal failed for {path}") + .WriteSentryWarn("Tag removal failed") + .Log(); } finally { diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfoFormatter.cs b/src/NzbDrone.Core/MediaFiles/MediaInfoFormatter.cs index 60bb6b75d..4909cfcc8 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfoFormatter.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfoFormatter.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using NLog; -using NLog.Fluent; using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Core.Parser; @@ -65,10 +64,10 @@ namespace NzbDrone.Core.MediaFiles } else { - Logger.Debug() - .Message("Unknown audio format: '{0}'.", string.Join(", ", mediaInfo.AudioFormat)) - .WriteSentryWarn("UnknownAudioFormat", mediaInfo.AudioFormat) - .Write(); + Logger.ForDebugEvent() + .Message("Unknown audio format: '{0}'.", string.Join(", ", mediaInfo.AudioFormat)) + .WriteSentryWarn("UnknownAudioFormat", mediaInfo.AudioFormat) + .Log(); return "Unknown"; } diff --git a/src/NzbDrone.Mono/Lidarr.Mono.csproj b/src/NzbDrone.Mono/Lidarr.Mono.csproj index 8c59b7ec9..a254e31ed 100644 --- a/src/NzbDrone.Mono/Lidarr.Mono.csproj +++ b/src/NzbDrone.Mono/Lidarr.Mono.csproj @@ -4,7 +4,7 @@ true - + diff --git a/src/NzbDrone.Test.Common/Lidarr.Test.Common.csproj b/src/NzbDrone.Test.Common/Lidarr.Test.Common.csproj index 56d7b19f6..506cd84fc 100644 --- a/src/NzbDrone.Test.Common/Lidarr.Test.Common.csproj +++ b/src/NzbDrone.Test.Common/Lidarr.Test.Common.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/NzbDrone.Update/Lidarr.Update.csproj b/src/NzbDrone.Update/Lidarr.Update.csproj index 4425a843d..ce0c5acfd 100644 --- a/src/NzbDrone.Update/Lidarr.Update.csproj +++ b/src/NzbDrone.Update/Lidarr.Update.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/NzbDrone.Windows/Lidarr.Windows.csproj b/src/NzbDrone.Windows/Lidarr.Windows.csproj index e6e8d7116..1da818088 100644 --- a/src/NzbDrone.Windows/Lidarr.Windows.csproj +++ b/src/NzbDrone.Windows/Lidarr.Windows.csproj @@ -4,7 +4,7 @@ true - + From f74b6e5275ef19847ab83da3922efc42e9b8e6c6 Mon Sep 17 00:00:00 2001 From: "servarr[bot]" <68984020+servarr[bot]@users.noreply.github.com> Date: Mon, 26 Sep 2022 17:19:13 -0500 Subject: [PATCH 0018/1478] New: Parse version with a space before 'v' * New: Parse anime version with a space before 'v' (cherry picked from commit e9123982f33ab35ca022f91f345da05fef23d6dc) * Delete AnimeVersionFixture.cs Co-authored-by: Mark McDowall Co-authored-by: Qstick --- src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs | 1 - src/NzbDrone.Core/Parser/QualityParser.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs index 98ffd96be..f0536942d 100644 --- a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs @@ -7,7 +7,6 @@ using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.ParserTests { [TestFixture] - public class QualityParserFixture : CoreTest { public static object[] SelfQualityParserCases = diff --git a/src/NzbDrone.Core/Parser/QualityParser.cs b/src/NzbDrone.Core/Parser/QualityParser.cs index 2865db0df..02e8f32e7 100644 --- a/src/NzbDrone.Core/Parser/QualityParser.cs +++ b/src/NzbDrone.Core/Parser/QualityParser.cs @@ -20,7 +20,7 @@ namespace NzbDrone.Core.Parser private static readonly Regex RepackRegex = new Regex(@"\b(?repack|rerip)\b", RegexOptions.Compiled | RegexOptions.IgnoreCase); - private static readonly Regex VersionRegex = new Regex(@"\dv(?\d)\b|\[v(?\d)\]", + private static readonly Regex VersionRegex = new Regex(@"\d[-._ ]?v(?\d)[-._ ]|\[v(?\d)\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex RealRegex = new Regex(@"\b(?REAL)\b", From 24c2b1519f33e24120525b6a437d605359ec254d Mon Sep 17 00:00:00 2001 From: Servarr Date: Mon, 26 Sep 2022 22:25:46 +0000 Subject: [PATCH 0019/1478] Automated API Docs update --- src/Lidarr.Api.V1/openapi.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Lidarr.Api.V1/openapi.json b/src/Lidarr.Api.V1/openapi.json index 1410d99d9..791985233 100644 --- a/src/Lidarr.Api.V1/openapi.json +++ b/src/Lidarr.Api.V1/openapi.json @@ -12646,6 +12646,14 @@ }, "nullable": true }, + "indexerIds": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "nullable": true + }, "artistIds": { "type": "array", "items": { From dc7b5b52dcd782042dac7e402b2ba2a9e334a2f8 Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 28 Jun 2021 22:02:09 -0400 Subject: [PATCH 0020/1478] Added searchEngine support in Newznab/Torznab caps Co-Authored-By: Taloth (cherry picked from commit eb76dd5248988c8101d9414aef0215f01f81cf00) --- .../SearchDefinitionFixture.cs | 6 +-- .../NewznabCapabilitiesProviderFixture.cs | 25 ++++++++++- .../Definitions/AlbumSearchCriteria.cs | 3 +- .../Definitions/SearchCriteriaBase.cs | 3 +- .../FileList/FileListRequestGenerator.cs | 4 +- .../Gazelle/GazelleRequestGenerator.cs | 4 +- .../Headphones/HeadphonesRequestGenerator.cs | 4 +- .../Indexers/Newznab/NewznabCapabilities.cs | 4 ++ .../Newznab/NewznabCapabilitiesProvider.cs | 19 +++++++-- .../Newznab/NewznabRequestGenerator.cs | 41 ++++++++++++++++--- .../Indexers/Rarbg/RarbgRequestGenerator.cs | 4 +- .../Redacted/RedactedRequestGenerator.cs | 4 +- 12 files changed, 95 insertions(+), 26 deletions(-) diff --git a/src/NzbDrone.Core.Test/IndexerSearchTests/SearchDefinitionFixture.cs b/src/NzbDrone.Core.Test/IndexerSearchTests/SearchDefinitionFixture.cs index a0a921ce8..4cce55185 100644 --- a/src/NzbDrone.Core.Test/IndexerSearchTests/SearchDefinitionFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerSearchTests/SearchDefinitionFixture.cs @@ -13,7 +13,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests public void should_replace_some_special_characters_artist(string artist, string expected) { Subject.Artist = new Artist { Name = artist }; - Subject.ArtistQuery.Should().Be(expected); + Subject.CleanArtistQuery.Should().Be(expected); } [TestCase("…and Justice for All", "and+Justice+for+All")] @@ -25,14 +25,14 @@ namespace NzbDrone.Core.Test.IndexerSearchTests public void should_replace_some_special_characters(string album, string expected) { Subject.AlbumTitle = album; - Subject.AlbumQuery.Should().Be(expected); + Subject.CleanAlbumQuery.Should().Be(expected); } [TestCase("+", "+")] public void should_not_replace_some_special_characters_if_result_empty_string(string album, string expected) { Subject.AlbumTitle = album; - Subject.AlbumQuery.Should().Be(expected); + Subject.CleanAlbumQuery.Should().Be(expected); } } } diff --git a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabCapabilitiesProviderFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabCapabilitiesProviderFixture.cs index f7afd220d..c36b4dd75 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabCapabilitiesProviderFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabCapabilitiesProviderFixture.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Xml; using FluentAssertions; using Moq; @@ -97,5 +97,28 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests ExceptionVerification.ExpectedErrors(1); } + + [Test] + public void should_use_default_searchengine_if_missing() + { + GivenCapsResponse(_caps); + + var caps = Subject.GetCapabilities(_settings); + + caps.TextSearchEngine.Should().Be("sphinx"); + caps.AudioTextSearchEngine.Should().Be("sphinx"); + } + + [Test] + public void should_use_specified_searchengine() + { + GivenCapsResponse(_caps.Replace(" GetQueryTitle($"{AlbumTitle}{(Disambiguation.IsNullOrWhiteSpace() ? string.Empty : $"+{Disambiguation}")}"); + public string AlbumQuery => $"{AlbumTitle}{(Disambiguation.IsNullOrWhiteSpace() ? string.Empty : $"+{Disambiguation}")}"; + public string CleanAlbumQuery => GetQueryTitle(AlbumQuery); public override string ToString() { diff --git a/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs b/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs index 8dbbe7b6c..087087658 100644 --- a/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs +++ b/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs @@ -20,7 +20,8 @@ namespace NzbDrone.Core.IndexerSearch.Definitions public List Albums { get; set; } public List Tracks { get; set; } - public string ArtistQuery => GetQueryTitle(Artist.Name); + public string ArtistQuery => Artist.Name; + public string CleanArtistQuery => GetQueryTitle(ArtistQuery); public static string GetQueryTitle(string title) { diff --git a/src/NzbDrone.Core/Indexers/FileList/FileListRequestGenerator.cs b/src/NzbDrone.Core/Indexers/FileList/FileListRequestGenerator.cs index 42748ff38..df7c47dba 100644 --- a/src/NzbDrone.Core/Indexers/FileList/FileListRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/FileList/FileListRequestGenerator.cs @@ -23,7 +23,7 @@ namespace NzbDrone.Core.Indexers.FileList { var pageableRequests = new IndexerPageableRequestChain(); - pageableRequests.Add(GetRequest("search-torrents", Settings.Categories, string.Format("&type=name&query={0}+{1}", Uri.EscapeDataString(searchCriteria.ArtistQuery.Trim()), Uri.EscapeDataString(searchCriteria.AlbumQuery.Trim())))); + pageableRequests.Add(GetRequest("search-torrents", Settings.Categories, string.Format("&type=name&query={0}+{1}", Uri.EscapeDataString(searchCriteria.CleanArtistQuery.Trim()), Uri.EscapeDataString(searchCriteria.CleanAlbumQuery.Trim())))); return pageableRequests; } @@ -32,7 +32,7 @@ namespace NzbDrone.Core.Indexers.FileList { var pageableRequests = new IndexerPageableRequestChain(); - pageableRequests.Add(GetRequest("search-torrents", Settings.Categories, string.Format("&type=name&query={0}", Uri.EscapeDataString(searchCriteria.ArtistQuery.Trim())))); + pageableRequests.Add(GetRequest("search-torrents", Settings.Categories, string.Format("&type=name&query={0}", Uri.EscapeDataString(searchCriteria.CleanArtistQuery.Trim())))); return pageableRequests; } diff --git a/src/NzbDrone.Core/Indexers/Gazelle/GazelleRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Gazelle/GazelleRequestGenerator.cs index 1353fcc5c..a43630fa4 100644 --- a/src/NzbDrone.Core/Indexers/Gazelle/GazelleRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/Gazelle/GazelleRequestGenerator.cs @@ -30,14 +30,14 @@ namespace NzbDrone.Core.Indexers.Gazelle public IndexerPageableRequestChain GetSearchRequests(AlbumSearchCriteria searchCriteria) { var pageableRequests = new IndexerPageableRequestChain(); - pageableRequests.Add(GetRequest(string.Format("&artistname={0}&groupname={1}", searchCriteria.ArtistQuery, searchCriteria.AlbumQuery))); + pageableRequests.Add(GetRequest(string.Format("&artistname={0}&groupname={1}", searchCriteria.CleanArtistQuery, searchCriteria.CleanAlbumQuery))); return pageableRequests; } public IndexerPageableRequestChain GetSearchRequests(ArtistSearchCriteria searchCriteria) { var pageableRequests = new IndexerPageableRequestChain(); - pageableRequests.Add(GetRequest(string.Format("&artistname={0}", searchCriteria.ArtistQuery))); + pageableRequests.Add(GetRequest(string.Format("&artistname={0}", searchCriteria.CleanArtistQuery))); return pageableRequests; } diff --git a/src/NzbDrone.Core/Indexers/Headphones/HeadphonesRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Headphones/HeadphonesRequestGenerator.cs index 882f33e5e..88ae02afb 100644 --- a/src/NzbDrone.Core/Indexers/Headphones/HeadphonesRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/Headphones/HeadphonesRequestGenerator.cs @@ -39,7 +39,7 @@ namespace NzbDrone.Core.Indexers.Headphones pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "search", - NewsnabifyTitle($"&q={searchCriteria.ArtistQuery}+{searchCriteria.AlbumQuery}"))); + NewsnabifyTitle($"&q={searchCriteria.CleanArtistQuery}+{searchCriteria.CleanAlbumQuery}"))); return pageableRequests; } @@ -53,7 +53,7 @@ namespace NzbDrone.Core.Indexers.Headphones pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "search", - NewsnabifyTitle($"&q={searchCriteria.ArtistQuery}"))); + NewsnabifyTitle($"&q={searchCriteria.CleanArtistQuery}"))); return pageableRequests; } diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilities.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilities.cs index 035915704..2fa6cf0ec 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilities.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilities.cs @@ -10,6 +10,8 @@ namespace NzbDrone.Core.Indexers.Newznab public string[] SupportedTvSearchParameters { get; set; } public string[] SupportedAudioSearchParameters { get; set; } public bool SupportsAggregateIdSearch { get; set; } + public string TextSearchEngine { get; set; } + public string AudioTextSearchEngine { get; set; } public List Categories { get; set; } public NewznabCapabilities() @@ -20,6 +22,8 @@ namespace NzbDrone.Core.Indexers.Newznab SupportedTvSearchParameters = new[] { "q", "rid", "season", "ep" }; // This should remain 'rid' for older newznab installs. SupportedAudioSearchParameters = new[] { "q", "artist", "album" }; SupportsAggregateIdSearch = false; + TextSearchEngine = "sphinx"; // This should remain 'sphinx' for older newznab installs + AudioTextSearchEngine = "sphinx"; // This should remain 'sphinx' for older newznab installs Categories = new List(); } } diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs index 46d9b14e4..5de9805b7 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs @@ -118,9 +118,14 @@ namespace NzbDrone.Core.Indexers.Newznab { capabilities.SupportedSearchParameters = null; } - else if (xmlBasicSearch.Attribute("supportedParams") != null) + else { - capabilities.SupportedSearchParameters = xmlBasicSearch.Attribute("supportedParams").Value.Split(','); + if (xmlBasicSearch.Attribute("supportedParams") != null) + { + capabilities.SupportedSearchParameters = xmlBasicSearch.Attribute("supportedParams").Value.Split(','); + } + + capabilities.TextSearchEngine = xmlBasicSearch.Attribute("searchEngine")?.Value ?? capabilities.TextSearchEngine; } var xmlTvSearch = xmlSearching.Element("tv-search"); @@ -139,9 +144,15 @@ namespace NzbDrone.Core.Indexers.Newznab { capabilities.SupportedAudioSearchParameters = null; } - else if (xmlAudioSearch.Attribute("supportedParams") != null) + else { - capabilities.SupportedAudioSearchParameters = xmlAudioSearch.Attribute("supportedParams").Value.Split(','); + if (xmlAudioSearch.Attribute("supportedParams") != null) + { + capabilities.SupportedAudioSearchParameters = xmlAudioSearch.Attribute("supportedParams").Value.Split(','); + capabilities.SupportsAggregateIdSearch = true; + } + + capabilities.AudioTextSearchEngine = xmlAudioSearch.Attribute("searchEngine")?.Value ?? capabilities.AudioTextSearchEngine; } } diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs index f7f167b34..9b4022a23 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs @@ -45,6 +45,26 @@ namespace NzbDrone.Core.Indexers.Newznab } } + private string TextSearchEngine + { + get + { + var capabilities = _capabilitiesProvider.GetCapabilities(Settings); + + return capabilities.TextSearchEngine; + } + } + + private string AudioTextSearchEngine + { + get + { + var capabilities = _capabilitiesProvider.GetCapabilities(Settings); + + return capabilities.AudioTextSearchEngine; + } + } + public virtual IndexerPageableRequestChain GetRecentRequests() { var pageableRequests = new IndexerPageableRequestChain(); @@ -69,19 +89,25 @@ namespace NzbDrone.Core.Indexers.Newznab if (SupportsAudioSearch) { + var artistQuery = AudioTextSearchEngine == "raw" ? searchCriteria.ArtistQuery : searchCriteria.CleanArtistQuery; + var albumQuery = AudioTextSearchEngine == "raw" ? searchCriteria.AlbumQuery : searchCriteria.CleanAlbumQuery; + AddAudioPageableRequests(pageableRequests, searchCriteria, - NewsnabifyTitle($"&artist={searchCriteria.ArtistQuery}&album={searchCriteria.AlbumQuery}")); + NewsnabifyTitle($"&artist={artistQuery}&album={albumQuery}")); } if (SupportsSearch) { pageableRequests.AddTier(); + var artistQuery = TextSearchEngine == "raw" ? searchCriteria.ArtistQuery : searchCriteria.CleanArtistQuery; + var albumQuery = TextSearchEngine == "raw" ? searchCriteria.AlbumQuery : searchCriteria.CleanAlbumQuery; + pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "search", - NewsnabifyTitle($"&q={searchCriteria.ArtistQuery}+{searchCriteria.AlbumQuery}"))); + NewsnabifyTitle($"&q={artistQuery}+{albumQuery}"))); } return pageableRequests; @@ -93,19 +119,22 @@ namespace NzbDrone.Core.Indexers.Newznab if (SupportsAudioSearch) { + var queryTitle = AudioTextSearchEngine == "raw" ? searchCriteria.ArtistQuery : searchCriteria.CleanArtistQuery; + AddAudioPageableRequests(pageableRequests, searchCriteria, - NewsnabifyTitle($"&artist={searchCriteria.ArtistQuery}")); + NewsnabifyTitle($"&artist={queryTitle}")); } if (SupportsSearch) { pageableRequests.AddTier(); + var queryTitle = TextSearchEngine == "raw" ? searchCriteria.ArtistQuery : searchCriteria.CleanArtistQuery; pageableRequests.Add(GetPagedRequests(MaxPages, - Settings.Categories, - "search", - NewsnabifyTitle($"&q={searchCriteria.ArtistQuery}"))); + Settings.Categories, + "search", + NewsnabifyTitle($"&q={queryTitle}"))); } return pageableRequests; diff --git a/src/NzbDrone.Core/Indexers/Rarbg/RarbgRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Rarbg/RarbgRequestGenerator.cs index a1fae523c..d36a5332a 100644 --- a/src/NzbDrone.Core/Indexers/Rarbg/RarbgRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/Rarbg/RarbgRequestGenerator.cs @@ -30,7 +30,7 @@ namespace NzbDrone.Core.Indexers.Rarbg { var pageableRequests = new IndexerPageableRequestChain(); - pageableRequests.Add(GetPagedRequests("search", null, "{0}+{1}", searchCriteria.ArtistQuery, searchCriteria.AlbumQuery)); + pageableRequests.Add(GetPagedRequests("search", null, "{0}+{1}", searchCriteria.CleanArtistQuery, searchCriteria.CleanAlbumQuery)); return pageableRequests; } @@ -39,7 +39,7 @@ namespace NzbDrone.Core.Indexers.Rarbg { var pageableRequests = new IndexerPageableRequestChain(); - pageableRequests.Add(GetPagedRequests("search", null, "{0}", searchCriteria.ArtistQuery)); + pageableRequests.Add(GetPagedRequests("search", null, "{0}", searchCriteria.CleanArtistQuery)); return pageableRequests; } diff --git a/src/NzbDrone.Core/Indexers/Redacted/RedactedRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Redacted/RedactedRequestGenerator.cs index a20ceaa69..9e5487960 100644 --- a/src/NzbDrone.Core/Indexers/Redacted/RedactedRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/Redacted/RedactedRequestGenerator.cs @@ -28,14 +28,14 @@ namespace NzbDrone.Core.Indexers.Redacted public IndexerPageableRequestChain GetSearchRequests(AlbumSearchCriteria searchCriteria) { var pageableRequests = new IndexerPageableRequestChain(); - pageableRequests.Add(GetRequest(string.Format("&artistname={0}&groupname={1}", searchCriteria.ArtistQuery, searchCriteria.AlbumQuery))); + pageableRequests.Add(GetRequest(string.Format("&artistname={0}&groupname={1}", searchCriteria.CleanArtistQuery, searchCriteria.CleanAlbumQuery))); return pageableRequests; } public IndexerPageableRequestChain GetSearchRequests(ArtistSearchCriteria searchCriteria) { var pageableRequests = new IndexerPageableRequestChain(); - pageableRequests.Add(GetRequest(string.Format("&artistname={0}", searchCriteria.ArtistQuery))); + pageableRequests.Add(GetRequest(string.Format("&artistname={0}", searchCriteria.CleanArtistQuery))); return pageableRequests; } From c63b08265cb8244f7779268aba0a32f270a7a30c Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 26 Sep 2022 17:57:54 -0500 Subject: [PATCH 0021/1478] Fixed: Escape Characters as needed for *znab queries Co-Authored-By: bakerboy448 <55419169+bakerboy448@users.noreply.github.com> --- .../Indexers/Newznab/NewznabRequestGenerator.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs index 9b4022a23..05efd709f 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using NzbDrone.Common.Extensions; @@ -94,7 +95,7 @@ namespace NzbDrone.Core.Indexers.Newznab AddAudioPageableRequests(pageableRequests, searchCriteria, - NewsnabifyTitle($"&artist={artistQuery}&album={albumQuery}")); + $"&artist={NewsnabifyTitle(artistQuery)}&album={NewsnabifyTitle(albumQuery)}"); } if (SupportsSearch) @@ -107,7 +108,7 @@ namespace NzbDrone.Core.Indexers.Newznab pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "search", - NewsnabifyTitle($"&q={artistQuery}+{albumQuery}"))); + $"&q={NewsnabifyTitle($"{artistQuery}+{albumQuery}")}")); } return pageableRequests; @@ -123,7 +124,7 @@ namespace NzbDrone.Core.Indexers.Newznab AddAudioPageableRequests(pageableRequests, searchCriteria, - NewsnabifyTitle($"&artist={queryTitle}")); + $"&artist={NewsnabifyTitle(queryTitle)}"); } if (SupportsSearch) @@ -134,7 +135,7 @@ namespace NzbDrone.Core.Indexers.Newznab pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "search", - NewsnabifyTitle($"&q={queryTitle}"))); + $"&q={NewsnabifyTitle(queryTitle)}")); } return pageableRequests; @@ -180,7 +181,8 @@ namespace NzbDrone.Core.Indexers.Newznab private static string NewsnabifyTitle(string title) { - return title.Replace("+", "%20"); + title = title.Replace("+", " "); + return Uri.EscapeDataString(title); } } } From 25c9de857b376fb30b02184b82a128b59bff4a50 Mon Sep 17 00:00:00 2001 From: bakerboy448 <55419169+bakerboy448@users.noreply.github.com> Date: Thu, 22 Sep 2022 11:49:19 -0500 Subject: [PATCH 0022/1478] Fixed: Repack Preference Ignored (cherry picked from commit 04447d9d4db8cc3d54baf0a721f4430cf77908c4) --- .../RepackSpecificationFixture.cs | 85 +++++++++++++++++++ .../Specifications/RepackSpecification.cs | 20 ++++- 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RepackSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RepackSpecificationFixture.cs index 17d49a457..dc3f6713a 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/RepackSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RepackSpecificationFixture.cs @@ -1,9 +1,11 @@ +using System; using System.Collections.Generic; using System.Linq; using FizzWare.NBuilder; using FluentAssertions; using Moq; using NUnit.Framework; +using NzbDrone.Core.Configuration; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Music; @@ -244,5 +246,88 @@ namespace NzbDrone.Core.Test.DecisionEngineTests .Should() .BeFalse(); } + + [Test] + public void should_return_true_when_repacks_are_not_preferred() + { + Mocker.GetMock() + .Setup(s => s.DownloadPropersAndRepacks) + .Returns(ProperDownloadTypes.DoNotPrefer); + + _trackFiles.Select(c => + { + c.ReleaseGroup = ""; + return c; + }).ToList(); + + _trackFiles.Select(c => + { + c.Quality = new QualityModel(Quality.FLAC); + return c; + }).ToList(); + + var remoteAlbum = Builder.CreateNew() + .With(e => e.ParsedAlbumInfo = _parsedAlbumInfo) + .With(e => e.Albums = _albums) + .Build(); + + Subject.IsSatisfiedBy(remoteAlbum, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_true_when_repack_but_auto_download_repacks_is_true() + { + Mocker.GetMock() + .Setup(s => s.DownloadPropersAndRepacks) + .Returns(ProperDownloadTypes.PreferAndUpgrade); + + _parsedAlbumInfo.Quality.Revision.IsRepack = true; + + _trackFiles.Select(c => + { + c.ReleaseGroup = "Lidarr"; + return c; + }).ToList(); + _trackFiles.Select(c => + { + c.Quality = new QualityModel(Quality.FLAC); + return c; + }).ToList(); + + var remoteAlbum = Builder.CreateNew() + .With(e => e.ParsedAlbumInfo = _parsedAlbumInfo) + .With(e => e.Albums = _albums) + .Build(); + + Subject.IsSatisfiedBy(remoteAlbum, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_false_when_repack_but_auto_download_repacks_is_false() + { + Mocker.GetMock() + .Setup(s => s.DownloadPropersAndRepacks) + .Returns(ProperDownloadTypes.DoNotUpgrade); + + _parsedAlbumInfo.Quality.Revision.IsRepack = true; + + _trackFiles.Select(c => + { + c.ReleaseGroup = "Lidarr"; + return c; + }).ToList(); + _trackFiles.Select(c => + { + c.Quality = new QualityModel(Quality.FLAC); + return c; + }).ToList(); + + var remoteAlbum = Builder.CreateNew() + .With(e => e.ParsedAlbumInfo = _parsedAlbumInfo) + .With(e => e.Albums = _albums) + .Build(); + + Subject.IsSatisfiedBy(remoteAlbum, null).Accepted.Should().BeFalse(); + } } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RepackSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RepackSpecification.cs index 86198d776..f44db3f9f 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RepackSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RepackSpecification.cs @@ -1,9 +1,11 @@ using System; using NLog; using NzbDrone.Common.Extensions; +using NzbDrone.Core.Configuration; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Qualities; namespace NzbDrone.Core.DecisionEngine.Specifications { @@ -11,12 +13,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { private readonly IMediaFileService _mediaFileService; private readonly UpgradableSpecification _upgradableSpecification; + private readonly IConfigService _configService; private readonly Logger _logger; - public RepackSpecification(IMediaFileService mediaFileService, UpgradableSpecification upgradableSpecification, Logger logger) + public RepackSpecification(IMediaFileService mediaFileService, UpgradableSpecification upgradableSpecification, IConfigService configService, Logger logger) { _mediaFileService = mediaFileService; _upgradableSpecification = upgradableSpecification; + _configService = configService; _logger = logger; } @@ -30,6 +34,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return Decision.Accept(); } + var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks; + + if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer) + { + _logger.Debug("Repacks are not preferred, skipping check"); + return Decision.Accept(); + } + foreach (var album in subject.Albums) { var releaseGroup = subject.ParsedAlbumInfo.ReleaseGroup; @@ -39,6 +51,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { if (_upgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedAlbumInfo.Quality)) { + if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotUpgrade) + { + _logger.Debug("Auto downloading of repacks is disabled"); + return Decision.Reject("Repack downloading is disabled"); + } + var fileReleaseGroup = file.ReleaseGroup; if (fileReleaseGroup.IsNullOrWhiteSpace()) From ef1e744e2f0936fbd4dc7e1c932cc5e25da66194 Mon Sep 17 00:00:00 2001 From: Chromo-residuum-opec <12422133+ufUNnxagpM@users.noreply.github.com> Date: Thu, 22 Sep 2022 09:51:16 -0700 Subject: [PATCH 0023/1478] Update help text for rTorrent download client options (cherry picked from commit d2a23f7bcdf71800f019644d7b6b5d712e311d7f) --- .../Download/Clients/rTorrent/RTorrentSettings.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs index fc668ad56..d3411b3d8 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs @@ -37,10 +37,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent [FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)] public int Port { get; set; } - [FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to ruTorrent")] + [FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to rTorrent")] public bool UseSsl { get; set; } - [FieldDefinition(3, Label = "Url Path", Type = FieldType.Textbox, HelpText = "Path to the XMLRPC endpoint, see http(s)://[host]:[port]/[urlPath]. When using ruTorrent this usually is RPC2 or (path to ruTorrent)/plugins/rpc/rpc.php")] + [FieldDefinition(3, Label = "Url Path", Type = FieldType.Textbox, HelpText = "Path to the XMLRPC endpoint, see http(s)://[host]:[port]/[urlPath]. This is usually RPC2 or [path to ruTorrent]/plugins/rpc/rpc.php when using ruTorrent.")] public string UrlBase { get; set; } [FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)] @@ -49,7 +49,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent [FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)] public string Password { get; set; } - [FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional.")] + [FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated non-Lidarr downloads. Using a category is optional, but strongly recommended.")] public string MusicCategory { get; set; } [FieldDefinition(7, Label = "Post-Import Category", Type = FieldType.Textbox, Advanced = true, HelpText = "Category for Lidarr to set after it has imported the download. Lidarr will not remove torrents in that category even if seeding finished. Leave blank to keep same category.")] @@ -64,7 +64,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent [FieldDefinition(10, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")] public int OlderTvPriority { get; set; } - [FieldDefinition(11, Label = "Add Stopped", Type = FieldType.Checkbox, HelpText = "Enabling will add torrents and magnets to ruTorrent in a stopped state. This may break magnet files.")] + [FieldDefinition(11, Label = "Add Stopped", Type = FieldType.Checkbox, HelpText = "Enabling will add torrents and magnets to rTorrent in a stopped state. This may break magnet files.")] public bool AddStopped { get; set; } public NzbDroneValidationResult Validate() From 64c2fb2deefe44eea10f98343b2f257b8e3d4f34 Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 26 Sep 2022 18:54:00 -0500 Subject: [PATCH 0024/1478] Fixed: Fall back to sorting by release title if artist is not matched --- src/Lidarr.Api.V1/Queue/QueueController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Lidarr.Api.V1/Queue/QueueController.cs b/src/Lidarr.Api.V1/Queue/QueueController.cs index 8787845da..40402bb3e 100644 --- a/src/Lidarr.Api.V1/Queue/QueueController.cs +++ b/src/Lidarr.Api.V1/Queue/QueueController.cs @@ -172,7 +172,7 @@ namespace Lidarr.Api.V1.Queue case "status": return q => q.Status; case "artists.sortName": - return q => q.Artist?.SortName ?? string.Empty; + return q => q.Artist?.SortName ?? q.Title; case "title": return q => q.Title; case "album": From 6670aa71160d0b27cf02cdd009a664d1442c2449 Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 26 Sep 2022 19:07:17 -0500 Subject: [PATCH 0025/1478] New: Add Release group to history for all events Co-Authored-By: Mark McDowall --- src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs | 2 ++ src/NzbDrone.Core/Download/IgnoredDownloadService.cs | 1 + src/NzbDrone.Core/History/EntityHistoryService.cs | 8 ++++++++ 3 files changed, 11 insertions(+) diff --git a/src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs b/src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs index ca999c88e..490bff25f 100644 --- a/src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs +++ b/src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using NzbDrone.Common.Messaging; +using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.Qualities; namespace NzbDrone.Core.Download @@ -13,5 +14,6 @@ namespace NzbDrone.Core.Download public DownloadClientItemClientInfo DownloadClientInfo { get; set; } public string DownloadId { get; set; } public string Message { get; set; } + public TrackedDownload TrackedDownload { get; set; } } } diff --git a/src/NzbDrone.Core/Download/IgnoredDownloadService.cs b/src/NzbDrone.Core/Download/IgnoredDownloadService.cs index ceaea619d..02d01cb5d 100644 --- a/src/NzbDrone.Core/Download/IgnoredDownloadService.cs +++ b/src/NzbDrone.Core/Download/IgnoredDownloadService.cs @@ -42,6 +42,7 @@ namespace NzbDrone.Core.Download SourceTitle = trackedDownload.DownloadItem.Title, DownloadClientInfo = trackedDownload.DownloadItem.DownloadClientInfo, DownloadId = trackedDownload.DownloadItem.DownloadId, + TrackedDownload = trackedDownload, Message = "Manually ignored" }; diff --git a/src/NzbDrone.Core/History/EntityHistoryService.cs b/src/NzbDrone.Core/History/EntityHistoryService.cs index 302f23801..93c29fe8e 100644 --- a/src/NzbDrone.Core/History/EntityHistoryService.cs +++ b/src/NzbDrone.Core/History/EntityHistoryService.cs @@ -197,6 +197,7 @@ namespace NzbDrone.Core.History }; history.Data.Add("StatusMessages", message.TrackedDownload.StatusMessages.ToJson()); + history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteAlbum?.ParsedAlbumInfo?.ReleaseGroup); _historyRepository.Insert(history); } } @@ -234,6 +235,7 @@ namespace NzbDrone.Core.History history.Data.Add("DroppedPath", message.TrackInfo.Path); history.Data.Add("ImportedPath", message.ImportedTrack.Path); history.Data.Add("DownloadClient", message.DownloadClientInfo.Name); + history.Data.Add("ReleaseGroup", message.TrackInfo.ReleaseGroup); _historyRepository.Insert(history); } @@ -276,6 +278,8 @@ namespace NzbDrone.Core.History DownloadId = message.TrackedDownload.DownloadItem.DownloadId }; + history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteAlbum?.ParsedAlbumInfo?.ReleaseGroup); + _historyRepository.Insert(history); } } @@ -307,6 +311,7 @@ namespace NzbDrone.Core.History }; history.Data.Add("Reason", message.Reason.ToString()); + history.Data.Add("ReleaseGroup", message.TrackFile.ReleaseGroup); _historyRepository.Insert(history); } @@ -332,6 +337,7 @@ namespace NzbDrone.Core.History history.Data.Add("SourcePath", sourcePath); history.Data.Add("Path", path); + history.Data.Add("ReleaseGroup", message.TrackFile.ReleaseGroup); _historyRepository.Insert(history); } @@ -355,6 +361,7 @@ namespace NzbDrone.Core.History }; history.Data.Add("TagsScrubbed", message.Scrubbed.ToString()); + history.Data.Add("ReleaseGroup", message.TrackFile.ReleaseGroup); history.Data.Add("Diff", message.Diff.Select(x => new { Field = x.Key, @@ -388,6 +395,7 @@ namespace NzbDrone.Core.History }; history.Data.Add("DownloadClient", message.DownloadClientInfo.Name); + history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteAlbum?.ParsedAlbumInfo?.ReleaseGroup); history.Data.Add("Message", message.Message); historyToAdd.Add(history); From 4703f8ac6ca5a60f3463a0499b81b159746bd1eb Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 26 Sep 2022 19:12:08 -0500 Subject: [PATCH 0026/1478] Fixed: Clarify Indexer Priority Helptext Co-Authored-By: bakerboy448 <55419169+bakerboy448@users.noreply.github.com> --- src/NzbDrone.Core/Localization/Core/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 230ce6453..4dfbf403d 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -432,7 +432,7 @@ "PreviewRetag": "Preview Retag", "PrimaryAlbumTypes": "Primary Album Types", "PrimaryTypes": "Primary Types", - "PriorityHelpText": "Indexer Priority from 1 (Highest) to 50 (Lowest). Default: 25.", + "PriorityHelpText": "Indexer Priority from 1 (Highest) to 50 (Lowest). Default: 25. Used when grabbing releases as a tiebreaker for otherwise equal releases, Lidarr will still use all enabled indexers for RSS Sync and Searching.", "Profiles": "Profiles", "Proper": "Proper", "PropersAndRepacks": "Propers and Repacks", From 7dde5e0e42a5e77ff63fe35c3a6a443f0e37c2b7 Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 26 Sep 2022 20:02:46 -0500 Subject: [PATCH 0027/1478] New: Send TrackFile in Webhook retag payload Fixes #2533 --- src/NzbDrone.Core/Notifications/Webhook/Webhook.cs | 3 ++- src/NzbDrone.Core/Notifications/Webhook/WebhookRetagPayload.cs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/Notifications/Webhook/Webhook.cs b/src/NzbDrone.Core/Notifications/Webhook/Webhook.cs index 9257b7601..ac4177028 100644 --- a/src/NzbDrone.Core/Notifications/Webhook/Webhook.cs +++ b/src/NzbDrone.Core/Notifications/Webhook/Webhook.cs @@ -87,7 +87,8 @@ namespace NzbDrone.Core.Notifications.Webhook var payload = new WebhookRetagPayload { EventType = WebhookEventType.Retag, - Artist = new WebhookArtist(message.Artist) + Artist = new WebhookArtist(message.Artist), + TrackFile = new WebhookTrackFile(message.TrackFile) }; _proxy.SendWebhook(payload, Settings); diff --git a/src/NzbDrone.Core/Notifications/Webhook/WebhookRetagPayload.cs b/src/NzbDrone.Core/Notifications/Webhook/WebhookRetagPayload.cs index b969060da..970900c16 100644 --- a/src/NzbDrone.Core/Notifications/Webhook/WebhookRetagPayload.cs +++ b/src/NzbDrone.Core/Notifications/Webhook/WebhookRetagPayload.cs @@ -3,5 +3,6 @@ namespace NzbDrone.Core.Notifications.Webhook public class WebhookRetagPayload : WebhookPayload { public WebhookArtist Artist { get; set; } + public WebhookTrackFile TrackFile { get; set; } } } From 7fcebba7860299f4bee6b97f4739d08d4a7ff614 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 2 Oct 2022 18:54:17 -0500 Subject: [PATCH 0028/1478] Fixed: Parse WEBFLAC as FLAC Fixes #3013 --- src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs | 2 ++ src/NzbDrone.Core/Parser/QualityParser.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs index f0536942d..640049fb9 100644 --- a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs @@ -100,6 +100,7 @@ namespace NzbDrone.Core.Test.ParserTests } [TestCase("Kendrick Lamar - DAMN (2017) FLAC", null, 0)] + [TestCase("Kid_Cudi-Entergalactic-WEBFLAC-2022-NACHOS", null, 0)] [TestCase("Alicia Keys - Vault Playlist Vol. 1 (2017) [FLAC CD]", null, 0)] [TestCase("Gorillaz - Humanz (Deluxe) - lossless FLAC Tracks - 2017 - CDrip", null, 0)] [TestCase("David Bowie - Blackstar (2016) [FLAC]", null, 0)] @@ -117,6 +118,7 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("Beck.-.Guero.2005.[2016.Remastered].24bit.96kHz.LOSSLESS.FLAC", null, 0, 0)] [TestCase("[R.E.M - Lifes Rich Pageant(1986) [24bit192kHz 2016 Remaster]LOSSLESS FLAC]", null, 0, 0)] + [TestCase("Kid_Cudi-Entergalactic-24BIT-WEBFLAC-2022-NACHOS", null, 0, 0)] [TestCase("", "Flac Audio", 5057, 24)] public void should_parse_flac_24bit_quality(string title, string desc, int bitrate, int sampleSize) { diff --git a/src/NzbDrone.Core/Parser/QualityParser.cs b/src/NzbDrone.Core/Parser/QualityParser.cs index 02e8f32e7..34083fecc 100644 --- a/src/NzbDrone.Core/Parser/QualityParser.cs +++ b/src/NzbDrone.Core/Parser/QualityParser.cs @@ -40,7 +40,7 @@ namespace NzbDrone.Core.Parser private static readonly Regex SampleSizeRegex = new Regex(@"\b(?:(?24[ ]bit|24bit|[\[\(].*24bit.*[\]\)]))"); - private static readonly Regex CodecRegex = new Regex(@"\b(?:(?MPEG Version \d(.5)? Audio, Layer 1|MP1)|(?MPEG Version \d(.5)? Audio, Layer 2|MP2)|(?MP3.*VBR|MPEG Version \d(.5)? Audio, Layer 3 vbr)|(?MP3|MPEG Version \d(.5)? Audio, Layer 3)|(?flac)|(?wavpack|wv)|(?alac)|(?WMA\d?)|(?WAV|PCM)|(?M4A|M4P|M4B|AAC|mp4a|MPEG-4 Audio(?!.*alac))|(?OGG|OGA|Vorbis))\b|(?monkey's audio|[\[|\(].*\bape\b.*[\]|\)])|(?Opus Version \d(.5)? Audio|[\[|\(].*\bopus\b.*[\]|\)])", + private static readonly Regex CodecRegex = new Regex(@"\b(?:(?MPEG Version \d(.5)? Audio, Layer 1|MP1)|(?MPEG Version \d(.5)? Audio, Layer 2|MP2)|(?MP3.*VBR|MPEG Version \d(.5)? Audio, Layer 3 vbr)|(?MP3|MPEG Version \d(.5)? Audio, Layer 3)|(?(web)?flac)|(?wavpack|wv)|(?alac)|(?WMA\d?)|(?WAV|PCM)|(?M4A|M4P|M4B|AAC|mp4a|MPEG-4 Audio(?!.*alac))|(?OGG|OGA|Vorbis))\b|(?monkey's audio|[\[|\(].*\bape\b.*[\]|\)])|(?Opus Version \d(.5)? Audio|[\[|\(].*\bopus\b.*[\]|\)])", RegexOptions.Compiled | RegexOptions.IgnoreCase); public static QualityModel ParseQuality(string name, string desc, int fileBitrate, int fileSampleSize = 0) From 977fb31a914eadf172138946400b548bc0d5787e Mon Sep 17 00:00:00 2001 From: Qstick Date: Fri, 14 Oct 2022 22:13:44 -0500 Subject: [PATCH 0029/1478] Update FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 8988a141d..49c3e2d71 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +github: lidarr # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: lidarr ko_fi: # Replace with a single Ko-fi username From aabd25510ec0e9e12b0e4d571b46ed182f91d9a6 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 16 Oct 2022 23:27:42 -0500 Subject: [PATCH 0030/1478] Add thread to discord notification Fixes #3042 --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5457f61b9..6714d055d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1022,3 +1022,4 @@ stages: SYSTEM_ACCESSTOKEN: $(System.AccessToken) DISCORDCHANNELID: $(discordChannelId) DISCORDWEBHOOKKEY: $(discordWebhookKey) + DISCORDTHREADID: $(discordThreadId) From c4d8f663224f563879225ab36306211ff35213db Mon Sep 17 00:00:00 2001 From: Zak Saunders Date: Tue, 18 Oct 2022 00:51:55 +0100 Subject: [PATCH 0031/1478] New: Darkmode (#3039) * New: Native Theme Engine Co-Authored-By: Zak Saunders (cherry picked from commit 2291f3e00eb2ff9268a0b2f49da8dde82ee13c04) * Update CSS for themes Co-authored-by: Qstick --- frontend/postcss.config.js | 1 - frontend/src/Activity/Queue/ProtocolLabel.css | 8 +- frontend/src/Album/AlbumTitleLink.css | 2 +- .../Album/Delete/DeleteAlbumModalContent.css | 2 +- frontend/src/Album/Details/AlbumDetails.css | 8 +- .../src/Album/Details/AlbumDetailsMedium.css | 8 +- frontend/src/Album/SceneInfo.css | 5 + frontend/src/AlbumStudio/AlbumStudioAlbum.css | 14 +- frontend/src/App/App.js | 9 +- frontend/src/App/ApplyTheme.js | 49 ++++ .../Delete/DeleteArtistModalContent.css | 2 +- frontend/src/Artist/Details/ArtistDetails.css | 10 +- .../Artist/Details/ArtistDetailsSeason.css | 10 +- .../Delete/DeleteArtistModalContent.css | 2 +- .../src/Artist/Index/ArtistIndexFooter.css | 12 +- .../Index/Banners/ArtistIndexBanner.css | 14 +- .../Index/Banners/ArtistIndexBannerInfo.css | 2 +- .../Index/Overview/ArtistIndexOverview.css | 10 +- .../Index/Posters/ArtistIndexPoster.css | 17 +- .../ProgressBar/ArtistIndexProgressBar.css | 2 +- .../src/Artist/Index/Table/ArtistIndexRow.css | 6 +- frontend/src/Calendar/Agenda/AgendaEvent.css | 4 +- frontend/src/Calendar/Day/CalendarDay.css | 10 +- frontend/src/Calendar/Day/CalendarDays.css | 2 +- frontend/src/Calendar/Day/DayOfWeek.css | 4 +- .../src/Calendar/Events/CalendarEvent.css | 40 ++- .../Events/CalendarEventQueueDetails.js | 3 +- frontend/src/Components/Alert.css | 24 +- frontend/src/Components/Card.css | 6 +- .../src/Components/CircularProgressBar.js | 3 +- frontend/src/Components/FieldSet.css | 2 +- .../FileBrowser/FileBrowserModalContent.css | 2 +- .../Filter/Builder/FilterBuilderRow.css | 2 +- .../Builder/FilterBuilderRowValueTag.css | 3 +- .../Filter/CustomFilters/CustomFilter.css | 2 +- .../src/Components/Form/AutoSuggestInput.css | 8 +- frontend/src/Components/Form/CheckInput.css | 32 +-- .../Components/Form/EnhancedSelectInput.css | 14 +- .../Form/EnhancedSelectInputOption.css | 10 +- .../Form/EnhancedSelectInputSelectedValue.css | 2 +- .../src/Components/Form/FormInputGroup.css | 2 +- .../src/Components/Form/FormInputHelpText.css | 10 +- frontend/src/Components/Form/FormLabel.css | 4 +- .../Form/HintedSelectInputOption.css | 2 +- .../Form/HintedSelectInputSelectedValue.css | 2 +- frontend/src/Components/Form/Input.css | 19 +- .../src/Components/Form/KeyValueListInput.css | 4 +- .../Components/Form/KeyValueListInputItem.css | 2 +- .../Form/RootFolderSelectInputOption.css | 2 +- .../RootFolderSelectInputSelectedValue.css | 2 +- frontend/src/Components/Form/TagInput.css | 6 +- frontend/src/Components/Form/TextInput.css | 2 +- frontend/src/Components/Form/UMaskInput.css | 2 +- frontend/src/Components/HeartRating.css | 2 +- frontend/src/Components/Icon.css | 22 +- frontend/src/Components/Label.css | 58 +++-- frontend/src/Components/Link/Button.css | 60 ++--- frontend/src/Components/Link/IconButton.css | 4 +- frontend/src/Components/Link/Link.css | 4 +- .../Components/Loading/LoadingIndicator.css | 2 +- frontend/src/Components/Menu/MenuButton.css | 4 +- frontend/src/Components/Menu/MenuContent.css | 2 +- frontend/src/Components/Menu/MenuItem.css | 10 +- .../src/Components/Menu/MenuItemSeparator.css | 2 +- frontend/src/Components/Modal/Modal.css | 2 +- .../src/Components/Modal/ModalContent.css | 4 +- frontend/src/Components/Modal/ModalFooter.css | 2 +- frontend/src/Components/Modal/ModalHeader.css | 2 +- .../src/Components/MonitorToggleButton.css | 2 +- .../Page/Header/ArtistSearchInput.css | 20 +- .../Page/Header/ArtistSearchResult.css | 2 +- .../Header/KeyboardShortcutsModalContent.css | 4 +- .../src/Components/Page/Header/PageHeader.css | 6 +- .../Page/Header/PageHeaderActionsMenu.css | 2 +- frontend/src/Components/Page/LoadingPage.css | 2 + .../src/Components/Page/PageContentFooter.css | 2 +- .../src/Components/Page/PageJumpBarItem.css | 2 +- .../Page/Sidebar/Messages/Message.css | 12 +- .../Components/Page/Sidebar/PageSidebar.css | 6 +- .../Page/Sidebar/PageSidebarItem.css | 12 +- .../Components/Page/Toolbar/PageToolbar.css | 4 +- .../Page/Toolbar/PageToolbarButton.css | 8 +- frontend/src/Components/ProgressBar.css | 22 +- .../Components/Scroller/OverlayScroller.css | 4 +- .../Components/Table/Cells/TableRowCell.css | 2 +- .../Table/TableOptions/TableOptionsColumn.css | 2 +- frontend/src/Components/Table/TablePager.css | 4 +- frontend/src/Components/Table/TableRow.css | 2 +- .../src/Components/Table/VirtualTableRow.css | 2 +- frontend/src/Components/Tooltip/Popover.css | 5 +- frontend/src/Components/Tooltip/Tooltip.css | 41 +-- .../Artist/SelectArtistRow.css | 2 +- .../InteractiveImportModalContent.css | 2 +- .../Interactive/InteractiveImportRow.css | 2 +- .../InteractiveImportRowCellPlaceholder.css | 2 +- frontend/src/Organize/OrganizePreviewRow.css | 2 +- frontend/src/Retag/RetagPreviewRow.css | 2 +- frontend/src/Search/AddNewItem.css | 6 +- .../Search/Album/AddNewAlbumModalContent.css | 6 +- .../Search/Album/AddNewAlbumSearchResult.css | 10 +- .../Artist/AddNewArtistModalContent.css | 4 +- .../Artist/AddNewArtistSearchResult.css | 6 +- .../src/Settings/AdvancedSettingsButton.css | 6 +- .../DownloadClients/DownloadClients.css | 8 +- .../RemotePathMappings/RemotePathMapping.css | 2 +- .../ImportListExclusion.css | 2 +- .../ImportLists/ImportLists/ImportLists.css | 8 +- .../Settings/Indexers/Indexers/Indexers.css | 8 +- .../MediaManagement/Naming/NamingModal.css | 17 ++ .../MediaManagement/Naming/NamingOption.css | 11 +- .../RootFolder/RootFolders.css | 8 +- .../Notifications/Notifications.css | 8 +- .../Settings/Profiles/Delay/DelayProfile.css | 2 +- .../Profiles/Metadata/MetadataProfiles.css | 8 +- .../Profiles/Quality/QualityProfileItem.css | 2 +- .../Quality/QualityProfileItemGroup.css | 4 +- .../Profiles/Quality/QualityProfiles.css | 8 +- .../Profiles/Release/ReleaseProfiles.css | 8 +- .../Quality/Definition/QualityDefinition.css | 8 +- .../Quality/Definition/QualityDefinitions.css | 2 +- frontend/src/Settings/Settings.css | 4 +- .../Tags/Details/TagDetailsModalContent.css | 2 +- frontend/src/Settings/UI/UISettings.js | 24 +- frontend/src/Styles/Mixins/scroller.css | 4 +- frontend/src/Styles/Themes/dark.js | 239 ++++++++++++++++++ frontend/src/Styles/Themes/index.js | 7 + .../{Variables/colors.js => Themes/light.js} | 69 ++++- frontend/src/Styles/scaffolding.css | 2 +- .../System/Events/LogsTableDetailsModal.css | 4 +- frontend/src/System/Events/LogsTableRow.css | 6 +- .../src/TrackFile/ExpandingFileDetails.css | 4 +- frontend/src/index.css | 2 +- src/Lidarr.Api.V1/Config/ConfigController.cs | 4 +- .../Config/UiConfigController.cs | 24 +- src/Lidarr.Api.V1/Config/UiConfigResource.cs | 6 +- .../Frontend/InitializeJsController.cs | 1 + .../Configuration/ConfigFileProvider.cs | 3 + 137 files changed, 894 insertions(+), 448 deletions(-) create mode 100644 frontend/src/App/ApplyTheme.js create mode 100644 frontend/src/Styles/Themes/dark.js create mode 100644 frontend/src/Styles/Themes/index.js rename frontend/src/Styles/{Variables/colors.js => Themes/light.js} (76%) diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js index 6d15f7369..9b0c88719 100644 --- a/frontend/postcss.config.js +++ b/frontend/postcss.config.js @@ -1,7 +1,6 @@ const reload = require('require-nocache')(module); const cssVarsFiles = [ - './src/Styles/Variables/colors', './src/Styles/Variables/dimensions', './src/Styles/Variables/fonts', './src/Styles/Variables/animations', diff --git a/frontend/src/Activity/Queue/ProtocolLabel.css b/frontend/src/Activity/Queue/ProtocolLabel.css index 259fd5c65..110c7e01c 100644 --- a/frontend/src/Activity/Queue/ProtocolLabel.css +++ b/frontend/src/Activity/Queue/ProtocolLabel.css @@ -1,13 +1,13 @@ .torrent { composes: label from '~Components/Label.css'; - border-color: $torrentColor; - background-color: $torrentColor; + border-color: var(--torrentColor); + background-color: var(--torrentColor); } .usenet { composes: label from '~Components/Label.css'; - border-color: $usenetColor; - background-color: $usenetColor; + border-color: var(--usenetColor); + background-color: var(--usenetColor); } diff --git a/frontend/src/Album/AlbumTitleLink.css b/frontend/src/Album/AlbumTitleLink.css index 47d897238..7fd85c836 100644 --- a/frontend/src/Album/AlbumTitleLink.css +++ b/frontend/src/Album/AlbumTitleLink.css @@ -2,7 +2,7 @@ composes: link from '~Components/Link/Link.css'; &:hover { - color: $linkHoverColor; + color: var(--linkHoverColor); text-decoration: underline; } } diff --git a/frontend/src/Album/Delete/DeleteAlbumModalContent.css b/frontend/src/Album/Delete/DeleteAlbumModalContent.css index dbfef0871..df8e4822e 100644 --- a/frontend/src/Album/Delete/DeleteAlbumModalContent.css +++ b/frontend/src/Album/Delete/DeleteAlbumModalContent.css @@ -8,5 +8,5 @@ .deleteFilesMessage { margin-top: 20px; - color: $dangerColor; + color: var(--dangerColor); } diff --git a/frontend/src/Album/Details/AlbumDetails.css b/frontend/src/Album/Details/AlbumDetails.css index 28902dd6c..dd6626c96 100644 --- a/frontend/src/Album/Details/AlbumDetails.css +++ b/frontend/src/Album/Details/AlbumDetails.css @@ -20,7 +20,7 @@ position: absolute; width: 100%; height: 100%; - background: $black; + background: var(--black); opacity: 0.7; } @@ -29,7 +29,7 @@ padding: 30px; width: 100%; height: 100%; - color: $white; + color: var(--white); } .cover { @@ -75,7 +75,7 @@ width: 40px; &:hover { - color: $iconButtonHoverLightColor; + color: var(--iconButtonHoverLightColor); } } @@ -99,7 +99,7 @@ white-space: nowrap; &:hover { - color: $iconButtonHoverLightColor; + color: var(--iconButtonHoverLightColor); } } diff --git a/frontend/src/Album/Details/AlbumDetailsMedium.css b/frontend/src/Album/Details/AlbumDetailsMedium.css index 67418316d..865d84a0a 100644 --- a/frontend/src/Album/Details/AlbumDetailsMedium.css +++ b/frontend/src/Album/Details/AlbumDetailsMedium.css @@ -1,8 +1,8 @@ .medium { margin-bottom: 20px; - border: 1px solid $borderColor; + border: 1px solid var(--borderColor); border-radius: 4px; - background-color: $white; + background-color: var(--white); &:last-of-type { margin-bottom: 0; @@ -72,13 +72,13 @@ .tracks { padding-top: 15px; - border-top: 1px solid $borderColor; + border-top: 1px solid var(--borderColor); } .collapseButtonContainer { padding: 10px 15px; width: 100%; - border-top: 1px solid $borderColor; + border-top: 1px solid var(--borderColor); border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; background-color: #fafafa; diff --git a/frontend/src/Album/SceneInfo.css b/frontend/src/Album/SceneInfo.css index 8a5f4bccd..6f1155494 100644 --- a/frontend/src/Album/SceneInfo.css +++ b/frontend/src/Album/SceneInfo.css @@ -15,3 +15,8 @@ margin-left: 100px; } + +.comment { + color: var(--darkGray); + font-size: $smallFontSize; +} diff --git a/frontend/src/AlbumStudio/AlbumStudioAlbum.css b/frontend/src/AlbumStudio/AlbumStudioAlbum.css index f3c9f6102..d412e1d49 100644 --- a/frontend/src/AlbumStudio/AlbumStudioAlbum.css +++ b/frontend/src/AlbumStudio/AlbumStudioAlbum.css @@ -3,9 +3,9 @@ align-items: stretch; overflow: hidden; margin: 2px 4px; - border: 1px solid $borderColor; + border: 1px solid var(--borderColor); border-radius: 4px; - background-color: #eee; + background-color: var(--seasonBackgroundColor); cursor: default; } @@ -17,15 +17,15 @@ padding: 0 4px; border-width: 0 1px; border-style: solid; - border-color: $borderColor; - background-color: $white; - color: $defaultColor; + border-color: var(--borderColor); + background-color: var(--white); + color: var(--defaultColor); } .tracks { padding: 0 4px; - background-color: $white; - color: $defaultColor; + background-color: var(--episodesBackgroundColor); + color: var(--defaultColor); } .allTracks { diff --git a/frontend/src/App/App.js b/frontend/src/App/App.js index e4384040a..3871b14e9 100644 --- a/frontend/src/App/App.js +++ b/frontend/src/App/App.js @@ -4,6 +4,7 @@ import React from 'react'; import DocumentTitle from 'react-document-title'; import { Provider } from 'react-redux'; import PageConnector from 'Components/Page/PageConnector'; +import ApplyTheme from './ApplyTheme'; import AppRoutes from './AppRoutes'; function App({ store, history }) { @@ -11,9 +12,11 @@ function App({ store, history }) { - - - + + + + + diff --git a/frontend/src/App/ApplyTheme.js b/frontend/src/App/ApplyTheme.js new file mode 100644 index 000000000..170b51596 --- /dev/null +++ b/frontend/src/App/ApplyTheme.js @@ -0,0 +1,49 @@ +import PropTypes from 'prop-types'; +import React, { Fragment, useEffect } from 'react'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import themes from 'Styles/Themes'; + +function createMapStateToProps() { + return createSelector( + (state) => state.settings.ui.item.theme || window.Lidarr.theme, + ( + theme + ) => { + return { + theme + }; + } + ); +} + +function ApplyTheme({ theme, children }) { + // Update the CSS Variables + function updateCSSVariables() { + const arrayOfVariableKeys = Object.keys(themes[theme]); + const arrayOfVariableValues = Object.values(themes[theme]); + + // Loop through each array key and set the CSS Variables + arrayOfVariableKeys.forEach((cssVariableKey, index) => { + // Based on our snippet from MDN + document.documentElement.style.setProperty( + `--${cssVariableKey}`, + arrayOfVariableValues[index] + ); + }); + } + + // On Component Mount and Component Update + useEffect(() => { + updateCSSVariables(theme); + }, [theme]); + + return {children}; +} + +ApplyTheme.propTypes = { + theme: PropTypes.string.isRequired, + children: PropTypes.object.isRequired +}; + +export default connect(createMapStateToProps)(ApplyTheme); diff --git a/frontend/src/Artist/Delete/DeleteArtistModalContent.css b/frontend/src/Artist/Delete/DeleteArtistModalContent.css index dbfef0871..df8e4822e 100644 --- a/frontend/src/Artist/Delete/DeleteArtistModalContent.css +++ b/frontend/src/Artist/Delete/DeleteArtistModalContent.css @@ -8,5 +8,5 @@ .deleteFilesMessage { margin-top: 20px; - color: $dangerColor; + color: var(--dangerColor); } diff --git a/frontend/src/Artist/Details/ArtistDetails.css b/frontend/src/Artist/Details/ArtistDetails.css index e2e24ea3c..ee2171999 100644 --- a/frontend/src/Artist/Details/ArtistDetails.css +++ b/frontend/src/Artist/Details/ArtistDetails.css @@ -26,7 +26,7 @@ position: absolute; width: 100%; height: 100%; - background: $black; + background: var(--black); opacity: 0.7; } @@ -35,7 +35,7 @@ padding: 30px; width: 100%; height: 100%; - color: $white; + color: var(--white); } .poster { @@ -53,7 +53,7 @@ } .metadataMessage { - color: $helpTextColor; + color: var(--helpTextColor); text-align: center; font-weight: 300; font-size: 20px; @@ -88,7 +88,7 @@ width: 40px; &:hover { - color: $iconButtonHoverLightColor; + color: var(--iconButtonHoverLightColor); } } @@ -112,7 +112,7 @@ white-space: nowrap; &:hover { - color: $iconButtonHoverLightColor; + color: var(--iconButtonHoverLightColor); } } diff --git a/frontend/src/Artist/Details/ArtistDetailsSeason.css b/frontend/src/Artist/Details/ArtistDetailsSeason.css index 127f0c772..b1cb2632e 100644 --- a/frontend/src/Artist/Details/ArtistDetailsSeason.css +++ b/frontend/src/Artist/Details/ArtistDetailsSeason.css @@ -1,8 +1,8 @@ .albumType { margin-bottom: 20px; - border: 1px solid $borderColor; + border: 1px solid var(--borderColor); border-radius: 4px; - background-color: $white; + background-color: var(--cardBackgroundColor); &:last-of-type { margin-bottom: 0; @@ -77,7 +77,7 @@ .albums { padding-top: 15px; - border-top: 1px solid $borderColor; + border-top: 1px solid var(--borderColor); } .collapseButtonContainer { @@ -86,10 +86,10 @@ justify-content: center; padding: 10px 15px; width: 100%; - border-top: 1px solid $borderColor; + border-top: 1px solid var(--borderColor); border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; - background-color: #fafafa; + background-color: var(--collapseButtonBackgroundColor); } .collapseButtonIcon { diff --git a/frontend/src/Artist/Editor/Delete/DeleteArtistModalContent.css b/frontend/src/Artist/Editor/Delete/DeleteArtistModalContent.css index 950fdc27d..02a0514be 100644 --- a/frontend/src/Artist/Editor/Delete/DeleteArtistModalContent.css +++ b/frontend/src/Artist/Editor/Delete/DeleteArtistModalContent.css @@ -9,5 +9,5 @@ .path { margin-left: 5px; - color: $dangerColor; + color: var(--dangerColor); } diff --git a/frontend/src/Artist/Index/ArtistIndexFooter.css b/frontend/src/Artist/Index/ArtistIndexFooter.css index 71d0439b6..c1c4b5a46 100644 --- a/frontend/src/Artist/Index/ArtistIndexFooter.css +++ b/frontend/src/Artist/Index/ArtistIndexFooter.css @@ -21,32 +21,32 @@ .continuing { composes: legendItemColor; - background-color: $primaryColor; + background-color: var(--primaryColor); } .ended { composes: legendItemColor; - background-color: $successColor; + background-color: var(--successColor); } .missingMonitored { composes: legendItemColor; - background-color: $dangerColor; + background-color: var(--dangerColor); &:global(.colorImpaired) { - background: repeating-linear-gradient(90deg, color($dangerColor shade(5%)), color($dangerColor shade(5%)) 5px, color($dangerColor shade(15%)) 5px, color($dangerColor shade(15%)) 10px); + background: repeating-linear-gradient(90deg, color(#f05050 shade(5%)), color(#f05050 shade(5%)) 5px, color(#f05050 shade(15%)) 5px, color(#f05050 shade(15%)) 10px); } } .missingUnmonitored { composes: legendItemColor; - background-color: $warningColor; + background-color: var(--warningColor); &:global(.colorImpaired) { - background: repeating-linear-gradient(45deg, $warningColor, $warningColor 5px, color($warningColor tint(15%)) 5px, color($warningColor tint(15%)) 10px); + background: repeating-linear-gradient(45deg, #ffa500, #ffa500 5px, color(#ffa500 tint(15%)) 5px, color(#ffa500 tint(15%)) 10px); } } diff --git a/frontend/src/Artist/Index/Banners/ArtistIndexBanner.css b/frontend/src/Artist/Index/Banners/ArtistIndexBanner.css index 3f9bfdd8b..e22472389 100644 --- a/frontend/src/Artist/Index/Banners/ArtistIndexBanner.css +++ b/frontend/src/Artist/Index/Banners/ArtistIndexBanner.css @@ -9,7 +9,7 @@ $hoverScale: 1.05; &:hover { z-index: 2; - box-shadow: 0 0 12px $black; + box-shadow: 0 0 12px var(--black); transition: all 200ms ease-in; .controls { @@ -27,7 +27,7 @@ $hoverScale: 1.05; composes: link from '~Components/Link/Link.css'; display: block; - background-color: $defaultColor; + background-color: var(--defaultColor); } .nextAiring { @@ -39,8 +39,8 @@ $hoverScale: 1.05; .title { @add-mixin truncate; - background-color: $defaultColor; - color: $white; + background-color: var(--defaultColor); + color: var(--white); text-align: center; font-size: $smallFontSize; } @@ -53,8 +53,8 @@ $hoverScale: 1.05; height: 0; border-width: 0 25px 25px 0; border-style: solid; - border-color: transparent $dangerColor transparent transparent; - color: $white; + border-color: transparent var(--dangerColor) transparent transparent; + color: var(--white); } .controls { @@ -64,7 +64,7 @@ $hoverScale: 1.05; z-index: 3; border-radius: 4px; background-color: #216044; - color: $white; + color: var(--white); font-size: $smallFontSize; opacity: 0; transition: opacity 0; diff --git a/frontend/src/Artist/Index/Banners/ArtistIndexBannerInfo.css b/frontend/src/Artist/Index/Banners/ArtistIndexBannerInfo.css index aab27d827..17741773d 100644 --- a/frontend/src/Artist/Index/Banners/ArtistIndexBannerInfo.css +++ b/frontend/src/Artist/Index/Banners/ArtistIndexBannerInfo.css @@ -1,5 +1,5 @@ .info { - background-color: #fafbfc; + background-color: var(--seriesBackgroundColor); text-align: center; font-size: $smallFontSize; } diff --git a/frontend/src/Artist/Index/Overview/ArtistIndexOverview.css b/frontend/src/Artist/Index/Overview/ArtistIndexOverview.css index 054319ebc..3b1888228 100644 --- a/frontend/src/Artist/Index/Overview/ArtistIndexOverview.css +++ b/frontend/src/Artist/Index/Overview/ArtistIndexOverview.css @@ -3,7 +3,7 @@ $hoverScale: 1.05; .container { &:hover { .content { - background-color: $tableRowHoverBackgroundColor; + background-color: var(--tableRowHoverBackgroundColor); } } } @@ -25,10 +25,10 @@ $hoverScale: 1.05; composes: link from '~Components/Link/Link.css'; display: block; - color: $defaultColor; + color: var(--defaultColor); &:hover { - color: $defaultColor; + color: var(--defaultColor); text-decoration: none; } } @@ -42,8 +42,8 @@ $hoverScale: 1.05; height: 0; border-width: 0 25px 25px 0; border-style: solid; - border-color: transparent $dangerColor transparent transparent; - color: $white; + border-color: transparent var(--dangerColor) transparent transparent; + color: var(--white); } .info { diff --git a/frontend/src/Artist/Index/Posters/ArtistIndexPoster.css b/frontend/src/Artist/Index/Posters/ArtistIndexPoster.css index d09daa3e0..8e691c74b 100644 --- a/frontend/src/Artist/Index/Posters/ArtistIndexPoster.css +++ b/frontend/src/Artist/Index/Posters/ArtistIndexPoster.css @@ -5,7 +5,7 @@ $hoverScale: 1.05; &:hover { z-index: 2; - box-shadow: 0 0 12px $black; + box-shadow: 0 0 12px var(--black); transition: all 200ms ease-in; .controls { @@ -25,7 +25,7 @@ $hoverScale: 1.05; position: relative; display: block; height: 70px; - background-color: $defaultColor; + background-color: var(--defaultColor); } .overlayTitle { @@ -38,13 +38,13 @@ $hoverScale: 1.05; padding: 5px; width: 100%; height: 100%; - color: $offWhite; + color: var(--offWhite); text-align: center; font-size: 20px; } .nextAiring { - background-color: #fafbfc; + background-color: var(--seriesBackgroundColor); text-align: center; font-size: $smallFontSize; } @@ -52,8 +52,7 @@ $hoverScale: 1.05; .title { @add-mixin truncate; - background-color: $defaultColor; - color: $white; + background-color: var(--seriesBackgroundColor); text-align: center; font-size: $smallFontSize; } @@ -67,8 +66,8 @@ $hoverScale: 1.05; height: 0; border-width: 0 25px 25px 0; border-style: solid; - border-color: transparent $dangerColor transparent transparent; - color: $white; + border-color: transparent var(--dangerColor) transparent transparent; + color: var(--white); } .controls { @@ -78,7 +77,7 @@ $hoverScale: 1.05; z-index: 3; border-radius: 4px; background-color: #216044; - color: $white; + color: var(--white); font-size: $smallFontSize; opacity: 0; transition: opacity 0; diff --git a/frontend/src/Artist/Index/ProgressBar/ArtistIndexProgressBar.css b/frontend/src/Artist/Index/ProgressBar/ArtistIndexProgressBar.css index b98bb33d5..ce5313877 100644 --- a/frontend/src/Artist/Index/ProgressBar/ArtistIndexProgressBar.css +++ b/frontend/src/Artist/Index/ProgressBar/ArtistIndexProgressBar.css @@ -3,7 +3,7 @@ border-radius: 0; background-color: #5b5b5b; - color: $white; + color: var(--white); transition: width 200ms ease; } diff --git a/frontend/src/Artist/Index/Table/ArtistIndexRow.css b/frontend/src/Artist/Index/Table/ArtistIndexRow.css index 29c89c696..b75ad6afd 100644 --- a/frontend/src/Artist/Index/Table/ArtistIndexRow.css +++ b/frontend/src/Artist/Index/Table/ArtistIndexRow.css @@ -37,7 +37,7 @@ position: relative; display: block; height: 70px; - background-color: $defaultColor; + background-color: var(--defaultColor); } .bannerImage { @@ -55,7 +55,7 @@ padding: 5px; width: 100%; height: 100%; - color: $offWhite; + color: var(--offWhite); text-align: center; font-size: 20px; } @@ -110,7 +110,7 @@ } .ratings { - composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css'; + composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; flex: 0 0 80px; } diff --git a/frontend/src/Calendar/Agenda/AgendaEvent.css b/frontend/src/Calendar/Agenda/AgendaEvent.css index 801e3c3e0..a987a4b8c 100644 --- a/frontend/src/Calendar/Agenda/AgendaEvent.css +++ b/frontend/src/Calendar/Agenda/AgendaEvent.css @@ -2,11 +2,11 @@ display: flex; overflow-x: hidden; padding: 5px; - border-bottom: 1px solid $borderColor; + border-bottom: 1px solid var(--borderColor); font-size: $defaultFontSize; &:hover { - background-color: $tableRowHoverBackgroundColor; + background-color: var(--tableRowHoverBackgroundColor); } } diff --git a/frontend/src/Calendar/Day/CalendarDay.css b/frontend/src/Calendar/Day/CalendarDay.css index 79eb67ae7..22d1b1ccf 100644 --- a/frontend/src/Calendar/Day/CalendarDay.css +++ b/frontend/src/Calendar/Day/CalendarDay.css @@ -2,8 +2,8 @@ flex: 1 0 14.28%; overflow: hidden; min-height: 70px; - border-bottom: 1px solid $calendarBorderColor; - border-left: 1px solid $calendarBorderColor; + border-bottom: 1px solid var(--calendarBorderColor); + border-left: 1px solid var(--calendarBorderColor); } .isSingleDay { @@ -12,14 +12,14 @@ .dayOfMonth { padding-right: 5px; - border-bottom: 1px solid $calendarBorderColor; + border-bottom: 1px solid var(--calendarBorderColor); text-align: right; } .isToday { - background-color: $calendarTodayBackgroundColor; + background-color: var(--calendarTodayBackgroundColor); } .isDifferentMonth { - color: $disabledColor; + color: var(--disabledColor); } diff --git a/frontend/src/Calendar/Day/CalendarDays.css b/frontend/src/Calendar/Day/CalendarDays.css index b6dd2100c..507dd0ede 100644 --- a/frontend/src/Calendar/Day/CalendarDays.css +++ b/frontend/src/Calendar/Day/CalendarDays.css @@ -1,6 +1,6 @@ .days { display: flex; - border-right: 1px solid $calendarBorderColor; + border-right: 1px solid var(--calendarBorderColor); } .day, diff --git a/frontend/src/Calendar/Day/DayOfWeek.css b/frontend/src/Calendar/Day/DayOfWeek.css index 8c3552e55..bb73fa3ba 100644 --- a/frontend/src/Calendar/Day/DayOfWeek.css +++ b/frontend/src/Calendar/Day/DayOfWeek.css @@ -1,6 +1,6 @@ .dayOfWeek { flex: 1 0 14.28%; - background-color: #e4eaec; + background-color: var(--calendarBackgroudColor); text-align: center; } @@ -9,5 +9,5 @@ } .isToday { - background-color: $calendarTodayBackgroundColor; + background-color: var(--calendarTodayBackgroundColor); } diff --git a/frontend/src/Calendar/Events/CalendarEvent.css b/frontend/src/Calendar/Events/CalendarEvent.css index 055d51882..b317c72ce 100644 --- a/frontend/src/Calendar/Events/CalendarEvent.css +++ b/frontend/src/Calendar/Events/CalendarEvent.css @@ -1,9 +1,11 @@ +$fullColorGradient: rgba(244, 245, 246, 0.2); + .event { overflow-x: hidden; margin: 4px 2px; padding: 5px; - border-bottom: 1px solid $borderColor; - border-left: 4px solid $borderColor; + border-bottom: 1px solid var(--calendarBorderColor); + border-left: 4px solid var(--calendarBorderColor); font-size: 12px; &:global(.colorImpaired) { @@ -25,7 +27,7 @@ } .artistName { - color: #3a3f51; + color: var(--calendarTextDimAlternate); font-size: $defaultFontSize; } @@ -42,38 +44,50 @@ */ .downloaded { - border-left-color: $successColor !important; + border-left-color: var(--successColor) !important; &:global(.colorImpaired) { - border-left-color: color($successColor, saturation(+15%)) !important; + border-left-color: color(#27c24c saturation(+15%)) !important; } } .downloading { - border-left-color: $purple !important; + border-left-color: var(--purple) !important; + + &:global(.fullColor) { + background-color: rgba(122, 67, 182, 0.4) !important; + } } .unmonitored { - border-left-color: $gray !important; + border-left-color: var(--gray) !important; + + &:global(.fullColor) { + background-color: rgba(173, 173, 173, 0.5) !important; + } &:global(.colorImpaired) { - background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px); + background: repeating-linear-gradient(45deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px); + } + + &:global(.fullColor.colorImpaired) { + background: repeating-linear-gradient(45deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px); } } .missing { - border-left-color: $dangerColor !important; + border-left-color: var(--dangerColor) !important; &:global(.colorImpaired) { - border-left-color: color($dangerColor saturation(+15%)) !important; - background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px); + border-left-color: color(#f05050 saturation(+15%)) !important; + background: repeating-linear-gradient(90deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px); } } .unreleased { - border-left-color: $primaryColor !important; + border-left-color: var(--primaryColor) !important; &:global(.colorImpaired) { - background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px); + background: repeating-linear-gradient(90deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px); } } diff --git a/frontend/src/Calendar/Events/CalendarEventQueueDetails.js b/frontend/src/Calendar/Events/CalendarEventQueueDetails.js index 6e65c4e1a..5df7e8ea4 100644 --- a/frontend/src/Calendar/Events/CalendarEventQueueDetails.js +++ b/frontend/src/Calendar/Events/CalendarEventQueueDetails.js @@ -2,7 +2,6 @@ import PropTypes from 'prop-types'; import React from 'react'; import QueueDetails from 'Activity/Queue/QueueDetails'; import CircularProgressBar from 'Components/CircularProgressBar'; -import colors from 'Styles/Variables/colors'; import translate from 'Utilities/String/translate'; function CalendarEventQueueDetails(props) { @@ -35,7 +34,7 @@ function CalendarEventQueueDetails(props) { progress={progress} size={20} strokeWidth={2} - strokeColor={colors.purple} + strokeColor={'#7a43b6'} /> } diff --git a/frontend/src/Components/Alert.css b/frontend/src/Components/Alert.css index 312fbb4f2..135d1ee2b 100644 --- a/frontend/src/Components/Alert.css +++ b/frontend/src/Components/Alert.css @@ -7,25 +7,25 @@ } .danger { - border-color: $alertDangerBorderColor; - background-color: $alertDangerBackgroundColor; - color: $alertDangerColor; + border-color: var(--alertDangerBorderColor); + background-color: var(--alertDangerBackgroundColor); + color: var(--alertDangerColor); } .info { - border-color: $alertInfoBorderColor; - background-color: $alertInfoBackgroundColor; - color: $alertInfoColor; + border-color: var(--alertInfoBorderColor); + background-color: var(--alertInfoBackgroundColor); + color: var(--alertInfoColor); } .success { - border-color: $alertSuccessBorderColor; - background-color: $alertSuccessBackgroundColor; - color: $alertSuccessColor; + border-color: var(--alertSuccessBorderColor); + background-color: var(--alertSuccessBackgroundColor); + color: var(--alertSuccessColor); } .warning { - border-color: $alertWarningBorderColor; - background-color: $alertWarningBackgroundColor; - color: $alertWarningColor; + border-color: var(--alertWarningBorderColor); + background-color: var(--alertWarningBackgroundColor); + color: var(--alertWarningColor); } diff --git a/frontend/src/Components/Card.css b/frontend/src/Components/Card.css index b54bbcdf4..49a9abd76 100644 --- a/frontend/src/Components/Card.css +++ b/frontend/src/Components/Card.css @@ -3,9 +3,9 @@ margin: 10px; padding: 10px; border-radius: 3px; - background-color: $white; - box-shadow: 0 0 10px 1px $cardShadowColor; - color: $defaultColor; + background-color: var(--cardBackgroundColor); + box-shadow: 0 0 10px 1px var(--cardShadowColor); + color: var(--defaultColor); } .underlay { diff --git a/frontend/src/Components/CircularProgressBar.js b/frontend/src/Components/CircularProgressBar.js index f373de1d6..6fc47aed9 100644 --- a/frontend/src/Components/CircularProgressBar.js +++ b/frontend/src/Components/CircularProgressBar.js @@ -1,6 +1,5 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import colors from 'Styles/Variables/colors'; import styles from './CircularProgressBar.css'; class CircularProgressBar extends Component { @@ -132,7 +131,7 @@ CircularProgressBar.defaultProps = { containerClassName: styles.circularProgressBarContainer, size: 60, strokeWidth: 5, - strokeColor: colors.lidarrGreen, + strokeColor: '#00A65B', showProgressText: false }; diff --git a/frontend/src/Components/FieldSet.css b/frontend/src/Components/FieldSet.css index 6da64f0e0..9ea0dbab1 100644 --- a/frontend/src/Components/FieldSet.css +++ b/frontend/src/Components/FieldSet.css @@ -13,7 +13,7 @@ width: 100%; border: 0; border-bottom: 1px solid #e5e5e5; - color: #3a3f51; + color: var(--textColor); font-size: 21px; line-height: inherit; diff --git a/frontend/src/Components/FileBrowser/FileBrowserModalContent.css b/frontend/src/Components/FileBrowser/FileBrowserModalContent.css index 7ddb9e806..db31d6f86 100644 --- a/frontend/src/Components/FileBrowser/FileBrowserModalContent.css +++ b/frontend/src/Components/FileBrowser/FileBrowserModalContent.css @@ -13,7 +13,7 @@ } .faqLink { - color: $alertWarningColor; + color: var(--alertWarningColor); font-weight: bold; } diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderRow.css b/frontend/src/Components/Filter/Builder/FilterBuilderRow.css index c5471b253..bcfc3b04e 100644 --- a/frontend/src/Components/Filter/Builder/FilterBuilderRow.css +++ b/frontend/src/Components/Filter/Builder/FilterBuilderRow.css @@ -3,7 +3,7 @@ margin-bottom: 5px; &:hover { - background-color: $tableRowHoverBackgroundColor; + background-color: var(--tableRowHoverBackgroundColor); } } diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderRowValueTag.css b/frontend/src/Components/Filter/Builder/FilterBuilderRowValueTag.css index 9bf027af9..f77146a06 100644 --- a/frontend/src/Components/Filter/Builder/FilterBuilderRowValueTag.css +++ b/frontend/src/Components/Filter/Builder/FilterBuilderRowValueTag.css @@ -15,5 +15,6 @@ .or { margin: 0 3px; - color: $themeDarkColor; + color: var(--themeDarkColor); + line-height: 31px; } diff --git a/frontend/src/Components/Filter/CustomFilters/CustomFilter.css b/frontend/src/Components/Filter/CustomFilters/CustomFilter.css index 7acb69dc7..e2b8c72bf 100644 --- a/frontend/src/Components/Filter/CustomFilters/CustomFilter.css +++ b/frontend/src/Components/Filter/CustomFilters/CustomFilter.css @@ -4,7 +4,7 @@ padding: 5px; &:hover { - background-color: $tableRowHoverBackgroundColor; + background-color: var(--tableRowHoverBackgroundColor); } } diff --git a/frontend/src/Components/Form/AutoSuggestInput.css b/frontend/src/Components/Form/AutoSuggestInput.css index 0f3279cb9..7dc416960 100644 --- a/frontend/src/Components/Form/AutoSuggestInput.css +++ b/frontend/src/Components/Form/AutoSuggestInput.css @@ -27,10 +27,10 @@ overflow-y: auto; max-height: 200px; width: 100%; - border: 1px solid $inputBorderColor; + border: 1px solid var(--inputBorderColor); border-radius: 4px; - background-color: $white; - box-shadow: inset 0 1px 1px $inputBoxShadowColor; + background-color: var(--inputBackgroundColor); + box-shadow: inset 0 1px 1px var(--inputBoxShadowColor); } } @@ -46,5 +46,5 @@ } .suggestionHighlighted { - background-color: $menuItemHoverBackgroundColor; + background-color: var(--menuItemHoverBackgroundColor); } diff --git a/frontend/src/Components/Form/CheckInput.css b/frontend/src/Components/Form/CheckInput.css index e0b05eca3..171121482 100644 --- a/frontend/src/Components/Form/CheckInput.css +++ b/frontend/src/Components/Form/CheckInput.css @@ -32,21 +32,21 @@ height: 20px; border: 1px solid #ccc; border-radius: 2px; - background-color: $white; - color: $white; + background-color: var(--white); + color: var(--white); text-align: center; line-height: 20px; } .checkbox:focus + .input { outline: 0; - border-color: $inputFocusBorderColor; - box-shadow: inset 0 1px 1px $inputBoxShadowColor, 0 0 8px $inputFocusBoxShadowColor; + border-color: var(--inputFocusBorderColor); + box-shadow: inset 0 1px 1px var(--inputBoxShadowColor), 0 0 8px var(--inputFocusBoxShadowColor); } .dangerIsChecked { - border-color: $dangerColor; - background-color: $dangerColor; + border-color: var(--dangerColor); + background-color: var(--dangerColor); &.isDisabled { opacity: 0.7; @@ -54,8 +54,8 @@ } .primaryIsChecked { - border-color: $primaryColor; - background-color: $primaryColor; + border-color: var(--primaryColor); + background-color: var(--primaryColor); &.isDisabled { opacity: 0.7; @@ -63,8 +63,8 @@ } .successIsChecked { - border-color: $successColor; - background-color: $successColor; + border-color: var(--successColor); + background-color: var(--successColor); &.isDisabled { opacity: 0.7; @@ -72,8 +72,8 @@ } .warningIsChecked { - border-color: $warningColor; - background-color: $warningColor; + border-color: var(--warningColor); + background-color: var(--warningColor); &.isDisabled { opacity: 0.7; @@ -82,15 +82,15 @@ .isNotChecked { &.isDisabled { - border-color: $disabledCheckInputColor; - background-color: $disabledCheckInputColor; + border-color: var(--disabledCheckInputColor); + background-color: var(--disabledCheckInputColor); opacity: 0.7; } } .isIndeterminate { - border-color: $gray; - background-color: $gray; + border-color: var(--gray); + background-color: var(--gray); } .helpText { diff --git a/frontend/src/Components/Form/EnhancedSelectInput.css b/frontend/src/Components/Form/EnhancedSelectInput.css index f971f6517..56f5564b9 100644 --- a/frontend/src/Components/Form/EnhancedSelectInput.css +++ b/frontend/src/Components/Form/EnhancedSelectInput.css @@ -39,7 +39,7 @@ .dropdownArrowContainerDisabled { composes: dropdownArrowContainer; - color: $disabledInputColor; + color: var(--disabledInputColor); } .optionsContainer { @@ -50,9 +50,9 @@ .options { composes: scroller from '~Components/Scroller/Scroller.css'; - border: 1px solid $inputBorderColor; + border: 1px solid var(--inputBorderColor); border-radius: 4px; - background-color: $white; + background-color: var(--inputBackgroundColor); } .optionsModal { @@ -76,9 +76,9 @@ .optionsModalScroller { composes: scroller from '~Components/Scroller/Scroller.css'; - border: 1px solid $inputBorderColor; + border: 1px solid var(--inputBorderColor); border-radius: 4px; - background-color: $white; + background-color: var(--inputBackgroundColor); } .loading { @@ -90,7 +90,7 @@ display: flex; justify-content: flex-end; height: 40px; - border-bottom: 1px solid $borderColor; + border-bottom: 1px solid var(--borderColor); } .mobileCloseButton { @@ -100,6 +100,6 @@ line-height: 40px; &:hover { - color: $modalCloseButtonHoverColor; + color: var(--modalCloseButtonHoverColor); } } diff --git a/frontend/src/Components/Form/EnhancedSelectInputOption.css b/frontend/src/Components/Form/EnhancedSelectInputOption.css index f6b6136ae..8dcfa25d5 100644 --- a/frontend/src/Components/Form/EnhancedSelectInputOption.css +++ b/frontend/src/Components/Form/EnhancedSelectInputOption.css @@ -7,7 +7,7 @@ cursor: default; &:hover { - background-color: #f8f8f8; + background-color: var(--inputHoverBackgroundColor); } } @@ -24,17 +24,17 @@ } .isSelected { - background-color: #e2e2e2; + background-color: var(--inputSelectedBackgroundColor); &:hover { - background-color: #e2e2e2; + background-color: var(--inputSelectedBackgroundColor); } &.isMobile { background-color: inherit; .iconContainer { - color: $primaryColor; + color: var(--primaryColor); } } } @@ -49,7 +49,7 @@ .isMobile { height: 50px; - border-bottom: 1px solid $borderColor; + border-bottom: 1px solid var(--borderColor); &:last-child { border: none; diff --git a/frontend/src/Components/Form/EnhancedSelectInputSelectedValue.css b/frontend/src/Components/Form/EnhancedSelectInputSelectedValue.css index 6b8b73af9..908126689 100644 --- a/frontend/src/Components/Form/EnhancedSelectInputSelectedValue.css +++ b/frontend/src/Components/Form/EnhancedSelectInputSelectedValue.css @@ -3,5 +3,5 @@ } .isDisabled { - color: $disabledInputColor; + color: var(--disabledInputColor); } diff --git a/frontend/src/Components/Form/FormInputGroup.css b/frontend/src/Components/Form/FormInputGroup.css index 1a1b104e6..32d91e29b 100644 --- a/frontend/src/Components/Form/FormInputGroup.css +++ b/frontend/src/Components/Form/FormInputGroup.css @@ -40,7 +40,7 @@ } .pendingChangesIcon { - color: $warningColor; + color: var(--warningColor); font-size: 20px; line-height: 35px; } diff --git a/frontend/src/Components/Form/FormInputHelpText.css b/frontend/src/Components/Form/FormInputHelpText.css index 756fd90db..0a6897d27 100644 --- a/frontend/src/Components/Form/FormInputHelpText.css +++ b/frontend/src/Components/Form/FormInputHelpText.css @@ -1,14 +1,14 @@ .helpText { margin-top: 5px; - color: $helpTextColor; + color: var(--helpTextColor); line-height: 20px; } .isError { - color: $dangerColor; + color: var(--dangerColor); .link { - color: $dangerColor; + color: var(--dangerColor); &:hover { color: #e01313; @@ -17,10 +17,10 @@ } .isWarning { - color: $warningColor; + color: var(--warningColor); .link { - color: $warningColor; + color: var(--warningColor); &:hover { color: #e36c00; diff --git a/frontend/src/Components/Form/FormLabel.css b/frontend/src/Components/Form/FormLabel.css index 236f4aab0..074b6091d 100644 --- a/frontend/src/Components/Form/FormLabel.css +++ b/frontend/src/Components/Form/FormLabel.css @@ -7,11 +7,11 @@ } .hasError { - color: $dangerColor; + color: var(--dangerColor); } .isAdvanced { - color: $advancedFormLabelColor; + color: var(--advancedFormLabelColor); } @media only screen and (max-width: $breakpointLarge) { diff --git a/frontend/src/Components/Form/HintedSelectInputOption.css b/frontend/src/Components/Form/HintedSelectInputOption.css index 74d1fb088..74bf6a86e 100644 --- a/frontend/src/Components/Form/HintedSelectInputOption.css +++ b/frontend/src/Components/Form/HintedSelectInputOption.css @@ -18,6 +18,6 @@ @add-mixin truncate; margin-left: 15px; - color: $darkGray; + color: var(--darkGray); font-size: $smallFontSize; } diff --git a/frontend/src/Components/Form/HintedSelectInputSelectedValue.css b/frontend/src/Components/Form/HintedSelectInputSelectedValue.css index a31970a9e..f779b83d7 100644 --- a/frontend/src/Components/Form/HintedSelectInputSelectedValue.css +++ b/frontend/src/Components/Form/HintedSelectInputSelectedValue.css @@ -18,7 +18,7 @@ flex: 1 10 0; margin-left: 15px; - color: $gray; + color: var(--gray); text-align: right; font-size: $smallFontSize; } diff --git a/frontend/src/Components/Form/Input.css b/frontend/src/Components/Form/Input.css index e9ca23d8f..dd9ee888d 100644 --- a/frontend/src/Components/Form/Input.css +++ b/frontend/src/Components/Form/Input.css @@ -2,26 +2,27 @@ padding: 6px 16px; width: 100%; height: 35px; - border: 1px solid $inputBorderColor; + border: 1px solid var(--inputBorderColor); border-radius: 4px; - background-color: $white; - box-shadow: inset 0 1px 1px $inputBoxShadowColor; + background-color: var(--inputBackgroundColor); + box-shadow: inset 0 1px 1px var(--inputBoxShadowColor); + color: var(--textColor); &:focus { outline: 0; - border-color: $inputFocusBorderColor; - box-shadow: inset 0 1px 1px $inputBoxShadowColor, 0 0 8px $inputFocusBoxShadowColor; + border-color: var(--inputFocusBorderColor); + box-shadow: inset 0 1px 1px var(--inputBoxShadowColor), 0 0 8px var(--inputFocusBoxShadowColor); } } .hasError { - border-color: $inputErrorBorderColor; - box-shadow: inset 0 1px 1px $inputBoxShadowColor, 0 0 8px $inputErrorBoxShadowColor; + border-color: var(--inputErrorBorderColor); + box-shadow: inset 0 1px 1px var(--inputBoxShadowColor), 0 0 8px var(--inputErrorBoxShadowColor); } .hasWarning { - border-color: $inputWarningBorderColor; - box-shadow: inset 0 1px 1px $inputBoxShadowColor, 0 0 8px $inputWarningBoxShadowColor; + border-color: var(--inputWarningBorderColor); + box-shadow: inset 0 1px 1px var(--inputBoxShadowColor), 0 0 8px var(--inputWarningBoxShadowColor); } .hasButton { diff --git a/frontend/src/Components/Form/KeyValueListInput.css b/frontend/src/Components/Form/KeyValueListInput.css index 8bf23610b..d86e6a512 100644 --- a/frontend/src/Components/Form/KeyValueListInput.css +++ b/frontend/src/Components/Form/KeyValueListInput.css @@ -7,8 +7,8 @@ &.isFocused { outline: 0; - border-color: $inputFocusBorderColor; - box-shadow: inset 0 1px 1px $inputBoxShadowColor, 0 0 8px $inputFocusBoxShadowColor; + border-color: var(--inputFocusBorderColor); + box-shadow: inset 0 1px 1px var(--inputBoxShadowColor), 0 0 8px var(--inputFocusBoxShadowColor); } } diff --git a/frontend/src/Components/Form/KeyValueListInputItem.css b/frontend/src/Components/Form/KeyValueListInputItem.css index e136823c2..88c5847ee 100644 --- a/frontend/src/Components/Form/KeyValueListInputItem.css +++ b/frontend/src/Components/Form/KeyValueListInputItem.css @@ -1,7 +1,7 @@ .itemContainer { display: flex; margin-bottom: 3px; - border-bottom: 1px solid $inputBorderColor; + border-bottom: 1px solid var(--inputBorderColor); &:last-child { margin-bottom: 0; diff --git a/frontend/src/Components/Form/RootFolderSelectInputOption.css b/frontend/src/Components/Form/RootFolderSelectInputOption.css index d8b44fcad..f7a2759fd 100644 --- a/frontend/src/Components/Form/RootFolderSelectInputOption.css +++ b/frontend/src/Components/Form/RootFolderSelectInputOption.css @@ -15,6 +15,6 @@ .freeSpace { margin-left: 15px; - color: $darkGray; + color: var(--darkGray); font-size: $smallFontSize; } diff --git a/frontend/src/Components/Form/RootFolderSelectInputSelectedValue.css b/frontend/src/Components/Form/RootFolderSelectInputSelectedValue.css index 6b0cf9e4f..86107a624 100644 --- a/frontend/src/Components/Form/RootFolderSelectInputSelectedValue.css +++ b/frontend/src/Components/Form/RootFolderSelectInputSelectedValue.css @@ -16,7 +16,7 @@ .freeSpace { flex: 0 0 auto; margin-left: 15px; - color: $gray; + color: var(--gray); text-align: right; font-size: $smallFontSize; } diff --git a/frontend/src/Components/Form/TagInput.css b/frontend/src/Components/Form/TagInput.css index 9328c762a..eeddab5b4 100644 --- a/frontend/src/Components/Form/TagInput.css +++ b/frontend/src/Components/Form/TagInput.css @@ -7,8 +7,8 @@ &.isFocused { outline: 0; - border-color: $inputFocusBorderColor; - box-shadow: inset 0 1px 1px $inputBoxShadowColor, 0 0 8px $inputFocusBoxShadowColor; + border-color: var(--inputFocusBorderColor); + box-shadow: inset 0 1px 1px var(--inputBoxShadowColor), 0 0 8px var(--inputFocusBoxShadowColor); } } @@ -28,4 +28,6 @@ width: 0%; height: 31px; border: none; + background-color: var(--inputBackground); + color: var(--textColor); } diff --git a/frontend/src/Components/Form/TextInput.css b/frontend/src/Components/Form/TextInput.css index 80503704d..a3d9692c6 100644 --- a/frontend/src/Components/Form/TextInput.css +++ b/frontend/src/Components/Form/TextInput.css @@ -3,7 +3,7 @@ } .readOnly { - background-color: #eee; + background-color: var(--inputReadOnlyBackgroundColor); } .hasError { diff --git a/frontend/src/Components/Form/UMaskInput.css b/frontend/src/Components/Form/UMaskInput.css index 695af6384..91486687e 100644 --- a/frontend/src/Components/Form/UMaskInput.css +++ b/frontend/src/Components/Form/UMaskInput.css @@ -49,5 +49,5 @@ } .readOnly { - background-color: #eee; + background-color: var(--inputReadOnlyBackgroundColor); } diff --git a/frontend/src/Components/HeartRating.css b/frontend/src/Components/HeartRating.css index 705adfcae..030605dbd 100644 --- a/frontend/src/Components/HeartRating.css +++ b/frontend/src/Components/HeartRating.css @@ -1,4 +1,4 @@ .heart { margin-right: 5px; - color: $themeRed; + color: var(--themeRed); } diff --git a/frontend/src/Components/Icon.css b/frontend/src/Components/Icon.css index db95e019b..69ffc40f5 100644 --- a/frontend/src/Components/Icon.css +++ b/frontend/src/Components/Icon.css @@ -1,5 +1,5 @@ .danger { - color: $dangerColor; + color: var(--dangerColor); } .default { @@ -7,25 +7,33 @@ } .disabled { - color: $disabledColor; + color: var(--disabledColor); } .info { - color: $infoColor; + color: var(--infoColor); + + &:global(.darken) { + color: color(var(--infoColor) shade(30%)); + } } .pink { - color: $pink; + color: var(--pink); + + &:global(.darken) { + color: color(var(--pink) shade(30%)); + } } .success { - color: $successColor; + color: var(--successColor); } .warning { - color: $warningColor; + color: var(--warningColor); } .purple { - color: $purple; + color: var(--purple); } diff --git a/frontend/src/Components/Label.css b/frontend/src/Components/Label.css index df17427d9..f3ff83993 100644 --- a/frontend/src/Components/Label.css +++ b/frontend/src/Components/Label.css @@ -3,7 +3,7 @@ margin: 2px; border: 1px solid; border-radius: 2px; - color: $white; + color: var(--white); text-align: center; white-space: nowrap; line-height: 1; @@ -13,76 +13,78 @@ /** Kinds **/ .danger { - border-color: $dangerColor; - background-color: $dangerColor; + border-color: var(--dangerColor); + background-color: var(--dangerColor); &.outline { - color: $dangerColor; + color: var(--dangerColor); } } .default { - border-color: $themeLightColor; - background-color: $themeLightColor; + border-color: var(--themeLightColor); + background-color: var(--themeLightColor); &.outline { - color: $themeLightColor; + color: var(--themeLightColor); } } .disabled { - border-color: $disabledColor; - background-color: $disabledColor; + border-color: var(--disabledColor); + background-color: var(--disabledColor); &.outline { - color: $disabledColor; + color: var(--white); } } .info { - border-color: $infoColor; - background-color: $infoColor; + border-color: var(--infoColor); + background-color: var(--infoColor); + color: var(--infoTextColor); &.outline { - color: $infoColor; + color: var(--infoColor); } } .inverse { - border-color: $lightGray; - background-color: $lightGray; - color: $defaultColor; + border-color: var(--inverseLabelColor); + background-color: var(--inverseLabelColor); + color: var(--inverseLabelTextColor); &.outline { - background-color: $defaultColor !important; - color: $lightGray; + background-color: var(--inverseLabelTextColor) !important; + color: var(--inverseLabelColor); } } .primary { - border-color: $primaryColor; - background-color: $primaryColor; + border-color: var(--primaryColor); + background-color: var(--primaryColor); &.outline { - color: $primaryColor; + color: var(--primaryColor); } } .success { - border-color: $successColor; - background-color: $successColor; + border-color: var(--successColor); + background-color: var(--successColor); + color: #eee; &.outline { - color: $successColor; + color: var(--successColor); } } .warning { - border-color: $warningColor; - background-color: $warningColor; + border-color: var(--warningColor); + background-color: var(--warningColor); &.outline { - color: $warningColor; + color: var(--warningColor); } } @@ -107,5 +109,5 @@ /** Outline **/ .outline { - background-color: $white; + background-color: var(--disabledLabelColor); } diff --git a/frontend/src/Components/Link/Button.css b/frontend/src/Components/Link/Button.css index d5b7e8200..0855b2de6 100644 --- a/frontend/src/Components/Link/Button.css +++ b/frontend/src/Components/Link/Button.css @@ -19,62 +19,62 @@ } .danger { - border-color: $dangerBorderColor; - background-color: $dangerBackgroundColor; - color: $white; + border-color: var(--dangerBorderColor); + background-color: var(--dangerBackgroundColor); + color: var(--white); &:hover { - border-color: $dangerHoverBorderColor; - background-color: $dangerHoverBackgroundColor; - color: $white; + border-color: var(--dangerHoverBorderColor); + background-color: var(--dangerHoverBackgroundColor); + color: var(--white); } } .default { - border-color: $defaultBorderColor; - background-color: $defaultBackgroundColor; - color: $defaultColor; + border-color: var(--defaultBorderColor); + background-color: var(--defaultBackgroundColor); + color: var(--defaultColor); &:hover { - border-color: $defaultHoverBorderColor; - background-color: $defaultHoverBackgroundColor; - color: $defaultColor; + border-color: var(--defaultHoverBorderColor); + background-color: var(--defaultHoverBackgroundColor); + color: var(--defaultColor); } } .primary { - border-color: $primaryBorderColor; - background-color: $primaryBackgroundColor; - color: $white; + border-color: var(--primaryBorderColor); + background-color: var(--primaryBackgroundColor); + color: var(--white); &:hover { - border-color: $primaryHoverBorderColor; - background-color: $primaryHoverBackgroundColor; - color: $white; + border-color: var(--primaryHoverBorderColor); + background-color: var(--primaryHoverBackgroundColor); + color: var(--white); } } .success { - border-color: $successBorderColor; - background-color: $successBackgroundColor; - color: $white; + border-color: var(--successBorderColor); + background-color: var(--successBackgroundColor); + color: var(--white); &:hover { - border-color: $successHoverBorderColor; - background-color: $successHoverBackgroundColor; - color: $white; + border-color: var(--successHoverBorderColor); + background-color: var(--successHoverBackgroundColor); + color: var(--white); } } .warning { - border-color: $warningBorderColor; - background-color: $warningBackgroundColor; - color: $white; + border-color: var(--warningBorderColor); + background-color: var(--warningBackgroundColor); + color: var(--white); &:hover { - border-color: $warningHoverBorderColor; - background-color: $warningHoverBackgroundColor; - color: $white; + border-color: var(--warningHoverBorderColor); + background-color: var(--warningHoverBackgroundColor); + color: var(--white); } } diff --git a/frontend/src/Components/Link/IconButton.css b/frontend/src/Components/Link/IconButton.css index 2061243ee..b697e3bd4 100644 --- a/frontend/src/Components/Link/IconButton.css +++ b/frontend/src/Components/Link/IconButton.css @@ -12,10 +12,10 @@ &:hover { border: none; background-color: inherit; - color: $iconButtonHoverColor; + color: var(--iconButtonHoverColor); } &.isDisabled { - color: $iconButtonDisabledColor; + color: var(--iconButtonDisabledColor); } } diff --git a/frontend/src/Components/Link/Link.css b/frontend/src/Components/Link/Link.css index ff0ed8d0c..47b6fb852 100644 --- a/frontend/src/Components/Link/Link.css +++ b/frontend/src/Components/Link/Link.css @@ -15,10 +15,10 @@ } .to { - color: $linkColor; + color: var(--linkColor); &:hover { - color: $linkHoverColor; + color: var(--linkHoverColor); text-decoration: underline; } } diff --git a/frontend/src/Components/Loading/LoadingIndicator.css b/frontend/src/Components/Loading/LoadingIndicator.css index fd224b1d6..8732d786b 100644 --- a/frontend/src/Components/Loading/LoadingIndicator.css +++ b/frontend/src/Components/Loading/LoadingIndicator.css @@ -26,7 +26,7 @@ .ripple { position: absolute; - border: 2px solid #3a3f51; + border: 2px solid var(--themeDarkColor); border-radius: 100%; animation: rippleContainer 1.25s 0s infinite cubic-bezier(0.21, 0.53, 0.56, 0.8); animation-fill-mode: both; diff --git a/frontend/src/Components/Menu/MenuButton.css b/frontend/src/Components/Menu/MenuButton.css index 38812cfb7..ef7a8a5ff 100644 --- a/frontend/src/Components/Menu/MenuButton.css +++ b/frontend/src/Components/Menu/MenuButton.css @@ -10,12 +10,12 @@ } &:hover { - color: $toobarButtonHoverColor; + color: var(--toobarButtonHoverColor); } } .isDisabled { - color: $disabledColor; + color: var(--disabledColor); pointer-events: none; } diff --git a/frontend/src/Components/Menu/MenuContent.css b/frontend/src/Components/Menu/MenuContent.css index b9327fdd7..d6c990131 100644 --- a/frontend/src/Components/Menu/MenuContent.css +++ b/frontend/src/Components/Menu/MenuContent.css @@ -2,7 +2,7 @@ z-index: $popperZIndex; display: flex; flex-direction: column; - background-color: $toolbarMenuItemBackgroundColor; + background-color: var(--toolbarMenuItemBackgroundColor); line-height: 20px; } diff --git a/frontend/src/Components/Menu/MenuItem.css b/frontend/src/Components/Menu/MenuItem.css index 2eb2817af..a14a06014 100644 --- a/frontend/src/Components/Menu/MenuItem.css +++ b/frontend/src/Components/Menu/MenuItem.css @@ -5,19 +5,19 @@ padding: 10px 20px; min-width: 150px; max-width: 250px; - background-color: $toolbarMenuItemBackgroundColor; - color: $menuItemColor; + background-color: var(--toolbarMenuItemBackgroundColor); + color: var(--menuItemColor); line-height: 20px; &:hover, &:focus { - background-color: $toolbarMenuItemHoverBackgroundColor; - color: $menuItemHoverColor; + background-color: var(--toolbarMenuItemHoverBackgroundColor); + color: var(--menuItemHoverColor); text-decoration: none; } } .isDisabled { - color: $disabledColor; + color: var(--disabledColor); pointer-events: none; } diff --git a/frontend/src/Components/Menu/MenuItemSeparator.css b/frontend/src/Components/Menu/MenuItemSeparator.css index e48e7f16f..199b15b56 100644 --- a/frontend/src/Components/Menu/MenuItemSeparator.css +++ b/frontend/src/Components/Menu/MenuItemSeparator.css @@ -2,5 +2,5 @@ overflow: hidden; min-height: 1px; height: 1px; - background-color: $themeDarkColor; + background-color: var(--themeDarkColor); } diff --git a/frontend/src/Components/Modal/Modal.css b/frontend/src/Components/Modal/Modal.css index d7269ea46..33f849945 100644 --- a/frontend/src/Components/Modal/Modal.css +++ b/frontend/src/Components/Modal/Modal.css @@ -12,7 +12,7 @@ justify-content: center; width: 100%; height: 100%; - background-color: $modalBackdropBackgroundColor; + background-color: var(--modalBackdropBackgroundColor); opacity: 1; } diff --git a/frontend/src/Components/Modal/ModalContent.css b/frontend/src/Components/Modal/ModalContent.css index afd798dfa..d7a1212ec 100644 --- a/frontend/src/Components/Modal/ModalContent.css +++ b/frontend/src/Components/Modal/ModalContent.css @@ -4,7 +4,7 @@ flex-direction: column; flex-grow: 1; width: 100%; - background-color: $modalBackgroundColor; + background-color: var(--modalBackgroundColor); } .closeButton { @@ -18,6 +18,6 @@ line-height: 60px; &:hover { - color: $modalCloseButtonHoverColor; + color: var(--modalCloseButtonHoverColor); } } diff --git a/frontend/src/Components/Modal/ModalFooter.css b/frontend/src/Components/Modal/ModalFooter.css index 3b817d2bf..9ce992b2e 100644 --- a/frontend/src/Components/Modal/ModalFooter.css +++ b/frontend/src/Components/Modal/ModalFooter.css @@ -4,7 +4,7 @@ justify-content: flex-end; flex-shrink: 0; padding: 15px 30px; - border-top: 1px solid $borderColor; + border-top: 1px solid var(--borderColor); a, button { diff --git a/frontend/src/Components/Modal/ModalHeader.css b/frontend/src/Components/Modal/ModalHeader.css index eab77a9f8..cd1e54d84 100644 --- a/frontend/src/Components/Modal/ModalHeader.css +++ b/frontend/src/Components/Modal/ModalHeader.css @@ -3,6 +3,6 @@ flex-shrink: 0; padding: 15px 50px 15px 30px; - border-bottom: 1px solid $borderColor; + border-bottom: 1px solid var(--borderColor); font-size: 18px; } diff --git a/frontend/src/Components/MonitorToggleButton.css b/frontend/src/Components/MonitorToggleButton.css index 09b64f1ab..59376129d 100644 --- a/frontend/src/Components/MonitorToggleButton.css +++ b/frontend/src/Components/MonitorToggleButton.css @@ -6,6 +6,6 @@ } .isDisabled { - color: $disabledColor; + color: var(--disabledColor); cursor: not-allowed; } diff --git a/frontend/src/Components/Page/Header/ArtistSearchInput.css b/frontend/src/Components/Page/Header/ArtistSearchInput.css index cbec874bd..95a796c80 100644 --- a/frontend/src/Components/Page/Header/ArtistSearchInput.css +++ b/frontend/src/Components/Page/Header/ArtistSearchInput.css @@ -12,22 +12,22 @@ .ripple { composes: ripple from '~Components/Loading/LoadingIndicator.css'; - border: 2px solid $toolbarColor; + border: 2px solid var(--toolbarColor); } .input { margin-left: 8px; width: 200px; border: none; - border-bottom: solid 1px $white; + border-bottom: solid 1px var(--white); border-radius: 0; background-color: transparent; box-shadow: none; - color: $white; + color: var(--white); transition: border 0.3s ease-out; &::placeholder { - color: $white; + color: var(--white); transition: color 0.3s ease-out; } @@ -60,13 +60,13 @@ overflow-y: auto; min-width: 100%; max-height: 230px; - border: 1px solid $themeDarkColor; + border: 1px solid var(--themeDarkColor); border-radius: 4px; border-top-left-radius: 0; border-top-right-radius: 0; - background-color: $themeDarkColor; - box-shadow: inset 0 1px 1px $inputBoxShadowColor; - color: $menuItemColor; + background-color: var(--themeDarkColor); + box-shadow: inset 0 1px 1px var(--inputBoxShadowColor); + color: var(--menuItemColor); } } @@ -82,12 +82,12 @@ } .highlighted { - background-color: $primaryHoverBackgroundColor; + background-color: var(--themeLightColor); } .sectionTitle { padding: 5px 8px; - color: $disabledColor; + color: var(--disabledColor); } .addNewArtistSuggestion { diff --git a/frontend/src/Components/Page/Header/ArtistSearchResult.css b/frontend/src/Components/Page/Header/ArtistSearchResult.css index 4d21d4640..6f850d103 100644 --- a/frontend/src/Components/Page/Header/ArtistSearchResult.css +++ b/frontend/src/Components/Page/Header/ArtistSearchResult.css @@ -21,7 +21,7 @@ .alternateTitle { composes: title; - color: $disabledColor; + color: var(--disabledColor); font-size: $smallFontSize; } diff --git a/frontend/src/Components/Page/Header/KeyboardShortcutsModalContent.css b/frontend/src/Components/Page/Header/KeyboardShortcutsModalContent.css index 4425e0e0d..e8a2b0ce2 100644 --- a/frontend/src/Components/Page/Header/KeyboardShortcutsModalContent.css +++ b/frontend/src/Components/Page/Header/KeyboardShortcutsModalContent.css @@ -8,8 +8,8 @@ .key { padding: 2px 4px; border-radius: 3px; - background-color: $defaultColor; + background-color: var(--defaultColor); box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); - color: $white; + color: var(--white); font-size: 16px; } diff --git a/frontend/src/Components/Page/Header/PageHeader.css b/frontend/src/Components/Page/Header/PageHeader.css index c4dc3f844..b94bd64b8 100644 --- a/frontend/src/Components/Page/Header/PageHeader.css +++ b/frontend/src/Components/Page/Header/PageHeader.css @@ -4,8 +4,8 @@ align-items: center; flex: 0 0 auto; height: $headerHeight; - background-color: $themeAlternateBlue; - color: $white; + background-color: var(--pageHeaderBackgroundColor); + color: var(--white); } .logoContainer { @@ -41,7 +41,7 @@ composes: link from '~Components/Link/Link.css'; width: 30px; - color: $themeRed; + color: var(--themeRed); text-align: center; line-height: 60px; diff --git a/frontend/src/Components/Page/Header/PageHeaderActionsMenu.css b/frontend/src/Components/Page/Header/PageHeaderActionsMenu.css index 0fee43911..5d41fc62e 100644 --- a/frontend/src/Components/Page/Header/PageHeaderActionsMenu.css +++ b/frontend/src/Components/Page/Header/PageHeaderActionsMenu.css @@ -5,7 +5,7 @@ text-align: center; &:hover { - color: $themeDarkColor; + color: var(--themeDarkColor); } } diff --git a/frontend/src/Components/Page/LoadingPage.css b/frontend/src/Components/Page/LoadingPage.css index 31f293f62..93ad82d9a 100644 --- a/frontend/src/Components/Page/LoadingPage.css +++ b/frontend/src/Components/Page/LoadingPage.css @@ -1,5 +1,7 @@ .page { composes: page from '~./Page.css'; + + background-color: var(--pageBackground); } .logoFull { diff --git a/frontend/src/Components/Page/PageContentFooter.css b/frontend/src/Components/Page/PageContentFooter.css index 0f323bc9a..4709af871 100644 --- a/frontend/src/Components/Page/PageContentFooter.css +++ b/frontend/src/Components/Page/PageContentFooter.css @@ -2,7 +2,7 @@ display: flex; flex: 0 0 auto; padding: 20px; - background-color: #f1f1f1; + background-color: var(--pageFooterBackground); } @media only screen and (max-width: $breakpointSmall) { diff --git a/frontend/src/Components/Page/PageJumpBarItem.css b/frontend/src/Components/Page/PageJumpBarItem.css index f1a0c3699..235562a2e 100644 --- a/frontend/src/Components/Page/PageJumpBarItem.css +++ b/frontend/src/Components/Page/PageJumpBarItem.css @@ -1,6 +1,6 @@ .jumpBarItem { flex: 1 1 $jumpBarItemHeight; - border-bottom: 1px solid $borderColor; + border-bottom: 1px solid var(--borderColor); text-align: center; font-weight: bold; diff --git a/frontend/src/Components/Page/Sidebar/Messages/Message.css b/frontend/src/Components/Page/Sidebar/Messages/Message.css index 7d53adb69..d1d94ce08 100644 --- a/frontend/src/Components/Page/Sidebar/Messages/Message.css +++ b/frontend/src/Components/Page/Sidebar/Messages/Message.css @@ -1,6 +1,6 @@ .message { display: flex; - border-left: 3px solid $infoColor; + border-left: 3px solid var(--infoColor); } .iconContainer, @@ -9,7 +9,7 @@ justify-content: center; flex-direction: column; padding: 2px 0; - color: $sidebarColor; + color: var(--sidebarColor); } .iconContainer { @@ -26,17 +26,17 @@ /* Types */ .error { - border-left-color: $dangerColor; + border-left-color: var(--dangerColor); } .info { - border-left-color: $infoColor; + border-left-color: var(--infoColor); } .success { - border-left-color: $successColor; + border-left-color: var(--successColor); } .warning { - border-left-color: $warningColor; + border-left-color: var(--warningColor); } diff --git a/frontend/src/Components/Page/Sidebar/PageSidebar.css b/frontend/src/Components/Page/Sidebar/PageSidebar.css index 3f2abeee7..ee44c0407 100644 --- a/frontend/src/Components/Page/Sidebar/PageSidebar.css +++ b/frontend/src/Components/Page/Sidebar/PageSidebar.css @@ -2,7 +2,7 @@ flex: 0 0 $sidebarWidth; overflow: hidden; width: $sidebarWidth; - background-color: $sidebarBackgroundColor; + background-color: var(--sidebarBackgroundColor); transition: transform 300ms ease-in-out; transform: translateX(0); } @@ -11,8 +11,8 @@ display: flex; flex-direction: column; overflow: hidden; - background-color: $sidebarBackgroundColor; - color: $white; + background-color: var(--sidebarBackgroundColor); + color: var(--white); } @media only screen and (max-width: $breakpointSmall) { diff --git a/frontend/src/Components/Page/Sidebar/PageSidebarItem.css b/frontend/src/Components/Page/Sidebar/PageSidebarItem.css index dac40927f..7c9d12073 100644 --- a/frontend/src/Components/Page/Sidebar/PageSidebarItem.css +++ b/frontend/src/Components/Page/Sidebar/PageSidebarItem.css @@ -1,21 +1,21 @@ .item { border-left: 3px solid transparent; - color: $sidebarColor; + color: var(--sidebarColor); transition: border-left 0.3s ease-in-out; } .isActiveItem { - border-left: 3px solid $themeBlue; + border-left: 3px solid var(--themeAlternateBlue); } .link { display: block; padding: 12px 24px; - color: $sidebarColor; + color: var(--sidebarColor); &:hover, &:focus { - color: $themeBlue; + color: var(--themeBlue); text-decoration: none; } } @@ -27,11 +27,11 @@ } .isActiveLink { - color: $themeBlue; + color: var(--themeAlternateBlue); } .isActiveParentLink { - background-color: $sidebarActiveBackgroundColor; + background-color: var(--sidebarActiveBackgroundColor); } .iconContainer { diff --git a/frontend/src/Components/Page/Toolbar/PageToolbar.css b/frontend/src/Components/Page/Toolbar/PageToolbar.css index e040bc884..d7fb0d5d8 100644 --- a/frontend/src/Components/Page/Toolbar/PageToolbar.css +++ b/frontend/src/Components/Page/Toolbar/PageToolbar.css @@ -4,8 +4,8 @@ flex: 0 0 auto; padding: 0 20px; height: $toolbarHeight; - background-color: $toolbarBackgroundColor; - color: $toolbarColor; + background-color: var(--toolbarBackgroundColor); + color: var(--toolbarColor); line-height: 60px; } diff --git a/frontend/src/Components/Page/Toolbar/PageToolbarButton.css b/frontend/src/Components/Page/Toolbar/PageToolbarButton.css index e729ed000..0b6918296 100644 --- a/frontend/src/Components/Page/Toolbar/PageToolbarButton.css +++ b/frontend/src/Components/Page/Toolbar/PageToolbarButton.css @@ -6,16 +6,16 @@ text-align: center; &:hover { - color: $toobarButtonHoverColor; + color: var(--toobarButtonHoverColor); } &.isDisabled { - color: $disabledColor; + color: var(--disabledColor); } } .isDisabled { - color: $disabledColor; + color: var(--disabledColor); } .labelContainer { @@ -27,7 +27,7 @@ .label { padding: 0 3px; - color: $toolbarLabelColor; + color: var(--toolbarLabelColor); font-size: $extraSmallFontSize; line-height: calc($extraSmallFontSize + 1px); } diff --git a/frontend/src/Components/ProgressBar.css b/frontend/src/Components/ProgressBar.css index 777187eec..33c047bc6 100644 --- a/frontend/src/Components/ProgressBar.css +++ b/frontend/src/Components/ProgressBar.css @@ -3,7 +3,7 @@ overflow: hidden; width: 100%; border-radius: 4px; - background-color: #f5f5f5; + background-color: var(--progressBarBackgroundColor); box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); } @@ -14,13 +14,13 @@ width: 0; height: 100%; box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); - color: $white; + color: var(--white); transition: width 0.6s ease; } .frontTextContainer { z-index: 1; - color: $white; + color: var(--white); } .backTextContainer, @@ -42,35 +42,35 @@ } .primary { - background-color: $primaryColor; + background-color: var(--primaryColor); } .danger { - background-color: $dangerColor; + background-color: var(--dangerColor); &:global(.colorImpaired) { - background: repeating-linear-gradient(90deg, color($dangerColor shade(5%)), color($dangerColor shade(5%)) 5px, color($dangerColor shade(15%)) 5px, color($dangerColor shade(15%)) 10px); + background: repeating-linear-gradient(90deg, color(#f05050 shade(5%)), color(#f05050 shade(5%)) 5px, color(#f05050 shade(15%)) 5px, color(#f05050 shade(15%)) 10px); } } .success { - background-color: $successColor; + background-color: var(--successColor); } .purple { - background-color: $purple; + background-color: var(--purple); } .warning { - background-color: $warningColor; + background-color: var(--warningColor); &:global(.colorImpaired) { - background: repeating-linear-gradient(45deg, $warningColor, $warningColor 5px, color($warningColor tint(15%)) 5px, color($warningColor tint(15%)) 10px); + background: repeating-linear-gradient(45deg, #ffa500, #ffa500 5px, color(#ffa500 tint(15%)) 5px, color(#ffa500 tint(15%)) 10px); } } .info { - background-color: $infoColor; + background-color: var(--infoColor); } .small { diff --git a/frontend/src/Components/Scroller/OverlayScroller.css b/frontend/src/Components/Scroller/OverlayScroller.css index 0e8a2ffd7..c1836d365 100644 --- a/frontend/src/Components/Scroller/OverlayScroller.css +++ b/frontend/src/Components/Scroller/OverlayScroller.css @@ -10,10 +10,10 @@ min-height: 100px; border: 1px solid transparent; border-radius: 5px; - background-color: $scrollbarBackgroundColor; + background-color: var(--scrollbarBackgroundColor); background-clip: padding-box; &:hover { - background-color: $scrollbarHoverBackgroundColor; + background-color: var(--scrollbarHoverBackgroundColor); } } diff --git a/frontend/src/Components/Table/Cells/TableRowCell.css b/frontend/src/Components/Table/Cells/TableRowCell.css index 1c3e6fc5a..47ce0d22e 100644 --- a/frontend/src/Components/Table/Cells/TableRowCell.css +++ b/frontend/src/Components/Table/Cells/TableRowCell.css @@ -1,6 +1,6 @@ .cell { padding: 8px; - border-top: 1px solid #eee; + border-top: 1px solid var(--borderColor); line-height: 1.52857143; } diff --git a/frontend/src/Components/Table/TableOptions/TableOptionsColumn.css b/frontend/src/Components/Table/TableOptions/TableOptionsColumn.css index 204773c3d..ef3d9b062 100644 --- a/frontend/src/Components/Table/TableOptions/TableOptionsColumn.css +++ b/frontend/src/Components/Table/TableOptions/TableOptionsColumn.css @@ -4,7 +4,7 @@ width: 100%; border: 1px solid #aaa; border-radius: 4px; - background: #fafafa; + background: var(--inputBackgroundColor); } .checkContainer { diff --git a/frontend/src/Components/Table/TablePager.css b/frontend/src/Components/Table/TablePager.css index 19f5a8f6b..d73a0d0c0 100644 --- a/frontend/src/Components/Table/TablePager.css +++ b/frontend/src/Components/Table/TablePager.css @@ -46,11 +46,11 @@ } .records { - color: $disabledColor; + color: var(--disabledColor); } .disabledPageButton { - color: $disabledColor; + color: var(--disabledColor); } .pageSelect { diff --git a/frontend/src/Components/Table/TableRow.css b/frontend/src/Components/Table/TableRow.css index dcc6ad8cf..df297a5fe 100644 --- a/frontend/src/Components/Table/TableRow.css +++ b/frontend/src/Components/Table/TableRow.css @@ -2,6 +2,6 @@ transition: background-color 500ms; &:hover { - background-color: $tableRowHoverBackgroundColor; + background-color: var(--tableRowHoverBackgroundColor); } } diff --git a/frontend/src/Components/Table/VirtualTableRow.css b/frontend/src/Components/Table/VirtualTableRow.css index f4c825b64..fcae53bf6 100644 --- a/frontend/src/Components/Table/VirtualTableRow.css +++ b/frontend/src/Components/Table/VirtualTableRow.css @@ -3,7 +3,7 @@ transition: background-color 500ms; &:hover { - background-color: #fafbfc; + background-color: var(--tableRowHoverBackgroundColor); } } diff --git a/frontend/src/Components/Tooltip/Popover.css b/frontend/src/Components/Tooltip/Popover.css index 7b0592844..7d9e9da5d 100644 --- a/frontend/src/Components/Tooltip/Popover.css +++ b/frontend/src/Components/Tooltip/Popover.css @@ -1,13 +1,14 @@ .title { padding: 10px 20px; - border-bottom: 1px solid $popoverTitleBorderColor; - background-color: $popoverTitleBackgroundColor; + border-bottom: 1px solid var(--popoverTitleBorderColor); + background-color: var(--popoverTitleBackgroundColor); font-size: 16px; } .body { overflow: auto; padding: 10px; + background-color: var(--popoverBodyBackgroundColor); } .tooltipBody { diff --git a/frontend/src/Components/Tooltip/Tooltip.css b/frontend/src/Components/Tooltip/Tooltip.css index c5ddc7272..8ab533d07 100644 --- a/frontend/src/Components/Tooltip/Tooltip.css +++ b/frontend/src/Components/Tooltip/Tooltip.css @@ -7,13 +7,14 @@ position: relative; &.default { - background-color: $white; - box-shadow: 0 5px 10px $popoverShadowColor; + background-color: var(--popoverBodyBackgroundColor); + box-shadow: 0 5px 10px var(--popoverShadowColor); } &.inverse { - background-color: $themeDarkColor; - box-shadow: 0 5px 10px $popoverShadowInverseColor; + background-color: var(--themeDarkColor); + box-shadow: 0 5px 10px var(--popoverShadowInverseColor); + color: var(--white); } } @@ -49,20 +50,20 @@ content: ' '; &.default { - border-top-color: $popoverArrowBorderColor; + border-top-color: var(--popoverArrowBorderColor); } &.inverse { - border-top-color: $popoverArrowBorderInverseColor; + border-top-color: var(--popoverArrowBorderInverseColor); } } &.default { - border-top-color: $popoverArrowBorderColor; + border-top-color: var(--popoverArrowBorderColor); } &.inverse { - border-top-color: $popoverArrowBorderInverseColor; + border-top-color: var(--popoverArrowBorderInverseColor); } } @@ -78,20 +79,20 @@ content: ' '; &.default { - border-right-color: $popoverArrowBorderColor; + border-right-color: var(--popoverArrowBorderColor); } &.inverse { - border-right-color: $popoverArrowBorderInverseColor; + border-right-color: var(--popoverArrowBorderInverseColor); } } &.default { - border-right-color: $popoverArrowBorderColor; + border-right-color: var(--popoverArrowBorderColor); } &.inverse { - border-right-color: $popoverArrowBorderInverseColor; + border-right-color: var(--popoverArrowBorderInverseColor); } } @@ -107,20 +108,20 @@ content: ' '; &.default { - border-bottom-color: $popoverArrowBorderColor; + border-bottom-color: var(--popoverArrowBorderColor); } &.inverse { - border-bottom-color: $popoverArrowBorderInverseColor; + border-bottom-color: var(--popoverArrowBorderInverseColor); } } &.default { - border-bottom-color: $popoverArrowBorderColor; + border-bottom-color: var(--popoverArrowBorderColor); } &.inverse { - border-bottom-color: $popoverArrowBorderInverseColor; + border-bottom-color: var(--popoverArrowBorderInverseColor); } } @@ -136,20 +137,20 @@ content: ' '; &.default { - border-left-color: $popoverArrowBorderColor; + border-left-color: var(--popoverArrowBorderColor); } &.inverse { - border-left-color: $popoverArrowBorderInverseColor; + border-left-color: var(--popoverArrowBorderInverseColor); } } &.default { - border-left-color: $popoverArrowBorderColor; + border-left-color: var(--popoverArrowBorderColor); } &.inverse { - border-left-color: $popoverArrowBorderInverseColor; + border-left-color: var(--popoverArrowBorderInverseColor); } } diff --git a/frontend/src/InteractiveImport/Artist/SelectArtistRow.css b/frontend/src/InteractiveImport/Artist/SelectArtistRow.css index 376c3fe84..e0df66697 100644 --- a/frontend/src/InteractiveImport/Artist/SelectArtistRow.css +++ b/frontend/src/InteractiveImport/Artist/SelectArtistRow.css @@ -1,4 +1,4 @@ .artist { padding: 8px; - border-bottom: 1px solid $borderColor; + border-bottom: 1px solid var(--borderColor); } diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.css b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.css index d50f3a261..573b16667 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.css +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.css @@ -35,7 +35,7 @@ } .errorMessage { - color: $dangerColor; + color: var(--dangerColor); } @media only screen and (max-width: $breakpointSmall) { diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.css b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.css index 2a23e6d4d..d5286412c 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.css +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.css @@ -25,5 +25,5 @@ .additionalFile { composes: row from '~Components/Table/TableRow.css'; - color: $disabledColor; + color: var(--disabledColor); } diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportRowCellPlaceholder.css b/frontend/src/InteractiveImport/Interactive/InteractiveImportRowCellPlaceholder.css index 941988144..bf4351181 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportRowCellPlaceholder.css +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportRowCellPlaceholder.css @@ -3,5 +3,5 @@ margin: -8px 0; width: 100%; height: 25px; - border: 2px dashed $dangerColor; + border: 2px dashed var(--dangerColor); } diff --git a/frontend/src/Organize/OrganizePreviewRow.css b/frontend/src/Organize/OrganizePreviewRow.css index 1b3c8ca47..18592cf2e 100644 --- a/frontend/src/Organize/OrganizePreviewRow.css +++ b/frontend/src/Organize/OrganizePreviewRow.css @@ -2,7 +2,7 @@ display: flex; margin-bottom: 5px; padding: 5px 0; - border-bottom: 1px solid $borderColor; + border-bottom: 1px solid var(--borderColor); &:last-of-type { margin-bottom: 0; diff --git a/frontend/src/Retag/RetagPreviewRow.css b/frontend/src/Retag/RetagPreviewRow.css index e59b03f19..771f3c486 100644 --- a/frontend/src/Retag/RetagPreviewRow.css +++ b/frontend/src/Retag/RetagPreviewRow.css @@ -2,7 +2,7 @@ display: flex; margin-bottom: 5px; padding: 5px 0; - border-bottom: 1px solid $borderColor; + border-bottom: 1px solid var(--borderColor); &:last-of-type { margin-bottom: 0; diff --git a/frontend/src/Search/AddNewItem.css b/frontend/src/Search/AddNewItem.css index 7c558d6d0..d587bfbb8 100644 --- a/frontend/src/Search/AddNewItem.css +++ b/frontend/src/Search/AddNewItem.css @@ -6,12 +6,12 @@ .searchIconContainer { width: 58px; height: 46px; - border: 1px solid $inputBorderColor; + border: 1px solid var(--inputBorderColor); border-right: none; border-radius: 4px; border-top-right-radius: 0; border-bottom-right-radius: 0; - background-color: #edf1f2; + background-color: var(--searchIconContainerBackgroundColor); text-align: center; line-height: 46px; } @@ -25,7 +25,7 @@ } .clearLookupButton { - border: 1px solid $inputBorderColor; + border: 1px solid var(--inputBorderColor); border-left: none; border-top-right-radius: 4px; border-bottom-right-radius: 4px; diff --git a/frontend/src/Search/Album/AddNewAlbumModalContent.css b/frontend/src/Search/Album/AddNewAlbumModalContent.css index d1d6bb24d..445621685 100644 --- a/frontend/src/Search/Album/AddNewAlbumModalContent.css +++ b/frontend/src/Search/Album/AddNewAlbumModalContent.css @@ -25,7 +25,7 @@ .disambiguation { margin-bottom: 20px; - color: $disabledColor; + color: var(--disabledColor); font-weight: 300; font-size: 20px; } @@ -55,9 +55,9 @@ .albumType { margin-bottom: 20px; - border: 1px solid $borderColor; + border: 1px solid var(--borderColor); border-radius: 4px; - background-color: $white; + background-color: var(--white); &:last-of-type { margin-bottom: 0; diff --git a/frontend/src/Search/Album/AddNewAlbumSearchResult.css b/frontend/src/Search/Album/AddNewAlbumSearchResult.css index c57cb1549..28ad1f2d1 100644 --- a/frontend/src/Search/Album/AddNewAlbumSearchResult.css +++ b/frontend/src/Search/Album/AddNewAlbumSearchResult.css @@ -9,13 +9,15 @@ .underlay { @add-mixin cover; - background-color: $white; + background-color: var(--addSeriesBackgroundColor); transition: background 500ms; &:hover { - background-color: #eaf2ff; + background-color: var(--pageBackground); + box-shadow: 0 0 12px var(--black); color: inherit; text-decoration: none; + transition: all 200ms ease-in; } } @@ -54,7 +56,7 @@ .year { margin-left: 10px; - color: $disabledColor; + color: var(--disabledColor); } .icons { @@ -70,7 +72,7 @@ margin-top: -4px; margin-left: auto; - color: $textColor; + color: var(--textColor); } .mbLinkIcon { diff --git a/frontend/src/Search/Artist/AddNewArtistModalContent.css b/frontend/src/Search/Artist/AddNewArtistModalContent.css index dcc427693..642947851 100644 --- a/frontend/src/Search/Artist/AddNewArtistModalContent.css +++ b/frontend/src/Search/Artist/AddNewArtistModalContent.css @@ -4,7 +4,7 @@ .year { margin-left: 5px; - color: $disabledColor; + color: var(--disabledColor); } .poster { @@ -24,7 +24,7 @@ .disambiguation { margin-bottom: 20px; - color: $disabledColor; + color: var(--disabledColor); font-weight: 300; font-size: 20px; } diff --git a/frontend/src/Search/Artist/AddNewArtistSearchResult.css b/frontend/src/Search/Artist/AddNewArtistSearchResult.css index d8266c734..3ff77ddc7 100644 --- a/frontend/src/Search/Artist/AddNewArtistSearchResult.css +++ b/frontend/src/Search/Artist/AddNewArtistSearchResult.css @@ -9,7 +9,7 @@ .underlay { @add-mixin cover; - background-color: $white; + background-color: var(--white); transition: background 500ms; &:hover { @@ -54,7 +54,7 @@ .year { margin-left: 10px; - color: $disabledColor; + color: var(--disabledColor); } .icons { @@ -70,7 +70,7 @@ margin-top: -4px; margin-left: auto; - color: $textColor; + color: var(--textColor); } .mbLinkIcon { diff --git a/frontend/src/Settings/AdvancedSettingsButton.css b/frontend/src/Settings/AdvancedSettingsButton.css index 5f0d3b9f2..f08bdc017 100644 --- a/frontend/src/Settings/AdvancedSettingsButton.css +++ b/frontend/src/Settings/AdvancedSettingsButton.css @@ -19,13 +19,13 @@ } .indicatorBackground { - color: $themeDarkColor; + color: var(--themeDarkColor); } .enabled { - color: $successColor; + color: var(--successColor); } .disabled { - color: $dangerColor; + color: var(--dangerColor); } diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClients.css b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClients.css index 81b4f1510..5f650aaf9 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClients.css +++ b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClients.css @@ -6,15 +6,15 @@ .addDownloadClient { composes: downloadClient from '~./DownloadClient.css'; - background-color: $cardAlternateBackgroundColor; - color: $gray; + background-color: var(--cardAlternateBackgroundColor); + color: var(--gray); text-align: center; } .center { display: inline-block; padding: 5px 20px 0; - border: 1px solid $borderColor; + border: 1px solid var(--borderColor); border-radius: 4px; - background-color: $white; + background-color: var(--cardCenterBackgroundColor); } diff --git a/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMapping.css b/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMapping.css index 13f35bed4..4db0b47ac 100644 --- a/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMapping.css +++ b/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMapping.css @@ -3,7 +3,7 @@ align-items: stretch; margin-bottom: 10px; height: 30px; - border-bottom: 1px solid $borderColor; + border-bottom: 1px solid var(--borderColor); line-height: 30px; } diff --git a/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusion.css b/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusion.css index 4c274831c..349ebc925 100644 --- a/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusion.css +++ b/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusion.css @@ -3,7 +3,7 @@ align-items: stretch; margin-bottom: 10px; height: 30px; - border-bottom: 1px solid $borderColor; + border-bottom: 1px solid var(--borderColor); line-height: 30px; } diff --git a/frontend/src/Settings/ImportLists/ImportLists/ImportLists.css b/frontend/src/Settings/ImportLists/ImportLists/ImportLists.css index 3db4e69d6..e704fc568 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/ImportLists.css +++ b/frontend/src/Settings/ImportLists/ImportLists/ImportLists.css @@ -6,15 +6,15 @@ .addList { composes: list from '~./ImportList.css'; - background-color: $cardAlternateBackgroundColor; - color: $gray; + background-color: var(--cardAlternateBackgroundColor); + color: var(--gray); text-align: center; } .center { display: inline-block; padding: 5px 20px 0; - border: 1px solid $borderColor; + border: 1px solid var(--borderColor); border-radius: 4px; - background-color: $white; + background-color: var(--cardCenterBackgroundColor); } diff --git a/frontend/src/Settings/Indexers/Indexers/Indexers.css b/frontend/src/Settings/Indexers/Indexers/Indexers.css index bf2e72ba4..16597d0e0 100644 --- a/frontend/src/Settings/Indexers/Indexers/Indexers.css +++ b/frontend/src/Settings/Indexers/Indexers/Indexers.css @@ -6,15 +6,15 @@ .addIndexer { composes: indexer from '~./Indexer.css'; - background-color: $cardAlternateBackgroundColor; - color: $gray; + background-color: var(--cardAlternateBackgroundColor); + color: var(--gray); text-align: center; } .center { display: inline-block; padding: 5px 20px 0; - border: 1px solid $borderColor; + border: 1px solid var(--borderColor); border-radius: 4px; - background-color: $white; + background-color: var(--cardCenterBackgroundColor); } diff --git a/frontend/src/Settings/MediaManagement/Naming/NamingModal.css b/frontend/src/Settings/MediaManagement/Naming/NamingModal.css index c178d82cb..f65bed4df 100644 --- a/frontend/src/Settings/MediaManagement/Naming/NamingModal.css +++ b/frontend/src/Settings/MediaManagement/Naming/NamingModal.css @@ -16,3 +16,20 @@ margin-left: 10px; width: 200px; } + +.footNote { + display: flex; + color: var(--helpTextColor); + + .icon { + margin-top: 3px; + margin-right: 5px; + padding: 2px; + } + + code { + padding: 0 1px; + border: 1px solid var(--borderColor); + background-color: #f7f7f7; + } +} diff --git a/frontend/src/Settings/MediaManagement/Naming/NamingOption.css b/frontend/src/Settings/MediaManagement/Naming/NamingOption.css index d9f865936..b692362fb 100644 --- a/frontend/src/Settings/MediaManagement/Naming/NamingOption.css +++ b/frontend/src/Settings/MediaManagement/Naming/NamingOption.css @@ -3,7 +3,7 @@ align-items: center; flex-wrap: wrap; margin: 3px; - border: 1px solid $borderColor; + border: 1px solid var(--borderColor); &:hover { .token { @@ -27,7 +27,7 @@ .token { flex: 0 0 50%; padding: 6px 16px; - background-color: #eee; + background-color: var(--popoverTitleBackgroundColor); font-family: $monoSpaceFontFamily; } @@ -37,7 +37,12 @@ align-self: stretch; flex: 0 0 50%; padding: 6px 16px; - background-color: #ddd; + background-color: var(--popoverBodyBackgroundColor); + + .footNote { + padding: 2px; + color: #aaa; + } } .lower { diff --git a/frontend/src/Settings/MediaManagement/RootFolder/RootFolders.css b/frontend/src/Settings/MediaManagement/RootFolder/RootFolders.css index 6ecc1572c..3b3bcc17a 100644 --- a/frontend/src/Settings/MediaManagement/RootFolder/RootFolders.css +++ b/frontend/src/Settings/MediaManagement/RootFolder/RootFolders.css @@ -6,15 +6,15 @@ .addRootFolder { composes: rootFolder from '~./RootFolder.css'; - background-color: $cardAlternateBackgroundColor; - color: $gray; + background-color: var(--cardAlternateBackgroundColor); + color: var(--gray); text-align: center; } .center { display: inline-block; padding: 5px 20px 0; - border: 1px solid $borderColor; + border: 1px solid var(--borderColor); border-radius: 4px; - background-color: $white; + background-color: var(--cardCenterBackgroundColor); } diff --git a/frontend/src/Settings/Notifications/Notifications/Notifications.css b/frontend/src/Settings/Notifications/Notifications/Notifications.css index 11ea6e11f..986226ad8 100644 --- a/frontend/src/Settings/Notifications/Notifications/Notifications.css +++ b/frontend/src/Settings/Notifications/Notifications/Notifications.css @@ -6,15 +6,15 @@ .addNotification { composes: notification from '~./Notification.css'; - background-color: $cardAlternateBackgroundColor; - color: $gray; + background-color: var(--cardAlternateBackgroundColor); + color: var(--gray); text-align: center; } .center { display: inline-block; padding: 5px 20px 0; - border: 1px solid $borderColor; + border: 1px solid var(--borderColor); border-radius: 4px; - background-color: $white; + background-color: var(--cardCenterBackgroundColor); } diff --git a/frontend/src/Settings/Profiles/Delay/DelayProfile.css b/frontend/src/Settings/Profiles/Delay/DelayProfile.css index 238742efd..e2d6cd199 100644 --- a/frontend/src/Settings/Profiles/Delay/DelayProfile.css +++ b/frontend/src/Settings/Profiles/Delay/DelayProfile.css @@ -3,7 +3,7 @@ align-items: stretch; margin-bottom: 10px; height: 30px; - border-bottom: 1px solid $borderColor; + border-bottom: 1px solid var(--borderColor); line-height: 30px; } diff --git a/frontend/src/Settings/Profiles/Metadata/MetadataProfiles.css b/frontend/src/Settings/Profiles/Metadata/MetadataProfiles.css index 87ae2f44b..f5b69a80e 100644 --- a/frontend/src/Settings/Profiles/Metadata/MetadataProfiles.css +++ b/frontend/src/Settings/Profiles/Metadata/MetadataProfiles.css @@ -6,8 +6,8 @@ .addMetadataProfile { composes: metadataProfile from '~./MetadataProfile.css'; - background-color: $cardAlternateBackgroundColor; - color: $gray; + background-color: var(--cardAlternateBackgroundColor); + color: var(--gray); text-align: center; font-size: 45px; } @@ -15,7 +15,7 @@ .center { display: inline-block; padding: 5px 20px 0; - border: 1px solid $borderColor; + border: 1px solid var(--borderColor); border-radius: 4px; - background-color: $white; + background-color: var(--cardCenterBackgroundColor); } diff --git a/frontend/src/Settings/Profiles/Quality/QualityProfileItem.css b/frontend/src/Settings/Profiles/Quality/QualityProfileItem.css index bf54fedc2..c10bcd200 100644 --- a/frontend/src/Settings/Profiles/Quality/QualityProfileItem.css +++ b/frontend/src/Settings/Profiles/Quality/QualityProfileItem.css @@ -4,7 +4,7 @@ width: 100%; border: 1px solid #aaa; border-radius: 4px; - background: #fafafa; + background: var(--inputBackgroundColor); &.isInGroup { border-style: dashed; diff --git a/frontend/src/Settings/Profiles/Quality/QualityProfileItemGroup.css b/frontend/src/Settings/Profiles/Quality/QualityProfileItemGroup.css index dad463b28..772bd9a80 100644 --- a/frontend/src/Settings/Profiles/Quality/QualityProfileItemGroup.css +++ b/frontend/src/Settings/Profiles/Quality/QualityProfileItemGroup.css @@ -2,10 +2,10 @@ width: 100%; border: 1px solid #aaa; border-radius: 4px; - background: #fafafa; + background: var(--inputBackgroundColor); &.editGroups { - background: #fcfcfc; + background: var(--inputBackgroundColor); } } diff --git a/frontend/src/Settings/Profiles/Quality/QualityProfiles.css b/frontend/src/Settings/Profiles/Quality/QualityProfiles.css index 437d152d2..32dad3fac 100644 --- a/frontend/src/Settings/Profiles/Quality/QualityProfiles.css +++ b/frontend/src/Settings/Profiles/Quality/QualityProfiles.css @@ -6,8 +6,8 @@ .addQualityProfile { composes: qualityProfile from '~./QualityProfile.css'; - background-color: $cardAlternateBackgroundColor; - color: $gray; + background-color: var(--cardAlternateBackgroundColor); + color: var(--gray); text-align: center; font-size: 45px; } @@ -15,7 +15,7 @@ .center { display: inline-block; padding: 5px 20px 0; - border: 1px solid $borderColor; + border: 1px solid var(--borderColor); border-radius: 4px; - background-color: $white; + background-color: var(--cardCenterBackgroundColor); } diff --git a/frontend/src/Settings/Profiles/Release/ReleaseProfiles.css b/frontend/src/Settings/Profiles/Release/ReleaseProfiles.css index 8f5a81252..9e9715e77 100644 --- a/frontend/src/Settings/Profiles/Release/ReleaseProfiles.css +++ b/frontend/src/Settings/Profiles/Release/ReleaseProfiles.css @@ -6,15 +6,15 @@ .addReleaseProfile { composes: releaseProfile from '~./ReleaseProfile.css'; - background-color: $cardAlternateBackgroundColor; - color: $gray; + background-color: var(--cardAlternateBackgroundColor); + color: var(--gray); text-align: center; } .center { display: inline-block; padding: 5px 20px 0; - border: 1px solid $borderColor; + border: 1px solid var(--borderColor); border-radius: 4px; - background-color: $white; + background-color: var(--cardCenterBackgroundColor); } diff --git a/frontend/src/Settings/Quality/Definition/QualityDefinition.css b/frontend/src/Settings/Quality/Definition/QualityDefinition.css index 134e14d0d..f9d303498 100644 --- a/frontend/src/Settings/Quality/Definition/QualityDefinition.css +++ b/frontend/src/Settings/Quality/Definition/QualityDefinition.css @@ -4,7 +4,7 @@ margin: 5px 0; padding-top: 5px; height: 45px; - border-top: 1px solid $borderColor; + border-top: 1px solid var(--borderColor); } .quality, @@ -28,7 +28,7 @@ top: 9px; margin: 0 5px; height: 3px; - background-color: $sliderAccentColor; + background-color: var(--sliderAccentColor); box-shadow: 0 0 0 #000; &:nth-child(odd) { @@ -41,9 +41,9 @@ z-index: 0 !important; width: 18px; height: 18px; - border: 3px solid $sliderAccentColor; + border: 3px solid var(--sliderAccentColor); border-radius: 50%; - background-color: $white; + background-color: var(--white); text-align: center; cursor: pointer; } diff --git a/frontend/src/Settings/Quality/Definition/QualityDefinitions.css b/frontend/src/Settings/Quality/Definition/QualityDefinitions.css index 9f4afbe0e..3da40c624 100644 --- a/frontend/src/Settings/Quality/Definition/QualityDefinitions.css +++ b/frontend/src/Settings/Quality/Definition/QualityDefinitions.css @@ -25,7 +25,7 @@ .sizeLimitHelpText { max-width: 500px; - color: $helpTextColor; + color: var(--helpTextColor); } @media only screen and (max-width: $breakpointSmall) { diff --git a/frontend/src/Settings/Settings.css b/frontend/src/Settings/Settings.css index 38e88e67f..ef69e9674 100644 --- a/frontend/src/Settings/Settings.css +++ b/frontend/src/Settings/Settings.css @@ -2,7 +2,7 @@ composes: link from '~Components/Link/Link.css'; border-bottom: 1px solid #e5e5e5; - color: #3a3f51; + color: var(--textColor); font-size: 21px; &:hover { @@ -14,5 +14,5 @@ .summary { margin-top: 10px; margin-bottom: 30px; - color: $dimColor; + color: var(--helpTextColor); } diff --git a/frontend/src/Settings/Tags/Details/TagDetailsModalContent.css b/frontend/src/Settings/Tags/Details/TagDetailsModalContent.css index d11136863..75b157063 100644 --- a/frontend/src/Settings/Tags/Details/TagDetailsModalContent.css +++ b/frontend/src/Settings/Tags/Details/TagDetailsModalContent.css @@ -10,7 +10,7 @@ .restriction { margin-bottom: 5px; padding-bottom: 5px; - border-bottom: 1px solid $borderColor; + border-bottom: 1px solid var(--borderColor); &:last-child { margin: 0; diff --git a/frontend/src/Settings/UI/UISettings.js b/frontend/src/Settings/UI/UISettings.js index 883054797..3bfc84e06 100644 --- a/frontend/src/Settings/UI/UISettings.js +++ b/frontend/src/Settings/UI/UISettings.js @@ -10,6 +10,8 @@ import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; import { inputTypes } from 'Helpers/Props'; import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; +import themes from 'Styles/Themes'; +import titleCase from 'Utilities/String/titleCase'; import translate from 'Utilities/String/translate'; import styles from './UISettings.css'; @@ -62,6 +64,8 @@ class UISettings extends Component { } = this.props; const uiLanguages = languages.filter((item) => item.value !== 'Original'); + const themeOptions = Object.keys(themes) + .map((theme) => ({ key: theme, value: titleCase(theme) })); return ( @@ -183,9 +187,23 @@ class UISettings extends Component { legend={translate('Style')} > - - {translate('EnableColorImpairedMode')} - + Theme + + + + + {translate('EnableColorImpairedMode')} + + + + Enable Color-Impaired Mode : RestController where TResource : RestResource, new() { - private readonly IConfigService _configService; + protected readonly IConfigService _configService; protected ConfigController(IConfigService configService) { @@ -32,7 +32,7 @@ namespace Lidarr.Api.V1.Config } [RestPutById] - public ActionResult SaveConfig(TResource resource) + public virtual ActionResult SaveConfig(TResource resource) { var dictionary = resource.GetType() .GetProperties(BindingFlags.Instance | BindingFlags.Public) diff --git a/src/Lidarr.Api.V1/Config/UiConfigController.cs b/src/Lidarr.Api.V1/Config/UiConfigController.cs index fbeb2016a..9aa47004b 100644 --- a/src/Lidarr.Api.V1/Config/UiConfigController.cs +++ b/src/Lidarr.Api.V1/Config/UiConfigController.cs @@ -1,4 +1,8 @@ +using System.Linq; +using System.Reflection; using Lidarr.Http; +using Lidarr.Http.REST.Attributes; +using Microsoft.AspNetCore.Mvc; using NzbDrone.Core.Configuration; namespace Lidarr.Api.V1.Config @@ -6,14 +10,30 @@ namespace Lidarr.Api.V1.Config [V1ApiController("config/ui")] public class UiConfigController : ConfigController { - public UiConfigController(IConfigService configService) + private readonly IConfigFileProvider _configFileProvider; + + public UiConfigController(IConfigFileProvider configFileProvider, IConfigService configService) : base(configService) { + _configFileProvider = configFileProvider; + } + + [RestPutById] + public override ActionResult SaveConfig(UiConfigResource resource) + { + var dictionary = resource.GetType() + .GetProperties(BindingFlags.Instance | BindingFlags.Public) + .ToDictionary(prop => prop.Name, prop => prop.GetValue(resource, null)); + + _configFileProvider.SaveConfigDictionary(dictionary); + _configService.SaveConfigDictionary(dictionary); + + return Accepted(resource.Id); } protected override UiConfigResource ToResource(IConfigService model) { - return UiConfigResourceMapper.ToResource(model); + return UiConfigResourceMapper.ToResource(_configFileProvider, model); } } } diff --git a/src/Lidarr.Api.V1/Config/UiConfigResource.cs b/src/Lidarr.Api.V1/Config/UiConfigResource.cs index 3e6883dda..d972ea390 100644 --- a/src/Lidarr.Api.V1/Config/UiConfigResource.cs +++ b/src/Lidarr.Api.V1/Config/UiConfigResource.cs @@ -23,11 +23,12 @@ namespace Lidarr.Api.V1.Config public bool ExpandEPByDefault { get; set; } public bool ExpandBroadcastByDefault { get; set; } public bool ExpandOtherByDefault { get; set; } + public string Theme { get; set; } } public static class UiConfigResourceMapper { - public static UiConfigResource ToResource(IConfigService model) + public static UiConfigResource ToResource(IConfigFileProvider config, IConfigService model) { return new UiConfigResource { @@ -46,7 +47,8 @@ namespace Lidarr.Api.V1.Config ExpandSingleByDefault = model.ExpandSingleByDefault, ExpandEPByDefault = model.ExpandEPByDefault, ExpandBroadcastByDefault = model.ExpandBroadcastByDefault, - ExpandOtherByDefault = model.ExpandOtherByDefault + ExpandOtherByDefault = model.ExpandOtherByDefault, + Theme = config.Theme }; } } diff --git a/src/Lidarr.Http/Frontend/InitializeJsController.cs b/src/Lidarr.Http/Frontend/InitializeJsController.cs index d44d7582f..cab1ccc36 100644 --- a/src/Lidarr.Http/Frontend/InitializeJsController.cs +++ b/src/Lidarr.Http/Frontend/InitializeJsController.cs @@ -50,6 +50,7 @@ namespace Lidarr.Http.Frontend builder.AppendLine($" release: '{BuildInfo.Release}',"); builder.AppendLine($" version: '{BuildInfo.Version.ToString()}',"); builder.AppendLine($" instanceName: '{_configFileProvider.InstanceName.ToString()}',"); + builder.AppendLine($" theme: '{_configFileProvider.Theme.ToString()}',"); builder.AppendLine($" branch: '{_configFileProvider.Branch.ToLower()}',"); builder.AppendLine($" analytics: {_analyticsService.IsEnabled.ToString().ToLowerInvariant()},"); builder.AppendLine($" userHash: '{HashUtil.AnonymousToken()}',"); diff --git a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs index 0e9f7c85b..99b11aba0 100644 --- a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs +++ b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs @@ -48,6 +48,7 @@ namespace NzbDrone.Core.Configuration string SyslogServer { get; } int SyslogPort { get; } string SyslogLevel { get; } + string Theme { get; } } public class ConfigFileProvider : IConfigFileProvider @@ -183,6 +184,8 @@ namespace NzbDrone.Core.Configuration public string LogLevel => GetValue("LogLevel", "info"); public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false); + + public string Theme => GetValue("Theme", "light", persist: false); public bool LogSql => GetValueBoolean("LogSql", false, persist: false); public int LogRotate => GetValueInt("LogRotate", 50, persist: false); public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false); From 0edab1720595400b7d405be084ba72ebf5d080ec Mon Sep 17 00:00:00 2001 From: bakerboy448 <55419169+bakerboy448@users.noreply.github.com> Date: Wed, 5 Oct 2022 16:00:11 -0500 Subject: [PATCH 0032/1478] update feature request template [skip ci] (cherry picked from commit 362e664ce6d301e8283d489b3e0d945db46d22d3) --- .github/ISSUE_TEMPLATE/feature_request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 6b4209825..ef142b0ad 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -5,9 +5,9 @@ body: - type: checkboxes attributes: label: Is there an existing issue for this? - description: Please search to see if an issue already exists for the feature you are requesting. + description: Please search to see if an open or closed issue already exists for the feature you are requesting. If a request exists and is closed note that it may only be fixed in an unstable branch. options: - - label: I have searched the existing issues + - label: I have searched the existing open and closed issues required: true - type: textarea attributes: From 12413a2a9ae2867a1e32b261d39eb2d65b27198e Mon Sep 17 00:00:00 2001 From: Servarr Date: Tue, 18 Oct 2022 00:00:44 +0000 Subject: [PATCH 0033/1478] Automated API Docs update --- src/Lidarr.Api.V1/openapi.json | 80 ++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/src/Lidarr.Api.V1/openapi.json b/src/Lidarr.Api.V1/openapi.json index 791985233..fb099fb06 100644 --- a/src/Lidarr.Api.V1/openapi.json +++ b/src/Lidarr.Api.V1/openapi.json @@ -8218,44 +8218,6 @@ } }, "/api/v1/config/ui/{id}": { - "get": { - "tags": [ - "UiConfig" - ], - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "text/plain": { - "schema": { - "$ref": "#/components/schemas/UiConfigResource" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/UiConfigResource" - } - }, - "text/json": { - "schema": { - "$ref": "#/components/schemas/UiConfigResource" - } - } - } - } - } - }, "put": { "tags": [ "UiConfig" @@ -8311,6 +8273,44 @@ } } } + }, + "get": { + "tags": [ + "UiConfig" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/UiConfigResource" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/UiConfigResource" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/UiConfigResource" + } + } + } + } + } } }, "/api/v1/config/ui": { @@ -13191,6 +13191,10 @@ }, "expandOtherByDefault": { "type": "boolean" + }, + "theme": { + "type": "string", + "nullable": true } }, "additionalProperties": false From a2d129116827c01b80691c6a910da11449f1109a Mon Sep 17 00:00:00 2001 From: Qstick Date: Fri, 21 Oct 2022 23:45:42 -0500 Subject: [PATCH 0034/1478] Fixed: Assume WEB is MP3-320 Fixes #3038 --- .../ParserTests/QualityParserFixture.cs | 1 + src/NzbDrone.Core/Parser/QualityParser.cs | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs index 640049fb9..0dafda8d0 100644 --- a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs @@ -78,6 +78,7 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("Ricardo Arjona - APNEA (Single 2014) (320 kbps)", null, 0)] [TestCase("Kehlani - SweetSexySavage (Deluxe Edition) (2017) 320", null, 0)] [TestCase("Anderson Paak - Malibu (320)(2016)", null, 0)] + [TestCase("Zeynep_Erbay-Flashlights_On_Love-WEB-2022-BABAS", null, 0)] [TestCase("", "MPEG Version 1 Audio, Layer 3", 320)] public void should_parse_mp3_320_quality(string title, string desc, int bitrate) { diff --git a/src/NzbDrone.Core/Parser/QualityParser.cs b/src/NzbDrone.Core/Parser/QualityParser.cs index 34083fecc..e6c5174c3 100644 --- a/src/NzbDrone.Core/Parser/QualityParser.cs +++ b/src/NzbDrone.Core/Parser/QualityParser.cs @@ -43,6 +43,9 @@ namespace NzbDrone.Core.Parser private static readonly Regex CodecRegex = new Regex(@"\b(?:(?MPEG Version \d(.5)? Audio, Layer 1|MP1)|(?MPEG Version \d(.5)? Audio, Layer 2|MP2)|(?MP3.*VBR|MPEG Version \d(.5)? Audio, Layer 3 vbr)|(?MP3|MPEG Version \d(.5)? Audio, Layer 3)|(?(web)?flac)|(?wavpack|wv)|(?alac)|(?WMA\d?)|(?WAV|PCM)|(?M4A|M4P|M4B|AAC|mp4a|MPEG-4 Audio(?!.*alac))|(?OGG|OGA|Vorbis))\b|(?monkey's audio|[\[|\(].*\bape\b.*[\]|\)])|(?Opus Version \d(.5)? Audio|[\[|\(].*\bopus\b.*[\]|\)])", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex WebRegex = new Regex(@"\b(?WEB)(?:\b|$|[ .])", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + public static QualityModel ParseQuality(string name, string desc, int fileBitrate, int fileSampleSize = 0) { Logger.Debug("Trying to parse quality for {0}", name); @@ -219,7 +222,14 @@ namespace NzbDrone.Core.Parser } else { - result.Quality = Quality.Unknown; + if (WebRegex.IsMatch(normalizedName)) + { + result.Quality = Quality.MP3_320; + } + else + { + result.Quality = Quality.Unknown; + } } break; From ab0cd623d250b73328ff6c21afe3b42489251e7d Mon Sep 17 00:00:00 2001 From: Chris <1967906+psylenced@users.noreply.github.com> Date: Mon, 17 Oct 2022 02:42:33 +1100 Subject: [PATCH 0035/1478] Fixed: Cleanse Discord Webhook URLs Closes #3037 (cherry picked from commit d1f2a8a9486471f4986da2fa16d5439ccf0426e1) --- .../InstrumentationTests/CleanseLogMessageFixture.cs | 4 ++++ src/NzbDrone.Common/Instrumentation/CleanseLogMessage.cs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs b/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs index 32c3f4c0d..2dbd0ce10 100644 --- a/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs +++ b/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs @@ -87,6 +87,10 @@ namespace NzbDrone.Common.Test.InstrumentationTests // Webhooks - Notifiarr [TestCase(@"https://xxx.yyy/api/v1/notification/lidarr/9pr04sg6-0123-3210-imav-eql2tyu8xyui")] + // Discord + [TestCase(@"https://discord.com/api/webhooks/mySecret")] + [TestCase(@"https://discord.com/api/webhooks/mySecret/01233210")] + public void should_clean_message(string message) { var cleansedMessage = CleanseLogMessage.Cleanse(message); diff --git a/src/NzbDrone.Common/Instrumentation/CleanseLogMessage.cs b/src/NzbDrone.Common/Instrumentation/CleanseLogMessage.cs index 18e28cfc7..30f44a50b 100644 --- a/src/NzbDrone.Common/Instrumentation/CleanseLogMessage.cs +++ b/src/NzbDrone.Common/Instrumentation/CleanseLogMessage.cs @@ -60,6 +60,9 @@ namespace NzbDrone.Common.Instrumentation new Regex(@",""info_hash"":""(?[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@",""pass[- _]?key"":""(?[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@",""rss[- _]?key"":""(?[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase), + + // Discord + new Regex(@"discord.com/api/webhooks/((?[\w-]+)/)?(?[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase) }; private static readonly Regex CleanseRemoteIPRegex = new Regex(@"(?:Auth-\w+(? Date: Sat, 22 Oct 2022 22:02:05 -0500 Subject: [PATCH 0036/1478] Update No Tag string Fixes #3024 --- frontend/src/Settings/Tags/Tags.js | 2 +- src/NzbDrone.Core/Localization/Core/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/Settings/Tags/Tags.js b/frontend/src/Settings/Tags/Tags.js index 29b8480f5..453860426 100644 --- a/frontend/src/Settings/Tags/Tags.js +++ b/frontend/src/Settings/Tags/Tags.js @@ -15,7 +15,7 @@ function Tags(props) { if (!items.length) { return (
- {translate('NoTagsHaveBeenAddedYetAddTagsToLinkArtistsWithDelayProfilesRestrictionsOrNotificationsClickLinkTohttpswikiservarrcomlidarrsettingstagshereLinkToFindOutMoreAboutTagsInLidarr')} + {translate('NoTagsHaveBeenAddedYet')}
); } diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 4dfbf403d..27ae36337 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -386,7 +386,7 @@ "NoMinimumForAnyRuntime": "No minimum for any runtime", "None": "None", "NoneData": "No albums will be monitored", - "NoTagsHaveBeenAddedYetAddTagsToLinkArtistsWithDelayProfilesRestrictionsOrNotificationsClickLinkTohttpswikiservarrcomlidarrsettingstagshereLinkToFindOutMoreAboutTagsInLidarr": "No tags have been added yet. Add tags to link artists with delay profiles, restrictions, or notifications. Click here to find out more about tags in Lidarr.", + "NoTagsHaveBeenAddedYet": "No tags have been added yet", "NotificationTriggers": "Notification Triggers", "NoUpdatesAreAvailable": "No updates are available", "OnApplicationUpdate": "On Application Update", From 37fbc65eb6c6b5fd9bb26eb2989586544898ddc3 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sat, 22 Oct 2022 22:37:17 -0500 Subject: [PATCH 0037/1478] Fixed: Correct diff previews when new tag image is null Related to #2861 --- .../MediaFiles/AudioTagService.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/MediaFiles/AudioTagService.cs b/src/NzbDrone.Core/MediaFiles/AudioTagService.cs index 6f4692189..82d1e1274 100644 --- a/src/NzbDrone.Core/MediaFiles/AudioTagService.cs +++ b/src/NzbDrone.Core/MediaFiles/AudioTagService.cs @@ -208,10 +208,17 @@ namespace NzbDrone.Core.MediaFiles return; } - var newTags = GetTrackMetadata(trackfile); var path = trackfile.Path; + var oldTags = ReadAudioTag(path); + var newTags = GetTrackMetadata(trackfile); - var diff = ReadAudioTag(path).Diff(newTags); + // We don't overwrite image when new image is null + if (newTags.ImageFile == null && !_configService.ScrubAudioTags) + { + newTags.ImageSize = oldTags.ImageSize; + } + + var diff = oldTags.Diff(newTags); _rootFolderWatchingService.ReportFileSystemChangeBeginning(path); @@ -350,6 +357,13 @@ namespace NzbDrone.Core.MediaFiles var oldTags = ReadAudioTag(f.Path); var newTags = GetTrackMetadata(f); + + // We don't overwrite image when new image is null + if (newTags.ImageFile == null && !_configService.ScrubAudioTags) + { + newTags.ImageSize = oldTags.ImageSize; + } + var diff = oldTags.Diff(newTags); if (diff.Any()) From 8c5b227b17eef8dd108f7f05c6d346b11c2b3041 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sat, 22 Oct 2022 23:32:14 -0500 Subject: [PATCH 0038/1478] Fixed: Ignore VA on Gazelle search for Various Artist Albums Fixes #2829 --- .../GazelleRequestGeneratorFixture.cs | 93 +++++++++++++++++++ .../Gazelle/GazelleRequestGenerator.cs | 11 ++- 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/NzbDrone.Core.Test/IndexerTests/GazelleTests/GazelleRequestGeneratorFixture.cs diff --git a/src/NzbDrone.Core.Test/IndexerTests/GazelleTests/GazelleRequestGeneratorFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/GazelleTests/GazelleRequestGeneratorFixture.cs new file mode 100644 index 000000000..947d6aebd --- /dev/null +++ b/src/NzbDrone.Core.Test/IndexerTests/GazelleTests/GazelleRequestGeneratorFixture.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Moq; +using NLog; +using NUnit.Framework; +using NzbDrone.Common.Cache; +using NzbDrone.Common.Http; +using NzbDrone.Core.Indexers.Gazelle; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.IndexerTests.NewznabTests +{ + public class GazelleRequestGeneratorFixture : CoreTest + { + private AlbumSearchCriteria _singleAlbumSearchCriteria; + private AlbumSearchCriteria _variousArtistSearchCriteria; + + [SetUp] + public void SetUp() + { + Subject.Settings = new GazelleSettings() + { + BaseUrl = "http://127.0.0.1:1234/", + Username = "someuser", + Password = "somepass" + }; + + _singleAlbumSearchCriteria = new AlbumSearchCriteria + { + Artist = new Music.Artist { Name = "Alien Ant Farm" }, + AlbumTitle = "TruANT" + }; + + _variousArtistSearchCriteria = new AlbumSearchCriteria + { + Artist = new Music.Artist { Name = "Various Artists" }, + AlbumTitle = "TruANT" + }; + + Mocker.GetMock() + .Setup(v => v.Execute(It.IsAny())) + .Returns(r => new HttpResponse(r, new HttpHeader(), "{ \"status\": \"success\", \"response\": { \"authkey\": \"key\", \"passkey\": \"key\" } }")); + + Mocker.GetMock>>() + .Setup(v => v.Find(It.IsAny())) + .Returns(r => new Dictionary { { "some", "cookie" } }); + + Subject.AuthCookieCache = Mocker.Resolve>>(); + + Subject.HttpClient = Mocker.Resolve(); + + Subject.Logger = Mocker.Resolve(); + } + + [Test] + public void should_use_all_categories_for_feed() + { + var results = Subject.GetRecentRequests(); + + results.GetAllTiers().Should().HaveCount(1); + + var page = results.GetAllTiers().First().First(); + + page.Url.Query.Should().Be("action=browse&searchstr="); + } + + [Test] + public void should_search_by_artist_and_album_if_supported() + { + var results = Subject.GetSearchRequests(_singleAlbumSearchCriteria); + results.GetTier(0).Should().HaveCount(1); + + var page = results.GetAllTiers().First().First(); + + page.Url.Query.Should().Contain("artistname=Alien+Ant+Farm"); + page.Url.Query.Should().Contain("groupname=TruANT"); + } + + [Test] + public void should_only_search_by_album_if_various_artist() + { + var results = Subject.GetSearchRequests(_variousArtistSearchCriteria); + results.GetTier(0).Should().HaveCount(1); + + var page = results.GetAllTiers().First().First(); + + page.Url.Query.Should().NotContain("artistname="); + page.Url.Query.Should().Contain("groupname=TruANT"); + } + } +} diff --git a/src/NzbDrone.Core/Indexers/Gazelle/GazelleRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Gazelle/GazelleRequestGenerator.cs index a43630fa4..750ed5914 100644 --- a/src/NzbDrone.Core/Indexers/Gazelle/GazelleRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/Gazelle/GazelleRequestGenerator.cs @@ -30,7 +30,16 @@ namespace NzbDrone.Core.Indexers.Gazelle public IndexerPageableRequestChain GetSearchRequests(AlbumSearchCriteria searchCriteria) { var pageableRequests = new IndexerPageableRequestChain(); - pageableRequests.Add(GetRequest(string.Format("&artistname={0}&groupname={1}", searchCriteria.CleanArtistQuery, searchCriteria.CleanAlbumQuery))); + + if (searchCriteria.CleanArtistQuery == "VA") + { + pageableRequests.Add(GetRequest(string.Format("&groupname={0}", searchCriteria.CleanAlbumQuery))); + } + else + { + pageableRequests.Add(GetRequest(string.Format("&artistname={0}&groupname={1}", searchCriteria.CleanArtistQuery, searchCriteria.CleanAlbumQuery))); + } + return pageableRequests; } From 206d34c6422bc9978db9751cc6a7d7987a21261a Mon Sep 17 00:00:00 2001 From: Stevie Robinson Date: Thu, 24 Mar 2022 21:49:37 +0100 Subject: [PATCH 0039/1478] New: Add optional Source Title column to history (cherry picked from commit 581fb2cb3d47d62fe16b840081647056ec77043d) --- frontend/src/Activity/History/HistoryRow.js | 10 ++++++++++ frontend/src/Store/Actions/historyActions.js | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/frontend/src/Activity/History/HistoryRow.js b/frontend/src/Activity/History/HistoryRow.js index 96f59fe4e..98718f70f 100644 --- a/frontend/src/Activity/History/HistoryRow.js +++ b/frontend/src/Activity/History/HistoryRow.js @@ -178,6 +178,16 @@ class HistoryRow extends Component { ); } + if (name === 'sourceTitle') { + return ( + + {sourceTitle} + + ); + } + if (name === 'details') { return ( Date: Sat, 22 Oct 2022 23:45:49 -0500 Subject: [PATCH 0040/1478] Fixed: (Newznab) Search with year for self-title albums Fixes #2716 --- .../Newznab/NewznabRequestGenerator.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs index 05efd709f..14379613c 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs @@ -93,9 +93,16 @@ namespace NzbDrone.Core.Indexers.Newznab var artistQuery = AudioTextSearchEngine == "raw" ? searchCriteria.ArtistQuery : searchCriteria.CleanArtistQuery; var albumQuery = AudioTextSearchEngine == "raw" ? searchCriteria.AlbumQuery : searchCriteria.CleanAlbumQuery; + var searchQuery = $"&artist={NewsnabifyTitle(artistQuery)}&album={NewsnabifyTitle(albumQuery)}"; + + if (artistQuery == albumQuery && searchCriteria.AlbumYear > 0) + { + searchQuery = $"&artist={NewsnabifyTitle(artistQuery)}&album={NewsnabifyTitle(albumQuery)}&year={searchCriteria.AlbumYear}"; + } + AddAudioPageableRequests(pageableRequests, searchCriteria, - $"&artist={NewsnabifyTitle(artistQuery)}&album={NewsnabifyTitle(albumQuery)}"); + searchQuery); } if (SupportsSearch) @@ -105,10 +112,17 @@ namespace NzbDrone.Core.Indexers.Newznab var artistQuery = TextSearchEngine == "raw" ? searchCriteria.ArtistQuery : searchCriteria.CleanArtistQuery; var albumQuery = TextSearchEngine == "raw" ? searchCriteria.AlbumQuery : searchCriteria.CleanAlbumQuery; + var searchQuery = $"{artistQuery}+{albumQuery}"; + + if (artistQuery == albumQuery) + { + searchQuery = $"{artistQuery}+{albumQuery}+{searchCriteria.AlbumYear}"; + } + pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "search", - $"&q={NewsnabifyTitle($"{artistQuery}+{albumQuery}")}")); + $"&q={NewsnabifyTitle(searchQuery)}")); } return pageableRequests; From 02152d85a1c12d89b308cab3b9e93f1b3889e41a Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 23 Oct 2022 01:16:58 -0500 Subject: [PATCH 0041/1478] Fixed: Kodi Library Update Notification Fixes #2574 --- .../Xbmc/GetArtistPathFixture.cs | 17 +++++++++++++++-- .../Notifications/Xbmc/Model/KodiArtist.cs | 2 +- .../Notifications/Xbmc/Model/KodiSource.cs | 10 ++++++++++ .../Notifications/Xbmc/Model/SourceResponse.cs | 9 +++++++++ .../Notifications/Xbmc/Model/SourceResult.cs | 15 +++++++++++++++ .../Notifications/Xbmc/XbmcJsonApiProxy.cs | 10 +++++++++- .../Notifications/Xbmc/XbmcService.cs | 12 +++++++++++- 7 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 src/NzbDrone.Core/Notifications/Xbmc/Model/KodiSource.cs create mode 100644 src/NzbDrone.Core/Notifications/Xbmc/Model/SourceResponse.cs create mode 100644 src/NzbDrone.Core/Notifications/Xbmc/Model/SourceResult.cs diff --git a/src/NzbDrone.Core.Test/NotificationTests/Xbmc/GetArtistPathFixture.cs b/src/NzbDrone.Core.Test/NotificationTests/Xbmc/GetArtistPathFixture.cs index 4340555e5..8a15cf60c 100644 --- a/src/NzbDrone.Core.Test/NotificationTests/Xbmc/GetArtistPathFixture.cs +++ b/src/NzbDrone.Core.Test/NotificationTests/Xbmc/GetArtistPathFixture.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using DryIoc.ImTools; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; @@ -17,6 +18,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc private XbmcSettings _settings; private Music.Artist _artist; private List _xbmcArtist; + private List _xbmcSources; [SetUp] public void Setup() @@ -27,14 +29,25 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc _xbmcArtist = Builder.CreateListOfSize(3) .All() .With(s => s.MusicbrainzArtistId = new List { "0" }) + .With(s => s.SourceId = new List { 1 }) .TheFirst(1) .With(s => s.MusicbrainzArtistId = new List { MB_ID.ToString() }) .Build() .ToList(); + _xbmcSources = Builder.CreateListOfSize(1) + .All() + .With(s => s.SourceId = _xbmcArtist.First().SourceId.First()) + .Build() + .ToList(); + Mocker.GetMock() .Setup(s => s.GetArtist(_settings)) .Returns(_xbmcArtist); + + Mocker.GetMock() + .Setup(s => s.GetSources(_settings)) + .Returns(_xbmcSources); } private void GivenMatchingMusicbrainzId() @@ -77,7 +90,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc { GivenMatchingMusicbrainzId(); - Subject.GetArtistPath(_settings, _artist).Should().Be(_xbmcArtist.First().File); + Subject.GetArtistPath(_settings, _artist).Should().Be(_xbmcSources.First().File); } [Test] @@ -85,7 +98,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc { GivenMatchingTitle(); - Subject.GetArtistPath(_settings, _artist).Should().Be(_xbmcArtist.First().File); + Subject.GetArtistPath(_settings, _artist).Should().Be(_xbmcSources.First().File); } } } diff --git a/src/NzbDrone.Core/Notifications/Xbmc/Model/KodiArtist.cs b/src/NzbDrone.Core/Notifications/Xbmc/Model/KodiArtist.cs index 1e86c7c9d..cba1719f7 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/Model/KodiArtist.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/Model/KodiArtist.cs @@ -7,6 +7,6 @@ namespace NzbDrone.Core.Notifications.Xbmc.Model public int ArtistId { get; set; } public string Label { get; set; } public List MusicbrainzArtistId { get; set; } - public string File { get; set; } + public List SourceId { get; set; } } } diff --git a/src/NzbDrone.Core/Notifications/Xbmc/Model/KodiSource.cs b/src/NzbDrone.Core/Notifications/Xbmc/Model/KodiSource.cs new file mode 100644 index 000000000..0c45e406d --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Xbmc/Model/KodiSource.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace NzbDrone.Core.Notifications.Xbmc.Model +{ + public class KodiSource + { + public int SourceId { get; set; } + public string File { get; set; } + } +} diff --git a/src/NzbDrone.Core/Notifications/Xbmc/Model/SourceResponse.cs b/src/NzbDrone.Core/Notifications/Xbmc/Model/SourceResponse.cs new file mode 100644 index 000000000..d8c6a3601 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Xbmc/Model/SourceResponse.cs @@ -0,0 +1,9 @@ +namespace NzbDrone.Core.Notifications.Xbmc.Model +{ + public class SourceResponse + { + public string Id { get; set; } + public string JsonRpc { get; set; } + public SourceResult Result { get; set; } + } +} diff --git a/src/NzbDrone.Core/Notifications/Xbmc/Model/SourceResult.cs b/src/NzbDrone.Core/Notifications/Xbmc/Model/SourceResult.cs new file mode 100644 index 000000000..8c2c3c866 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Xbmc/Model/SourceResult.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace NzbDrone.Core.Notifications.Xbmc.Model +{ + public class SourceResult + { + public Dictionary Limits { get; set; } + public List Sources; + + public SourceResult() + { + Sources = new List(); + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs b/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs index f130199ba..b2bd6895e 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs @@ -15,6 +15,7 @@ namespace NzbDrone.Core.Notifications.Xbmc void CleanLibrary(XbmcSettings settings); List GetActivePlayers(XbmcSettings settings); List GetArtist(XbmcSettings settings); + List GetSources(XbmcSettings settings); } public class XbmcJsonApiProxy : IXbmcJsonApiProxy @@ -68,11 +69,18 @@ namespace NzbDrone.Core.Notifications.Xbmc public List GetArtist(XbmcSettings settings) { - var response = ProcessRequest(settings, "AudioLibrary.GetArtists", new List { "properties", "musicbrainzartistid" }); + var response = ProcessRequest(settings, "AudioLibrary.GetArtists", true, new List { "sourceid", "musicbrainzartistid" }); return Json.Deserialize(response).Result.Artists; } + public List GetSources(XbmcSettings settings) + { + var response = ProcessRequest(settings, "AudioLibrary.GetSources", new List { "file" }); + + return Json.Deserialize(response).Result.Sources; + } + private string ProcessRequest(XbmcSettings settings, string method, params object[] parameters) { var url = HttpRequestBuilder.BuildBaseUrl(settings.UseSsl, settings.Host, settings.Port, "jsonrpc"); diff --git a/src/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs b/src/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs index 334837b95..7756b35fc 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs @@ -3,6 +3,7 @@ using System.Linq; using FluentValidation.Results; using NLog; using NzbDrone.Core.Music; +using NzbDrone.Core.Notifications.Xbmc.Model; namespace NzbDrone.Core.Notifications.Xbmc { @@ -70,7 +71,16 @@ namespace NzbDrone.Core.Notifications.Xbmc return musicBrainzId == artist.Metadata.Value.ForeignArtistId || s.Label == artist.Name; }); - return matchingArtist?.File; + KodiSource matchingSource = null; + + if (matchingArtist != null && matchingArtist.SourceId.Any()) + { + var allSources = _proxy.GetSources(settings); + + matchingSource = allSources.FirstOrDefault(s => s.SourceId == matchingArtist.SourceId.FirstOrDefault()); + } + + return matchingSource?.File; } private void UpdateLibrary(XbmcSettings settings, Artist artist) From 80143e0ae1d4c0407174d83f07ab945b8a327d9d Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 23 Oct 2022 01:52:21 -0500 Subject: [PATCH 0042/1478] Fix UpdateFixture artist path test --- .../NotificationTests/Xbmc/UpdateFixture.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/NzbDrone.Core.Test/NotificationTests/Xbmc/UpdateFixture.cs b/src/NzbDrone.Core.Test/NotificationTests/Xbmc/UpdateFixture.cs index 852a7173a..08bcf4920 100644 --- a/src/NzbDrone.Core.Test/NotificationTests/Xbmc/UpdateFixture.cs +++ b/src/NzbDrone.Core.Test/NotificationTests/Xbmc/UpdateFixture.cs @@ -16,6 +16,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc private const string MB_ID = "9f4e41c3-2648-428e-b8c7-dc10465b49ac"; private XbmcSettings _settings; private List _xbmcArtist; + private List _xbmcSources; [SetUp] public void Setup() @@ -26,15 +27,26 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc _xbmcArtist = Builder.CreateListOfSize(3) .TheFirst(1) .With(s => s.MusicbrainzArtistId = new List { MB_ID.ToString() }) + .With(s => s.SourceId = new List { 1 }) .TheNext(2) .With(s => s.MusicbrainzArtistId = new List()) .Build() .ToList(); + _xbmcSources = Builder.CreateListOfSize(1) + .All() + .With(s => s.SourceId = _xbmcArtist.First().SourceId.First()) + .Build() + .ToList(); + Mocker.GetMock() .Setup(s => s.GetArtist(_settings)) .Returns(_xbmcArtist); + Mocker.GetMock() + .Setup(s => s.GetSources(_settings)) + .Returns(_xbmcSources); + Mocker.GetMock() .Setup(s => s.GetActivePlayers(_settings)) .Returns(new List()); From 878f415e214e7e4055031db2d93e537dd67f1b9b Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Sun, 7 Aug 2022 14:54:39 -0400 Subject: [PATCH 0043/1478] New: Add application URL to host configuration settings Fixes #2933 Closes #2934 (cherry picked from commit 762042ba97c2ae689cee32d8e66a458f6d7a8adc) --- frontend/src/Settings/General/HostSettings.js | 16 ++++++++++++++++ src/Lidarr.Api.V1/Config/HostConfigResource.cs | 4 +++- src/NzbDrone.Core/Configuration/ConfigService.cs | 2 ++ .../Configuration/IConfigService.cs | 1 + src/NzbDrone.Core/Localization/Core/en.json | 2 ++ 5 files changed, 24 insertions(+), 1 deletion(-) diff --git a/frontend/src/Settings/General/HostSettings.js b/frontend/src/Settings/General/HostSettings.js index acb70eaa1..56eb3e89b 100644 --- a/frontend/src/Settings/General/HostSettings.js +++ b/frontend/src/Settings/General/HostSettings.js @@ -21,6 +21,7 @@ function HostSettings(props) { port, urlBase, instanceName, + applicationUrl, enableSsl, sslPort, sslCertPath, @@ -96,6 +97,21 @@ function HostSettings(props) { />
+ + {translate('ApplicationURL')} + + + + GetValueEnum("CertificateValidation", CertificateValidationType.Enabled); + public string ApplicationUrl => GetValue("ApplicationUrl", string.Empty); + private string GetValue(string key) { return GetValue(key, string.Empty); diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs index baad9ab95..3d1be0eb1 100644 --- a/src/NzbDrone.Core/Configuration/IConfigService.cs +++ b/src/NzbDrone.Core/Configuration/IConfigService.cs @@ -98,5 +98,6 @@ namespace NzbDrone.Core.Configuration int BackupRetention { get; } CertificateValidationType CertificateValidation { get; } + string ApplicationUrl { get; } } } diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 27ae36337..88662d08c 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -41,6 +41,8 @@ "APIKey": "API Key", "ApiKeyHelpTextWarning": "Requires restart to take effect", "AppDataDirectory": "AppData directory", + "ApplicationURL": "Application URL", + "ApplicationUrlHelpText": "This application's external URL including http(s)://, port and URL base", "ApplyTags": "Apply Tags", "ApplyTagsHelpTexts1": "How to apply tags to the selected artist", "ApplyTagsHelpTexts2": "Add: Add the tags the existing list of tags", From 64d15e3e94033f3cd60c3c05e53f911067ebd89c Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 23 Oct 2022 10:38:01 -0500 Subject: [PATCH 0044/1478] New: Add indexer name to the download report log Fixes #3043 --- src/NzbDrone.Core/Download/DownloadService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/Download/DownloadService.cs b/src/NzbDrone.Core/Download/DownloadService.cs index 66ec2c25e..676835c98 100644 --- a/src/NzbDrone.Core/Download/DownloadService.cs +++ b/src/NzbDrone.Core/Download/DownloadService.cs @@ -110,7 +110,7 @@ namespace NzbDrone.Core.Download albumGrabbedEvent.DownloadId = downloadClientId; } - _logger.ProgressInfo("Report sent to {0}. {1}", downloadClient.Definition.Name, downloadTitle); + _logger.ProgressInfo("Report sent to {0} from indexer {1}. {2}", downloadClient.Definition.Name, remoteAlbum.Release.Indexer, downloadTitle); _eventAggregator.PublishEvent(albumGrabbedEvent); } } From 117436b199fc6dc678d6371c373ab89aff5e28f0 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sat, 15 Oct 2022 20:26:01 -0500 Subject: [PATCH 0045/1478] Cleanup dual target and mono code --- src/Lidarr.Api.V1/System/SystemController.cs | 4 ++-- .../PathExtensionFixture.cs | 2 +- .../EnvironmentInfo/PlatformInfo.cs | 22 +------------------ .../Extensions/PathExtensions.cs | 13 ++++------- .../UpdateTests/UpdateServiceFixture.cs | 4 ++-- .../ServerSideNotificationService.cs | 2 +- .../Update/InstallUpdateService.cs | 4 ++-- src/NzbDrone.Core/Update/UpdatePackage.cs | 3 +-- .../Update/UpdatePackageProvider.cs | 5 +++-- .../DiskProviderTests/FreeSpaceFixture.cs | 1 + src/NzbDrone.Mono/Disk/DiskProvider.cs | 15 ++++--------- src/NzbDrone.Test.Common/TestBase.cs | 2 +- .../UpdateEngine/InstallUpdateService.cs | 2 +- 13 files changed, 24 insertions(+), 55 deletions(-) diff --git a/src/Lidarr.Api.V1/System/SystemController.cs b/src/Lidarr.Api.V1/System/SystemController.cs index ec854b7c3..cc461df34 100644 --- a/src/Lidarr.Api.V1/System/SystemController.cs +++ b/src/Lidarr.Api.V1/System/SystemController.cs @@ -70,7 +70,7 @@ namespace Lidarr.Api.V1.System AppData = _appFolderInfo.GetAppDataPath(), OsName = _osInfo.Name, OsVersion = _osInfo.Version, - IsNetCore = PlatformInfo.IsNetCore, + IsNetCore = true, IsLinux = OsInfo.IsLinux, IsOsx = OsInfo.IsOsx, IsWindows = OsInfo.IsWindows, @@ -82,7 +82,7 @@ namespace Lidarr.Api.V1.System MigrationVersion = _database.Migration, UrlBase = _configFileProvider.UrlBase, RuntimeVersion = _platformInfo.Version, - RuntimeName = PlatformInfo.Platform, + RuntimeName = "netcore", StartTime = _runtimeInfo.StartTime, PackageVersion = _deploymentInfoProvider.PackageVersion, PackageAuthor = _deploymentInfoProvider.PackageAuthor, diff --git a/src/NzbDrone.Common.Test/PathExtensionFixture.cs b/src/NzbDrone.Common.Test/PathExtensionFixture.cs index f5dec0346..b8402593d 100644 --- a/src/NzbDrone.Common.Test/PathExtensionFixture.cs +++ b/src/NzbDrone.Common.Test/PathExtensionFixture.cs @@ -279,7 +279,7 @@ namespace NzbDrone.Common.Test [Test] public void GetUpdateClientExePath() { - GetIAppDirectoryInfo().GetUpdateClientExePath(PlatformType.DotNet).Should().BeEquivalentTo(@"C:\Temp\lidarr_update\Lidarr.Update.exe".AsOsAgnostic()); + GetIAppDirectoryInfo().GetUpdateClientExePath().Should().BeEquivalentTo(@"C:\Temp\lidarr_update\Lidarr.Update".AsOsAgnostic().ProcessNameToExe()); } [Test] diff --git a/src/NzbDrone.Common/EnvironmentInfo/PlatformInfo.cs b/src/NzbDrone.Common/EnvironmentInfo/PlatformInfo.cs index 76f323652..68a0ca092 100644 --- a/src/NzbDrone.Common/EnvironmentInfo/PlatformInfo.cs +++ b/src/NzbDrone.Common/EnvironmentInfo/PlatformInfo.cs @@ -2,13 +2,6 @@ using System; namespace NzbDrone.Common.EnvironmentInfo { - public enum PlatformType - { - DotNet = 0, - Mono = 1, - NetCore = 2 - } - public interface IPlatformInfo { Version Version { get; } @@ -16,31 +9,18 @@ namespace NzbDrone.Common.EnvironmentInfo public class PlatformInfo : IPlatformInfo { - private static PlatformType _platform; private static Version _version; static PlatformInfo() { - _platform = PlatformType.NetCore; _version = Environment.Version; } - public static PlatformType Platform => _platform; - public static bool IsDotNet => Platform == PlatformType.DotNet; - public static bool IsNetCore => Platform == PlatformType.NetCore; - public static string PlatformName { get { - if (IsDotNet) - { - return ".NET"; - } - else - { - return ".NET Core"; - } + return ".NET"; } } diff --git a/src/NzbDrone.Common/Extensions/PathExtensions.cs b/src/NzbDrone.Common/Extensions/PathExtensions.cs index 8d0e611a3..07e67f5f0 100644 --- a/src/NzbDrone.Common/Extensions/PathExtensions.cs +++ b/src/NzbDrone.Common/Extensions/PathExtensions.cs @@ -266,9 +266,9 @@ namespace NzbDrone.Common.Extensions return substring.Substring(0, lastSeparatorIndex); } - public static string ProcessNameToExe(this string processName, PlatformType runtime) + public static string ProcessNameToExe(this string processName) { - if (OsInfo.IsWindows || runtime != PlatformType.NetCore) + if (OsInfo.IsWindows) { processName += ".exe"; } @@ -276,11 +276,6 @@ namespace NzbDrone.Common.Extensions return processName; } - public static string ProcessNameToExe(this string processName) - { - return processName.ProcessNameToExe(PlatformInfo.Platform); - } - public static string GetAppDataPath(this IAppFolderInfo appFolderInfo) { return appFolderInfo.AppDataFolder; @@ -346,9 +341,9 @@ namespace NzbDrone.Common.Extensions return Path.Combine(GetUpdatePackageFolder(appFolderInfo), UPDATE_CLIENT_FOLDER_NAME); } - public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo, PlatformType runtime) + public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo) { - return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE_NAME).ProcessNameToExe(runtime); + return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE_NAME).ProcessNameToExe(); } public static string GetDatabase(this IAppFolderInfo appFolderInfo) diff --git a/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs b/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs index e2abfe8d6..7497fbfc8 100644 --- a/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs +++ b/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs @@ -69,7 +69,7 @@ namespace NzbDrone.Core.Test.UpdateTests .Returns(true); Mocker.GetMock() - .Setup(v => v.FileExists(It.Is(s => s.EndsWith("Lidarr.Update.exe")))) + .Setup(v => v.FileExists(It.Is(s => s.EndsWith("Lidarr.Update".ProcessNameToExe())))) .Returns(true); _sandboxFolder = Mocker.GetMock().Object.GetUpdateSandboxFolder(); @@ -165,7 +165,7 @@ namespace NzbDrone.Core.Test.UpdateTests public void should_return_with_warning_if_updater_doesnt_exists() { Mocker.GetMock() - .Setup(v => v.FileExists(It.Is(s => s.EndsWith("Lidarr.Update.exe")))) + .Setup(v => v.FileExists(It.Is(s => s.EndsWith("Lidarr.Update".ProcessNameToExe())))) .Returns(false); Subject.Execute(new ApplicationUpdateCommand()); diff --git a/src/NzbDrone.Core/HealthCheck/ServerSideNotificationService.cs b/src/NzbDrone.Core/HealthCheck/ServerSideNotificationService.cs index 0a40c1224..0be0557ba 100644 --- a/src/NzbDrone.Core/HealthCheck/ServerSideNotificationService.cs +++ b/src/NzbDrone.Core/HealthCheck/ServerSideNotificationService.cs @@ -52,7 +52,7 @@ namespace NzbDrone.Core.HealthCheck .AddQueryParam("version", BuildInfo.Version) .AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant()) .AddQueryParam("arch", RuntimeInformation.OSArchitecture) - .AddQueryParam("runtime", PlatformInfo.Platform.ToString().ToLowerInvariant()) + .AddQueryParam("runtime", "netcore") .AddQueryParam("branch", _configFileProvider.Branch) .Build(); try diff --git a/src/NzbDrone.Core/Update/InstallUpdateService.cs b/src/NzbDrone.Core/Update/InstallUpdateService.cs index 7c1cdd390..11e1193b0 100644 --- a/src/NzbDrone.Core/Update/InstallUpdateService.cs +++ b/src/NzbDrone.Core/Update/InstallUpdateService.cs @@ -146,7 +146,7 @@ namespace NzbDrone.Core.Update _logger.Info("Preparing client"); _diskTransferService.TransferFolder(_appFolderInfo.GetUpdateClientFolder(), updateSandboxFolder, TransferMode.Move); - var updateClientExePath = _appFolderInfo.GetUpdateClientExePath(updatePackage.Runtime); + var updateClientExePath = _appFolderInfo.GetUpdateClientExePath(); if (!_diskProvider.FileExists(updateClientExePath)) { @@ -155,7 +155,7 @@ namespace NzbDrone.Core.Update } // Set executable flag on update app - if (OsInfo.IsOsx || (OsInfo.IsLinux && PlatformInfo.IsNetCore)) + if (OsInfo.IsOsx || OsInfo.IsLinux) { _diskProvider.SetFilePermissions(updateClientExePath, "755", null); } diff --git a/src/NzbDrone.Core/Update/UpdatePackage.cs b/src/NzbDrone.Core/Update/UpdatePackage.cs index 016398d0f..ca97ad5d0 100644 --- a/src/NzbDrone.Core/Update/UpdatePackage.cs +++ b/src/NzbDrone.Core/Update/UpdatePackage.cs @@ -1,4 +1,4 @@ -using System; +using System; using NzbDrone.Common.EnvironmentInfo; namespace NzbDrone.Core.Update @@ -12,6 +12,5 @@ namespace NzbDrone.Core.Update public UpdateChanges Changes { get; set; } public string Hash { get; set; } public string Branch { get; set; } - public PlatformType Runtime { get; set; } } } diff --git a/src/NzbDrone.Core/Update/UpdatePackageProvider.cs b/src/NzbDrone.Core/Update/UpdatePackageProvider.cs index 36904007e..a2001b2da 100644 --- a/src/NzbDrone.Core/Update/UpdatePackageProvider.cs +++ b/src/NzbDrone.Core/Update/UpdatePackageProvider.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; +using FluentValidation.Validators; using NzbDrone.Common.Cloud; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Http; @@ -36,7 +37,7 @@ namespace NzbDrone.Core.Update .AddQueryParam("version", currentVersion) .AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant()) .AddQueryParam("arch", RuntimeInformation.OSArchitecture) - .AddQueryParam("runtime", PlatformInfo.Platform.ToString().ToLowerInvariant()) + .AddQueryParam("runtime", "netcore") .AddQueryParam("runtimeVer", _platformInfo.Version) .SetSegment("branch", branch); @@ -63,7 +64,7 @@ namespace NzbDrone.Core.Update .AddQueryParam("version", currentVersion) .AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant()) .AddQueryParam("arch", RuntimeInformation.OSArchitecture) - .AddQueryParam("runtime", PlatformInfo.Platform.ToString().ToLowerInvariant()) + .AddQueryParam("runtime", "netcore") .AddQueryParam("runtimeVer", _platformInfo.Version) .SetSegment("branch", branch); diff --git a/src/NzbDrone.Mono.Test/DiskProviderTests/FreeSpaceFixture.cs b/src/NzbDrone.Mono.Test/DiskProviderTests/FreeSpaceFixture.cs index f23673217..b1c518c90 100644 --- a/src/NzbDrone.Mono.Test/DiskProviderTests/FreeSpaceFixture.cs +++ b/src/NzbDrone.Mono.Test/DiskProviderTests/FreeSpaceFixture.cs @@ -19,6 +19,7 @@ namespace NzbDrone.Mono.Test.DiskProviderTests .Returns(s => s); } + [Ignore("Docker")] [Test] public void should_be_able_to_check_space_on_ramdrive() { diff --git a/src/NzbDrone.Mono/Disk/DiskProvider.cs b/src/NzbDrone.Mono/Disk/DiskProvider.cs index e8875ea09..b447662ec 100644 --- a/src/NzbDrone.Mono/Disk/DiskProvider.cs +++ b/src/NzbDrone.Mono/Disk/DiskProvider.cs @@ -263,9 +263,7 @@ namespace NzbDrone.Mono.Disk newFile.CreateSymbolicLinkTo(fullPath); } } - else if (((PlatformInfo.Platform == PlatformType.Mono && PlatformInfo.GetVersion() >= new Version(6, 0)) || - PlatformInfo.Platform == PlatformType.NetCore) && - (!FileExists(destination) || overwrite)) + else if (!FileExists(destination) || overwrite) { TransferFilePatched(source, destination, overwrite, false); } @@ -310,14 +308,9 @@ namespace NzbDrone.Mono.Disk throw; } } - else if ((PlatformInfo.Platform == PlatformType.Mono && PlatformInfo.GetVersion() >= new Version(6, 0)) || - PlatformInfo.Platform == PlatformType.NetCore) - { - TransferFilePatched(source, destination, false, true); - } else { - base.MoveFileInternal(source, destination); + TransferFilePatched(source, destination, false, true); } } @@ -329,7 +322,7 @@ namespace NzbDrone.Mono.Disk // Catch the exception and attempt to handle these edgecases // Mono 6.x till 6.10 doesn't properly try use rename first. - if (move && (PlatformInfo.Platform == PlatformType.NetCore)) + if (move) { if (Syscall.lstat(source, out var sourcestat) == 0 && Syscall.lstat(destination, out var deststat) != 0 && @@ -357,7 +350,7 @@ namespace NzbDrone.Mono.Disk var dstInfo = new FileInfo(destination); var exists = dstInfo.Exists && srcInfo.Exists; - if (PlatformInfo.Platform == PlatformType.NetCore && exists && dstInfo.Length == srcInfo.Length) + if (exists && dstInfo.Length == srcInfo.Length) { // mono 6.0, mono 6.4 and netcore 3.1 bug: full length file since utime and chmod happens at the end _logger.Debug("{3} failed to {2} file likely due to known {3} bug, attempting to {2} directly. '{0}' -> '{1}'", source, destination, move ? "move" : "copy", PlatformInfo.PlatformName); diff --git a/src/NzbDrone.Test.Common/TestBase.cs b/src/NzbDrone.Test.Common/TestBase.cs index 6768856d0..8a8d54828 100644 --- a/src/NzbDrone.Test.Common/TestBase.cs +++ b/src/NzbDrone.Test.Common/TestBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Threading; using FluentAssertions; diff --git a/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs b/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs index 7b78093c6..6d23fc968 100644 --- a/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs +++ b/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs @@ -126,7 +126,7 @@ namespace NzbDrone.Update.UpdateEngine _diskTransferService.MirrorFolder(_appFolderInfo.GetUpdatePackageFolder(), installationFolder); // Set executable flag on Lidarr app and bundled fpcalc - if (OsInfo.IsOsx || (OsInfo.IsLinux && PlatformInfo.IsNetCore)) + if (OsInfo.IsOsx || OsInfo.IsLinux) { _diskProvider.SetFilePermissions(Path.Combine(installationFolder, "Lidarr"), "755", null); _diskProvider.SetFilePermissions(Path.Combine(installationFolder, "fpcalc"), "755", null); From 8bb630c5db81c91f18d31f642c7de2e0960af9ea Mon Sep 17 00:00:00 2001 From: bakerboy448 <55419169+bakerboy448@users.noreply.github.com> Date: Wed, 13 Jul 2022 10:51:28 -0500 Subject: [PATCH 0046/1478] New: Use Notifiarr API New: Notifiarr Add Instance Name Support Fixed: Notifiarr - Better HTTP Error Handling also quiet sentry (cherry picked from commit 1db690ad39ec103c0f4dc89ac4545801ef95bec7) Fixed: Improve Notifiarr Exception Handling and Validation Errors (cherry picked from commit 6aaa024d71b939030950460ae986ada5bbae5ad7) --- .../CleanseLogMessageFixture.cs | 2 +- .../Instrumentation/CleanseLogMessage.cs | 2 +- .../Notifiarr/NotifiarrException.cs | 2 +- .../Notifications/Notifiarr/NotifiarrProxy.cs | 65 +++++++++++-------- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs b/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs index 2dbd0ce10..e9cec085f 100644 --- a/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs +++ b/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs @@ -85,7 +85,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests [TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")] // Webhooks - Notifiarr - [TestCase(@"https://xxx.yyy/api/v1/notification/lidarr/9pr04sg6-0123-3210-imav-eql2tyu8xyui")] + [TestCase(@"https://xxx.yyy/api/v1/notification/lidarr/mySecret")] // Discord [TestCase(@"https://discord.com/api/webhooks/mySecret")] diff --git a/src/NzbDrone.Common/Instrumentation/CleanseLogMessage.cs b/src/NzbDrone.Common/Instrumentation/CleanseLogMessage.cs index 30f44a50b..df0f49e1e 100644 --- a/src/NzbDrone.Common/Instrumentation/CleanseLogMessage.cs +++ b/src/NzbDrone.Common/Instrumentation/CleanseLogMessage.cs @@ -53,7 +53,7 @@ namespace NzbDrone.Common.Instrumentation // Webhooks // Notifiarr - new Regex(@"api/v[0-9]/notification/sonarr/(?[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase), + new Regex(@"api/v[0-9]/notification/lidarr/(?[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase), // Indexer Responses new Regex(@"avistaz\.[a-z]{2,3}\\\/rss\\\/download\\\/(?[^&=]+?)\\\/(?[^&=]+?)\.torrent", RegexOptions.Compiled | RegexOptions.IgnoreCase), diff --git a/src/NzbDrone.Core/Notifications/Notifiarr/NotifiarrException.cs b/src/NzbDrone.Core/Notifications/Notifiarr/NotifiarrException.cs index 283edda13..512a9179f 100644 --- a/src/NzbDrone.Core/Notifications/Notifiarr/NotifiarrException.cs +++ b/src/NzbDrone.Core/Notifications/Notifiarr/NotifiarrException.cs @@ -1,4 +1,4 @@ -using System; +using System; using NzbDrone.Common.Exceptions; namespace NzbDrone.Core.Notifications.Notifiarr diff --git a/src/NzbDrone.Core/Notifications/Notifiarr/NotifiarrProxy.cs b/src/NzbDrone.Core/Notifications/Notifiarr/NotifiarrProxy.cs index 6e7688ddd..89d1bad19 100644 --- a/src/NzbDrone.Core/Notifications/Notifiarr/NotifiarrProxy.cs +++ b/src/NzbDrone.Core/Notifications/Notifiarr/NotifiarrProxy.cs @@ -1,11 +1,10 @@ -using System; -using System.Collections.Generic; +using System; using System.Collections.Specialized; -using System.Net; using FluentValidation.Results; using NLog; -using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; namespace NzbDrone.Core.Notifications.Notifiarr { @@ -17,13 +16,15 @@ namespace NzbDrone.Core.Notifications.Notifiarr public class NotifiarrProxy : INotifiarrProxy { - private const string URL = "https://notifiarr.com/notifier.php"; + private const string URL = "https://notfiarr.com"; private readonly IHttpClient _httpClient; + private readonly IConfigFileProvider _configFileProvider; private readonly Logger _logger; - public NotifiarrProxy(IHttpClient httpClient, Logger logger) + public NotifiarrProxy(IHttpClient httpClient, IConfigFileProvider configFileProvider, Logger logger) { _httpClient = httpClient; + _configFileProvider = configFileProvider; _logger = logger; } @@ -35,8 +36,8 @@ namespace NzbDrone.Core.Notifications.Notifiarr } catch (NotifiarrException ex) { - _logger.Error(ex, "Unable to send notification"); - throw new NotifiarrException("Unable to send notification"); + _logger.Error(ex, ex.Message); + throw new NotifiarrException(ex.Message); } } @@ -50,21 +51,14 @@ namespace NzbDrone.Core.Notifications.Notifiarr SendNotification(variables, settings); return null; } - catch (HttpException ex) + catch (NotifiarrException ex) { - if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) - { - _logger.Error(ex, "API key is invalid: " + ex.Message); - return new ValidationFailure("APIKey", "API key is invalid"); - } - - _logger.Error(ex, "Unable to send test message: " + ex.Message); - return new ValidationFailure("APIKey", "Unable to send test notification"); + return new ValidationFailure("APIKey", ex.Message); } catch (Exception ex) { - _logger.Error(ex, "Unable to send test notification: " + ex.Message); - return new ValidationFailure("", "Unable to send test notification"); + _logger.Error(ex, ex.Message); + return new ValidationFailure("", "Unable to send test notification. Check the log for more details."); } } @@ -72,8 +66,9 @@ namespace NzbDrone.Core.Notifications.Notifiarr { try { - var requestBuilder = new HttpRequestBuilder(URL).Post(); - requestBuilder.AddFormParameter("api", settings.APIKey).Build(); + var instanceName = _configFileProvider.InstanceName; + var requestBuilder = new HttpRequestBuilder(URL + "/api/v1/notification/lidarr/" + settings.APIKey).Post(); + requestBuilder.AddFormParameter("instanceName", instanceName).Build(); foreach (string key in message.Keys) { @@ -86,13 +81,31 @@ namespace NzbDrone.Core.Notifications.Notifiarr } catch (HttpException ex) { - if (ex.Response.StatusCode == HttpStatusCode.BadRequest) + var responseCode = ex.Response.StatusCode; + switch ((int)responseCode) { - _logger.Error(ex, "API key is invalid"); - throw; + case 401: + _logger.Error("Unauthorized", "HTTP 401 - API key is invalid"); + throw new NotifiarrException("API key is invalid"); + case 400: + _logger.Error("Invalid Request", "HTTP 400 - Unable to send notification. Ensure Lidarr Integration is enabled & assigned a channel on Notifiarr"); + throw new NotifiarrException("Unable to send notification. Ensure Lidarr Integration is enabled & assigned a channel on Notifiarr"); + case 502: + case 503: + case 504: + _logger.Error("Service Unavailable", "Unable to send notification. Service Unavailable"); + throw new NotifiarrException("Unable to send notification. Service Unavailable", ex); + case 520: + case 521: + case 522: + case 523: + case 524: + _logger.Error(ex, "Cloudflare Related HTTP Error - Unable to send notification"); + throw new NotifiarrException("Cloudflare Related HTTP Error - Unable to send notification", ex); + default: + _logger.Error(ex, "Unknown HTTP Error - Unable to send notification"); + throw new NotifiarrException("Unknown HTTP Error - Unable to send notification", ex); } - - throw new NotifiarrException("Unable to send notification", ex); } } } From 970b68e808bd0899b69e6bb41e2e2b7379f6ab77 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 20 Nov 2020 14:43:04 -0800 Subject: [PATCH 0047/1478] New: Validate that naming formats don't contain illegal characters Fixes #1768 (cherry picked from commit 145c644c9d8f1636027da8a782a7e74f3182c678) --- .../Organizer/FileNameValidation.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/NzbDrone.Core/Organizer/FileNameValidation.cs b/src/NzbDrone.Core/Organizer/FileNameValidation.cs index e7d7235b9..ec54c73cf 100644 --- a/src/NzbDrone.Core/Organizer/FileNameValidation.cs +++ b/src/NzbDrone.Core/Organizer/FileNameValidation.cs @@ -1,6 +1,10 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Text.RegularExpressions; using FluentValidation; using FluentValidation.Validators; +using NzbDrone.Common.Extensions; namespace NzbDrone.Core.Organizer { @@ -12,18 +16,23 @@ namespace NzbDrone.Core.Organizer public static IRuleBuilderOptions ValidTrackFormat(this IRuleBuilder ruleBuilder) { ruleBuilder.SetValidator(new NotEmptyValidator(null)); + ruleBuilder.SetValidator(new IllegalCharactersValidator()); + return ruleBuilder.SetValidator(new ValidStandardTrackFormatValidator()); } public static IRuleBuilderOptions ValidArtistFolderFormat(this IRuleBuilder ruleBuilder) { ruleBuilder.SetValidator(new NotEmptyValidator(null)); + ruleBuilder.SetValidator(new IllegalCharactersValidator()); + return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.ArtistNameRegex)).WithMessage("Must contain Artist name"); } public static IRuleBuilderOptions ValidAlbumFolderFormat(this IRuleBuilder ruleBuilder) { ruleBuilder.SetValidator(new NotEmptyValidator(null)); + ruleBuilder.SetValidator(new IllegalCharactersValidator()); return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.AlbumTitleRegex)).WithMessage("Must contain Album title"); //.SetValidator(new RegularExpressionValidator(FileNameBuilder.ReleaseDateRegex)).WithMessage("Must contain Release year"); @@ -51,4 +60,42 @@ namespace NzbDrone.Core.Organizer return true; } } + + public class IllegalCharactersValidator : PropertyValidator + { + private readonly char[] _invalidPathChars = Path.GetInvalidPathChars(); + + public IllegalCharactersValidator() + : base("Contains illegal characters: {InvalidCharacters}") + { + + } + + protected override bool IsValid(PropertyValidatorContext context) + { + var value = context.PropertyValue as string; + var invalidCharacters = new List(); + + if (value.IsNullOrWhiteSpace()) + { + return true; + } + + foreach (var i in _invalidPathChars) + { + if (value.IndexOf(i) >= 0) + { + invalidCharacters.Add(i); + } + } + + if (invalidCharacters.Any()) + { + context.MessageFormatter.AppendArgument("InvalidCharacters", string.Join("", invalidCharacters)); + return false; + } + + return true; + } + } } From b0327163efc24040758921bf99eeb4489503179e Mon Sep 17 00:00:00 2001 From: Qstick Date: Tue, 25 Oct 2022 20:28:15 -0500 Subject: [PATCH 0048/1478] Fix Lint Issue --- src/NzbDrone.Core/Organizer/FileNameValidation.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/Organizer/FileNameValidation.cs b/src/NzbDrone.Core/Organizer/FileNameValidation.cs index ec54c73cf..6159fb8eb 100644 --- a/src/NzbDrone.Core/Organizer/FileNameValidation.cs +++ b/src/NzbDrone.Core/Organizer/FileNameValidation.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -68,7 +68,6 @@ namespace NzbDrone.Core.Organizer public IllegalCharactersValidator() : base("Contains illegal characters: {InvalidCharacters}") { - } protected override bool IsValid(PropertyValidatorContext context) From 41a072613c9ca0bf2b93e803d2c2d39c25fc53c3 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 26 Oct 2022 17:05:14 -0700 Subject: [PATCH 0049/1478] Fixed: Re-downloading of series images (cherry picked from commit 26eab7d821950f0c3b46ee53003636959cdb2872) --- src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs index c3d630a86..411b7e6d6 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs @@ -145,7 +145,7 @@ namespace NzbDrone.Common.Http.Dispatchers headers.Add(responseMessage.Content.Headers.ToNameValueCollection()); - return new HttpResponse(request, new HttpHeader(responseMessage.Headers), data, responseMessage.StatusCode); + return new HttpResponse(request, new HttpHeader(headers), data, responseMessage.StatusCode); } protected virtual System.Net.Http.HttpClient GetClient(HttpUri uri) From 62ae8ff1dc5e54f7d30887cb10bee4db255f7bc3 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 30 Oct 2022 19:02:27 -0500 Subject: [PATCH 0050/1478] Ignore brotli test on osx (cherry picked from commit 2afe6af5a6dac26fe9d2c20e806a93b621e18bbf) --- src/NzbDrone.Common.Test/Http/HttpClientFixture.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs index 5828c9fb2..6cd02333b 100644 --- a/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs +++ b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs @@ -213,6 +213,7 @@ namespace NzbDrone.Common.Test.Http } [Test] + [Platform(Exclude = "MacOsX", Reason = "Azure agent update prevents brotli on OSX")] public void should_execute_get_using_brotli() { var request = new HttpRequest($"https://{_httpBinHost}/brotli"); From a528c9e0081d2af13e7128feac716244b1b9f12a Mon Sep 17 00:00:00 2001 From: ta264 Date: Wed, 2 Nov 2022 21:19:30 +0000 Subject: [PATCH 0051/1478] Use wildcard for FreeBSD itemPattern --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6714d055d..01944fc6b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -641,7 +641,7 @@ stages: inputs: buildType: 'current' artifactName: Packages - itemPattern: '/$(pattern)' + itemPattern: '**/$(pattern)' targetPath: $(Build.ArtifactStagingDirectory) - bash: | mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin From b903f61360c9f5a22fc2bda46065d899ff674766 Mon Sep 17 00:00:00 2001 From: bakerboy448 <55419169+bakerboy448@users.noreply.github.com> Date: Thu, 3 Nov 2022 08:54:30 -0500 Subject: [PATCH 0052/1478] Fixed: Notifiarr - fix invalid/broken url (#3053) --- .../Notifications/Notifiarr/NotifiarrProxy.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/NzbDrone.Core/Notifications/Notifiarr/NotifiarrProxy.cs b/src/NzbDrone.Core/Notifications/Notifiarr/NotifiarrProxy.cs index 89d1bad19..41a1ed904 100644 --- a/src/NzbDrone.Core/Notifications/Notifiarr/NotifiarrProxy.cs +++ b/src/NzbDrone.Core/Notifications/Notifiarr/NotifiarrProxy.cs @@ -16,7 +16,7 @@ namespace NzbDrone.Core.Notifications.Notifiarr public class NotifiarrProxy : INotifiarrProxy { - private const string URL = "https://notfiarr.com"; + private const string URL = "https://notifiarr.com"; private readonly IHttpClient _httpClient; private readonly IConfigFileProvider _configFileProvider; private readonly Logger _logger; @@ -30,15 +30,7 @@ namespace NzbDrone.Core.Notifications.Notifiarr public void SendNotification(StringDictionary message, NotifiarrSettings settings) { - try - { ProcessNotification(message, settings); - } - catch (NotifiarrException ex) - { - _logger.Error(ex, ex.Message); - throw new NotifiarrException(ex.Message); - } } public ValidationFailure Test(NotifiarrSettings settings) @@ -101,9 +93,9 @@ namespace NzbDrone.Core.Notifications.Notifiarr case 523: case 524: _logger.Error(ex, "Cloudflare Related HTTP Error - Unable to send notification"); - throw new NotifiarrException("Cloudflare Related HTTP Error - Unable to send notification", ex); + throw new NotifiarrException("Cloudflare Related HTTP Error - Unable to send notification", ex); default: - _logger.Error(ex, "Unknown HTTP Error - Unable to send notification"); + _logger.Error(ex, "Unknown HTTP Error - Unable to send notification"); throw new NotifiarrException("Unknown HTTP Error - Unable to send notification", ex); } } From 940070eb9dab299263d7113c55622ab4b30b7d09 Mon Sep 17 00:00:00 2001 From: Servarr Date: Thu, 3 Nov 2022 14:00:35 +0000 Subject: [PATCH 0053/1478] Automated API Docs update --- src/Lidarr.Api.V1/openapi.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Lidarr.Api.V1/openapi.json b/src/Lidarr.Api.V1/openapi.json index fb099fb06..bb8fbc8b3 100644 --- a/src/Lidarr.Api.V1/openapi.json +++ b/src/Lidarr.Api.V1/openapi.json @@ -10300,6 +10300,10 @@ "type": "string", "nullable": true }, + "applicationUrl": { + "type": "string", + "nullable": true + }, "updateAutomatically": { "type": "boolean" }, From 7d8a4abc8e75c9230d8bba5186aabf2969c63d8c Mon Sep 17 00:00:00 2001 From: luz paz Date: Sun, 30 Oct 2022 17:53:02 -0400 Subject: [PATCH 0054/1478] Fix various typos Found via `codespell -q3 -S ./yarn.lock,./src/NzbDrone.Core.Test,./src/NzbDrone.Core/Localization -L bitap,tempdate,uptodate` --- frontend/src/Components/SignalRConnector.js | 2 +- .../AlbumRelease/SelectAlbumReleaseModalContent.js | 2 +- src/Lidarr.Api.V1/ProviderResource.cs | 2 +- src/NzbDrone.Common.Test/Http/HttpClientFixture.cs | 2 +- src/NzbDrone.Common/EnvironmentInfo/AppFolderFactory.cs | 2 +- .../Instrumentation/Sentry/SentryTarget.cs | 2 +- src/NzbDrone.Common/OAuth/OAuthTools.cs | 2 +- src/NzbDrone.Common/ServiceProvider.cs | 2 +- src/NzbDrone.Core/Datastore/ExpressionVisitor.cs | 2 +- .../DecisionEngine/DownloadDecisionComparer.cs | 2 +- .../Specifications/UpgradableSpecification.cs | 2 +- .../Download/Clients/Transmission/TransmissionProxy.cs | 2 +- .../Extras/Metadata/ExistingMetadataImporter.cs | 2 +- .../HealthCheck/Checks/DownloadClientRootFolderCheck.cs | 2 +- .../HealthCheck/Checks/RemotePathMappingCheck.cs | 8 ++++---- src/NzbDrone.Core/History/EntityHistoryService.cs | 2 +- .../MediaFiles/TrackImport/ImportApprovedTracks.cs | 2 +- .../Specifications/ArtistPathInRootFolderSpecification.cs | 2 +- src/NzbDrone.Core/Notifications/Mailgun/Mailgun.cs | 2 +- .../Notifications/MediaBrowser/MediaBrowserSettings.cs | 2 +- src/NzbDrone.Core/Notifications/Twitter/TwitterService.cs | 2 +- src/NzbDrone.Core/Parser/FingerprintingService.cs | 2 +- src/NzbDrone.Core/Parser/Parser.cs | 4 ++-- src/NzbDrone.Host/UtilityModeRouter.cs | 2 +- 24 files changed, 28 insertions(+), 28 deletions(-) diff --git a/frontend/src/Components/SignalRConnector.js b/frontend/src/Components/SignalRConnector.js index 86a4d7790..743583167 100644 --- a/frontend/src/Components/SignalRConnector.js +++ b/frontend/src/Components/SignalRConnector.js @@ -170,7 +170,7 @@ class SignalRConnector extends Component { const resource = body.resource; const status = resource.status; - // Both sucessful and failed commands need to be + // Both successful and failed commands need to be // completed, otherwise they spin until they timeout. if (status === 'completed' || status === 'failed') { diff --git a/frontend/src/InteractiveImport/AlbumRelease/SelectAlbumReleaseModalContent.js b/frontend/src/InteractiveImport/AlbumRelease/SelectAlbumReleaseModalContent.js index 2f83dd7f9..ae611d936 100644 --- a/frontend/src/InteractiveImport/AlbumRelease/SelectAlbumReleaseModalContent.js +++ b/frontend/src/InteractiveImport/AlbumRelease/SelectAlbumReleaseModalContent.js @@ -50,7 +50,7 @@ class SelectAlbumReleaseModalContent extends Component { scrollDirection={scrollDirections.NONE} > - Overrriding a release here will disable automatic release selection for that album in future. + Overriding a release here will disable automatic release selection for that album in future. diff --git a/src/Lidarr.Api.V1/ProviderResource.cs b/src/Lidarr.Api.V1/ProviderResource.cs index 217cc570e..444d64d53 100644 --- a/src/Lidarr.Api.V1/ProviderResource.cs +++ b/src/Lidarr.Api.V1/ProviderResource.cs @@ -38,7 +38,7 @@ namespace Lidarr.Api.V1 Tags = definition.Tags, Fields = SchemaBuilder.ToSchema(definition.Settings), - //lidarr/supported is an disambagation page. the # should be a header on the page with appropiate details/link + //lidarr/supported is an disambagation page. the # should be a header on the page with appropriate details/link InfoLink = string.Format("https://wiki.servarr.com/lidarr/supported#{0}", definition.Implementation.ToLower()) }; diff --git a/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs index 6cd02333b..1f2a18916 100644 --- a/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs +++ b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs @@ -46,7 +46,7 @@ namespace NzbDrone.Common.Test.Http // Use mirrors for tests that use two hosts var candidates = new[] { "httpbin1.servarr.com" }; - // httpbin.org is broken right now, occassionally redirecting to https if it's unavailable. + // httpbin.org is broken right now, occasionally redirecting to https if it's unavailable. _httpBinHost = mainHost; _httpBinHosts = candidates.Where(IsTestSiteAvailable).ToArray(); diff --git a/src/NzbDrone.Common/EnvironmentInfo/AppFolderFactory.cs b/src/NzbDrone.Common/EnvironmentInfo/AppFolderFactory.cs index a6135fd04..e750995bd 100644 --- a/src/NzbDrone.Common/EnvironmentInfo/AppFolderFactory.cs +++ b/src/NzbDrone.Common/EnvironmentInfo/AppFolderFactory.cs @@ -63,7 +63,7 @@ namespace NzbDrone.Common.EnvironmentInfo } catch (Exception ex) { - _logger.Warn(ex, "Coudn't set app folder permission"); + _logger.Warn(ex, "Couldn't set app folder permission"); } } diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs index f17eb467a..3327795e5 100644 --- a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs +++ b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs @@ -119,7 +119,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry _debounce = new SentryDebounce(); // initialize to true and reconfigure later - // Otherwise it will default to false and any errors occuring + // Otherwise it will default to false and any errors occurring // before config file gets read will not be filtered FilterEvents = true; SentryEnabled = true; diff --git a/src/NzbDrone.Common/OAuth/OAuthTools.cs b/src/NzbDrone.Common/OAuth/OAuthTools.cs index c2b14c519..fb012bd1a 100644 --- a/src/NzbDrone.Common/OAuth/OAuthTools.cs +++ b/src/NzbDrone.Common/OAuth/OAuthTools.cs @@ -260,7 +260,7 @@ namespace NzbDrone.Common.OAuth } /// - /// Creates a request elements concatentation value to send with a request. + /// Creates a request elements concatenation value to send with a request. /// This is also known as the signature base. /// /// diff --git a/src/NzbDrone.Common/ServiceProvider.cs b/src/NzbDrone.Common/ServiceProvider.cs index 4243c78c5..6c6c8db4f 100644 --- a/src/NzbDrone.Common/ServiceProvider.cs +++ b/src/NzbDrone.Common/ServiceProvider.cs @@ -217,7 +217,7 @@ namespace NzbDrone.Common if (dacls.Contains(authenticatedUsersDacl)) { - // Permssions already set + // Permissions already set return; } diff --git a/src/NzbDrone.Core/Datastore/ExpressionVisitor.cs b/src/NzbDrone.Core/Datastore/ExpressionVisitor.cs index bcb977af1..2605bbd9b 100644 --- a/src/NzbDrone.Core/Datastore/ExpressionVisitor.cs +++ b/src/NzbDrone.Core/Datastore/ExpressionVisitor.cs @@ -87,7 +87,7 @@ namespace NzbDrone.Core.Datastore } /// - /// Visits the memeber access expression. To be implemented by user. + /// Visits the member access expression. To be implemented by user. /// /// /// diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs index ca7663310..c310a3223 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs @@ -110,7 +110,7 @@ namespace NzbDrone.Core.DecisionEngine private int ComparePeersIfTorrent(DownloadDecision x, DownloadDecision y) { // Different protocols should get caught when checking the preferred protocol, - // since we're dealing with the same series in our comparisions + // since we're dealing with the same series in our comparisons if (x.RemoteAlbum.Release.DownloadProtocol != DownloadProtocol.Torrent || y.RemoteAlbum.Release.DownloadProtocol != DownloadProtocol.Torrent) { diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs index a471d1028..aa687be9a 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs @@ -52,7 +52,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return ProfileComparisonResult.Equal; } - // Quality Treated as Equal if Propers are not Prefered + // Quality Treated as Equal if Propers are not Preferred if (_configService.DownloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer && newQuality.Revision.CompareTo(currentQualities.Min(q => q.Revision)) > 0) { diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionProxy.cs b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionProxy.cs index b9ae4605a..2c2334f44 100644 --- a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionProxy.cs @@ -141,7 +141,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission private TransmissionResponse GetSessionVariables(TransmissionSettings settings) { - // Retrieve transmission information such as the default download directory, bandwith throttling and seed ratio. + // Retrieve transmission information such as the default download directory, bandwidth throttling and seed ratio. return ProcessRequest("session-get", null, settings); } diff --git a/src/NzbDrone.Core/Extras/Metadata/ExistingMetadataImporter.cs b/src/NzbDrone.Core/Extras/Metadata/ExistingMetadataImporter.cs index 61519dd46..d0c70d597 100644 --- a/src/NzbDrone.Core/Extras/Metadata/ExistingMetadataImporter.cs +++ b/src/NzbDrone.Core/Extras/Metadata/ExistingMetadataImporter.cs @@ -46,7 +46,7 @@ namespace NzbDrone.Core.Extras.Metadata foreach (var possibleMetadataFile in filterResult.FilesOnDisk) { - // Don't process files that have known Subtitle file extensions (saves a bit of unecessary processing) + // Don't process files that have known Subtitle file extensions (saves a bit of unnecessary processing) if (LyricFileExtensions.Extensions.Contains(Path.GetExtension(possibleMetadataFile))) { continue; diff --git a/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRootFolderCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRootFolderCheck.cs index f54237ef4..b0d4d4540 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRootFolderCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRootFolderCheck.cs @@ -57,7 +57,7 @@ namespace NzbDrone.Core.HealthCheck.Checks } catch (Exception ex) { - _logger.Error(ex, "Unknown error occured in DownloadClientRootFolderCheck HealthCheck"); + _logger.Error(ex, "Unknown error occurred in DownloadClientRootFolderCheck HealthCheck"); } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/RemotePathMappingCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/RemotePathMappingCheck.cs index 385d1e934..e245956d4 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/RemotePathMappingCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/RemotePathMappingCheck.cs @@ -99,7 +99,7 @@ namespace NzbDrone.Core.HealthCheck.Checks } catch (Exception ex) { - _logger.Error(ex, "Unknown error occured in RemotePathMapping HealthCheck"); + _logger.Error(ex, "Unknown error occurred in RemotePathMapping HealthCheck"); } } @@ -130,11 +130,11 @@ namespace NzbDrone.Core.HealthCheck.Checks { // If the file doesn't exist but TrackInfo is not null then the message is coming from // ImportApprovedTracks and the file must have been removed part way through processing - return new HealthCheck(GetType(), HealthCheckResult.Error, $"File {trackPath} was removed part way though procesing."); + return new HealthCheck(GetType(), HealthCheckResult.Error, $"File {trackPath} was removed part way through processing."); } } - // If the previous case did not match then the failure occured in DownloadedTracksImportService, + // If the previous case did not match then the failure occurred in DownloadedTracksImportService, // while trying to locate the files reported by the download client var client = _downloadClientProvider.Get(failureMessage.DownloadClientInfo.Id); try @@ -191,7 +191,7 @@ namespace NzbDrone.Core.HealthCheck.Checks } catch (Exception ex) { - _logger.Error(ex, "Unknown error occured in RemotePathMapping HealthCheck"); + _logger.Error(ex, "Unknown error occurred in RemotePathMapping HealthCheck"); } return new HealthCheck(GetType()); diff --git a/src/NzbDrone.Core/History/EntityHistoryService.cs b/src/NzbDrone.Core/History/EntityHistoryService.cs index 93c29fe8e..a50e296fb 100644 --- a/src/NzbDrone.Core/History/EntityHistoryService.cs +++ b/src/NzbDrone.Core/History/EntityHistoryService.cs @@ -100,7 +100,7 @@ namespace NzbDrone.Core.History var allHistory = _historyRepository.FindDownloadHistory(trackedDownload.TrackInfo.Artist.Id, trackedDownload.ImportedTrack.Quality); - //Find download related items for these episdoes + //Find download related items for these episodes var albumsHistory = allHistory.Where(h => albumIds.Contains(h.AlbumId)).ToList(); var processedDownloadId = albumsHistory diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs index 7b89b343b..9b769b66a 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs @@ -123,7 +123,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport album.AlbumReleases = _releaseService.SetMonitored(newRelease); // Publish album edited event. - // Deliberatly don't put in the old album since we don't want to trigger an ArtistScan. + // Deliberately don't put in the old album since we don't want to trigger an ArtistScan. _eventAggregator.PublishEvent(new AlbumEditedEvent(album, album)); } diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Specifications/ArtistPathInRootFolderSpecification.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Specifications/ArtistPathInRootFolderSpecification.cs index ed414dacc..7ca413c90 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/Specifications/ArtistPathInRootFolderSpecification.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Specifications/ArtistPathInRootFolderSpecification.cs @@ -25,7 +25,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Specifications // Prevent imports to artists that are no longer inside a root folder Lidarr manages var artist = item.AlbumRelease.Album.Value.Artist.Value; - // a new artist will have empty path, and will end up having path assinged based on file location + // a new artist will have empty path, and will end up having path assigned based on file location var pathToCheck = artist.Path.IsNotNullOrWhiteSpace() ? artist.Path : item.LocalTracks.First().Path.GetParentPath(); if (_rootFolderService.GetBestRootFolder(pathToCheck) == null) diff --git a/src/NzbDrone.Core/Notifications/Mailgun/Mailgun.cs b/src/NzbDrone.Core/Notifications/Mailgun/Mailgun.cs index 9723e2f0f..05c7513b7 100644 --- a/src/NzbDrone.Core/Notifications/Mailgun/Mailgun.cs +++ b/src/NzbDrone.Core/Notifications/Mailgun/Mailgun.cs @@ -49,7 +49,7 @@ namespace NzbDrone.Core.Notifications.Mailgun const string body = "This is a test message from Lidarr, though Mailgun."; _proxy.SendNotification(title, body, Settings); - _logger.Info("Successsfully sent email though Mailgun."); + _logger.Info("Successfully sent email though Mailgun."); } catch (Exception ex) { diff --git a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserSettings.cs b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserSettings.cs index ed92f97fd..9dd64d0cb 100644 --- a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserSettings.cs +++ b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserSettings.cs @@ -36,7 +36,7 @@ namespace NzbDrone.Core.Notifications.Emby [FieldDefinition(3, Label = "API Key", Privacy = PrivacyLevel.ApiKey)] public string ApiKey { get; set; } - [FieldDefinition(4, Label = "Send Notifications", HelpText = "Have MediaBrowser send notfications to configured providers", Type = FieldType.Checkbox)] + [FieldDefinition(4, Label = "Send Notifications", HelpText = "Have MediaBrowser send notifications to configured providers", Type = FieldType.Checkbox)] public bool Notify { get; set; } [FieldDefinition(5, Label = "Update Library", HelpText = "Update Library on Import & Rename?", Type = FieldType.Checkbox)] diff --git a/src/NzbDrone.Core/Notifications/Twitter/TwitterService.cs b/src/NzbDrone.Core/Notifications/Twitter/TwitterService.cs index e784b64a7..9e19ef059 100644 --- a/src/NzbDrone.Core/Notifications/Twitter/TwitterService.cs +++ b/src/NzbDrone.Core/Notifications/Twitter/TwitterService.cs @@ -77,7 +77,7 @@ namespace NzbDrone.Core.Notifications.Twitter using (var reader = new StreamReader(responseStream)) { var responseBody = reader.ReadToEnd(); - _logger.Trace("Reponse: {0} Status Code: {1}", responseBody, httpResponse.StatusCode); + _logger.Trace("Response: {0} Status Code: {1}", responseBody, httpResponse.StatusCode); throw new TwitterException("Error received from Twitter: " + responseBody, ex); } } diff --git a/src/NzbDrone.Core/Parser/FingerprintingService.cs b/src/NzbDrone.Core/Parser/FingerprintingService.cs index e6e89ff89..c9d66a7b7 100644 --- a/src/NzbDrone.Core/Parser/FingerprintingService.cs +++ b/src/NzbDrone.Core/Parser/FingerprintingService.cs @@ -68,7 +68,7 @@ namespace NzbDrone.Core.Parser } catch (Exception ex) { - _logger.Error(ex, "Somthing went wrong detecting fpcalc"); + _logger.Error(ex, "Something went wrong detecting fpcalc"); } } diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index 519ff5a8a..f2a9c9192 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -123,7 +123,7 @@ namespace NzbDrone.Core.Parser RegexOptions.IgnoreCase | RegexOptions.Compiled), //Artist - Year - Album - // Hypen with no or more spaces between artist/album/year + // Hyphen with no or more spaces between artist/album/year new Regex(@"^(?:(?.+?)(?:-))(?\d{4})(?:-)(?[^-]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled), }; @@ -617,7 +617,7 @@ namespace NzbDrone.Core.Parser var artistName = matchCollection[0].Groups["artist"].Value./*Removed for cases like Will.I.Am Replace('.', ' ').*/Replace('_', ' '); artistName = RequestInfoRegex.Replace(artistName, "").Trim(' '); - // Coppied from Radarr (https://github.com/Radarr/Radarr/blob/develop/src/NzbDrone.Core/Parser/Parser.cs) + // Copied from Radarr (https://github.com/Radarr/Radarr/blob/develop/src/NzbDrone.Core/Parser/Parser.cs) // TODO: Split into separate method and write unit tests for. var parts = artistName.Split('.'); artistName = ""; diff --git a/src/NzbDrone.Host/UtilityModeRouter.cs b/src/NzbDrone.Host/UtilityModeRouter.cs index 60135e2bd..a047032f3 100644 --- a/src/NzbDrone.Host/UtilityModeRouter.cs +++ b/src/NzbDrone.Host/UtilityModeRouter.cs @@ -77,7 +77,7 @@ namespace NzbDrone.Host case ApplicationModes.RegisterUrl: { - _logger.Debug("Regiser URL selected"); + _logger.Debug("Register URL selected"); _remoteAccessAdapter.MakeAccessible(false); break; From 651a4358fc833f781935bc4cfb2e141a93811867 Mon Sep 17 00:00:00 2001 From: Qstick Date: Thu, 3 Nov 2022 15:54:27 -0500 Subject: [PATCH 0055/1478] Create CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md | 132 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..52e67a0a4 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations From 000ccf78ada513462f9c31fb98f73f579d46c4d7 Mon Sep 17 00:00:00 2001 From: Qstick Date: Thu, 3 Nov 2022 22:28:55 -0500 Subject: [PATCH 0056/1478] Translation Updates Fixes #3058 Fixes #2889 --- .../src/Album/Edit/EditAlbumModalContent.js | 4 +- frontend/src/AlbumStudio/AlbumStudio.js | 4 +- .../src/Artist/Edit/EditArtistModalContent.js | 4 +- .../AudioTags/RetagArtistModalContent.js | 5 +- .../Editor/Delete/DeleteArtistModalContent.js | 7 +- .../Organize/OrganizeArtistModalContent.js | 7 +- .../Artist/Editor/Tags/TagsModalContent.js | 10 +- .../History/ArtistHistoryModalContent.js | 12 +- .../FileBrowser/FileBrowserModalContent.js | 8 +- .../Builder/FilterBuilderModalContent.js | 9 +- .../CustomFiltersModalContent.js | 3 +- .../src/Components/Form/FormInputGroup.js | 3 +- frontend/src/Components/Form/PlaylistInput.js | 3 +- .../src/Components/Menu/FilterMenuContent.js | 3 +- .../Components/Page/Sidebar/PageSidebar.js | 65 +++---- .../Album/SelectAlbumModalContent.js | 10 +- .../SelectAlbumReleaseModalContent.js | 9 +- .../Artist/SelectArtistModalContent.js | 4 +- .../Confirmation/ConfirmImportModalContent.js | 7 +- ...teractiveImportSelectFolderModalContent.js | 13 +- .../InteractiveImportModalContent.js | 43 ++--- .../Quality/SelectQualityModalContent.js | 6 +- .../Track/SelectTrackModalContent.js | 13 +- .../InteractiveSearch/InteractiveSearch.js | 27 +-- .../Organize/OrganizePreviewModalContent.js | 4 +- .../src/Retag/RetagPreviewModalContent.js | 6 +- .../src/Settings/AdvancedSettingsButton.js | 2 +- .../DownloadClients/AddDownloadClientItem.js | 7 +- .../EditDownloadClientModalContent.js | 8 +- .../EditRemotePathMappingModalContent.js | 8 +- .../RemotePathMappings/RemotePathMappings.js | 12 +- .../EditImportListExclusionModalContent.js | 8 +- .../ImportLists/AddImportListItem.js | 7 +- .../ImportLists/EditImportListModalContent.js | 22 +-- .../Indexers/Indexers/AddIndexerItem.js | 7 +- .../Indexers/EditIndexerModalContent.js | 10 +- .../MediaManagement/MediaManagement.js | 22 +-- .../RootFolder/EditRootFolderModalContent.js | 8 +- .../Metadata/EditMetadataModalContent.js | 6 +- .../Notifications/AddNotificationItem.js | 7 +- .../EditNotificationModalContent.js | 10 +- .../Settings/Profiles/Delay/DelayProfile.js | 4 +- .../Settings/Profiles/Delay/DelayProfiles.js | 16 +- .../Delay/EditDelayProfileModalContent.js | 55 +++--- .../EditDelayProfileModalContentConnector.js | 8 - .../EditMetadataProfileModalContent.js | 8 +- .../Quality/EditQualityProfileModalContent.js | 10 +- .../Release/EditReleaseProfileModalContent.js | 8 +- .../Quality/Definition/QualityDefinitions.js | 15 +- frontend/src/Settings/UI/UISettings.js | 10 +- frontend/src/Store/Actions/albumActions.js | 21 +-- .../src/Store/Actions/albumStudioActions.js | 15 +- frontend/src/Store/Actions/artistActions.js | 13 +- .../src/Store/Actions/artistEditorActions.js | 31 ++-- .../src/Store/Actions/artistIndexActions.js | 63 +++---- .../src/Store/Actions/blocklistActions.js | 13 +- frontend/src/Store/Actions/calendarActions.js | 5 +- frontend/src/Store/Actions/historyActions.js | 43 ++--- frontend/src/Store/Actions/queueActions.js | 29 +-- frontend/src/Store/Actions/releaseActions.js | 25 +-- frontend/src/Store/Actions/systemActions.js | 8 +- frontend/src/Store/Actions/trackActions.js | 17 +- .../src/Store/Actions/trackFileActions.js | 11 +- frontend/src/Store/Actions/wantedActions.js | 29 +-- frontend/src/System/Backup/BackupRow.js | 6 +- frontend/src/System/Backup/Backups.js | 6 +- .../Backup/RestoreBackupModalContent.js | 10 +- frontend/src/System/Logs/Files/LogFiles.js | 4 +- .../src/System/Status/DiskSpace/DiskSpace.js | 6 +- .../src/System/Status/Donations/Donations.js | 3 +- frontend/src/System/Status/Health/Health.js | 4 +- .../src/System/Tasks/Queued/QueuedTasks.js | 10 +- .../System/Tasks/Scheduled/ScheduledTasks.js | 10 +- .../Editor/TrackFileEditorModalContent.js | 8 +- src/NzbDrone.Core/Localization/Core/en.json | 169 +++++++++++++++++- 75 files changed, 665 insertions(+), 461 deletions(-) diff --git a/frontend/src/Album/Edit/EditAlbumModalContent.js b/frontend/src/Album/Edit/EditAlbumModalContent.js index d5a1482bd..ac3f53d5a 100644 --- a/frontend/src/Album/Edit/EditAlbumModalContent.js +++ b/frontend/src/Album/Edit/EditAlbumModalContent.js @@ -108,14 +108,14 @@ class EditAlbumModalContent extends Component { - Save + {translate('Save')} diff --git a/frontend/src/AlbumStudio/AlbumStudio.js b/frontend/src/AlbumStudio/AlbumStudio.js index d78af4771..58ae6e5fe 100644 --- a/frontend/src/AlbumStudio/AlbumStudio.js +++ b/frontend/src/AlbumStudio/AlbumStudio.js @@ -32,13 +32,13 @@ const columns = [ }, { name: 'sortName', - label: 'Name', + label: translate('Name'), isSortable: true, isVisible: true }, { name: 'albumCount', - label: 'Albums', + label: translate('Albums'), isSortable: false, isVisible: true } diff --git a/frontend/src/Artist/Edit/EditArtistModalContent.js b/frontend/src/Artist/Edit/EditArtistModalContent.js index 7205ee032..c919aec88 100644 --- a/frontend/src/Artist/Edit/EditArtistModalContent.js +++ b/frontend/src/Artist/Edit/EditArtistModalContent.js @@ -211,14 +211,14 @@ class EditArtistModalContent extends Component { - Save + {translate('Save')} diff --git a/frontend/src/Artist/Editor/AudioTags/RetagArtistModalContent.js b/frontend/src/Artist/Editor/AudioTags/RetagArtistModalContent.js index 96cfddf1b..7f632ad57 100644 --- a/frontend/src/Artist/Editor/AudioTags/RetagArtistModalContent.js +++ b/frontend/src/Artist/Editor/AudioTags/RetagArtistModalContent.js @@ -8,6 +8,7 @@ import ModalContent from 'Components/Modal/ModalContent'; import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import { icons, kinds } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import styles from './RetagArtistModalContent.css'; function RetagArtistModalContent(props) { @@ -50,14 +51,14 @@ function RetagArtistModalContent(props) { diff --git a/frontend/src/Artist/Editor/Delete/DeleteArtistModalContent.js b/frontend/src/Artist/Editor/Delete/DeleteArtistModalContent.js index 4083c3187..6e04fc7e6 100644 --- a/frontend/src/Artist/Editor/Delete/DeleteArtistModalContent.js +++ b/frontend/src/Artist/Editor/Delete/DeleteArtistModalContent.js @@ -9,6 +9,7 @@ import ModalContent from 'Components/Modal/ModalContent'; import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import { inputTypes, kinds } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import styles from './DeleteArtistModalContent.css'; class DeleteArtistModalContent extends Component { @@ -51,7 +52,7 @@ class DeleteArtistModalContent extends Component { return ( - Delete Selected Artist + {translate('DeleteArtist')} @@ -99,14 +100,14 @@ class DeleteArtistModalContent extends Component { diff --git a/frontend/src/Artist/Editor/Organize/OrganizeArtistModalContent.js b/frontend/src/Artist/Editor/Organize/OrganizeArtistModalContent.js index edbbed977..30a6929cd 100644 --- a/frontend/src/Artist/Editor/Organize/OrganizeArtistModalContent.js +++ b/frontend/src/Artist/Editor/Organize/OrganizeArtistModalContent.js @@ -8,6 +8,7 @@ import ModalContent from 'Components/Modal/ModalContent'; import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import { icons, kinds } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import styles from './OrganizeArtistModalContent.css'; function OrganizeArtistModalContent(props) { @@ -20,7 +21,7 @@ function OrganizeArtistModalContent(props) { return ( - Organize Selected Artist + {translate('OrganizeArtist')} @@ -51,14 +52,14 @@ function OrganizeArtistModalContent(props) { diff --git a/frontend/src/Artist/Editor/Tags/TagsModalContent.js b/frontend/src/Artist/Editor/Tags/TagsModalContent.js index 6e6da1ef1..29929ea4b 100644 --- a/frontend/src/Artist/Editor/Tags/TagsModalContent.js +++ b/frontend/src/Artist/Editor/Tags/TagsModalContent.js @@ -61,9 +61,9 @@ class TagsModalContent extends Component { } = this.state; const applyTagsOptions = [ - { key: 'add', value: 'Add' }, - { key: 'remove', value: 'Remove' }, - { key: 'replace', value: 'Replace' } + { key: 'add', value: translate('Add') }, + { key: 'remove', value: translate('Remove') }, + { key: 'replace', value: translate('Replace') } ]; return ( @@ -169,14 +169,14 @@ class TagsModalContent extends Component { diff --git a/frontend/src/Artist/History/ArtistHistoryModalContent.js b/frontend/src/Artist/History/ArtistHistoryModalContent.js index 5fb701817..5dd77ad9f 100644 --- a/frontend/src/Artist/History/ArtistHistoryModalContent.js +++ b/frontend/src/Artist/History/ArtistHistoryModalContent.js @@ -18,32 +18,32 @@ const columns = [ }, { name: 'album', - label: 'Album', + label: translate('Album'), isVisible: true }, { name: 'sourceTitle', - label: 'Source Title', + label: translate('SourceTitle'), isVisible: true }, { name: 'quality', - label: 'Quality', + label: translate('Quality'), isVisible: true }, { name: 'date', - label: 'Date', + label: translate('Date'), isVisible: true }, { name: 'details', - label: 'Details', + label: translate('Details'), isVisible: true }, { name: 'actions', - label: 'Actions', + label: translate('Actions'), isVisible: true } ]; diff --git a/frontend/src/Components/FileBrowser/FileBrowserModalContent.js b/frontend/src/Components/FileBrowser/FileBrowserModalContent.js index c6ea3458a..b00683045 100644 --- a/frontend/src/Components/FileBrowser/FileBrowserModalContent.js +++ b/frontend/src/Components/FileBrowser/FileBrowserModalContent.js @@ -21,12 +21,12 @@ import styles from './FileBrowserModalContent.css'; const columns = [ { name: 'type', - label: 'Type', + label: translate('Type'), isVisible: true }, { name: 'name', - label: 'Name', + label: translate('Name'), isVisible: true } ]; @@ -226,13 +226,13 @@ class FileBrowserModalContent extends Component { diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderModalContent.js b/frontend/src/Components/Filter/Builder/FilterBuilderModalContent.js index d718aab0c..d33f4d4fb 100644 --- a/frontend/src/Components/Filter/Builder/FilterBuilderModalContent.js +++ b/frontend/src/Components/Filter/Builder/FilterBuilderModalContent.js @@ -8,6 +8,7 @@ import ModalContent from 'Components/Modal/ModalContent'; import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import { inputTypes } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import FilterBuilderRow from './FilterBuilderRow'; import styles from './FilterBuilderModalContent.css'; @@ -151,7 +152,7 @@ class FilterBuilderModalContent extends Component {
- Label + {translate('Label')}
@@ -165,7 +166,7 @@ class FilterBuilderModalContent extends Component {
-
Filters
+
{translate('Filters')}
{ @@ -192,7 +193,7 @@ class FilterBuilderModalContent extends Component { - Save + {translate('Save')} diff --git a/frontend/src/Components/Filter/CustomFilters/CustomFiltersModalContent.js b/frontend/src/Components/Filter/CustomFilters/CustomFiltersModalContent.js index 840c676c8..116bd3e8b 100644 --- a/frontend/src/Components/Filter/CustomFilters/CustomFiltersModalContent.js +++ b/frontend/src/Components/Filter/CustomFilters/CustomFiltersModalContent.js @@ -5,6 +5,7 @@ import ModalBody from 'Components/Modal/ModalBody'; import ModalContent from 'Components/Modal/ModalContent'; import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; +import translate from 'Utilities/String/translate'; import CustomFilter from './CustomFilter'; import styles from './CustomFiltersModalContent.css'; @@ -24,7 +25,7 @@ function CustomFiltersModalContent(props) { return ( - Custom Filters + {translate('CustomFilters')} diff --git a/frontend/src/Components/Form/FormInputGroup.js b/frontend/src/Components/Form/FormInputGroup.js index 827b346c2..e1d4ec07c 100644 --- a/frontend/src/Components/Form/FormInputGroup.js +++ b/frontend/src/Components/Form/FormInputGroup.js @@ -2,6 +2,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import Link from 'Components/Link/Link'; import { inputTypes } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import AlbumReleaseSelectInputConnector from './AlbumReleaseSelectInputConnector'; import AutoCompleteInput from './AutoCompleteInput'; import CaptchaInputConnector from './CaptchaInputConnector'; @@ -227,7 +228,7 @@ function FormInputGroup(props) { - More Info + {translate('MoreInfo')} } diff --git a/frontend/src/Components/Form/PlaylistInput.js b/frontend/src/Components/Form/PlaylistInput.js index 906236e4f..8891dc794 100644 --- a/frontend/src/Components/Form/PlaylistInput.js +++ b/frontend/src/Components/Form/PlaylistInput.js @@ -8,6 +8,7 @@ import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; import TableRow from 'Components/Table/TableRow'; import tagShape from 'Helpers/Props/Shapes/tagShape'; +import translate from 'Utilities/String/translate'; import getSelectedIds from 'Utilities/Table/getSelectedIds'; import selectAll from 'Utilities/Table/selectAll'; import toggleSelected from 'Utilities/Table/toggleSelected'; @@ -16,7 +17,7 @@ import styles from './PlaylistInput.css'; const columns = [ { name: 'name', - label: 'Playlist', + label: translate('Playlist'), isSortable: false, isVisible: true } diff --git a/frontend/src/Components/Menu/FilterMenuContent.js b/frontend/src/Components/Menu/FilterMenuContent.js index 09313cc55..1fdb2476f 100644 --- a/frontend/src/Components/Menu/FilterMenuContent.js +++ b/frontend/src/Components/Menu/FilterMenuContent.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import translate from 'Utilities/String/translate'; import FilterMenuItem from './FilterMenuItem'; import MenuContent from './MenuContent'; import MenuItem from './MenuItem'; @@ -61,7 +62,7 @@ class FilterMenuContent extends Component { { showCustomFilters && - Custom Filters + {translate('CustomFilters')} } diff --git a/frontend/src/Components/Page/Sidebar/PageSidebar.js b/frontend/src/Components/Page/Sidebar/PageSidebar.js index f027c5cdc..1387b3c99 100644 --- a/frontend/src/Components/Page/Sidebar/PageSidebar.js +++ b/frontend/src/Components/Page/Sidebar/PageSidebar.js @@ -10,6 +10,7 @@ import { icons } from 'Helpers/Props'; import locationShape from 'Helpers/Props/Shapes/locationShape'; import dimensions from 'Styles/Variables/dimensions'; import HealthStatusConnector from 'System/Status/Health/HealthStatusConnector'; +import translate from 'Utilities/String/translate'; import MessagesConnector from './Messages/MessagesConnector'; import PageSidebarItem from './PageSidebarItem'; import styles from './PageSidebar.css'; @@ -20,24 +21,24 @@ const SIDEBAR_WIDTH = parseInt(dimensions.sidebarWidth); const links = [ { iconName: icons.ARTIST_CONTINUING, - title: 'Library', + title: translate('Library'), to: '/', alias: '/artist', children: [ { - title: 'Add New', + title: translate('AddNew'), to: '/add/search' }, { - title: 'Mass Editor', + title: translate('MassEditor'), to: '/artisteditor' }, { - title: 'Album Studio', + title: translate('AlbumStudio'), to: '/albumstudio' }, { - title: 'Unmapped Files', + title: translate('UnmappedFiles'), to: '/unmapped' } ] @@ -45,26 +46,26 @@ const links = [ { iconName: icons.CALENDAR, - title: 'Calendar', + title: translate('Calendar'), to: '/calendar' }, { iconName: icons.ACTIVITY, - title: 'Activity', + title: translate('Activity'), to: '/activity/queue', children: [ { - title: 'Queue', + title: translate('Queue'), to: '/activity/queue', statusComponent: QueueStatusConnector }, { - title: 'History', + title: translate('History'), to: '/activity/history' }, { - title: 'Blocklist', + title: translate('Blocklist'), to: '/activity/blocklist' } ] @@ -72,15 +73,15 @@ const links = [ { iconName: icons.WARNING, - title: 'Wanted', + title: translate('Wanted'), to: '/wanted/missing', children: [ { - title: 'Missing', + title: translate('Missing'), to: '/wanted/missing' }, { - title: 'Cutoff Unmet', + title: translate('CutoffUnmet'), to: '/wanted/cutoffunmet' } ] @@ -88,51 +89,51 @@ const links = [ { iconName: icons.SETTINGS, - title: 'Settings', + title: translate('Settings'), to: '/settings', children: [ { - title: 'Media Management', + title: translate('MediaManagement'), to: '/settings/mediamanagement' }, { - title: 'Profiles', + title: translate('Profiles'), to: '/settings/profiles' }, { - title: 'Quality', + title: translate('Quality'), to: '/settings/quality' }, { - title: 'Indexers', + title: translate('Indexers'), to: '/settings/indexers' }, { - title: 'Download Clients', + title: translate('DownloadClients'), to: '/settings/downloadclients' }, { - title: 'Import Lists', + title: translate('ImportLists'), to: '/settings/importlists' }, { - title: 'Connect', + title: translate('Connect'), to: '/settings/connect' }, { - title: 'Metadata', + title: translate('Metadata'), to: '/settings/metadata' }, { - title: 'Tags', + title: translate('Tags'), to: '/settings/tags' }, { - title: 'General', + title: translate('General'), to: '/settings/general' }, { - title: 'UI', + title: translate('UI'), to: '/settings/ui' } ] @@ -140,32 +141,32 @@ const links = [ { iconName: icons.SYSTEM, - title: 'System', + title: translate('System'), to: '/system/status', children: [ { - title: 'Status', + title: translate('Status'), to: '/system/status', statusComponent: HealthStatusConnector }, { - title: 'Tasks', + title: translate('Tasks'), to: '/system/tasks' }, { - title: 'Backup', + title: translate('Backup'), to: '/system/backup' }, { - title: 'Updates', + title: translate('Updates'), to: '/system/updates' }, { - title: 'Events', + title: translate('Events'), to: '/system/events' }, { - title: 'Log Files', + title: translate('LogFiles'), to: '/system/logs/files' } ] diff --git a/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js b/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js index 0cc95e0c8..33c318557 100644 --- a/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js +++ b/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js @@ -18,22 +18,22 @@ import styles from './SelectAlbumModalContent.css'; const columns = [ { name: 'title', - label: 'Album Title', + label: translate('AlbumTitle'), isVisible: true }, { name: 'albumType', - label: 'Album Type', + label: translate('AlbumType'), isVisible: true }, { name: 'releaseDate', - label: 'Release Date', + label: translate('ReleaseDate'), isVisible: true }, { name: 'status', - label: 'Album Status', + label: translate('AlbumStatus'), isVisible: true } ]; @@ -124,7 +124,7 @@ class SelectAlbumModalContent extends Component { diff --git a/frontend/src/InteractiveImport/AlbumRelease/SelectAlbumReleaseModalContent.js b/frontend/src/InteractiveImport/AlbumRelease/SelectAlbumReleaseModalContent.js index ae611d936..5c59b360b 100644 --- a/frontend/src/InteractiveImport/AlbumRelease/SelectAlbumReleaseModalContent.js +++ b/frontend/src/InteractiveImport/AlbumRelease/SelectAlbumReleaseModalContent.js @@ -10,18 +10,19 @@ import Scroller from 'Components/Scroller/Scroller'; import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; import { scrollDirections } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import SelectAlbumReleaseRow from './SelectAlbumReleaseRow'; import styles from './SelectAlbumReleaseModalContent.css'; const columns = [ { name: 'album', - label: 'Album', + label: translate('Album'), isVisible: true }, { name: 'release', - label: 'Album Release', + label: translate('AlbumRelease'), isVisible: true } ]; @@ -42,7 +43,7 @@ class SelectAlbumReleaseModalContent extends Component { return ( - Manual Import - Select Album Release + {translate('ManualImport')} - {translate('SelectAlbumRelease')} diff --git a/frontend/src/InteractiveImport/Artist/SelectArtistModalContent.js b/frontend/src/InteractiveImport/Artist/SelectArtistModalContent.js index f61f77f7b..981cda21e 100644 --- a/frontend/src/InteractiveImport/Artist/SelectArtistModalContent.js +++ b/frontend/src/InteractiveImport/Artist/SelectArtistModalContent.js @@ -47,7 +47,7 @@ class SelectArtistModalContent extends Component { return ( - Manual Import - Select Artist + {translate('ManualImport')} - {translate('SelectArtist')} diff --git a/frontend/src/InteractiveImport/Confirmation/ConfirmImportModalContent.js b/frontend/src/InteractiveImport/Confirmation/ConfirmImportModalContent.js index fa69d8cac..491841714 100644 --- a/frontend/src/InteractiveImport/Confirmation/ConfirmImportModalContent.js +++ b/frontend/src/InteractiveImport/Confirmation/ConfirmImportModalContent.js @@ -9,6 +9,7 @@ import ModalContent from 'Components/Modal/ModalContent'; import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import { kinds } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; function formatAlbumFiles(items, album) { @@ -73,7 +74,7 @@ class ConfirmImportModalContent extends Component { { !isFetching && isPopulated && - Are you sure? + {translate('AreYouSure')} } @@ -105,14 +106,14 @@ class ConfirmImportModalContent extends Component { !isFetching && isPopulated && diff --git a/frontend/src/InteractiveImport/Folder/InteractiveImportSelectFolderModalContent.js b/frontend/src/InteractiveImport/Folder/InteractiveImportSelectFolderModalContent.js index e3ad5e0e8..f30c3cb3d 100644 --- a/frontend/src/InteractiveImport/Folder/InteractiveImportSelectFolderModalContent.js +++ b/frontend/src/InteractiveImport/Folder/InteractiveImportSelectFolderModalContent.js @@ -10,17 +10,18 @@ import ModalHeader from 'Components/Modal/ModalHeader'; import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; import { icons, kinds, sizes } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import RecentFolderRow from './RecentFolderRow'; import styles from './InteractiveImportSelectFolderModalContent.css'; const recentFoldersColumns = [ { name: 'folder', - label: 'Folder' + label: translate('Folder') }, { name: 'lastUsed', - label: 'Last Used' + label: translate('LastUsed') }, { name: 'actions', @@ -75,7 +76,7 @@ class InteractiveImportSelectFolderModalContent extends Component { return ( - Manual Import - Select Folder + {translate('ManualImport')} - {translate('SelectFolder')} @@ -124,7 +125,7 @@ class InteractiveImportSelectFolderModalContent extends Component { name={icons.QUICK} /> - Move Automatically + {translate('MoveAutomatically')}
@@ -141,7 +142,7 @@ class InteractiveImportSelectFolderModalContent extends Component { name={icons.INTERACTIVE} /> - Interactive Import + {translate('InteractiveImport')} @@ -149,7 +150,7 @@ class InteractiveImportSelectFolderModalContent extends Component { diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js index cb6c7b62f..be555d969 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js @@ -22,6 +22,7 @@ import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal'; import ConfirmImportModal from 'InteractiveImport/Confirmation/ConfirmImportModal'; import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal'; import getErrorMessage from 'Utilities/Object/getErrorMessage'; +import translate from 'Utilities/String/translate'; import getSelectedIds from 'Utilities/Table/getSelectedIds'; import selectAll from 'Utilities/Table/selectAll'; import toggleSelected from 'Utilities/Table/toggleSelected'; @@ -31,35 +32,35 @@ import styles from './InteractiveImportModalContent.css'; const columns = [ { name: 'path', - label: 'Path', + label: translate('Path'), isSortable: true, isVisible: true }, { name: 'artist', - label: 'Artist', + label: translate('Artist'), isSortable: true, isVisible: true }, { name: 'album', - label: 'Album', + label: translate('Album'), isVisible: true }, { name: 'tracks', - label: 'Track(s)', + label: translate('Tracks'), isVisible: true }, { name: 'quality', - label: 'Quality', + label: translate('Quality'), isSortable: true, isVisible: true }, { name: 'size', - label: 'Size', + label: translate('Size'), isSortable: true, isVisible: true }, @@ -80,8 +81,8 @@ const filterExistingFilesOptions = { }; const importModeOptions = [ - { key: 'move', value: 'Move Files' }, - { key: 'copy', value: 'Hardlink/Copy Files' } + { key: 'move', value: translate('MoveFiles') }, + { key: 'copy', value: translate('HardlinkCopyFiles') } ]; const SELECT = 'select'; @@ -288,23 +289,23 @@ class InteractiveImportModalContent extends Component { const errorMessage = getErrorMessage(error, 'Unable to load manual import items'); const bulkSelectOptions = [ - { key: SELECT, value: 'Select...', disabled: true }, - { key: ALBUM, value: 'Select Album' }, - { key: ALBUM_RELEASE, value: 'Select Album Release' }, - { key: QUALITY, value: 'Select Quality' } + { key: SELECT, value: translate('Select...'), disabled: true }, + { key: ALBUM, value: translate('SelectAlbum') }, + { key: ALBUM_RELEASE, value: translate('SelectAlbumRelease') }, + { key: QUALITY, value: translate('SelectQuality') } ]; if (allowArtistChange) { bulkSelectOptions.splice(1, 0, { key: ARTIST, - value: 'Select Artist' + value: translate('SelectArtist') }); } return ( - Manual Import - {title || folder} + {translate('ManualImport')} - {title || folder} @@ -320,7 +321,7 @@ class InteractiveImportModalContent extends Component {
{ - filterExistingFiles ? 'Unmapped Files Only' : 'All Files' + filterExistingFiles ? translate('UnmappedFilesOnly') : translate('AllFiles') }
@@ -331,7 +332,7 @@ class InteractiveImportModalContent extends Component { isSelected={!filterExistingFiles} onPress={this.onFilterExistingFilesChange} > - All Files + {translate('AllFiles')} - Unmapped Files Only + {translate('UnmappedFilesOnly')} @@ -366,7 +367,7 @@ class InteractiveImportModalContent extends Component { isSelected={!replaceExistingFiles} onPress={this.onReplaceExistingFilesChange} > - Combine With Existing Files + {translate('CombineWithExistingFiles')} - Replace Existing Files + {translate('ReplaceExistingFiles')} @@ -474,7 +475,7 @@ class InteractiveImportModalContent extends Component {
{ @@ -487,7 +488,7 @@ class InteractiveImportModalContent extends Component { isDisabled={!selectedIds.length || !!invalidRowsSelected.length || inconsistentAlbumReleases} onPress={this.onImportSelectedPress} > - Import + {translate('Import')}
diff --git a/frontend/src/InteractiveImport/Quality/SelectQualityModalContent.js b/frontend/src/InteractiveImport/Quality/SelectQualityModalContent.js index 6b57735a6..e7abd1184 100644 --- a/frontend/src/InteractiveImport/Quality/SelectQualityModalContent.js +++ b/frontend/src/InteractiveImport/Quality/SelectQualityModalContent.js @@ -81,7 +81,7 @@ class SelectQualityModalContent extends Component { return ( - Manual Import - Select Quality + {translate('ManualImport')} - {translate('SelectQuality')} @@ -145,14 +145,14 @@ class SelectQualityModalContent extends Component { diff --git a/frontend/src/InteractiveImport/Track/SelectTrackModalContent.js b/frontend/src/InteractiveImport/Track/SelectTrackModalContent.js index 482e9024a..ea70de52c 100644 --- a/frontend/src/InteractiveImport/Track/SelectTrackModalContent.js +++ b/frontend/src/InteractiveImport/Track/SelectTrackModalContent.js @@ -12,6 +12,7 @@ import TableBody from 'Components/Table/TableBody'; import { kinds } from 'Helpers/Props'; import ExpandingFileDetails from 'TrackFile/ExpandingFileDetails'; import getErrorMessage from 'Utilities/Object/getErrorMessage'; +import translate from 'Utilities/String/translate'; import getSelectedIds from 'Utilities/Table/getSelectedIds'; import selectAll from 'Utilities/Table/selectAll'; import toggleSelected from 'Utilities/Table/toggleSelected'; @@ -20,7 +21,7 @@ import SelectTrackRow from './SelectTrackRow'; const columns = [ { name: 'mediumNumber', - label: 'Medium', + label: translate('Medium'), isSortable: true, isVisible: true }, @@ -32,12 +33,12 @@ const columns = [ }, { name: 'title', - label: 'Title', + label: translate('Title'), isVisible: true }, { name: 'trackStatus', - label: 'Status', + label: translate('Status'), isVisible: true } ]; @@ -137,7 +138,7 @@ class SelectTrackModalContent extends Component { return ( - Manual Import - Select Track(s): + {translate('ManualImport')} - {translate('SelectTracks')} @@ -201,14 +202,14 @@ class SelectTrackModalContent extends Component { diff --git a/frontend/src/InteractiveSearch/InteractiveSearch.js b/frontend/src/InteractiveSearch/InteractiveSearch.js index 719a781bc..c91bdf6f8 100644 --- a/frontend/src/InteractiveSearch/InteractiveSearch.js +++ b/frontend/src/InteractiveSearch/InteractiveSearch.js @@ -7,6 +7,7 @@ import PageMenuButton from 'Components/Menu/PageMenuButton'; import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; import { align, icons, sortDirections } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector'; import InteractiveSearchRow from './InteractiveSearchRow'; import styles from './InteractiveSearch.css'; @@ -14,43 +15,43 @@ import styles from './InteractiveSearch.css'; const columns = [ { name: 'protocol', - label: 'Source', + label: translate('Source'), isSortable: true, isVisible: true }, { name: 'age', - label: 'Age', + label: translate('Age'), isSortable: true, isVisible: true }, { name: 'title', - label: 'Title', + label: translate('Title'), isSortable: true, isVisible: true }, { name: 'indexer', - label: 'Indexer', + label: translate('Indexer'), isSortable: true, isVisible: true }, { name: 'size', - label: 'Size', + label: translate('Size'), isSortable: true, isVisible: true }, { name: 'peers', - label: 'Peers', + label: translate('Peers'), isSortable: true, isVisible: true }, { name: 'qualityWeight', - label: 'Quality', + label: translate('Quality'), isSortable: true, isVisible: true }, @@ -58,7 +59,7 @@ const columns = [ name: 'preferredWordScore', label: React.createElement(Icon, { name: icons.SCORE, - title: 'Preferred word score' + title: translate('PreferredWordScore') }), isSortable: true, isVisible: true @@ -67,7 +68,7 @@ const columns = [ name: 'rejections', label: React.createElement(Icon, { name: icons.DANGER, - title: 'Rejections' + title: translate('rejections') }), isSortable: true, fixedSortDirection: sortDirections.ASCENDING, @@ -125,7 +126,7 @@ function InteractiveSearch(props) { { !isFetching && error ?
- Unable to load results for this album search. Try again later + {translate('UnableToLoadInteractiveSearch')}
: null } @@ -133,7 +134,7 @@ function InteractiveSearch(props) { { !isFetching && isPopulated && !totalReleasesCount ?
- No results found + {translate('NoResults')}
: null } @@ -141,7 +142,7 @@ function InteractiveSearch(props) { { !!totalReleasesCount && isPopulated && !items.length ?
- All results are hidden by the applied filter + {translate('AllResultsFiltered')}
: null } @@ -177,7 +178,7 @@ function InteractiveSearch(props) { { totalReleasesCount !== items.length && !!items.length ?
- Some results are hidden by the applied filter + {translate('SomeResultsFiltered')}
: null } diff --git a/frontend/src/Organize/OrganizePreviewModalContent.js b/frontend/src/Organize/OrganizePreviewModalContent.js index d714b6cc8..61e24d9fa 100644 --- a/frontend/src/Organize/OrganizePreviewModalContent.js +++ b/frontend/src/Organize/OrganizePreviewModalContent.js @@ -160,14 +160,14 @@ class OrganizePreviewModalContent extends Component {
diff --git a/frontend/src/Retag/RetagPreviewModalContent.js b/frontend/src/Retag/RetagPreviewModalContent.js index ef73eed77..06b209260 100644 --- a/frontend/src/Retag/RetagPreviewModalContent.js +++ b/frontend/src/Retag/RetagPreviewModalContent.js @@ -89,7 +89,7 @@ class RetagPreviewModalContent extends Component { return ( - Write Metadata Tags + {translate('WriteMetadataTags')} @@ -156,14 +156,14 @@ class RetagPreviewModalContent extends Component { diff --git a/frontend/src/Settings/AdvancedSettingsButton.js b/frontend/src/Settings/AdvancedSettingsButton.js index cc7be45a5..d786bad50 100644 --- a/frontend/src/Settings/AdvancedSettingsButton.js +++ b/frontend/src/Settings/AdvancedSettingsButton.js @@ -45,7 +45,7 @@ function AdvancedSettingsButton(props) {
- {advancedSettings ? 'Hide Advanced' : 'Show Advanced'} + {advancedSettings ? translate('HideAdvanced') : translate('ShowAdvanced')}
diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/AddDownloadClientItem.js b/frontend/src/Settings/DownloadClients/DownloadClients/AddDownloadClientItem.js index 2abf039dc..0b1113022 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/AddDownloadClientItem.js +++ b/frontend/src/Settings/DownloadClients/DownloadClients/AddDownloadClientItem.js @@ -5,6 +5,7 @@ import Link from 'Components/Link/Link'; import Menu from 'Components/Menu/Menu'; import MenuContent from 'Components/Menu/MenuContent'; import { sizes } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import AddDownloadClientPresetMenuItem from './AddDownloadClientPresetMenuItem'; import styles from './AddDownloadClientItem.css'; @@ -57,7 +58,7 @@ class AddDownloadClientItem extends Component { size={sizes.SMALL} onPress={this.onDownloadClientSelect} > - Custom + {translate('Custom')} @@ -65,7 +66,7 @@ class AddDownloadClientItem extends Component { className={styles.presetsMenuButton} size={sizes.SMALL} > - Presets + {translate('Presets')} @@ -90,7 +91,7 @@ class AddDownloadClientItem extends Component { to={infoLink} size={sizes.SMALL} > - More info + {translate('MoreInfo')} diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js b/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js index 99ba8788e..4fed8bc4a 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js +++ b/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js @@ -189,7 +189,7 @@ class EditDownloadClientModalContent extends Component { kind={kinds.DANGER} onPress={onDeleteDownloadClientPress} > - Delete + {translate('Delete')} } @@ -198,13 +198,13 @@ class EditDownloadClientModalContent extends Component { error={saveError} onPress={onTestPress} > - Test + {translate('Test')} - Save + {translate('Save')} diff --git a/frontend/src/Settings/DownloadClients/RemotePathMappings/EditRemotePathMappingModalContent.js b/frontend/src/Settings/DownloadClients/RemotePathMappings/EditRemotePathMappingModalContent.js index bc2a08740..3c07a22fa 100644 --- a/frontend/src/Settings/DownloadClients/RemotePathMappings/EditRemotePathMappingModalContent.js +++ b/frontend/src/Settings/DownloadClients/RemotePathMappings/EditRemotePathMappingModalContent.js @@ -41,7 +41,7 @@ function EditRemotePathMappingModalContent(props) { return ( - {id ? 'Edit Remote Path Mapping' : 'Add Remote Path Mapping'} + {id ? translate('EditRemotePathMapping') : translate('AddRemotePathMapping')} @@ -114,14 +114,14 @@ function EditRemotePathMappingModalContent(props) { kind={kinds.DANGER} onPress={onDeleteRemotePathMappingPress} > - Delete + {translate('Delete')} } - Save + {translate('Save')} diff --git a/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMappings.js b/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMappings.js index 0e9635af4..3795917ba 100644 --- a/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMappings.js +++ b/frontend/src/Settings/DownloadClients/RemotePathMappings/RemotePathMappings.js @@ -51,9 +51,15 @@ class RemotePathMappings extends Component { {...otherProps} >
-
Host
-
Remote Path
-
Local Path
+
+ {translate('Host')} +
+
+ {translate('RemotePath')} +
+
+ {translate('LocalPath')} +
diff --git a/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.js b/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.js index fe38bfb67..f61da304f 100644 --- a/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.js +++ b/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.js @@ -39,7 +39,7 @@ function EditImportListExclusionModalContent(props) { return ( - {id ? 'Edit Import List Exclusion' : 'Add Import List Exclusion'} + {id ? translate('EditImportListExclusion') : translate('AddImportListExclusion')} @@ -99,14 +99,14 @@ function EditImportListExclusionModalContent(props) { kind={kinds.DANGER} onPress={onDeleteImportListExclusionPress} > - Delete + {translate('Delete')} } - Save + {translate('Save')} diff --git a/frontend/src/Settings/ImportLists/ImportLists/AddImportListItem.js b/frontend/src/Settings/ImportLists/ImportLists/AddImportListItem.js index a7f9e8603..40a10f657 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/AddImportListItem.js +++ b/frontend/src/Settings/ImportLists/ImportLists/AddImportListItem.js @@ -5,6 +5,7 @@ import Link from 'Components/Link/Link'; import Menu from 'Components/Menu/Menu'; import MenuContent from 'Components/Menu/MenuContent'; import { sizes } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import AddImportListPresetMenuItem from './AddImportListPresetMenuItem'; import styles from './AddImportListItem.css'; @@ -57,7 +58,7 @@ class AddImportListItem extends Component { size={sizes.SMALL} onPress={this.onListSelect} > - Custom + {translate('Custom')} @@ -65,7 +66,7 @@ class AddImportListItem extends Component { className={styles.presetsMenuButton} size={sizes.SMALL} > - Presets + {translate('Presets')} @@ -90,7 +91,7 @@ class AddImportListItem extends Component { to={infoLink} size={sizes.SMALL} > - More info + {translate('MoreInfo')}
diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js index c30e9f42a..e65c68b27 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js @@ -28,17 +28,17 @@ function ImportListMonitoringOptionsPopoverContent() { ); @@ -47,9 +47,9 @@ function ImportListMonitoringOptionsPopoverContent() { function EditImportListModalContent(props) { const monitorOptions = [ - { key: 'none', value: 'None' }, - { key: 'specificAlbum', value: 'Specific Album' }, - { key: 'entireArtist', value: 'All Artist Albums' } + { key: 'none', value: translate('None') }, + { key: 'specificAlbum', value: translate('SpecificAlbum') }, + { key: 'entireArtist', value: translate('All Artist Albums') } ]; const { @@ -89,7 +89,7 @@ function EditImportListModalContent(props) { return ( - {id ? 'Edit List' : 'Add List'} + {id ? translate('EditList') : translate('AddList')} @@ -318,7 +318,7 @@ function EditImportListModalContent(props) { kind={kinds.DANGER} onPress={onDeleteImportListPress} > - Delete + {translate('Delete')} } @@ -327,13 +327,13 @@ function EditImportListModalContent(props) { error={saveError} onPress={onTestPress} > - Test + {translate('Test')} - Save + {translate('Save')} diff --git a/frontend/src/Settings/Indexers/Indexers/AddIndexerItem.js b/frontend/src/Settings/Indexers/Indexers/AddIndexerItem.js index 9138114f1..58b7ce50d 100644 --- a/frontend/src/Settings/Indexers/Indexers/AddIndexerItem.js +++ b/frontend/src/Settings/Indexers/Indexers/AddIndexerItem.js @@ -5,6 +5,7 @@ import Link from 'Components/Link/Link'; import Menu from 'Components/Menu/Menu'; import MenuContent from 'Components/Menu/MenuContent'; import { sizes } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import AddIndexerPresetMenuItem from './AddIndexerPresetMenuItem'; import styles from './AddIndexerItem.css'; @@ -57,7 +58,7 @@ class AddIndexerItem extends Component { size={sizes.SMALL} onPress={this.onIndexerSelect} > - Custom + {translate('Custom')} @@ -65,7 +66,7 @@ class AddIndexerItem extends Component { className={styles.presetsMenuButton} size={sizes.SMALL} > - Presets + {translate('Presets')} @@ -90,7 +91,7 @@ class AddIndexerItem extends Component { to={infoLink} size={sizes.SMALL} > - More info + {translate('MoreInfo')} diff --git a/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js b/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js index 3ddd0223c..3baaa72f2 100644 --- a/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js +++ b/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js @@ -53,7 +53,7 @@ function EditIndexerModalContent(props) { return ( - {`${id ? 'Edit' : 'Add'} Indexer - ${implementationName}`} + {`${id ? translate('EditIndexer') : translate('AddIndexer')} - ${implementationName}`} @@ -204,7 +204,7 @@ function EditIndexerModalContent(props) { kind={kinds.DANGER} onPress={onDeleteIndexerPress} > - Delete + {translate('Delete')} } @@ -213,13 +213,13 @@ function EditIndexerModalContent(props) { error={saveError} onPress={onTestPress} > - Test + {translate('Test')} - Save + {translate('Save')} diff --git a/frontend/src/Settings/MediaManagement/MediaManagement.js b/frontend/src/Settings/MediaManagement/MediaManagement.js index cc1321976..37c0bb9b6 100644 --- a/frontend/src/Settings/MediaManagement/MediaManagement.js +++ b/frontend/src/Settings/MediaManagement/MediaManagement.js @@ -15,26 +15,26 @@ import NamingConnector from './Naming/NamingConnector'; import RootFoldersConnector from './RootFolder/RootFoldersConnector'; const rescanAfterRefreshOptions = [ - { key: 'always', value: 'Always' }, - { key: 'afterManual', value: 'After Manual Refresh' }, - { key: 'never', value: 'Never' } + { key: 'always', value: translate('Always') }, + { key: 'afterManual', value: translate('AfterManualRefresh') }, + { key: 'never', value: translate('Never') } ]; const allowFingerprintingOptions = [ - { key: 'allFiles', value: 'Always' }, - { key: 'newFiles', value: 'For new imports only' }, - { key: 'never', value: 'Never' } + { key: 'allFiles', value: translate('Always') }, + { key: 'newFiles', value: translate('ForNewImportsOnly') }, + { key: 'never', value: translate('Never') } ]; const downloadPropersAndRepacksOptions = [ - { key: 'preferAndUpgrade', value: 'Prefer and Upgrade' }, - { key: 'doNotUpgrade', value: 'Do not Upgrade Automatically' }, - { key: 'doNotPrefer', value: 'Do not Prefer' } + { key: 'preferAndUpgrade', value: translate('PreferAndUpgrade') }, + { key: 'doNotUpgrade', value: translate('DoNotUpgradeAutomatically') }, + { key: 'doNotPrefer', value: translate('DoNotPrefer') } ]; const fileDateOptions = [ - { key: 'none', value: 'None' }, - { key: 'albumReleaseDate', value: 'Album Release Date' } + { key: 'none', value: translate('None') }, + { key: 'albumReleaseDate', value: translate('AlbumReleaseDate') } ]; class MediaManagement extends Component { diff --git a/frontend/src/Settings/MediaManagement/RootFolder/EditRootFolderModalContent.js b/frontend/src/Settings/MediaManagement/RootFolder/EditRootFolderModalContent.js index e68f03459..3aab11085 100644 --- a/frontend/src/Settings/MediaManagement/RootFolder/EditRootFolderModalContent.js +++ b/frontend/src/Settings/MediaManagement/RootFolder/EditRootFolderModalContent.js @@ -51,7 +51,7 @@ function EditRootFolderModalContent(props) { return ( - {id ? 'Edit Root Folder' : 'Add Root Folder'} + {id ? translate('EditRootFolder') : translate('AddRootFolder')} @@ -216,14 +216,14 @@ function EditRootFolderModalContent(props) { kind={kinds.DANGER} onPress={onDeleteRootFolderPress} > - Delete + {translate('Delete')} } - Save + {translate('Save')} diff --git a/frontend/src/Settings/Metadata/Metadata/EditMetadataModalContent.js b/frontend/src/Settings/Metadata/Metadata/EditMetadataModalContent.js index 4a1699975..812478d23 100644 --- a/frontend/src/Settings/Metadata/Metadata/EditMetadataModalContent.js +++ b/frontend/src/Settings/Metadata/Metadata/EditMetadataModalContent.js @@ -35,7 +35,7 @@ function EditMetadataModalContent(props) { return ( - Edit {name.value} Metadata + {translate('EditMetadata')} - {name.value} @@ -75,7 +75,7 @@ function EditMetadataModalContent(props) { - Save + {translate('Save')} diff --git a/frontend/src/Settings/Notifications/Notifications/AddNotificationItem.js b/frontend/src/Settings/Notifications/Notifications/AddNotificationItem.js index 19fda3281..4cf8a6d57 100644 --- a/frontend/src/Settings/Notifications/Notifications/AddNotificationItem.js +++ b/frontend/src/Settings/Notifications/Notifications/AddNotificationItem.js @@ -5,6 +5,7 @@ import Link from 'Components/Link/Link'; import Menu from 'Components/Menu/Menu'; import MenuContent from 'Components/Menu/MenuContent'; import { sizes } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import AddNotificationPresetMenuItem from './AddNotificationPresetMenuItem'; import styles from './AddNotificationItem.css'; @@ -57,7 +58,7 @@ class AddNotificationItem extends Component { size={sizes.SMALL} onPress={this.onNotificationSelect} > - Custom + {translate('Custom')} @@ -65,7 +66,7 @@ class AddNotificationItem extends Component { className={styles.presetsMenuButton} size={sizes.SMALL} > - Presets + {translate('Presets')} @@ -90,7 +91,7 @@ class AddNotificationItem extends Component { to={infoLink} size={sizes.SMALL} > - More info + {translate('MoreInfo')} diff --git a/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js b/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js index 45708a731..cc3977661 100644 --- a/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js +++ b/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js @@ -48,7 +48,7 @@ function EditNotificationModalContent(props) { return ( - {`${id ? 'Edit' : 'Add'} Connection - ${implementationName}`} + {`${id ? translate('EditConnection') : translate('AddConnection')} - ${implementationName}`} @@ -136,7 +136,7 @@ function EditNotificationModalContent(props) { kind={kinds.DANGER} onPress={onDeleteNotificationPress} > - Delete + {translate('Delete')} } @@ -145,13 +145,13 @@ function EditNotificationModalContent(props) { error={saveError} onPress={onTestPress} > - Test + {translate('Test')} - Save + {translate('Save')} diff --git a/frontend/src/Settings/Profiles/Delay/DelayProfile.js b/frontend/src/Settings/Profiles/Delay/DelayProfile.js index 4e2473599..c0d5912bc 100644 --- a/frontend/src/Settings/Profiles/Delay/DelayProfile.js +++ b/frontend/src/Settings/Profiles/Delay/DelayProfile.js @@ -88,9 +88,9 @@ class DelayProfile extends Component { let preferred = titleCase(preferredProtocol); if (!enableUsenet) { - preferred = 'Only Torrent'; + preferred = translate('OnlyTorrent'); } else if (!enableTorrent) { - preferred = 'Only Usenet'; + preferred = translate('OnlyUsenet'); } return ( diff --git a/frontend/src/Settings/Profiles/Delay/DelayProfiles.js b/frontend/src/Settings/Profiles/Delay/DelayProfiles.js index ed77a223f..83ac09bf8 100644 --- a/frontend/src/Settings/Profiles/Delay/DelayProfiles.js +++ b/frontend/src/Settings/Profiles/Delay/DelayProfiles.js @@ -82,10 +82,18 @@ class DelayProfiles extends Component { >
-
Protocol
-
Usenet Delay
-
Torrent Delay
-
Tags
+
+ {translate('PreferredProtocol')} +
+
+ {translate('UsenetDelay')} +
+
+ {translate('TorrentDelay')} +
+
+ {translate('Tags')} +
diff --git a/frontend/src/Settings/Profiles/Delay/EditDelayProfileModalContent.js b/frontend/src/Settings/Profiles/Delay/EditDelayProfileModalContent.js index 6868b4f31..b31e7ce5c 100644 --- a/frontend/src/Settings/Profiles/Delay/EditDelayProfileModalContent.js +++ b/frontend/src/Settings/Profiles/Delay/EditDelayProfileModalContent.js @@ -17,6 +17,13 @@ import { boolSettingShape, numberSettingShape, tagSettingShape } from 'Helpers/P import translate from 'Utilities/String/translate'; import styles from './EditDelayProfileModalContent.css'; +const protocolOptions = [ + { key: 'preferUsenet', value: translate('PreferUsenet') }, + { key: 'preferTorrent', value: translate('PreferTorrent') }, + { key: 'onlyUsenet', value: translate('OnlyUsenet') }, + { key: 'onlyTorrent', value: translate('OnlyTorrent') } +]; + function EditDelayProfileModalContent(props) { const { id, @@ -26,7 +33,6 @@ function EditDelayProfileModalContent(props) { saveError, item, protocol, - protocolOptions, onInputChange, onProtocolChange, onSavePress, @@ -46,29 +52,29 @@ function EditDelayProfileModalContent(props) { return ( - {id ? 'Edit Delay Profile' : 'Add Delay Profile'} + {id ? translate('EditDelayProfile') : translate('AddDelayProfile')} { - isFetching && - + isFetching ? + : + null } { - !isFetching && !!error && + !isFetching && !!error ?
{translate('UnableToAddANewQualityProfilePleaseTryAgain')} -
+
: + null } { - !isFetching && !error && + !isFetching && !error ?
- - {translate('Protocol')} - + {translate('PreferredProtocol')} - - {translate('UsenetDelay')} - + {translate('UsenetDelay')} - - {translate('TorrentDelay')} - + {translate('TorrentDelay')} - This is the default profile. It applies to all artist that don't have an explicit profile. + {translate('DefaultDelayProfileHelpText')} : - - {translate('Tags')} - + {translate('Tags')} } - + : + null } { - id && id > 1 && + id && id > 1 ? + {translate('Delete')} + : + null } - Save + {translate('Save')} @@ -186,7 +188,6 @@ EditDelayProfileModalContent.propTypes = { saveError: PropTypes.object, item: PropTypes.shape(delayProfileShape).isRequired, protocol: PropTypes.string.isRequired, - protocolOptions: PropTypes.arrayOf(PropTypes.object).isRequired, onInputChange: PropTypes.func.isRequired, onProtocolChange: PropTypes.func.isRequired, onSavePress: PropTypes.func.isRequired, diff --git a/frontend/src/Settings/Profiles/Delay/EditDelayProfileModalContentConnector.js b/frontend/src/Settings/Profiles/Delay/EditDelayProfileModalContentConnector.js index 65b700747..7606cb347 100644 --- a/frontend/src/Settings/Profiles/Delay/EditDelayProfileModalContentConnector.js +++ b/frontend/src/Settings/Profiles/Delay/EditDelayProfileModalContentConnector.js @@ -16,13 +16,6 @@ const newDelayProfile = { tags: [] }; -const protocolOptions = [ - { key: 'preferUsenet', value: 'Prefer Usenet' }, - { key: 'preferTorrent', value: 'Prefer Torrent' }, - { key: 'onlyUsenet', value: 'Only Usenet' }, - { key: 'onlyTorrent', value: 'Only Torrent' } -]; - function createDelayProfileSelector() { return createSelector( (state, { id }) => id, @@ -78,7 +71,6 @@ function createMapStateToProps() { return { protocol, - protocolOptions, ...delayProfile }; } diff --git a/frontend/src/Settings/Profiles/Metadata/EditMetadataProfileModalContent.js b/frontend/src/Settings/Profiles/Metadata/EditMetadataProfileModalContent.js index eda520bea..c6b9c4464 100644 --- a/frontend/src/Settings/Profiles/Metadata/EditMetadataProfileModalContent.js +++ b/frontend/src/Settings/Profiles/Metadata/EditMetadataProfileModalContent.js @@ -46,7 +46,7 @@ function EditMetadataProfileModalContent(props) { return ( - {id ? 'Edit Metadata Profile' : 'Add Metadata Profile'} + {id ? translate('EditMetadataProfile') : translate('AddMetadataProfile')} @@ -117,7 +117,7 @@ function EditMetadataProfileModalContent(props) { isDisabled={isInUse} onPress={onDeleteMetadataProfilePress} > - Delete + {translate('Delete')}
} @@ -125,7 +125,7 @@ function EditMetadataProfileModalContent(props) { - Save + {translate('Save')} diff --git a/frontend/src/Settings/Profiles/Quality/EditQualityProfileModalContent.js b/frontend/src/Settings/Profiles/Quality/EditQualityProfileModalContent.js index edd41c55a..b275792e8 100644 --- a/frontend/src/Settings/Profiles/Quality/EditQualityProfileModalContent.js +++ b/frontend/src/Settings/Profiles/Quality/EditQualityProfileModalContent.js @@ -119,7 +119,7 @@ class EditQualityProfileModalContent extends Component { onMeasure={this.onHeaderMeasure} > - {id ? 'Edit Quality Profile' : 'Add Quality Profile'} + {id ? translate('EditQualityProfile') : translate('AddQualityProfile')} @@ -161,7 +161,7 @@ class EditQualityProfileModalContent extends Component { - Upgrades Allowed + {translate('UpgradesAllowed')} - Delete + {translate('Delete')} } @@ -234,7 +234,7 @@ class EditQualityProfileModalContent extends Component { - Save + {translate('Save')} diff --git a/frontend/src/Settings/Profiles/Release/EditReleaseProfileModalContent.js b/frontend/src/Settings/Profiles/Release/EditReleaseProfileModalContent.js index 486a707ff..200a65cde 100644 --- a/frontend/src/Settings/Profiles/Release/EditReleaseProfileModalContent.js +++ b/frontend/src/Settings/Profiles/Release/EditReleaseProfileModalContent.js @@ -43,7 +43,7 @@ function EditReleaseProfileModalContent(props) { return ( - {id ? 'Edit Release Profile' : 'Add Release Profile'} + {id ? translate('EditReleaseProfile') : translate('AddReleaseProfile')} @@ -170,14 +170,14 @@ function EditReleaseProfileModalContent(props) { kind={kinds.DANGER} onPress={onDeleteReleaseProfilePress} > - Delete + {translate('Delete')} } - Save + {translate('Save')} diff --git a/frontend/src/Settings/Quality/Definition/QualityDefinitions.js b/frontend/src/Settings/Quality/Definition/QualityDefinitions.js index e7303679d..392b9e1a7 100644 --- a/frontend/src/Settings/Quality/Definition/QualityDefinitions.js +++ b/frontend/src/Settings/Quality/Definition/QualityDefinitions.js @@ -25,9 +25,16 @@ class QualityDefinitions extends Component { {...otherProps} >
-
Quality
-
Title
-
Size Limit
+
+ {translate('Quality')} +
+
+ {translate('Title')} +
+
+ {translate('SizeLimit')} +
+ { advancedSettings ?
@@ -53,7 +60,7 @@ class QualityDefinitions extends Component {
- Limits are automatically adjusted for the album duration. + {translate('QualityLimitsHelpText')}
diff --git a/frontend/src/Settings/UI/UISettings.js b/frontend/src/Settings/UI/UISettings.js index 3bfc84e06..614ae659b 100644 --- a/frontend/src/Settings/UI/UISettings.js +++ b/frontend/src/Settings/UI/UISettings.js @@ -187,23 +187,19 @@ class UISettings extends Component { legend={translate('Style')} > - Theme + {translate('Theme')} - - {translate('EnableColorImpairedMode')} - - - Enable Color-Impaired Mode + {translate('EnableColorImpairedMode')} { @@ -324,12 +325,12 @@ export const defaultState = { }, { name: 'ratings', - label: 'Rating', + label: translate('Rating'), type: filterBuilderTypes.NUMBER }, { name: 'tags', - label: 'Tags', + label: translate('Tags'), type: filterBuilderTypes.ARRAY, valueType: filterBuilderValueTypes.TAG } diff --git a/frontend/src/Store/Actions/blocklistActions.js b/frontend/src/Store/Actions/blocklistActions.js index efc74640c..b529f10c6 100644 --- a/frontend/src/Store/Actions/blocklistActions.js +++ b/frontend/src/Store/Actions/blocklistActions.js @@ -4,6 +4,7 @@ import { sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers'; +import translate from 'Utilities/String/translate'; import { set, updateItem } from './baseActions'; import createHandleActions from './Creators/createHandleActions'; import createRemoveItemHandler from './Creators/createRemoveItemHandler'; @@ -32,36 +33,36 @@ export const defaultState = { columns: [ { name: 'artists.sortName', - label: 'Artist Name', + label: translate('ArtistName'), isSortable: true, isVisible: true }, { name: 'sourceTitle', - label: 'Source Title', + label: translate('SourceTitle'), isSortable: true, isVisible: true }, { name: 'quality', - label: 'Quality', + label: translate('Quality'), isVisible: true }, { name: 'date', - label: 'Date', + label: translate('Date'), isSortable: true, isVisible: true }, { name: 'indexer', - label: 'Indexer', + label: translate('Indexer'), isSortable: true, isVisible: false }, { name: 'actions', - columnLabel: 'Actions', + columnLabel: translate('Actions'), isVisible: true, isModifiable: false } diff --git a/frontend/src/Store/Actions/calendarActions.js b/frontend/src/Store/Actions/calendarActions.js index 8da7f44be..b7215ea76 100644 --- a/frontend/src/Store/Actions/calendarActions.js +++ b/frontend/src/Store/Actions/calendarActions.js @@ -7,6 +7,7 @@ import * as commandNames from 'Commands/commandNames'; import { filterTypes } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; +import translate from 'Utilities/String/translate'; import { set, update } from './baseActions'; import { executeCommandHelper } from './commandActions'; import createHandleActions from './Creators/createHandleActions'; @@ -50,7 +51,7 @@ export const defaultState = { filters: [ { key: 'all', - label: 'All', + label: translate('All'), filters: [ { key: 'monitored', @@ -61,7 +62,7 @@ export const defaultState = { }, { key: 'monitored', - label: 'Monitored Only', + label: translate('MonitoredOnly'), filters: [ { key: 'monitored', diff --git a/frontend/src/Store/Actions/historyActions.js b/frontend/src/Store/Actions/historyActions.js index aba7b5345..7bfdb5843 100644 --- a/frontend/src/Store/Actions/historyActions.js +++ b/frontend/src/Store/Actions/historyActions.js @@ -3,6 +3,7 @@ import { filterTypes, sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers'; +import translate from 'Utilities/String/translate'; import { updateItem } from './baseActions'; import createHandleActions from './Creators/createHandleActions'; import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers'; @@ -29,61 +30,61 @@ export const defaultState = { columns: [ { name: 'eventType', - columnLabel: 'Event Type', + columnLabel: translate('EventType'), isVisible: true, isModifiable: false }, { name: 'artists.sortName', - label: 'Artist', + label: translate('Artist'), isSortable: true, isVisible: true }, { name: 'albums.title', - label: 'Album Title', + label: translate('AlbumTitle'), isSortable: true, isVisible: true }, { name: 'trackTitle', - label: 'Track Title', + label: translate('TrackTitle'), isVisible: true }, { name: 'quality', - label: 'Quality', + label: translate('Quality'), isVisible: true }, { name: 'date', - label: 'Date', + label: translate('Date'), isSortable: true, isVisible: true }, { name: 'downloadClient', - label: 'Download Client', + label: translate('DownloadClient'), isVisible: false }, { name: 'indexer', - label: 'Indexer', + label: translate('Indexer'), isVisible: false }, { name: 'releaseGroup', - label: 'Release Group', + label: translate('ReleaseGroup'), isVisible: false }, { name: 'sourceTitle', - label: 'Source Title', + label: translate('SourceTitle'), isVisible: false }, { name: 'details', - columnLabel: 'Details', + columnLabel: translate('Details'), isVisible: true, isModifiable: false } @@ -94,12 +95,12 @@ export const defaultState = { filters: [ { key: 'all', - label: 'All', + label: translate('All'), filters: [] }, { key: 'grabbed', - label: 'Grabbed', + label: translate('Grabbed'), filters: [ { key: 'eventType', @@ -110,7 +111,7 @@ export const defaultState = { }, { key: 'trackFileImported', - label: 'Track Imported', + label: translate('TrackImported'), filters: [ { key: 'eventType', @@ -121,7 +122,7 @@ export const defaultState = { }, { key: 'failed', - label: 'Download Failed', + label: translate('DownloadFailed'), filters: [ { key: 'eventType', @@ -132,7 +133,7 @@ export const defaultState = { }, { key: 'importFailed', - label: 'Import Failed', + label: translate('ImportFailed'), filters: [ { key: 'eventType', @@ -143,7 +144,7 @@ export const defaultState = { }, { key: 'downloadImported', - label: 'Download Imported', + label: translate('DownloadImported'), filters: [ { key: 'eventType', @@ -154,7 +155,7 @@ export const defaultState = { }, { key: 'deleted', - label: 'Deleted', + label: translate('Deleted'), filters: [ { key: 'eventType', @@ -165,7 +166,7 @@ export const defaultState = { }, { key: 'renamed', - label: 'Renamed', + label: translate('Renamed'), filters: [ { key: 'eventType', @@ -176,7 +177,7 @@ export const defaultState = { }, { key: 'retagged', - label: 'Retagged', + label: translate('Retagged'), filters: [ { key: 'eventType', @@ -187,7 +188,7 @@ export const defaultState = { }, { key: 'ignored', - label: 'Ignored', + label: translate('Ignored'), filters: [ { key: 'eventType', diff --git a/frontend/src/Store/Actions/queueActions.js b/frontend/src/Store/Actions/queueActions.js index 581427959..4449f24d4 100644 --- a/frontend/src/Store/Actions/queueActions.js +++ b/frontend/src/Store/Actions/queueActions.js @@ -5,6 +5,7 @@ import { sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers'; +import translate from 'Utilities/String/translate'; import { set, updateItem } from './baseActions'; import createFetchHandler from './Creators/createFetchHandler'; import createHandleActions from './Creators/createHandleActions'; @@ -57,86 +58,86 @@ export const defaultState = { columns: [ { name: 'status', - columnLabel: 'Status', + columnLabel: translate('Status'), isSortable: true, isVisible: true, isModifiable: false }, { name: 'artists.sortName', - label: 'Artist', + label: translate('Artist'), isSortable: true, isVisible: true }, { name: 'albums.title', - label: 'Album Title', + label: translate('AlbumTitle'), isSortable: true, isVisible: true }, { name: 'albums.releaseDate', - label: 'Album Release Date', + label: translate('AlbumReleaseDate'), isSortable: true, isVisible: false }, { name: 'quality', - label: 'Quality', + label: translate('Quality'), isSortable: true, isVisible: true }, { name: 'protocol', - label: 'Protocol', + label: translate('Protocol'), isSortable: true, isVisible: false }, { name: 'indexer', - label: 'Indexer', + label: translate('Indexer'), isSortable: true, isVisible: false }, { name: 'downloadClient', - label: 'Download Client', + label: translate('DownloadClient'), isSortable: true, isVisible: false }, { name: 'title', - label: 'Release Title', + label: translate('ReleaseTitle'), isSortable: true, isVisible: false }, { name: 'size', - label: 'Size', + label: translate('Size'), isSortable: true, isVisibile: false }, { name: 'outputPath', - label: 'Output Path', + label: translate('OutputPath'), isSortable: false, isVisible: false }, { name: 'estimatedCompletionTime', - label: 'Time Left', + label: translate('TimeLeft'), isSortable: true, isVisible: true }, { name: 'progress', - label: 'Progress', + label: translate('Progress'), isSortable: true, isVisible: true }, { name: 'actions', - columnLabel: 'Actions', + columnLabel: translate('Actions'), isVisible: true, isModifiable: false } diff --git a/frontend/src/Store/Actions/releaseActions.js b/frontend/src/Store/Actions/releaseActions.js index 0adf2d12b..582f6e361 100644 --- a/frontend/src/Store/Actions/releaseActions.js +++ b/frontend/src/Store/Actions/releaseActions.js @@ -2,6 +2,7 @@ import { createAction } from 'redux-actions'; import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; +import translate from 'Utilities/String/translate'; import createFetchHandler from './Creators/createFetchHandler'; import createHandleActions from './Creators/createHandleActions'; import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer'; @@ -51,12 +52,12 @@ export const defaultState = { filters: [ { key: 'all', - label: 'All', + label: translate('All'), filters: [] }, { key: 'discography-pack', - label: 'Discography', + label: translate('Discography'), filters: [ { key: 'discography', @@ -67,7 +68,7 @@ export const defaultState = { }, { key: 'not-discography-pack', - label: 'Not Discography', + label: translate('NotDiscography'), filters: [ { key: 'discography', @@ -154,51 +155,51 @@ export const defaultState = { filterBuilderProps: [ { name: 'title', - label: 'Title', + label: translate('Title'), type: filterBuilderTypes.STRING }, { name: 'age', - label: 'Age', + label: translate('Age'), type: filterBuilderTypes.NUMBER }, { name: 'protocol', - label: 'Protocol', + label: translate('Protocol'), type: filterBuilderTypes.EXACT, valueType: filterBuilderValueTypes.PROTOCOL }, { name: 'indexerId', - label: 'Indexer', + label: translate('Indexer'), type: filterBuilderTypes.EXACT, valueType: filterBuilderValueTypes.INDEXER }, { name: 'size', - label: 'Size', + label: translate('Size'), type: filterBuilderTypes.NUMBER, valueType: filterBuilderValueTypes.BYTES }, { name: 'seeders', - label: 'Seeders', + label: translate('Seeders'), type: filterBuilderTypes.NUMBER }, { name: 'leechers', - label: 'Peers', + label: translate('Peers'), type: filterBuilderTypes.NUMBER }, { name: 'quality', - label: 'Quality', + label: translate('Quality'), type: filterBuilderTypes.EXACT, valueType: filterBuilderValueTypes.QUALITY }, { name: 'rejectionCount', - label: 'Rejection Count', + label: translate('RejectionCount'), type: filterBuilderTypes.NUMBER } ], diff --git a/frontend/src/Store/Actions/systemActions.js b/frontend/src/Store/Actions/systemActions.js index eac93e69c..4910e462d 100644 --- a/frontend/src/Store/Actions/systemActions.js +++ b/frontend/src/Store/Actions/systemActions.js @@ -121,12 +121,12 @@ export const defaultState = { filters: [ { key: 'all', - label: 'All', + label: translate('All'), filters: [] }, { key: 'info', - label: 'Info', + label: translate('Info'), filters: [ { key: 'level', @@ -137,7 +137,7 @@ export const defaultState = { }, { key: 'warn', - label: 'Warn', + label: translate('Warn'), filters: [ { key: 'level', @@ -148,7 +148,7 @@ export const defaultState = { }, { key: 'error', - label: 'Error', + label: translate('Error'), filters: [ { key: 'level', diff --git a/frontend/src/Store/Actions/trackActions.js b/frontend/src/Store/Actions/trackActions.js index d8f474159..d40e2860d 100644 --- a/frontend/src/Store/Actions/trackActions.js +++ b/frontend/src/Store/Actions/trackActions.js @@ -1,6 +1,7 @@ import { createAction } from 'redux-actions'; import { sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; +import translate from 'Utilities/String/translate'; import createFetchHandler from './Creators/createFetchHandler'; import createHandleActions from './Creators/createHandleActions'; import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer'; @@ -27,42 +28,42 @@ export const defaultState = { columns: [ { name: 'medium', - label: 'Medium', + label: translate('Medium'), isVisible: false }, { name: 'absoluteTrackNumber', - label: 'Track', + label: translate('Track'), isVisible: true }, { name: 'title', - label: 'Title', + label: translate('Title'), isVisible: true }, { name: 'path', - label: 'Path', + label: translate('Path'), isVisible: false }, { name: 'duration', - label: 'Duration', + label: translate('Duration'), isVisible: true }, { name: 'audioInfo', - label: 'Audio Info', + label: translate('AudioInfo'), isVisible: true }, { name: 'status', - label: 'Status', + label: translate('Status'), isVisible: true }, { name: 'actions', - columnLabel: 'Actions', + columnLabel: translate('Actions'), isVisible: true, isModifiable: false } diff --git a/frontend/src/Store/Actions/trackFileActions.js b/frontend/src/Store/Actions/trackFileActions.js index cf676ddd2..0e4f91d54 100644 --- a/frontend/src/Store/Actions/trackFileActions.js +++ b/frontend/src/Store/Actions/trackFileActions.js @@ -5,6 +5,7 @@ import albumEntities from 'Album/albumEntities'; import { sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; +import translate from 'Utilities/String/translate'; import { removeItem, set, updateItem } from './baseActions'; import createFetchHandler from './Creators/createFetchHandler'; import createHandleActions from './Creators/createHandleActions'; @@ -43,32 +44,32 @@ export const defaultState = { columns: [ { name: 'path', - label: 'Path', + label: translate('Path'), isSortable: true, isVisible: true, isModifiable: false }, { name: 'size', - label: 'Size', + label: translate('Size'), isSortable: true, isVisible: true }, { name: 'dateAdded', - label: 'Date Added', + label: translate('DateAdded'), isSortable: true, isVisible: true }, { name: 'quality', - label: 'Quality', + label: translate('Quality'), isSortable: true, isVisible: true }, { name: 'actions', - columnLabel: 'Actions', + columnLabel: translate('Actions'), isVisible: true, isModifiable: false } diff --git a/frontend/src/Store/Actions/wantedActions.js b/frontend/src/Store/Actions/wantedActions.js index f8a63d982..da7fd2162 100644 --- a/frontend/src/Store/Actions/wantedActions.js +++ b/frontend/src/Store/Actions/wantedActions.js @@ -2,6 +2,7 @@ import { createAction } from 'redux-actions'; import { filterTypes, sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers'; +import translate from 'Utilities/String/translate'; import createBatchToggleAlbumMonitoredHandler from './Creators/createBatchToggleAlbumMonitoredHandler'; import createHandleActions from './Creators/createHandleActions'; import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers'; @@ -29,25 +30,25 @@ export const defaultState = { columns: [ { name: 'artists.sortName', - label: 'Artist Name', + label: translate('ArtistName'), isSortable: true, isVisible: true }, { name: 'albums.title', - label: 'Album Title', + label: translate('AlbumTitle'), isSortable: true, isVisible: true }, { name: 'albumType', - label: 'Album Type', + label: translate('AlbumType'), isSortable: true, isVisible: true }, { name: 'releaseDate', - label: 'Release Date', + label: translate('ReleaseDate'), isSortable: true, isVisible: true }, @@ -58,7 +59,7 @@ export const defaultState = { // }, { name: 'actions', - columnLabel: 'Actions', + columnLabel: translate('Actions'), isVisible: true, isModifiable: false } @@ -69,7 +70,7 @@ export const defaultState = { filters: [ { key: 'monitored', - label: 'Monitored', + label: translate('Monitored'), filters: [ { key: 'monitored', @@ -80,7 +81,7 @@ export const defaultState = { }, { key: 'unmonitored', - label: 'Unmonitored', + label: translate('Unmonitored'), filters: [ { key: 'monitored', @@ -103,25 +104,25 @@ export const defaultState = { columns: [ { name: 'artists.sortName', - label: 'Artist Name', + label: translate('ArtistName'), isSortable: true, isVisible: true }, { name: 'albums.title', - label: 'Album Title', + label: translate('AlbumTitle'), isSortable: true, isVisible: true }, { name: 'albumType', - label: 'Album Type', + label: translate('AlbumType'), isSortable: true, isVisible: true }, { name: 'releaseDate', - label: 'Release Date', + label: translate('ReleaseDate'), isSortable: true, isVisible: true }, @@ -132,7 +133,7 @@ export const defaultState = { // }, { name: 'actions', - columnLabel: 'Actions', + columnLabel: translate('Actions'), isVisible: true, isModifiable: false } @@ -143,7 +144,7 @@ export const defaultState = { filters: [ { key: 'monitored', - label: 'Monitored', + label: translate('Monitored'), filters: [ { key: 'monitored', @@ -154,7 +155,7 @@ export const defaultState = { }, { key: 'unmonitored', - label: 'Unmonitored', + label: translate('Unmonitored'), filters: [ { key: 'monitored', diff --git a/frontend/src/System/Backup/BackupRow.js b/frontend/src/System/Backup/BackupRow.js index 53274f289..a7f122e6f 100644 --- a/frontend/src/System/Backup/BackupRow.js +++ b/frontend/src/System/Backup/BackupRow.js @@ -76,14 +76,14 @@ class BackupRow extends Component { } = this.state; let iconClassName = icons.SCHEDULED; - let iconTooltip = translate('IconTooltip'); + let iconTooltip = translate('Scheduled'); if (type === 'manual') { iconClassName = icons.INTERACTIVE; - iconTooltip = 'Manual'; + iconTooltip = translate('Manual'); } else if (type === 'update') { iconClassName = icons.UPDATE; - iconTooltip = 'Before update'; + iconTooltip = translate('BeforeUpdate'); } return ( diff --git a/frontend/src/System/Backup/Backups.js b/frontend/src/System/Backup/Backups.js index 67aa72cc3..7a5e399d0 100644 --- a/frontend/src/System/Backup/Backups.js +++ b/frontend/src/System/Backup/Backups.js @@ -20,17 +20,17 @@ const columns = [ }, { name: 'name', - label: 'Name', + label: translate('Name'), isVisible: true }, { name: 'size', - label: 'Size', + label: translate('Size'), isVisible: true }, { name: 'time', - label: 'Time', + label: translate('Time'), isVisible: true }, { diff --git a/frontend/src/System/Backup/RestoreBackupModalContent.js b/frontend/src/System/Backup/RestoreBackupModalContent.js index 24926e685..a277a5499 100644 --- a/frontend/src/System/Backup/RestoreBackupModalContent.js +++ b/frontend/src/System/Backup/RestoreBackupModalContent.js @@ -14,7 +14,7 @@ import styles from './RestoreBackupModalContent.css'; function getErrorMessage(error) { if (!error || !error.responseJSON || !error.responseJSON.message) { - return 'Error restoring backup'; + return translate('ErrorRestoringBackup'); } return error.responseJSON.message; @@ -146,7 +146,7 @@ class RestoreBackupModalContent extends Component { { - !!id && `Would you like to restore the backup '${name}'?` + !!id && translate('WouldYouLikeToRestoreBackup', [name]) } { @@ -203,11 +203,11 @@ class RestoreBackupModalContent extends Component {
- Note: Lidarr will automatically restart and reload the UI during the restore process. + {translate('RestoreBackupAdditionalInfo')}
- Restore + {translate('Restore')}
diff --git a/frontend/src/System/Logs/Files/LogFiles.js b/frontend/src/System/Logs/Files/LogFiles.js index aceb19351..48ac75ddb 100644 --- a/frontend/src/System/Logs/Files/LogFiles.js +++ b/frontend/src/System/Logs/Files/LogFiles.js @@ -19,12 +19,12 @@ import LogFilesTableRow from './LogFilesTableRow'; const columns = [ { name: 'filename', - label: 'Filename', + label: translate('Filename'), isVisible: true }, { name: 'lastWriteTime', - label: 'Last Write Time', + label: translate('LastWriteTime'), isVisible: true }, { diff --git a/frontend/src/System/Status/DiskSpace/DiskSpace.js b/frontend/src/System/Status/DiskSpace/DiskSpace.js index 87674c7a9..b05b27f4b 100644 --- a/frontend/src/System/Status/DiskSpace/DiskSpace.js +++ b/frontend/src/System/Status/DiskSpace/DiskSpace.js @@ -15,17 +15,17 @@ import styles from './DiskSpace.css'; const columns = [ { name: 'path', - label: 'Location', + label: translate('Location'), isVisible: true }, { name: 'freeSpace', - label: 'Free Space', + label: translate('FreeSpace'), isVisible: true }, { name: 'totalSpace', - label: 'Total Space', + label: translate('TotalSpace'), isVisible: true }, { diff --git a/frontend/src/System/Status/Donations/Donations.js b/frontend/src/System/Status/Donations/Donations.js index 220fbee25..b3bff8dff 100644 --- a/frontend/src/System/Status/Donations/Donations.js +++ b/frontend/src/System/Status/Donations/Donations.js @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import FieldSet from 'Components/FieldSet'; import Link from 'Components/Link/Link'; +import translate from 'Utilities/String/translate'; import styles from '../styles.css'; class Donations extends Component { @@ -10,7 +11,7 @@ class Donations extends Component { render() { return ( -
+
- Close + {translate('Close')} diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 88662d08c..4dc606652 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -5,34 +5,61 @@ "About": "About", "Absolute": "Absolute", "Actions": "Actions", + "Activity": "Activity", + "Add": "Add", + "AddConnection": "Add Connection", + "AddDelayProfile": "Add Delay Profile", + "Added": "Added", "AddedArtistSettings": "Added Artist Settings", + "AddImportListExclusion": "Add Import List Exclusion", "AddImportListExclusionHelpText": "Prevent artist from being added to Lidarr by Import lists", + "AddIndexer": "Add Indexer", "AddingTag": "Adding tag", + "AddList": "Add List", "AddListExclusion": "Add List Exclusion", + "AddMetadataProfile": "Add Metadata Profile", "AddMissing": "Add missing", + "AddNew": "Add New", "AddNewItem": "Add New Item", + "AddQualityProfile": "Add Quality Profile", + "AddReleaseProfile": "Add Release Profile", + "AddRemotePathMapping": "Add Remote Path Mapping", + "AddRootFolder": "Add Root Folder", "AdvancedSettingsHiddenClickToShow": "Hidden, click to show", "AdvancedSettingsShownClickToHide": "Shown, click to hide", + "AfterManualRefresh": "After Manual Refresh", + "Age": "Age", "AgeWhenGrabbed": "Age (when grabbed)", "Album": "Album", "AlbumHasNotAired": "Album has not aired", "AlbumIsDownloading": "Album is downloading", "AlbumIsDownloadingInterp": "Album is downloading - {0}% {1}", "AlbumIsNotMonitored": "Album is not monitored", + "AlbumRelease": "Album Release", + "AlbumReleaseDate": "Album Release Date", + "Albums": "Albums", + "AlbumStatus": "Album Status", "AlbumStudio": "Album Studio", + "AlbumTitle": "Album Title", + "AlbumType": "Album Type", + "All": "All", "AllAlbums": "All Albums", "AllAlbumsData": "Monitor all albums except specials", "AllArtistAlbums": "All Artist Albums", "AllExpandedCollapseAll": "Collapse All", "AllExpandedExpandAll": "Expand All", + "AllFiles": "All Files", + "AllMonitoringOptionHelpText": "Monitor artists and all albums for each artist included on the import list", "AllowArtistChangeClickToChangeArtist": "Click to change artist", "AllowFingerprinting": "Allow Fingerprinting", "AllowFingerprintingHelpText": "Use fingerprinting to improve accuracy of track matching", "AllowFingerprintingHelpTextWarning": "This requires Lidarr to read parts of the file which will slow down scans and may cause high disk or network activity.", + "AllResultsFiltered": "All results are hidden by the applied filter", "AlreadyInYourLibrary": "Already in your library", "AlternateTitles": "Alternate Titles", "AlternateTitleslength1Title": "Title", "AlternateTitleslength1Titles": "Titles", + "Always": "Always", "Analytics": "Analytics", "AnalyticsEnabledHelpText": "Send anonymous usage and error information to Lidarr's servers. This includes information on your browser, which Lidarr WebUI pages you use, error reporting as well as OS and runtime version. We will use this information to prioritize features and bug fixes.", "AnalyticsEnabledHelpTextWarning": "Requires restart to take effect", @@ -43,28 +70,35 @@ "AppDataDirectory": "AppData directory", "ApplicationURL": "Application URL", "ApplicationUrlHelpText": "This application's external URL including http(s)://, port and URL base", + "Apply": "Apply", "ApplyTags": "Apply Tags", "ApplyTagsHelpTexts1": "How to apply tags to the selected artist", "ApplyTagsHelpTexts2": "Add: Add the tags the existing list of tags", "ApplyTagsHelpTexts3": "Remove: Remove the entered tags", "ApplyTagsHelpTexts4": "Replace: Replace the tags with the entered tags (enter no tags to clear all tags)", + "AreYouSure": "Are you sure?", "Artist": "Artist", "ArtistAlbumClickToChangeTrack": "Click to change track", "ArtistClickToChangeAlbum": "Click to change album", "ArtistEditor": "Artist Editor", "ArtistFolderFormat": "Artist Folder Format", + "ArtistName": "Artist Name", "ArtistNameHelpText": "The name of the artist/album to exclude (can be anything meaningful)", "Artists": "Artists", + "ArtistType": "Artist Type", + "AudioInfo": "Audio Info", "Authentication": "Authentication", "AuthenticationMethodHelpText": "Require Username and Password to access Lidarr", "Automatic": "Automatic", "AutomaticallySwitchRelease": "Automatically Switch Release", "AutoRedownloadFailedHelpText": "Automatically search for and attempt to download a different release", + "Backup": "Backup", "BackupFolderHelpText": "Relative paths will be under Lidarr's AppData directory", "BackupIntervalHelpText": "Interval to backup the Lidarr DB and settings", "BackupNow": "Backup Now", "BackupRetentionHelpText": "Automatic backups older than the retention period will be cleaned up automatically", "Backups": "Backups", + "BeforeUpdate": "Before Update", "BindAddress": "Bind Address", "BindAddressHelpText": "Valid IPv4 address or '*' for all interfaces", "BindAddressHelpTextWarning": "Requires restart to take effect", @@ -93,27 +127,36 @@ "ClientPriority": "Client Priority", "CloneIndexer": "Clone Indexer", "CloneProfile": "Clone Profile", + "Close": "Close", "CollapseMultipleAlbums": "Collapse Multiple Albums", "CollapseMultipleAlbumsHelpText": "Collapse multiple albums releasing on the same day", "Columns": "Columns", + "CombineWithExistingFiles": "Combine With Existing Files", "CompletedDownloadHandling": "Completed Download Handling", "Component": "Component", + "Connect": "Connect", "Connections": "Connections", "ConnectSettings": "Connect Settings", "Continuing": "Continuing", "ContinuingAllTracksDownloaded": "Continuing (All tracks downloaded)", "ContinuingMoreAlbumsAreExpected": "More albums are expected", "ContinuingNoAdditionalAlbumsAreExpected": "No additional albums are expected", + "ContinuingOnly": "ContinuingOnly", "CopyUsingHardlinksHelpText": "Use Hardlinks when trying to copy files from torrents that are still being seeded", "CopyUsingHardlinksHelpTextWarning": "Occasionally, file locks may prevent renaming files that are being seeded. You may temporarily disable seeding and use Lidarr's rename function as a work around.", "Country": "Country", "CreateEmptyArtistFolders": "Create empty artist folders", "CreateEmptyArtistFoldersHelpText": "Create missing artist folders during disk scan", "CreateGroup": "Create group", + "Custom": "Custom", + "CustomFilters": "Custom Filters", "CutoffHelpText": "Once this quality is reached Lidarr will no longer download albums", "CutoffUnmet": "Cutoff Unmet", + "Date": "Date", + "DateAdded": "Date Added", "Dates": "Dates", "DBMigration": "DB Migration", + "DefaultDelayProfileHelpText": "This is the default profile. It applies to all artist that don't have an explicit profile.", "DefaultLidarrTags": "Default Lidarr Tags", "DefaultMetadataProfileIdHelpText": "Default Metadata Profile for artists detected in this folder", "DefaultMonitorOptionHelpText": "Which albums should be monitored on initial add for artists detected in this folder", @@ -123,8 +166,10 @@ "DelayProfile": "Delay Profile", "DelayProfiles": "Delay Profiles", "Delete": "Delete", + "DeleteArtist": "Delete Selected Artist", "DeleteBackup": "Delete Backup", "DeleteBackupMessageText": "Are you sure you want to delete the backup '{0}'?", + "Deleted": "Deleted", "DeleteDelayProfile": "Delete Delay Profile", "DeleteDelayProfileMessageText": "Are you sure you want to delete this delay profile?", "DeleteDownloadClient": "Delete Download Client", @@ -157,16 +202,23 @@ "DestinationPath": "Destination Path", "DetailedProgressBar": "Detailed Progress Bar", "DetailedProgressBarHelpText": "Show text on progess bar", + "Details": "Details", "Disambiguation": "Disambiguation", "DiscCount": "Disc Count", "DiscNumber": "Disc Number", + "Discography": "Discography", "DiskSpace": "Disk Space", "Docker": "Docker", + "Donations": "Donations", + "DoNotPrefer": "Do Not Prefer", + "DoNotUpgradeAutomatically": "Do not Upgrade Automatically", "DownloadClient": "Download Client", "DownloadClients": "Download Clients", "DownloadClientSettings": "Download Client Settings", + "DownloadFailed": "Download Failed", "DownloadFailedCheckDownloadClientForMoreDetails": "Download failed: check download client for more details", "DownloadFailedInterp": "Download failed: {0}", + "DownloadImported": "Download Imported", "Downloading": "Downloading", "DownloadPropersAndRepacksHelpTexts1": "Whether or not to automatically upgrade to Propers/Repacks", "DownloadPropersAndRepacksHelpTexts2": "Use 'Do not Prefer' to sort by preferred word score over propers/repacks", @@ -174,6 +226,17 @@ "Duration": "Duration", "Edit": "Edit", "EditArtist": "Edit Artist", + "EditConnection": "Edit Connection", + "EditDelayProfile": "Edit Delay Profile", + "EditImportListExclusion": "Edit Import List Exclusion", + "EditIndexer": "Edit Indexer", + "EditList": "Edit List", + "EditMetadata": "Edit Metadata", + "EditMetadataProfile": "Edit Metadata Profile", + "EditQualityProfile": "Edit Quality Profile", + "EditReleaseProfile": "Edit Release Profile", + "EditRemotePathMapping": "Edit Remote Path Mapping", + "EditRootFolder": "Edit Root Folder", "Enable": "Enable", "EnableAutomaticAdd": "Enable Automatic Add", "EnableAutomaticAddHelpText": "Add artist/albums to Lidarr when syncs are performed via the UI or by Lidarr", @@ -190,11 +253,16 @@ "EnableSslHelpText": " Requires restart running as administrator to take effect", "Ended": "Ended", "EndedAllTracksDownloaded": "Ended (All tracks downloaded)", + "EndedOnly": "Ended Only", "EntityName": "Entity Name", "Episode": "Episode", "EpisodeDoesNotHaveAnAbsoluteEpisodeNumber": "Episode does not have an absolute episode number", + "Error": "Error", "ErrorLoadingContents": "Error loading contents", "ErrorLoadingPreviews": "Error loading previews", + "ErrorRestoringBackup": "Error restoring backup", + "Events": "Events", + "EventType": "Event Type", "Exception": "Exception", "ExistingAlbums": "Existing Albums", "ExistingAlbumsData": "Monitor albums that have files or have not released yet", @@ -213,6 +281,7 @@ "FileNames": "File Names", "Files": "Files", "FilterPlaceHolder": "Filter artist", + "Filters": "Filters", "FirstAlbum": "First Album", "FirstAlbumData": "Monitor the first albums. All other albums will be ignored", "FirstDayOfWeek": "First Day of Week", @@ -223,23 +292,30 @@ "ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "For more information on the individual download clients, click the more info buttons.", "ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "For more information on the individual indexers, click on the info buttons.", "ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "For more information on the individual lists, click on the info buttons.", + "ForNewImportsOnly": "For new imports only", + "FreeSpace": "Free Space", "FutureAlbums": "Future Albums", "FutureAlbumsData": "Monitor albums that have not released yet", "FutureDays": "Future Days", "FutureDaysHelpText": "Days for iCal feed to look into the future", + "General": "General", "GeneralSettings": "General Settings", + "Genres": "Genres", "Global": "Global", "GoToArtistListing": "Go to artist listing", "GoToInterp": "Go to {0}", "Grab": "Grab", + "Grabbed": "Grabbed", "GrabID": "Grab ID", "GrabRelease": "Grab Release", "GrabReleaseMessageText": "Lidarr was unable to determine which artist and album this release was for. Lidarr may be unable to automatically import this release. Do you want to grab '{0}'?", "GrabSelected": "Grab Selected", "Group": "Group", + "HardlinkCopyFiles": "Hardlink/Copy Files", "HasMonitoredAlbumsNoMonitoredAlbumsForThisArtist": "No monitored albums for this artist", "HasPendingChangesNoChanges": "No Changes", "HasPendingChangesSaveChanges": "Save Changes", + "HideAdvanced": "Hide Advanced", "HideAlbums": "Hide albums", "HideTracks": "Hide tracks", "History": "History", @@ -252,13 +328,16 @@ "IconForCutoffUnmet": "Icon for Cutoff Unmet", "IconTooltip": "Scheduled", "IfYouDontAddAnImportListExclusionAndTheArtistHasAMetadataProfileOtherThanNoneThenThisAlbumMayBeReaddedDuringTheNextArtistRefresh": "If you don't add an import list exclusion and the artist has a metadata profile other than 'None' then this album may be re-added during the next artist refresh.", + "Ignored": "Ignored", "IgnoredAddresses": "Ignored Addresses", "IgnoredHelpText": "The release will be rejected if it contains one or more of terms (case insensitive)", "IgnoredPlaceHolder": "Add new restriction", "IllRestartLater": "I'll restart later", + "Import": "Import", "ImportedTo": "Imported To", "ImportExtraFiles": "Import Extra Files", "ImportExtraFilesHelpText": "Import matching extra files (subtitles, nfo, etc) after importing an track file", + "ImportFailed": "Import Failed", "ImportFailedInterp": "Import failed: {0}", "ImportFailures": "Import failures", "Importing": "Importing", @@ -280,8 +359,10 @@ "Indexers": "Indexers", "IndexerSettings": "Indexer Settings", "IndexerTagHelpText": "Only use this indexer for artist with at least one matching tag. Leave blank to use with all artists.", + "Info": "Info", "InstanceName": "Instance Name", "InstanceNameHelpText": "Instance name in tab and for Syslog app name", + "InteractiveImport": "Interactive Import", "InteractiveSearch": "Interactive Search", "Interval": "Interval", "IsCutoffCutoff": "Cutoff", @@ -299,11 +380,16 @@ "IsTagUsedCannotBeDeletedWhileInUse": "Cannot be deleted while in use", "Label": "Label", "Language": "Language", + "LastAlbum": "Last Album", + "LastDuration": "Last Duration", + "LastExecution": "Last Execution", + "LastUsed": "Last Used", + "LastWriteTime": "Last Write Time", "LatestAlbum": "Latest Album", "LatestAlbumData": "Monitor the latest albums and future albums", "LaunchBrowserHelpText": " Open a web browser and navigate to Lidarr homepage on app start.", "Level": "Level", - "Lidarr": "Lidarr", + "Library": "Library", "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr supports many popular torrent and usenet download clients.", "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr supports any indexer that uses the Newznab standard, as well as other indexers listed below.", "LidarrSupportsMultipleListsForImportingAlbumsAndArtistsIntoTheDatabase": "Lidarr supports multiple lists for importing Albums and Artists into the database.", @@ -313,6 +399,7 @@ "Local": "Local", "LocalPath": "Local Path", "LocalPathHelpText": "Path that Lidarr should use to access the remote path locally", + "Location": "Location", "LogFiles": "Log Files", "Logging": "Logging", "LogLevel": "Log Level", @@ -321,21 +408,26 @@ "LongDateFormat": "Long Date Format", "MaintenanceRelease": "Maintenance release", "ManageTracks": "Manage Tracks", + "Manual": "Manual", "ManualDownload": "Manual Download", "ManualImport": "Manual Import", "MarkAsFailed": "Mark as Failed", "MarkAsFailedMessageText": "Are you sure you want to mark '{0}' as failed?", "MassAlbumsCutoffUnmetWarning": "Are you sure you want to search for all '{0}' Cutoff Unmet albums?", "MassAlbumsSearchWarning": "Are you sure you want to search for all '{0}' missing albums?", + "MassEditor": "Mass Editor", "MaximumLimits": "Maximum Limits", "MaximumSize": "Maximum Size", "MaximumSizeHelpText": "Maximum size for a release to be grabbed in MB. Set to zero to set to unlimited.", "Mechanism": "Mechanism", + "MediaCount": "Media Count", "MediaInfo": "Media Info", + "MediaManagement": "MediaManagement", "MediaManagementSettings": "Media Management Settings", "Medium": "Medium", "MediumFormat": "Medium Format", "Message": "Message", + "Metadata": "Metadata", "MetadataConsumers": "Metadata Consumers", "MetadataProfile": "Metadata Profile", "MetadataProfileIdHelpText": "Metadata Profile list items should be added with", @@ -353,6 +445,7 @@ "Missing": "Missing", "MissingAlbums": "Missing Albums", "MissingAlbumsData": "Monitor albums that do not have files or have not released yet", + "MissingTracks": "Missing Tracks", "MissingTracksArtistMonitored": "Missing Tracks (Artist monitored)", "MissingTracksArtistNotMonitored": "Missing Tracks (Artist not monitored)", "Mode": "Mode", @@ -360,12 +453,15 @@ "MonitorArtist": "Monitor Artist", "Monitored": "Monitored", "MonitoredHelpText": "Download monitored albums from this artist", + "MonitoredOnly": "Monitored Only", "MonitoringOptions": "Monitoring Options", "MonitoringOptionsHelpText": "Which albums should be monitored after the artist is added (one-time adjustment)", "MonitorNewItems": "Monitor New Albums", "MonitorNewItemsHelpText": "Which new albums should be monitored", "MonoVersion": "Mono Version", "MoreInfo": "More Info", + "MoveAutomatically": "Move Automatically", + "MoveFiles": "Move Files", "MultiDiscTrackFormat": "Multi Disc Track Format", "MusicBrainzAlbumID": "MusicBrainz Album ID", "MusicBrainzArtistID": "MusicBrainz Artist ID", @@ -378,8 +474,11 @@ "Name": "Name", "NamingSettings": "Naming Settings", "NETCore": ".NET", + "Never": "Never", "New": "New", "NewAlbums": "New Albums", + "NextAlbum": "Next Album", + "NextExecution": "Next Execution", "NoBackupsAreAvailable": "No backups are available", "NoHistory": "No history.", "NoLeaveIt": "No, Leave It", @@ -388,9 +487,13 @@ "NoMinimumForAnyRuntime": "No minimum for any runtime", "None": "None", "NoneData": "No albums will be monitored", + "NoneMonitoringOptionHelpText": "Do not monitor artists or albums", + "NoResults": "No Results Found", "NoTagsHaveBeenAddedYet": "No tags have been added yet", + "NotDiscography": "Not Discography", "NotificationTriggers": "Notification Triggers", "NoUpdatesAreAvailable": "No updates are available", + "Ok": "Ok", "OnApplicationUpdate": "On Application Update", "OnApplicationUpdateHelpText": "On Application Update", "OnDownloadFailure": "On Download Failure", @@ -401,6 +504,8 @@ "OnHealthIssueHelpText": "On Health Issue", "OnImportFailure": "On Import Failure", "OnImportFailureHelpText": "On Import Failure", + "OnlyTorrent": "Only Torrent", + "OnlyUsenet": "Only Usenet", "OnReleaseImport": "On Release Import", "OnReleaseImportHelpText": "On Release Import", "OnRename": "On Rename", @@ -411,8 +516,11 @@ "OnUpgradeHelpText": "On Upgrade", "OpenBrowserOnStart": "Open browser on start", "Options": "Options", + "Organize": "Organize", + "OrganizeArtist": "Organize Selected Artist", "Original": "Original", "Other": "Other", + "OutputPath": "Output Path", "PackageVersion": "Package Version", "PageSize": "Page Size", "PageSizeHelpText": "Number of items to show on each page", @@ -422,20 +530,28 @@ "Path": "Path", "PathHelpText": "Root Folder containing your music library", "PathHelpTextWarning": "This must be different to the directory where your download client puts files", + "Peers": "Peers", "Permissions": "Permissions", + "Playlist": "Playlist", "Port": "Port", "PortNumber": "Port Number", "PosterSize": "Poster Size", + "PreferAndUpgrade": "Prefer and Upgrade", "Preferred": "Preferred", "PreferredHelpTexts1": "The release will be preferred based on the each term's score (case insensitive)", "PreferredHelpTexts2": "A positive score will be more preferred", "PreferredHelpTexts3": "A negative score will be less preferred", + "PreferredProtocol": "Preferred Protocol", + "PreferredWordScore": "Preferred word score", + "Presets": "Presets", "PreviewRename": "Preview Rename", "PreviewRetag": "Preview Retag", "PrimaryAlbumTypes": "Primary Album Types", "PrimaryTypes": "Primary Types", "PriorityHelpText": "Indexer Priority from 1 (Highest) to 50 (Lowest). Default: 25. Used when grabbing releases as a tiebreaker for otherwise equal releases, Lidarr will still use all enabled indexers for RSS Sync and Searching.", + "Proceed": "Proceed", "Profiles": "Profiles", + "Progress": "Progress", "Proper": "Proper", "PropersAndRepacks": "Propers and Repacks", "Protocol": "Protocol", @@ -449,13 +565,14 @@ "PublishedDate": "Published Date", "Quality": "Quality", "QualityDefinitions": "Quality Definitions", + "QualityLimitsHelpText": "Limits are automatically adjusted for the album duration.", "QualityProfile": "Quality Profile", "QualityProfileIdHelpText": "Quality Profile list items should be added with", "QualityProfiles": "Quality Profiles", "QualitySettings": "Quality Settings", "Queue": "Queue", - "Radarr": "Radarr", - "Readarr": "Readarr", + "Queued": "Queued", + "Rating": "Rating", "ReadTheWikiForMoreInformation": "Read the Wiki for more information", "Real": "Real", "Reason": "Reason", @@ -469,6 +586,7 @@ "RefreshArtist": "Refresh Artist", "RefreshInformationAndScanDisk": "Refresh information and scan disk", "RefreshScan": "Refresh & Scan", + "RejectionCount": "Rejection Count", "Release": " Release", "ReleaseDate": "Release Date", "ReleaseGroup": "Release Group", @@ -476,6 +594,7 @@ "ReleaseRejected": "Release Rejected", "ReleasesHelpText": "Change release for this album", "ReleaseStatuses": "Release Statuses", + "ReleaseTitle": "Release Title", "ReleaseWillBeProcessedInterp": "Release will be processed {0}", "Reload": "Reload", "RemotePath": "Remote Path", @@ -497,9 +616,12 @@ "RemoveSelectedMessageText": "Are you sure you want to remove the selected items from the blocklist?", "RemoveTagExistingTag": "Existing tag", "RemoveTagRemovingTag": "Removing tag", + "Renamed": "Renamed", "RenameTracks": "Rename Tracks", "RenameTracksHelpText": "Lidarr will use the existing file name if renaming is disabled", "Reorder": "Reorder", + "Replace": "Replace", + "ReplaceExistingFiles": "Replace Existing Files", "ReplaceIllegalCharacters": "Replace Illegal Characters", "ReplaceIllegalCharactersHelpText": "Replace illegal characters. If unchecked, Lidarr will remove them instead", "RequiredHelpText": "The release must contain at least one of these terms (case insensitive)", @@ -514,18 +636,24 @@ "Restart": "Restart", "RestartLidarr": "Restart Lidarr", "RestartNow": "Restart Now", + "RestartRequiredHelpTextWarning": "Requires restart to take effect", "Restore": "Restore", "RestoreBackup": "Restore Backup", + "RestoreBackupAdditionalInfo": "Note: Lidarr will automatically restart and reload the UI during the restore process.", "Result": "Result", + "Retag": "Retag", + "Retagged": "Retagged", "Retention": "Retention", "RetentionHelpText": "Usenet only: Set to zero to set for unlimited retention", "RetryingDownloadInterp": "Retrying download {0} at {1}", "RootFolder": "Root Folder", + "RootFolderPath": "Root Folder Path", "RootFolderPathHelpText": "Root Folder list items will be added to", "RootFolders": "Root Folders", "RSSSync": "RSS Sync", "RSSSyncInterval": "RSS Sync Interval", "RssSyncIntervalHelpText": "Interval in minutes. Set to zero to disable (this will stop all automatic release grabbing)", + "Save": "Save", "SceneInformation": "Scene Information", "SceneNumberHasntBeenVerifiedYet": "Scene number hasn't been verified yet", "Scheduled": "Scheduled", @@ -544,10 +672,19 @@ "SearchMonitored": "Search Monitored", "SearchSelected": "Search Selected", "Season": "Season", + "SecificMonitoringOptionHelpText": "Monitor artists but only monitor albums explicitly included in the list", "SecondaryAlbumTypes": "Secondary Album Types", "SecondaryTypes": "Secondary Types", "Security": "Security", + "Seeders": "Seeders", + "Select...": "Select...", + "SelectAlbum": "Select Album", + "SelectAlbumRelease": "Select Album Release", + "SelectArtist": "Select Artist", "SelectedCountArtistsSelectedInterp": "{0} Artist(s) Selected", + "SelectFolder": "Select Folder", + "SelectQuality": "Select Quality", + "SelectTracks": "Select Tracks", "SendAnonymousUsageData": "Send Anonymous Usage Data", "SetPermissions": "Set Permissions", "SetPermissionsLinuxHelpText": "Should chmod be run when files are imported/renamed?", @@ -559,6 +696,7 @@ "ShouldMonitorHelpText": "Monitor artists and albums added from this list", "ShouldSearch": "Search for New Items", "ShouldSearchHelpText": "Search indexers for newly added items. Use with caution for large lists.", + "ShowAdvanced": "Show Advanced", "ShowAlbumCount": "Show Album Count", "ShowBanners": "Show Banners", "ShowBannersHelpText": "Show banners instead of names", @@ -580,15 +718,18 @@ "ShowTitleHelpText": "Show artist name under poster", "ShowUnknownArtistItems": "Show Unknown Artist Items", "Size": " Size", + "SizeLimit": "Size Limit", + "SizeOnDisk": "Size on Disk", "SkipFreeSpaceCheck": "Skip Free Space Check", "SkipFreeSpaceCheckWhenImportingHelpText": "Use when Lidarr is unable to detect free space from your artist root folder", "SkipRedownload": "Skip Redownload", "SkipredownloadHelpText": "Prevents Lidarr from trying download alternative releases for the removed items", - "Sonarr": "Sonarr", + "SomeResultsFiltered": "Some results are hidden by the applied filter", "SorryThatAlbumCannotBeFound": "Sorry, that album cannot be found.", "SorryThatArtistCannotBeFound": "Sorry, that artist cannot be found.", "Source": "Source", "SourcePath": "Source Path", + "SourceTitle": "Source Title", "SpecificAlbum": "Specific Album", "SSLCertPassword": "SSL Cert Password", "SslCertPasswordHelpText": "Password for pfx file", @@ -599,6 +740,7 @@ "SSLPort": "SSL Port", "SslPortHelpTextWarning": "Requires restart to take effect", "StandardTrackFormat": "Standard Track Format", + "Started": "Started", "StartTypingOrSelectAPathBelow": "Start typing or select a path below", "StartupDirectory": "Startup directory", "Status": "Status", @@ -610,6 +752,7 @@ "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Search is not supported with this indexer", "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Will be used when automatic searches are performed via the UI or by Lidarr", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Will be used when interactive search is used", + "System": "System", "TagAudioFilesWithMetadata": "Tag Audio Files with Metadata", "TagIsNotUsedAndCanBeDeleted": "Tag is not used and can be deleted", "Tags": "Tags", @@ -617,34 +760,44 @@ "Tasks": "Tasks", "TBA": "TBA", "Term": "Term", + "Test": "Test", "TestAll": "Test All", "TestAllClients": "Test All Clients", "TestAllIndexers": "Test All Indexers", "TestAllLists": "Test All Lists", "TheAlbumsFilesWillBeDeleted": "The album's files will be deleted.", "TheArtistFolderStrongpathstrongAndAllOfItsContentWillBeDeleted": "The artist folder {path} and all of its content will be deleted.", + "Theme": "Theme", + "ThemeHelpText": "Change Application UI Theme", "ThisCannotBeCancelled": "This cannot be cancelled once started without disabling all of your indexers.", "ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "This will apply to all indexers, please follow the rules set forth by them", "Time": "Time", "TimeFormat": "Time Format", + "TimeLeft": "Time Left", + "Title": "Title", "TorrentDelay": "Torrent Delay", "TorrentDelayHelpText": "Delay in minutes to wait before grabbing a torrent", "Torrents": "Torrents", "TotalFileSize": "Total File Size", + "TotalSpace": "Total Space", "TotalTrackCountTracksTotalTrackFileCountTracksWithFilesInterp": "{0} tracks total. {1} tracks with files.", "Track": "Track", "TrackArtist": "Track Artist", + "TrackCount": "Track Count", "TrackDownloaded": "Track Downloaded", "TrackFileCounttotalTrackCountTracksDownloadedInterp": "{0}/{1} tracks downloaded", "TrackFileCountTrackCountTotalTotalTrackCountInterp": "{0} / {1} (Total: {2})", "TrackFilesCountMessage": "No track files", + "TrackImported": "Track Imported", "TrackMissingFromDisk": "Track missing from disk", "TrackNaming": "Track Naming", "TrackNumber": "Track Number", + "TrackProgress": "Track Progress", "Tracks": "Tracks", "TrackStatus": "Track status", "TrackTitle": "Track Title", "Type": "Type", + "UI": "UI", "UILanguage": "UI Language", "UILanguageHelpText": "Language that Lidarr will use for UI", "UILanguageHelpTextWarning": "Browser Reload Required", @@ -668,6 +821,7 @@ "UnableToLoadImportListExclusions": "Unable to load Import List Exclusions", "UnableToLoadIndexerOptions": "Unable to load indexer options", "UnableToLoadIndexers": "Unable to load Indexers", + "UnableToLoadInteractiveSerach": "Unable to load results for this album search. Try again later", "UnableToLoadLists": "Unable to load Lists", "UnableToLoadMediaManagementSettings": "Unable to load Media Management settings", "UnableToLoadMetadata": "Unable to load Metadata", @@ -686,8 +840,10 @@ "UnableToLoadUISettings": "Unable to load UI settings", "Ungroup": "Ungroup", "UnmappedFiles": "UnmappedFiles", + "UnmappedFilesOnly": "Unmapped Files Only", "Unmonitored": "Unmonitored", "UnmonitoredHelpText": "Include unmonitored albums in the iCal feed", + "UnmonitoredOnly": "Unmonitored Only", "UpdateAll": "Update all", "UpdateAutomaticallyHelpText": "Automatically download and install updates. You will still be able to install from System: Updates", "UpdateMechanismHelpText": "Use Lidarr's built-in updater or a script", @@ -695,6 +851,7 @@ "UpdateScriptPathHelpText": "Path to a custom script that takes an extracted update package and handle the remainder of the update process", "UpdatingIsDisabledInsideADockerContainerUpdateTheContainerImageInstead": "Updating is disabled inside a docker container. Update the container image instead.", "UpgradeAllowedHelpText": "If disabled qualities will not be upgraded", + "UpgradesAllowed": "Upgrades Allowed", "Uptime": "Uptime", "URLBase": "URL Base", "UrlBaseHelpText": "For reverse proxy support, default is empty", @@ -709,10 +866,14 @@ "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Branch to use to update Lidarr", "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Branch used by external update mechanism", "Version": "Version", + "Wanted": "Wanted", + "Warn": "Warn", "WatchLibraryForChangesHelpText": "Rescan automatically when files change in a root folder", "WatchRootFoldersForFileChanges": "Watch Root Folders for file changes", "WeekColumnHeader": "Week Column Header", + "WouldYouLikeToRestoreBackup": "Would you like to restore the backup {0} ?", "WriteAudioTagsHelpTextWarning": "Selecting 'All files' will alter existing files when they are imported.", + "WriteMetadataTags": "Write Metadata Tags", "WriteMetadataToAudioFiles": "Write Metadata to Audio Files", "Year": "Year", "YesCancel": "Yes, Cancel" From b3587d906295593c9db6bd9636eb4931b201ae03 Mon Sep 17 00:00:00 2001 From: Qstick Date: Thu, 3 Nov 2022 22:46:48 -0500 Subject: [PATCH 0057/1478] Update various translation files --- src/NzbDrone.Core/Localization/Core/ar.json | 475 +++++++++++++++- src/NzbDrone.Core/Localization/Core/bg.json | 57 +- src/NzbDrone.Core/Localization/Core/ca.json | 475 +++++++++++++++- src/NzbDrone.Core/Localization/Core/cs.json | 54 +- src/NzbDrone.Core/Localization/Core/da.json | 48 +- src/NzbDrone.Core/Localization/Core/de.json | 25 +- src/NzbDrone.Core/Localization/Core/el.json | 56 +- src/NzbDrone.Core/Localization/Core/es.json | 64 ++- src/NzbDrone.Core/Localization/Core/fi.json | 26 +- src/NzbDrone.Core/Localization/Core/fr.json | 79 ++- src/NzbDrone.Core/Localization/Core/he.json | 56 +- src/NzbDrone.Core/Localization/Core/hi.json | 17 +- src/NzbDrone.Core/Localization/Core/hu.json | 11 +- src/NzbDrone.Core/Localization/Core/is.json | 58 +- src/NzbDrone.Core/Localization/Core/it.json | 76 ++- src/NzbDrone.Core/Localization/Core/ja.json | 11 +- src/NzbDrone.Core/Localization/Core/ko.json | 29 +- src/NzbDrone.Core/Localization/Core/lt.json | 1 + src/NzbDrone.Core/Localization/Core/lv.json | 1 + .../Localization/Core/nb_NO.json | 76 ++- src/NzbDrone.Core/Localization/Core/nl.json | 56 +- src/NzbDrone.Core/Localization/Core/pl.json | 63 ++- src/NzbDrone.Core/Localization/Core/pt.json | 509 +++++++++++++++++- .../Localization/Core/pt_BR.json | 59 +- src/NzbDrone.Core/Localization/Core/ro.json | 476 +++++++++++++++- src/NzbDrone.Core/Localization/Core/ru.json | 477 +++++++++++++++- src/NzbDrone.Core/Localization/Core/sk.json | 49 +- src/NzbDrone.Core/Localization/Core/sv.json | 45 +- src/NzbDrone.Core/Localization/Core/th.json | 475 +++++++++++++++- src/NzbDrone.Core/Localization/Core/tr.json | 59 +- src/NzbDrone.Core/Localization/Core/uk.json | 38 +- src/NzbDrone.Core/Localization/Core/vi.json | 475 +++++++++++++++- .../Localization/Core/zh_CN.json | 14 +- .../Localization/Core/zh_TW.json | 5 +- 34 files changed, 4099 insertions(+), 396 deletions(-) create mode 100644 src/NzbDrone.Core/Localization/Core/lt.json create mode 100644 src/NzbDrone.Core/Localization/Core/lv.json diff --git a/src/NzbDrone.Core/Localization/Core/ar.json b/src/NzbDrone.Core/Localization/Core/ar.json index 5f3423627..9b52c57c6 100644 --- a/src/NzbDrone.Core/Localization/Core/ar.json +++ b/src/NzbDrone.Core/Localization/Core/ar.json @@ -1,5 +1,478 @@ { "Language": "لغة", "UILanguage": "لغة واجهة المستخدم", - "ClickToChangeQuality": "انقر لتغيير الجودة" + "ClickToChangeQuality": "انقر لتغيير الجودة", + "MediaInfo": "معلومات الوسائط", + "ReleaseStatuses": "حالة الإصدار", + "TorrentDelay": "تأخير السيل", + "UnableToLoadIndexers": "تعذر تحميل المفهرسات", + "Ungroup": "فك التجميع", + "UpdateAll": "تحديث الجميع", + "Usenet": "يوزنت", + "Actions": "أجراءات", + "Authentication": "المصادقة", + "DeleteTag": "حذف العلامة", + "DiskSpace": "مساحة القرص", + "GeneralSettings": "الاعدادات العامة", + "History": "التاريخ", + "IndexerPriority": "أولوية المفهرس", + "MediaManagementSettings": "إعدادات إدارة الوسائط", + "Medium": "متوسط", + "MetadataSettings": "إعدادات البيانات الوصفية", + "MIA": "MIA", + "MinimumAge": "الحد الإدنى للعمر", + "Mode": "الوضع", + "Monitored": "مراقب", + "MoreInfo": "مزيد من المعلومات", + "OnGrabHelpText": "عند الاستيلاء", + "OnHealthIssue": "في قضية الصحة", + "OnRename": "عند إعادة التسمية", + "OnRenameHelpText": "عند إعادة التسمية", + "Options": "خيارات", + "Path": "مسار", + "Profiles": "مظهر", + "Proper": "لائق", + "ProtocolHelpText": "اختر البروتوكول (البروتوكولات) الذي تريد استخدامه وأي بروتوكول مفضل عند الاختيار بين الإصدارات المتساوية", + "ProxyType": "نوع الوكيل", + "QualityProfiles": "ملامح الجودة", + "QualitySettings": "إعدادات الجودة", + "Queue": "طابور", + "RecycleBinCleanupDaysHelpText": "اضبط على 0 لتعطيل التنظيف التلقائي", + "RecycleBinCleanupDaysHelpTextWarning": "سيتم تنظيف الملفات الموجودة في سلة المحذوفات الأقدم من عدد الأيام المحدد تلقائيًا", + "RecyclingBinCleanup": "تنظيف سلة إعادة التدوير", + "Redownload": "إعادة التنزيل", + "Refresh": "تحديث", + "RefreshInformationAndScanDisk": "تحديث المعلومات ومسح القرص", + "ReleaseGroup": "مجموعة الإصدار", + "ReleaseRejected": "تحرير مرفوض", + "ReleaseWillBeProcessedInterp": "ستتم معالجة الإصدار {0}", + "Reload": "إعادة تحميل", + "RemotePath": "مسار بعيد", + "RemotePathHelpText": "مسار الجذر إلى الدليل الذي يصل إليه Download Client", + "RemoveCompletedDownloadsHelpText": "قم بإزالة التنزيلات المستوردة من سجل عميل التنزيل", + "RemovedFromTaskQueue": "تمت إزالته من قائمة انتظار المهام", + "RemoveFailedDownloadsHelpText": "قم بإزالة التنزيلات الفاشلة من سجل عميل التنزيل", + "RemoveFilter": "قم بإزالة الفلتر", + "RemoveFromBlocklist": "إزالة من القائمة السوداء", + "RemoveFromDownloadClient": "إزالة من Download Client", + "RemoveFromQueue": "إزالة من قائمة الانتظار", + "RemoveHelpTextWarning": "ستؤدي الإزالة إلى إزالة التنزيل والملف (الملفات) من عميل التنزيل.", + "RemoveSelected": "ازل المحدد", + "RemoveTagExistingTag": "علامة موجودة", + "ReplaceIllegalCharactersHelpText": "استبدل الأحرف غير القانونية. إذا لم يتم تحديده ، فسوف يقوم Lidarr بإزالتها بدلاً من ذلك", + "RequiredHelpText": "يجب أن يحتوي الإصدار على واحد على الأقل من هذه المصطلحات (غير حساس لحالة الأحرف)", + "RequiredPlaceHolder": "أضف قيدًا جديدًا", + "RequiresRestartToTakeEffect": "يتطلب إعادة التشغيل ليصبح ساري المفعول", + "RescanAfterRefreshHelpText": "أعد فحص مجلد الفيلم بعد تحديث الفيلم", + "RescanAfterRefreshHelpTextWarning": "لن يكتشف Lidarr تلقائيًا التغييرات التي تطرأ على الملفات عند عدم تعيينه على \"دائمًا\"", + "RescanArtistFolderAfterRefresh": "إعادة فحص مجلد الفيلم بعد التحديث", + "Reset": "إعادة تعيين", + "ResetAPIKey": "إعادة تعيين مفتاح API", + "ResetAPIKeyMessageText": "هل أنت متأكد أنك تريد إعادة تعيين مفتاح API الخاص بك؟", + "Restart": "إعادة تشغيل", + "RestartLidarr": "أعد تشغيل Lidarr", + "RestartNow": "اعد البدء الان", + "RSSSync": "مزامنة RSS", + "RSSSyncInterval": "الفاصل الزمني لمزامنة RSS", + "RssSyncIntervalHelpText": "الفاصل بالدقائق. اضبط على صفر للتعطيل (سيؤدي هذا إلى إيقاف كل عمليات الاستيلاء على التحرير التلقائي)", + "Scheduled": "المقرر", + "Score": "أحرز هدفا", + "Search": "بحث", + "SearchAll": "بحث عن", + "SearchForMissing": "البحث عن المفقودين", + "SearchSelected": "بحث مُحدد", + "Security": "الأمان", + "SetPermissions": "تعيين أذونات", + "SetPermissionsLinuxHelpText": "هل يجب تشغيل chmod عند استيراد الملفات / إعادة تسميتها؟", + "SetPermissionsLinuxHelpTextWarning": "إذا لم تكن متأكدًا من وظيفة هذه الإعدادات ، فلا تقم بتغييرها.", + "Settings": "الإعدادات", + "ShortDateFormat": "تنسيق التاريخ القصير", + "ShowCutoffUnmetIconHelpText": "إظهار رمز للملفات عندما لا يتم الوفاء بالقطع", + "ShowMonitored": "عرض مراقب", + "ShowMonitoredHelpText": "إظهار حالة المراقبة تحت الملصق", + "ShownAboveEachColumnWhenWeekIsTheActiveView": "يظهر فوق كل عمود عندما يكون الأسبوع هو العرض النشط", + "ShowPath": "عرض المسار", + "ShowQualityProfile": "عرض ملف تعريف الجودة", + "ShowQualityProfileHelpText": "إظهار ملف تعريف الجودة تحت الملصق", + "ShowRelativeDates": "إظهار التواريخ النسبية", + "ShowRelativeDatesHelpText": "عرض نسبي (اليوم / أمس / إلخ) أو التواريخ المطلقة", + "ShowSearchActionHelpText": "إظهار زر البحث عند التمرير", + "ShowSizeOnDisk": "عرض الحجم على القرص", + "ShowUnknownArtistItems": "إظهار عناصر الفيلم غير المعروفة", + "SkipFreeSpaceCheck": "تخطي فحص المساحة الخالية", + "SkipFreeSpaceCheckWhenImportingHelpText": "استخدم عندما يتعذر على Lidarr اكتشاف مساحة خالية من مجلد جذر الفيلم", + "SorryThatArtistCannotBeFound": "آسف ، لا يمكن العثور على هذا الفيلم.", + "SSLCertPassword": "كلمة مرور شهادة SSL", + "SslCertPasswordHelpText": "كلمة المرور لملف pfx", + "SslCertPasswordHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول", + "SSLCertPath": "مسار شهادة SSL", + "SslCertPathHelpText": "مسار ملف pfx", + "SslCertPathHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول", + "SSLPort": "منفذ SSL", + "SslPortHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول", + "StandardTrackFormat": "تنسيق الفيلم القياسي", + "StartTypingOrSelectAPathBelow": "ابدأ الكتابة أو حدد المسار أدناه", + "StartupDirectory": "دليل بدء التشغيل", + "SuccessMyWorkIsDoneNoFilesToRename": "نجاح! تم الانتهاء من عملي ، ولا توجد ملفات لإعادة تسميتها.", + "SuccessMyWorkIsDoneNoFilesToRetag": "نجاح! تم الانتهاء من عملي ، ولا توجد ملفات لإعادة تسميتها.", + "RootFolders": "مجلدات الجذر", + "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "البحث غير معتمد مع هذا المفهرس", + "ScriptPath": "مسار البرنامج النصي", + "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "سيتم استخدامها عند استخدام البحث التفاعلي", + "TestAll": "اختبار الكل", + "TestAllIndexers": "اختبار كافة المفهرسات", + "TestAllLists": "اختبر كل القوائم", + "SendAnonymousUsageData": "إرسال بيانات الاستخدام المجهولة", + "ThisCannotBeCancelled": "لا يمكن إلغاء هذا بمجرد البدء دون إعادة تشغيل Whisparr.", + "TorrentDelayHelpText": "تأخير في دقائق للانتظار قبل الاستيلاء على سيل", + "ShowSearch": "إظهار البحث", + "Size": " بحجم", + "Track": "أثر", + "Source": "مصدر", + "SourcePath": "مسار المصدر", + "UnableToAddANewDownloadClientPleaseTryAgain": "غير قادر على إضافة عميل تنزيل جديد ، يرجى المحاولة مرة أخرى.", + "IndexerSettings": "إعدادات المفهرس", + "IsCutoffCutoff": "قطع", + "UnableToAddANewImportListExclusionPleaseTryAgain": "تعذر إضافة استبعاد قائمة جديدة ، يرجى المحاولة مرة أخرى.", + "Local": "محلي", + "Mechanism": "آلية", + "UnableToAddANewMetadataProfilePleaseTryAgain": "غير قادر على إضافة ملف تعريف جودة جديد ، يرجى المحاولة مرة أخرى.", + "MustContain": "يجب أن يحتوي على", + "Name": "اسم", + "NoUpdatesAreAvailable": "لا توجد تحديثات متوفرة", + "PageSizeHelpText": "عدد العناصر التي سيتم عرضها في كل صفحة", + "Permissions": "أذونات", + "PublishedDate": "تاريخ النشر", + "Quality": "جودة", + "QualityDefinitions": "تعريفات الجودة", + "UnableToAddANewNotificationPleaseTryAgain": "تعذر إضافة إشعار جديد ، يرجى المحاولة مرة أخرى.", + "UnableToAddANewQualityProfilePleaseTryAgain": "غير قادر على إضافة ملف تعريف جودة جديد ، يرجى المحاولة مرة أخرى.", + "ReleaseDate": "يوم الاصدار", + "RemoveSelectedMessageText": "هل أنت متأكد أنك تريد إزالة العناصر المحددة من القائمة السوداء؟", + "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "لا يتم دعم RSS مع هذا المفهرس", + "TagIsNotUsedAndCanBeDeleted": "العلامة غير مستخدمة ويمكن حذفها", + "TestAllClients": "اختبر كل العملاء", + "Tracks": "أثر", + "UnableToLoadHistory": "تعذر تحميل التاريخ", + "UsenetDelay": "تأخير يوزنت", + "UnableToAddANewRemotePathMappingPleaseTryAgain": "غير قادر على إضافة تعيين مسار بعيد جديد ، يرجى المحاولة مرة أخرى.", + "UnableToAddANewRootFolderPleaseTryAgain": "غير قادر على إضافة مفهرس جديد ، يرجى المحاولة مرة أخرى.", + "UnableToLoadBlocklist": "تعذر تحميل القائمة السوداء", + "UnableToLoadDelayProfiles": "تعذر تحميل ملفات تعريف التأخير", + "UnableToLoadDownloadClientOptions": "تعذر تحميل خيارات عميل التنزيل", + "UnableToLoadDownloadClients": "تعذر تحميل عملاء التنزيل", + "UnableToLoadGeneralSettings": "تعذر تحميل الإعدادات العامة", + "UnableToLoadImportListExclusions": "تعذر تحميل استثناءات القائمة", + "UnableToLoadMetadata": "تعذر تحميل البيانات الوصفية", + "UnableToLoadMetadataProfiles": "تعذر تحميل ملفات تعريف التأخير", + "UnableToLoadNamingSettings": "تعذر تحميل إعدادات التسمية", + "UnableToLoadNotifications": "تعذر تحميل الإخطارات", + "UnableToLoadQualityDefinitions": "تعذر تحميل تعريفات الجودة", + "UnableToLoadQualityProfiles": "تعذر تحميل ملفات تعريف الجودة", + "UnableToLoadReleaseProfiles": "تعذر تحميل ملفات تعريف التأخير", + "UnableToLoadRemotePathMappings": "تعذر تحميل تعيينات المسار البعيد", + "Unmonitored": "غير خاضع للرقابة", + "UnmonitoredHelpText": "قم بتضمين الأفلام غير الخاضعة للرقابة في موجز iCal", + "UpdateAutomaticallyHelpText": "تنزيل التحديثات وتثبيتها تلقائيًا. ستظل قادرًا على التثبيت من النظام: التحديثات", + "UpdateMechanismHelpText": "استخدم المحدث أو البرنامج النصي المدمج في Lidarr", + "Updates": "التحديثات", + "UpdateScriptPathHelpText": "المسار إلى برنامج نصي مخصص يأخذ حزمة تحديث مستخرجة ويتعامل مع ما تبقى من عملية التحديث", + "UrlBaseHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول", + "UseHardlinksInsteadOfCopy": "استخدم الروابط الثابتة بدلاً من النسخ", + "Username": "اسم المستخدم", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "فرع لاستخدامه لتحديث Lidarr", + "ApiKeyHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول", + "AppDataDirectory": "دليل AppData", + "ApplyTags": "تطبيق العلامات", + "ApplyTagsHelpTexts1": "كيفية تطبيق العلامات على الأفلام المختارة", + "ApplyTagsHelpTexts2": "إضافة: أضف العلامات إلى قائمة العلامات الموجودة", + "ApplyTagsHelpTexts3": "إزالة: قم بإزالة العلامات التي تم إدخالها", + "ApplyTagsHelpTexts4": "استبدال: استبدل العلامات بالعلامات التي تم إدخالها (لا تدخل أي علامات لمسح جميع العلامات)", + "ArtistAlbumClickToChangeTrack": "انقر لتغيير الفيلم", + "AuthenticationMethodHelpText": "طلب اسم المستخدم وكلمة المرور للوصول إلى Lidarr", + "AutoRedownloadFailedHelpText": "ابحث تلقائيًا عن إصدار مختلف وحاول تنزيله", + "BackupFolderHelpText": "ستكون المسارات النسبية ضمن دليل AppData الخاص بـ Lidarr", + "BackupNow": "اعمل نسخة احتياطية الان", + "BackupRetentionHelpText": "سيتم تنظيف النسخ الاحتياطية التلقائية الأقدم من فترة الاحتفاظ تلقائيًا", + "Backups": "النسخ الاحتياطية", + "BindAddress": "عنوان ملزم", + "BindAddressHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول", + "Blocklist": "القائمة السوداء", + "BlocklistHelpText": "يمنع Lidarr من الاستيلاء على هذا الإصدار تلقائيًا مرة أخرى", + "BlocklistRelease": "إصدار القائمة السوداء", + "Branch": "فرع شجرة", + "BypassProxyForLocalAddresses": "تجاوز الوكيل للعناوين المحلية", + "Calendar": "التقويم", + "CalendarWeekColumnHeaderHelpText": "يظهر فوق كل عمود عندما يكون الأسبوع هو العرض النشط", + "ChangeHasNotBeenSavedYet": "لم يتم حفظ التغيير بعد", + "ChmodFolder": "مجلد chmod", + "ChmodFolderHelpText": "Octal ، يتم تطبيقه أثناء الاستيراد / إعادة التسمية إلى مجلدات وملفات الوسائط (بدون تنفيذ بت)", + "ChmodFolderHelpTextWarning": "يعمل هذا فقط إذا كان المستخدم الذي يقوم بتشغيل Lidarr هو مالك الملف. من الأفضل التأكد من قيام عميل التنزيل بتعيين الأذونات بشكل صحيح.", + "ChownGroupHelpText": "اسم المجموعة أو gid. استخدم gid لأنظمة الملفات البعيدة.", + "ChownGroupHelpTextWarning": "يعمل هذا فقط إذا كان المستخدم الذي يقوم بتشغيل Lidarr هو مالك الملف. من الأفضل التأكد من أن عميل التنزيل يستخدم نفس مجموعة Lidarr.", + "Clear": "واضح", + "ClientPriority": "أولوية العميل", + "CloneIndexer": "مفهرس استنساخ", + "CloneProfile": "الملف الشخصي استنساخ", + "Columns": "الأعمدة", + "CompletedDownloadHandling": "معالجة التحميل الكامل", + "Component": "مكون", + "Connections": "روابط", + "ConnectSettings": "ربط الإعدادات", + "CopyUsingHardlinksHelpText": "استخدم Hardlinks عند محاولة نسخ الملفات من السيول التي لا تزال تحت البذور", + "CopyUsingHardlinksHelpTextWarning": "من حين لآخر ، قد تمنع أقفال الملفات إعادة تسمية الملفات التي يتم زرعها. يمكنك تعطيل البذر مؤقتًا واستخدام وظيفة إعادة تسمية Lidarr كحل بديل.", + "CreateEmptyArtistFolders": "إنشاء مجلدات أفلام فارغة", + "CreateEmptyArtistFoldersHelpText": "قم بإنشاء مجلدات فيلم مفقودة أثناء فحص القرص", + "CreateGroup": "إنشاء مجموعة", + "CutoffHelpText": "بمجرد الوصول إلى هذه الجودة ، لن يقوم Lidarr بتنزيل الأفلام", + "CutoffUnmet": "قطع غير ملباة", + "Dates": "تواريخ", + "DBMigration": "ترحيل DB", + "DelayProfile": "ملف التأخير", + "DelayProfiles": "ملفات تعريف التأخير", + "Delete": "حذف", + "DeleteBackup": "حذف النسخة الاحتياطية", + "DeleteBackupMessageText": "هل تريد بالتأكيد حذف النسخة الاحتياطية \"{0}\"؟", + "DeleteDelayProfile": "حذف ملف تعريف التأخير", + "DeleteDelayProfileMessageText": "هل أنت متأكد أنك تريد حذف ملف تعريف التأخير هذا؟", + "DeleteDownloadClient": "حذف Download Client", + "DeleteDownloadClientMessageText": "هل أنت متأكد أنك تريد حذف برنامج التنزيل \"{0}\"؟", + "DeleteEmptyFolders": "احذف المجلدات الفارغة", + "DeleteImportListExclusion": "حذف استبعاد قائمة الاستيراد", + "DeleteImportListExclusionMessageText": "هل أنت متأكد أنك تريد حذف استثناء قائمة الاستيراد؟", + "DeleteImportListMessageText": "هل أنت متأكد أنك تريد حذف القائمة \"{0}\"؟", + "DeleteIndexer": "حذف المفهرس", + "DeleteIndexerMessageText": "هل أنت متأكد أنك تريد حذف المفهرس \"{0}\"؟", + "DeleteMetadataProfileMessageText": "هل أنت متأكد من أنك تريد حذف ملف تعريف الجودة {0}", + "DeleteNotification": "حذف الإعلام", + "DeleteNotificationMessageText": "هل تريد بالتأكيد حذف الإشعار \"{0}\"؟", + "DeleteQualityProfile": "حذف ملف تعريف الجودة", + "DeleteQualityProfileMessageText": "هل أنت متأكد من أنك تريد حذف ملف تعريف الجودة {0}", + "DeleteReleaseProfile": "حذف ملف تعريف التأخير", + "DeleteReleaseProfileMessageText": "هل أنت متأكد أنك تريد حذف ملف تعريف التأخير هذا؟", + "DeleteRootFolderMessageText": "هل أنت متأكد أنك تريد حذف المفهرس \"{0}\"؟", + "DeleteSelectedTrackFiles": "حذف ملفات الأفلام المحددة", + "DeleteSelectedTrackFilesMessageText": "هل أنت متأكد أنك تريد حذف ملفات الأفلام المحددة؟", + "DeleteTagMessageText": "هل أنت متأكد أنك تريد حذف العلامة \"{0}\"؟", + "DestinationPath": "مسار الوجهة", + "DetailedProgressBar": "شريط تقدم مفصل", + "DetailedProgressBarHelpText": "إظهار النص على شريط التقدم", + "DownloadClient": "تحميل العميل", + "DownloadClients": "تحميل العملاء", + "DownloadClientSettings": "تنزيل إعدادات العميل", + "DownloadFailedCheckDownloadClientForMoreDetails": "فشل التنزيل: تحقق من عميل التنزيل لمزيد من التفاصيل", + "DownloadFailedInterp": "فشل التنزيل: {0}", + "Downloading": "جارى التحميل", + "DownloadPropersAndRepacksHelpTexts1": "ما إذا كان سيتم الترقية تلقائيًا إلى Propers / Repacks أم لا", + "DownloadWarningCheckDownloadClientForMoreDetails": "تحذير التنزيل: تحقق من عميل التنزيل لمزيد من التفاصيل", + "Edit": "تعديل", + "Enable": "ممكن", + "EnableAutomaticAdd": "تمكين إضافة تلقائية", + "EnableAutomaticSearch": "تمكين البحث التلقائي", + "EnableColorImpairedMode": "تفعيل وضع ضعف الألوان", + "EnableColorImpairedModeHelpText": "تم تغيير النمط للسماح للمستخدمين الذين يعانون من ضعف الألوان بتمييز المعلومات المشفرة بالألوان بشكل أفضل", + "EnableCompletedDownloadHandlingHelpText": "استيراد التنزيلات المكتملة تلقائيًا من عميل التنزيل", + "EnableHelpText": "تفعيل إنشاء ملف البيانات الوصفية لنوع البيانات الوصفية هذا", + "EnableInteractiveSearch": "تمكين البحث التفاعلي", + "EnableRSS": "تمكين RSS", + "EnableSSL": "تمكين SSL", + "EnableSslHelpText": " يتطلب إعادة التشغيل قيد التشغيل كمسؤول ليصبح ساري المفعول", + "Ended": "انتهى", + "ErrorLoadingContents": "خطأ في تحميل المحتويات", + "ErrorLoadingPreviews": "خطأ في تحميل المعاينات", + "Exception": "استثناء", + "ExtraFileExtensionsHelpTexts1": "قائمة مفصولة بفواصل بالملفات الإضافية المراد استيرادها (سيتم استيراد .nfo كـ .nfo-Orig)", + "FileDateHelpText": "تغيير تاريخ الملف عند الاستيراد / إعادة الفحص", + "FileManagement": "إدارة الملفات", + "Filename": "اسم الملف", + "FileNames": "أسماء الملفات", + "Files": "الملفات", + "ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "لمزيد من المعلومات حول عملاء التنزيل الفرديين ، انقر فوق أزرار المعلومات.", + "FirstDayOfWeek": "اليوم الأول من الأسبوع", + "Fixed": "ثابت", + "Folder": "مجلد", + "Folders": "المجلدات", + "ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "لمزيد من المعلومات حول المفهرسات الفردية ، انقر فوق أزرار المعلومات.", + "ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "لمزيد من المعلومات حول قوائم الاستيراد الفردية ، انقر فوق أزرار المعلومات.", + "Global": "عالمي", + "GoToInterp": "انتقل إلى {0}", + "Grab": "إختطاف", + "GrabID": "انتزاع معرف", + "GrabRelease": "انتزاع الإصدار", + "GrabReleaseMessageText": "لم يتمكن Lidarr من تحديد الفيلم الذي كان هذا الإصدار من أجله. قد يتعذر على Lidarr استيراد هذا الإصدار تلقائيًا. هل تريد انتزاع \"{0}\"؟", + "GrabSelected": "انتزاع المحدد", + "Group": "مجموعة", + "HasPendingChangesNoChanges": "لا تغيرات", + "HasPendingChangesSaveChanges": "حفظ التغييرات", + "Host": "مضيف", + "HostHelpText": "نفس المضيف الذي حددته لبرنامج Download Client البعيد", + "Hostname": "اسم المضيف", + "ICalFeed": "تغذية iCal", + "ICalHttpUrlHelpText": "انسخ عنوان URL هذا إلى العميل (العملاء) أو انقر للاشتراك إذا كان متصفحك يدعم webcal", + "ICalLink": "رابط iCal", + "IconForCutoffUnmet": "رمز القطع غير الملائم", + "IconTooltip": "المقرر", + "IgnoredAddresses": "العناوين التي تم تجاهلها", + "IgnoredHelpText": "سيتم رفض الإصدار إذا كان يحتوي على واحد أو أكثر من المصطلحات (غير حساس لحالة الأحرف)", + "IgnoredPlaceHolder": "أضف قيدًا جديدًا", + "IllRestartLater": "سأعيد التشغيل لاحقًا", + "ImportedTo": "مستورد إلى", + "ImportExtraFiles": "استيراد ملفات اضافية", + "ImportExtraFilesHelpText": "استيراد ملفات إضافية مطابقة (ترجمات ، nfo ، إلخ) بعد استيراد ملف فيلم", + "ImportFailedInterp": "فشل الاستيراد: {0}", + "Importing": "استيراد", + "IncludeHealthWarningsHelpText": "قم بتضمين التحذيرات الصحية", + "IncludeUnknownArtistItemsHelpText": "إظهار العناصر بدون فيلم في قائمة الانتظار. يمكن أن يشمل ذلك الأفلام التي تمت إزالتها أو أي شيء آخر في فئة Lidarr", + "IncludeUnmonitored": "تضمين غير مراقب", + "Indexer": "مفهرس", + "Indexers": "مفهرسات", + "InteractiveSearch": "بحث تفاعلي", + "Interval": "فترة", + "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "قم بالترقية حتى يتم تلبية هذه الجودة أو تجاوزها", + "IsTagUsedCannotBeDeletedWhileInUse": "لا يمكن حذفه أثناء الاستخدام", + "LaunchBrowserHelpText": " افتح مستعرض ويب وانتقل إلى صفحة Lidarr الرئيسية عند بدء التطبيق.", + "Level": "مستوى", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "يدعم Whisparr أي عميل تنزيل يستخدم معيار Newznab ، بالإضافة إلى عملاء التنزيل الآخرين المدرجة أدناه.", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "يدعم Lidarr أي مفهرس يستخدم معيار Newznab ، بالإضافة إلى مفهرسات أخرى مذكورة أدناه.", + "LidarrTags": "العلامات الرادار", + "LoadingTrackFilesFailed": "فشل تحميل ملفات الفيلم", + "LocalPath": "مسار محلي", + "LocalPathHelpText": "المسار الذي يجب أن يستخدمه Lidarr للوصول إلى المسار البعيد محليًا", + "LogFiles": "ملفات الدخول", + "Logging": "تسجيل", + "LogLevel": "تسجيل مستوى", + "LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "يجب تمكين تسجيل التتبع مؤقتًا فقط", + "Logs": "السجلات", + "LongDateFormat": "تنسيق التاريخ الطويل", + "ManualImport": "استيراد يدوي", + "MarkAsFailed": "وضع علامة فشل", + "MarkAsFailedMessageText": "هل أنت متأكد أنك تريد وضع علامة \"{0}\" على أنه فشل؟", + "MaximumLimits": "الحدود القصوى", + "MaximumSize": "أكبر مقاس", + "MaximumSizeHelpText": "الحد الأقصى لحجم الإصدار المراد الحصول عليه بالميجابايت. اضبط على الصفر للتعيين إلى غير محدود", + "Message": "رسالة", + "MustNotContain": "يجب ألا يحتوي", + "NamingSettings": "إعدادات التسمية", + "New": "جديد", + "NoLeaveIt": "لا ، اتركها", + "NoLimitForAnyRuntime": "لا يوجد حد لأي وقت تشغيل", + "NoLogFiles": "لا توجد ملفات سجل", + "NoMinimumForAnyRuntime": "لا يوجد حد أدنى لأي وقت تشغيل", + "None": "لا شيء", + "NotificationTriggers": "مشغلات الإخطار", + "PriorityHelpText": "أولوية المفهرس من 1 (الأعلى) إلى 50 (الأدنى). الافتراضي: 25.", + "ReadTheWikiForMoreInformation": "اقرأ Wiki لمزيد من المعلومات", + "RemoveTagRemovingTag": "إزالة العلامة", + "RenameTracksHelpText": "سيستخدم Lidarr اسم الملف الحالي إذا تم تعطيل إعادة التسمية", + "Reorder": "إعادة ترتيب", + "ReplaceIllegalCharacters": "استبدل الأحرف غير القانونية", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "سيتم استخدامه عند إجراء عمليات البحث التلقائي عبر واجهة المستخدم أو بواسطة Lidarr", + "Tags": "العلامات", + "Torrents": "السيول", + "TotalFileSize": "إجمالي حجم الملف", + "QualityProfile": "ملف الجودة", + "RemotePathMappings": "تعيينات المسار البعيد", + "Remove": "إزالة", + "Restore": "استعادة", + "RestoreBackup": "استرجاع النسخة الاحتياطية", + "Result": "نتيجة", + "Retention": "احتفاظ", + "RetentionHelpText": "Usenet فقط: اضبط على صفر لتعيين احتفاظ غير محدود", + "RetryingDownloadInterp": "إعادة محاولة التنزيل {0} في {1}", + "Status": "الحالة", + "Time": "زمن", + "TimeFormat": "تنسيق الوقت", + "UnableToLoadTheCalendar": "تعذر تحميل التقويم", + "UnableToLoadUISettings": "تعذر تحميل إعدادات واجهة المستخدم", + "Year": "عام", + "YesCancel": "نعم إلغاء", + "UILanguageHelpTextWarning": "يلزم إعادة تحميل المتصفح", + "UISettings": "إعدادات واجهة المستخدم", + "UnableToAddANewIndexerPleaseTryAgain": "غير قادر على إضافة مفهرس جديد ، يرجى المحاولة مرة أخرى.", + "UnableToAddANewListPleaseTryAgain": "غير قادر على إضافة قائمة جديدة ، يرجى المحاولة مرة أخرى.", + "About": "نبدة عن", + "AddListExclusion": "إضافة استبعاد قائمة", + "AddingTag": "مضيفا العلامة", + "AdvancedSettingsHiddenClickToShow": "مخفي ، انقر للعرض", + "AdvancedSettingsShownClickToHide": "يظهر ، انقر للإخفاء", + "AgeWhenGrabbed": "العمر (عند الإمساك)", + "AlbumIsDownloadingInterp": "يتم تنزيل الفيلم - {0}٪ {1}", + "MinimumLimits": "الحد الأدنى", + "Missing": "مفقود", + "BindAddressHelpText": "عنوان IP4 صالح أو \"*\" لجميع الواجهات", + "CertificateValidationHelpText": "تغيير مدى صرامة التحقق من صحة شهادة HTTPS", + "OnGrab": "عند الاستيلاء", + "OnHealthIssueHelpText": "في قضية الصحة", + "OnUpgrade": "عند الترقية", + "OnUpgradeHelpText": "عند الترقية", + "OpenBrowserOnStart": "افتح المتصفح عند البدء", + "Original": "أصلي", + "PackageVersion": "إصدار الحزمة", + "PageSize": "مقاس الصفحه", + "Password": "كلمه السر", + "Port": "ميناء", + "PortNumber": "رقم المنفذ", + "PosterSize": "حجم الملصق", + "Preferred": "يفضل", + "PreviewRename": "معاينة إعادة تسمية", + "PropersAndRepacks": "المناسبين و Repacks", + "Protocol": "بروتوكول", + "Proxy": "الوكيل", + "ProxyBypassFilterHelpText": "استخدم \"،\" كفاصل ، و \"*.\" كحرف بدل للنطاقات الفرعية", + "ProxyPasswordHelpText": "ما عليك سوى إدخال اسم مستخدم وكلمة مرور إذا كان أحدهما مطلوبًا. اتركها فارغة وإلا.", + "ProxyUsernameHelpText": "ما عليك سوى إدخال اسم مستخدم وكلمة مرور إذا كان أحدهما مطلوبًا. اتركها فارغة وإلا.", + "Real": "حقيقة", + "Reason": "السبب", + "RecycleBinHelpText": "ستنتقل ملفات الأفلام إلى هنا عند حذفها بدلاً من حذفها نهائيًا", + "RecyclingBin": "صندوق إعادة التدوير", + "SorryThatAlbumCannotBeFound": "آسف ، لا يمكن العثور على هذا الفيلم.", + "Style": "أسلوب", + "Tasks": "مهام", + "ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "سينطبق هذا على جميع المفهرسين ، يرجى اتباع القواعد المنصوص عليها من قبلهم", + "UnableToLoadBackups": "تعذر تحميل النسخ الاحتياطية", + "Type": "اكتب", + "UnableToLoadIndexerOptions": "تعذر تحميل خيارات المفهرس", + "UnableToLoadMediaManagementSettings": "تعذر تحميل إعدادات إدارة الوسائط", + "UnableToLoadQualities": "غير قادر على تحميل الصفات", + "UsenetDelayHelpText": "تأخر بالدقائق للانتظار قبل الحصول على إصدار من Usenet", + "UseProxy": "استخدام بروكسي", + "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "يستخدم الفرع بواسطة آلية التحديث الخارجية", + "Version": "الإصدار", + "UILanguageHelpText": "اللغة التي سيستخدمها Lidarr لواجهة المستخدم", + "UpgradeAllowedHelpText": "إذا لن تتم ترقية الصفات المعوقين", + "Uptime": "مدة التشغيل", + "URLBase": "قاعدة URL", + "UrlBaseHelpText": "لدعم الوكيل العكسي ، الافتراضي فارغ", + "WeekColumnHeader": "رأس عمود الأسبوع", + "20MinutesTwenty": "120 دقيقة: {0}", + "45MinutesFourtyFive": "90 دقيقة: {0}", + "60MinutesSixty": "60 دقيقة: {0}", + "APIKey": "مفتاح API", + "AlreadyInYourLibrary": "بالفعل في مكتبتك", + "AlternateTitles": "عنوان بديل", + "AlternateTitleslength1Title": "عنوان", + "AlternateTitleslength1Titles": "الألقاب", + "Analytics": "تحليلات", + "AnalyticsEnabledHelpText": "إرسال معلومات الاستخدام والخطأ المجهولة إلى خوادم Lidarr. يتضمن ذلك معلومات حول متصفحك ، وصفحات Lidarr WebUI التي تستخدمها ، والإبلاغ عن الأخطاء بالإضافة إلى إصدار نظام التشغيل ووقت التشغيل. سنستخدم هذه المعلومات لتحديد أولويات الميزات وإصلاحات الأخطاء.", + "AnalyticsEnabledHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول", + "Automatic": "تلقائي", + "Cancel": "إلغاء", + "CancelMessageText": "هل أنت متأكد أنك تريد إلغاء هذه المهمة المعلقة؟", + "CertificateValidation": "التحقق من صحة الشهادة", + "ChangeFileDate": "تغيير تاريخ الملف", + "DelayingDownloadUntilInterp": "تأجيل التنزيل حتى {0} الساعة {1}", + "MinimumAgeHelpText": "Usenet فقط: الحد الأدنى للعمر بالدقائق من NZBs قبل الاستيلاء عليها. استخدم هذا لمنح الإصدارات الجديدة وقتًا للنشر إلى مزود usenet.", + "MinimumFreeSpace": "أدنى مساحة حرة", + "MinimumFreeSpaceWhenImportingHelpText": "منع الاستيراد إذا كان سيترك أقل من هذا القدر من مساحة القرص المتوفرة", + "NoBackupsAreAvailable": "لا توجد نسخ احتياطية متاحة", + "NoHistory": "لا تاريخ", + "RefreshScan": "التحديث والمسح الضوئي", + "RootFolder": "المجلد الرئيسي", + "ShowDateAdded": "إظهار تاريخ الإضافة", + "UnableToLoadLists": "تعذر تحميل القوائم", + "UnableToLoadRootFolders": "تعذر تحميل مجلدات الجذر", + "UnableToLoadTags": "تعذر تحميل العلامات", + "MonoVersion": "نسخة أحادية", + "Docker": "عامل ميناء", + "NETCore": ".شبكة" } diff --git a/src/NzbDrone.Core/Localization/Core/bg.json b/src/NzbDrone.Core/Localization/Core/bg.json index 40e92ee55..2330f4e80 100644 --- a/src/NzbDrone.Core/Localization/Core/bg.json +++ b/src/NzbDrone.Core/Localization/Core/bg.json @@ -2,7 +2,7 @@ "Language": "Език", "UILanguage": "Език на потребителския интерфейс", "AutoRedownloadFailedHelpText": "Автоматично търсете и се опитвайте да изтеглите различна версия", - "BackupFolderHelpText": "Относителните пътища ще бъдат в директорията AppData на Radarr", + "BackupFolderHelpText": "Относителните пътища ще бъдат в директорията AppData на Lidarr", "BackupNow": "Архивиране сега", "BackupRetentionHelpText": "Автоматичните архиви, по-стари от периода на съхранение, ще бъдат почистени автоматично", "BindAddress": "Адрес за обвързване", @@ -28,10 +28,10 @@ "RecycleBinCleanupDaysHelpTextWarning": "Файловете в кошчето, по-стари от избрания брой дни, ще се почистват автоматично", "RemoveTagExistingTag": "Съществуващ маркер", "RemoveTagRemovingTag": "Премахване на етикет", - "RenameTracksHelpText": "Radarr ще използва съществуващото име на файл, ако преименуването е деактивирано", + "RenameTracksHelpText": "Lidarr ще използва съществуващото име на файл, ако преименуването е деактивирано", "Reorder": "Пренареждане", "ReplaceIllegalCharacters": "Заменете незаконните символи", - "ReplaceIllegalCharactersHelpText": "Заменете незаконните символи. Ако не е отметнато, Radarr ще ги премахне вместо това", + "ReplaceIllegalCharactersHelpText": "Заменете незаконните символи. Ако не е отметнато, Lidarr ще ги премахне вместо това", "RequiredHelpText": "Освобождаването трябва да съдържа поне един от тези термини (без регистрация)", "RequiredPlaceHolder": "Добавете ново ограничение", "RescanAfterRefreshHelpText": "Повторно сканиране на папката с филм след освежаване на филма", @@ -67,7 +67,7 @@ "UnableToLoadQualityProfiles": "Не може да се заредят качествени профили", "UnableToLoadReleaseProfiles": "Профилите за забавяне не могат да се заредят", "UnableToLoadTheCalendar": "Календарът не може да се зареди", - "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Клон, който да се използва за актуализиране на Radarr", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Клон, който да се използва за актуализиране на Lidarr", "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Клон, използван от външен механизъм за актуализация", "Version": "Версия", "WeekColumnHeader": "Заглавка на колоната на седмицата", @@ -83,15 +83,15 @@ "AlternateTitles": "Алтернативно заглавие", "AlternateTitleslength1Title": "Заглавие", "AlternateTitleslength1Titles": "Заглавия", - "AnalyticsEnabledHelpText": "Изпращайте анонимна информация за използването и грешките до сървърите на Radarr. Това включва информация за вашия браузър, кои страници на Radarr WebUI използвате, отчитане на грешки, както и версията на операционната система и времето за изпълнение. Ще използваме тази информация, за да дадем приоритет на функциите и корекциите на грешки.", + "AnalyticsEnabledHelpText": "Изпращайте анонимна информация за използването и грешките до сървърите на Lidarr. Това включва информация за вашия браузър, кои страници на Lidarr WebUI използвате, отчитане на грешки, както и версията на операционната система и времето за изпълнение. Ще използваме тази информация, за да дадем приоритет на функциите и корекциите на грешки.", "AnalyticsEnabledHelpTextWarning": "Изисква рестартиране, за да влезе в сила", "Automatic": "Автоматично", "CopyUsingHardlinksHelpText": "Използвайте твърди връзки, когато се опитвате да копирате файлове от торенти, които все още се посяват", - "CopyUsingHardlinksHelpTextWarning": "Понякога заключванията на файлове могат да попречат на преименуване на файлове, които се засяват. Можете временно да деактивирате засяването и да използвате функцията за преименуване на Radarr като работа.", + "CopyUsingHardlinksHelpTextWarning": "Понякога заключванията на файлове могат да попречат на преименуване на файлове, които се засяват. Можете временно да деактивирате засяването и да използвате функцията за преименуване на Lidarr като работа.", "CreateEmptyArtistFolders": "Създайте празни папки с филми", "CreateEmptyArtistFoldersHelpText": "Създайте липсващи папки с филми по време на сканиране на диска", "CreateGroup": "Създай група", - "CutoffHelpText": "След достигане на това качество Radarr вече няма да изтегля филми", + "CutoffHelpText": "След достигане на това качество Lidarr вече няма да изтегля филми", "CutoffUnmet": "Прекъсване Неудовлетворено", "DBMigration": "DB миграция", "Dates": "Дати", @@ -113,9 +113,9 @@ "ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "За повече информация относно отделните индексатори щракнете върху информационните бутони.", "ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "За повече информация относно отделните списъци за импортиране щракнете върху бутоните с информация.", "IsTagUsedCannotBeDeletedWhileInUse": "Не може да се изтрие, докато се използва", - "LaunchBrowserHelpText": " Отворете уеб браузър и отворете началната страница на Radarr при стартиране на приложението.", - "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Radarr поддържа всеки клиент за изтегляне, който използва стандарта Newznab, както и други клиенти за изтегляне, изброени по-долу.", - "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr поддържа всеки индексатор, който използва стандарта Newznab, както и други индексатори, изброени по-долу.", + "LaunchBrowserHelpText": " Отворете уеб браузър и отворете началната страница на Lidarr при стартиране на приложението.", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr поддържа всеки клиент за изтегляне, който използва стандарта Newznab, както и други клиенти за изтегляне, изброени по-долу.", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr поддържа всеки индексатор, който използва стандарта Newznab, както и други индексатори, изброени по-долу.", "Local": "Местен", "MetadataSettings": "Настройки на метаданните", "Password": "Парола", @@ -129,7 +129,7 @@ "Ungroup": "Разгрупиране", "UnmonitoredHelpText": "Включете непроменени филми в iCal емисията", "UpdateAutomaticallyHelpText": "Автоматично изтегляне и инсталиране на актуализации. Все още ще можете да инсталирате от System: Updates", - "UpdateMechanismHelpText": "Използвайте вградения в Radarr актуализатор или скрипт", + "UpdateMechanismHelpText": "Използвайте вградения в Lidarr актуализатор или скрипт", "UpdateScriptPathHelpText": "Път към персонализиран скрипт, който взема извлечен пакет за актуализация и обработва останалата част от процеса на актуализация", "Updates": "Актуализации", "UpgradeAllowedHelpText": "Ако инвалидните качества няма да бъдат надградени", @@ -155,7 +155,7 @@ "ApplyTagsHelpTexts4": "Замяна: Заменете маркерите с въведените маркери (не въвеждайте маркери, за да изчистите всички маркери)", "ArtistAlbumClickToChangeTrack": "Щракнете, за да промените филма", "Authentication": "Удостоверяване", - "AuthenticationMethodHelpText": "Изисквайте потребителско име и парола за достъп до Radarr", + "AuthenticationMethodHelpText": "Изисквайте потребителско име и парола за достъп до Lidarr", "Backups": "Архиви", "BlocklistRelease": "Освобождаване на черния списък", "Branch": "Клон", @@ -170,9 +170,9 @@ "ChangeHasNotBeenSavedYet": "Промяната все още не е запазена", "ChmodFolder": "chmod папка", "ChmodFolderHelpText": "Осмично, приложено по време на импортиране / преименуване към медийни папки и файлове (без битове за изпълнение)", - "ChmodFolderHelpTextWarning": "Това работи само ако потребителят, работещ с Radarr, е собственик на файла. По-добре е да се уверите, че клиентът за изтегляне правилно задава разрешенията.", + "ChmodFolderHelpTextWarning": "Това работи само ако потребителят, работещ с Lidarr, е собственик на файла. По-добре е да се уверите, че клиентът за изтегляне правилно задава разрешенията.", "ChownGroupHelpText": "Име на група или gid. Използвайте gid за отдалечени файлови системи.", - "ChownGroupHelpTextWarning": "Това работи само ако потребителят, работещ с Radarr, е собственик на файла. По-добре е да се уверите, че клиентът за изтегляне използва същата група като Radarr.", + "ChownGroupHelpTextWarning": "Това работи само ако потребителят, работещ с Lidarr, е собственик на файла. По-добре е да се уверите, че клиентът за изтегляне използва същата група като Lidarr.", "Clear": "Ясно", "ClickToChangeQuality": "Щракнете, за да промените качеството", "ClientPriority": "Приоритет на клиента", @@ -218,7 +218,7 @@ "Grab": "Грабнете", "GrabID": "Идентификатор на грабване", "GrabRelease": "Grab Release", - "GrabReleaseMessageText": "Radarr не успя да определи за кой филм е предназначено това издание. Radarr може да не може автоматично да импортира тази версия. Искате ли да вземете „{0}“?", + "GrabReleaseMessageText": "Lidarr не успя да определи за кой филм е предназначено това издание. Lidarr може да не може автоматично да импортира тази версия. Искате ли да вземете „{0}“?", "GrabSelected": "Grab Selected", "Group": "Група", "HasPendingChangesNoChanges": "Без промени", @@ -242,7 +242,7 @@ "ImportFailedInterp": "Неуспешно импортиране: {0}", "Importing": "Импортиране", "IncludeHealthWarningsHelpText": "Включете здравни предупреждения", - "IncludeUnknownArtistItemsHelpText": "Показване на елементи без филм в опашката. Това може да включва премахнати филми или нещо друго от категорията на Radarr", + "IncludeUnknownArtistItemsHelpText": "Показване на елементи без филм в опашката. Това може да включва премахнати филми или нещо друго от категорията на Lidarr", "IncludeUnmonitored": "Включете Без наблюдение", "Indexer": "Индексатор", "IndexerPriority": "Индексатор Приоритет", @@ -255,7 +255,7 @@ "Level": "Ниво", "LoadingTrackFilesFailed": "Зареждането на филмови файлове не бе успешно", "LocalPath": "Местен път", - "LocalPathHelpText": "Път, който Radarr трябва да използва за локален достъп до отдалечения път", + "LocalPathHelpText": "Път, който Lidarr трябва да използва за локален достъп до отдалечения път", "LogFiles": "Лог файлове", "Logging": "Регистрация", "LogLevel": "Log Level", @@ -318,7 +318,6 @@ "QualityProfiles": "Качествени профили", "QualitySettings": "Настройки за качество", "Queue": "Опашка", - "Radarr": "Радар", "ReadTheWikiForMoreInformation": "Прочетете Wiki за повече информация", "Real": "Истински", "Reason": "Причина", @@ -351,13 +350,13 @@ "RemoveSelected": "Премахнете избраното", "RemoveSelectedMessageText": "Наистина ли искате да премахнете избраните елементи от черния списък?", "RequiresRestartToTakeEffect": "Изисква рестартиране, за да влезе в сила", - "RescanAfterRefreshHelpTextWarning": "Radarr няма автоматично да открива промени във файлове, когато не е зададено на „Винаги“", + "RescanAfterRefreshHelpTextWarning": "Lidarr няма автоматично да открива промени във файлове, когато не е зададено на „Винаги“", "RescanArtistFolderAfterRefresh": "Повторно сканиране на папка за филм след опресняване", "Reset": "Нулиране", "ResetAPIKey": "Нулиране на API ключ", "ResetAPIKeyMessageText": "Наистина ли искате да нулирате своя API ключ?", "Restart": "Рестартирам", - "RestartLidarr": "Рестартирайте Radarr", + "RestartLidarr": "Рестартирайте Lidarr", "RestartNow": "Рестартирай сега", "Restore": "Възстанови", "RestoreBackup": "Възстанови архива", @@ -396,7 +395,7 @@ "SuccessMyWorkIsDoneNoFilesToRetag": "Успех! Работата ми приключи, няма файлове за преименуване.", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS не се поддържа с този индексатор", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Търсенето не се поддържа с този индексатор", - "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Ще се използва, когато се извършват автоматични търсения чрез потребителския интерфейс или от Radarr", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Ще се използва, когато се извършват автоматични търсения чрез потребителския интерфейс или от Lidarr", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Ще се използва, когато се използва интерактивно търсене", "TagIsNotUsedAndCanBeDeleted": "Етикетът не се използва и може да бъде изтрит", "Tags": "Етикети", @@ -415,7 +414,7 @@ "Track": "Проследяване", "Type": "Тип", "URLBase": "URL база", - "UILanguageHelpText": "Език, който Radarr ще използва за потребителски интерфейс", + "UILanguageHelpText": "Език, който Lidarr ще използва за потребителски интерфейс", "Year": "Година", "Analytics": "Анализ", "DownloadFailedInterp": "Изтеглянето не бе успешно: {0}", @@ -456,12 +455,22 @@ "ShowMonitoredHelpText": "Показване на наблюдаваното състояние под плакат", "Size": " Размер", "SkipFreeSpaceCheck": "Пропуснете проверката на свободното пространство", - "SkipFreeSpaceCheckWhenImportingHelpText": "Използвайте, когато Radarr не е в състояние да открие свободно място от основната папка на вашия филм", + "SkipFreeSpaceCheckWhenImportingHelpText": "Използвайте, когато Lidarr не е в състояние да открие свободно място от основната папка на вашия филм", "SorryThatAlbumCannotBeFound": "За съжаление този филм не може да бъде намерен.", "SorryThatArtistCannotBeFound": "За съжаление този филм не може да бъде намерен.", "Source": "Източник", "SourcePath": "Път на източника", "UnableToLoadIndexerOptions": "Опциите за индексатор не могат да се заредят", "Unmonitored": "Без наблюдение", - "UpdateAll": "Актуализирай всички" + "UpdateAll": "Актуализирай всички", + "OnGrab": "На Граб", + "ThisCannotBeCancelled": "Това не може да бъде отменено след стартиране без рестартиране на Whisparr.", + "Component": "Съставна част", + "OnRename": "При преименуване", + "Tracks": "Проследяване", + "Docker": "Докер", + "OnHealthIssue": "По здравен въпрос", + "OnUpgrade": "При надстройка", + "Usenet": "Usenet", + "MonoVersion": "Моно версия" } diff --git a/src/NzbDrone.Core/Localization/Core/ca.json b/src/NzbDrone.Core/Localization/Core/ca.json index fc5accbfd..9b731d8b7 100644 --- a/src/NzbDrone.Core/Localization/Core/ca.json +++ b/src/NzbDrone.Core/Localization/Core/ca.json @@ -8,5 +8,478 @@ "RecyclingBin": "Paperera de reciclatge", "Refresh": "Actualització", "Reload": "Recarregar", - "About": "Quant a" + "About": "Quant a", + "UnableToLoadTags": "No es poden carregar les etiquetes", + "Result": "Resultat", + "Status": "Estat", + "BindAddress": "Adreça d'enllaç", + "DeleteQualityProfileMessageText": "Esteu segur que voleu suprimir el perfil de qualitat {0}", + "DeleteReleaseProfile": "Suprimeix el perfil de retard", + "DeleteReleaseProfileMessageText": "Esteu segur que voleu suprimir aquest perfil de retard?", + "DownloadClients": "Descàrrega Clients", + "EnableColorImpairedMode": "Activa el mode amb alteracions del color", + "EnableHelpText": "Activa la creació de fitxers de metadades per a aquest tipus de metadades", + "Filename": "Nom de fitxer", + "FileNames": "Noms de fitxers", + "FirstDayOfWeek": "Primer dia de la setmana", + "Fixed": "Corregit", + "ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Per obtenir més informació sobre els clients de baixada individuals, feu clic als botons de més informació.", + "ICalFeed": "Canal iCal", + "ImportedTo": "Importat a", + "UISettings": "Configuració de la interfície", + "LaunchBrowserHelpText": " Obriu un navegador web i navegueu a la pàgina d'inici de Lidarr a l'inici de l'aplicació.", + "Mode": "Mode", + "Time": "Temps", + "OnApplicationUpdate": "A l'actualitzar de l'aplicació", + "RecyclingBinCleanup": "Neteja de la paperera de reciclatge", + "Remove": "Elimina", + "RemoveCompletedDownloadsHelpText": "Elimineu les descàrregues importades de l'historial del client de baixades", + "RemovedFromTaskQueue": "S'ha eliminat de la cua de tasques", + "ReplaceIllegalCharactersHelpText": "Substitueix caràcters il·legals. Si no es marca, Lidarr els eliminarà", + "RequiredHelpText": "El llançament ha de contenir almenys un d'aquests termes (no distingeix entre majúscules i minúscules)", + "RequiredPlaceHolder": "Afegeix una nova restricció", + "SearchSelected": "Cerca seleccionada", + "Settings": "Configuració", + "ShortDateFormat": "Format de data curta", + "Term": "Terme", + "TestAll": "Prova-ho tot", + "UrlBaseHelpText": "Per al suport de servidor intermediari invers, el valor predeterminat és buit", + "UrlBaseHelpTextWarning": "Cal reiniciar perquè tingui efecte", + "MetadataProfile": "perfil de metadades", + "MetadataProfiles": "perfil de metadades", + "MetadataSettings": "Configuració de metadades", + "MinimumLimits": "Límits mínims", + "Missing": "Absents", + "MustContain": "Ha de contenir", + "MustNotContain": "No ha de contenir", + "Name": "Nom", + "NamingSettings": "Configuració de noms", + "RecycleBinCleanupDaysHelpTextWarning": "Els fitxers de la paperera de reciclatge més antics que el nombre de dies seleccionat es netejaran automàticament", + "RemotePath": "Camí remot", + "RemotePathHelpText": "Camí arrel al directori al qual accedeix el client de baixada", + "RemotePathMappings": "Mapes de camins remots", + "Reorder": "Reordena", + "ReplaceIllegalCharacters": "Substitueix caràcters il·legals", + "RequiresRestartToTakeEffect": "Cal reiniciar perquè tingui efecte", + "RescanAfterRefreshHelpText": "Torneu a escanejar la carpeta de la pel·lícula després d'actualitzar la pel·lícula", + "ShowSearch": "Mostra la cerca", + "ShowSearchActionHelpText": "Mostra el botó de cerca al passar el cursor", + "ShowSizeOnDisk": "Mostra la mida al disc", + "ShowUnknownArtistItems": "Mostra elements de pel·lícula desconeguda", + "SkipFreeSpaceCheck": "Omet la comprovació d'espai lliure", + "SkipFreeSpaceCheckWhenImportingHelpText": "Utilitzeu-lo quan Lidarr no pugui detectar espai lliure a la carpeta arrel de la pel·lícula", + "SSLCertPassword": "Contrasenya de certificat SSL", + "SslPortHelpTextWarning": "Cal reiniciar perquè tingui efecte", + "StartTypingOrSelectAPathBelow": "Comenceu a escriure o seleccioneu un camí a continuació", + "StartupDirectory": "Directori d'inici", + "SuccessMyWorkIsDoneNoFilesToRename": "Èxit! La feina està acabada, no hi ha fitxers per canviar el nom.", + "SuccessMyWorkIsDoneNoFilesToRetag": "Èxit! La feina està acabada, no hi ha fitxers per canviar el nom.", + "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS no és compatible amb aquest indexador", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "S'utilitzarà quan es realitzin cerques automàtiques mitjançant la interfície d'usuari o per Lidarr", + "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "S'utilitzarà quan s'utilitzi la cerca interactiva", + "TagIsNotUsedAndCanBeDeleted": "L'etiqueta no està en ús i es pot suprimir", + "Tags": "Etiquetes", + "Tasks": "Tasques", + "ThisCannotBeCancelled": "No es pot cancel·lar un cop iniciat sense desactivar tots els vostres indexadors.", + "TorrentDelay": "Retard del torrent", + "TorrentDelayHelpText": "Retard en minuts per esperar abans de capturar un torrent", + "Torrents": "Torrents", + "UnableToLoadGeneralSettings": "No es pot carregar la configuració general", + "UnableToLoadHistory": "No es pot carregar l'historial", + "UnableToLoadImportListExclusions": "No es poden carregar les exclusions de la llista", + "UnableToLoadIndexerOptions": "No es poden carregar les opcions de l'indexador", + "RemoveCompleted": "S'ha eliminat", + "RemoveDownloadsAlert": "La configuració d'eliminació s'ha mogut a la configuració del client de baixada a la taula anterior.", + "Usenet": "Usenet", + "Score": "Puntuació", + "SearchAll": "Cerca-ho tot", + "ShowDateAdded": "Mostra la data d'addició", + "Size": " Mida", + "ApplyTags": "Aplica etiquetes", + "ApplyTagsHelpTexts4": "Substituïu: substituïu les etiquetes per les etiquetes introduïdes (no introduïu cap etiqueta per esborrar totes les etiquetes)", + "BackupFolderHelpText": "Els camins relatius estaran sota el directori AppData del Lidarr", + "BackupRetentionHelpText": "Les còpies de seguretat automàtiques anteriors al període de retenció s'eliminaran automàticament", + "Backups": "Còpies de seguretat", + "BindAddressHelpTextWarning": "Cal reiniciar perquè tingui efecte", + "Blocklist": "Llista de bloquejats", + "BlocklistHelpText": "Impedeix que Lidarr torni a capturar aquesta versió automàticament", + "ChangeFileDate": "Canvia la data del fitxer", + "ChangeHasNotBeenSavedYet": "El canvi encara no s'ha desat", + "ChmodFolder": "Carpeta chmod", + "DeleteImportListExclusion": "Suprimeix l'exclusió de la llista d'importació", + "DeleteImportListMessageText": "Esteu segur que voleu suprimir la llista '{0}'?", + "DeleteIndexer": "Suprimeix l'indexador", + "DestinationPath": "Camí de destinació", + "EnableCompletedDownloadHandlingHelpText": "Importa automàticament les descàrregues completades des del client de descàrregues", + "EnableInteractiveSearch": "Activa la cerca interactiva", + "EnableRSS": "Activa RSS", + "EnableSSL": "Activa SSL", + "ErrorLoadingContents": "S'ha produït un error en carregar el contingut", + "Exception": "Excepció", + "Track": "Traça", + "ExtraFileExtensionsHelpTexts1": "Llista separada per comes de fitxers addicionals per importar (.nfo s'importarà com a .nfo-orig)", + "FileDateHelpText": "Canvia la data del fitxer en importar/reescanejar", + "FileManagement": "Gestió del fitxers", + "GoToInterp": "Vés a {0}", + "GrabID": "Captura ID", + "HasPendingChangesNoChanges": "Sense Canvis", + "Hostname": "Nom d'amfitrió", + "ICalLink": "Enllaç iCal`", + "IconForCutoffUnmet": "Icona per a tall no assolit", + "IconTooltip": "Programat", + "IncludeUnknownArtistItemsHelpText": "Mostra elements sense pel·lícula a la cua. Això podria incloure pel·lícules eliminades o qualsevol altra cosa de la categoria de Lidarr", + "Indexers": "Indexadors", + "IndexerSettings": "Configuració de l'indexador", + "Interval": "Interval", + "IsCutoffCutoff": "Tallar", + "IsTagUsedCannotBeDeletedWhileInUse": "No es pot suprimir mentre està en ús", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr admet qualsevol indexador que utilitzi l'estàndard Newznab, així com altres indexadors que s'enumeren a continuació.", + "LidarrTags": "Etiquetes de Lidarr", + "LoadingTrackFilesFailed": "No s'han pogut carregar els fitxers de pel·lícules", + "Local": "Local", + "LocalPath": "Camí local", + "LocalPathHelpText": "Camí que Lidarr hauria d'utilitzar per accedir al camí remot localment", + "MinimumAge": "Edat Mínima", + "Monitored": "Monitorat", + "MoreInfo": "Més informació", + "NoBackupsAreAvailable": "No hi ha còpies de seguretat disponibles", + "NETCore": ".NET", + "NoHistory": "Sense història", + "NoLeaveIt": "No, deixa-ho", + "NotificationTriggers": "Activadors de notificacions", + "NoUpdatesAreAvailable": "No hi ha actualitzacions disponibles", + "OnHealthIssueHelpText": "Al detectar incidència", + "OnRenameHelpText": "Al reanomenar", + "RecycleBinHelpText": "Els fitxers de pel·lícula aniran aquí quan se suprimeixin en lloc de suprimir-se permanentment", + "Redownload": "Torna a baixar", + "ShowRelativeDates": "Mostra les dates relatives", + "Language": "Idioma", + "UILanguage": "Idioma de la interfície", + "OnApplicationUpdateHelpText": "A l'actualitzar de l'aplicació", + "PreviewRename": "Vista prèvia reanomenat", + "Proxy": "Servidor intermediari", + "ProxyBypassFilterHelpText": "Utilitzeu ',' com a separador i '*.' com a comodí per als subdominis", + "ProxyUsernameHelpText": "Només cal que introduïu un nom d'usuari i una contrasenya si cal. Deixeu-los en blanc en cas contrari.", + "PublishedDate": "Data de publicació", + "Quality": "Qualitat", + "QualityDefinitions": "Definicions de qualitat", + "Real": "Real", + "RefreshInformationAndScanDisk": "Actualitza la informació i escaneja el disc", + "RefreshScan": "Actualitza i escaneja", + "ReleaseDate": "Dates de llançament", + "ReleaseGroup": "Grup de llançament", + "ReleaseRejected": "Llançament rebutjat", + "ReleaseStatuses": "Estat de llançament", + "ReleaseWillBeProcessedInterp": "El llançament es processarà {0}", + "RemoveFilter": "Treu el filtre", + "RemoveFromBlocklist": "Elimina de la llista de bloqueig", + "RemoveFromDownloadClient": "Elimina del client de baixada", + "RemoveFromQueue": "Elimina de la cua", + "RemoveSelected": "Elimina els seleccionats", + "RemoveSelectedMessageText": "Esteu segur que voleu suprimir els elements seleccionats de la llista de blocs?", + "RemoveTagExistingTag": "Etiqueta existent", + "RemoveTagRemovingTag": "S'està eliminant l'etiqueta", + "RenameTracksHelpText": "Lidarr utilitzarà el nom del fitxer existent si el reanomenat està desactivat", + "Reset": "Restableix", + "ResetAPIKey": "Restableix la clau de l'API", + "ResetAPIKeyMessageText": "Esteu segur que voleu restablir la clau de l'API?", + "Restart": "Reinicia", + "RestartLidarr": "Reinicia Lidarr", + "RestartNow": "Reinicia ara", + "RetentionHelpText": "Només Usenet: establiu-lo a zero per establir una retenció il·limitada", + "RssSyncIntervalHelpText": "Interval en minuts. Establiu a zero per desactivar (això aturarà tota captura automàtica de llançaments)", + "Scheduled": "Programat", + "Season": "temporada", + "Security": "Seguretat", + "SendAnonymousUsageData": "Envia dades d'ús anònimes", + "SetPermissions": "Estableix permisos", + "SetPermissionsLinuxHelpText": "S'ha d'executar chmod quan els fitxers s'importen o es reanomenen?", + "SetPermissionsLinuxHelpTextWarning": "Si no esteu segur de què fan aquestes configuracions, no les modifiqueu.", + "ShowMonitored": "Mostra monitorats", + "ShowMonitoredHelpText": "Mostra l'estat monitorat sota el pòster", + "SorryThatAlbumCannotBeFound": "Ho sentim, aquesta pel·lícula no s'ha trobat.", + "SorryThatArtistCannotBeFound": "Ho sentim, aquesta pel·lícula no s'ha trobat.", + "Source": "Font", + "SourcePath": "Camí de la font", + "SslCertPasswordHelpText": "Contrasenya per al fitxer pfx", + "SslCertPasswordHelpTextWarning": "Cal reiniciar perquè tingui efecte", + "SSLCertPath": "Camí del certificat SSL", + "SslCertPathHelpText": "Camí al fitxer pfx", + "Style": "Estil", + "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "La cerca no és compatible amb aquest indexador", + "TestAllClients": "Prova tots els clients", + "TestAllIndexers": "Prova tots els indexadors", + "TestAllLists": "Prova totes les llistes", + "TimeFormat": "Format horari", + "TotalFileSize": "Mida total del fitxer", + "Type": "Tipus", + "UnableToAddANewListPleaseTryAgain": "No es pot afegir una llista nova, torneu-ho a provar.", + "UnableToAddANewMetadataProfilePleaseTryAgain": "No es pot afegir un perfil de qualitat nou, torneu-ho a provar.", + "UnableToAddANewNotificationPleaseTryAgain": "No es pot afegir una notificació nova, torneu-ho a provar.", + "UnableToAddANewRootFolderPleaseTryAgain": "No es pot afegir un indexador nou, torneu-ho a provar.", + "UnableToLoadBackups": "No es poden carregar còpies de seguretat", + "UnableToLoadDelayProfiles": "No es poden carregar els perfils de retard", + "UnableToLoadDownloadClientOptions": "No es poden carregar les opcions del client de baixada", + "UnableToLoadDownloadClients": "No es poden carregar els clients de baixada", + "UnableToLoadIndexers": "No es poden carregar els indexadors", + "UnableToLoadLists": "No es poden carregar les llistes", + "UnableToLoadTheCalendar": "No es pot carregar el calendari", + "ApplyTagsHelpTexts1": "Com aplicar etiquetes a les pel·lícules seleccionades", + "Clear": "Esborra", + "ClientPriority": "Prioritat del client", + "CloneIndexer": "Clona indexadors", + "CloneProfile": "Clona el perfil", + "Columns": "Columnes", + "AdvancedSettingsShownClickToHide": "Es mostra, feu clic per amagar", + "AgeWhenGrabbed": "Edat (quan es captura)", + "AlternateTitles": "Títol alternatiu", + "AlternateTitleslength1Title": "Títol", + "AlternateTitleslength1Titles": "Títols", + "Analytics": "Anàlisi", + "AnalyticsEnabledHelpText": "Envieu informació anònima d'ús i errors als servidors de Lidarr. Això inclou informació sobre el vostre navegador, quines pàgines Lidarr WebUI feu servir, informes d'errors, així com el sistema operatiu i la versió del temps d'execució. Utilitzarem aquesta informació per prioritzar les funcions i les correccions d'errors.", + "AnalyticsEnabledHelpTextWarning": "Cal reiniciar perquè tingui efecte", + "APIKey": "Clau API", + "ApiKeyHelpTextWarning": "Cal reiniciar perquè tingui efecte", + "AppDataDirectory": "Directori AppData", + "ApplyTagsHelpTexts2": "Afegeix: afegeix les etiquetes a la llista d'etiquetes existent", + "ApplyTagsHelpTexts3": "Eliminar: elimina les etiquetes introduïdes", + "Authentication": "Autenticació", + "AuthenticationMethodHelpText": "Requereix nom d'usuari i contrasenya per accedir al radar", + "Automatic": "Automàtic", + "AutoRedownloadFailedHelpText": "Cerca i intenta baixar automàticament una versió diferent", + "BackupNow": "Fes ara la còpia de seguretat", + "BlocklistRelease": "Publicació de la llista de bloqueig", + "Branch": "Branca", + "BypassProxyForLocalAddresses": "Ometre el servidor intermediari per a adreces locals", + "Calendar": "Calendari", + "CalendarWeekColumnHeaderHelpText": "Es mostra a sobre de cada columna quan la setmana és la visualització activa", + "Cancel": "Cancel·la", + "CancelMessageText": "Esteu segur que voleu cancel·lar aquesta tasca pendent?", + "CertificateValidation": "Validació del certificat", + "ChmodFolderHelpText": "Octal, aplicat durant la importació/reanomenat de carpetes i fitxers multimèdia (sense bits d'execució)", + "ChmodFolderHelpTextWarning": "Això només funciona si l'usuari que executa Lidarr és el propietari del fitxer. És millor assegurar-se que el client de descàrrega estableixi correctament els permisos.", + "ChownGroupHelpText": "Nom del grup o gid. Utilitzeu gid per a sistemes de fitxers remots.", + "ChownGroupHelpTextWarning": "Això només funciona si l'usuari que executa Lidarr és el propietari del fitxer. És millor assegurar-se que el client de descàrrega utilitza el mateix grup que Lidarr.", + "ConnectSettings": "Configuració de connexió", + "CopyUsingHardlinksHelpText": "Utilitzeu els enllaços durs quan intenteu copiar fitxers de torrents que encara s'estan sembrant", + "CopyUsingHardlinksHelpTextWarning": "De tant en tant, els bloquejos de fitxers poden impedir reanomenar els fitxers que s'estan sembrant. Podeu desactivar temporalment la compartició i utilitzar la funció de reanomenar de Lidarr com a solució.", + "CreateEmptyArtistFolders": "Creeu carpetes buides per a les pel·lícules", + "CreateEmptyArtistFoldersHelpText": "Creeu carpetes de pel·lícules que falten durant l'exploració del disc", + "CreateGroup": "Crea un grup", + "CutoffHelpText": "Un cop s'assoleixi aquesta qualitat, Lidarr ja no baixarà pel·lícules", + "CutoffUnmet": "Tall no assolit", + "Dates": "Dates", + "DBMigration": "Migració de BD", + "DelayingDownloadUntilInterp": "S'està retardant la baixada fins a les {0} a les {1}", + "DelayProfile": "Perfil de retard", + "DelayProfiles": "Perfils de retard", + "Delete": "Suprimeix", + "DeleteBackup": "Suprimeix la còpia de seguretat", + "DeleteBackupMessageText": "Esteu segur que voleu suprimir la còpia de seguretat '{0}'?", + "DeleteDelayProfile": "Suprimeix el perfil de retard", + "DeleteDelayProfileMessageText": "Esteu segur que voleu suprimir aquest perfil de retard?", + "DeleteDownloadClient": "Suprimeix el client de descàrrega", + "DeleteDownloadClientMessageText": "Esteu segur que voleu suprimir el client de baixada '{0}'?", + "DeleteEmptyFolders": "Suprimeix les carpetes buides", + "DeleteImportListExclusionMessageText": "Esteu segur que voleu suprimir aquesta exclusió de la llista d'importació?", + "DeleteIndexerMessageText": "Esteu segur que voleu suprimir l'indexador '{0}'?", + "DeleteMetadataProfileMessageText": "Esteu segur que voleu suprimir el perfil de qualitat {0}", + "DeleteNotification": "Suprimeix la notificació", + "DeleteNotificationMessageText": "Esteu segur que voleu suprimir la notificació '{0}'?", + "DeleteQualityProfile": "Suprimeix el perfil de qualitat", + "DeleteRootFolderMessageText": "Esteu segur que voleu suprimir l'indexador '{0}'?", + "DeleteSelectedTrackFiles": "Suprimeix els fitxers de pel·lícules seleccionats", + "DeleteSelectedTrackFilesMessageText": "Esteu segur que voleu suprimir els fitxers de pel·lícules seleccionats?", + "DeleteTag": "Suprimeix l'etiqueta", + "DeleteTagMessageText": "Esteu segur que voleu suprimir l'etiqueta '{0}'?", + "DetailedProgressBar": "Barra de progrés detallada", + "DetailedProgressBarHelpText": "Mostra el text a la barra de progrés", + "DiskSpace": "Espai en disc", + "Docker": "Docker", + "DownloadClient": "Client de baixada", + "DownloadClientSettings": "Baixeu la configuració del client", + "DownloadFailedCheckDownloadClientForMoreDetails": "La baixada ha fallat: comproveu el client de descàrrega per obtenir més detalls", + "DownloadFailedInterp": "La baixada ha fallat: {0}", + "Downloading": "S'està baixant", + "DownloadPropersAndRepacksHelpTexts1": "Si s'ha d'actualitzar automàticament o no a Propers/Repacks", + "DownloadWarningCheckDownloadClientForMoreDetails": "Avís de descàrrega: consulteu el client de descàrrega per obtenir més detalls", + "Duration": "durada", + "Edit": "Edita", + "Enable": "Activa", + "EnableAutomaticAdd": "Activa l'Afegit automàtic", + "EnableAutomaticSearch": "Activa la cerca automàtica", + "EnableColorImpairedModeHelpText": "Estil alternat per permetre als usuaris amb problemes de color distingir millor la informació codificada per colors", + "EnableSslHelpText": " Cal reiniciar l'execució com a administrador perquè tingui efecte", + "Ended": "S'ha acabat", + "ErrorLoadingPreviews": "S'ha produït un error en carregar les previsualitzacions", + "Files": "Fitxers", + "Folders": "Carpetes", + "ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "Per obtenir més informació sobre els indexadors individuals, feu clic als botons d'informació.", + "Grab": "Captura", + "GrabRelease": "Captura novetat", + "GrabReleaseMessageText": "Lidarr no ha pogut determinar per a quina pel·lícula era aquest llançament. És possible que Lidarr no pugui importar automàticament aquesta versió. Voleu capturar \"{0}\"?", + "GrabSelected": "Captura sel·leccionada", + "Group": "Grup", + "History": "Història", + "Host": "Amfitrió", + "HostHelpText": "El mateix amfitrió que heu especificat per al client de baixada remota", + "ICalHttpUrlHelpText": "Copieu aquest URL als vostres clients o feu clic per subscriure's si el vostre navegador és compatible amb webcal", + "IgnoredAddresses": "Adreces ignorades", + "IgnoredHelpText": "La publicació es rebutjarà si conté un o més dels termes (no distingeix entre majúscules i minúscules)", + "IgnoredPlaceHolder": "Afegeix una nova restricció", + "IllRestartLater": "Reinicia més tard", + "ImportExtraFiles": "Importa fitxers addicionals", + "ImportExtraFilesHelpText": "Importeu fitxers addicionals coincidents (subtítols, nfo, etc.) després d'importar un fitxer de pel·lícula", + "ImportFailedInterp": "ImportFailedInterp", + "Importing": "s'està important", + "IncludeHealthWarningsHelpText": "Inclou advertències de salut", + "IncludeUnmonitored": "Inclou no monitorat", + "Indexer": "Indexador", + "IndexerPriority": "Prioritat de l'indexador", + "InteractiveSearch": "Cerca interactiva", + "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Actualitzeu fins que s'assoleixi o superi aquesta qualitat", + "Label": "Etiqueta", + "Level": "Nivell", + "LogFiles": "Fitxers de registre", + "LogLevel": "Nivell de registre", + "MaximumSize": "Mida màxima", + "MaximumSizeHelpText": "Mida màxima per a una versió que es pot capturar en MB. Establiu a zero per establir-lo en il·limitat", + "Mechanism": "Mecanisme", + "MediaInfo": "Informació de mitjans", + "MediaManagementSettings": "Configuració de gestió de mitjans", + "Medium": "Suport", + "Message": "Missatge", + "MIA": "MIA", + "MinimumAgeHelpText": "Només Usenet: edat mínima en minuts dels NZB abans de ser capturats. Utilitzeu-ho per donar temps a les noves versions per propagar-se al vostre proveïdor d'usenet.", + "MinimumFreeSpace": "Espai lliure mínim", + "MinimumFreeSpaceWhenImportingHelpText": "Eviteu la importació si quedara menys d'aquesta quantitat d'espai disponible en disc", + "NoLimitForAnyRuntime": "Sense límit de temps d'execució", + "NoLogFiles": "No hi ha fitxers de registre", + "NoMinimumForAnyRuntime": "No hi ha mínim per a cap temps d'execució", + "None": "Cap", + "OnHealthIssue": "Al detectar incidència", + "OnRename": "Al reanomenar", + "OnUpgrade": "A l'actualitzar", + "OnUpgradeHelpText": "A l'actualitzar", + "OpenBrowserOnStart": "Obriu el navegador a l'inici", + "Options": "Opcions", + "Original": "Original", + "PackageVersion": "Versió del paquet", + "PageSize": "Mida de la pàgina", + "PageSizeHelpText": "Nombre d'elements per mostrar a cada pàgina", + "Path": "Camí", + "Preferred": "Preferit", + "Profiles": "Perfils", + "Proper": "Proper", + "PropersAndRepacks": "Propietats i Repacks", + "Protocol": "Protocol", + "ProtocolHelpText": "Trieu quin(s) protocol(s) utilitzar i quin és el preferit quan escolliu entre versions iguals", + "ProxyPasswordHelpText": "Només cal que introduïu un nom d'usuari i una contrasenya si cal. Deixeu-los en blanc en cas contrari.", + "ProxyType": "Tipus de servidor intermediari", + "QualitySettings": "Configuració de qualitat", + "ReadTheWikiForMoreInformation": "Llegiu el Wiki per a més informació", + "Reason": "Raó", + "RemoveFailed": "Ha fallat l'eliminació", + "RemoveFailedDownloadsHelpText": "Elimineu les baixades fallides de l'historial del client de baixada", + "RemoveHelpTextWarning": "L'eliminació esborrarà la baixada i els fitxers del client de baixada.", + "RescanAfterRefreshHelpTextWarning": "Lidarr no detectarà automàticament els canvis als fitxers quan no estigui configurat com a \"Sempre\"", + "RescanArtistFolderAfterRefresh": "Torna a escanejar la carpeta de pel·lícules després de l'actualització", + "Restore": "Restaura", + "RestoreBackup": "Restaura còpia de seguretat", + "Retention": "Retenció", + "RetryingDownloadInterp": "S'està tornant a provar de baixar {0} a {1}", + "RootFolder": "Carpeta arrel", + "RootFolders": "Carpetes arrel", + "RSSSync": "Sincronització RSS", + "RSSSyncInterval": "Interval de sincronització RSS", + "ScriptPath": "Camí de l'script", + "Search": "Cerca", + "SearchForMissing": "Cerca absents", + "ShowCutoffUnmetIconHelpText": "Mostra la icona dels fitxers quan no s'hagi assolit el tall", + "ShownAboveEachColumnWhenWeekIsTheActiveView": "Es mostra a sobre de cada columna quan la setmana és la visualització activa", + "ShowPath": "Mostra el camí", + "ShowQualityProfile": "Mostra el perfil de qualitat", + "ShowQualityProfileHelpText": "Mostra el perfil de qualitat sota el pòster", + "ShowRelativeDatesHelpText": "Mostra dates relatives (avui/ahir/etc) o absolutes", + "SslCertPathHelpTextWarning": "Cal reiniciar perquè tingui efecte", + "SSLPort": "Port SSL", + "ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "Això s'aplicarà a tots els indexadors, si us plau, seguiu les regles establertes per ells", + "UnableToAddANewRemotePathMappingPleaseTryAgain": "No es pot afegir una nova assignació de camins remots, torneu-ho a provar.", + "UnableToLoadBlocklist": "No es pot carregar la llista de bloqueig", + "UnableToLoadMediaManagementSettings": "No es pot carregar la configuració de gestió de mitjans", + "UnableToLoadMetadata": "No es poden carregar les metadades", + "UnableToLoadMetadataProfiles": "No es poden carregar els perfils de qualitat", + "UnableToLoadNamingSettings": "No es pot carregar la configuració de nomenclatura", + "UnableToLoadNotifications": "No es poden carregar les notificacions", + "UnableToLoadQualities": "No es poden carregar qualitats", + "UnableToLoadRemotePathMappings": "No s'han pogut carregar els mapes de camins remots", + "UnableToLoadRootFolders": "No es poden carregar les carpetes arrel", + "UnableToLoadUISettings": "No es pot carregar la configuració de la IU", + "Ungroup": "Desagrupa", + "Unmonitored": "No monitorat", + "UnmonitoredHelpText": "Inclou pel·lícules no monitorades al canal iCal", + "UpdateAll": "Actualitzar-ho tot", + "UpdateAutomaticallyHelpText": "Baixeu i instal·leu les actualitzacions automàticament. Encara podreu instal·lar des de Sistema: Actualitzacions", + "UpdateMechanismHelpText": "Utilitzeu l'actualitzador integrat de Lidarr o un script", + "Updates": "Actualitzacions", + "UpdateScriptPathHelpText": "Camí a un script personalitzat que pren un paquet d'actualització i gestiona la resta del procés d'actualització", + "URLBase": "Base URL", + "UseHardlinksInsteadOfCopy": "Utilitzeu enllaços durs en lloc de copiar", + "UsenetDelay": "Retard d'Usenet", + "UsenetDelayHelpText": "Retard en minuts per esperar abans de capturar una versió d'Usenet", + "UseProxy": "Utilitzeu el servidor intermediari", + "UserAgentProvidedByTheAppThatCalledTheAPI": "Agent d'usuari proporcionat per l'aplicació per fer peticions a l'API", + "Username": "Nom d'usuari", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Branca que s'utilitza per actualitzar Lidarr", + "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Branca utilitzada pel mecanisme d'actualització extern", + "Version": "Versió", + "WeekColumnHeader": "Capçalera de la columna de la setmana", + "CertificateValidationHelpText": "Canvieu la validació estricta de la certificació HTTPS. No canvieu tret que entengueu els riscos.", + "UILanguageHelpText": "Idioma que utilitzarà Lidarr per a la interfície d'usuari", + "UILanguageHelpTextWarning": "Es requereix una recàrrega del navegador", + "UnableToAddANewDownloadClientPleaseTryAgain": "No es pot afegir un nou client de descàrrega, torneu-ho a provar.", + "UnableToAddANewImportListExclusionPleaseTryAgain": "No es pot afegir una nova exclusió de llista, torneu-ho a provar.", + "Uptime": "Temps de funcionament", + "20MinutesTwenty": "60 minuts: {0}", + "45MinutesFourtyFive": "60 minuts: {0}", + "60MinutesSixty": "60 minuts: {0}", + "ClickToChangeQuality": "Feu clic per canviar la qualitat", + "CompletedDownloadHandling": "Gestió de descàrregues completades", + "Connections": "Connexions", + "ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "Per obtenir més informació sobre les llistes d'importació individuals, feu clic als botons d'informació.", + "GeneralSettings": "Configuració general", + "Global": "Global", + "LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "El registre de traça només s'hauria d'habilitar temporalment", + "Logging": "Registre", + "Logs": "Registres", + "LongDateFormat": "Format de data llarga", + "ManualImport": "Importació manual", + "MarkAsFailed": "Marca com a fallat", + "MarkAsFailedMessageText": "Esteu segur que voleu marcar '{0}' com a fallat?", + "MaximumLimits": "Límits màxims", + "AddingTag": "Afegint etiqueta", + "AddListExclusion": "Afegeix l'exclusió de la llista", + "AdvancedSettingsHiddenClickToShow": "Amagat, feu clic per mostrar", + "AlbumIsDownloadingInterp": "La pel·lícula s'està baixant - {0}% {1}", + "AlreadyInYourLibrary": "Ja a la teva biblioteca", + "Component": "Component", + "Folder": "Carpeta", + "HasPendingChangesSaveChanges": "Desa els canvis", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr admet molts clients de baixada de torrent i usenet populars.", + "OnGrab": "Al capturar", + "OnGrabHelpText": "Al capturar", + "Permissions": "Permisos", + "Port": "Port^", + "PortNumber": "Número de port", + "PosterSize": "Mida del pòster", + "RecycleBinCleanupDaysHelpText": "Establiu a 0 per desactivar la neteja automàtica", + "UnableToAddANewIndexerPleaseTryAgain": "No es pot afegir un indexador nou, torneu-ho a provar.", + "UnableToAddANewQualityProfilePleaseTryAgain": "No es pot afegir un perfil de qualitat nou, torneu-ho a provar.", + "UnableToLoadQualityDefinitions": "No es poden carregar les definicions de qualitat", + "UnableToLoadQualityProfiles": "No es poden carregar els perfils de qualitat", + "UnableToLoadReleaseProfiles": "No es poden carregar els perfils de retard", + "UpgradeAllowedHelpText": "Si les qualitats estan desactivades no s'actualitzaran", + "Year": "Any", + "YesCancel": "Si, cancel·la", + "BindAddressHelpText": "Adreça IPv4 vàlida o '*' per a totes les interfícies" } diff --git a/src/NzbDrone.Core/Localization/Core/cs.json b/src/NzbDrone.Core/Localization/Core/cs.json index 6f4e48e5c..824be02f9 100644 --- a/src/NzbDrone.Core/Localization/Core/cs.json +++ b/src/NzbDrone.Core/Localization/Core/cs.json @@ -4,7 +4,7 @@ "Tracks": "Stopa", "Columns": "Sloupce", "CopyUsingHardlinksHelpText": "Hardlinks použijte, když se pokoušíte kopírovat soubory z torrentů, které se stále používají", - "CopyUsingHardlinksHelpTextWarning": "Zámky souborů mohou občas zabránit přejmenování souborů, které se právě vysazují. Výsev můžete dočasně deaktivovat a použít funkci Radarr pro přejmenování.", + "CopyUsingHardlinksHelpTextWarning": "Zámky souborů mohou občas zabránit přejmenování souborů, které se právě vysazují. Výsev můžete dočasně deaktivovat a použít funkci Lidarr pro přejmenování.", "CutoffUnmet": "Cut-off Unmet", "MIA": "MIA", "Actions": "Akce", @@ -13,7 +13,7 @@ "ArtistAlbumClickToChangeTrack": "Kliknutím změníte film", "Authentication": "Ověření", "AutoRedownloadFailedHelpText": "Automaticky vyhledejte a pokuste se stáhnout jiné vydání", - "BackupFolderHelpText": "Relativní cesty budou v adresáři AppData společnosti Radarr", + "BackupFolderHelpText": "Relativní cesty budou v adresáři AppData společnosti Lidarr", "BindAddressHelpTextWarning": "Vyžaduje restart, aby se projevilo", "BlocklistRelease": "Vydání černé listiny", "Branch": "Větev", @@ -32,8 +32,8 @@ "About": "O", "AddListExclusion": "Přidat vyloučení seznamu", "ChmodFolder": "Složka chmod", - "ChmodFolderHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru Radarr vlastníkem souboru. Je lepší zajistit, aby stahovací klient správně nastavil oprávnění.", - "ChownGroupHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru Radarr vlastníkem souboru. Je lepší zajistit, aby stahovací klient používal stejnou skupinu jako Radarr.", + "ChmodFolderHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru Lidarr vlastníkem souboru. Je lepší zajistit, aby stahovací klient správně nastavil oprávnění.", + "ChownGroupHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru Lidarr vlastníkem souboru. Je lepší zajistit, aby stahovací klient používal stejnou skupinu jako Lidarr.", "AddingTag": "Přidávání značky", "AdvancedSettingsHiddenClickToShow": "Skryté, kliknutím zobrazíte", "AdvancedSettingsShownClickToHide": "Zobrazeno, kliknutím se skryjete", @@ -72,13 +72,13 @@ "AlternateTitleslength1Title": "Titul", "AlternateTitleslength1Titles": "Tituly", "Analytics": "Analytics", - "AnalyticsEnabledHelpText": "Odesílejte anonymní informace o použití a chybách na servery Radarru. To zahrnuje informace o vašem prohlížeči, které stránky Radarr WebUI používáte, hlášení chyb a také verzi operačního systému a běhového prostředí. Tyto informace použijeme k upřednostnění funkcí a oprav chyb.", + "AnalyticsEnabledHelpText": "Odesílejte anonymní informace o použití a chybách na servery Radarru. To zahrnuje informace o vašem prohlížeči, které stránky Lidarr WebUI používáte, hlášení chyb a také verzi operačního systému a běhového prostředí. Tyto informace použijeme k upřednostnění funkcí a oprav chyb.", "AnalyticsEnabledHelpTextWarning": "Vyžaduje restart, aby se projevilo", "ApiKeyHelpTextWarning": "Vyžaduje restart, aby se projevilo", "AppDataDirectory": "Adresář AppData", "ApplyTags": "Použít značky", "IncludeHealthWarningsHelpText": "Zahrnout zdravotní varování", - "IncludeUnknownArtistItemsHelpText": "Zobrazit položky bez filmu ve frontě. To by mohlo zahrnovat odstraněné filmy nebo cokoli jiného v kategorii Radarr", + "IncludeUnknownArtistItemsHelpText": "Zobrazit položky bez filmu ve frontě. To by mohlo zahrnovat odstraněné filmy nebo cokoli jiného v kategorii Lidarr", "IncludeUnmonitored": "Zahrnout Nesledováno", "Indexer": "Indexer", "IndexerPriority": "Priorita indexování", @@ -116,7 +116,6 @@ "QualityProfiles": "Profily kvality", "QualitySettings": "Nastavení kvality", "Queue": "Fronta", - "Radarr": "Radarr", "ReleaseWillBeProcessedInterp": "Vydání bude zpracováno {0}", "RemovedFromTaskQueue": "Odebráno z fronty úkolů", "RemoveFromBlocklist": "Odebrat z černé listiny", @@ -125,7 +124,7 @@ "RemoveHelpTextWarning": "Odebráním odstraníte stažené soubory a soubory z klienta pro stahování.", "RemoveTagExistingTag": "Stávající značka", "RemoveTagRemovingTag": "Odebírání značky", - "RenameTracksHelpText": "Pokud je přejmenování zakázáno, použije Radarr stávající název souboru", + "RenameTracksHelpText": "Pokud je přejmenování zakázáno, použije Lidarr stávající název souboru", "Reorder": "Přeobjednat", "ReplaceIllegalCharacters": "Nahraďte nelegální znaky", "ReplaceIllegalCharactersHelpText": "Nahraďte nepovolené znaky. Pokud není zaškrtnuto, radarr je místo toho odstraní", @@ -154,7 +153,7 @@ "Time": "Čas", "TorrentDelayHelpText": "Zpoždění v minutách čekání před popadnutím torrentu", "TotalFileSize": "Celková velikost souboru", - "AuthenticationMethodHelpText": "Vyžadovat uživatelské jméno a heslo pro přístup k Radarr", + "AuthenticationMethodHelpText": "Vyžadovat uživatelské jméno a heslo pro přístup k Lidarr", "Automatic": "Automatický", "BackupNow": "Zálohovat hned", "BypassProxyForLocalAddresses": "Obejít proxy pro místní adresy", @@ -168,11 +167,11 @@ "DownloadFailedCheckDownloadClientForMoreDetails": "Stahování se nezdařilo: pro více podrobností zkontrolujte stahovacího klienta", "DownloadPropersAndRepacksHelpTexts1": "Zda se má automaticky upgradovat na Propers / Repacks", "ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "Další informace o jednotlivých seznamech importů získáte kliknutím na informační tlačítka.", - "GrabReleaseMessageText": "Radarr nebyl schopen určit, pro který film je toto vydání určeno. Radarr nemusí být schopen toto vydání automaticky importovat. Chcete chytit „{0}“?", + "GrabReleaseMessageText": "Lidarr nebyl schopen určit, pro který film je toto vydání určeno. Lidarr nemusí být schopen toto vydání automaticky importovat. Chcete chytit „{0}“?", "HasPendingChangesSaveChanges": "Uložit změny", "IgnoredHelpText": "Vydání bude odmítnuto, pokud obsahuje jeden nebo více výrazů (nerozlišují se malá a velká písmena)", - "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr podporuje jakýkoli indexer, který používá standard Newznab, stejně jako další indexery uvedené níže.", - "LidarrTags": "Radarr tagy", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr podporuje jakýkoli indexer, který používá standard Newznab, stejně jako další indexery uvedené níže.", + "LidarrTags": "Lidarr tagy", "LoadingTrackFilesFailed": "Načítání filmových souborů se nezdařilo", "LocalPath": "Místní cesta", "MinimumFreeSpaceWhenImportingHelpText": "Zabraňte importu, pokud by bylo k dispozici méně než toto množství místa na disku", @@ -221,13 +220,13 @@ "RequiredPlaceHolder": "Přidat nové omezení", "RequiresRestartToTakeEffect": "Vyžaduje restart, aby se projevilo", "RescanAfterRefreshHelpText": "Po obnovení filmu znovu prohledejte složku filmu", - "RescanAfterRefreshHelpTextWarning": "Radarr nebude automaticky detekovat změny souborů, pokud není nastaveno na „Vždy“", + "RescanAfterRefreshHelpTextWarning": "Lidarr nebude automaticky detekovat změny souborů, pokud není nastaveno na „Vždy“", "RescanArtistFolderAfterRefresh": "Po obnovení znovu vyhledejte složku filmu", "Reset": "Resetovat", "ResetAPIKey": "Resetovat klíč API", "ResetAPIKeyMessageText": "Opravdu chcete resetovat klíč API?", "Restart": "Restartujte", - "RestartLidarr": "Restartujte Radarr", + "RestartLidarr": "Restartujte Lidarr", "RestartNow": "Restartovat nyní", "Restore": "Obnovit", "Result": "Výsledek", @@ -256,7 +255,7 @@ "ShowQualityProfile": "Zobrazit profil kvality", "Size": " Velikost", "SkipFreeSpaceCheck": "Přeskočit kontrolu volného prostoru", - "SkipFreeSpaceCheckWhenImportingHelpText": "Použijte, když Radarr nedokáže detekovat volné místo z kořenové složky filmu", + "SkipFreeSpaceCheckWhenImportingHelpText": "Použijte, když Lidarr nedokáže detekovat volné místo z kořenové složky filmu", "SorryThatAlbumCannotBeFound": "Je nám líto, ale tento film nelze najít.", "SorryThatArtistCannotBeFound": "Je nám líto, ale tento film nelze najít.", "Source": "Zdroj", @@ -271,7 +270,7 @@ "SuccessMyWorkIsDoneNoFilesToRename": "Úspěch! Moje práce je hotová, žádné soubory k přejmenování.", "SuccessMyWorkIsDoneNoFilesToRetag": "Úspěch! Moje práce je hotová, žádné soubory k přejmenování.", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Vyhledávání není u tohoto indexeru podporováno", - "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Použije se, když se automatické vyhledávání provádí pomocí uživatelského rozhraní nebo Radarr", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Použije se, když se automatické vyhledávání provádí pomocí uživatelského rozhraní nebo Lidarr", "Type": "Typ", "UISettings": "Nastavení uživatelského rozhraní", "UnableToLoadHistory": "Nelze načíst historii", @@ -301,7 +300,7 @@ "Unmonitored": "Nemonitorováno", "UnmonitoredHelpText": "Zahrnout nemonitorované filmy do zdroje iCal", "UpdateAutomaticallyHelpText": "Automaticky stahovat a instalovat aktualizace. Stále budete moci instalovat ze systému: Aktualizace", - "UpdateMechanismHelpText": "Použijte vestavěný aktualizátor Radarr nebo skript", + "UpdateMechanismHelpText": "Použijte vestavěný aktualizátor Lidarr nebo skript", "Updates": "Aktualizace", "UpdateScriptPathHelpText": "Cesta k vlastnímu skriptu, který přebírá extrahovaný balíček aktualizace a zpracovává zbytek procesu aktualizace", "UpgradeAllowedHelpText": "Pokud budou deaktivovány vlastnosti, nebudou upgradovány", @@ -313,7 +312,7 @@ "UsenetDelayHelpText": "Zpoždění v minutách čekání před uvolněním z Usenetu", "UseProxy": "Použij proxy", "Username": "Uživatelské jméno", - "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Pobočka, která se má použít k aktualizaci Radarr", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Pobočka, která se má použít k aktualizaci Lidarr", "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Pobočka používaná mechanismem externí aktualizace", "Version": "Verze", "WeekColumnHeader": "Záhlaví sloupce týdne", @@ -332,7 +331,7 @@ "CompletedDownloadHandling": "Zpracování stahování bylo dokončeno", "Connections": "Připojení", "CreateGroup": "Vytvořit skupinu", - "CutoffHelpText": "Jakmile je této kvality dosaženo, Radarr již nebude stahovat filmy", + "CutoffHelpText": "Jakmile je této kvality dosaženo, Lidarr již nebude stahovat filmy", "DelayProfiles": "Profily zpoždění", "DeleteBackupMessageText": "Opravdu chcete smazat zálohu „{0}“?", "DeleteDelayProfile": "Smazat profil zpoždění", @@ -399,10 +398,10 @@ "GrabSelected": "Chyťte vybrané", "HasPendingChangesNoChanges": "Žádné změny", "IgnoredAddresses": "Ignorované adresy", - "LaunchBrowserHelpText": " Otevřete webový prohlížeč a při spuštění aplikace přejděte na domovskou stránku Radarr.", - "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Radarr podporuje libovolného klienta pro stahování, který používá standard Newznab, stejně jako další klienty pro stahování uvedené níže.", + "LaunchBrowserHelpText": " Otevřete webový prohlížeč a při spuštění aplikace přejděte na domovskou stránku Lidarr.", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr podporuje libovolného klienta pro stahování, který používá standard Newznab, stejně jako další klienty pro stahování uvedené níže.", "Local": "Místní", - "LocalPathHelpText": "Cesta, kterou by Radarr měl použít pro místní přístup ke vzdálené cestě", + "LocalPathHelpText": "Cesta, kterou by Lidarr měl použít pro místní přístup ke vzdálené cestě", "LogFiles": "Záznam souborů", "LogLevel": "Úroveň protokolu", "LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "Trasování protokolování by mělo být povoleno pouze dočasně", @@ -454,7 +453,7 @@ "Track": "Stopa", "UpdateAll": "Aktualizovat vše", "Usenet": "Usenet", - "UILanguageHelpText": "Jazyk, který Radarr použije pro uživatelské rozhraní", + "UILanguageHelpText": "Jazyk, který Lidarr použije pro uživatelské rozhraní", "UILanguageHelpTextWarning": "Vyžaduje se opětovné načtení prohlížeče", "UnableToAddANewDownloadClientPleaseTryAgain": "Nelze přidat nového klienta pro stahování, zkuste to znovu.", "UnableToAddANewImportListExclusionPleaseTryAgain": "Nelze přidat nové vyloučení ze seznamu, zkuste to znovu.", @@ -467,5 +466,12 @@ "UnableToLoadBackups": "Nelze načíst zálohy", "UnableToLoadDelayProfiles": "Nelze načíst profily zpoždění", "UnableToLoadIndexers": "Nelze načíst indexery", - "UsenetDelay": "Usenet Zpoždění" + "UsenetDelay": "Usenet Zpoždění", + "OnRename": "Při přejmenování", + "OnUpgrade": "Při upgradu", + "OnHealthIssue": "K otázce zdraví", + "ThisCannotBeCancelled": "Toto nelze zrušit po spuštění bez restartování Whisparru.", + "OnGrab": "Chyť", + "NETCore": ".NET Core", + "MonoVersion": "Mono verze" } diff --git a/src/NzbDrone.Core/Localization/Core/da.json b/src/NzbDrone.Core/Localization/Core/da.json index 06b33e8ee..bd73b1867 100644 --- a/src/NzbDrone.Core/Localization/Core/da.json +++ b/src/NzbDrone.Core/Localization/Core/da.json @@ -80,7 +80,6 @@ "QualityProfile": "Kvalitetsprofil", "QualityProfiles": "Kvalitetsprofiler", "QualitySettings": "Kvalitetsindstillinger", - "Radarr": "Radarr", "ReleaseRejected": "Udgivelse afvist", "ReleaseStatuses": "Frigør status", "ReleaseWillBeProcessedInterp": "Udgivelsen behandles {0}", @@ -96,21 +95,21 @@ "RemoveSelected": "Fjern valgte", "RemoveSelectedMessageText": "Er du sikker på, at du vil fjerne de valgte emner fra sortlisten?", "RemoveTagExistingTag": "Eksisterende mærke", - "RenameTracksHelpText": "Radarr bruger det eksisterende filnavn, hvis omdøbning er deaktiveret", + "RenameTracksHelpText": "Lidarr bruger det eksisterende filnavn, hvis omdøbning er deaktiveret", "Reorder": "Omarranger", "ReplaceIllegalCharacters": "Udskift ulovlige tegn", - "ReplaceIllegalCharactersHelpText": "Udskift ulovlige tegn. Hvis det ikke er markeret, fjerner Radarr dem i stedet", + "ReplaceIllegalCharactersHelpText": "Udskift ulovlige tegn. Hvis det ikke er markeret, fjerner Lidarr dem i stedet", "RequiredHelpText": "Frigivelsen skal indeholde mindst et af disse udtryk (ufølsom)", "RequiredPlaceHolder": "Tilføj ny begrænsning", "RequiresRestartToTakeEffect": "Kræver genstart for at træde i kraft", "RescanAfterRefreshHelpText": "Scan igen filmmappen efter opdatering af filmen", - "RescanAfterRefreshHelpTextWarning": "Radarr registrerer ikke automatisk ændringer i filer, når de ikke er indstillet til 'Altid'", + "RescanAfterRefreshHelpTextWarning": "Lidarr registrerer ikke automatisk ændringer i filer, når de ikke er indstillet til 'Altid'", "RescanArtistFolderAfterRefresh": "Genscan filmmappe efter opdatering", "Reset": "Nulstil", "ResetAPIKey": "Nulstil API-nøgle", "ResetAPIKeyMessageText": "Er du sikker på, at du vil nulstille din API-nøgle?", "Restart": "Genstart", - "RestartLidarr": "Genstart Radarr", + "RestartLidarr": "Genstart Lidarr", "RestartNow": "Genstart nu", "Restore": "Gendan", "RestoreBackup": "Gendan sikkerhedskopi", @@ -141,7 +140,7 @@ "StartTypingOrSelectAPathBelow": "Start med at skrive, eller vælg en sti nedenfor", "Status": "Status", "Style": "Stil", - "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Bruges, når der foretages automatiske søgninger via brugergrænsefladen eller af Radarr", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Bruges, når der foretages automatiske søgninger via brugergrænsefladen eller af Lidarr", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Bruges, når der bruges interaktiv søgning", "TagIsNotUsedAndCanBeDeleted": "Tag bruges ikke og kan slettes", "Tags": "Mærker", @@ -163,12 +162,12 @@ "UnmonitoredHelpText": "Inkluder ikke-overvågede film i iCal-feedet", "Updates": "Opdateringer", "Version": "Version", - "LaunchBrowserHelpText": " Åbn en webbrowser, og naviger til Radarr-hjemmesiden ved start af appen.", + "LaunchBrowserHelpText": " Åbn en webbrowser, og naviger til Lidarr-hjemmesiden ved start af appen.", "AdvancedSettingsShownClickToHide": "Vist, klik for at skjule", "AgeWhenGrabbed": "Alder (når grebet)", "AlbumIsDownloadingInterp": "Film downloades - {0}% {1}", "AlternateTitles": "Alternativ Titel", - "UILanguageHelpText": "Sprog, som Radarr vil bruge til UI", + "UILanguageHelpText": "Sprog, som Lidarr vil bruge til UI", "UISettings": "UI-indstillinger", "UnableToAddANewImportListExclusionPleaseTryAgain": "Kunne ikke tilføje en ny listeekskludering. Prøv igen.", "UnableToAddANewIndexerPleaseTryAgain": "Kunne ikke tilføje en ny indekser. Prøv igen.", @@ -246,7 +245,7 @@ "ShowMonitoredHelpText": "Vis overvåget status under plakat", "Size": " Størrelse", "SkipFreeSpaceCheck": "Spring fri pladscheck over", - "SkipFreeSpaceCheckWhenImportingHelpText": "Brug, når Radarr ikke er i stand til at registrere ledig plads fra din filmrodmappe", + "SkipFreeSpaceCheckWhenImportingHelpText": "Brug, når Lidarr ikke er i stand til at registrere ledig plads fra din filmrodmappe", "SorryThatAlbumCannotBeFound": "Beklager, den film kan ikke findes.", "SorryThatArtistCannotBeFound": "Beklager, den film kan ikke findes.", "Source": "Kilde", @@ -261,7 +260,7 @@ "ApplyTagsHelpTexts4": "Erstat: Udskift tags med de indtastede tags (indtast ingen tags for at rydde alle tags)", "ArtistAlbumClickToChangeTrack": "Klik for at skifte film", "Authentication": "Godkendelse", - "AuthenticationMethodHelpText": "Kræv brugernavn og adgangskode for at få adgang til Radarr", + "AuthenticationMethodHelpText": "Kræv brugernavn og adgangskode for at få adgang til Lidarr", "AutoRedownloadFailedHelpText": "Søg automatisk efter og forsøg at downloade en anden udgivelse", "BackupFolderHelpText": "Relative stier vil være under Radarrs AppData-bibliotek", "BackupNow": "Backup Nu", @@ -282,9 +281,9 @@ "ChangeHasNotBeenSavedYet": "Ændring er endnu ikke gemt", "ChmodFolder": "chmod mappe", "ChmodFolderHelpText": "Oktal, anvendt under import / omdøbning til mediemapper og filer (uden udførelse af bits)", - "ChmodFolderHelpTextWarning": "Dette fungerer kun, hvis den bruger, der kører Radarr, er ejeren af filen. Det er bedre at sikre, at downloadklienten indstiller tilladelserne korrekt.", + "ChmodFolderHelpTextWarning": "Dette fungerer kun, hvis den bruger, der kører Lidarr, er ejeren af filen. Det er bedre at sikre, at downloadklienten indstiller tilladelserne korrekt.", "ChownGroupHelpText": "Gruppens navn eller gid. Brug gid til eksterne filsystemer.", - "ChownGroupHelpTextWarning": "Dette fungerer kun, hvis den bruger, der kører Radarr, er ejeren af filen. Det er bedre at sikre, at downloadklienten bruger den samme gruppe som Radarr.", + "ChownGroupHelpTextWarning": "Dette fungerer kun, hvis den bruger, der kører Lidarr, er ejeren af filen. Det er bedre at sikre, at downloadklienten bruger den samme gruppe som Lidarr.", "Clear": "Ryd", "ClickToChangeQuality": "Klik for at ændre kvalitet", "ClientPriority": "Kundens prioritet", @@ -298,7 +297,7 @@ "CreateEmptyArtistFolders": "Opret tomme filmmapper", "CreateEmptyArtistFoldersHelpText": "Opret manglende filmmapper under diskscanning", "CreateGroup": "Opret gruppe", - "CutoffHelpText": "Når denne kvalitet er nået, downloader Radarr ikke længere film", + "CutoffHelpText": "Når denne kvalitet er nået, downloader Lidarr ikke længere film", "CutoffUnmet": "Afskåret ude", "Dates": "Datoer", "DBMigration": "DB Migration", @@ -360,7 +359,7 @@ "GeneralSettings": "Generelle indstillinger", "Global": "Global", "GoToInterp": "Gå til {0}", - "GrabReleaseMessageText": "Radarr var ikke i stand til at bestemme, hvilken film denne udgivelse var til. Radarr kan muligvis ikke automatisk importere denne udgivelse. Vil du hente '{0}'?", + "GrabReleaseMessageText": "Lidarr var ikke i stand til at bestemme, hvilken film denne udgivelse var til. Lidarr kan muligvis ikke automatisk importere denne udgivelse. Vil du hente '{0}'?", "GrabSelected": "Greb Valgt", "Group": "Gruppe", "HasPendingChangesNoChanges": "Ingen ændringer", @@ -395,7 +394,7 @@ "Type": "Type", "UnableToLoadQualityProfiles": "Kunne ikke indlæse kvalitetsprofiler", "Ungroup": "Fjern gruppe", - "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Filial, der skal bruges til at opdatere Radarr", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Filial, der skal bruges til at opdatere Lidarr", "SuccessMyWorkIsDoneNoFilesToRename": "Succes! Mit arbejde er udført, ingen filer at omdøbe.", "SuccessMyWorkIsDoneNoFilesToRetag": "Succes! Mit arbejde er udført, ingen filer at omdøbe.", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS understøttes ikke med denne indekseringsenhed", @@ -404,13 +403,13 @@ "UnableToLoadDownloadClientOptions": "Kunne ikke indlæse indstillinger for downloadklient", "UnableToLoadGeneralSettings": "Kan ikke indlæse generelle indstillinger", "IsTagUsedCannotBeDeletedWhileInUse": "Kan ikke slettes under brug", - "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Radarr understøtter enhver downloadklient, der bruger Newznab-standarden, samt andre downloadklienter, der er anført nedenfor.", - "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr understøtter enhver indekserer, der bruger Newznab-standarden såvel som andre indeksatorer, der er anført nedenfor.", - "LidarrTags": "Radarr tags", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr understøtter enhver downloadklient, der bruger Newznab-standarden, samt andre downloadklienter, der er anført nedenfor.", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr understøtter enhver indekserer, der bruger Newznab-standarden såvel som andre indeksatorer, der er anført nedenfor.", + "LidarrTags": "Lidarr tags", "LoadingTrackFilesFailed": "Indlæsning af filmfiler mislykkedes", "Local": "Lokal", "LocalPath": "Lokal sti", - "LocalPathHelpText": "Sti, som Radarr skal bruge for at få adgang til den eksterne sti lokalt", + "LocalPathHelpText": "Sti, som Lidarr skal bruge for at få adgang til den eksterne sti lokalt", "LogFiles": "Logfiler", "LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "Sporlogning bør kun aktiveres midlertidigt", "Logging": "Logning", @@ -433,7 +432,7 @@ "AlternateTitleslength1Title": "Titel", "AlternateTitleslength1Titles": "Titler", "Analytics": "Analyser", - "AnalyticsEnabledHelpText": "Send anonym brugs- og fejlinformation til Radarrs servere. Dette inkluderer information i din browser, hvilke Radarr WebUI-sider du bruger, fejlrapportering samt OS og runtime-version. Vi bruger disse oplysninger til at prioritere funktioner og fejlrettelser.", + "AnalyticsEnabledHelpText": "Send anonym brugs- og fejlinformation til Radarrs servere. Dette inkluderer information i din browser, hvilke Lidarr WebUI-sider du bruger, fejlrapportering samt OS og runtime-version. Vi bruger disse oplysninger til at prioritere funktioner og fejlrettelser.", "AnalyticsEnabledHelpTextWarning": "Kræver genstart for at træde i kraft", "ApiKeyHelpTextWarning": "Kræver genstart for at træde i kraft", "Automatic": "Automatisk", @@ -466,5 +465,12 @@ "Downloading": "Downloader", "ExtraFileExtensionsHelpTexts1": "Kommasepareret liste over ekstra filer, der skal importeres (.nfo importeres som .nfo-orig)", "FileDateHelpText": "Skift fildato ved import / genscanning", - "FileManagement": "Fil Håndtering" + "FileManagement": "Fil Håndtering", + "OnGrab": "On Grab", + "Tracks": "Spor", + "OnHealthIssue": "Om sundhedsspørgsmål", + "OnRename": "Om omdøb", + "OnUpgrade": "Ved opgradering", + "ThisCannotBeCancelled": "Dette kan ikke annulleres en gang startet uden genstart af Whisparr.", + "MonoVersion": "Mono-version" } diff --git a/src/NzbDrone.Core/Localization/Core/de.json b/src/NzbDrone.Core/Localization/Core/de.json index 541427948..6c43ba008 100644 --- a/src/NzbDrone.Core/Localization/Core/de.json +++ b/src/NzbDrone.Core/Localization/Core/de.json @@ -119,7 +119,7 @@ "CalendarWeekColumnHeaderHelpText": "Wird in der Wochenansicht über jeder Spalte angezeigt", "CancelMessageText": "Diese laufende Aufgabe wirklich abbrechen?", "ChownGroupHelpText": "Gruppenname oder gid. Verwenden Sie gid für entfernte Dateisysteme.", - "ChownGroupHelpTextWarning": "Dies funktioniert nur, wenn der Benutzer, der Radarr ausführt, der Eigentümer der Datei ist. Es ist besser, sicherzustellen, dass der Download-Client die gleiche Gruppe wie Radarr verwendet.", + "ChownGroupHelpTextWarning": "Dies funktioniert nur, wenn der Benutzer, der Lidarr ausführt, der Eigentümer der Datei ist. Es ist besser, sicherzustellen, dass der Download-Client die gleiche Gruppe wie Lidarr verwendet.", "CreateEmptyArtistFoldersHelpText": "Leere Filmordner für fehlende Filme beim Scan erstellen", "DeleteMetadataProfileMessageText": "Qualitätsprofil '{0}' wirklich löschen?", "DeleteQualityProfileMessageText": "Qualitätsprofil '{0}' wirklich löschen?", @@ -147,9 +147,9 @@ "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Solange bis die Qualität erreicht oder übertroffen wird verbessern", "IsTagUsedCannotBeDeletedWhileInUse": "Kann während der Benutzung nicht gelöscht werden", "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Jeder Indexer der den Newznab-Standard verwendet oder unten aufgelistet ist wird untertützt.", - "LidarrTags": "Radarr Tags", + "LidarrTags": "Lidarr Tags", "LocalPath": "Lokaler Pfad", - "LocalPathHelpText": "Pfad, den Radarr verwenden sollte, um lokal auf den Entfernten-Pfad zuzugreifen", + "LocalPathHelpText": "Pfad, den Lidarr verwenden sollte, um lokal auf den Entfernten-Pfad zuzugreifen", "LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "Trace logging sollte nur kurzzeitig aktiviert werden", "Logs": "Protokolle", "MaintenanceRelease": "Wartungsupdate", @@ -187,7 +187,6 @@ "GrabID": "Erfass ID", "PublishedDate": "Veröffentlichungs Datum", "Quality": "Qualität", - "Radarr": "Radarr", "RemoveFromBlocklist": "Aus der Sperrliste entfernen", "RemoveFromDownloadClient": "Aus dem Downloader entfernen", "RemoveFromQueue": "Aus der Warteschlage entfernen", @@ -219,13 +218,13 @@ "GrabReleaseMessageText": "Das Release konnte keinem Film zugeordnet werden. Ein automatischer Import wird nicht möglich sein. Trotzdem '{0}' erfassen?", "GrabSelected": "Auswahl erfassen", "Group": "Gruppe", - "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Wird für automatische Suchen genutzt die vom Benutzer oder von Radarr gestartet werden", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Wird für automatische Suchen genutzt die vom Benutzer oder von Lidarr gestartet werden", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Wird für die manuelle Suche benutzt", "RssSyncIntervalHelpText": "Intervall in Minuten. Zum deaktivieren auf 0 setzen ( Dies wird das automatische Release erfassen deaktivieren )", "SSLCertPath": "Pfad zum SSL Zertifikat", "AlternateTitleslength1Titles": "Titel", "AnalyticsEnabledHelpTextWarning": "Erfordert einen Neustart", - "AnyReleaseOkHelpText": "Readarr wechselt automatisch zu der Edition, die am besten zu den heruntergeladenen Dateien passt", + "AnyReleaseOkHelpText": "Lidarr wechselt automatisch zu der Edition, die am besten zu den heruntergeladenen Dateien passt", "SearchAll": "Suche alle", "SslPortHelpTextWarning": "Erfordert einen Neustart", "StandardTrackFormat": "Standard Filmformat", @@ -335,7 +334,7 @@ "Indexers": "Indexer", "IndexerSettings": "Indexer Einstellungen", "Label": "Label", - "LaunchBrowserHelpText": " Öffne die Startseite von Radarr im Webbrowser nach dem Start.", + "LaunchBrowserHelpText": " Öffne die Startseite von Lidarr im Webbrowser nach dem Start.", "Level": "Stufe", "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr unterstützt viele populäre Torrent und Usenet Download Clients.", "LoadingTrackFilesFailed": "Laden der Film-Dateien fehlgeschlagen", @@ -421,7 +420,7 @@ "Reset": "Zurücksetzen", "ResetAPIKey": "API-Schlüssel zurücksetzen", "Restart": "Neustarten", - "RestartLidarr": "Radarr Neustarten", + "RestartLidarr": "Lidarr Neustarten", "RestoreBackup": "Backup einspielen", "Result": "Ergebnis", "RetentionHelpText": "Nur Usenet: Für eine umbegrenzte Aufbewahrung auf 0 setzen", @@ -485,7 +484,7 @@ "UnableToLoadNamingSettings": "Umbenennungeinstellungen konnten nicht geladen werden", "Updates": "Updates", "Usenet": "Usenet", - "BackupIntervalHelpText": "Intervall zum Sichern der Readarr-DB und der Einstellungen", + "BackupIntervalHelpText": "Intervall zum Sichern der Lidarr-DB und der Einstellungen", "UnableToLoadBlocklist": "Sperrliste konnte nicht geladen werden", "UnableToLoadDelayProfiles": "Verzögerungsprofile konnten nicht geladen werden", "UnableToLoadDownloadClientOptions": "Downloader Einstellungen konnten nicht geladen werden", @@ -493,7 +492,6 @@ "UnableToLoadGeneralSettings": "Allgemeine Einstellungen konnten nicht geladen werden", "UnableToLoadHistory": "Verlauf konnte nicht geladen werden.", "Username": "Benutzername", - "Readarr": "Readarr", "Country": "Land", "DeleteImportList": "Importliste löschen", "Album": "Album", @@ -614,7 +612,6 @@ "IsShowingMonitoredUnmonitorSelected": "Ausgewählte nicht mehr beobachten", "LatestAlbum": "Neuestes Album", "LatestAlbumData": "Beobachte die neuesten Alben und alle zukünftigen Alben", - "Lidarr": "Lidarr", "LidarrSupportsMultipleListsForImportingAlbumsAndArtistsIntoTheDatabase": "Lidarr unterstützt mehrere Listen für den Import von Alben und Künstlern in die Datenbank.", "ManageTracks": "Titel verwalten", "ManualDownload": "Manueller Download", @@ -692,5 +689,9 @@ "AddImportListExclusionHelpText": "Verhindern, dass der Künstler durch Importlisten zu Lidarr hinzugefügt wird", "AlbumStudio": "Albumstudio", "IndexerIdvalue0IncludeInPreferredWordsRenamingFormat": "In das Umbenennungsformat {Preferred Word} einbeziehen", - "OnHealthIssue": "Bei Zustandsproblem" + "OnHealthIssue": "Bei Zustandsproblem", + "ThisCannotBeCancelled": "Nach dem Start kann dies nicht mehr abgebrochen werden ohne alle Indexer zu deaktivieren.", + "AddedArtistSettings": "Autor Einstellungen hinzugefügt", + "ImportListSpecificSettings": "Listenspezifische Einstellungen importieren", + "MonoVersion": "Mono Version" } diff --git a/src/NzbDrone.Core/Localization/Core/el.json b/src/NzbDrone.Core/Localization/Core/el.json index 631f7f66e..b1b2f12ec 100644 --- a/src/NzbDrone.Core/Localization/Core/el.json +++ b/src/NzbDrone.Core/Localization/Core/el.json @@ -1,7 +1,7 @@ { "Language": "Γλώσσα", "UILanguage": "Γλώσσα διεπαφής χρήστη", - "BackupFolderHelpText": "Οι σχετικές διαδρομές θα βρίσκονται στον κατάλογο AppData του Radarr", + "BackupFolderHelpText": "Οι σχετικές διαδρομές θα βρίσκονται στον κατάλογο AppData του Lidarr", "BackupRetentionHelpText": "Τα αυτόματα αντίγραφα ασφαλείας που είναι παλαιότερα από την περίοδο διατήρησης θα καθαρίζονται αυτόματα", "BindAddress": "Δεσμευμένη διεύθυνση", "BindAddressHelpText": "Έγκυρη διεύθυνση IP4 ή «*» για όλες τις διεπαφές", @@ -17,9 +17,9 @@ "ChangeHasNotBeenSavedYet": "Η αλλαγή δεν έχει αποθηκευτεί ακόμα", "ChmodFolder": "φάκελος chmod", "ChmodFolderHelpText": "Οκτάλ, εφαρμόζεται κατά την εισαγωγή / μετονομασία σε φακέλους πολυμέσων και αρχεία (χωρίς εκτελέσιμα bit)", - "ChmodFolderHelpTextWarning": "Αυτό λειτουργεί μόνο εάν ο χρήστης που εκτελεί το Radarr είναι ο κάτοχος του αρχείου. Είναι καλύτερο να διασφαλίσετε ότι ο πελάτης λήψης ορίζει σωστά τα δικαιώματα.", + "ChmodFolderHelpTextWarning": "Αυτό λειτουργεί μόνο εάν ο χρήστης που εκτελεί το Lidarr είναι ο κάτοχος του αρχείου. Είναι καλύτερο να διασφαλίσετε ότι ο πελάτης λήψης ορίζει σωστά τα δικαιώματα.", "ChownGroupHelpText": "Όνομα ομάδας ή gid. Χρησιμοποιήστε το gid για απομακρυσμένα συστήματα αρχείων.", - "ChownGroupHelpTextWarning": "Αυτό λειτουργεί μόνο εάν ο χρήστης που εκτελεί το Radarr είναι ο κάτοχος του αρχείου. Είναι καλύτερο να διασφαλίσετε ότι ο πελάτης λήψης χρησιμοποιεί την ίδια ομάδα με το Radarr.", + "ChownGroupHelpTextWarning": "Αυτό λειτουργεί μόνο εάν ο χρήστης που εκτελεί το Lidarr είναι ο κάτοχος του αρχείου. Είναι καλύτερο να διασφαλίσετε ότι ο πελάτης λήψης χρησιμοποιεί την ίδια ομάδα με το Lidarr.", "Clear": "Καθαρισμός", "CloneIndexer": "Δείκτης κλώνου", "CloneProfile": "Προφίλ κλώνου", @@ -51,15 +51,15 @@ "ImportExtraFiles": "Εισαγωγή επιπλέον αρχείων", "ImportExtraFilesHelpText": "Εισαγωγή αντίστοιχων επιπλέον αρχείων (υπότιτλοι, nfo, κ.λπ.) μετά την εισαγωγή ενός αρχείου ταινίας", "ImportFailedInterp": "Η εισαγωγή απέτυχε: {0}", - "IncludeUnknownArtistItemsHelpText": "Εμφάνιση στοιχείων χωρίς ταινία στην ουρά. Αυτό μπορεί να περιλαμβάνει ταινίες που έχουν αφαιρεθεί ή οτιδήποτε άλλο στην κατηγορία του Radarr", + "IncludeUnknownArtistItemsHelpText": "Εμφάνιση στοιχείων χωρίς ταινία στην ουρά. Αυτό μπορεί να περιλαμβάνει ταινίες που έχουν αφαιρεθεί ή οτιδήποτε άλλο στην κατηγορία του Lidarr", "Indexers": "Ευρετήρια", "InteractiveSearch": "Διαδραστική αναζήτηση", "IsCutoffCutoff": "Αποκόβω", "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Αναβαθμίστε έως ότου ικανοποιηθεί ή ξεπεραστεί αυτή η ποιότητα", - "LaunchBrowserHelpText": " Ανοίξτε ένα πρόγραμμα περιήγησης ιστού και μεταβείτε στην αρχική σελίδα του Radarr κατά την έναρξη της εφαρμογής.", + "LaunchBrowserHelpText": " Ανοίξτε ένα πρόγραμμα περιήγησης ιστού και μεταβείτε στην αρχική σελίδα του Lidarr κατά την έναρξη της εφαρμογής.", "Level": "Επίπεδο", - "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Το Radarr υποστηρίζει οποιονδήποτε πελάτη λήψης που χρησιμοποιεί το πρότυπο Newznab, καθώς και άλλους πελάτες λήψης που αναφέρονται παρακάτω.", - "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Το Radarr υποστηρίζει οποιοδήποτε ευρετήριο που χρησιμοποιεί το πρότυπο Newznab, καθώς και άλλους δείκτες που αναφέρονται παρακάτω.", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Το Lidarr υποστηρίζει οποιονδήποτε πελάτη λήψης που χρησιμοποιεί το πρότυπο Newznab, καθώς και άλλους πελάτες λήψης που αναφέρονται παρακάτω.", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Το Lidarr υποστηρίζει οποιοδήποτε ευρετήριο που χρησιμοποιεί το πρότυπο Newznab, καθώς και άλλους δείκτες που αναφέρονται παρακάτω.", "PageSize": "Μέγεθος σελίδας", "PosterSize": "Μέγεθος αφίσας", "Preferred": "Προνομιούχος", @@ -82,16 +82,16 @@ "RemoveTagExistingTag": "Υφιστάμενη ετικέτα", "Reorder": "Παραγγέλλω πάλι", "ReplaceIllegalCharacters": "Αντικαταστήστε τους παράνομους χαρακτήρες", - "ReplaceIllegalCharactersHelpText": "Αντικαταστήστε τους παράνομους χαρακτήρες. Εάν δεν είναι επιλεγμένο, το Radarr θα τα καταργήσει", + "ReplaceIllegalCharactersHelpText": "Αντικαταστήστε τους παράνομους χαρακτήρες. Εάν δεν είναι επιλεγμένο, το Lidarr θα τα καταργήσει", "RequiredHelpText": "Η κυκλοφορία πρέπει να περιέχει τουλάχιστον έναν από αυτούς τους όρους (μη ευαίσθητη υπόθεση)", "RequiredPlaceHolder": "Προσθέστε νέο περιορισμό", "RequiresRestartToTakeEffect": "Απαιτείται επανεκκίνηση για να τεθεί σε ισχύ", "RescanAfterRefreshHelpText": "Κάντε σάρωση του φακέλου ταινίας μετά την ανανέωση της ταινίας", - "RescanAfterRefreshHelpTextWarning": "Το Radarr δεν θα εντοπίσει αυτόματα αλλαγές στα αρχεία όταν δεν έχει οριστεί σε \"Πάντα\"", + "RescanAfterRefreshHelpTextWarning": "Το Lidarr δεν θα εντοπίσει αυτόματα αλλαγές στα αρχεία όταν δεν έχει οριστεί σε \"Πάντα\"", "RescanArtistFolderAfterRefresh": "Επανεκκίνηση φακέλου ταινίας μετά την ανανέωση", "Reset": "Επαναφορά", "ResetAPIKey": "Επαναφορά κλειδιού API", - "RestartLidarr": "Επανεκκινήστε το Radarr", + "RestartLidarr": "Επανεκκινήστε το Lidarr", "RestartNow": "Επανεκκίνηση τώρα", "RetryingDownloadInterp": "Επανάληψη λήψης {0} στις {1}", "RootFolder": "Φάκελος ρίζας", @@ -145,7 +145,7 @@ "UnmonitoredHelpText": "Συμπεριλάβετε ταινίες χωρίς παρακολούθηση στη ροή iCal", "UpdateAll": "Ενημέρωση όλων", "UpdateAutomaticallyHelpText": "Αυτόματη λήψη και εγκατάσταση ενημερώσεων. Θα εξακολουθείτε να μπορείτε να κάνετε εγκατάσταση από το System: Updates", - "UpdateMechanismHelpText": "Χρησιμοποιήστε το ενσωματωμένο πρόγραμμα ενημέρωσης του Radarr ή ένα σενάριο", + "UpdateMechanismHelpText": "Χρησιμοποιήστε το ενσωματωμένο πρόγραμμα ενημέρωσης του Lidarr ή ένα σενάριο", "Updates": "Ενημερώσεις", "UpdateScriptPathHelpText": "Διαδρομή σε ένα προσαρμοσμένο σενάριο που λαμβάνει ένα εξαγόμενο πακέτο ενημέρωσης και χειρίζεται το υπόλοιπο της διαδικασίας ενημέρωσης", "UpgradeAllowedHelpText": "Εάν οι ιδιότητες με ειδικές ανάγκες δεν θα αναβαθμιστούν", @@ -158,7 +158,7 @@ "UsenetDelay": "Καθυστέρηση Usenet", "UsenetDelayHelpText": "Καθυστέρηση σε λίγα λεπτά για να περιμένετε πριν πάρετε μια κυκλοφορία από το Usenet", "Username": "Όνομα χρήστη", - "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Υποκατάστημα για χρήση για την ενημέρωση του Radarr", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Υποκατάστημα για χρήση για την ενημέρωση του Lidarr", "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Υποκατάστημα που χρησιμοποιείται από εξωτερικό μηχανισμό ενημέρωσης", "Version": "Εκδοχή", "45MinutesFourtyFive": "60 λεπτά: {0}", @@ -172,7 +172,7 @@ "AgeWhenGrabbed": "Ηλικία (όταν αρπαχτεί)", "AlbumIsDownloadingInterp": "Λήψη ταινίας - {0}% {1}", "AlreadyInYourLibrary": "Ήδη στη βιβλιοθήκη σας", - "AnalyticsEnabledHelpText": "Στείλτε ανώνυμες πληροφορίες χρήσης και σφάλματος στους διακομιστές του Radarr. Αυτό περιλαμβάνει πληροφορίες στο πρόγραμμα περιήγησής σας, ποιες σελίδες Radarr WebUI χρησιμοποιείτε, αναφορά σφαλμάτων καθώς και έκδοση λειτουργικού συστήματος και χρόνου εκτέλεσης. Θα χρησιμοποιήσουμε αυτές τις πληροφορίες για να δώσουμε προτεραιότητα σε λειτουργίες και διορθώσεις σφαλμάτων.", + "AnalyticsEnabledHelpText": "Στείλτε ανώνυμες πληροφορίες χρήσης και σφάλματος στους διακομιστές του Lidarr. Αυτό περιλαμβάνει πληροφορίες στο πρόγραμμα περιήγησής σας, ποιες σελίδες Lidarr WebUI χρησιμοποιείτε, αναφορά σφαλμάτων καθώς και έκδοση λειτουργικού συστήματος και χρόνου εκτέλεσης. Θα χρησιμοποιήσουμε αυτές τις πληροφορίες για να δώσουμε προτεραιότητα σε λειτουργίες και διορθώσεις σφαλμάτων.", "ManualImport": "Μη αυτόματη εισαγωγή", "Scheduled": "Προγραμματισμένος", "ScriptPath": "Διαδρομή σεναρίου", @@ -188,7 +188,7 @@ "ShowMonitoredHelpText": "Εμφάνιση της παρακολούθησης κατάστασης στην αφίσα", "Size": " Μέγεθος", "SkipFreeSpaceCheck": "Παράλειψη ελέγχου ελεύθερου χώρου", - "SkipFreeSpaceCheckWhenImportingHelpText": "Χρησιμοποιήστε το όταν το Radarr δεν μπορεί να εντοπίσει ελεύθερο χώρο από τον ριζικό φάκελο της ταινίας σας", + "SkipFreeSpaceCheckWhenImportingHelpText": "Χρησιμοποιήστε το όταν το Lidarr δεν μπορεί να εντοπίσει ελεύθερο χώρο από τον ριζικό φάκελο της ταινίας σας", "SorryThatAlbumCannotBeFound": "Δυστυχώς, δεν είναι δυνατή η εύρεση αυτής της ταινίας.", "SorryThatArtistCannotBeFound": "Δυστυχώς, δεν είναι δυνατή η εύρεση αυτής της ταινίας.", "Source": "Πηγή", @@ -208,7 +208,7 @@ "ApplyTagsHelpTexts4": "Αντικατάσταση: Αντικαταστήστε τις ετικέτες με τις εισαγόμενες ετικέτες (μην εισάγετε ετικέτες για να διαγράψετε όλες τις ετικέτες)", "ArtistAlbumClickToChangeTrack": "Κάντε κλικ για να αλλάξετε ταινία", "Authentication": "Αυθεντικοποίηση", - "AuthenticationMethodHelpText": "Απαιτήστε όνομα χρήστη και κωδικό πρόσβασης για πρόσβαση στο Radarr", + "AuthenticationMethodHelpText": "Απαιτήστε όνομα χρήστη και κωδικό πρόσβασης για πρόσβαση στο Lidarr", "AutoRedownloadFailedHelpText": "Αυτόματη αναζήτηση και απόπειρα λήψης διαφορετικής έκδοσης", "Backups": "Δημιουργία αντιγράφων ασφαλείας", "BindAddressHelpTextWarning": "Απαιτείται επανεκκίνηση για να τεθεί σε ισχύ", @@ -220,11 +220,11 @@ "Connections": "Συνδέσεις", "ConnectSettings": "Σύνδεση ρυθμίσεων", "CopyUsingHardlinksHelpText": "Χρησιμοποιήστε σκληρούς συνδέσμους όταν προσπαθείτε να αντιγράψετε αρχεία από torrents που εξακολουθούν να σπέρνονται", - "CopyUsingHardlinksHelpTextWarning": "Περιστασιακά, τα κλειδώματα αρχείων ενδέχεται να αποτρέψουν τη μετονομασία αρχείων που έχουν σπαρθεί. Μπορείτε προσωρινά να απενεργοποιήσετε τη σπορά και να χρησιμοποιήσετε τη λειτουργία μετονομασίας Radarr ως εργασία.", + "CopyUsingHardlinksHelpTextWarning": "Περιστασιακά, τα κλειδώματα αρχείων ενδέχεται να αποτρέψουν τη μετονομασία αρχείων που έχουν σπαρθεί. Μπορείτε προσωρινά να απενεργοποιήσετε τη σπορά και να χρησιμοποιήσετε τη λειτουργία μετονομασίας Lidarr ως εργασία.", "CreateEmptyArtistFolders": "Δημιουργήστε άδειους φακέλους ταινιών", "CreateEmptyArtistFoldersHelpText": "Δημιουργήστε φακέλους ταινιών που λείπουν κατά τη σάρωση δίσκου", "CreateGroup": "Δημιουργησε ΟΜΑΔΑ", - "CutoffHelpText": "Μόλις επιτευχθεί αυτή η ποιότητα, το Radarr δεν θα κατεβάζει πλέον ταινίες", + "CutoffHelpText": "Μόλις επιτευχθεί αυτή η ποιότητα, το Lidarr δεν θα κατεβάζει πλέον ταινίες", "CutoffUnmet": "Αποκοπή Unmet", "DelayProfile": "Προφίλ χρονοκαθυστέρησης", "DelayProfiles": "Προφίλ χρονοκαθυστέρησης", @@ -286,7 +286,7 @@ "Grab": "Αρπάζω", "GrabID": "Πιάσε ταυτότητα", "GrabRelease": "Πιάσε την απελευθέρωση", - "GrabReleaseMessageText": "Ο Radarr δεν μπόρεσε να προσδιορίσει ποια ταινία ήταν αυτή η κυκλοφορία. Το Radarr ενδέχεται να μην μπορεί να εισαγάγει αυτόματα αυτήν την κυκλοφορία. Θέλετε να τραβήξετε το \"{0}\";", + "GrabReleaseMessageText": "Ο Lidarr δεν μπόρεσε να προσδιορίσει ποια ταινία ήταν αυτή η κυκλοφορία. Το Lidarr ενδέχεται να μην μπορεί να εισαγάγει αυτόματα αυτήν την κυκλοφορία. Θέλετε να τραβήξετε το \"{0}\";", "GrabSelected": "Επιλογή αρπαγής", "Group": "Ομάδα", "HasPendingChangesNoChanges": "Χωρίς αλλαγές", @@ -305,11 +305,11 @@ "IndexerSettings": "Ρυθμίσεις ευρετηρίου", "Interval": "Διάστημα", "IsTagUsedCannotBeDeletedWhileInUse": "Δεν είναι δυνατή η διαγραφή κατά τη χρήση", - "LidarrTags": "Ετικέτες Radarr", + "LidarrTags": "Ετικέτες Lidarr", "LoadingTrackFilesFailed": "Η φόρτωση αρχείων ταινίας απέτυχε", "Local": "Τοπικός", "LocalPath": "Τοπικό μονοπάτι", - "LocalPathHelpText": "Διαδρομή που πρέπει να χρησιμοποιήσει ο Radarr για πρόσβαση τοπικά στην απομακρυσμένη διαδρομή", + "LocalPathHelpText": "Διαδρομή που πρέπει να χρησιμοποιήσει ο Lidarr για πρόσβαση τοπικά στην απομακρυσμένη διαδρομή", "LogFiles": "Αρχεία καταγραφής", "Logging": "Ξύλευση", "LogLevel": "Επίπεδο καταγραφής", @@ -370,7 +370,6 @@ "QualityProfiles": "Προφίλ ποιότητας", "QualitySettings": "Ρυθμίσεις ποιότητας", "Queue": "Ουρά", - "Radarr": "Ραντάρ", "ReadTheWikiForMoreInformation": "Διαβάστε το Wiki για περισσότερες πληροφορίες", "Real": "Πραγματικός", "Reason": "Λόγος", @@ -400,7 +399,7 @@ "RemoveFromDownloadClient": "Κατάργηση από τον πελάτη λήψης", "RemoveSelected": "Αφαιρέστε τα επιλεγμένα", "RemoveTagRemovingTag": "Κατάργηση ετικέτας", - "RenameTracksHelpText": "Το Radarr θα χρησιμοποιήσει το υπάρχον όνομα αρχείου εάν η μετονομασία είναι απενεργοποιημένη", + "RenameTracksHelpText": "Το Lidarr θα χρησιμοποιήσει το υπάρχον όνομα αρχείου εάν η μετονομασία είναι απενεργοποιημένη", "ResetAPIKeyMessageText": "Είστε βέβαιοι ότι θέλετε να επαναφέρετε το κλειδί API σας;", "Restart": "Επανεκκίνηση", "Restore": "Επαναφέρω", @@ -421,7 +420,7 @@ "SuccessMyWorkIsDoneNoFilesToRetag": "Επιτυχία! Η δουλειά μου ολοκληρώθηκε, δεν υπάρχουν αρχεία για μετονομασία.", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "Το RSS δεν υποστηρίζεται με αυτό το ευρετήριο", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Η αναζήτηση δεν υποστηρίζεται με αυτό το ευρετήριο", - "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Θα χρησιμοποιηθεί όταν πραγματοποιούνται αυτόματες αναζητήσεις μέσω του περιβάλλοντος χρήστη ή του Radarr", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Θα χρησιμοποιηθεί όταν πραγματοποιούνται αυτόματες αναζητήσεις μέσω του περιβάλλοντος χρήστη ή του Lidarr", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Θα χρησιμοποιηθεί όταν χρησιμοποιείται διαδραστική αναζήτηση", "TagIsNotUsedAndCanBeDeleted": "Η ετικέτα δεν χρησιμοποιείται και μπορεί να διαγραφεί", "Tags": "Ετικέτες", @@ -442,7 +441,7 @@ "UnableToLoadLists": "Δεν είναι δυνατή η φόρτωση λιστών", "UnableToLoadTags": "Δεν είναι δυνατή η φόρτωση ετικετών", "UseProxy": "Χρησιμοποιήστε διακομιστή μεσολάβησης", - "UILanguageHelpText": "Γλώσσα που θα χρησιμοποιήσει ο Radarr για τη διεπαφή χρήστη", + "UILanguageHelpText": "Γλώσσα που θα χρησιμοποιήσει ο Lidarr για τη διεπαφή χρήστη", "UILanguageHelpTextWarning": "Απαιτείται επαναφόρτωση προγράμματος περιήγησης", "UISettings": "Ρυθμίσεις διεπαφής χρήστη", "UnableToAddANewDownloadClientPleaseTryAgain": "Δεν είναι δυνατή η προσθήκη νέου προγράμματος-πελάτη λήψης. Δοκιμάστε ξανά.", @@ -466,5 +465,12 @@ "MarkAsFailed": "Επισήμανση ως αποτυχημένου", "DelayingDownloadUntilInterp": "Καθυστέρηση λήψης έως τις {0} στις {1}", "Score": "Σκορ", - "Settings": "Ρυθμίσεις" + "Settings": "Ρυθμίσεις", + "OnRename": "Μετονομασία", + "OnUpgrade": "Κατά την αναβάθμιση", + "ThisCannotBeCancelled": "Αυτό δεν μπορεί να ακυρωθεί μόλις ξεκινήσει χωρίς επανεκκίνηση του Whisparr.", + "OnHealthIssue": "Σχετικά με το θέμα της υγείας", + "OnGrab": "Στο Grab", + "Tracks": "Ιχνος", + "MonoVersion": "Μονο έκδοση" } diff --git a/src/NzbDrone.Core/Localization/Core/es.json b/src/NzbDrone.Core/Localization/Core/es.json index dfcf1a33c..5c4e439aa 100644 --- a/src/NzbDrone.Core/Localization/Core/es.json +++ b/src/NzbDrone.Core/Localization/Core/es.json @@ -5,30 +5,30 @@ "Connections": "Conexiones", "NoMinimumForAnyRuntime": "Sin mínimo para el tiempo de ejecución", "Quality": "Calidad", - "AuthenticationMethodHelpText": "Requerir nombre de usuario y contraseña para acceder Radarr", + "AuthenticationMethodHelpText": "Requerir nombre de usuario y contraseña para acceder Lidarr", "AutoRedownloadFailedHelpText": "Buscar e intentar descargar automáticamente una versión diferente", - "BackupFolderHelpText": "Las rutas relativas estarán en el directorio AppData de Radarr", + "BackupFolderHelpText": "Las rutas relativas estarán en el directorio AppData de Lidarr", "BackupNow": "Backup Ahora", "BackupRetentionHelpText": "Backups automáticos anteriores al período de retención serán borrados automáticamente", "Backups": "Copias de seguridad", "BindAddress": "Dirección de Ligado", "BindAddressHelpText": "Dirección IP4 válida o '*' para todas las interfaces", "Blocklist": "Bloqueadas", - "BlocklistHelpText": "Evita que Radarr vuelva a capturar esta película automáticamente", + "BlocklistHelpText": "Evita que Lidarr vuelva a capturar esta película automáticamente", "BlocklistRelease": "Bloquear este Estreno", "Branch": "Rama", "BypassProxyForLocalAddresses": "Omitir Proxy para Direcciones Locales", "CalendarWeekColumnHeaderHelpText": "Mostrado sobre cada columna cuando la vista activa es semana", "CancelMessageText": "Seguro que quieres cancelar esta tarea pendiente?", "CertificateValidation": "Validación del certificado", - "ChmodFolderHelpTextWarning": "Esto solo funciona si el usuario que ejecuta Radarr es el propietario del archivo. Es mejor asegurarse de que el cliente de descarga establezca los permisos correctamente.", - "ChownGroupHelpTextWarning": "Esto solo funciona si el usuario que ejecuta Radarr es el propietario del archivo. Es mejor asegurarse de que el cliente de descarga use el mismo grupo que Radarr.", + "ChmodFolderHelpTextWarning": "Esto solo funciona si el usuario que ejecuta Lidarr es el propietario del archivo. Es mejor asegurarse de que el cliente de descarga establezca los permisos correctamente.", + "ChownGroupHelpTextWarning": "Esto solo funciona si el usuario que ejecuta Lidarr es el propietario del archivo. Es mejor asegurarse de que el cliente de descarga use el mismo grupo que Lidarr.", "CloneIndexer": "Clonar Indexer", "CloneProfile": "Clonar Perfil", "CompletedDownloadHandling": "Manipulación de descargas completas", "ConnectSettings": "Conectar Ajustes", - "CopyUsingHardlinksHelpTextWarning": "Ocasionalmente, los archivos blqoueados impiden renombrar los archivos que siguen seedeando. Puedes desactivar el seedeo temporalmete y usar la función de renombrado de Radarr como alternativa.", - "CutoffHelpText": "Una vez que se alcanze esta calidad, Radarr no descargará películas", + "CopyUsingHardlinksHelpTextWarning": "Ocasionalmente, los archivos blqoueados impiden renombrar los archivos que siguen seedeando. Puedes desactivar el seedeo temporalmete y usar la función de renombrado de Lidarr como alternativa.", + "CutoffHelpText": "Una vez que se alcanze esta calidad, Lidarr no descargará películas", "CutoffUnmet": "Corte No Alcanzado", "DeleteBackupMessageText": "Seguro que quieres eliminar la copia de seguridad '{0}'?", "DeleteDelayProfile": "Borrar Perfil de Retraso", @@ -57,12 +57,12 @@ "GrabID": "Capturar ID", "IgnoredHelpText": "Este lanzamiento será rechazado si contiene uno ó más de estos términos (mayúsculas ó minúsculas)", "IllRestartLater": "Lo reiniciaré más tarde", - "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr soporta cualquier indexer que utilice el estandar Newznab, como también cualquiera de los indexers listados debajo.", - "LidarrTags": "Etiquetas de Radarr", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr soporta cualquier indexer que utilice el estandar Newznab, como también cualquiera de los indexers listados debajo.", + "LidarrTags": "Etiquetas de Lidarr", "LoadingTrackFilesFailed": "La carga de los archivos ha fallado", "Local": "Local", "LocalPath": "Ruta local", - "LocalPathHelpText": "La ruta que Radarr tiene que usar para acceder a la ruta remota localmente", + "LocalPathHelpText": "La ruta que Lidarr tiene que usar para acceder a la ruta remota localmente", "LogFiles": "Archivos de Registro", "Logging": "Registro de eventos", "LogLevel": "Nivel de Registro", @@ -120,7 +120,6 @@ "QualityProfiles": "Perfiles de Calidad", "QualitySettings": "Ajustes de Calidad", "Queue": "Cola", - "Radarr": "Radarr", "ReadTheWikiForMoreInformation": "Lee la Wiki para más información", "Real": "Real", "RecycleBinHelpText": "Los archivos iran aquí una vez se hayan borrado en vez de ser borrados permanentemente", @@ -149,20 +148,20 @@ "RemoveSelectedMessageText": "¿Está seguro de que desea eliminar los elementos seleccionados de la lista negra?", "RemoveTagExistingTag": "Etiqueta existente", "RemoveTagRemovingTag": "Eliminando etiqueta", - "RenameTracksHelpText": "Radarr usará el nombre del archivo si el renombrado está deshabilitado", + "RenameTracksHelpText": "Lidarr usará el nombre del archivo si el renombrado está deshabilitado", "Reorder": "Reordenar", "ReplaceIllegalCharacters": "Reemplazar Caracteres Ilegales", - "ReplaceIllegalCharactersHelpText": "Reemplazar caracteres ilegales. Si está desactivado, Radarr los eliminará si no", + "ReplaceIllegalCharactersHelpText": "Reemplazar caracteres ilegales. Si está desactivado, Lidarr los eliminará si no", "RequiredHelpText": "El comunicado debe contener al menos uno de estos términos (no distingue entre mayúsculas y minúsculas)", "RequiredPlaceHolder": "Añadir nueva restricción", "RequiresRestartToTakeEffect": "Requiere reiniciar para que surta efecto", "RescanAfterRefreshHelpText": "Reescanear la carpeta de películas después de actualizar la película", - "RescanAfterRefreshHelpTextWarning": "Radarr no detectará los cambios automáticamente en los ficheros si no se ajusta a 'Siempre'", + "RescanAfterRefreshHelpTextWarning": "Lidarr no detectará los cambios automáticamente en los ficheros si no se ajusta a 'Siempre'", "RescanArtistFolderAfterRefresh": "Reescanear la Carpeta de Películas después de Actualizar", "ResetAPIKey": "Reajustar API", "ResetAPIKeyMessageText": "¿Está seguro de que desea restablecer su clave API?", "Restart": "Reiniciar", - "RestartLidarr": "Reiniciar Radarr", + "RestartLidarr": "Reiniciar Lidarr", "RestartNow": "Reiniciar Ahora", "Restore": "Restaurar", "RestoreBackup": "Recuperar Backup", @@ -200,7 +199,7 @@ "SuccessMyWorkIsDoneNoFilesToRetag": "Éxito! Mi trabajo está hecho, no hay archivos pendientes de renombrar.", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS no son soportadas por este indexer", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Buscar no está soportado por este indexer", - "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Se usará cuando las búsquedas automáticas se realicen desde el UI o por Radarr", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Se usará cuando las búsquedas automáticas se realicen desde el UI o por Lidarr", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Se usará cuando se utilice la búsqueda interactiva", "TagIsNotUsedAndCanBeDeleted": "La etiqueta no se usa y puede ser borrada", "Tags": "Etiquetas", @@ -215,7 +214,7 @@ "TorrentDelay": "Retraso del Torrent", "TorrentDelayHelpText": "Retraso en minutos a esperar antes de descargar un torrent", "Torrents": "Torrents", - "UILanguageHelpText": "Lenguaje que Radarr usara para el UI", + "UILanguageHelpText": "Lenguaje que Lidarr usara para el UI", "UILanguageHelpTextWarning": "Recargar el Navegador", "UISettings": "Ajustes del UI", "UnableToAddANewDownloadClientPleaseTryAgain": "No se ha podido añadir un nuevo gestor de descargas, prueba otra vez.", @@ -255,7 +254,7 @@ "UnmonitoredHelpText": "Incluir las peliculas no monitoreadas en el feed de iCal", "UpdateAll": "Actualizar Todo", "UpdateAutomaticallyHelpText": "Descargar e instalar actualizaciones automáticamente. Se podrán instalar desde Sistema: Actualizaciones también", - "UpdateMechanismHelpText": "Usar el actualizador de Radarr o un script", + "UpdateMechanismHelpText": "Usar el actualizador de Lidarr o un script", "Updates": "Actualizaciones", "UpdateScriptPathHelpText": "Ruta del script propio que toma el paquete de actualización y se encarga del proceso de actualización restante", "UpgradeAllowedHelpText": "Si está desactivado las calidades no serán actualizadas", @@ -280,7 +279,7 @@ "AlternateTitleslength1Title": "Título", "AlternateTitleslength1Titles": "Títulos", "Analytics": "Analíticas", - "AnalyticsEnabledHelpText": "Envíe información anónima de uso y error a los servidores de Radarr. Esto incluye información sobre su navegador, qué páginas de Radarr WebUI utiliza, informes de errores, así como el sistema operativo y la versión en tiempo de ejecución. Usaremos esta información para priorizar funciones y correcciones de errores.", + "AnalyticsEnabledHelpText": "Envíe información anónima de uso y error a los servidores de Lidarr. Esto incluye información sobre su navegador, qué páginas de Lidarr WebUI utiliza, informes de errores, así como el sistema operativo y la versión en tiempo de ejecución. Usaremos esta información para priorizar funciones y correcciones de errores.", "AnalyticsEnabledHelpTextWarning": "Requiere reiniciar para que surta efecto", "Dates": "Fechas", "Scheduled": "Programado", @@ -303,7 +302,7 @@ "ShowMonitoredHelpText": "Mostrar el estado de monitoreo debajo del poster", "Size": " Tamaño", "SkipFreeSpaceCheck": "Saltarse Comprobación de Espacio Disponible", - "SkipFreeSpaceCheckWhenImportingHelpText": "Usar cuando Radarr no pueda detectar el espacio disponible en la carpeta de películas", + "SkipFreeSpaceCheckWhenImportingHelpText": "Usar cuando Lidarr no pueda detectar el espacio disponible en la carpeta de películas", "SorryThatAlbumCannotBeFound": "Lo siento, no he encontrado esa película.", "SorryThatArtistCannotBeFound": "Lo siento, no he encontrado esa película.", "Source": "Origen", @@ -389,7 +388,7 @@ "GoToInterp": "Ir a {0}", "Grab": "Capturar", "GrabRelease": "Capturar Estreno", - "GrabReleaseMessageText": "Radarr no pudo determinar para qué película es este lanzamiento. Radarr no podrá importar este lanzamiento automáticamente. Quieres descargar {0}?", + "GrabReleaseMessageText": "Lidarr no pudo determinar para qué película es este lanzamiento. Lidarr no podrá importar este lanzamiento automáticamente. Quieres descargar {0}?", "GrabSelected": "Capturar Seleccionados", "Group": "Grupo", "HasPendingChangesNoChanges": "Sin Cambios", @@ -411,7 +410,7 @@ "ImportFailedInterp": "Importación fallida: {0}", "Importing": "Importando", "IncludeHealthWarningsHelpText": "Incluir Alertas de Salud", - "IncludeUnknownArtistItemsHelpText": "Mostrar items sin ninguna película en la cola, esto incluye películas renombradas o cualquier otra cosa en la categoría de Radarr", + "IncludeUnknownArtistItemsHelpText": "Mostrar items sin ninguna película en la cola, esto incluye películas renombradas o cualquier otra cosa en la categoría de Lidarr", "IncludeUnmonitored": "Icluir No Monitoreados", "Indexer": "Indexador", "IndexerPriority": "Prioridad del Indexer", @@ -422,7 +421,7 @@ "IsCutoffCutoff": "Corte", "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Actualizar hasta que se alcance o supere esta calidad", "IsTagUsedCannotBeDeletedWhileInUse": "No se puede eliminar estando en uso", - "LaunchBrowserHelpText": " Abrir un navegador web e ir a la página de inicio de Radarr al arrancar la app.", + "LaunchBrowserHelpText": " Abrir un navegador web e ir a la página de inicio de Lidarr al arrancar la app.", "Level": "Nivel", "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Raddar soporta cualquier gestor de descargas que utilice el estandar Newznab, como también los clientes indicados debajo.", "Logs": "Registros", @@ -460,7 +459,7 @@ "Type": "Tipo", "UsenetDelay": "Retraso de Usenet", "Username": "Nombre de usuario", - "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Qué rama usar para actualizar Radarr", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Qué rama usar para actualizar Lidarr", "NETCore": ".NET", "UnableToAddANewRemotePathMappingPleaseTryAgain": "No se ha podido añadir una nueva ruta remota, prueba otra vez.", "UnableToAddANewRootFolderPleaseTryAgain": "No se ha podido añadir un nuevo formato propio, prueba otra vez.", @@ -481,5 +480,20 @@ "OnApplicationUpdate": "Al actualizar la aplicación", "OnApplicationUpdateHelpText": "Al actualizar la aplicación", "RemoveCompleted": "Eliminación completada", - "AllowFingerprinting": "Permitir impresión digital" + "AllowFingerprinting": "Permitir impresión digital", + "OnRename": "En Renombrado", + "Season": "Temporada", + "ThisCannotBeCancelled": "Esto no puede ser cancelado una vez iniciado sin deshabilitar todos sus indexadores.", + "OnGrab": "Al Capturar", + "OnHealthIssue": "En Problema de Salud", + "Term": "Término", + "MetadataProfile": "perfil de metadatos", + "MetadataProfiles": "perfil de metadatos", + "UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent proporcionado por la aplicación llamó a la API", + "OnUpgrade": "Al Actualizar", + "DeleteTrackFileMessageText": "¿Seguro que quieres eliminar {0}?", + "Label": "Etiqueta", + "AllowArtistChangeClickToChangeArtist": "Click para cambiar el autor", + "AddedArtistSettings": "Añadidas las opciones de Autor", + "MonoVersion": "Version de Mono" } diff --git a/src/NzbDrone.Core/Localization/Core/fi.json b/src/NzbDrone.Core/Localization/Core/fi.json index 09d3921f0..60d314892 100644 --- a/src/NzbDrone.Core/Localization/Core/fi.json +++ b/src/NzbDrone.Core/Localization/Core/fi.json @@ -371,7 +371,7 @@ "HostHelpText": "Sama isäntä, jonka määritit etälatausasiakkaalle", "ICalFeed": "iCal-syöte", "ImportFailedInterp": "Tuonti epäonnistui: {0}", - "RestartLidarr": "Käynnistä Radarr uudelleen", + "RestartLidarr": "Käynnistä Lidarr uudelleen", "RestartNow": "Käynnistä uudelleen nyt", "RestoreBackup": "Palauta varmuuskopio", "RetentionHelpText": "Vain Usenet: Aseta nollaan asettamaan rajoittamaton säilytys", @@ -448,7 +448,6 @@ "PublishedDate": "Julkaisupäivä", "QualityProfile": "Laatuprofiili", "QualityProfiles": "Laatuprofiilit", - "Radarr": "Radarr", "ReadTheWikiForMoreInformation": "Lue lisätietoja Wikistä", "Real": "Todellinen", "Reason": "Syy", @@ -470,7 +469,7 @@ "RemoveFromQueue": "Poista jonosta", "RemoveHelpTextWarning": "Poistaminen poistaa latauksen ja tiedostot latausohjelmasta.", "RemoveSelected": "Poista valitut", - "RenameTracksHelpText": "Radarr käyttää olemassa olevaa tiedostonimeä, jos uudelleennimeäminen ei ole käytössä.", + "RenameTracksHelpText": "Lidarr käyttää olemassa olevaa tiedostonimeä, jos uudelleennimeäminen ei ole käytössä.", "Reorder": "Järjestä uudelleen", "RescanArtistFolderAfterRefresh": "Tarkista kirjailijakansio päivityksen jälkeen uudelleen", "ResetAPIKeyMessageText": "Haluatko varmasti uudistaa API-avaimesi?", @@ -502,7 +501,7 @@ "Ungroup": "Pura ryhmä", "WriteMetadataToAudioFiles": "Kirjoita metatiedot äänitiedostoihin", "AlbumIsDownloadingInterp": "Kirjaa ladataan - {0} % {1}", - "AnyReleaseOkHelpText": "Readarr vaihtaa automaattisesti versioon, joka vastaa ladattuja tiedostoja parhaiten", + "AnyReleaseOkHelpText": "Lidarr vaihtaa automaattisesti versioon, joka vastaa ladattuja tiedostoja parhaiten", "Label": "Tunniste", "Score": "Pisteet", "SourcePath": "Lähteen polku", @@ -535,5 +534,22 @@ "MetadataSourceHelpText": "Vaihtoehtoinen metatietolähde. Käytä oletusta jättämällä tyhjäksi.", "MetadataProfileIdHelpText": "Metatietoprofiili, joka listalta lisätyille kohteille tulee asettaa.", "MetadataProfiles": "Metatietoprofiilit", - "PathHelpText": "Musiikkikokoelmasi pääkansio." + "PathHelpText": "Musiikkikokoelmasi pääkansio.", + "RemoveCompleted": "Poista valmistuneet", + "OnUpgrade": "Kun elokuva päivitetään", + "RemoveDownloadsAlert": "Poistoasetukset on siirretty yllä olevassa taulukossa yksittäisten lataustyökalujen alle.", + "SearchForAllMissingAlbums": "Etsi kaikkia puuttuvia kirjoja", + "ThisCannotBeCancelled": "Tämän peruminen on aloituksen jälkeen mahdollista vain poistamalla kaikki tietolähteet käytöstä.", + "OnGrab": "Kun elokuva siepataan", + "OnHealthIssue": "Kun havaitaan kuntoon liittyvä ongelma", + "OnRename": "Kun elokuva nimetään uudelleen", + "RemoveFailed": "Poisto epäonnistui", + "Other": "Muu", + "Tracks": "Jäljitys", + "WatchLibraryForChangesHelpText": "Suorita automaattinen uudelleentutkinta, kun juurikansiossa havaitaan tiedostomuutoksia.", + "AddedArtistSettings": "Uuden kirjailijan oletusasetukset", + "MonitorAlbumExistingOnlyWarning": "Tämä on kirjakohtaisen valvonnan kertaluontoinen määritys. Käytä Kirjailija/Muokkaa-valintaa hallinnoidaksesi mitä uusille kirjalisäyksille tehdään.", + "MonitoringOptionsHelpText": "Kansiosta löydetyille kirjailijoille oletusarvoisesti asetettava kirjojen valvontataso (kertaluontoinen määritys).", + "MonitorNewItemsHelpText": "Uusien kirjojen valvontatapa.", + "MonoVersion": "Mono-versio" } diff --git a/src/NzbDrone.Core/Localization/Core/fr.json b/src/NzbDrone.Core/Localization/Core/fr.json index 9707bf7b6..5f9a8c663 100644 --- a/src/NzbDrone.Core/Localization/Core/fr.json +++ b/src/NzbDrone.Core/Localization/Core/fr.json @@ -36,7 +36,6 @@ "MinimumAge": "Âge minimum", "Mode": "Mode", "NotificationTriggers": "Déclencheurs de notification", - "Radarr": "Radarr", "StartupDirectory": "Répertoire de démarrage", "Tracks": "Trace", "Type": "Type", @@ -47,7 +46,7 @@ "ApiKeyHelpTextWarning": "Nécessite un redémarrage pour prendre effet", "ApplyTagsHelpTexts1": "Comment appliquer des tags à l'auteur sélectionné", "Blocklist": "Liste noire", - "BlocklistHelpText": "Empêche Radarr de récupérer automatiquement cette version", + "BlocklistHelpText": "Empêche Lidarr de récupérer automatiquement cette version", "BlocklistRelease": "Mettre cette release sur la liste noire", "Calendar": "Calendrier", "CalendarWeekColumnHeaderHelpText": "Affiché au dessus de chaque colonne quand \"Semaine\" est l'affichage actif", @@ -57,9 +56,9 @@ "ChmodFolder": "chmod Dossier", "ChmodFolderHelpText": "Nombre, appliqué durant l'import/renommage vers les dossiers et fichiers multimédias (sans exécuter les bits)", "Unmonitored": "Non surveillé", - "ChmodFolderHelpTextWarning": "Fonctionne uniquement si l'utilisateur exécutant Radarr est le propriétaire du fichier. Il est recommandé de vérifier les permissions du client de téléchargement.", + "ChmodFolderHelpTextWarning": "Fonctionne uniquement si l'utilisateur exécutant Lidarr est le propriétaire du fichier. Il est recommandé de vérifier les permissions du client de téléchargement.", "ChownGroupHelpText": "Nom du Groupe ou GID. Utiliser le GID pour les systèmes de fichier distants.", - "ChownGroupHelpTextWarning": "Fonctionne uniquement si l'utilisateur exécutant Radarr est le propriétaire du fichier. Il est recommandé de vérifier que le client de téléchargement utilise le meme Groupe que Radarr.", + "ChownGroupHelpTextWarning": "Fonctionne uniquement si l'utilisateur exécutant Lidarr est le propriétaire du fichier. Il est recommandé de vérifier que le client de téléchargement utilise le meme Groupe que Lidarr.", "Clear": "Effacer", "ClickToChangeQuality": "Cliquer pour changer la qualité", "ClientPriority": "Priorité du client", @@ -71,11 +70,11 @@ "Connections": "Connexions", "ConnectSettings": "Paramètres de connexion", "CopyUsingHardlinksHelpText": "Utiliser des liens fixes quand on essaye de copier des fichiers appartenant à des torrents en cours de partage", - "CopyUsingHardlinksHelpTextWarning": "De temps en temps, des verrouillages de fichiers peuvent empêcher de renommer des fichiers qui sont en cours de partage. Vous pouvez temporairement arrêter le partage et utiliser la fonction de renommage de Radarr comme solution de contournement.", + "CopyUsingHardlinksHelpTextWarning": "De temps en temps, des verrouillages de fichiers peuvent empêcher de renommer des fichiers qui sont en cours de partage. Vous pouvez temporairement arrêter le partage et utiliser la fonction de renommage de Lidarr comme solution de contournement.", "CreateEmptyArtistFolders": "Créer des dossiers films vides", "CreateEmptyArtistFoldersHelpText": "Créer les dossiers films manquants pendant le scan du disque", "CreateGroup": "Créer un groupe", - "CutoffHelpText": "Quand cette qualité est atteinte, Radarr ne téléchargera plus de films", + "CutoffHelpText": "Quand cette qualité est atteinte, Lidarr ne téléchargera plus de films", "CutoffUnmet": "Limite non satisfaite", "Dates": "Dates", "DBMigration": "Migration de la base de données", @@ -152,7 +151,7 @@ "Grab": "Attraper", "GrabID": "ID du grab", "GrabRelease": "Télécharger la version", - "GrabReleaseMessageText": "Radarr n'a pas été en mesure de déterminer à quel film cette version était destinée. Radarr peut être incapable d'importer automatiquement cette version. Voulez-vous récupérer '{0}' ?", + "GrabReleaseMessageText": "Lidarr n'a pas été en mesure de déterminer à quel film cette version était destinée. Lidarr peut être incapable d'importer automatiquement cette version. Voulez-vous récupérer '{0}' ?", "GrabSelected": "Saisir la sélection", "Group": "Groupe", "HasPendingChangesNoChanges": "Aucun changement", @@ -173,7 +172,7 @@ "ImportFailedInterp": "Importation a échoué : {0}", "Importing": "Importation", "IncludeHealthWarningsHelpText": "Inclure avertissements santé", - "IncludeUnknownArtistItemsHelpText": "Afficher les éléments sans film dans la file d'attente. Cela peut inclure des films supprimés ou tout autre élément de la catégorie de Radarr", + "IncludeUnknownArtistItemsHelpText": "Afficher les éléments sans film dans la file d'attente. Cela peut inclure des films supprimés ou tout autre élément de la catégorie de Lidarr", "IncludeUnmonitored": "Inclure non surveillé", "Indexer": "Indexeur", "IndexerPriority": "Priorité de l'indexeur", @@ -185,15 +184,15 @@ "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Mettre à niveau jusqu'à ce que cette qualité soit atteinte ou dépassée", "IsTagUsedCannotBeDeletedWhileInUse": "Ne peut pas être supprimé pendant l'utilisation", "Label": "Label", - "LaunchBrowserHelpText": " Ouvrer un navigateur Web et accéder à la page d'accueil de Radarr au démarrage de l'application.", + "LaunchBrowserHelpText": " Ouvrer un navigateur Web et accéder à la page d'accueil de Lidarr au démarrage de l'application.", "Level": "Niveau", - "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Radarr prend en charge tout client de téléchargement qui utilise le standard Newznab, ainsi que d'autres clients de téléchargement répertoriés ci-dessous.", - "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr prend en charge tout indexeur qui utilise le standard Newznab, ainsi que d'autres indexeurs répertoriés ci-dessous.", - "LidarrTags": "Radarr Tags", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr prend en charge tout client de téléchargement qui utilise le standard Newznab, ainsi que d'autres clients de téléchargement répertoriés ci-dessous.", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr prend en charge tout indexeur qui utilise le standard Newznab, ainsi que d'autres indexeurs répertoriés ci-dessous.", + "LidarrTags": "Lidarr Tags", "LoadingTrackFilesFailed": "Le chargement des fichiers vidéo a échoué", "Local": "Local", "LocalPath": "Chemin local", - "LocalPathHelpText": "Chemin local que Radarr doit utiliser pour accéder au chemin distant", + "LocalPathHelpText": "Chemin local que Lidarr doit utiliser pour accéder au chemin distant", "Logging": "Enregistrement", "LogLevel": "Niveau du journal", "LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "La journalisation des traces ne doit être activée que temporairement", @@ -299,15 +298,15 @@ "RemoveSelectedMessageText": "Êtes-vous sûr de vouloir supprimer les films sélectionnés de la liste noire ?", "RemoveTagExistingTag": "Tag existant", "RemoveTagRemovingTag": "Suppression du tag", - "RenameTracksHelpText": "Radarr utilisera le nom de fichier existant si le changement de nom est désactivé", + "RenameTracksHelpText": "Lidarr utilisera le nom de fichier existant si le changement de nom est désactivé", "Reorder": "Réorganiser", "ReplaceIllegalCharacters": "Remplacer les caractères illégaux", - "ReplaceIllegalCharactersHelpText": "Remplacer les caractères illégaux. Si elle n'est pas cochée, Radarr les supprimera à la place", + "ReplaceIllegalCharactersHelpText": "Remplacer les caractères illégaux. Si elle n'est pas cochée, Lidarr les supprimera à la place", "RequiredHelpText": "La version doit contenir au moins un de ces termes (insensible à la casse)", "RequiredPlaceHolder": "Ajouter une nouvelle restriction", "RequiresRestartToTakeEffect": "Nécessite un redémarrage pour prendre effet", "RescanAfterRefreshHelpText": "Réanalyser le dossier du film après avoir actualisé le film", - "RescanAfterRefreshHelpTextWarning": "Radarr ne détectera pas automatiquement les modifications apportées aux fichiers lorsqu'il n'est pas défini sur «Toujours»", + "RescanAfterRefreshHelpTextWarning": "Lidarr ne détectera pas automatiquement les modifications apportées aux fichiers lorsqu'il n'est pas défini sur «Toujours»", "RescanArtistFolderAfterRefresh": "Réanalyser le dossier de films après l'actualisation", "Reset": "Réinitialiser", "ResetAPIKey": "Réinitialiser la clé API", @@ -346,7 +345,7 @@ "SuccessMyWorkIsDoneNoFilesToRetag": "Victoire ! Mon travail est terminé, aucun fichier à renommer.", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS n'est pas pris en charge avec cet indexeur", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "La recherche n'est pas prise en charge avec cet indexeur", - "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Sera utilisé lorsque les recherches automatiques sont effectuées via l'interface utilisateur ou par Radarr", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Sera utilisé lorsque les recherches automatiques sont effectuées via l'interface utilisateur ou par Lidarr", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Sera utilisé lorsque la recherche interactive est utilisée", "TagIsNotUsedAndCanBeDeleted": "La balise n'est pas utilisée et peut être supprimée", "Tags": "Tags", @@ -364,7 +363,7 @@ "TotalFileSize": "Taille totale du fichier", "Track": "Trace", "UISettings": "Paramètres UI", - "RestartLidarr": "Redémarrer Radarr", + "RestartLidarr": "Redémarrer Lidarr", "RestartNow": "Redémarrer maintenant", "Restore": "Restaurer", "RestoreBackup": "Restaurer la sauvegarde", @@ -386,7 +385,7 @@ "UnableToLoadLists": "Impossible de charger les listes", "UnableToLoadMetadata": "Impossible de charger les métadonnées", "UnableToLoadMetadataProfiles": "Impossible de charger les profils de délai", - "UILanguageHelpText": "Langue que Radarr utilisera pour l'interface utilisateur", + "UILanguageHelpText": "Langue que Lidarr utilisera pour l'interface utilisateur", "UILanguageHelpTextWarning": "Rechargement du navigateur requis", "UnableToAddANewDownloadClientPleaseTryAgain": "Impossible d'ajouter un nouveau client de téléchargement, veuillez réessayer.", "UnableToAddANewImportListExclusionPleaseTryAgain": "Impossible d'ajouter une nouvelle exclusion de liste, veuillez réessayer.", @@ -401,7 +400,7 @@ "UnableToLoadQualityProfiles": "Impossible de charger les profils de qualité", "Updates": "Mises à jour", "Username": "Nom d'utilisateur", - "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Branche à utiliser pour mettre à jour Radarr", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Branche à utiliser pour mettre à jour Lidarr", "Version": "Version", "UnableToLoadReleaseProfiles": "Impossible de charger les profils de délai", "UnableToLoadRemotePathMappings": "Impossible de charger les mappages de chemins distants", @@ -413,7 +412,7 @@ "UnmonitoredHelpText": "Inclure les films non surveillés dans le flux iCal", "UpdateAll": "Tout actualiser", "UpdateAutomaticallyHelpText": "Télécharger et installer automatiquement les mises à jour. Vous pourrez toujours installer à partir de System : Updates", - "UpdateMechanismHelpText": "Utiliser le programme de mise à jour intégré de Radarr ou un script", + "UpdateMechanismHelpText": "Utiliser le programme de mise à jour intégré de Lidarr ou un script", "UpdateScriptPathHelpText": "Chemin vers un script personnalisé qui prend un package de mise à jour extraite et gère le reste du processus de mise à jour", "UpgradeAllowedHelpText": "Ne sera pas mis à jour si la qualité est désactivée", "Uptime": "Durée de fonctionnent", @@ -441,12 +440,12 @@ "AlternateTitles": "Titre alternatif", "AlternateTitleslength1Title": "Titre", "AlternateTitleslength1Titles": "Titres", - "AnalyticsEnabledHelpText": "Envoyer des informations anonymes sur l'utilisation et les erreurs vers les serveurs de Radarr. Cela inclut des informations sur votre navigateur, quelle page Radarr WebUI vous utilisez, les rapports d'erreur ainsi que le système d'exploitation et sa version. Nous utiliserons ces informations pour prioriser les nouvelles fonctionnalités et les corrections de bugs.", + "AnalyticsEnabledHelpText": "Envoyer des informations anonymes sur l'utilisation et les erreurs vers les serveurs de Lidarr. Cela inclut des informations sur votre navigateur, quelle page Lidarr WebUI vous utilisez, les rapports d'erreur ainsi que le système d'exploitation et sa version. Nous utiliserons ces informations pour prioriser les nouvelles fonctionnalités et les corrections de bugs.", "AnalyticsEnabledHelpTextWarning": "Nécessite un redémarrage pour prendre effet", "ArtistAlbumClickToChangeTrack": "Cliquer pour changer le film", - "AuthenticationMethodHelpText": "Requière un identifiant et un mot de passe pour accéder à Radarr", + "AuthenticationMethodHelpText": "Requière un identifiant et un mot de passe pour accéder à Lidarr", "AutoRedownloadFailedHelpText": "Chercher et essayer de télécharger une version différente automatiquement", - "BackupFolderHelpText": "Les chemins relatifs pointeront sous le repertoire AppData de Radarr", + "BackupFolderHelpText": "Les chemins relatifs pointeront sous le repertoire AppData de Lidarr", "BindAddressHelpTextWarning": "Nécessite un redémarrage pour prendre effet", "DelayingDownloadUntilInterp": "Retarder le téléchargement jusqu'au {0} à {1}", "Scheduled": "Programmé", @@ -470,7 +469,7 @@ "ShowMonitoredHelpText": "Afficher le statut surveillé sous l'affiche", "Size": " Taille", "SkipFreeSpaceCheck": "Ignorer la vérification de l'espace libre", - "SkipFreeSpaceCheckWhenImportingHelpText": "À utiliser lorsque Radarr ne parvient pas à détecter l'espace libre dans le dossier racine de votre film", + "SkipFreeSpaceCheckWhenImportingHelpText": "À utiliser lorsque Lidarr ne parvient pas à détecter l'espace libre dans le dossier racine de votre film", "SorryThatAlbumCannotBeFound": "Désolé, ce film est introuvable.", "SorryThatArtistCannotBeFound": "Désolé, ce film est introuvable.", "Source": "Source", @@ -513,5 +512,33 @@ "Duration": "Durée", "RemoveFailed": "Echec de la suppression", "RemoveDownloadsAlert": "Les paramètres de suppression ont été déplacés dans les réglages de chaque client de téléchargement dans le tableau ci-dessus.", - "ThisCannotBeCancelled": "Cela ne peut pas être annulé une fois démarré sans désactiver tous vos indexeurs." + "ThisCannotBeCancelled": "Cela ne peut pas être annulé une fois démarré sans désactiver tous vos indexeurs.", + "OnGrab": "À la Récupération", + "OnHealthIssue": "Lors d'un problème de santé", + "OnRename": "Lors du changement de nom", + "OnUpgrade": "Lors de la mise à niveau", + "ExpandAlbumByDefaultHelpText": "Album", + "MonoVersion": "Mono Version", + "Continuing": "Continuant", + "ContinuingAllTracksDownloaded": "Continuation (Tous les livres téléchargés)", + "DefaultLidarrTags": "Tags Lidarr par défaut", + "DefaultMetadataProfileIdHelpText": "Profil de métadonnées par défaut pour les auteurs détectés dans ce dossier", + "DefaultQualityProfileIdHelpText": "Profil de qualité par défaut pour les auteurs détectés dans ce dossier", + "DefaultTagsHelpText": "Profil de métadonnées par défaut pour les auteurs détectés dans ce dossier", + "DefaultMonitorOptionHelpText": "Quels livres doivent être surveillés lors de l'ajout initial pour les auteurs détectés dans ce dossier", + "FutureAlbumsData": "Surveiller les livres qui ne sont pas encore sortis", + "MetadataProfiles": "profil de métadonnées", + "MissingAlbumsData": "Surveiller les livres qui n'ont pas de fichiers ou qui ne sont pas encore sortis", + "NoneData": "Aucun livre ne sera surveillé", + "ContinuingNoAdditionalAlbumsAreExpected": "Aucun livre supplémentaire n'est prévu", + "Country": "Pays", + "ExistingAlbumsData": "Surveiller les livres qui ne sont pas encore sortis ou partiellement", + "FirstAlbumData": "Surveiller le premier livre. Tous les autres livres seront ignorés", + "TrackFileCountTrackCountTotalTotalTrackCountInterp": "{0} / {1} (Total  : {2})", + "AddedArtistSettings": "Ajout des paramètres de l'auteur", + "MetadataProfile": "profil de métadonnées", + "StatusEndedContinuing": "Continuant", + "Term": "Terme", + "TrackFileCounttotalTrackCountTracksDownloadedInterp": "{0}/{1} livres téléchargés", + "WriteMetadataToAudioFiles": "Écrire des métadonnées dans les fichiers audio" } diff --git a/src/NzbDrone.Core/Localization/Core/he.json b/src/NzbDrone.Core/Localization/Core/he.json index 262224281..0a4a6ab2e 100644 --- a/src/NzbDrone.Core/Localization/Core/he.json +++ b/src/NzbDrone.Core/Localization/Core/he.json @@ -19,7 +19,7 @@ "CreateEmptyArtistFolders": "צור תיקיות סרט ריקות", "CreateEmptyArtistFoldersHelpText": "צור תיקיות סרט חסרות במהלך סריקת הדיסק", "CreateGroup": "צור קבוצה", - "CutoffHelpText": "לאחר שהאיכות הזו תושג Radarr כבר לא תוריד סרטים", + "CutoffHelpText": "לאחר שהאיכות הזו תושג Lidarr כבר לא תוריד סרטים", "Dates": "תאריכים", "DBMigration": "הגירת DB", "DelayProfile": "עיכוב פרופיל", @@ -66,7 +66,7 @@ "Grab": "לִתְפּוֹס", "GrabID": "תעודת זהות", "GrabRelease": "שחרור תפוס", - "GrabReleaseMessageText": "רדאר לא הצליח לקבוע לאיזה סרט הסרט הזה נועד. ייתכן ש- Radarr לא תוכל לייבא גרסה זו באופן אוטומטי. האם אתה רוצה לתפוס את '{0}'?", + "GrabReleaseMessageText": "רדאר לא הצליח לקבוע לאיזה סרט הסרט הזה נועד. ייתכן ש- Lidarr לא תוכל לייבא גרסה זו באופן אוטומטי. האם אתה רוצה לתפוס את '{0}'?", "Group": "קְבוּצָה", "HasPendingChangesNoChanges": "אין שינויים", "HasPendingChangesSaveChanges": "שמור שינויים", @@ -84,19 +84,19 @@ "ImportFailedInterp": "הייבוא נכשל: {0}", "Importing": "מייבא", "IncludeHealthWarningsHelpText": "כלול אזהרות בריאות", - "IncludeUnknownArtistItemsHelpText": "הצג פריטים ללא סרט בתור. זה יכול לכלול סרטים שהוסרו או כל דבר אחר בקטגוריה של Radarr", + "IncludeUnknownArtistItemsHelpText": "הצג פריטים ללא סרט בתור. זה יכול לכלול סרטים שהוסרו או כל דבר אחר בקטגוריה של Lidarr", "IncludeUnmonitored": "כלול ללא פיקוח", "IndexerPriority": "עדיפות אינדקס", "Indexers": "אינדקסים", "IndexerSettings": "הגדרות אינדקס", "InteractiveSearch": "חיפוש אינטראקטיבי", "Interval": "הַפסָקָה", - "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Radarr תומך בכל לקוח הורדות המשתמש בתקן Newznab, כמו גם בלקוחות הורדה אחרים המפורטים להלן.", - "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr תומך בכל אינדקס שמשתמש בתקן Newznab, כמו גם באינדקסים אחרים המפורטים להלן.", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr תומך בכל לקוח הורדות המשתמש בתקן Newznab, כמו גם בלקוחות הורדה אחרים המפורטים להלן.", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr תומך בכל אינדקס שמשתמש בתקן Newznab, כמו גם באינדקסים אחרים המפורטים להלן.", "LoadingTrackFilesFailed": "טעינת קבצי הסרט נכשלה", "Local": "מְקוֹמִי", "LocalPath": "נתיב מקומי", - "LocalPathHelpText": "נתיב שבו Radarr אמור להשתמש כדי לגשת לנתיב המרוחק באופן מקומי", + "LocalPathHelpText": "נתיב שבו Lidarr אמור להשתמש כדי לגשת לנתיב המרוחק באופן מקומי", "LogFiles": "קבצי יומן", "Logging": "רישום", "LogLevel": "רמת יומן", @@ -201,15 +201,15 @@ "RemoveSelectedMessageText": "האם אתה בטוח שברצונך להסיר את הפריטים שנבחרו מהרשימה השחורה?", "RemoveTagExistingTag": "תג קיים", "RemoveTagRemovingTag": "הסרת התג", - "RenameTracksHelpText": "Radarr ישתמש בשם הקובץ הקיים אם שינוי שם אינו זמין", + "RenameTracksHelpText": "Lidarr ישתמש בשם הקובץ הקיים אם שינוי שם אינו זמין", "Reorder": "להזמין מחדש", "ReplaceIllegalCharacters": "החלף תווים לא חוקיים", - "ReplaceIllegalCharactersHelpText": "החלף תווים לא חוקיים. אם לא מסומן, Radarr יסיר אותם במקום זאת", + "ReplaceIllegalCharactersHelpText": "החלף תווים לא חוקיים. אם לא מסומן, Lidarr יסיר אותם במקום זאת", "RequiredHelpText": "המהדורה חייבת להכיל לפחות אחד ממונחים אלה (חסר רישיות)", "RequiredPlaceHolder": "הוסף הגבלה חדשה", "RequiresRestartToTakeEffect": "נדרש הפעלה מחדש כדי להיכנס לתוקף", "RescanAfterRefreshHelpText": "סרוק מחדש את תיקיית הסרט לאחר רענון הסרט", - "RescanAfterRefreshHelpTextWarning": "Radarr לא יאתר אוטומטית שינויים בקבצים כאשר לא מוגדר כ'תמיד '", + "RescanAfterRefreshHelpTextWarning": "Lidarr לא יאתר אוטומטית שינויים בקבצים כאשר לא מוגדר כ'תמיד '", "RescanArtistFolderAfterRefresh": "סרוק מחדש את תיקיית הסרט לאחר רענון", "Reset": "אִתחוּל", "ResetAPIKey": "אפס את מפתח ה- API", @@ -248,7 +248,7 @@ "SuccessMyWorkIsDoneNoFilesToRetag": "הַצלָחָה! העבודה שלי נעשית, אין קבצים לשינוי שם.", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS אינו נתמך עם אינדקס זה", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "אינדקס זה אינו נתמך בחיפוש", - "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "ישמש כאשר חיפושים אוטומטיים מבוצעים דרך ממשק המשתמש או על ידי Radarr", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "ישמש כאשר חיפושים אוטומטיים מבוצעים דרך ממשק המשתמש או על ידי Lidarr", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "ישמש כאשר נעשה שימוש בחיפוש אינטראקטיבי", "TagIsNotUsedAndCanBeDeleted": "לא משתמשים בתג וניתן למחוק אותו", "Tags": "תגים", @@ -272,7 +272,7 @@ "ExtraFileExtensionsHelpTexts1": "רשימה מופרדת באמצעות פסיקים של קבצים נוספים לייבוא (.nfo יובא כ- nfo-orig)", "FileDateHelpText": "שנה את תאריך הקובץ בעת הייבוא / סריקה מחדש", "FileManagement": "ניהול קבצים", - "UILanguageHelpText": "שפה בה Radarr ישתמש עבור ממשק המשתמש", + "UILanguageHelpText": "שפה בה Lidarr ישתמש עבור ממשק המשתמש", "UILanguageHelpTextWarning": "חובה לטעון דפדפן", "UISettings": "הגדרות ממשק המשתמש", "UnableToAddANewDownloadClientPleaseTryAgain": "לא ניתן להוסיף לקוח הורדות חדש, נסה שוב.", @@ -313,7 +313,7 @@ "UnmonitoredHelpText": "כלול סרטים ללא פיקוח בפיד iCal", "UpdateAll": "עדכן הכל", "UpdateAutomaticallyHelpText": "הורד והתקין עדכונים באופן אוטומטי. עדיין תוכל להתקין ממערכת: עדכונים", - "UpdateMechanismHelpText": "השתמש במעדכן המובנה של Radarr או בסקריפט", + "UpdateMechanismHelpText": "השתמש במעדכן המובנה של Lidarr או בסקריפט", "UpdateScriptPathHelpText": "נתיב לסקריפט מותאם אישית שלוקח חבילת עדכון שחולצה ומטפל בשארית תהליך העדכון", "UpgradeAllowedHelpText": "אם תכונות הנכים לא ישודרגו", "Uptime": "זמן עבודה", @@ -325,7 +325,7 @@ "UsenetDelayHelpText": "עיכוב תוך דקות להמתין לפני שתופס שחרור מאוסנט", "UseProxy": "תשתמש בפרוקסי", "Username": "שם משתמש", - "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "ענף לשימוש עדכון Radarr", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "ענף לשימוש עדכון Lidarr", "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "ענף המשמש את מנגנון העדכון החיצוני", "Version": "גִרְסָה", "WeekColumnHeader": "כותרת עמודות שבוע", @@ -343,7 +343,7 @@ "AlternateTitleslength1Title": "כותרת", "AlternateTitleslength1Titles": "כותרות", "Analytics": "ניתוח", - "AnalyticsEnabledHelpText": "שלח פרטי שימוש ושגיאה אנונימיים לשרתי Radarr. זה כולל מידע בדפדפן שלך, אילו דפי Radarr WebUI אתה משתמש, דיווח על שגיאות וכן מערכת הפעלה וגרסת זמן ריצה. אנו נשתמש במידע זה כדי לתעדף תכונות ותיקוני באגים.", + "AnalyticsEnabledHelpText": "שלח פרטי שימוש ושגיאה אנונימיים לשרתי Lidarr. זה כולל מידע בדפדפן שלך, אילו דפי Lidarr WebUI אתה משתמש, דיווח על שגיאות וכן מערכת הפעלה וגרסת זמן ריצה. אנו נשתמש במידע זה כדי לתעדף תכונות ותיקוני באגים.", "AnalyticsEnabledHelpTextWarning": "נדרש הפעלה מחדש כדי להיכנס לתוקף", "Automatic": "אוֹטוֹמָטִי", "DelayingDownloadUntilInterp": "מעכב את ההורדה עד {0} בשעה {1}", @@ -364,7 +364,7 @@ "ShowDateAdded": "הצגת תאריך הוספה", "ShowMonitored": "הצג פיקוח", "ShowMonitoredHelpText": "הצג סטטוס פיקוח תחת פוסטר", - "SkipFreeSpaceCheckWhenImportingHelpText": "השתמש כאשר Radarr אינו מצליח לזהות מקום פנוי מתיקיית שורש הסרט שלך", + "SkipFreeSpaceCheckWhenImportingHelpText": "השתמש כאשר Lidarr אינו מצליח לזהות מקום פנוי מתיקיית שורש הסרט שלך", "SorryThatAlbumCannotBeFound": "מצטערים, הסרט הזה לא נמצא.", "SorryThatArtistCannotBeFound": "מצטערים, הסרט הזה לא נמצא.", "Source": "מָקוֹר", @@ -383,9 +383,9 @@ "ApplyTagsHelpTexts4": "החלף: החלף את התגים בתגיות שהוזנו (אין להזין תגים כדי למחוק את כל התגים)", "ArtistAlbumClickToChangeTrack": "לחץ כדי לשנות סרט", "Authentication": "אימות", - "AuthenticationMethodHelpText": "דרוש שם משתמש וסיסמה כדי לגשת ל Radarr", + "AuthenticationMethodHelpText": "דרוש שם משתמש וסיסמה כדי לגשת ל Lidarr", "AutoRedownloadFailedHelpText": "חפש אוטומטית ונסה להוריד מהדורה אחרת", - "BackupFolderHelpText": "נתיבים יחסית יהיו תחת ספריית AppData של Radarr", + "BackupFolderHelpText": "נתיבים יחסית יהיו תחת ספריית AppData של Lidarr", "BackupNow": "גיבוי עכשיו", "BackupRetentionHelpText": "גיבויים אוטומטיים ישנים יותר מתקופת השמירה ינוקו אוטומטית", "Backups": "גיבויים", @@ -405,13 +405,13 @@ "ChangeHasNotBeenSavedYet": "השינוי עדיין לא נשמר", "ChmodFolder": "תיקיית chmod", "ChmodFolderHelpText": "אוקטאל, מוחל במהלך ייבוא / שינוי שם לתיקיות מדיה וקבצים (ללא סיביות ביצוע)", - "ChmodFolderHelpTextWarning": "זה עובד רק אם המשתמש שמפעיל את Radarr הוא הבעלים של הקובץ. עדיף לוודא שלקוח ההורדה מגדיר את ההרשאות כהלכה.", + "ChmodFolderHelpTextWarning": "זה עובד רק אם המשתמש שמפעיל את Lidarr הוא הבעלים של הקובץ. עדיף לוודא שלקוח ההורדה מגדיר את ההרשאות כהלכה.", "ChownGroupHelpText": "שם הקבוצה או ה- gid. השתמש ב- gid עבור מערכות קבצים מרוחקות.", - "ChownGroupHelpTextWarning": "זה עובד רק אם המשתמש שמפעיל את Radarr הוא הבעלים של הקובץ. עדיף להבטיח שלקוח ההורדה ישתמש באותה קבוצה כמו Radarr.", + "ChownGroupHelpTextWarning": "זה עובד רק אם המשתמש שמפעיל את Lidarr הוא הבעלים של הקובץ. עדיף להבטיח שלקוח ההורדה ישתמש באותה קבוצה כמו Lidarr.", "Clear": "ברור", "ClickToChangeQuality": "לחץ כדי לשנות את האיכות", "CopyUsingHardlinksHelpText": "השתמש בקישורים קשיחים כשאתה מנסה להעתיק קבצים מטורמים שעדיין נזרעים", - "CopyUsingHardlinksHelpTextWarning": "לעיתים, נעילת קבצים עשויה למנוע שינוי שם של קבצים שנזרעים. אתה יכול להשבית זמנית את הזריעה ולהשתמש בפונקציית השם של Radarr כעבודה מסביב.", + "CopyUsingHardlinksHelpTextWarning": "לעיתים, נעילת קבצים עשויה למנוע שינוי שם של קבצים שנזרעים. אתה יכול להשבית זמנית את הזריעה ולהשתמש בפונקציית השם של Lidarr כעבודה מסביב.", "CutoffUnmet": "חתך שלא הושג", "DeleteTag": "מחק את התג", "DiskSpace": "שטח דיסק", @@ -440,7 +440,7 @@ "IsCutoffCutoff": "לחתוך", "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "שדרג עד שתתקיים או תחרוג מאיכות זו", "IsTagUsedCannotBeDeletedWhileInUse": "לא ניתן למחוק בזמן השימוש", - "LaunchBrowserHelpText": " פתח דפדפן אינטרנט ונווט אל דף הבית של Radarr בהתחלת האפליקציה.", + "LaunchBrowserHelpText": " פתח דפדפן אינטרנט ונווט אל דף הבית של Lidarr בהתחלת האפליקציה.", "Level": "רָמָה", "LidarrTags": "תגיות רדאר", "MIA": "MIA", @@ -461,11 +461,21 @@ "QualityDefinitions": "הגדרות איכות", "Queue": "תוֹר", "RSSSync": "סנכרון RSS", - "Radarr": "רדאר", "ShowSizeOnDisk": "הצג גודל בדיסק", "Size": " גודל", "SkipFreeSpaceCheck": "דלג על בדיקת מקום פנוי", "UnableToLoadMetadata": "לא ניתן לטעון מטא נתונים", "Updates": "עדכונים", - "Scheduled": "מתוזמן" + "Scheduled": "מתוזמן", + "OnHealthIssue": "בנושא הבריאות", + "OnRename": "על שינוי שם", + "OnUpgrade": "בשדרוג", + "ThisCannotBeCancelled": "לא ניתן לבטל פעולה זו לאחר שהתחילה מבלי להפעיל מחדש את Whisparr.", + "OnGrab": "על לתפוס", + "MonoVersion": "גרסת מונו", + "NETCore": ".NET Core", + "BlocklistHelpText": "מנע מראדרר להוסיף את ההוצאה הזאת שוב", + "Duration": "אורך", + "OnApplicationUpdate": "כשהאפליקציה מעדכנת גרסא", + "OnApplicationUpdateHelpText": "כשהאפליקציה מעדכנת גרסא" } diff --git a/src/NzbDrone.Core/Localization/Core/hi.json b/src/NzbDrone.Core/Localization/Core/hi.json index c85856065..8863b6725 100644 --- a/src/NzbDrone.Core/Localization/Core/hi.json +++ b/src/NzbDrone.Core/Localization/Core/hi.json @@ -2,7 +2,7 @@ "Language": "भाषा: हिन्दी", "UILanguage": "यूआई भाषा", "ArtistAlbumClickToChangeTrack": "फिल्म बदलने के लिए क्लिक करें", - "AuthenticationMethodHelpText": "Radarr का उपयोग करने के लिए उपयोगकर्ता नाम और पासवर्ड की आवश्यकता है", + "AuthenticationMethodHelpText": "Lidarr का उपयोग करने के लिए उपयोगकर्ता नाम और पासवर्ड की आवश्यकता है", "AutoRedownloadFailedHelpText": "स्वचालित रूप से खोजें और एक अलग रिलीज़ को डाउनलोड करने का प्रयास करें", "BackupFolderHelpText": "रिलेटिव पाथ रेडर के ऐपडाटा डायरेक्टरी के तहत होगा", "BackupNow": "अब समर्थन देना", @@ -77,7 +77,7 @@ "Filename": "फ़ाइल का नाम", "FileNames": "फ़ाइल नाम", "Files": "फ़ाइलें", - "GrabReleaseMessageText": "रेडर यह निर्धारित करने में असमर्थ था कि यह फिल्म किस फिल्म के लिए है। Radarr इस रिलीज़ को स्वचालित रूप से आयात करने में असमर्थ हो सकता है। क्या आप '{0}' को हथियाना चाहते हैं?", + "GrabReleaseMessageText": "रेडर यह निर्धारित करने में असमर्थ था कि यह फिल्म किस फिल्म के लिए है। Lidarr इस रिलीज़ को स्वचालित रूप से आयात करने में असमर्थ हो सकता है। क्या आप '{0}' को हथियाना चाहते हैं?", "Group": "समूह", "HasPendingChangesNoChanges": "कोई बदलाव नहीं", "HasPendingChangesSaveChanges": "परिवर्तनों को सुरक्षित करें", @@ -260,7 +260,7 @@ "AlternateTitles": "वैकल्पिक शीर्षक", "AlternateTitleslength1Titles": "टाइटल", "Analytics": "एनालिटिक्स", - "AnalyticsEnabledHelpText": "बेनामी उपयोग और त्रुटि जानकारी को Radarr के सर्वर पर भेजें। इसमें आपके ब्राउज़र की जानकारी शामिल है, जो आपके द्वारा उपयोग किए जाने वाले रेडर वेबयूआई पृष्ठों, त्रुटि रिपोर्टिंग के साथ-साथ ओएस और रनटाइम संस्करण भी है। हम इस जानकारी का उपयोग सुविधाओं और बग फिक्स को प्राथमिकता देने के लिए करेंगे।", + "AnalyticsEnabledHelpText": "बेनामी उपयोग और त्रुटि जानकारी को Lidarr के सर्वर पर भेजें। इसमें आपके ब्राउज़र की जानकारी शामिल है, जो आपके द्वारा उपयोग किए जाने वाले रेडर वेबयूआई पृष्ठों, त्रुटि रिपोर्टिंग के साथ-साथ ओएस और रनटाइम संस्करण भी है। हम इस जानकारी का उपयोग सुविधाओं और बग फिक्स को प्राथमिकता देने के लिए करेंगे।", "AnalyticsEnabledHelpTextWarning": "प्रभावी करने के लिए पुनरारंभ की आवश्यकता है", "Automatic": "स्वचालित", "DelayingDownloadUntilInterp": "देरी से डाउनलोड करने के लिए {0} पर {1}", @@ -451,7 +451,6 @@ "Queue": "कतार", "RSSSync": "आरएसएस सिंक", "RSSSyncInterval": "RSS सिंक अंतराल", - "Radarr": "Radarr", "ReadTheWikiForMoreInformation": "अधिक जानकारी के लिए विकी पढ़ें", "Real": "असली", "RecyclingBin": "रीसाइक्लिंग बिन", @@ -466,5 +465,13 @@ "SorryThatArtistCannotBeFound": "क्षमा करें, वह फिल्म नहीं मिल रही है।", "Source": "स्रोत", "SourcePath": "स्रोत पथ", - "Ungroup": "असमूहीकृत" + "Ungroup": "असमूहीकृत", + "ThisCannotBeCancelled": "यह एक बार रद्द नहीं किया जा सकता है जब तक कि रेडर को फिर से शुरू किए बिना।", + "OnGrab": "हड़पने पर", + "OnHealthIssue": "स्वास्थ्य के मुद्दे पर", + "OnRename": "नाम बदलने पर", + "OnUpgrade": "अपग्रेड पर", + "Tracks": "निशान", + "NETCore": ".NET कोर", + "MonoVersion": "मोनो संस्करण" } diff --git a/src/NzbDrone.Core/Localization/Core/hu.json b/src/NzbDrone.Core/Localization/Core/hu.json index e5402ac7e..5bd248fe5 100644 --- a/src/NzbDrone.Core/Localization/Core/hu.json +++ b/src/NzbDrone.Core/Localization/Core/hu.json @@ -414,7 +414,6 @@ "LatestAlbum": "Legújabb album", "LatestAlbumData": "Monitorozza a legújabb albumokat és a jövőbeli albumokat", "LaunchBrowserHelpText": " Nyisson meg egy böngészőt, és az alkalmazás indításakor lépjen a Lidarr kezdőlapjára.", - "Lidarr": "Lidarr", "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "A Lidarr minden olyan indexelőt támogat, amely a Newznab szabványt használja, valamint az alább felsorolt egyéb indexelőket.", "LidarrSupportsMultipleListsForImportingAlbumsAndArtistsIntoTheDatabase": "A Lidarr több listát is támogat az albumok és előadók adatbázisba történő importálásához.", "LidarrTags": "Lidarr Címkék", @@ -615,8 +614,6 @@ "Quality": "Minőség", "QualityProfile": "Minőségi Profil", "QualityProfiles": "Minőségi profilok", - "Radarr": "Radarr", - "Readarr": "Readarr", "Real": "Valódi", "Reorder": "Átrendezés", "ReplaceIllegalCharacters": "Az illegális karakterek cseréje", @@ -697,5 +694,11 @@ "OnImportFailure": "Importálási hiba esetén", "MassAlbumsSearchWarning": "Biztos benne, hogy megkeresi az összes \"{0}\" hiányzó albumot?", "MassAlbumsCutoffUnmetWarning": "Biztosan megkeresi az összes „{0}” Küszöbhatár alatti albumot?", - "ThisCannotBeCancelled": "Ezt folyamatot az összes indexelő letiltása nélkül nem lehet visszavonni, ha egyszer elindult." + "ThisCannotBeCancelled": "Ezt folyamatot az összes indexelő letiltása nélkül nem lehet visszavonni, ha egyszer elindult.", + "AddedArtistSettings": "Szerzői beállítások hozzáadva", + "ImportListSpecificSettings": "Listaspecifikus beállítások importálása", + "MonoVersion": "Mono Verzió", + "MonitorAlbumExistingOnlyWarning": "Ez az egyes könyvek felügyelt beállításának egyszeri módosítása. A Szerző/Szerkesztés alatti lehetőség segítségével szabályozhatja, hogy mi történjen az újonnan hozzáadott könyvekkel", + "MonitoringOptionsHelpText": "Mely könyveket érdemes figyelni a szerző hozzáadása után (egyszeri módosítás)", + "MonitorNewItemsHelpText": "Milyen új könyveket kell figyelni" } diff --git a/src/NzbDrone.Core/Localization/Core/is.json b/src/NzbDrone.Core/Localization/Core/is.json index 0ed1110cd..ed131ef5e 100644 --- a/src/NzbDrone.Core/Localization/Core/is.json +++ b/src/NzbDrone.Core/Localization/Core/is.json @@ -11,9 +11,9 @@ "ApplyTagsHelpTexts3": "Fjarlægja: Fjarlægðu innsláttarmerkin", "ApplyTagsHelpTexts4": "Skipta um: Skiptu um merkin með innsláttu merkjunum (sláðu inn engin merki til að hreinsa öll merki)", "ArtistAlbumClickToChangeTrack": "Smelltu til að breyta kvikmynd", - "AuthenticationMethodHelpText": "Krefjast notandanafns og lykilorðs til að fá aðgang að Radarr", + "AuthenticationMethodHelpText": "Krefjast notandanafns og lykilorðs til að fá aðgang að Lidarr", "AutoRedownloadFailedHelpText": "Leitaðu sjálfkrafa að og reyndu að hlaða niður annarri útgáfu", - "BackupFolderHelpText": "Hlutfallslegir slóðir verða undir AppData skránni hjá Radarr", + "BackupFolderHelpText": "Hlutfallslegir slóðir verða undir AppData skránni hjá Lidarr", "BackupRetentionHelpText": "Sjálfvirk afrit sem eru eldri en varðveislutímabilið verða hreinsuð upp sjálfkrafa", "Backups": "Afrit", "BindAddress": "Bind heimilisfang", @@ -33,9 +33,9 @@ "ChangeHasNotBeenSavedYet": "Breyting hefur ekki verið vistuð ennþá", "ChmodFolder": "chmod Mappa", "ChmodFolderHelpText": "Octal, beitt við innflutning / endurnefna á fjölmiðlamöppur og skrár (án þess að framkvæma bita)", - "ChmodFolderHelpTextWarning": "Þetta virkar aðeins ef notandinn sem keyrir Radarr er eigandi skráarinnar. Það er betra að tryggja að niðurhalsviðskiptavinurinn stilli heimildirnar rétt.", + "ChmodFolderHelpTextWarning": "Þetta virkar aðeins ef notandinn sem keyrir Lidarr er eigandi skráarinnar. Það er betra að tryggja að niðurhalsviðskiptavinurinn stilli heimildirnar rétt.", "ChownGroupHelpText": "Hópur nafn eða gid. Notaðu gid fyrir fjarskrárkerfi.", - "ChownGroupHelpTextWarning": "Þetta virkar aðeins ef notandinn sem keyrir Radarr er eigandi skráarinnar. Það er betra að tryggja að niðurhalsviðskiptavinurinn noti sama hóp og Radarr.", + "ChownGroupHelpTextWarning": "Þetta virkar aðeins ef notandinn sem keyrir Lidarr er eigandi skráarinnar. Það er betra að tryggja að niðurhalsviðskiptavinurinn noti sama hóp og Lidarr.", "ClickToChangeQuality": "Smelltu til að breyta gæðum", "ClientPriority": "Forgangur viðskiptavinar", "CloneIndexer": "Clone Indexer", @@ -43,11 +43,11 @@ "Component": "Hluti", "ConnectSettings": "Tengdu stillingar", "CopyUsingHardlinksHelpText": "Notaðu harða tengla þegar þú reynir að afrita skrár úr straumum sem enn er sáð", - "CopyUsingHardlinksHelpTextWarning": "Stundum geta skráarlásar komið í veg fyrir að endurnefna skrár sem verið er að sá. Þú getur slökkt á sáningu tímabundið og notað endurnefnaaðgerð Radarr sem vinnu.", + "CopyUsingHardlinksHelpTextWarning": "Stundum geta skráarlásar komið í veg fyrir að endurnefna skrár sem verið er að sá. Þú getur slökkt á sáningu tímabundið og notað endurnefnaaðgerð Lidarr sem vinnu.", "CreateEmptyArtistFolders": "Búðu til tómar kvikmyndamöppur", "CreateEmptyArtistFoldersHelpText": "Búðu til kvikmyndamöppur sem vantar meðan á diskaskönnun stendur", "CreateGroup": "Búðu til hóp", - "CutoffHelpText": "Þegar þessum gæðum er náð mun Radarr ekki lengur hlaða niður kvikmyndum", + "CutoffHelpText": "Þegar þessum gæðum er náð mun Lidarr ekki lengur hlaða niður kvikmyndum", "CutoffUnmet": "Cut-off Ómetið", "Dates": "Dagsetningar", "DBMigration": "DB fólksflutningar", @@ -123,7 +123,7 @@ "GoToInterp": "Farðu í {0}", "Grab": "Grípa", "GrabID": "Grípa skilríki", - "GrabReleaseMessageText": "Radarr gat ekki ákvarðað fyrir hvaða kvikmynd þessi útgáfa var gerð. Radarr gæti hugsanlega ekki flutt þessa útgáfu sjálfkrafa inn. Viltu grípa '{0}'?", + "GrabReleaseMessageText": "Lidarr gat ekki ákvarðað fyrir hvaða kvikmynd þessi útgáfa var gerð. Lidarr gæti hugsanlega ekki flutt þessa útgáfu sjálfkrafa inn. Viltu grípa '{0}'?", "GrabSelected": "Grípa valið", "Group": "Hópur", "HasPendingChangesNoChanges": "Engar breytingar", @@ -145,7 +145,7 @@ "ImportFailedInterp": "Innflutningur mistókst: {0}", "Importing": "Innflutningur", "IncludeHealthWarningsHelpText": "Láttu heilsuviðvaranir fylgja með", - "IncludeUnknownArtistItemsHelpText": "Sýnið atriði án kvikmyndar í biðröðinni. Þetta gæti falið í sér fjarlægðar kvikmyndir eða annað í flokknum Radarr", + "IncludeUnknownArtistItemsHelpText": "Sýnið atriði án kvikmyndar í biðröðinni. Þetta gæti falið í sér fjarlægðar kvikmyndir eða annað í flokknum Lidarr", "IncludeUnmonitored": "Hafa óeftirlit með", "Indexer": "Indexer", "IndexerPriority": "Forgangur indexers", @@ -156,9 +156,9 @@ "IsCutoffCutoff": "Skera af", "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Uppfærðu þar til þessum gæðum er fullnægt eða farið fram úr þeim", "Level": "Stig", - "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Radarr styður hvaða niðurhals viðskiptavinur sem notar Newznab staðalinn, auk annarra niðurhals viðskiptavina sem taldir eru upp hér að neðan.", - "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr styður alla vísitölufyrirtæki sem nota Newznab staðalinn, svo og aðrir verðtryggingaraðilar sem taldir eru upp hér að neðan.", - "LidarrTags": "Radarr merkimiðar", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr styður hvaða niðurhals viðskiptavinur sem notar Newznab staðalinn, auk annarra niðurhals viðskiptavina sem taldir eru upp hér að neðan.", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr styður alla vísitölufyrirtæki sem nota Newznab staðalinn, svo og aðrir verðtryggingaraðilar sem taldir eru upp hér að neðan.", + "LidarrTags": "Lidarr merkimiðar", "LoadingTrackFilesFailed": "Ekki tókst að hlaða kvikmyndaskrár", "Medium": "Miðlungs", "Message": "Skilaboð", @@ -188,19 +188,19 @@ "RemoveSelected": "Fjarlægja valið", "RemoveSelectedMessageText": "Ertu viss um að þú viljir fjarlægja valda hluti af svörtum lista?", "RemoveTagExistingTag": "Núverandi merki", - "RenameTracksHelpText": "Radarr mun nota núverandi skráarheiti ef ekki er gert endurnafn á nafni", - "ReplaceIllegalCharactersHelpText": "Skiptu um ólöglega stafi. Ef ekki er hakað við mun Radarr fjarlægja þá í staðinn", + "RenameTracksHelpText": "Lidarr mun nota núverandi skráarheiti ef ekki er gert endurnafn á nafni", + "ReplaceIllegalCharactersHelpText": "Skiptu um ólöglega stafi. Ef ekki er hakað við mun Lidarr fjarlægja þá í staðinn", "RequiredHelpText": "Útgáfan verður að innihalda að minnsta kosti einn af þessum hugtökum (lítt viðkvæm)", "RequiredPlaceHolder": "Bættu við nýjum takmörkunum", "RequiresRestartToTakeEffect": "Krefst endurræsingar til að taka gildi", "RescanAfterRefreshHelpText": "Skannaðu aftur kvikmyndamöppuna eftir að hafa endurnýjað myndina", - "RescanAfterRefreshHelpTextWarning": "Radarr finnur ekki sjálfkrafa breytingar á skrám þegar hann er ekki stilltur á „Alltaf“", + "RescanAfterRefreshHelpTextWarning": "Lidarr finnur ekki sjálfkrafa breytingar á skrám þegar hann er ekki stilltur á „Alltaf“", "RescanArtistFolderAfterRefresh": "Skanna aftur kvikmyndamöppu eftir endurnýjun", "Reset": "Endurstilla", "ResetAPIKey": "Endurstilla API lykil", "ResetAPIKeyMessageText": "Ertu viss um að þú viljir endurstilla API lykilinn þinn?", "Restart": "Endurræsa", - "RestartLidarr": "Endurræstu Radarr", + "RestartLidarr": "Endurræstu Lidarr", "RestartNow": "Endurræstu núna", "RSSSync": "RSS samstilling", "RSSSyncInterval": "RSS Sync bil", @@ -223,7 +223,7 @@ "Track": "Spor", "Type": "Tegund", "UISettings": "Stillingar HÍ", - "UILanguageHelpText": "Tungumál sem Radarr mun nota fyrir HÍ", + "UILanguageHelpText": "Tungumál sem Lidarr mun nota fyrir HÍ", "UILanguageHelpTextWarning": "Endurhlaða vafra krafist", "UnableToAddANewDownloadClientPleaseTryAgain": "Ekki er hægt að bæta við nýjum niðurhalsþjóni, reyndu aftur.", "UnableToAddANewImportListExclusionPleaseTryAgain": "Ekki er hægt að bæta við nýjum listaútilokun, reyndu aftur.", @@ -263,7 +263,7 @@ "AlternateTitleslength1Title": "Titill", "AlternateTitleslength1Titles": "Titlar", "Analytics": "Greiningar", - "AnalyticsEnabledHelpText": "Sendu nafnlausar upplýsingar um notkun og villur á netþjóna Radarr. Þetta felur í sér upplýsingar í vafranum þínum, hvaða Radarr WebUI síður þú notar, villuskýrslur sem og stýrikerfi og keyrsluútgáfu. Við munum nota þessar upplýsingar til að forgangsraða eiginleikum og villuleiðréttingum.", + "AnalyticsEnabledHelpText": "Sendu nafnlausar upplýsingar um notkun og villur á netþjóna Lidarr. Þetta felur í sér upplýsingar í vafranum þínum, hvaða Lidarr WebUI síður þú notar, villuskýrslur sem og stýrikerfi og keyrsluútgáfu. Við munum nota þessar upplýsingar til að forgangsraða eiginleikum og villuleiðréttingum.", "AnalyticsEnabledHelpTextWarning": "Krefst endurræsingar til að taka gildi", "ApiKeyHelpTextWarning": "Krefst endurræsingar til að taka gildi", "Score": "Mark", @@ -285,7 +285,7 @@ "ShowMonitoredHelpText": "Sýnið vöktaða stöðu undir veggspjaldi", "Size": " Stærð", "SkipFreeSpaceCheck": "Slepptu ókeypis plássathugun", - "SkipFreeSpaceCheckWhenImportingHelpText": "Notaðu þegar Radarr getur ekki greint laust pláss úr rótarmöppu kvikmyndarinnar", + "SkipFreeSpaceCheckWhenImportingHelpText": "Notaðu þegar Lidarr getur ekki greint laust pláss úr rótarmöppu kvikmyndarinnar", "SorryThatAlbumCannotBeFound": "Því miður er ekki hægt að finna þá kvikmynd.", "SorryThatArtistCannotBeFound": "Því miður er ekki hægt að finna þá kvikmynd.", "Source": "Heimild", @@ -299,7 +299,7 @@ "UsenetDelay": "Seinkun Usenet", "UsenetDelayHelpText": "Seinkaðu í nokkrar mínútur til að bíða áður en þú grípur losun frá Usenet", "Username": "Notendanafn", - "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Útibú til að nota til að uppfæra Radarr", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Útibú til að nota til að uppfæra Lidarr", "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Útibú notað af ytri uppfærslu", "Version": "Útgáfa", "Actions": "Aðgerðir", @@ -312,10 +312,10 @@ "FileManagement": "Skráastjórnun", "GrabRelease": "Grípa losun", "IsTagUsedCannotBeDeletedWhileInUse": "Ekki er hægt að eyða meðan hún er í notkun", - "LaunchBrowserHelpText": " Opnaðu vafra og farðu á Radarr heimasíðuna þegar forritið byrjar.", + "LaunchBrowserHelpText": " Opnaðu vafra og farðu á Lidarr heimasíðuna þegar forritið byrjar.", "Local": "Staðbundin", "LocalPath": "Local Path", - "LocalPathHelpText": "Leið sem Radarr ætti að nota til að komast á fjarstíginn á staðnum", + "LocalPathHelpText": "Leið sem Lidarr ætti að nota til að komast á fjarstíginn á staðnum", "LogFiles": "Log skrár", "Logging": "Skógarhögg", "LogLevel": "Log Level", @@ -386,7 +386,6 @@ "QualityDefinitions": "Gæðaskilgreiningar", "QualityProfile": "Gæðaprófíll", "QualityProfiles": "Gæðasnið", - "Radarr": "Radarr", "Real": "Alvöru", "ReleaseRejected": "Losun hafnað", "ReleaseStatuses": "Sleppa stöðu", @@ -433,14 +432,14 @@ "SuccessMyWorkIsDoneNoFilesToRetag": "Árangur! Vinnu minni er lokið, engar skrár til að endurnefna.", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS er ekki studd með þessum flokkara", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Leit er ekki studd með þessum flokkara", - "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Verður notað þegar sjálfvirkar leitir eru framkvæmdar í HÍ eða af Radarr", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Verður notað þegar sjálfvirkar leitir eru framkvæmdar í HÍ eða af Lidarr", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Verður notað þegar gagnvirk leit er notuð", "TagIsNotUsedAndCanBeDeleted": "Merkið er ekki notað og hægt er að eyða því", "Tags": "Merkimiðar", "UnableToLoadBackups": "Ekki er hægt að hlaða afrit", "UnableToLoadReleaseProfiles": "Ekki er hægt að hlaða seinkunarsnið", "UpdateAll": "Uppfæra allt", - "UpdateMechanismHelpText": "Notaðu innbyggða uppfærslu Radarr eða handrit", + "UpdateMechanismHelpText": "Notaðu innbyggða uppfærslu Lidarr eða handrit", "Updates": "Uppfærslur", "Year": "Ár", "YesCancel": "Já, hætta við", @@ -465,5 +464,14 @@ "DelayingDownloadUntilInterp": "Seinkar niðurhali til {0} kl. {1}", "Scheduled": "Tímaáætlun", "Ungroup": "Aftengja hópinn", - "Uptime": "Spenntur" + "Uptime": "Spenntur", + "ThisCannotBeCancelled": "Ekki er hægt að hætta við þetta þegar byrjað er án þess að endurræsa Whisparr.", + "OnGrab": "Á grípa", + "OnHealthIssue": "Um heilbrigðismál", + "OnRename": "Um Endurnefna", + "OnUpgrade": "Við uppfærslu", + "Tracks": "Spor", + "NETCore": ".NET algerlega", + "Docker": "Docker", + "MonoVersion": "Mónóútgáfa" } diff --git a/src/NzbDrone.Core/Localization/Core/it.json b/src/NzbDrone.Core/Localization/Core/it.json index 53585a60f..74213ea76 100644 --- a/src/NzbDrone.Core/Localization/Core/it.json +++ b/src/NzbDrone.Core/Localization/Core/it.json @@ -46,9 +46,9 @@ "ApplyTagsHelpTexts4": "Sostituire: sostituisci le tag con le tag inserite (non inserire nessuna tag per pulirle tutte)", "ArtistAlbumClickToChangeTrack": "Clicca per cambiare film", "Authentication": "Autenticazione", - "AuthenticationMethodHelpText": "Utilizza nome utente e password per accedere a Radarr", + "AuthenticationMethodHelpText": "Utilizza nome utente e password per accedere a Lidarr", "AutoRedownloadFailedHelpText": "Ricerca automatica e tentativo di scaricare un'altra versione", - "BackupFolderHelpText": "I percorsi relativi saranno sotto la directory AppData di Radarr", + "BackupFolderHelpText": "I percorsi relativi saranno sotto la directory AppData di Lidarr", "BackupNow": "Effettua ora il Backup", "BackupRetentionHelpText": "I backup automatici più vecchi del periodo di conservazione verranno eliminati automaticamente", "Backups": "I Backup", @@ -56,7 +56,7 @@ "BindAddressHelpText": "Indirizzo IPV4 valido o '*' per tutte le interfacce", "BindAddressHelpTextWarning": "Richiede il riavvio per avere effetti", "Blocklist": "Lista Nera", - "BlocklistHelpText": "Impedisci a Radarr di acquisire automaticamente questo versione", + "BlocklistHelpText": "Impedisci a Lidarr di acquisire automaticamente questo versione", "BlocklistRelease": "Release in blacklist", "Branch": "Ramo", "BypassProxyForLocalAddresses": "Evita il Proxy per gli indirizzi locali", @@ -67,9 +67,9 @@ "ChangeHasNotBeenSavedYet": "Il cambiamento non è stato ancora salvato", "ChmodFolder": "Permessi Cartella", "ChmodFolderHelpText": "Octal, applicato durante importazione/rinomina di cartelle e file (senza execute bits)", - "ChmodFolderHelpTextWarning": "Funziona solo se l'utente di Radarr è il proprietario del file. E' meglio assicurarsi che i client di download impostino i permessi correttamente.", + "ChmodFolderHelpTextWarning": "Funziona solo se l'utente di Lidarr è il proprietario del file. E' meglio assicurarsi che i client di download impostino i permessi correttamente.", "ChownGroupHelpText": "Nome Gruppo o guida. Usa guida per sistemi remoti.", - "ChownGroupHelpTextWarning": "Funziona solo se l'utente di Radarr è il proprietario del file. E' meglio assicurarsi che i client di download usino lo stesso gruppo di Radarr.", + "ChownGroupHelpTextWarning": "Funziona solo se l'utente di Lidarr è il proprietario del file. E' meglio assicurarsi che i client di download usino lo stesso gruppo di Lidarr.", "ClickToChangeQuality": "Clicca per cambiare qualità", "ClientPriority": "Priorità del Client", "CloneIndexer": "Clona Indexer", @@ -80,11 +80,11 @@ "Connections": "Collegamenti", "ConnectSettings": "Impostazioni di connessione", "CopyUsingHardlinksHelpText": "Utilizzare gli Hardlink quando si cerca di copiare file da un torrent che è ancora in fase di seeding", - "CopyUsingHardlinksHelpTextWarning": "Occasionalmente, i blocchi dei file possono impedire la ridenominazione dei file in fase di seeding. È possibile disattivare temporaneamente il seeding e utilizzare la funzione di rinomina di Radarr per evitare il problema.", + "CopyUsingHardlinksHelpTextWarning": "Occasionalmente, i blocchi dei file possono impedire la ridenominazione dei file in fase di seeding. È possibile disattivare temporaneamente il seeding e utilizzare la funzione di rinomina di Lidarr per evitare il problema.", "CreateEmptyArtistFolders": "Crea le cartelle dei film se non esistono", "CreateEmptyArtistFoldersHelpText": "Crea le cartelle dei film mancanti durante la scansione del disco", "CreateGroup": "Crea gruppo", - "CutoffHelpText": "Una volta raggiunta questa qualità, Radarr non scaricherà più film", + "CutoffHelpText": "Una volta raggiunta questa qualità, Lidarr non scaricherà più film", "CutoffUnmet": "Soglia Non Raggiunta", "Dates": "Date", "DBMigration": "Migrazione del DataBase", @@ -163,7 +163,7 @@ "Grab": "Preleva", "GrabID": "ID di Prelievo", "GrabRelease": "Preleva Release", - "GrabReleaseMessageText": "Radarr non è stato in grado di determinare a quale film si riferisce questa release. Radarr potrebbe non essere in grado di importarla automaticamente. Vuoi catturare '{0}'?", + "GrabReleaseMessageText": "Lidarr non è stato in grado di determinare a quale film si riferisce questa release. Lidarr potrebbe non essere in grado di importarla automaticamente. Vuoi catturare '{0}'?", "GrabSelected": "Recupera selezione", "Group": "Gruppo", "HasPendingChangesNoChanges": "Nessuna Modifica", @@ -186,7 +186,7 @@ "ImportFailedInterp": "Importazione fallita: {0}", "Importing": "Importazione", "IncludeHealthWarningsHelpText": "Includi gli avvisi di salute", - "IncludeUnknownArtistItemsHelpText": "Mostra le voci senza un film nella coda. Ciò potrebbe include film spostati o altro nelle categorie di Radarr", + "IncludeUnknownArtistItemsHelpText": "Mostra le voci senza un film nella coda. Ciò potrebbe include film spostati o altro nelle categorie di Lidarr", "IncludeUnmonitored": "Includi non Monitorati", "Indexer": "Indicizzatore", "IndexerPriority": "Priorità dell'indexer", @@ -198,15 +198,15 @@ "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Aggiorna finchè questa qualità non è raggiunta o superata", "IsTagUsedCannotBeDeletedWhileInUse": "Non può essere cancellato mentre è in uso", "Label": "Etichetta", - "LaunchBrowserHelpText": " Apri un browser e vai all'homepage di Radarr all'avvio dell'app.", + "LaunchBrowserHelpText": " Apri un browser e vai all'homepage di Lidarr all'avvio dell'app.", "Level": "Livello", - "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Radarr supporta qualunque client di download che usi gli standard Newznab, cosi come gli altri client sotto.", - "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr supporta qualunque indexer che usi gli standard Newznab, cosi come gli altri Indexer sotto.", - "LidarrTags": "Tag di Radarr", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr supporta qualunque client di download che usi gli standard Newznab, cosi come gli altri client sotto.", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr supporta qualunque indexer che usi gli standard Newznab, cosi come gli altri Indexer sotto.", + "LidarrTags": "Tag di Lidarr", "LoadingTrackFilesFailed": "Caricamento dei file del Film fallito", "Local": "Locale", "LocalPath": "Percorso locale", - "LocalPathHelpText": "Percorso che Radarr dovrebbe usare per accedere localmente al percorso remoto", + "LocalPathHelpText": "Percorso che Lidarr dovrebbe usare per accedere localmente al percorso remoto", "LogFiles": "File di Log", "Logging": "Logging", "LogLevel": "Livello di Log", @@ -275,7 +275,6 @@ "QualityProfiles": "Profili di Qualità", "QualitySettings": "Impostazione di Qualità", "Queue": "Coda", - "Radarr": "Radarr", "ReadTheWikiForMoreInformation": "Leggi le Wiki per maggiori informazioni", "Real": "Reale", "Reason": "Ragione", @@ -310,15 +309,15 @@ "RemoveSelectedMessageText": "Sei sicuro di voler rimuovere gli elementi selezionati della blacklist?", "RemoveTagExistingTag": "Tag esistente", "RemoveTagRemovingTag": "Sto eliminando il tag", - "RenameTracksHelpText": "Radarr userà i nomi dei file se rinomina è disabilitato", + "RenameTracksHelpText": "Lidarr userà i nomi dei file se rinomina è disabilitato", "Reorder": "Riordina", "ReplaceIllegalCharacters": "Sostituisci i caratteri non consentiti", - "ReplaceIllegalCharactersHelpText": "Sostituisci i caratteri non consentiti. Se non selezionato, Radarr invece li rimuoverà", + "ReplaceIllegalCharactersHelpText": "Sostituisci i caratteri non consentiti. Se non selezionato, Lidarr invece li rimuoverà", "RequiredHelpText": "La liberatoria deve contenere almeno uno di questi termini (senza distinzione tra maiuscole e minuscole)", "RequiredPlaceHolder": "Aggiungi una nuova restrizione", "RequiresRestartToTakeEffect": "Richiede il riavvio per avere effetti", "RescanAfterRefreshHelpText": "Riscansiona la cartella dopo aver ricaricato il film", - "RescanAfterRefreshHelpTextWarning": "Radarr non identificherà in automatico i cambiamenti ai file quando non impostato a \"Sempre\"", + "RescanAfterRefreshHelpTextWarning": "Lidarr non identificherà in automatico i cambiamenti ai file quando non impostato a \"Sempre\"", "RescanArtistFolderAfterRefresh": "Riscansiona la cartella del Film dopo il refresh", "Reset": "Resetta", "ResetAPIKey": "Resetta la Chiave API", @@ -339,7 +338,7 @@ "SuccessMyWorkIsDoneNoFilesToRetag": "Successo! Il mio lavoro è finito, nessun file da rinominare.", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS non è supportato con questo indexer", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "La ricerca non è supportata dal questo indexer", - "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Sarà usata quando la ricerca automatica è eseguita dalla UI o da Radarr", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Sarà usata quando la ricerca automatica è eseguita dalla UI o da Lidarr", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Sarà usata quando la ricerca interattiva è utilizzata", "Tags": "Tag", "Tasks": "Funzioni", @@ -355,7 +354,7 @@ "Torrents": "Torrent", "TotalFileSize": "Dimensione totale dei file", "Track": "Traccia", - "UILanguageHelpText": "Lingua che Radarr userà per la UI", + "UILanguageHelpText": "Lingua che Lidarr userà per la UI", "UILanguageHelpTextWarning": "Richiede il reload del Browser", "UISettings": "Impostazioni UI", "UnableToAddANewDownloadClientPleaseTryAgain": "Non riesco ad aggiungere un nuovo client di download, riprova.", @@ -394,7 +393,7 @@ "UnmonitoredHelpText": "Includi i film non monitorati nei feed di iCal", "UpdateAll": "Aggiorna Tutto", "UpdateAutomaticallyHelpText": "Download e installazione automatica degli aggiornamenti. Sarai comunque in grado in installarli dal menu Sistema: Aggiornamenti", - "UpdateMechanismHelpText": "Usa il programma di aggiornamento incorporato in Radarr o uno script", + "UpdateMechanismHelpText": "Usa il programma di aggiornamento incorporato in Lidarr o uno script", "Updates": "Aggiornamenti", "UpdateScriptPathHelpText": "Percorso verso uno script che prende un pacchetto di aggiornamento estratto e gestisce il resto del processo di aggiornamento", "UpgradeAllowedHelpText": "Se disabilitato la qualità non verrà incrementata", @@ -407,14 +406,14 @@ "UsenetDelayHelpText": "Minuti di attesa prima di prendere una release da Usenet", "UseProxy": "Usa Proxy", "Username": "Nome utente", - "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Branch da utilizzare per aggiornare Radarr", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Branch da utilizzare per aggiornare Lidarr", "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Branch utilizzato dal sistema di aggiornamento esterno", "Version": "Versione", "WeekColumnHeader": "Intestazione colonna settimana", "Year": "Anno", "YesCancel": "Si, annulla", "About": "Versione", - "AnalyticsEnabledHelpText": "Inviare informazioni anonime sull'utilizzo e sugli errori ai server di Radarr. Ciò include informazioni del tuo browser, come le pagine di Radarr che utilizzi, la segnalazione di errori e la versione del sistema operativo e del runtime. Utilizzeremo queste informazioni per dare priorità alle funzioni e alle correzioni di bug.", + "AnalyticsEnabledHelpText": "Inviare informazioni anonime sull'utilizzo e sugli errori ai server di Lidarr. Ciò include informazioni del tuo browser, come le pagine di Lidarr che utilizzi, la segnalazione di errori e la versione del sistema operativo e del runtime. Utilizzeremo queste informazioni per dare priorità alle funzioni e alle correzioni di bug.", "AnalyticsEnabledHelpTextWarning": "Richiede il riavvio per avere effetti", "Automatic": "Automatico", "DelayingDownloadUntilInterp": "Ritardare il download fino al {0} a {1}", @@ -436,7 +435,7 @@ "ShowDateAdded": "Mostra data di aggiunta", "ShowMonitored": "Mostra i monitorati", "ShowMonitoredHelpText": "Mostra lo stato Monitorato sotto il poster", - "SkipFreeSpaceCheckWhenImportingHelpText": "Usa quando Radarr non è in grado di determinare lo spazio libero della cartella di root dei film", + "SkipFreeSpaceCheckWhenImportingHelpText": "Usa quando Lidarr non è in grado di determinare lo spazio libero della cartella di root dei film", "SorryThatArtistCannotBeFound": "Mi spiace, impossibile trovare il film.", "Source": "Fonte", "SourcePath": "Percorso origine", @@ -460,7 +459,7 @@ "Password": "Password", "ResetAPIKeyMessageText": "Sei sicuro di voler reimpostare la tua chiave API?", "Restart": "Riavvia", - "RestartLidarr": "Riavvia Radarr", + "RestartLidarr": "Riavvia Lidarr", "RestartNow": "Riavvia adesso", "Restore": "Ripristina", "RestoreBackup": "Ripristina Backup", @@ -495,5 +494,30 @@ "Artist": "Artista", "Artists": "Artisti", "AutomaticallySwitchRelease": "Cambia automaticamente la release", - "BackupIntervalHelpText": "Intervallo per eseguire il backup del DB Lidarr e delle impostazioni" + "BackupIntervalHelpText": "Intervallo per eseguire il backup del DB Lidarr e delle impostazioni", + "RemoveCompleted": "Rimuovi completati", + "RemoveFailed": "Rimozione fallita", + "ThisCannotBeCancelled": "Questo non può essere annullato una volta avviato senza riavviare Whisparr.", + "OnApplicationUpdateHelpText": "All'aggiornamento dell'applicazione", + "RemoveDownloadsAlert": "Le impostazioni per la rimozione sono stati spostati nelle impostazioni individuali dei Client di Download nella tabella sopra.", + "OnApplicationUpdate": "All'aggiornamento dell'applicazione", + "OnGrab": "Quando viene prelevato", + "OnHealthIssue": "Quando c'è un problema", + "OnRename": "Durante la rinomina", + "OnUpgrade": "In aggiornamento", + "UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent esposto dalla app che ha chiamato la API", + "MonoVersion": "Versione Mono", + "WriteMetadataToAudioFiles": "Scrivi i Metadati sui File Audio", + "AddedArtistSettings": "Impostazioni dell'autore aggiunte", + "DeleteTrackFileMessageText": "Sicuro di voler eliminare {0}?", + "Duration": "Durata", + "Season": "Stagione", + "ArtistFolderFormat": "Formato Cartella Autore", + "ArtistNameHelpText": "Il nome dell'autore/libro da escludere (può essere qualsiasi cosa significativa)", + "AnyReleaseOkHelpText": "Lidarr cambierà automaticamente all'edizione che meglio si abbina al file scaricato", + "MetadataProfile": "profilo metadati", + "MetadataProfiles": "profilo metadati", + "Term": "Termine", + "TrackFileCounttotalTrackCountTracksDownloadedInterp": "{0}/{1} libri scaricati", + "TrackFileCountTrackCountTotalTotalTrackCountInterp": "{0} / {1} (Totale: {2})" } diff --git a/src/NzbDrone.Core/Localization/Core/ja.json b/src/NzbDrone.Core/Localization/Core/ja.json index 978c75be3..4aba3f0ad 100644 --- a/src/NzbDrone.Core/Localization/Core/ja.json +++ b/src/NzbDrone.Core/Localization/Core/ja.json @@ -148,7 +148,6 @@ "QualityProfiles": "品質プロファイル", "QualitySettings": "品質設定", "Queue": "キュー", - "Radarr": "ラダー", "ReadTheWikiForMoreInformation": "詳細については、Wikiをお読みください", "Real": "リアル", "Reason": "理由", @@ -466,5 +465,13 @@ "WeekColumnHeader": "週の列ヘッダー", "Year": "年", "YesCancel": "はい、キャンセル", - "AlreadyInYourLibrary": "すでにライブラリにあります" + "AlreadyInYourLibrary": "すでにライブラリにあります", + "ThisCannotBeCancelled": "Whisparrを再起動せずに一度開始すると、これをキャンセルすることはできません。", + "OnGrab": "グラブで", + "OnHealthIssue": "健康問題について", + "OnRename": "名前の変更について", + "OnUpgrade": "アップグレード時", + "Tracks": "痕跡", + "NETCore": ".NET Core", + "MonoVersion": "モノバージョン" } diff --git a/src/NzbDrone.Core/Localization/Core/ko.json b/src/NzbDrone.Core/Localization/Core/ko.json index f0c8cbb32..1e99cb6cf 100644 --- a/src/NzbDrone.Core/Localization/Core/ko.json +++ b/src/NzbDrone.Core/Localization/Core/ko.json @@ -262,7 +262,7 @@ "ResetAPIKey": "API 키 재설정", "ResetAPIKeyMessageText": "API 키를 재설정하시겠습니까?", "Restart": "재시작", - "RestartLidarr": "Radarr 다시 시작", + "RestartLidarr": "Lidarr 다시 시작", "RestartNow": "지금 다시 시작", "Restore": "복원", "RestoreBackup": "백업 복원", @@ -357,7 +357,7 @@ "UsenetDelayHelpText": "Usenet에서 릴리스를 가져 오기 전에 대기하는 데 몇 분 지연", "UseProxy": "프록시 사용", "Username": "사용자 이름", - "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Radarr 업데이트에 사용할 파생 버전", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Lidarr 업데이트에 사용할 파생 버전", "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "외부 업데이트 메커니즘에서 사용하는 분기", "Version": "버전", "WeekColumnHeader": "주 열 헤더", @@ -374,7 +374,7 @@ "AlternateTitleslength1Title": "표제", "AlternateTitleslength1Titles": "타이틀", "Analytics": "분석", - "AnalyticsEnabledHelpText": "익명의 사용 및 오류 정보를 Radarr의 서버에 보냅니다. 여기에는 브라우저에 대한 정보, 사용하는 Radarr WebUI 페이지, 오류 보고, OS 및 런타임 버전이 포함됩니다. 이 정보를 사용하여 기능 및 버그 수정의 우선 순위를 지정합니다.", + "AnalyticsEnabledHelpText": "익명의 사용 및 오류 정보를 Radarr의 서버에 보냅니다. 여기에는 브라우저에 대한 정보, 사용하는 Lidarr WebUI 페이지, 오류 보고, OS 및 런타임 버전이 포함됩니다. 이 정보를 사용하여 기능 및 버그 수정의 우선 순위를 지정합니다.", "AnalyticsEnabledHelpTextWarning": "적용하려면 다시 시작해야합니다.", "Score": "점수", "ScriptPath": "스크립트 경로", @@ -404,8 +404,8 @@ "ImportFailedInterp": "가져 오기 실패 : {0}", "Importing": "가져 오기", "IncludeHealthWarningsHelpText": "건강 경고 포함", - "IncludeUnknownArtistItemsHelpText": "대기열에 영화가없는 항목을 표시합니다. 여기에는 제거 된 영화 또는 Radarr 카테고리의 다른 항목이 포함될 수 있습니다.", - "LaunchBrowserHelpText": " 웹 브라우저를 열고 앱 시작시 Radarr 홈페이지로 이동합니다.", + "IncludeUnknownArtistItemsHelpText": "대기열에 영화가없는 항목을 표시합니다. 여기에는 제거 된 영화 또는 Lidarr 카테고리의 다른 항목이 포함될 수 있습니다.", + "LaunchBrowserHelpText": " 웹 브라우저를 열고 앱 시작시 Lidarr 홈페이지로 이동합니다.", "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Radarr는 Newznab 표준을 사용하는 모든 다운로드 클라이언트와 아래 나열된 다른 다운로드 클라이언트를 지원합니다.", "MustContain": "포함해야 함", "MustNotContain": "포함해서는 안 됨", @@ -455,5 +455,22 @@ "Dates": "날짜", "Docker": "Docker", "Scheduled": "예정", - "SearchSelected": "선택 검색" + "SearchSelected": "선택 검색", + "OnGrab": "잡기", + "OnHealthIssue": "건강 문제", + "OnUpgrade": "업그레이드시", + "InteractiveSearch": "대화형 검색", + "Level": "수평", + "NETCore": ".NET Core", + "Track": "자취", + "Tracks": "자취", + "UrlBaseHelpText": "역방향 프록시 지원의 경우 기본값은 비어 있습니다.", + "OnRename": "이름 변경시", + "ShowUnknownArtistItems": "알 수없는 영화 항목 표시", + "ThisCannotBeCancelled": "Whisparr를 다시 시작하지 않고 시작한 후에는 취소 할 수 없습니다.", + "Time": "시간", + "HostHelpText": "원격 다운로드 클라이언트에 지정한 것과 동일한 호스트", + "ReleaseStatuses": "출시 상태", + "RescanAfterRefreshHelpText": "영화를 새로 고친 후 영화 폴더를 다시 스캔하십시오.", + "MonoVersion": "모노 버전" } diff --git a/src/NzbDrone.Core/Localization/Core/lt.json b/src/NzbDrone.Core/Localization/Core/lt.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/src/NzbDrone.Core/Localization/Core/lt.json @@ -0,0 +1 @@ +{} diff --git a/src/NzbDrone.Core/Localization/Core/lv.json b/src/NzbDrone.Core/Localization/Core/lv.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/src/NzbDrone.Core/Localization/Core/lv.json @@ -0,0 +1 @@ +{} diff --git a/src/NzbDrone.Core/Localization/Core/nb_NO.json b/src/NzbDrone.Core/Localization/Core/nb_NO.json index b42fd3619..b579a589f 100644 --- a/src/NzbDrone.Core/Localization/Core/nb_NO.json +++ b/src/NzbDrone.Core/Localization/Core/nb_NO.json @@ -7,16 +7,16 @@ "ApplyTagsHelpTexts4": "Erstatt: Erstatt taggene med de angitte kodene (skriv inn ingen tagger for å slette alle taggene)", "ArtistAlbumClickToChangeTrack": "Klikk for å endre film", "Authentication": "Godkjenning", - "AuthenticationMethodHelpText": "Krev brukernavn og passord for å få tilgang til Radarr", + "AuthenticationMethodHelpText": "Krev brukernavn og passord for å få tilgang til Lidarr", "AutoRedownloadFailedHelpText": "Søk etter eller prøv å laste ned en annen versjon automatisk", - "BackupFolderHelpText": "Relative stier vil være under Radarr's AppData -katalog", + "BackupFolderHelpText": "Relative stier vil være under Lidarr's AppData -katalog", "BackupNow": "Sikkerhetskopier nå", "BackupRetentionHelpText": "Automatiske sikkerhetskopier som er eldre enn oppbevaringsperioden, blir ryddet opp automatisk", "Backups": "Sikkerhetskopier", "BindAddress": "Bind adresse", "BindAddressHelpText": "Gyldig IP4 -adresse eller \"*\" for alle grensesnitt", "Blocklist": "Svarteliste", - "BlocklistHelpText": "Hindrer Radarr i å automatisk gripe denne utgivelsen igjen", + "BlocklistHelpText": "Hindrer Lidarr i å automatisk gripe denne utgivelsen igjen", "BlocklistRelease": "Blacklist -utgivelse", "Branch": "Gren", "BypassProxyForLocalAddresses": "Omgå proxy for lokale adresser", @@ -29,9 +29,9 @@ "ChangeHasNotBeenSavedYet": "Endringen er ikke lagret ennå", "ChmodFolder": "chmod mappe", "ChmodFolderHelpText": "Octal, brukt under import/gi nytt navn til mediemapper og filer (uten utføringsbiter)", - "ChmodFolderHelpTextWarning": "Dette fungerer bare hvis brukeren som kjører Radarr er eieren av filen. Det er bedre å sikre at nedlastingsklienten angir tillatelsene riktig.", + "ChmodFolderHelpTextWarning": "Dette fungerer bare hvis brukeren som kjører Lidarr er eieren av filen. Det er bedre å sikre at nedlastingsklienten angir tillatelsene riktig.", "ChownGroupHelpText": "Gruppenavn eller gid. Bruk gid for eksterne filsystemer.", - "ChownGroupHelpTextWarning": "Dette fungerer bare hvis brukeren som kjører Radarr er eieren av filen. Det er bedre å sikre at nedlastingsklienten angir tillatelsene riktig.", + "ChownGroupHelpTextWarning": "Dette fungerer bare hvis brukeren som kjører Lidarr er eieren av filen. Det er bedre å sikre at nedlastingsklienten angir tillatelsene riktig.", "Clear": "Klar", "DeleteDelayProfileMessageText": "Er du sikker på at du vil slette denne forsinkelsesprofilen?", "DeleteDownloadClientMessageText": "Er du sikker på at du vil slette formattaggen {0}?", @@ -44,7 +44,7 @@ "AgeWhenGrabbed": "Alder (når den tas)", "AlternateTitles": "Alternativ tittel", "Analytics": "Analyse", - "AnalyticsEnabledHelpText": "Send anonym bruk og feilinformasjon til Radarrs servere. Dette inkluderer informasjon om nettleseren din, hvilke Radarr WebUI -sider du bruker, feilrapportering samt OS og kjøretidsversjon. Vi vil bruke denne informasjonen til å prioritere funksjoner og feilrettinger.", + "AnalyticsEnabledHelpText": "Send anonym bruk og feilinformasjon til Radarrs servere. Dette inkluderer informasjon om nettleseren din, hvilke Lidarr WebUI -sider du bruker, feilrapportering samt OS og kjøretidsversjon. Vi vil bruke denne informasjonen til å prioritere funksjoner og feilrettinger.", "Automatic": "Automatisk", "Actions": "Handlinger", "DeleteBackupMessageText": "Er du sikker på at du vil slette formattaggen {0}?", @@ -56,9 +56,69 @@ "RemoveSelectedMessageText": "Er du sikker på at du vil fjerne de valgte elementene fra svartelisten?", "ResetAPIKeyMessageText": "Er du sikker på at du vil tilbakestille API -nøkkelen din?", "ShowQualityProfile": "Legg til kvalitetsprofil", - "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Gren som skal brukes til å oppdatere Radarr", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Gren som skal brukes til å oppdatere Lidarr", "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Gren brukt av ekstern oppdateringsmekanisme", "APIKey": "API Nøkkel", "About": "Om", - "AlreadyInYourLibrary": "Allerede i biblioteket ditt" + "AlreadyInYourLibrary": "Allerede i biblioteket ditt", + "QualityProfiles": "Kvaltietsprofil", + "Refresh": "Oppdater", + "Season": "Sesong", + "Language": "språk", + "Password": "Passord", + "Queue": "Kø", + "RootFolder": "Rotmappe", + "Scheduled": "Planlagt", + "Search": "Søk", + "Torrents": "Torrents", + "Delete": "Slett", + "DownloadClients": "Nedlastingsklient", + "Files": "Fil", + "Host": "Vert", + "ICalFeed": "iCal Feed", + "ICalLink": "iCal Link", + "Indexers": "Indeksere", + "IsCutoffCutoff": "Avskjæring", + "Label": "Merke", + "LocalPath": "Lokal sti", + "MetadataProfile": "metadataprofil", + "MetadataProfiles": "metadataprofil", + "New": "Ny", + "RecyclingBin": "Papirkurv", + "URLBase": "URL Base", + "Username": "Brukernavn", + "ClickToChangeQuality": "Klikk for å endre kvalitet", + "ClientPriority": "Klientprioritet", + "CloneIndexer": "Klon indekser", + "CloneProfile": "Klon profil", + "Columns": "Kolonner", + "CompletedDownloadHandling": "Fullført nedlastingshåndtering", + "Component": "Komponent", + "Connections": "Tilkoblinger", + "ConnectSettings": "Tilkoblingsinnstillinger", + "Port": "Port", + "RootFolders": "Rotmappe", + "Settings": "Innstillinger", + "Hostname": "Vertsnavn", + "IconTooltip": "Planlagt", + "UpdateMechanismHelpText": "Bruk Prowlarrs innebygde oppdaterer eller et skript", + "Updates": "Oppdater", + "Usenet": "Usenet", + "Enable": "Aktiver", + "AlternateTitleslength1Title": "Tittel", + "AlternateTitleslength1Titles": "Tittel", + "CopyUsingHardlinksHelpText": "Bruk harde lenker ved forsøk på å kopiere filer fra torrents som fortsatt blir delt", + "CopyUsingHardlinksHelpTextWarning": "I blant kan låste filer forhindre å endre navn på filer som blir delt. Du kan midlertidig deaktivere deling og bruke Raddars navnefunksjon for å jobbe rundt dette.", + "DelayProfile": "Utsetningsprofil", + "DelayProfiles": "Utsetningsprofil", + "DownloadClient": "Nedlastingsklient", + "Grab": "Hent", + "Indexer": "Indekser", + "Protocol": "Protokoll", + "Quality": "kvalitet", + "QualityProfile": "Kvaltietsprofil", + "Reload": "Likemenn", + "RemotePathMappings": "Ekstern portmapping", + "Remove": "Slett", + "Term": "Periode" } diff --git a/src/NzbDrone.Core/Localization/Core/nl.json b/src/NzbDrone.Core/Localization/Core/nl.json index 235200317..cfe1cf9a0 100644 --- a/src/NzbDrone.Core/Localization/Core/nl.json +++ b/src/NzbDrone.Core/Localization/Core/nl.json @@ -16,7 +16,7 @@ "Backups": "Veiligheidskopieën", "BindAddressHelpText": "Geldig IPv4 adres of '*' voor alle interfaces", "BindAddressHelpTextWarning": "Herstarten vereist om in werking te treden", - "BlocklistHelpText": "Voorkom dat Radarr deze release nogmaals automatisch ophaalt", + "BlocklistHelpText": "Voorkom dat Lidarr deze release nogmaals automatisch ophaalt", "Branch": "Branch", "BypassProxyForLocalAddresses": "Omzeil Proxy voor Lokale Adressen", "Calendar": "Kalender", @@ -29,9 +29,9 @@ "ChangeHasNotBeenSavedYet": "Wijziging is nog niet opgeslagen", "ChmodFolder": "chmod Map", "ChmodFolderHelpText": "Octaal, toegepast tijdens importeren/hernoemen op media mappen en bestanden (zonder uitvoeringsbits)", - "ChmodFolderHelpTextWarning": "Dit werkt alleen als de gebruiker die Radarr draait de eigenaar is van het bestand. Het is beter om zeker te zijn dat de downloader de juiste rechten zet.", + "ChmodFolderHelpTextWarning": "Dit werkt alleen als de gebruiker die Lidarr draait de eigenaar is van het bestand. Het is beter om zeker te zijn dat de downloader de juiste rechten zet.", "ChownGroupHelpText": "Groep naam of gid. Gebruik gid voor externe bestandssystemen.", - "ChownGroupHelpTextWarning": "Dit werkt alleen als de gebruiker die Radarr draait de eigenaar is van het bestand. Het is beter om zeker te zijn dat de downloader dezelfde groep gebruikt als Radarr.", + "ChownGroupHelpTextWarning": "Dit werkt alleen als de gebruiker die Lidarr draait de eigenaar is van het bestand. Het is beter om zeker te zijn dat de downloader dezelfde groep gebruikt als Lidarr.", "Clear": "Vrij", "ClickToChangeQuality": "Klik om kwaliteit te wijzigen", "CompletedDownloadHandling": "Voltooide Download Afhandeling", @@ -39,7 +39,7 @@ "Connections": "Connecties", "ConnectSettings": "Connecties Instellingen", "CopyUsingHardlinksHelpText": "Gebruik hardlinks bij het kopiëren van torrent bestanden die nog actief zijn", - "CopyUsingHardlinksHelpTextWarning": "Sporadisch kan bestandsvergrendeling het hernoemen van in gebruik zijnde bestanden blokkeren. Als noodoplossing kunt u de hernoem functie van Radarr gebruiken na het opheffen van de bestandsvergrendeling.", + "CopyUsingHardlinksHelpTextWarning": "Sporadisch kan bestandsvergrendeling het hernoemen van in gebruik zijnde bestanden blokkeren. Als noodoplossing kunt u de hernoem functie van Lidarr gebruiken na het opheffen van de bestandsvergrendeling.", "CreateEmptyArtistFolders": "Lege film mappen aanmaken", "DelayProfiles": "Vertragingsprofielen", "Delete": "Verwijderen", @@ -101,7 +101,7 @@ "Grab": "Ophalen", "GrabID": "ID Ophalen", "GrabRelease": "Uitgave Ophalen", - "GrabReleaseMessageText": "Radarr was niet in staat om deze uitgave aan een film te koppelen. Radarr zal waarschijnlijk deze uitgave niet automatisch kunnen importeren. Wilt u '{0}' ophalen?", + "GrabReleaseMessageText": "Lidarr was niet in staat om deze uitgave aan een film te koppelen. Lidarr zal waarschijnlijk deze uitgave niet automatisch kunnen importeren. Wilt u '{0}' ophalen?", "Group": "Groep", "HasPendingChangesNoChanges": "Geen Wijzigingen", "HasPendingChangesSaveChanges": "Wijzigingen Opslaan", @@ -124,7 +124,7 @@ "ImportFailedInterp": "Importeren mislukt: {0}", "Importing": "Importeren", "IncludeHealthWarningsHelpText": "Voeg Gezondheidswaarschuwingen Toe", - "IncludeUnknownArtistItemsHelpText": "Toon items zonder een film in de wachtrij, dit kan verwijderde films, TV series of iets anders in Radarr zijn categorie omvatten", + "IncludeUnknownArtistItemsHelpText": "Toon items zonder een film in de wachtrij, dit kan verwijderde films, TV series of iets anders in Lidarr zijn categorie omvatten", "IncludeUnmonitored": "Voeg Onbewaakte Toe", "Indexer": "Indexeerder", "IndexerPriority": "Indexeerder Prioriteit", @@ -208,21 +208,21 @@ "RemoveSelectedMessageText": "Ben je zeker dat je de geselecteerde items wil verwijderen van de uitzonderingslijst?", "RemoveTagExistingTag": "Bestaande tag", "RemoveTagRemovingTag": "Tag verwijderen", - "RenameTracksHelpText": "Radarr zal de bestaande bestandsnaam gebruiken als hernoemen uitgeschakeld is", + "RenameTracksHelpText": "Lidarr zal de bestaande bestandsnaam gebruiken als hernoemen uitgeschakeld is", "Reorder": "Herordenen", "ReplaceIllegalCharacters": "Vervang Illegale Karakters", - "ReplaceIllegalCharactersHelpText": "Vervang illegale karakters. Indien niet aangevinkt, zal Radarr ze in de plaats daarvan verwijderen", + "ReplaceIllegalCharactersHelpText": "Vervang illegale karakters. Indien niet aangevinkt, zal Lidarr ze in de plaats daarvan verwijderen", "RequiredHelpText": "De release moet tenminste één van deze termen bevatten (hoofdletter gevoelig)", "RequiredPlaceHolder": "Voeg nieuwe beperking toe", "RequiresRestartToTakeEffect": "Herstarten vereist om in werking te treden", "RescanAfterRefreshHelpText": "De film map herscannen na het vernieuwen van de film", - "RescanAfterRefreshHelpTextWarning": "Radarr zal niet automatisch aanpassingen aan bestanden detecteren als dit niet op 'Altijd' staat", + "RescanAfterRefreshHelpTextWarning": "Lidarr zal niet automatisch aanpassingen aan bestanden detecteren als dit niet op 'Altijd' staat", "RescanArtistFolderAfterRefresh": "De film map herscannen na vernieuwen", "Reset": "Reset", "ResetAPIKey": "Reset API-sleutel", "ResetAPIKeyMessageText": "Bent u zeker dat u uw API-sleutel wilt resetten?", "Restart": "Herstart", - "RestartLidarr": "Herstart Radarr", + "RestartLidarr": "Herstart Lidarr", "RestartNow": "Herstart Nu", "Restore": "Herstellen", "RestoreBackup": "Veiligheidskopie Herstellen", @@ -262,7 +262,7 @@ "SuccessMyWorkIsDoneNoFilesToRetag": "Success! Mijn werk zit erop, geen bestanden te hernoemen.", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS wordt niet ondersteund door deze indexeerder", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Zoeken wordt niet ondersteund door deze indexeerder", - "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Zal worden gebruikt wanneer automatische zoekopdrachten worden uitgevoerd via de gebruikersinterface of door Radarr", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Zal worden gebruikt wanneer automatische zoekopdrachten worden uitgevoerd via de gebruikersinterface of door Lidarr", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Zal worden gebruikt wanneer interactief zoeken wordt gebruikt", "TagIsNotUsedAndCanBeDeleted": "Tag is niet in gebruik en kan verwijderd worden", "Tags": "Tags", @@ -329,7 +329,7 @@ "AlternateTitleslength1Title": "Titel", "AlternateTitleslength1Titles": "Titels", "Analytics": "Statistieken", - "AnalyticsEnabledHelpText": "Stuur anonieme gebruiks- en foutinformatie naar de servers van Radarr. Dit omvat informatie over uw browser, welke Radarr WebUI pagina's u gebruikt, foutrapportage en OS en runtime versie. We zullen deze informatie gebruiken om prioriteiten te stellen voor functies en het verhelpen van fouten.", + "AnalyticsEnabledHelpText": "Stuur anonieme gebruiks- en foutinformatie naar de servers van Lidarr. Dit omvat informatie over uw browser, welke Lidarr WebUI pagina's u gebruikt, foutrapportage en OS en runtime versie. We zullen deze informatie gebruiken om prioriteiten te stellen voor functies en het verhelpen van fouten.", "AnalyticsEnabledHelpTextWarning": "Herstarten vereist om in werking te treden", "Automatic": "Automatisch", "DelayingDownloadUntilInterp": "Vertraag download tot {0} op {1}", @@ -354,7 +354,7 @@ "ShowMonitoredHelpText": "Toon bewakingsstatus onder de poster", "Size": " Grootte", "SkipFreeSpaceCheck": "Vrije schijfruimte controle overslaan", - "SkipFreeSpaceCheckWhenImportingHelpText": "Gebruik dit wanneer Radarr geen vrije schijfruimte kan detecteren voor de hoofdmap van je films", + "SkipFreeSpaceCheckWhenImportingHelpText": "Gebruik dit wanneer Lidarr geen vrije schijfruimte kan detecteren voor de hoofdmap van je films", "SorryThatAlbumCannotBeFound": "Sorry, deze film kan niet worden gevonden.", "SorryThatArtistCannotBeFound": "Sorry, deze film kan niet worden gevonden.", "Source": "Bron", @@ -365,16 +365,16 @@ "AddingTag": "Tag toevoegen", "ApiKeyHelpTextWarning": "Herstarten vereist om in werking te treden", "Authentication": "Authenticatie", - "AuthenticationMethodHelpText": "Gebruikersnaam en wachtwoord nodig voor toegang tot Radarr", + "AuthenticationMethodHelpText": "Gebruikersnaam en wachtwoord nodig voor toegang tot Lidarr", "AutoRedownloadFailedHelpText": "Automatisch zoeken en probeer een andere release te downloaden", - "BackupFolderHelpText": "Relatieve paden zullen t.o.v. de Radarr AppData map bekeken worden", + "BackupFolderHelpText": "Relatieve paden zullen t.o.v. de Lidarr AppData map bekeken worden", "ClientPriority": "Client Prioriteit", "CloneIndexer": "Dupliceer Indexeerder", "CloneProfile": "Dupliceer Profiel", "Columns": "Kolommen", "CreateEmptyArtistFoldersHelpText": "Film mappen aanmaken voor ontbrekende films tijdens schijfscan", "CreateGroup": "Groep aanmaken", - "CutoffHelpText": "Wanneer deze kwaliteit is behaald, zal Radarr niet langer films downloaden", + "CutoffHelpText": "Wanneer deze kwaliteit is behaald, zal Lidarr niet langer films downloaden", "CutoffUnmet": "Onbereikte Drempel", "Dates": "Datum en tijd", "DBMigration": "DB Migratie", @@ -395,15 +395,15 @@ "DownloadClientSettings": "Downloader Instellingen", "GrabSelected": "Selectie Ophalen", "IsTagUsedCannotBeDeletedWhileInUse": "Kan niet verwijderd worden terwijl in gebruik", - "LaunchBrowserHelpText": " Open een web browser en navigeer naar de Radarr startpagina bij het starten van de app.", + "LaunchBrowserHelpText": " Open een web browser en navigeer naar de Lidarr startpagina bij het starten van de app.", "Level": "Niveau", - "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Radarr ondersteund elke downloader die gebruik maakt van de Newznab standaard, tevens ook de ander hieronder weergegeven downloaders.", - "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr ondersteund elke indexeerder die gebruik maakt van de Newznab standaard, tevens ook de ander hieronder weergegeven indexeerders.", - "LidarrTags": "Radarr Tags", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr ondersteund elke downloader die gebruik maakt van de Newznab standaard, tevens ook de ander hieronder weergegeven downloaders.", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr ondersteund elke indexeerder die gebruik maakt van de Newznab standaard, tevens ook de ander hieronder weergegeven indexeerders.", + "LidarrTags": "Lidarr Tags", "LoadingTrackFilesFailed": "Laden van filmbestanden is mislukt", "Local": "Lokaal", "LocalPath": "Lokaal Pad", - "LocalPathHelpText": "Het pad dat Radarr lokaal moet gebruiken om toegang te krijgen tot het externe pad", + "LocalPathHelpText": "Het pad dat Lidarr lokaal moet gebruiken om toegang te krijgen tot het externe pad", "LogFiles": "Logbestanden", "Logging": "Logbeheer", "LogLevel": "Log Niveau", @@ -443,7 +443,7 @@ "Path": "Pad", "Time": "Tijd", "Type": "Type", - "UILanguageHelpText": "Taal die Radarr zal gebruiken voor de gebruikersinterface", + "UILanguageHelpText": "Taal die Lidarr zal gebruiken voor de gebruikersinterface", "UnableToLoadIndexers": "Indexeerders kunnen niet worden geladen", "UnableToLoadUISettings": "Kon gebruikersinterface instellingen niet inladen", "Ungroup": "Degroeperen", @@ -463,7 +463,7 @@ "UseProxy": "Gebruik Proxy", "UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent geleverd door de app die de API heeft aangeroepen", "Username": "Gebruikersnaam", - "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Te gebruiken branch om Radarr bij te werken", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Te gebruiken branch om Lidarr bij te werken", "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Gebruikte branch door extern update mechanisme", "Version": "Versie", "WeekColumnHeader": "Week Kolom Koptekst", @@ -476,5 +476,13 @@ "AddMissing": "Voeg ontbrekende toe", "OnApplicationUpdateHelpText": "Bij applicatie update", "Duration": "Duur", - "OnApplicationUpdate": "Bij applicatie update" + "OnApplicationUpdate": "Bij applicatie update", + "OnHealthIssue": "Bij Gezondheidsprobleem", + "OnRename": "Bij Hernoemen", + "OnUpgrade": "Bij Opwaarderen", + "ThisCannotBeCancelled": "Eenmaal gestart kan dit niet worden geannuleerd zonder Whisparr opnieuw te starten.", + "Tracks": "Spoor", + "OnGrab": "Bij Ophalen", + "MonoVersion": "Mono Versie", + "AddedArtistSettings": "Auteur instellingen toegevoegd" } diff --git a/src/NzbDrone.Core/Localization/Core/pl.json b/src/NzbDrone.Core/Localization/Core/pl.json index 064430788..29902582a 100644 --- a/src/NzbDrone.Core/Localization/Core/pl.json +++ b/src/NzbDrone.Core/Localization/Core/pl.json @@ -2,9 +2,9 @@ "Language": "Język", "UILanguage": "Język interfejsu użytkownika", "Authentication": "Poświadczenie", - "AuthenticationMethodHelpText": "Wymagaj nazwy użytkownika i hasła, aby uzyskać dostęp do Radarr", + "AuthenticationMethodHelpText": "Wymagaj nazwy użytkownika i hasła, aby uzyskać dostęp do Lidarr", "AutoRedownloadFailedHelpText": "Automatycznie wyszukuj i próbuj pobrać inną wersję", - "BackupFolderHelpText": "Względne ścieżki będą znajdować się w katalogu AppData Radarr", + "BackupFolderHelpText": "Względne ścieżki będą znajdować się w katalogu AppData Lidarr", "BackupNow": "Zrób kopię zapasową teraz", "BackupRetentionHelpText": "Automatyczne kopie zapasowe starsze niż okres przechowywania zostaną automatycznie wyczyszczone", "Backups": "Kopie zapasowe", @@ -18,9 +18,9 @@ "Calendar": "Kalendarz", "ChmodFolder": "chmod Folder", "ChmodFolderHelpText": "Ósemkowy, stosowany podczas importu / zmiany nazwy do folderów multimedialnych i plików (bez bitów wykonania)", - "ChmodFolderHelpTextWarning": "Działa to tylko wtedy, gdy użytkownik uruchamiający Radarr jest właścicielem pliku. Lepiej jest upewnić się, że klient pobierania prawidłowo ustawia uprawnienia.", + "ChmodFolderHelpTextWarning": "Działa to tylko wtedy, gdy użytkownik uruchamiający Lidarr jest właścicielem pliku. Lepiej jest upewnić się, że klient pobierania prawidłowo ustawia uprawnienia.", "ChownGroupHelpText": "Nazwa grupy lub identyfikator. Użyj gid dla zdalnych systemów plików.", - "ChownGroupHelpTextWarning": "Działa to tylko wtedy, gdy użytkownik uruchamiający Radarr jest właścicielem pliku. Lepiej jest upewnić się, że klient pobierania używa tej samej grupy co Radarr.", + "ChownGroupHelpTextWarning": "Działa to tylko wtedy, gdy użytkownik uruchamiający Lidarr jest właścicielem pliku. Lepiej jest upewnić się, że klient pobierania używa tej samej grupy co Lidarr.", "Clear": "Wyczyść", "ClickToChangeQuality": "Kliknij, aby zmienić jakość", "ClientPriority": "Priorytet klienta", @@ -32,11 +32,11 @@ "Connections": "Znajomości", "ConnectSettings": "Ustawienia połączenia", "CopyUsingHardlinksHelpText": "Użyj twardych linków podczas próby kopiowania plików z torrentów, które wciąż są seedowane", - "CopyUsingHardlinksHelpTextWarning": "Czasami blokady plików mogą uniemożliwić zmianę nazw plików, które są wysyłane. Możesz tymczasowo wyłączyć wysyłanie i użyć funkcji zmiany nazwy Radarr jako obejścia.", + "CopyUsingHardlinksHelpTextWarning": "Czasami blokady plików mogą uniemożliwić zmianę nazw plików, które są wysyłane. Możesz tymczasowo wyłączyć wysyłanie i użyć funkcji zmiany nazwy Lidarr jako obejścia.", "CreateEmptyArtistFolders": "Utwórz puste foldery z filmami", "CreateEmptyArtistFoldersHelpText": "Utwórz brakujące foldery z filmami podczas skanowania dysku", "CreateGroup": "Stworzyć grupę", - "CutoffHelpText": "Po osiągnięciu tej jakości Radarr nie będzie już pobierał filmów", + "CutoffHelpText": "Po osiągnięciu tej jakości Lidarr nie będzie już pobierał filmów", "CutoffUnmet": "Odcięcie niespełnione", "Dates": "Daktyle", "DBMigration": "Migracja bazy danych", @@ -103,10 +103,10 @@ "Interval": "Interwał", "IsCutoffCutoff": "Odciąć", "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Uaktualnij, dopóki ta jakość nie zostanie osiągnięta lub przekroczona", - "LaunchBrowserHelpText": " Otwórz przeglądarkę internetową i przejdź do strony głównej Radarr po uruchomieniu aplikacji.", + "LaunchBrowserHelpText": " Otwórz przeglądarkę internetową i przejdź do strony głównej Lidarr po uruchomieniu aplikacji.", "Level": "Poziom", - "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Radarr obsługuje każdego klienta pobierania, który używa standardu Newznab, a także innych klientów pobierania wymienionych poniżej.", - "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr obsługuje każdy indeksator, który używa standardu Newznab, a także inne indeksatory wymienione poniżej.", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr obsługuje każdego klienta pobierania, który używa standardu Newznab, a także innych klientów pobierania wymienionych poniżej.", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr obsługuje każdy indeksator, który używa standardu Newznab, a także inne indeksatory wymienione poniżej.", "LidarrTags": "Tagi radarowe", "LoadingTrackFilesFailed": "Ładowanie plików filmowych nie powiodło się", "Local": "Lokalny", @@ -162,7 +162,6 @@ "QualityProfiles": "Profile jakości", "QualitySettings": "Ustawienia jakości", "Queue": "Kolejka", - "Radarr": "Radarr", "RecycleBinHelpText": "Pliki filmowe trafią tutaj po usunięciu, a nie na stałe", "RecyclingBinCleanup": "Czyszczenie kosza na śmieci", "Redownload": "Pobierz ponownie", @@ -218,7 +217,7 @@ "UnmonitoredHelpText": "Dołącz niemonitorowane filmy do źródła iCal", "UpdateAll": "Aktualizuj wszystko", "UpdateAutomaticallyHelpText": "Automatycznie pobieraj i instaluj aktualizacje. Nadal będziesz mógł zainstalować z System: Updates", - "UpdateMechanismHelpText": "Użyj wbudowanego aktualizatora Radarr lub skryptu", + "UpdateMechanismHelpText": "Użyj wbudowanego aktualizatora Lidarr lub skryptu", "Updates": "Aktualizacje", "UpdateScriptPathHelpText": "Ścieżka do niestandardowego skryptu, który pobiera wyodrębniony pakiet aktualizacji i obsługuje pozostałą część procesu aktualizacji", "UpgradeAllowedHelpText": "Jeśli niepełnosprawne cechy nie zostaną ulepszone", @@ -230,7 +229,7 @@ "UsenetDelayHelpText": "Opóźnij w ciągu kilku minut, aby poczekać przed pobraniem wersji z Usenetu", "UseProxy": "Użyj proxy", "Username": "Nazwa Użytkownika", - "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Oddział do użycia do aktualizacji Radarr", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Oddział do użycia do aktualizacji Lidarr", "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Gałąź używana przez zewnętrzny mechanizm aktualizacji", "Version": "Wersja", "WeekColumnHeader": "Nagłówek kolumny tygodnia", @@ -250,7 +249,7 @@ "AlternateTitleslength1Title": "Tytuł", "AlternateTitleslength1Titles": "Tytuły", "Analytics": "Analityka", - "AnalyticsEnabledHelpText": "Wysyłaj anonimowe informacje o użytkowaniu i błędach do serwerów Radarr. Obejmuje to informacje o Twojej przeglądarce, z których stron Radarr WebUI używasz, raportowanie błędów, a także wersję systemu operacyjnego i środowiska wykonawczego. Wykorzystamy te informacje, aby nadać priorytet funkcjom i poprawkom błędów.", + "AnalyticsEnabledHelpText": "Wysyłaj anonimowe informacje o użytkowaniu i błędach do serwerów Lidarr. Obejmuje to informacje o Twojej przeglądarce, z których stron Lidarr WebUI używasz, raportowanie błędów, a także wersję systemu operacyjnego i środowiska wykonawczego. Wykorzystamy te informacje, aby nadać priorytet funkcjom i poprawkom błędów.", "EnableInteractiveSearch": "Włącz wyszukiwanie interaktywne", "AnalyticsEnabledHelpTextWarning": "Wymaga ponownego uruchomienia, aby odniosło skutek", "AppDataDirectory": "Katalog AppData", @@ -275,7 +274,7 @@ "ShowMonitored": "Pokaż monitorowane", "ShowMonitoredHelpText": "Pokaż monitorowany status pod plakatem", "SkipFreeSpaceCheck": "Pomiń sprawdzanie wolnego miejsca", - "SkipFreeSpaceCheckWhenImportingHelpText": "Użyj, gdy Radarr nie może wykryć wolnego miejsca w folderze głównym filmu", + "SkipFreeSpaceCheckWhenImportingHelpText": "Użyj, gdy Lidarr nie może wykryć wolnego miejsca w folderze głównym filmu", "SorryThatAlbumCannotBeFound": "Przepraszamy, nie można znaleźć tego filmu.", "SorryThatArtistCannotBeFound": "Przepraszamy, nie można znaleźć tego filmu.", "SourcePath": "Ścieżka źródłowa", @@ -330,7 +329,7 @@ "Grab": "Chwycić", "GrabID": "Grab ID", "GrabRelease": "Grab Release", - "GrabReleaseMessageText": "Radarr nie był w stanie określić, dla którego filmu jest to wydanie. Radarr może nie być w stanie automatycznie zaimportować tej wersji. Czy chcesz złapać „{0}”?", + "GrabReleaseMessageText": "Lidarr nie był w stanie określić, dla którego filmu jest to wydanie. Lidarr może nie być w stanie automatycznie zaimportować tej wersji. Czy chcesz złapać „{0}”?", "GrabSelected": "Wybierz wybrane", "Group": "Grupa", "HasPendingChangesNoChanges": "Bez zmian", @@ -338,10 +337,10 @@ "History": "Historia", "HostHelpText": "Ten sam host, który podałeś dla zdalnego klienta pobierania", "IncludeHealthWarningsHelpText": "Uwzględnij ostrzeżenia zdrowotne", - "IncludeUnknownArtistItemsHelpText": "Pokaż elementy bez filmu w kolejce. Może to obejmować usunięte filmy lub cokolwiek innego w kategorii Radarr", + "IncludeUnknownArtistItemsHelpText": "Pokaż elementy bez filmu w kolejce. Może to obejmować usunięte filmy lub cokolwiek innego w kategorii Lidarr", "IsTagUsedCannotBeDeletedWhileInUse": "Nie można usunąć, gdy jest używany", "LocalPath": "Ścieżka lokalna", - "LocalPathHelpText": "Ścieżka, której Radarr powinien używać, aby uzyskać lokalny dostęp do ścieżki zdalnej", + "LocalPathHelpText": "Ścieżka, której Lidarr powinien używać, aby uzyskać lokalny dostęp do ścieżki zdalnej", "LogFiles": "Pliki dziennika", "Logging": "Logowanie", "LogLevel": "Poziom dziennika", @@ -388,21 +387,21 @@ "RemoveFailedDownloadsHelpText": "Usuń nieudane pobieranie z historii klienta pobierania", "RemoveFilter": "Usuń filtr", "RemoveFromBlocklist": "Usuń z czarnej listy", - "RenameTracksHelpText": "Radarr użyje istniejącej nazwy pliku, jeśli zmiana nazwy jest wyłączona", + "RenameTracksHelpText": "Lidarr użyje istniejącej nazwy pliku, jeśli zmiana nazwy jest wyłączona", "Reorder": "Zmień kolejność", "ReplaceIllegalCharacters": "Zastąp niedozwolone znaki", - "ReplaceIllegalCharactersHelpText": "Zastąp niedozwolone znaki. Jeśli odznaczone, Radarr usunie je zamiast tego", + "ReplaceIllegalCharactersHelpText": "Zastąp niedozwolone znaki. Jeśli odznaczone, Lidarr usunie je zamiast tego", "RequiredHelpText": "Informacja musi zawierać co najmniej jeden z tych terminów (bez rozróżniania wielkości liter)", "RequiredPlaceHolder": "Dodaj nowe ograniczenie", "RequiresRestartToTakeEffect": "Wymaga ponownego uruchomienia, aby odniosło skutek", "RescanAfterRefreshHelpText": "Ponownie przeskanuj folder z filmem po odświeżeniu filmu", - "RescanAfterRefreshHelpTextWarning": "Radarr nie wykryje automatycznie zmian w plikach, jeśli nie jest ustawiony na „Zawsze”", + "RescanAfterRefreshHelpTextWarning": "Lidarr nie wykryje automatycznie zmian w plikach, jeśli nie jest ustawiony na „Zawsze”", "RescanArtistFolderAfterRefresh": "Przeskanuj ponownie folder filmowy po odświeżeniu", "Reset": "Resetowanie", "ResetAPIKey": "Zresetuj klucz API", "ResetAPIKeyMessageText": "Czy na pewno chcesz zresetować swój klucz API?", "Restart": "Uruchom ponownie", - "RestartLidarr": "Zrestartuj Radarr", + "RestartLidarr": "Zrestartuj Lidarr", "RestartNow": "Zrestartuj teraz", "Restore": "Przywracać", "RestoreBackup": "Przywracania kopii zapasowej", @@ -429,7 +428,7 @@ "SuccessMyWorkIsDoneNoFilesToRetag": "Powodzenie! Moja praca jest skończona, brak plików do zmiany nazwy.", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS nie jest obsługiwany przez ten indeksator", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Wyszukiwanie nie jest obsługiwane przez ten indeksator", - "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Będzie używany, gdy automatyczne wyszukiwania są wykonywane przez interfejs użytkownika lub przez Radarr", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Będzie używany, gdy automatyczne wyszukiwania są wykonywane przez interfejs użytkownika lub przez Lidarr", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Będzie używany, gdy używane jest wyszukiwanie interaktywne", "TestAll": "Testuj wszystko", "TestAllClients": "Przetestuj wszystkich klientów", @@ -444,7 +443,7 @@ "TotalFileSize": "Całkowity rozmiar pliku", "Track": "Ślad", "Type": "Rodzaj", - "UILanguageHelpText": "Język, którego Radarr będzie używać w interfejsie użytkownika", + "UILanguageHelpText": "Język, którego Lidarr będzie używać w interfejsie użytkownika", "UILanguageHelpTextWarning": "Wymagane przeładowanie przeglądarki", "UISettings": "Ustawienia interfejsu użytkownika", "UnableToAddANewDownloadClientPleaseTryAgain": "Nie można dodać nowego klienta pobierania, spróbuj ponownie.", @@ -466,5 +465,21 @@ "About": "O", "Automatic": "Automatyczny", "Size": " Rozmiar", - "Source": "Źródło" + "Source": "Źródło", + "OnRename": "Zmiana nazwy", + "OnUpgrade": "Przy aktualizacji", + "ThisCannotBeCancelled": "Nie można tego anulować po uruchomieniu bez ponownego uruchamiania Whisparr.", + "NETCore": ".NET Core", + "OnGrab": "Na Grab", + "OnHealthIssue": "W kwestii zdrowia", + "Tracks": "Ślad", + "MonoVersion": "Wersja mono", + "RemoveCompleted": "Usuń zakończone", + "RemoveDownloadsAlert": "Ustawienia usuwania zostały przeniesione do ustawień poszczególnych klientów pobierania powyżej.", + "RemoveFailed": "Usuń nieudane", + "OnApplicationUpdate": "Przy aktualizacji aplikacji", + "OnApplicationUpdateHelpText": "Przy aktualizacji aplikacji", + "Duration": "Czas trwania", + "BlocklistHelpText": "Zapobiega ponownemu pobraniu tej wersji przez Lidarr", + "UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent podawany przez aplikację wywołującą API" } diff --git a/src/NzbDrone.Core/Localization/Core/pt.json b/src/NzbDrone.Core/Localization/Core/pt.json index b0022d38b..ef8602640 100644 --- a/src/NzbDrone.Core/Localization/Core/pt.json +++ b/src/NzbDrone.Core/Localization/Core/pt.json @@ -37,5 +37,512 @@ "Docker": "Docker", "ProxyPasswordHelpText": "Apenas insira o utilizador e a palavra-passe caso seja requerido. Caso contrário, deixe em branco.", "ProxyUsernameHelpText": "Apenas insira o utilizador e a palavra-passe caso seja requerido. Caso contrário, deixe em branco.", - "RemotePath": "Caminho Remoto" + "RemotePath": "Caminho Remoto", + "ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Para obter mais informações sobre cada cliente de transferências, clique nos botões de mais informação.", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "O Lidarr suporta muitos dos clientes de transferências torrent e usenet mais populares.", + "Tasks": "Tarefas", + "MaximumSizeHelpText": "Tamanho máximo de uma versão a capturar, em MB. Defina como zero para Ilimitado", + "Message": "Mensagem", + "MIA": "Desaparecidos", + "MinimumAge": "Tempo de vida mínimo", + "MinimumFreeSpace": "Espaço livre mínimo", + "Monitored": "Monitorado", + "MonitoringOptions": "Opções de monitorização", + "MoreInfo": "Mais informações", + "Name": "Nome", + "NamingSettings": "Definições de nomenclatura", + "New": "Novo", + "NoBackupsAreAvailable": "Não há cópias de segurança disponíveis", + "NoHistory": "Sem histórico.", + "NoLeaveIt": "Não, deixe-o", + "NoLimitForAnyRuntime": "Sem limite de tempo de execução", + "NoLogFiles": "Sem ficheiros de log", + "NoMinimumForAnyRuntime": "Sem mínimo para tempo de execução", + "None": "Nenhum", + "OnUpgrade": "Ao atualizar", + "OnUpgradeHelpText": "Ao atualizar", + "PageSizeHelpText": "Número de itens por página", + "PastDays": "Dias anteriores", + "PastDaysHelpText": "Dias anteriores a exibir no feed do iCal", + "Path": "Caminho", + "Port": "Porta", + "PortNumber": "Número da porta", + "PosterSize": "Tamanho do cartaz", + "Preferred": "Preferido", + "PreferredHelpTexts1": "A versão terá preferência de acordo com a pontuação de cada termo (não faz distinção entre maiúsculas e minúsculas)", + "PreviewRename": "Pré-visualizar renomeação", + "PreviewRetag": "Pré-visualizar nova etiqueta", + "PriorityHelpText": "Prioridade do indexador de 1 (mais alta) a 50 (mais baixa). Padrão: 25.", + "Profiles": "Perfis", + "Proper": "Proper", + "PropersAndRepacks": "Propers e Repacks", + "Protocol": "Protocolo", + "ProtocolHelpText": "Escolha que protocolo(s) utilizar e qual o preferido ao escolher entre versões iguais", + "RecyclingBin": "Reciclagem", + "RemovedFromTaskQueue": "Eliminado da fila de tarefas", + "RemoveFailedDownloadsHelpText": "Remover transferências falhadas do histórico do cliente de transferências", + "RemoveFromBlocklist": "Remover da lista de bloqueio", + "RemoveFromDownloadClient": "Remover do cliente de transferências", + "RemoveFromQueue": "Remover da fila", + "RemoveHelpTextWarning": "Remover eliminará a transferência e o(s) ficheiro(s) do cliente de transferências.", + "RemoveSelected": "Remover selecionado(s)", + "RemoveSelectedMessageText": "Tem a certeza que quer remover os itens selecionados da lista de bloqueio?", + "RemoveTagExistingTag": "Etiqueta existente", + "RemoveTagRemovingTag": "Eliminando etiqueta", + "RequiredHelpText": "A versão deve conter pelo menos um desses termos (não diferencia maiúsculas de minúsculas)", + "RequiredPlaceHolder": "Adicionar nova restrição", + "RequiresRestartToTakeEffect": "Requer reinício para aplicar alterações", + "Retention": "Retenção", + "RetentionHelpText": "Somente Usenet: defina como zero para retenção ilimitada", + "RetryingDownloadInterp": "Nova tentativa de transferência {0} em {1}", + "ScriptPath": "Caminho do script", + "ShowMonitoredHelpText": "Mostrar estado de monitorização abaixo do cartaz", + "ShowSearchActionHelpText": "Mostrar botão de pesquisa ao passar o cursor", + "ShowTitleHelpText": "Mostrar nome do autor abaixo do cartaz", + "SkipFreeSpaceCheckWhenImportingHelpText": "Usar quando o Lidarr não puder determinar o espaço livre em sua pasta raiz de filmes", + "SkipRedownload": "Ignorar nova transferência", + "SkipredownloadHelpText": "Impede que o Lidarr tente transferir versões alternativas para itens removidos", + "SorryThatAlbumCannotBeFound": "Desculpe, este filme não foi encontrado.", + "StandardTrackFormat": "Formato padrão de livro", + "StartTypingOrSelectAPathBelow": "Começa a digitar ou seleciona um caminho abaixo", + "StartupDirectory": "Diretório de arranque", + "Status": "Estado", + "StatusEndedContinuing": "Continuação", + "Style": "Estilo", + "SuccessMyWorkIsDoneNoFilesToRename": "Sucesso! Meu trabalho está feito, sem ficheiros para renomear.", + "SuccessMyWorkIsDoneNoFilesToRetag": "Sucesso! Meu trabalho está feito, não há novas etiquetas a adicionar a ficheiros.", + "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS não é suportado por esse indexador", + "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Este indexador não suporta pesquisas", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Será utilizado ao realizar pesquisas automáticas através da IU ou pelo Lidarr", + "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Será utilizado ao realizar uma pesquisa interativa", + "TagIsNotUsedAndCanBeDeleted": "A etiqueta não é utilizada e pode ser eliminada", + "Torrents": "Torrents", + "UnableToLoadDelayProfiles": "Não foi possível carregar os perfis de atraso", + "UnableToLoadDownloadClientOptions": "Não foi possível carregar as opções do cliente de transferências", + "UnableToLoadImportListExclusions": "Não foi possível carregar as exclusões de lista de importação", + "UnableToLoadIndexerOptions": "Não foi possível carregar as opções do indexador", + "UnableToLoadMediaManagementSettings": "Não foi possível carregar as definições da gestão de multimédia", + "UnableToLoadMetadata": "Não foi possível carregar os metadados", + "UnableToLoadMetadataProfiles": "Não foi possível carregar os perfis de metadados", + "UnableToLoadMetadataProviderSettings": "Não foi possível carregar as definições do fornecedor de metadados", + "UnableToLoadNotifications": "Não foi possível carregar as notificações", + "UnableToLoadQualities": "Não foi possível carregar as qualidades", + "UnableToLoadQualityDefinitions": "Não foi possível carregar as definições de qualidade", + "UnableToLoadReleaseProfiles": "Não foi possível carregar os perfis de versão", + "UnableToLoadRootFolders": "Não foi possível carregar as pastas raiz", + "UnableToLoadTags": "Não foi possível carregar as etiquetas", + "UnableToLoadTheCalendar": "Não foi possível carregar o calendário", + "UnableToLoadUISettings": "Não foi possível carregar as definições da IU", + "Ungroup": "Desagrupar", + "UnmappedFiles": "Ficheiros não mapeados", + "Unmonitored": "Não monitorado", + "UpdateAutomaticallyHelpText": "Transferir e instalar automaticamente as atualizações. Ainda é possível instalar a partir de Sistema: Atualizações", + "UpdateMechanismHelpText": "Usar o atualizador do Lidarr ou um script", + "Updates": "Atualizações", + "UpdateScriptPathHelpText": "Caminho para um script personalizado que toma um pacote de atualização extraído e lida com o restante do processo da atualização", + "UpdatingIsDisabledInsideADockerContainerUpdateTheContainerImageInstead": "A atualização está desativada em um contentor do Docker. Atualizar a imagem do contentor em vez disso.", + "UpgradeAllowedHelpText": "Se desativada, as qualidades não serão atualizadas", + "Uptime": "Tempo de atividade", + "URLBase": "URL base", + "UrlBaseHelpText": "Para suporte a proxy inverso, vazio por padrão", + "UrlBaseHelpTextWarning": "Requer reinício para aplicar alterações", + "UseHardlinksInsteadOfCopy": "Usar ligações fixas em vez de copiar", + "Usenet": "Usenet", + "UsenetDelay": "Atraso para Usenet", + "UsenetDelayHelpText": "Tempo, em minutos, para aguardar antes de capturar uma versão de Usenet", + "UserAgentProvidedByTheAppThatCalledTheAPI": "Par Utilizador-Agente fornecido pela aplicação que chamou a API", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Ramificação utilizada para atualizar o Lidarr", + "OnHealthIssue": "Ao ter problemas no estado de funcionamento", + "OnHealthIssueHelpText": "Ao ter problemas no estado de funcionamento", + "OnImportFailure": "Ao ocorrer falha na importação", + "OnImportFailureHelpText": "Ao ocorrer falha na importação", + "OnReleaseImport": "Ao importar versão", + "OnReleaseImportHelpText": "Ao importar versão", + "OnRename": "Ao renomear", + "OnRenameHelpText": "Ao renomear", + "PackageVersion": "Versão do pacote", + "PageSize": "Tamanho da página", + "Redownload": "Transferir novamente", + "Refresh": "Atualizar", + "RefreshInformationAndScanDisk": "Atualizar informações e analisar o disco", + "RefreshScan": "Atualizar e analisar", + "RenameTracksHelpText": "O Lidarr utilizará o nome atual do ficheiro se a renomeação estiver desativada", + "Reorder": "Reordenar", + "ReplaceIllegalCharacters": "Substituir carateres ilegais", + "ReplaceIllegalCharactersHelpText": "Substituir carateres ilegais. Se desmarcada, o Lidarr os removerá", + "RescanAfterRefreshHelpText": "Analisar novamente a pasta do autor após atualizá-lo", + "RescanAfterRefreshHelpTextWarning": "O Lidarr não detectará automaticamente mudanças nos ficheiros quando a opção selecionada não for \"Sempre\"", + "RescanArtistFolderAfterRefresh": "Analisar novamente a pasta do filme após atualizar", + "ResetAPIKey": "Repor chave da API", + "ResetAPIKeyMessageText": "Tem a certeza que quer repor a Chave da API?", + "Restart": "Reiniciar", + "RestartLidarr": "Reiniciar o Lidarr", + "RestartNow": "Reiniciar agora", + "Restore": "Restaurar", + "RestoreBackup": "Restaurar cópia de segurança", + "Result": "Resultado", + "RootFolderPathHelpText": "Os itens da pasta raiz serão adicionados a", + "RootFolders": "Pastas raiz", + "RSSSync": "Sincronização RSS", + "RSSSyncInterval": "Intervalo de sincronização RSS", + "RssSyncIntervalHelpText": "Intervalo em minutos. Defina como zero para desativar (isso parará toda a captura automática)", + "Scheduled": "Agendado", + "SearchForAllMissingAlbums": "Pesquisar todos os livros ausentes", + "SearchSelected": "Pesquisar selecionado(s)", + "Season": "Temporada", + "Security": "Segurança", + "SelectedCountArtistsSelectedInterp": "{0} autor(es) selecionado(s)", + "SendAnonymousUsageData": "Enviar dados anônimos de uso", + "SetPermissions": "Definir permissões", + "SetPermissionsLinuxHelpText": "Deve-se executar chmod ao importar/renomear ficheiros?", + "SetPermissionsLinuxHelpTextWarning": "Se você não conhece essa definição, não a altere.", + "Settings": "Definições", + "ShowBanners": "Mostrar faixas", + "ShowBannersHelpText": "Mostrar faixas em vez de nomes", + "ShowCutoffUnmetIconHelpText": "Mostrar ícone para ficheiros quando o limite não tiver sido alcançado", + "ShowDateAdded": "Mostrar data de adição", + "ShownAboveEachColumnWhenWeekIsTheActiveView": "Mostrar acima de cada coluna quando a semana é a vista ativa", + "ShowPath": "Mostrar caminho", + "ShowQualityProfile": "Mostrar perfil de qualidade", + "ShowQualityProfileHelpText": "Mostrar perfil de qualidade abaixo do cartaz", + "ShowRelativeDates": "Mostrar datas relativas", + "ShowRelativeDatesHelpText": "Mostrar datas relativas (Hoje, Ontem, etc.) ou absolutas", + "SkipFreeSpaceCheck": "Pular verificação de espaço livre", + "Source": "Origem", + "SourcePath": "Caminho de origem", + "SslCertPasswordHelpText": "Palavra-passe do ficheiro PFX", + "SslCertPasswordHelpTextWarning": "Requer reinício para aplicar alterações", + "SslCertPathHelpText": "Caminho para o ficheiro PFX", + "SslCertPathHelpTextWarning": "Requer reinício para aplicar alterações", + "SSLPort": "Porta SSL", + "SslPortHelpTextWarning": "Requer reinício para aplicar alterações", + "TrackFileCounttotalTrackCountTracksDownloadedInterp": "{0}/{1} livros transferidos", + "UISettings": "Definições da IU", + "UnableToAddANewDownloadClientPleaseTryAgain": "Não foi possível adicionar um novo cliente de transferências, tenta novamente.", + "UnableToAddANewListPleaseTryAgain": "Não foi possível adicionar uma nova lista, tenta novamente.", + "UnableToLoadHistory": "Não foi possível carregar o histórico.", + "UnableToLoadLists": "Não foi possível carregar as listas", + "WatchLibraryForChangesHelpText": "Analisar automaticamente quando houver mudanças na pasta raiz", + "WatchRootFoldersForFileChanges": "Monitorizar mudanças nas pastas raiz", + "WeekColumnHeader": "Cabeçalho da coluna de semana", + "YesCancel": "Sim, cancelar", + "ApplyTagsHelpTexts2": "Adicionar: agregar as etiquetas à lista existente de etiquetas", + "ApplyTagsHelpTexts3": "Remover: eliminar as etiquetas adicionadas", + "ApplyTagsHelpTexts4": "Substituir: mudar as etiquetas pelas adicionadas (deixe em branco para limpar todas as etiquetas)", + "ArtistAlbumClickToChangeTrack": "Clique para mudar o livro", + "ArtistNameHelpText": "O nome do autor/livro a eliminar (pode ser qualquer palavra)", + "Authentication": "Autenticação", + "AuthenticationMethodHelpText": "Solicitar nome de utilizador e palavra-passe para acessar ao Lidarr", + "AutoRedownloadFailedHelpText": "Pesquisar e tentar transferir automaticamente uma versão diferente", + "BackupFolderHelpText": "Caminhos relativos estarão na pasta AppData do Lidarr", + "BackupIntervalHelpText": "Intervalo para criar cópia de segurança das configurações e da base de dados do Lidarr", + "BackupNow": "Criar cópia de segurança", + "BackupRetentionHelpText": "Cópias de segurança automáticas anteriores ao período de retenção serão eliminadas automaticamente", + "Backups": "Cópias de segurança", + "BindAddress": "Endereço de vínculo", + "BlocklistHelpText": "Impede o Lidarr de capturar automaticamente estes ficheiros novamente", + "BlocklistRelease": "Bloquear versão", + "Branch": "Ramificação", + "BypassProxyForLocalAddresses": "Ignorar proxy para endereços locais", + "Calendar": "Calendário", + "CalendarWeekColumnHeaderHelpText": "Mostrar acima de cada coluna quando a semana é a vista ativa", + "CancelMessageText": "Tem a certeza que quer cancelar esta tarefa pendente?", + "CertificateValidation": "Validação de certificado", + "ChangeHasNotBeenSavedYet": "A mudança ainda não foi guardada", + "ChmodFolder": "Pasta chmod", + "ChmodFolderHelpText": "Octal, aplicado durante a importação/renomeação para pastas e ficheiros de multimédia (sem executar bits)", + "ChmodFolderHelpTextWarning": "Isso só funciona se o utilizador que executa o Lidarr é o proprietário do ficheiro. É melhor garantir que o cliente de transferências defina as permissões corretamente.", + "ChownGroupHelpText": "Nome do grupo ou gid. Use gid para sistemas de ficheiros remotos.", + "ChownGroupHelpTextWarning": "Isso só funciona se o utilizador que executa o Lidarr é o proprietário do ficheiro. É melhor garantir que o cliente de transferências utilize o mesmo grupo que o Lidarr.", + "Clear": "Limpar", + "ClickToChangeQuality": "Clique para mudar a qualidade", + "ClientPriority": "Prioridade do cliente", + "CloneIndexer": "Clonar indexador", + "CloneProfile": "Clonar perfil", + "CollapseMultipleAlbumsHelpText": "Fechar múltiplos livros lançados no mesmo dia", + "Columns": "Colunas", + "CompletedDownloadHandling": "Processamento de transferência concluída", + "Component": "Componente", + "Connections": "Ligações", + "ConnectSettings": "Definições de ligação", + "Continuing": "Continuação", + "ContinuingAllTracksDownloaded": "Continuação (todos os livros transferidos)", + "CertificateValidationHelpText": "Mudar nível de restrição da validação da certificação HTTPS. Não mude a menos que entenda os riscos.", + "CopyUsingHardlinksHelpTextWarning": "Ocasionalmente, bloqueios de ficheiros podem impedir a renomeação de ficheiros que ainda estão sendo provisionados. Você pode temporariamente desativar o provisionamento e utilizar a função de renomeação do Lidarr como uma solução alternativa.", + "CreateEmptyArtistFolders": "Criar pastas vazias para filmes", + "CreateEmptyArtistFoldersHelpText": "Criar pastas ausentes para filmes durante a análise do disco", + "CreateGroup": "Criar grupo", + "CutoffHelpText": "Quando esta qualidade for alcançada, o Lidarr não transferirá mais filmes", + "CutoffUnmet": "Limite não-correspondido", + "Dates": "Datas", + "DefaultTagsHelpText": "Há um perfil de qualidade padrão para autores nesta pasta", + "DelayProfile": "Perfil de atraso", + "DelayProfiles": "Perfis de atraso", + "DeleteBackup": "Eliminar cópia de segurança", + "DeleteDelayProfileMessageText": "Tem a certeza que quer eliminar este perfil de atraso?", + "DeleteDownloadClient": "Eliminar cliente de transferências", + "DeleteDownloadClientMessageText": "Tem a certeza que quer eliminar o cliente de transferências \"{0}\"?", + "DeleteEmptyFolders": "Eliminar pastas vazias", + "DeleteFilesHelpText": "Eliminar os ficheiros do livro e a pasta do autor", + "DeleteImportList": "Eliminar lista de importação", + "DeleteImportListExclusion": "Eliminar exclusão da lista de importação", + "DeleteImportListExclusionMessageText": "Tem a certeza que quer eliminar esta exclusão da lista de importação?", + "DeleteImportListMessageText": "Tem a certeza que quer eliminar a lista \"{0}\"?", + "DeleteIndexer": "Eliminar indexador", + "DeleteQualityProfileMessageText": "Tem a certeza que quer eliminar o perfil de qualidade \"{0}\"?", + "DeleteReleaseProfile": "Eliminar perfil de atraso", + "DeleteReleaseProfileMessageText": "Tem a certeza que quer eliminar este perfil de atraso?", + "DeleteRootFolder": "Eliminar pasta raiz", + "DestinationPath": "Caminho de destino", + "DetailedProgressBar": "Barra de progresso detalhada", + "DetailedProgressBarHelpText": "Mostrar texto na barra de progresso", + "DiskSpace": "Espaço em disco", + "DownloadClient": "Cliente de transferências", + "Downloading": "Transferindo", + "DownloadPropersAndRepacksHelpTexts1": "Atualizar ou não automaticamente para Propers/Repacks", + "Enable": "Ativar", + "EnableAutomaticAdd": "Ativar adição automática", + "EnableSslHelpText": " Requer reinício da aplicação como administrador para aplicar alterações", + "Ended": "Terminado", + "EndedAllTracksDownloaded": "Terminado (todos os livros transferidos)", + "ErrorLoadingContents": "Erro ao carregar conteúdo", + "ErrorLoadingPreviews": "Erro ao carregar pré-visualizações", + "ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "Para obter mais informações sobre cada lista, clique nos botões de informação.", + "GeneralSettings": "Definições gerais", + "HasPendingChangesSaveChanges": "Guardar mudanças", + "History": "Histórico", + "HostHelpText": "O mesmo anfitrião especificado para o cliente de transferências remoto", + "Hostname": "Nome do anfitrião", + "ICalFeed": "Feed do iCal", + "ICalHttpUrlHelpText": "Copie este URL para seu(s) cliente(s) ou clique para subscrever-se caso seu browser suporte webcal", + "ICalLink": "Ligação do iCal", + "IconTooltip": "Agendado", + "RemoveFilter": "Remover filtro", + "IgnoredAddresses": "Endereços ignorados", + "IgnoredHelpText": "A versão será rejeitada caso contenha um ou mais destes termos (sem distinção de maiúsculas ou minúsculas)", + "IgnoredPlaceHolder": "Adicionar nova restrição", + "IllRestartLater": "Reiniciarei mais tarde", + "ImportedTo": "Importado para", + "Score": "Pontuação", + "ShowMonitored": "Mostrar monitorado(s)", + "SSLCertPath": "Caminho do certificado SSL", + "ImportExtraFiles": "Importar ficheiros adicionais", + "ImportExtraFilesHelpText": "Importar ficheiros adicionais correspondentes (legendas, nfo, etc.) após importar o ficheiro do livro", + "ImportFailedInterp": "Falha na importação: {0}", + "Importing": "Importando", + "IsExpandedShowFileInfo": "Mostrar informações do ficheiro", + "IsTagUsedCannotBeDeletedWhileInUse": "Não é possível eliminar enquanto estiver em uso", + "Label": "Rótulo", + "LaunchBrowserHelpText": " Abrir o browser e a home page do Lidarr ao iniciar a aplicação.", + "Level": "Nível", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "O Lidarr suporta qualquer indexador que utiliza o padrão Newznab, bem como outros listados abaixo.", + "LidarrSupportsMultipleListsForImportingAlbumsAndArtistsIntoTheDatabase": "O Lidarr suporta múltiplas listas para importação de livros e autores para a base de dados.", + "LidarrTags": "Etiquetas do Lidarr", + "LoadingTrackFilesFailed": "Falha no carregamento dos ficheiros do livro", + "Local": "Local", + "LocalPathHelpText": "Caminho que o Lidarr deve usar para acessar localmente ao caminho remoto", + "LogLevel": "Nível de log", + "LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "O registo de rasteio somente deve ser ativado temporariamente", + "Logs": "Logs", + "UnableToLoadNamingSettings": "Não foi possível carregar as definições de nomenclatura", + "LongDateFormat": "Formato longo de data", + "ManualDownload": "Transferência manual", + "ManualImport": "Importação manual", + "MarkAsFailed": "Marcar como falhado", + "MarkAsFailedMessageText": "Tem a certeza que quer marcar \"{0}\" como falhado?", + "MaximumLimits": "Limites máximos", + "MaximumSize": "Tamanho máximo", + "Mechanism": "Mecanismo", + "TestAll": "Testar todos", + "UnableToLoadDownloadClients": "Não foi possível carregar os clientes de transferências", + "UnableToLoadGeneralSettings": "Não foi possível carregar as definições gerais", + "60MinutesSixty": "60 minutos: {0}", + "APIKey": "Chave da API", + "About": "Informações", + "AddListExclusion": "Adicionar exclusão de lista", + "AddingTag": "A adicionar etiqueta", + "AdvancedSettingsHiddenClickToShow": "Oculto, clique para mostrar", + "AdvancedSettingsShownClickToHide": "Visível, clique para ocultar", + "AgeWhenGrabbed": "Tempo de vida (quando capturado)", + "AnalyticsEnabledHelpTextWarning": "Requer reinício para aplicar alterações", + "Automatic": "Automático", + "MetadataSettings": "Definições de metadados", + "MinimumFreeSpaceWhenImportingHelpText": "Evitar a importação caso deixe menos espaço livre em disco que esta quantidade", + "MinimumLimits": "Limites mínimos", + "Missing": "Ausente", + "Mode": "Modo", + "MusicBrainzReleaseID": "ID da versão no MusicBrainz", + "MustContain": "Deve conter", + "MustNotContain": "Não deve conter", + "PathHelpTextWarning": "Deve ser diferente do diretório em que o cliente de transferências coloca os ficheiros", + "Permissions": "Permissões", + "Search": "Pesquisar", + "SearchAll": "Pesquisar todos", + "SearchForAllCutoffUnmetAlbums": "Pesquisar todos os livros com Limite não-correspondido", + "ShowSearch": "Mostrar pesquisa", + "ShowSizeOnDisk": "Mostrar tamanho em disco", + "TestAllClients": "Testar todos os clientes", + "TestAllIndexers": "Testar todos os indexadores", + "TestAllLists": "Testar todas as listas", + "ThisCannotBeCancelled": "Isso não pode ser cancelado uma vez iniciado sem desabilitar todos os seus indexadores.", + "ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "Isto se aplicará a todos os indexadores, siga as regras estabelecidas por eles", + "Time": "Hora", + "TimeFormat": "Formato de hora", + "TorrentDelay": "Atraso para torrents", + "TorrentDelayHelpText": "Atraso, em minutos, para aguardar antes de capturar um torrent", + "TotalFileSize": "Tamanho total do ficheiro", + "Track": "Rastreio", + "UnableToAddANewRemotePathMappingPleaseTryAgain": "Não foi possível adicionar um novo mapeamento de caminho remoto, tenta novamente.", + "Tracks": "Rastreio", + "UnableToAddANewRootFolderPleaseTryAgain": "Não foi possível adicionar uma nova pasta raiz, tenta novamente.", + "UnableToLoadBackups": "Não foi possível carregar as cópias de segurança", + "UnableToLoadBlocklist": "Não foi possível carregar a lista de bloqueio", + "UnableToLoadIndexers": "Não foi possível carregar os indexadores", + "UnableToLoadQualityProfiles": "Não foi possível carregar os perfis de qualidade", + "UnmonitoredHelpText": "Incluir filmes não monitorados no feed do iCal", + "UpdateAll": "Atualizar todos", + "UseProxy": "Usar proxy", + "Version": "Versão", + "NETCore": ".NET", + "OnGrab": "Ao capturar", + "Options": "Opções", + "Original": "Original", + "Other": "Outros", + "RecyclingBinCleanup": "Limpeza da reciclagem", + "Reset": "Repor", + "RootFolder": "Pasta raiz", + "SearchForMissing": "Pesquisar ausentes", + "SearchForMonitoredAlbums": "Pesquisar livros monitorados", + "SearchMonitored": "Pesquisar monitorados", + "ShortDateFormat": "Formato curto de data", + "ShowName": "Mostrar nome", + "ShowUnknownArtistItems": "Mostrar itens de autor desconhecido", + "Size": " Tamanho", + "SorryThatArtistCannotBeFound": "Desculpe, este autor não foi encontrado.", + "Type": "Tipo", + "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Ramificação utilizada pelo mecanismo externo de atualização", + "Actions": "Ações", + "ApiKeyHelpTextWarning": "Requer reinício para aplicar alterações", + "AppDataDirectory": "Pasta AppData", + "ApplyTags": "Aplicar etiquetas", + "ApplyTagsHelpTexts1": "Como aplicar etiquetas ao autor selecionado", + "BindAddressHelpTextWarning": "Requer reinício para aplicar alterações", + "Blocklist": "Lista de bloqueio", + "Cancel": "Cancelar", + "UILanguageHelpText": "Idioma que o Lidarr usará para a IU", + "UILanguageHelpTextWarning": "É preciso reiniciar o browser", + "UnableToAddANewImportListExclusionPleaseTryAgain": "Não foi possível adicionar uma nova exclusão de lista de importação, tenta novamente.", + "UnableToAddANewIndexerPleaseTryAgain": "Não foi possível adicionar um novo indexador, tenta novamente.", + "UnableToAddANewMetadataProfilePleaseTryAgain": "Não foi possível adicionar um novo perfil de metadados, tenta novamente.", + "UnableToAddANewNotificationPleaseTryAgain": "Não foi possível adicionar uma nova notificação, tenta novamente.", + "UnableToAddANewQualityProfilePleaseTryAgain": "Não foi possível adicionar um novo perfil de qualidade, tenta novamente.", + "UnableToLoadRemotePathMappings": "Não foi possível carregar os mapeamentos de caminhos remoto", + "DBMigration": "Migração da base de dados", + "DefaultMetadataProfileIdHelpText": "Há um perfil de qualidade padrão para autores nesta pasta", + "DefaultQualityProfileIdHelpText": "Há um perfil de qualidade padrão para autores nesta pasta", + "Delete": "Eliminar", + "DeleteIndexerMessageText": "Tem a certeza que quer eliminar o indexador \"{0}\"?", + "DeleteMetadataProfileMessageText": "Tem a certeza que quer eliminar o perfil de qualidade \"{0}\"?", + "DeleteNotification": "Eliminar notificação", + "DeleteNotificationMessageText": "Tem a certeza que quer eliminar a notificação \"{0}\"?", + "DeleteQualityProfile": "Eliminar perfil de qualidade", + "DeleteRootFolderMessageText": "Tem a certeza que quer eliminar a pasta raiz \"{0}\"?", + "DeleteSelectedTrackFiles": "Eliminar ficheiros de livro selecionados", + "DeleteSelectedTrackFilesMessageText": "Tem a certeza que quer eliminar os ficheiros de livro selecionados?", + "DeleteTag": "Eliminar etiqueta", + "DeleteTagMessageText": "Tem a certeza que quer eliminar a etiqueta \"{0}\"?", + "DeleteTrackFileMessageText": "Tem a certeza que quer eliminar {0}?", + "EnableAutomaticAddHelpText": "Adicionar autor/livros ao Lidarr ao sincronizar pela IU ou pelo Lidarr", + "EnableAutomaticSearch": "Ativar pesquisa automática", + "EnableColorImpairedMode": "Ativar modo de daltonismo", + "EnableHelpText": "Ativar criação do ficheiro de metadados para este tipo de metadados", + "EnableInteractiveSearch": "Ativar pesquisa interativa", + "EnableProfile": "Ativar perfil", + "EnableRSS": "Ativar RSS", + "EnableSSL": "Ativar SSL", + "ExistingTagsScrubbed": "Etiquetas existentes eliminadas", + "ExpandOtherByDefaultHelpText": "Outros", + "ExtraFileExtensionsHelpTexts1": "Lista separada por vírgulas de ficheiros adicionais a importar (.nfo será importado como .nfo-orig)", + "FileDateHelpText": "Mudar data do ficheiro ao importar/analisar", + "FileManagement": "Gestão do ficheiro", + "Filename": "Nome do ficheiro", + "FileNames": "Nomes de ficheiros", + "Files": "Ficheiros", + "FirstDayOfWeek": "Primeiro dia da semana", + "Fixed": "Corrigido", + "Folder": "Pasta", + "Folders": "Pastas", + "ForeignIdHelpText": "A ID do Musicbrainz para o autor/livro a eliminar", + "ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "Para obter mais informações sobre cada indexador, clique nos botões de informação.", + "FutureDaysHelpText": "Dias anteriores a exibir no feed do iCal", + "Global": "Global", + "GoToInterp": "Ir para {0}", + "Grab": "Capturar", + "GrabID": "Capturar ID", + "GrabRelease": "Capturar versão", + "GrabReleaseMessageText": "O Lidarr não pode determinar a que autor e livro pertence esta versão. O Lidarr pode ser incapaz de importar automaticamente esta versão. Deseja capturar \"{0}\"?", + "GrabSelected": "Capturar seleção", + "Group": "Grupo", + "HasPendingChangesNoChanges": "Sem mudanças", + "IncludeUnknownArtistItemsHelpText": "Mostrar itens sem um filme na fila. Isso pode incluir filmes eliminados ou qualquer outra coisa na categoria do Lidarr", + "IncludeUnmonitored": "Incluir não monitorados", + "Indexer": "Indexador", + "IndexerIdHelpTextWarning": "Usar um indexador específico com as palavras preferidas pode acarretar na captura de versões duplicadas", + "IndexerIdvalue0OnlySupportedWhenIndexerIsSetToAll": "Suportado apenas quando o indexador está definido como (Todos)", + "IndexerPriority": "Prioridade do indexador", + "Indexers": "Indexadores", + "IndexerSettings": "Definições do indexador", + "InteractiveSearch": "Pesquisa interativa", + "Interval": "Intervalo", + "IsCutoffCutoff": "Limite", + "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Atualizar até que esta qualidade seja alcançada ou superada", + "IsExpandedHideFileInfo": "Ocultar informações do ficheiro", + "Medium": "Médio", + "NotificationTriggers": "Acionadores de notificação", + "NoUpdatesAreAvailable": "Não há atualizações disponíveis", + "OnGrabHelpText": "Ao capturar", + "Proxy": "Proxy", + "ProxyBypassFilterHelpText": "Utilizar \",\" como separador e \"*.\" como caráter universal para subdomínios", + "ProxyType": "Tipo de proxy", + "PublishedDate": "Data de publicação", + "Quality": "Qualidade", + "QualityDefinitions": "Definições de qualidade", + "QualityProfile": "Perfil de qualidade", + "QualityProfiles": "Perfis de qualidade", + "QualitySettings": "Definições de qualidade", + "ReadTheWikiForMoreInformation": "Leia a Wiki para obter mais informações", + "Real": "Real", + "Reason": "Razão", + "RecycleBinCleanupDaysHelpText": "Defina como 0 para desativar a limpeza automática", + "RecycleBinHelpText": "Ficheiros de livros virão para cá ao serem apagados, em vez de serem permanentemente eliminados", + "ReleaseDate": "Data de lançamento", + "ReleaseGroup": "Grupo da versão", + "ReleaseProfiles": "Perfis de versão", + "ReleaseRejected": "Versão rejeitada", + "ReleaseStatuses": "Estado da versão", + "ReleaseWillBeProcessedInterp": "A versão será processada {0}", + "Reload": "Recarregar", + "RemotePathHelpText": "Caminho raiz para o diretório que o cliente de transferências acessa", + "RemotePathMappings": "Mapeamentos de caminho remoto", + "Remove": "Remover", + "RemoveCompletedDownloadsHelpText": "Remover transferências importadas do histórico do cliente de transferências", + "20MinutesTwenty": "20 minutos: {0}", + "45MinutesFourtyFive": "45 minutos: {0}", + "AlbumIsDownloadingInterp": "O livro está transferindo - {0}% {1}", + "AllExpandedCollapseAll": "Fechar tudo", + "AllowArtistChangeClickToChangeArtist": "Clique para mudar o autor", + "AllowFingerprintingHelpTextWarning": "Isto exige que o Lidarr leia partes do ficheiro, o que pode reduzir a velocidade das análises e gerar maior atividade na rede ou no disco.", + "AlreadyInYourLibrary": "Já está na sua biblioteca", + "AlternateTitles": "Título alternativo", + "AlternateTitleslength1Title": "Título", + "AlternateTitleslength1Titles": "Títulos", + "Analytics": "Análises", + "AnalyticsEnabledHelpText": "Envia informações anônimas de uso e de erros aos servidores do Lidarr. Isso inclui informações sobre seu browser, páginas utilizadas na WebUI do Lidarr, relatórios de erros, bem como as versões do sistema operativo e da aplicação. Utilizaremos essas informações para priorizar funcionalidades e correções de bugs.", + "DelayingDownloadUntilInterp": "Atrasando a transferência até {0} às {1}", + "Edit": "Editar", + "Queue": "Fila", + "WriteMetadataToAudioFiles": "Escrever metadados em ficheiros de áudio", + "Year": "Ano", + "BindAddressHelpText": "Endereço IPv4 válido ou \"*\" para todas as interfaces", + "MonoVersion": "Versão do Mono" } diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index 9ee788bb7..165d10889 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -142,7 +142,7 @@ "UseProxy": "Usar proxy", "UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent fornecido pelo aplicativo que chamou a API", "Username": "Nome de usuário", - "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Ramificação para atualização do Radarr", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Ramificação para atualização do Lidarr", "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Ramificação usada pelo mecanismo de atualização externo", "Version": "Versão", "WatchLibraryForChangesHelpText": "Verificar novamente quando houver alteração em arquivos de uma pasta raiz", @@ -152,10 +152,10 @@ "WriteMetadataToAudioFiles": "Gravar metadados em arquivos de áudio", "Year": "Ano", "ChmodFolderHelpText": "Octal, aplicado durante a importação/renomeação a pastas e arquivos de mídia (sem executar bits)", - "ChmodFolderHelpTextWarning": "Isso só funciona se o usuário que está executando o Radarr for o proprietário do arquivo. É melhor garantir que o cliente de download defina as permissões corretamente.", + "ChmodFolderHelpTextWarning": "Isso só funciona se o usuário que está executando o Lidarr for o proprietário do arquivo. É melhor garantir que o cliente de download defina as permissões corretamente.", "ChownGroup": "Grupo chown", "ChownGroupHelpText": "Nome ou ID do grupo. Use a ID para sistemas de arquivos remotos.", - "ChownGroupHelpTextWarning": "Isso só funciona se o usuário que está executando o Radarr for o proprietário do arquivo. É melhor garantir que o cliente de download use o mesmo grupo que o Radarr.", + "ChownGroupHelpTextWarning": "Isso só funciona se o usuário que está executando o Lidarr for o proprietário do arquivo. É melhor garantir que o cliente de download use o mesmo grupo que o Lidarr.", "Clear": "Limpar", "ClickToChangeQuality": "Clique para alterar a qualidade", "ClientPriority": "Prioridade do cliente", @@ -170,18 +170,18 @@ "ContinuingAllTracksDownloaded": "Continuação (todos os livros baixados)", "ContinuingNoAdditionalAlbumsAreExpected": "Não espera-se mais livros", "CopyUsingHardlinksHelpText": "Usar vínculos reais ao tentar copiar arquivos de torrents que ainda estão sendo semeados", - "CopyUsingHardlinksHelpTextWarning": "Ocasionalmente, os bloqueios de arquivo podem impedir a renomeação de arquivos que estão sendo semeados. Você pode desabilitar temporariamente a semeadura e usar a função de renomeação do Readarr como uma solução alternativa.", + "CopyUsingHardlinksHelpTextWarning": "Ocasionalmente, os bloqueios de arquivo podem impedir a renomeação de arquivos que estão sendo semeados. Você pode desabilitar temporariamente a semeadura e usar a função de renomeação do Lidarr como uma solução alternativa.", "Country": "País", "CreateEmptyArtistFolders": "Criar pastas de autor vazias", "CreateEmptyArtistFoldersHelpText": "Criar pastas de autor ausente durante a verificação do disco", "CreateGroup": "Criar grupo", - "CutoffHelpText": "Assim que esta qualidade for alcançada, o Radarr não baixará mais filmes", + "CutoffHelpText": "Assim que esta qualidade for alcançada, o Lidarr não baixará mais filmes", "CutoffUnmet": "Limite não atingido", "DBMigration": "Migração de banco de dados", - "DefaultLidarrTags": "Tags padrão do Readarr", + "DefaultLidarrTags": "Tags padrão do Lidarr", "DefaultMetadataProfileIdHelpText": "Há um perfil de metadados padrão para autores nesta pasta", "DefaultQualityProfileIdHelpText": "Há um perfil de qualidade padrão para autores nesta pasta", - "DefaultTagsHelpText": "Há tags padrão do Readarr para autores nesta pasta", + "DefaultTagsHelpText": "Há tags padrão do Lidarr para autores nesta pasta", "DelayProfile": "Perfil de atraso", "DelayProfiles": "Perfis de atraso", "Delete": "Excluir", @@ -232,7 +232,7 @@ "Edit": "Editar", "Enable": "Habilitar", "EnableAutomaticAdd": "Habilitar adição automática", - "EnableAutomaticAddHelpText": "Adicionar autor/livros ao Readarr ao sincronizar pela interface ou pelo Readarr", + "EnableAutomaticAddHelpText": "Adicionar autor/livros ao Lidarr ao sincronizar pela interface ou pelo Lidarr", "EnableAutomaticSearch": "Habilitar pesquisa automática", "EnableColorImpairedMode": "Habilitar modo para daltonismo", "EnableColorImpairedModeHelpText": "Estilo alterado para permitir que usuários com daltonismo distingam melhor as informações codificadas por cores", @@ -277,7 +277,7 @@ "Grab": "Obter", "GrabID": "Obter ID", "GrabRelease": "Capturar Versão", - "GrabReleaseMessageText": "O Readarr não conseguiu determinar a qual autor e livro esse lançamento está relacionado. O Readarr pode não conseguir importar automaticamente este lançamento. Quer obter \"{0}\"?", + "GrabReleaseMessageText": "O Lidarr não conseguiu determinar a qual autor e livro esse lançamento está relacionado. O Lidarr pode não conseguir importar automaticamente este lançamento. Quer obter \"{0}\"?", "GrabSelected": "Obter selecionado", "Group": "Grupo", "HasPendingChangesNoChanges": "Sem alterações", @@ -306,7 +306,7 @@ "ImportLists": "Listas de importação", "IncludeHealthWarningsHelpText": "Incluir avisos de integridade", "IncludePreferredWhenRenaming": "Incluir preferido ao renomear", - "IncludeUnknownArtistItemsHelpText": "Mostrar itens sem um autor na fila. Isso pode incluir autores e filmes removidos, ou qualquer outra coisa na categoria do Radarr", + "IncludeUnknownArtistItemsHelpText": "Mostrar itens sem um autor na fila. Isso pode incluir autores e filmes removidos, ou qualquer outra coisa na categoria do Lidarr", "IncludeUnmonitored": "Incluir não monitorados", "Indexer": "Indexador", "IndexerIdHelpText": "Especificar a qual indexador o perfil se aplica", @@ -328,9 +328,9 @@ "IsShowingMonitoredUnmonitorSelected": "Deixar de monitorar selecionados", "IsTagUsedCannotBeDeletedWhileInUse": "Não pode ser excluído durante o uso", "Label": "Rótulo", - "LaunchBrowserHelpText": " Abrir o navegador Web e navegar até a página inicial do Readarr ao iniciar o aplicativo.", + "LaunchBrowserHelpText": " Abrir o navegador Web e navegar até a página inicial do Lidarr ao iniciar o aplicativo.", "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr suporta muitos clientes de download torrent e usenet.", - "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "O Radarr oferece suporte a qualquer indexador que usa o padrão Newznab, além de outros indexadores, listados abaixo.", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "O Lidarr oferece suporte a qualquer indexador que usa o padrão Newznab, além de outros indexadores, listados abaixo.", "LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "O registro de rastreamento deve ser ativado apenas temporariamente", "LongDateFormat": "Formato longo da data", "MaintenanceRelease": "Versão de manutenção", @@ -448,7 +448,7 @@ "RequiredPlaceHolder": "Adicionar nova restrição", "RequiresRestartToTakeEffect": "Requer reiniciar para ter efeito", "RescanAfterRefreshHelpText": "Verificar novamente a pasta de autor após atualizar o autor", - "RescanAfterRefreshHelpTextWarning": "O Radarr não detectará automaticamente as alterações nos arquivos se não estiver definido como \"Sempre\"", + "RescanAfterRefreshHelpTextWarning": "O Lidarr não detectará automaticamente as alterações nos arquivos se não estiver definido como \"Sempre\"", "RescanArtistFolderAfterRefresh": "Verificar novamente a pasta do autor após atualizar", "Reset": "Redefinir", "ResetAPIKey": "Redefinir chave da API", @@ -491,7 +491,7 @@ "SuccessMyWorkIsDoneNoFilesToRetag": "Êba, já terminei! Não há novas tags a adicionar a arquivos.", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "O RSS não é compatível com este indexador", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "A pesquisa não é compatível com este indexador", - "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Será usado ao realizar pesquisas automáticas pela interface ou pelo Radarr", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Será usado ao realizar pesquisas automáticas pela interface ou pelo Lidarr", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Será usado com a pesquisa interativa", "TagAudioFilesWithMetadata": "Marcar arquivos de áudio com metadados", "TagIsNotUsedAndCanBeDeleted": "A tag não está em uso e pode ser excluída", @@ -525,7 +525,7 @@ "Scheduled": "Agendado", "Score": "Pontuação", "ScriptPath": "Caminho do script", - "ScrubAudioTagsHelpText": "Remover tags existentes dos arquivos, deixando apenas as adicionadas pelo Readarr.", + "ScrubAudioTagsHelpText": "Remover tags existentes dos arquivos, deixando apenas as adicionadas pelo Lidarr.", "ScrubExistingTags": "Limpar tags existentes", "Search": "Pesquisar", "SearchAll": "Pesquisar tudo", @@ -553,25 +553,25 @@ "ShowMonitoredHelpText": "Mostrar status de monitoramento sob o pôster", "Size": " Tamanho", "SkipFreeSpaceCheck": "Ignorar verificação de espaço livre", - "SkipFreeSpaceCheckWhenImportingHelpText": "Usar quando o Radarr não conseguir detectar espaço livre na pasta raiz do filme", + "SkipFreeSpaceCheckWhenImportingHelpText": "Usar quando o Lidarr não conseguir detectar espaço livre na pasta raiz do filme", "SkipRedownload": "Ignorar novo download", - "SkipredownloadHelpText": "Impede que o Readarr tente baixar lançamentos alternativos para itens removidos", + "SkipredownloadHelpText": "Impede que o Lidarr tente baixar lançamentos alternativos para itens removidos", "SorryThatAlbumCannotBeFound": "Desculpe, esse filme não pode ser encontrado.", "SorryThatArtistCannotBeFound": "Desculpe, esse autor não pode ser encontrado.", "Source": "Fonte", "SourcePath": "Caminho da fonte", "UpgradeAllowedHelpText": "Se desabilitada, as qualidades não serão atualizadas", - "UILanguageHelpText": "Idioma que o Radarr usará para a interface", + "UILanguageHelpText": "Idioma que o Lidarr usará para a interface", "UnableToLoadIndexers": "Não foi possível carregar os indexadores", "UnableToLoadLists": "Não foi possível carregar as listas", "UnmappedFiles": "Arquivos não mapeados", "FileManagement": "Gerenciamento de arquivo", - "LidarrSupportsMultipleListsForImportingAlbumsAndArtistsIntoTheDatabase": "O Readarr oferece suporte a várias listas para importar livros e autores para o banco de dados.", - "LidarrTags": "Tags do Radarr", + "LidarrSupportsMultipleListsForImportingAlbumsAndArtistsIntoTheDatabase": "O Lidarr oferece suporte a várias listas para importar livros e autores para o banco de dados.", + "LidarrTags": "Tags do Lidarr", "LoadingTrackFilesFailed": "Falha ao carregar arquivos do livro", "Local": "Local", "LocalPath": "Caminho Local", - "LocalPathHelpText": "Caminho que o Radarr deve usar para acessar o caminho remoto localmente", + "LocalPathHelpText": "Caminho que o Lidarr deve usar para acessar o caminho remoto localmente", "LogFiles": "Arquivos de log", "Logging": "Registro em log", "LogLevel": "Nível do log", @@ -595,16 +595,16 @@ "RemoveSelected": "Remover selecionado(s)", "RemoveTagExistingTag": "Tag existente", "RemoveTagRemovingTag": "Removendo tag", - "RenameTracksHelpText": "O Radarr usará o nome de arquivo existente se a renomeação estiver desativada", + "RenameTracksHelpText": "O Lidarr usará o nome de arquivo existente se a renomeação estiver desativada", "ReplaceIllegalCharacters": "Substituir caracteres ilegais", - "ReplaceIllegalCharactersHelpText": "Substituir caracteres ilegais. Se desmarcada, o Radarr irá removê-los", + "ReplaceIllegalCharactersHelpText": "Substituir caracteres ilegais. Se desmarcada, o Lidarr irá removê-los", "RemoveFilter": "Remover filtro", "RemoveFromBlocklist": "Remover da lista de bloqueio", "RemoveFromDownloadClient": "Remover do cliente de download", "RemoveFromQueue": "Remover da fila", "ResetAPIKeyMessageText": "Tem certeza de que deseja redefinir sua chave de API?", "Restart": "Reiniciar", - "RestartLidarr": "Reiniciar o Radarr", + "RestartLidarr": "Reiniciar o Lidarr", "RestartNow": "Reiniciar agora", "Restore": "Restaurar", "Result": "Resultado", @@ -644,7 +644,6 @@ "IsExpandedShowTracks": "Mostrar faixas", "LatestAlbum": "Último Álbum", "LatestAlbumData": "Monitorar os últimos e futuros álbuns", - "Lidarr": "Lidarr", "LoadingAlbumsFailed": "Falha ao carregar álbuns", "ManageTracks": "Gerenciar Faixas", "MissingAlbums": "Álbuns Ausentes", @@ -660,10 +659,8 @@ "PrimaryAlbumTypes": "Tipos de Álbum Primário", "PrimaryTypes": "Tipos Primário", "Prowlarr": "Prowlarr", - "Radarr": "Radarr", "RefreshArtist": "Atualizar Artista", "ReleasesHelpText": "Mudar lançamento para este álbum", - "Readarr": "Readarr", "SearchAlbum": "Pesquisar Álbum", "SearchBoxPlaceHolder": "exemplo: Breaking Benjamin, lidarr:854a1807-025b-42a8-ba8c-2a39717f1d25", "SecondaryAlbumTypes": "Tipo de Álbum Secundário", @@ -697,5 +694,11 @@ "ExpandBroadcastByDefaultHelpText": "Transmissão", "MassAlbumsSearchWarning": "Tem certeza de que deseja pesquisar todos os '{0}' álbuns ausentes?", "MassAlbumsCutoffUnmetWarning": "Tem certeza de que deseja pesquisar todos os álbuns '{0}' que não atingiram o corte?", - "ThisCannotBeCancelled": "Isso não pode ser cancelado uma vez iniciado sem desabilitar todos os seus indexadores." + "ThisCannotBeCancelled": "Isso não pode ser cancelado uma vez iniciado sem desabilitar todos os seus indexadores.", + "ImportListSpecificSettings": "Importar configurações específicas da lista", + "AddedArtistSettings": "Adicionado Configurações de Autor", + "MonitorAlbumExistingOnlyWarning": "Este é um ajuste único da configuração monitoramento para cada livro. Use a opção em Autor/Editar para controlar o que acontece com os livros recém-adicionados", + "MonitoringOptionsHelpText": "Quais livros devem ser monitorados após o autor ser adicionado (ajuste único)", + "MonitorNewItemsHelpText": "Quais novos livros devem ser monitorados", + "MonoVersion": "Versão do Mono" } diff --git a/src/NzbDrone.Core/Localization/Core/ro.json b/src/NzbDrone.Core/Localization/Core/ro.json index 47ccfb097..24622edff 100644 --- a/src/NzbDrone.Core/Localization/Core/ro.json +++ b/src/NzbDrone.Core/Localization/Core/ro.json @@ -1,4 +1,478 @@ { "Language": "Limbă", - "UILanguage": "Limbajul UI" + "UILanguage": "Limbajul UI", + "ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Pentru mai multe informații despre clienții individuali de descărcare, faceți clic pe butoanele de informații.", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Whisparr acceptă orice client de descărcare care utilizează standardul Newznab, precum și alți clienți de descărcare enumerați mai jos.", + "RemoveCompletedDownloadsHelpText": "Eliminați descărcările importate din istoricul clientului de descărcări", + "RemovedFromTaskQueue": "Eliminat din coada de activități", + "Unmonitored": "Nemonitorizat", + "UnmonitoredHelpText": "Includeți filme ne-monitorizate în fluxul iCal", + "UseProxy": "Utilizarea proxy", + "AlternateTitles": "Titlu alternativ", + "MediaInfo": "Informații media", + "MediaManagementSettings": "Setări de gestionare media", + "Medium": "Mediu", + "Message": "Mesaj", + "MetadataSettings": "Setări metadate", + "MIA": "MIA", + "MinimumAge": "Varsta minima", + "MinimumAgeHelpText": "Doar Usenet: vârsta minimă în minute a NZB-urilor înainte de a fi apucate. Utilizați acest lucru pentru a oferi timp noilor versiuni de difuzare către furnizorul dvs. de usenet.", + "MinimumFreeSpaceWhenImportingHelpText": "Împiedicați importul dacă ar lăsa mai puțin decât această cantitate de spațiu pe disc disponibil", + "MinimumLimits": "Limite minime", + "Mode": "Mod", + "Monitored": "Monitorizat", + "NotificationTriggers": "Declanșatoare de notificări", + "NoUpdatesAreAvailable": "Nu sunt disponibile actualizări", + "OnGrabHelpText": "Pe Grab", + "OnHealthIssueHelpText": "Cu privire la problema sănătății", + "ProxyPasswordHelpText": "Trebuie să introduceți un nume de utilizator și o parolă numai dacă este necesară. Lasă-le necompletate altfel.", + "ProxyUsernameHelpText": "Trebuie să introduceți un nume de utilizator și o parolă numai dacă este necesară. Lasă-le necompletate altfel.", + "PublishedDate": "Data publicării", + "Quality": "Calitate", + "QualityDefinitions": "Definiții de calitate", + "QualityProfile": "Profil de Calitate", + "QualityProfiles": "Profile de Calitate", + "QualitySettings": "Setări de calitate", + "Queue": "Coadă", + "ReadTheWikiForMoreInformation": "Citiți Wiki pentru mai multe informații", + "Real": "Real", + "Reason": "Motiv", + "RecycleBinCleanupDaysHelpText": "Setați la 0 pentru a dezactiva curățarea automată", + "RecycleBinCleanupDaysHelpTextWarning": "Fișierele din coșul de reciclare mai vechi de numărul de zile selectat vor fi curățate automat", + "RecycleBinHelpText": "Fișierele de film vor merge aici atunci când sunt șterse în loc să fie șterse definitiv", + "RecyclingBin": "Cos de reciclare", + "RecyclingBinCleanup": "Curățarea coșului de reciclare", + "Redownload": "Redescărcați", + "Refresh": "Reîmprospătează", + "RefreshInformationAndScanDisk": "Actualizați informațiile și discul de scanare", + "RefreshScan": "Reîmprospătează și scanează", + "RemoveFailedDownloadsHelpText": "Eliminați descărcările nereușite din istoricul clientului de descărcare", + "RemoveFilter": "Scoateți filtrul", + "RemoveSelectedMessageText": "Sigur doriți să eliminați elementele selectate din lista neagră?", + "RemoveTagExistingTag": "Etichetă existentă", + "RemoveTagRemovingTag": "Se elimină eticheta", + "RenameTracksHelpText": "Lidarr va folosi numele fișierului existent dacă redenumirea este dezactivată", + "Reorder": "Reordonează", + "ReplaceIllegalCharacters": "Înlocuiți personajele ilegale", + "ReplaceIllegalCharactersHelpText": "Înlocuiți personajele ilegale. Dacă nu este bifat, Lidarr le va elimina în schimb", + "RequiredHelpText": "Versiunea trebuie să conțină cel puțin unul dintre acești termeni (fără sensibilitate la majuscule și minuscule)", + "RequiresRestartToTakeEffect": "Necesită repornire pentru a intra în vigoare", + "RescanAfterRefreshHelpText": "Rescanează folderul filmului după reîmprospătarea filmului", + "RescanAfterRefreshHelpTextWarning": "Lidarr nu va detecta automat modificările fișierelor atunci când nu este setat la „Întotdeauna”", + "RescanArtistFolderAfterRefresh": "Rescanează dosarul filmului după reîmprospătare", + "ResetAPIKey": "Resetați cheia API", + "ResetAPIKeyMessageText": "Sigur doriți să vă resetați cheia API?", + "Restart": "Repornește", + "RestartLidarr": "Reporniți Lidarr", + "RestartNow": "Reporniți acum", + "Restore": "Restabili", + "RestoreBackup": "Restaurează salvarea", + "Result": "Rezultat", + "Retention": "Retenţie", + "RetentionHelpText": "Numai Usenet: Setați la zero pentru a seta pentru păstrarea nelimitată", + "RetryingDownloadInterp": "Reîncercați descărcarea {0} la {1}", + "RssSyncIntervalHelpText": "Interval în minute. Setați la zero pentru a dezactiva (acest lucru va opri toate apucările de eliberare automată)", + "SearchAll": "Caută în toate", + "SearchForMissing": "Caută ce lipsește", + "SearchSelected": "Caută selecția", + "Security": "Securitate", + "SendAnonymousUsageData": "Trimiteți date de utilizare anonime", + "SetPermissions": "Setați permisiunile", + "SetPermissionsLinuxHelpText": "Ar trebui să fie rulat chmod când fișierele sunt importate / redenumite?", + "SetPermissionsLinuxHelpTextWarning": "Dacă nu sunteți sigur ce fac aceste setări, nu le modificați.", + "Settings": "Setări", + "ShortDateFormat": "Format scurt de dată", + "ShowCutoffUnmetIconHelpText": "Afișați pictograma pentru fișiere atunci când limita nu a fost îndeplinită", + "ShowDateAdded": "Afișează data adăugării", + "ShowQualityProfile": "Afișați profilul de calitate", + "ShowQualityProfileHelpText": "Afișează profilul de calitate sub afiș", + "ShowRelativeDatesHelpText": "Afișați datele relative (Azi / Ieri / etc) sau absolute", + "ShowSearch": "Afișați Căutare", + "ShowSearchActionHelpText": "Afișați butonul de căutare pe hover", + "ShowSizeOnDisk": "Afișați dimensiunea pe disc", + "ShowUnknownArtistItems": "Afișați elemente de film necunoscute", + "SkipFreeSpaceCheckWhenImportingHelpText": "Folosiți atunci când Lidarr nu poate detecta spațiul liber din folderul rădăcină al filmului", + "SorryThatAlbumCannotBeFound": "Ne pare rău, filmul respectiv nu poate fi găsit.", + "SorryThatArtistCannotBeFound": "Ne pare rău, filmul respectiv nu poate fi găsit.", + "Source": "Sursă", + "SslCertPasswordHelpTextWarning": "Necesită repornire pentru a intra în vigoare", + "Style": "Stil", + "SuccessMyWorkIsDoneNoFilesToRename": "Succes! Mi-am terminat treaba, niciun fișier de redenumit.", + "SuccessMyWorkIsDoneNoFilesToRetag": "Succes! Mi-am terminat treaba, niciun fișier de redenumit.", + "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS nu este acceptat cu acest indexer", + "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Căutarea nu este acceptată cu acest indexer", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Va fi utilizat atunci când căutările automate sunt efectuate prin interfața de utilizare sau de către Lidarr", + "TagIsNotUsedAndCanBeDeleted": "Eticheta nu este utilizată și poate fi ștearsă", + "TestAll": "Testează Tot", + "TestAllClients": "Testați toți clienții", + "TestAllIndexers": "Testați toți indexatorii", + "TestAllLists": "Testați toate listele", + "ThisCannotBeCancelled": "Acest lucru nu poate fi anulat odată pornit fără a reporni Whisparr.", + "ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "Acest lucru se va aplica tuturor indexatorilor, vă rugăm să urmați regulile stabilite de aceștia", + "TorrentDelay": "Întârziere Torrent", + "Torrents": "Torente", + "TotalFileSize": "Dimensiunea totală a fișierului", + "Track": "Urmă", + "UnableToAddANewListPleaseTryAgain": "Imposibil de adăugat o nouă listă, încercați din nou.", + "UnableToAddANewMetadataProfilePleaseTryAgain": "Imposibil de adăugat un nou profil de calitate, încercați din nou.", + "UnableToAddANewNotificationPleaseTryAgain": "Imposibil de adăugat o nouă notificare, încercați din nou.", + "UnableToLoadUISettings": "Nu se pot încărca setările UI", + "UpdateAll": "Actualizează tot", + "UpdateAutomaticallyHelpText": "Descărcați și instalați automat actualizări. Veți putea în continuare să instalați din System: Updates", + "UpdateMechanismHelpText": "Utilizați actualizatorul încorporat al lui Lidarr sau un script", + "Updates": "Actualizări", + "UpdateScriptPathHelpText": "Calea către un script personalizat care preia un pachet de actualizare extras și se ocupă de restul procesului de actualizare", + "URLBase": "Baza URL", + "UrlBaseHelpText": "Pentru suport proxy invers, implicit este gol", + "UrlBaseHelpTextWarning": "Necesită repornire pentru a intra în vigoare", + "UsenetDelay": "Întârziere Usenet", + "UsenetDelayHelpText": "Întârziați în câteva minute pentru a aștepta înainte de a lua o eliberare de la Usenet", + "Username": "Nume de utilizator", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Sucursală de utilizat pentru actualizarea Lidarr", + "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Ramură utilizată de mecanismul extern de actualizare", + "WeekColumnHeader": "Antetul coloanei săptămânii", + "OnGrab": "Pe Grab", + "OnHealthIssue": "Cu privire la problema sănătății", + "OnRename": "La Redenumire", + "OnRenameHelpText": "La Redenumire", + "OnUpgrade": "La actualizare", + "OnUpgradeHelpText": "La actualizare", + "OpenBrowserOnStart": "Deschideți browserul la pornire", + "Options": "Opțiuni", + "Original": "Original", + "PackageVersion": "Versiunea pachetului", + "PageSize": "Mărimea Paginii", + "PageSizeHelpText": "Numărul de articole de afișat pe fiecare pagină", + "Password": "Parola", + "Protocol": "Protocol", + "Proxy": "Proxy", + "ProxyBypassFilterHelpText": "Folosiți „,” ca separator și „*.” ca un wildcard pentru subdomenii", + "RemoveFromBlocklist": "Eliminați de pe lista neagră", + "RemoveFromDownloadClient": "Eliminați din clientul de descărcare", + "RemoveFromQueue": "Eliminați din coadă", + "RemoveHelpTextWarning": "Eliminarea va elimina descărcarea și fișierele din clientul de descărcare.", + "RemoveSelected": "Șterge selecția", + "RootFolders": "Foldere Rădăcină", + "RSSSync": "Sincronizare RSS", + "RSSSyncInterval": "Interval de sincronizare RSS", + "Scheduled": "Programat", + "ShowMonitoredHelpText": "Afișați starea monitorizată sub afiș", + "ShownAboveEachColumnWhenWeekIsTheActiveView": "Afișat deasupra fiecărei coloane când săptămâna este vizualizarea activă", + "ShowPath": "Afișați calea", + "ShowRelativeDates": "Afișați datele relative", + "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Va fi utilizat atunci când este utilizată căutarea interactivă", + "Tags": "Etichete", + "Tasks": "Operațiuni", + "UnableToAddANewRemotePathMappingPleaseTryAgain": "Imposibil de adăugat o nouă mapare a căilor la distanță, încercați din nou.", + "UnableToLoadBackups": "Imposibil de încărcat copiile de rezervă", + "UnableToLoadBlocklist": "Imposibil de încărcat lista neagră", + "UnableToLoadDelayProfiles": "Nu se pot încărca profilurile de întârziere", + "UnableToLoadDownloadClientOptions": "Imposibil de încărcat opțiunile clientului de descărcare", + "UnableToLoadDownloadClients": "Nu se pot încărca clienții de descărcare", + "UnableToLoadGeneralSettings": "Nu se pot încărca setările generale", + "UnableToLoadMetadata": "Nu se pot încărca metadatele", + "UnableToLoadMetadataProfiles": "Nu se pot încărca profilurile de întârziere", + "UnableToLoadNamingSettings": "Nu se pot încărca setările de denumire", + "UnableToLoadQualityDefinitions": "Nu se pot încărca definițiile de calitate", + "UnableToLoadQualityProfiles": "Nu se pot încărca profilurile de calitate", + "UnableToLoadReleaseProfiles": "Nu se pot încărca profilurile de întârziere", + "UnableToLoadRemotePathMappings": "Imposibil de încărcat mapări de cale la distanță", + "UnableToLoadTags": "Nu se pot încărca etichete", + "UseHardlinksInsteadOfCopy": "Folosiți Hardlink-uri în loc de Copy", + "ApplyTagsHelpTexts1": "Cum se aplică etichete filmelor selectate", + "ApplyTagsHelpTexts2": "Adăugare: adăugați etichetele la lista de etichete existentă", + "ApplyTagsHelpTexts3": "Eliminați: eliminați etichetele introduse", + "ApplyTagsHelpTexts4": "Înlocuire: înlocuiți etichetele cu etichetele introduse (nu introduceți etichete pentru a șterge toate etichetele)", + "ArtistAlbumClickToChangeTrack": "Faceți clic pentru a schimba filmul", + "Authentication": "Autentificare", + "AuthenticationMethodHelpText": "Solicitați numele de utilizator și parola pentru a accesa Lidarr", + "AutoRedownloadFailedHelpText": "Căutați automat și încercați să descărcați o versiune diferită", + "BackupFolderHelpText": "Căile relative vor fi în directorul AppData al lui Lidarr", + "BackupNow": "Fă o copie de siguranță", + "BackupRetentionHelpText": "Copiile de rezervă automate mai vechi de perioada de păstrare vor fi curățate automat", + "Backups": "Copii de rezervă", + "BindAddress": "Adresa de legare", + "BindAddressHelpTextWarning": "Necesită repornire pentru a intra în vigoare", + "Blocklist": "Listă Neagră", + "BlocklistRelease": "Lansare pe lista neagră", + "Branch": "Ramură", + "BypassProxyForLocalAddresses": "Bypass Proxy pentru adrese locale", + "Calendar": "Calendar", + "CalendarWeekColumnHeaderHelpText": "Afișat deasupra fiecărei coloane când săptămâna este vizualizarea activă", + "Cancel": "Anulează", + "CancelMessageText": "Sigur doriți să anulați această sarcină în așteptare?", + "CertificateValidation": "Validarea certificatului", + "ChangeFileDate": "Schimbați data fișierului", + "ChangeHasNotBeenSavedYet": "Modificarea nu a fost încă salvată", + "ChmodFolder": "chmod Folder", + "ChmodFolderHelpText": "Octal, aplicat în timpul importului / redenumirii în dosare și fișiere media (fără biți de executare)", + "ChownGroupHelpText": "Numele grupului sau gid. Utilizați gid pentru sistemele de fișiere la distanță.", + "ChownGroupHelpTextWarning": "Acest lucru funcționează numai dacă utilizatorul care rulează Lidarr este proprietarul fișierului. Este mai bine să vă asigurați că clientul de descărcare folosește același grup ca Lidarr.", + "Clear": "Șterge", + "ClickToChangeQuality": "Faceți clic pentru a schimba calitatea", + "ClientPriority": "Prioritatea clientului", + "CloneIndexer": "Clonă Indexer", + "CloneProfile": "Profil de clonare", + "Columns": "Coloane", + "CompletedDownloadHandling": "Am finalizat procesarea descărcării", + "Component": "Componentă", + "Connections": "Conexiuni", + "ConnectSettings": "Setări conectare", + "CopyUsingHardlinksHelpText": "Folosiți Hardlink-uri atunci când încercați să copiați fișiere din torrente care sunt încă în curs de însămânțare", + "CopyUsingHardlinksHelpTextWarning": "Ocazional, blocarea fișierelor poate împiedica redenumirea fișierelor care sunt însămânțate. Puteți dezactiva temporar însămânțarea și puteți utiliza funcția de redenumire a lui Lidarr ca soluție.", + "CreateEmptyArtistFolders": "Creați dosare de film goale", + "CreateEmptyArtistFoldersHelpText": "Creați directoare de film lipsă în timpul scanării discului", + "CreateGroup": "Creați un grup", + "CutoffHelpText": "Odată atinsă această calitate, Lidarr nu va mai descărca filme", + "CutoffUnmet": "Calitate maximă neatinsă", + "Dates": "Date", + "DBMigration": "Migrarea DB", + "DelayProfile": "Profile de întârziere", + "DelayProfiles": "Profile de întârziere", + "Delete": "Șterge", + "DeleteBackup": "Ștergeți Backup", + "DeleteBackupMessageText": "Sigur doriți să ștergeți copia de rezervă „{0}”?", + "DeleteDelayProfile": "Ștergeți profilul de întârziere", + "DeleteDelayProfileMessageText": "Sigur doriți să ștergeți acest profil de întârziere?", + "DeleteDownloadClient": "Ștergeți clientul de descărcare", + "DeleteDownloadClientMessageText": "Sigur doriți să ștergeți clientul de descărcare „{0}”?", + "DeleteIndexer": "Ștergeți Indexer", + "DeleteIndexerMessageText": "Sigur doriți să ștergeți indexatorul „{0}”?", + "DeleteMetadataProfileMessageText": "Sigur doriți să ștergeți profilul de calitate {0}", + "DeleteNotification": "Ștergeți notificarea", + "DeleteNotificationMessageText": "Sigur doriți să ștergeți notificarea „{0}”?", + "DeleteQualityProfile": "Ștergeți profilul de calitate", + "DeleteQualityProfileMessageText": "Sigur doriți să ștergeți profilul de calitate {0}", + "DeleteReleaseProfile": "Ștergeți profilul de întârziere", + "DeleteReleaseProfileMessageText": "Sigur doriți să ștergeți acest profil de întârziere?", + "DeleteRootFolderMessageText": "Sigur doriți să ștergeți indexatorul „{0}”?", + "DeleteSelectedTrackFiles": "Ștergeți fișierele film selectate", + "DeleteSelectedTrackFilesMessageText": "Sigur doriți să ștergeți fișierele de film selectate?", + "DestinationPath": "Calea de destinație", + "DetailedProgressBar": "Bara de progres detaliată", + "DetailedProgressBarHelpText": "Afișați textul pe bara de progres", + "DiskSpace": "Spațiul pe disc", + "DownloadClient": "Client de descărcare", + "DownloadClients": "Clienți de descărcare", + "DownloadClientSettings": "Descărcați setările clientului", + "DownloadFailedCheckDownloadClientForMoreDetails": "Descărcare eșuată: verificați clientul de descărcare pentru mai multe detalii", + "DownloadFailedInterp": "Descărcarea nu a reușit: {0}", + "Downloading": "Descărcarea", + "DownloadPropersAndRepacksHelpTexts1": "Dacă actualizați sau nu automat la Propers / Repacks", + "DownloadWarningCheckDownloadClientForMoreDetails": "Avertisment privind descărcarea: verificați descărcarea clientului pentru mai multe detalii", + "Edit": "Editează", + "Enable": "Permite", + "EnableAutomaticAdd": "Activați adăugarea automată", + "EnableAutomaticSearch": "Activați Căutarea automată", + "EnableColorImpairedMode": "Activați modul afectat de culoare", + "EnableColorImpairedModeHelpText": "Stil modificat pentru a permite utilizatorilor cu deficiențe de culoare să distingă mai bine informațiile codificate prin culoare", + "EnableCompletedDownloadHandlingHelpText": "Importați automat descărcările finalizate de la clientul de descărcare", + "EnableHelpText": "Activați crearea fișierului de metadate pentru acest tip de metadate", + "EnableInteractiveSearch": "Activați căutarea interactivă", + "EnableSSL": "Activați SSL", + "EnableSslHelpText": " Necesită repornirea în funcție de administrator pentru a intra în vigoare", + "Ended": "Încheiat", + "ErrorLoadingContents": "Eroare la încărcarea conținutului", + "ErrorLoadingPreviews": "Eroare la încărcarea previzualizărilor", + "Exception": "Excepție", + "FileManagement": "Administrarea de fișiere", + "Filename": "Numele Fișierului", + "FileNames": "Numele fișierelor", + "Files": "Fișiere", + "FirstDayOfWeek": "Prima zi a săptămânii", + "Fixed": "Fix", + "Folder": "Dosar", + "Folders": "Dosare", + "ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "Pentru mai multe informații despre indexatorii individuali, faceți clic pe butoanele de informații.", + "ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "Pentru mai multe informații despre listele de import individuale, faceți clic pe butoanele de informații.", + "GeneralSettings": "setari generale", + "Global": "Global", + "GoToInterp": "Accesați {0}", + "Grab": "Apuca", + "GrabID": "Grab ID", + "GrabRelease": "Grab Release", + "GrabReleaseMessageText": "Lidarr nu a putut stabili pentru ce film a fost lansată această lansare. Este posibil ca Lidarr să nu poată importa automat această versiune. Doriți să luați „{0}”?", + "GrabSelected": "Prinderea selectată", + "IgnoredPlaceHolder": "Adăugați o restricție nouă", + "ImportedTo": "Importat în", + "ImportExtraFiles": "Importați fișiere suplimentare", + "ImportExtraFilesHelpText": "Importați fișiere suplimentare potrivite (subtitrări, nfo etc.) după importarea unui fișier de film", + "ImportFailedInterp": "Importul nu a reușit: {0}", + "Importing": "Importând", + "IncludeUnknownArtistItemsHelpText": "Afișați elemente fără film în coadă. Aceasta ar putea include filme eliminate sau orice altceva din categoria lui Lidarr", + "Indexer": "Indexator", + "IndexerPriority": "Prioritatea indexerului", + "Indexers": "Indexatori", + "IndexerSettings": "Setări Indexer", + "InteractiveSearch": "Căutare interactivă", + "Interval": "Interval", + "IsCutoffCutoff": "A tăia calea", + "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Faceți upgrade până când această calitate este atinsă sau depășită", + "IsTagUsedCannotBeDeletedWhileInUse": "Nu poate fi șters în timpul utilizării", + "LaunchBrowserHelpText": " Deschideți un browser web și navigați la pagina de pornire Lidarr la pornirea aplicației.", + "Level": "Nivel", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr acceptă orice indexer care utilizează standardul Newznab, precum și alți indexatori enumerați mai jos.", + "LidarrTags": "Etichete Lidarr", + "LoadingTrackFilesFailed": "Încărcarea fișierelor film a eșuat", + "Local": "Local", + "LocalPath": "Calea locală", + "ReleaseDate": "Date de lansare", + "ReleaseGroup": "Grup de apariție", + "ReleaseRejected": "Eliberare respinsă", + "ReleaseStatuses": "Statusul apariției", + "ReleaseWillBeProcessedInterp": "Eliberarea va fi procesată {0}", + "Reload": "Reîncarcă", + "RemotePath": "Calea la distanță", + "RemotePathHelpText": "Calea de rădăcină către directorul la care accesează Clientul de descărcare", + "RemotePathMappings": "Mapări pentru căi externe", + "Remove": "Elimina", + "SSLCertPath": "Calea SSL Cert", + "SslCertPathHelpText": "Calea către fișierul pfx", + "SslCertPathHelpTextWarning": "Necesită repornire pentru a intra în vigoare", + "SSLPort": "Port SSL", + "SslPortHelpTextWarning": "Necesită repornire pentru a intra în vigoare", + "StandardTrackFormat": "Format film standard", + "StartTypingOrSelectAPathBelow": "Începeți să tastați sau selectați o cale de mai jos", + "StartupDirectory": "Director de pornire", + "Status": "Status", + "UnableToLoadQualities": "Nu se pot încărca calități", + "Version": "Versiune", + "MinimumFreeSpace": "Spațiu liber minim", + "Missing": "Lipsește", + "RequiredPlaceHolder": "Adăugați o restricție nouă", + "Reset": "Resetați", + "RootFolder": "Folder Rădăcină", + "Score": "Scor", + "ScriptPath": "Calea Scriptului", + "Search": "Caută", + "SourcePath": "Calea sursei", + "SSLCertPassword": "Parola SSL Cert", + "SslCertPasswordHelpText": "Parola pentru fișierul pfx", + "TimeFormat": "Format de timp", + "TorrentDelayHelpText": "Întârziați în câteva minute pentru a aștepta înainte de a apuca un torent", + "Tracks": "Urmă", + "Type": "Tip", + "UILanguageHelpTextWarning": "Reîncărcare browser necesară", + "UISettings": "Setări UI", + "UnableToAddANewDownloadClientPleaseTryAgain": "Imposibil de adăugat un nou client de descărcare, încercați din nou.", + "UnableToAddANewImportListExclusionPleaseTryAgain": "Imposibil de adăugat o nouă listă de excludere, încercați din nou.", + "UnableToAddANewIndexerPleaseTryAgain": "Imposibil de adăugat un nou indexer, încercați din nou.", + "UnableToLoadTheCalendar": "Calendarul nu poate fi încărcat", + "Ungroup": "Dezgrupează", + "Usenet": "Usenet", + "Year": "An", + "YesCancel": "Da, Anulați", + "NETCore": ".NET Core", + "Path": "Cale", + "Permissions": "Permisiuni", + "Port": "Port", + "PortNumber": "Numarul portului", + "PreviewRename": "Previzualizare Redenumire", + "PriorityHelpText": "Prioritatea indexerului de la 1 (cea mai mare) la 50 (cea mai mică). Implicit: 25.", + "Profiles": "Profile", + "PropersAndRepacks": "Propers și Repacks", + "ProxyType": "Tip proxy", + "ProtocolHelpText": "Alegeți protocolul (protocolele) de utilizat și care este cel preferat atunci când alegeți între versiuni altfel egale", + "ShowMonitored": "Afișați monitorizat", + "UnableToAddANewQualityProfilePleaseTryAgain": "Imposibil de adăugat un nou profil de calitate, încercați din nou.", + "UnableToAddANewRootFolderPleaseTryAgain": "Imposibil de adăugat un nou indexer, încercați din nou.", + "UnableToLoadHistory": "Istoricul nu poate fi încărcat", + "UnableToLoadImportListExclusions": "Imposibil de încărcat excluderile din listă", + "UnableToLoadIndexerOptions": "Nu se pot încărca opțiunile indexerului", + "UnableToLoadIndexers": "Imposibil de încărcat indexatori", + "UnableToLoadLists": "Nu se pot încărca listele", + "UnableToLoadMediaManagementSettings": "Nu se pot încărca setările de gestionare media", + "UILanguageHelpText": "Limba pe care Lidarr o va folosi pentru interfața de utilizare", + "UnableToLoadRootFolders": "Imposibil de încărcat folderele rădăcină", + "UnableToLoadNotifications": "Nu se pot încărca notificările", + "UpgradeAllowedHelpText": "Dacă calitățile cu handicap nu vor fi actualizate", + "Uptime": "Timp de funcționare", + "Actions": "Acțiuni", + "ApiKeyHelpTextWarning": "Necesită repornire pentru a intra în vigoare", + "AppDataDirectory": "Directorul AppData", + "ApplyTags": "Aplicați etichete", + "ChmodFolderHelpTextWarning": "Acest lucru funcționează numai dacă utilizatorul care rulează Lidarr este proprietarul fișierului. Este mai bine să vă asigurați că clientul de descărcare setează corect permisiunile.", + "DeleteEmptyFolders": "Ștergeți folderele goale", + "DeleteImportListExclusion": "Ștergeți excluderea listei de import", + "DeleteImportListExclusionMessageText": "Sigur doriți să ștergeți această excludere din lista de importuri?", + "DeleteImportListMessageText": "Sigur doriți să ștergeți lista „{0}”?", + "DeleteTag": "Ștergeți eticheta", + "DeleteTagMessageText": "Sigur doriți să ștergeți eticheta „{0}”?", + "Docker": "Docher", + "ExtraFileExtensionsHelpTexts1": "Lista separată prin virgulă a fișierelor suplimentare de importat (.nfo va fi importat ca .nfo-orig)", + "FileDateHelpText": "Schimbați data fișierului la import / rescanare", + "Group": "grup", + "HasPendingChangesNoChanges": "Nicio Modificare", + "HasPendingChangesSaveChanges": "Salvează modificările", + "History": "Istorie", + "Host": "Gazdă", + "HostHelpText": "Aceeași gazdă pe care ați specificat-o pentru clientul de descărcare la distanță", + "Hostname": "Numele gazdei", + "ICalFeed": "Feed iCal", + "ICalHttpUrlHelpText": "Copiați această adresă URL către clienții dvs. sau faceți clic pentru a vă abona dacă browserul dvs. acceptă webcal", + "ICalLink": "Legătură iCal", + "IconForCutoffUnmet": "Pictogramă pentru Cutoff Unmet", + "IconTooltip": "Programat", + "IgnoredAddresses": "Adrese ignorate", + "IgnoredHelpText": "Eliberarea va fi respinsă dacă conține unul sau mai mulți termeni (insensibili la majuscule)", + "IllRestartLater": "Voi reporni mai târziu", + "IncludeHealthWarningsHelpText": "Includeți avertismente de sănătate", + "IncludeUnmonitored": "Includeți Unmonitored", + "LocalPathHelpText": "Calea pe care Lidarr ar trebui să o folosească pentru a accesa calea la distanță local", + "LogFiles": "Fișiere de loguri", + "Logging": "Logare", + "LogLevel": "Nivel jurnal", + "LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "Înregistrarea urmăririi trebuie activată doar temporar", + "Logs": "Jurnale", + "LongDateFormat": "Format de dată lungă", + "ManualImport": "Import Manual", + "MarkAsFailed": "Marcați ca nereușită", + "MarkAsFailedMessageText": "Sigur doriți să marcați „{0}” ca eșuat?", + "MaximumLimits": "Limite maxime", + "MaximumSize": "Dimensiune maximă", + "MaximumSizeHelpText": "Dimensiunea maximă pentru o versiune care poate fi preluată în MB. Setați la zero pentru a seta la nelimitat", + "Mechanism": "Mecanism", + "MoreInfo": "Mai multă informație", + "MustContain": "Trebuie sa contina", + "MustNotContain": "Nu trebuie să conțină", + "Name": "Nume", + "NamingSettings": "Setări de denumire", + "New": "Nou", + "NoBackupsAreAvailable": "Nu sunt disponibile copii de rezervă", + "NoHistory": "Fără istorie", + "NoLeaveIt": "Nu, lasă-l", + "NoLimitForAnyRuntime": "Fără limită pentru orice timp de rulare", + "NoLogFiles": "Nu există fișiere jurnal", + "NoMinimumForAnyRuntime": "Niciun minim pentru orice timp de rulare", + "None": "Nici unul", + "PosterSize": "Dimensiunea posterului", + "Preferred": "Preferat", + "Proper": "Corect", + "Time": "Timp", + "20MinutesTwenty": "120 de minute: {0}", + "45MinutesFourtyFive": "90 de minute: {0}", + "60MinutesSixty": "60 de minute: {0}", + "APIKey": "Cheie API", + "About": "Despre", + "AddListExclusion": "Adăugați excluderea listei", + "AddingTag": "Se adaugă etichetă", + "AdvancedSettingsHiddenClickToShow": "Ascuns, faceți clic pentru a afișa", + "AdvancedSettingsShownClickToHide": "Afișat, faceți clic pentru a ascunde", + "AgeWhenGrabbed": "Vârsta (când a fost apucat)", + "AlbumIsDownloadingInterp": "Filmul se descarcă - {0}% {1}", + "AlreadyInYourLibrary": "Deja în biblioteca dvs.", + "AlternateTitleslength1Title": "Titlu", + "AlternateTitleslength1Titles": "Titluri", + "Analytics": "Statistici", + "AnalyticsEnabledHelpText": "Trimiteți informații anonime privind utilizarea și erorile către serverele Lidarr. Aceasta include informații despre browserul dvs., ce pagini WebUI Lidarr utilizați, raportarea erorilor, precum și sistemul de operare și versiunea de execuție. Vom folosi aceste informații pentru a acorda prioritate caracteristicilor și remedierilor de erori.", + "AnalyticsEnabledHelpTextWarning": "Necesită repornire pentru a intra în vigoare", + "Automatic": "Automat", + "DelayingDownloadUntilInterp": "Întârzierea descărcării până la {0} la {1}", + "EnableRSS": "Activați RSS", + "Size": " Mărime", + "SkipFreeSpaceCheck": "Omiteți verificarea spațiului liber", + "BindAddressHelpText": "Adresă IP4 validă sau „*” pentru toate interfețele", + "CertificateValidationHelpText": "Modificați cât de strictă este validarea certificării HTTPS", + "MonoVersion": "Versiune mono", + "Label": "Etichetă" } diff --git a/src/NzbDrone.Core/Localization/Core/ru.json b/src/NzbDrone.Core/Localization/Core/ru.json index cb181248e..d9c956740 100644 --- a/src/NzbDrone.Core/Localization/Core/ru.json +++ b/src/NzbDrone.Core/Localization/Core/ru.json @@ -13,5 +13,480 @@ "QualityDefinitions": "Определения качества", "Usenet": "Usenet", "ReleaseGroup": "Релиз группа", - "Duration": "Длительность" + "Duration": "Длительность", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr поддерживает многие популярные торрент и usenet-клиенты для скачивания.", + "RemoveCompleted": "Удаление завершено", + "RemoveFromDownloadClient": "Удалить из загрузочного клиента", + "RemoveFromQueue": "Удалить из очереди", + "RemoveTagRemovingTag": "Удаление тега", + "RequiredHelpText": "Релиз должен содержать хотя бы одно из этих условий (без учета регистра)", + "Restore": "Восстановить", + "RestoreBackup": "Восстановить из резервной копии", + "RssSyncIntervalHelpText": "Интервал в минутах. Установите 0 чтобы выключить (остановит все автоматические захваты релизов)", + "Settings": "Настройки", + "ShowMonitored": "Показать отслеживаемые", + "ShowMonitoredHelpText": "Показывать отслеживаемый статус под плакатом", + "ShownAboveEachColumnWhenWeekIsTheActiveView": "Отображается над каждым столбцом, когда неделя активна", + "SSLCertPassword": "Пароль SSL сертификата", + "TestAllClients": "Тестировать всех клиентов", + "TestAllIndexers": "Тестировать все индексаторы", + "TestAllLists": "Тестировать все листы", + "UpdateAll": "Обновить всё", + "UpdateMechanismHelpText": "Используйте встроенное средство обновления Lidarr или скрипт", + "MaximumSizeHelpText": "Максимальный размер релиза в МВ. Установите 0 чтобы снять все ограничения", + "Message": "Сообщение", + "MinimumAge": "Минимальный возраст", + "MinimumAgeHelpText": "Только Usenet: минимальный возраст в минутах для NZB до того, как они будут получены. Используйте это, чтобы дать новым выпускам время распространиться среди вашего поставщика usenet.", + "MinimumFreeSpace": "Минимальное свободное место", + "MinimumFreeSpaceWhenImportingHelpText": "Не импортировать, если останется меньше указанного места на диске", + "MinimumLimits": "Минимальные ограничения", + "Missing": "Не найдено", + "MoreInfo": "Ещё инфо", + "NoMinimumForAnyRuntime": "Нет минимума для любого времени", + "None": "Ничто", + "NotificationTriggers": "Триггеры уведомления", + "OnGrabHelpText": "При захвате", + "OnRenameHelpText": "При переименовании", + "OnUpgradeHelpText": "При обновлении", + "OpenBrowserOnStart": "Открывать браузер при запуске", + "Options": "Опции", + "Original": "Оригинал", + "Path": "Путь", + "Permissions": "Разрешения", + "PortNumber": "Номер порта", + "PosterSize": "Размер постера", + "Preferred": "Предпочитаемый", + "PreviewRename": "Предпросмотр переименований", + "PriorityHelpText": "Приоритет индексаторов от 1 (наивысший) до 50 (низший). По-умолчанию: 25.", + "Profiles": "Профили", + "Proper": "Правильный", + "PropersAndRepacks": "Проперы и репаки", + "Protocol": "Протокол", + "ProtocolHelpText": "Выберите, какой протокол (ы) использовать и какой из них предпочтительнее при выборе между одинаковыми версиями", + "ProxyBypassFilterHelpText": "Используйте ',' в качестве разделителя и '*.' как подстановочный знак для поддоменов", + "ProxyPasswordHelpText": "Вам нужно ввести имя пользователя и пароль только если они необходимы. В противном случае оставьте их пустыми.", + "ProxyUsernameHelpText": "Вам нужно ввести имя пользователя и пароль только если они необходимы. В противном случае оставьте их пустыми.", + "PublishedDate": "Дата публикации", + "QualityProfile": "Профиль качества", + "QualityProfiles": "Профили качества", + "QualitySettings": "Настройки качества", + "Queue": "Очередь", + "ReadTheWikiForMoreInformation": "Прочтите Wiki для получения дополнительной информации", + "Real": "Настоящий", + "Reason": "Причина", + "RecycleBinCleanupDaysHelpText": "Установите 0, чтобы отключить автоматическую очистку", + "RecycleBinCleanupDaysHelpTextWarning": "Файлы в корзине старше указанного количества дней будут очищены автоматически", + "RecycleBinHelpText": "Файлы фильмов будут попадать сюда при удалении", + "RecyclingBin": "Мусорная корзина", + "RecyclingBinCleanup": "Очистка мусорной корзины", + "Redownload": "Перезакачать", + "Refresh": "Обновить", + "RefreshInformationAndScanDisk": "Обновить информацию и просканировать диск", + "RefreshScan": "Обновить & сканировать", + "ReleaseDate": "Дата выпуска", + "RemoveCompletedDownloadsHelpText": "Удалить импортированные загрузки из истории загрузок клиента", + "RemoveDownloadsAlert": "Настройки удаления были перенесены в отдельные настройки клиента загрузки выше.", + "RemoveFailed": "Удаление не удалось", + "RemoveFailedDownloadsHelpText": "Удалить неудачные загрузки из истории загрузок клиента", + "RemoveFilter": "Удалить фильтр", + "RemoveSelectedMessageText": "Удалить выбранные элементы из черного списка?", + "RemoveTagExistingTag": "Существующий тэг", + "RenameTracksHelpText": "Lidarr будет использовать существующее имя файла, если переименование отключено", + "ReplaceIllegalCharacters": "Замените недопустимые символы", + "ReplaceIllegalCharactersHelpText": "Замените недопустимые символы. Если этот флажок не установлен, Lidarr удалит их", + "RequiredPlaceHolder": "Добавить новое ограничение", + "RequiresRestartToTakeEffect": "Для вступления в силу требуется перезапуск", + "RescanAfterRefreshHelpText": "Повторно просканируйте папку с фильмом после обновления фильма", + "RescanAfterRefreshHelpTextWarning": "Lidarr не будет автоматически обнаруживать изменения в файлах, если не установлен параметр «Всегда»", + "RescanArtistFolderAfterRefresh": "Повторно сканировать папку с фильмом после обновления", + "Reset": "Сбросить", + "ResetAPIKey": "Сбросить API ключ", + "ResetAPIKeyMessageText": "Вы уверены, что хотите сбросить Ваш API ключ?", + "Restart": "Перезапустить", + "RestartLidarr": "Перезапустить Lidarr", + "Result": "Результат", + "Retention": "Удержание", + "RetentionHelpText": "Только Usenet: установите нулевое значение для неограниченного хранения", + "RetryingDownloadInterp": "Повторная загрузка {0} в {1}", + "RootFolder": "Корневой каталог", + "RootFolders": "Корневые папки", + "RSSSync": "Синхронизация RSS", + "RSSSyncInterval": "Интервал синхронизации RSS", + "Scheduled": "Запланировано", + "Score": "Очков", + "ScriptPath": "Путь к скрипту", + "Search": "Поиск", + "SearchAll": "Искать все", + "SearchForMissing": "Поиск пропавших", + "SearchSelected": "Искать выделенные", + "Security": "Безопасность", + "SendAnonymousUsageData": "Отправить анонимные данные об использовании", + "SetPermissions": "Установить разрешения", + "SetPermissionsLinuxHelpText": "Следует ли запускать chmod при импорте / переименовании файлов?", + "SetPermissionsLinuxHelpTextWarning": "Если вы не знаете, что делают эти настройки, не меняйте их.", + "ShortDateFormat": "Короткий формат даты", + "ShowCutoffUnmetIconHelpText": "Показывать значок для файлов, когда порог не соблюден", + "ShowDateAdded": "Показать добавленные даты", + "ShowPath": "Показать путь", + "ShowQualityProfile": "Показать профиль качества", + "ShowQualityProfileHelpText": "Показать профиль качества под постером", + "ShowRelativeDates": "Показать относительные даты", + "ShowRelativeDatesHelpText": "Показывать относительные (сегодня / вчера / и т. д.) или абсолютные даты", + "ShowSearch": "Показать поиск", + "ShowSearchActionHelpText": "Показать копку поиска по наведению", + "ShowSizeOnDisk": "Показать объём на диске", + "Size": " Размер", + "SkipFreeSpaceCheck": "Пропустить проверку свободного места", + "SkipFreeSpaceCheckWhenImportingHelpText": "Используется, когда Lidarr не может найти свободное место в корневой папке фильма", + "SorryThatAlbumCannotBeFound": "Извините, этот фильм не найден.", + "SorryThatArtistCannotBeFound": "Извините, этот фильм не найден.", + "Source": "Источник", + "SourcePath": "Исходный путь", + "SslCertPasswordHelpText": "Пароль pfx файла", + "SslCertPasswordHelpTextWarning": "Для вступления в силу требуется перезапуск", + "SSLCertPath": "Путь SSL сертификата", + "SslCertPathHelpText": "Путь к pfx файлу", + "SslPortHelpTextWarning": "Для вступления в силу требуется перезапуск", + "StandardTrackFormat": "Стандартный формат фильма", + "StartTypingOrSelectAPathBelow": "Начните вводить или выберите путь ниже", + "StartupDirectory": "Каталог автозагрузки", + "Style": "Стиль", + "SuccessMyWorkIsDoneNoFilesToRename": "Успех! Моя работа сделана, файлов для переименования нет.", + "SuccessMyWorkIsDoneNoFilesToRetag": "Успех! Моя работа сделана, файлов для переименования нет.", + "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS не поддерживается этим индексатором", + "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Поиск не поддерживается с этим индексатором", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Будет использовано для автоматических поисков через интерфейс или Lidarr", + "TagIsNotUsedAndCanBeDeleted": "Тег не используется и может быть удален", + "Tags": "Тэги", + "Tasks": "Задачи", + "TestAll": "Тестировать все", + "ThisCannotBeCancelled": "Нельзя отменить после запуска без перезапуска Whisparr.", + "ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "Это будет применяться ко всем индексаторам, пожалуйста, следуйте установленным ими правилам", + "TimeFormat": "Формат времени", + "TorrentDelay": "Задержка торрента", + "TorrentDelayHelpText": "Задержка в минутах перед скачиванием торрента", + "Torrents": "Торренты", + "TotalFileSize": "Общий объем файла", + "Track": "След", + "Type": "Тип", + "UISettings": "Настройки пользовательского интерфейса", + "UnableToAddANewDownloadClientPleaseTryAgain": "Не удалось добавить новый клиент загрузки, попробуйте еще раз.", + "UnableToAddANewImportListExclusionPleaseTryAgain": "Не удалось добавить новое исключение в список, попробуйте еще раз.", + "UnableToAddANewIndexerPleaseTryAgain": "Не удалось добавить новый индексатор, повторите попытку.", + "UnableToAddANewListPleaseTryAgain": "Не удалось добавить новый список, попробуйте еще раз.", + "UnableToAddANewMetadataProfilePleaseTryAgain": "Не удалось добавить новый профиль качества. Повторите попытку.", + "UnableToAddANewNotificationPleaseTryAgain": "Невозможно добавить новое уведомление, попробуйте еще раз.", + "UnableToAddANewQualityProfilePleaseTryAgain": "Не удалось добавить новый профиль качества. Повторите попытку.", + "UnableToAddANewRemotePathMappingPleaseTryAgain": "Не удалось добавить новый удаленный путь, попробуйте еще раз.", + "UnableToAddANewRootFolderPleaseTryAgain": "Невозможно добавить новый пользовательский формат, попробуйте ещё раз.", + "UnableToLoadIndexers": "Не удалось загрузить индексаторы", + "UnableToLoadLists": "Невозможно загрузить списки", + "UnableToLoadMediaManagementSettings": "Не удалось загрузить настройки управления медиа", + "UnableToLoadMetadata": "Невозможно загрузить метаданные", + "UnableToLoadMetadataProfiles": "Невозможно загрузить профили задержки", + "UnableToLoadNamingSettings": "Не удалось загрузить настройки именования", + "UnableToLoadNotifications": "Невозможно загрузить уведомления", + "UnableToLoadQualities": "Невозможно загрузить качества", + "UnableToLoadQualityDefinitions": "Не удалось загрузить определения качества", + "UnableToLoadQualityProfiles": "Не удалось загрузить профили качества", + "UnableToLoadTheCalendar": "Не удалось загрузить календарь", + "UnableToLoadUISettings": "Не удалось загрузить настройки пользовательского интерфейса", + "Unmonitored": "Не отслеживается", + "UnmonitoredHelpText": "Включите неконтролируемые фильмы в ленту iCal", + "UpdateAutomaticallyHelpText": "Автоматически загружать и устанавливать обновления. Вы так же можете установить в Система: Обновления", + "UpdateScriptPathHelpText": "Путь к пользовательскому скрипту, который обрабатывает остатки после процесса обновления", + "UpgradeAllowedHelpText": "Если отключено, то качества не будут обновляться", + "Uptime": "Время работы", + "URLBase": "Базовый URL", + "UrlBaseHelpText": "Для поддержки обратного прокси, по умолчанию пусто", + "YesCancel": "Да, отменить", + "OnApplicationUpdateHelpText": "О обновлении приложения", + "OnGrab": "При захвате", + "OnHealthIssue": "О проблемах в системе", + "OnRename": "При переименовании", + "OnUpgrade": "При обновлении", + "PackageVersion": "Версия пакета", + "PageSize": "Размер страницы", + "PageSizeHelpText": "Количество показываемое на каждой страницы", + "Password": "Пароль", + "ProxyType": "Тип прокси", + "Reorder": "Изменить порядок", + "UseHardlinksInsteadOfCopy": "Используйте жесткие ссылки вместо копирования", + "Year": "Год", + "UsenetDelay": "Usenet задержки", + "UsenetDelayHelpText": "Задержка в минутах перед получением релиза из Usenet", + "UseProxy": "Использовать прокси", + "UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent, представленный приложением, который вызывает API", + "Username": "Пользователь", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Ветвь для обновления Lidarr", + "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Ветвь, используемая внешним механизмом обновления", + "Version": "Версия", + "WeekColumnHeader": "Заголовок столбца с неделями", + "UnableToLoadBlocklist": "Не удалось загрузить черный список", + "UnableToLoadDelayProfiles": "Невозможно загрузить профили задержки", + "UnableToLoadDownloadClientOptions": "Не удалось загрузить параметры клиента загрузки", + "AddingTag": "Добавить ярлык", + "AddListExclusion": "Добавить исключения списка", + "ApplyTags": "Применить тэги", + "ApplyTagsHelpTexts1": "Как добавить ярлыки к выбранным фильмам", + "ApplyTagsHelpTexts2": "Добавить: добавить ярлыки к существующему списку", + "ApplyTagsHelpTexts3": "Убрать: Убрать введенные тэги", + "ApplyTagsHelpTexts4": "Заменить: Изменить существующие тэги на введенные тэги (оставьте пустым чтобы очистить все тэги)", + "ArtistAlbumClickToChangeTrack": "Нажать для смены фильма", + "Authentication": "Аутентификация", + "AuthenticationMethodHelpText": "Необходим логин и пароль для доступа в Lidarr", + "AutoRedownloadFailedHelpText": "Автоматически искать и пытаться скачать разные релизы", + "BackupFolderHelpText": "Относительные пути будут в каталоге AppData Lidarr", + "BackupNow": "Сделать резервную копию", + "BackupRetentionHelpText": "Автоматические резервные копии старше указанного периода будут автоматически удалены", + "Backups": "Резервные копии", + "BindAddress": "Привязать адрес", + "BindAddressHelpTextWarning": "Для вступления в силу требуется перезапуск", + "Blocklist": "Черный список", + "BlocklistHelpText": "Запрещает Lidarr автоматически получать этот релиз повторно", + "BlocklistRelease": "Релиз из черного списка", + "Branch": "Ветка", + "BypassProxyForLocalAddresses": "Обход прокси для локальных адресов", + "Calendar": "Календарь", + "CalendarWeekColumnHeaderHelpText": "Отображается над каждым столбцом, когда неделя активна", + "Cancel": "Отменить", + "CancelMessageText": "Вы уверены, что хотите убрать данную задачу из очереди?", + "CertificateValidation": "Проверка сертификата", + "ChangeFileDate": "Изменить дату файла", + "ChangeHasNotBeenSavedYet": "Изменения ещё не вступили в силу", + "ChmodFolder": "chmod Папка", + "ChmodFolderHelpText": "Восьмеричный, применяется при импорте / переименовании к медиа-папкам и файлам (без битов выполнения)", + "ChmodFolderHelpTextWarning": "Это работает только если пользователь Lidarr является владельцем файла. Проверьте, что программа для скачивания установила правильные разрешения.", + "ChownGroupHelpText": "Имя группы или id. Используйте id для отдалённой файловой системы.", + "ChownGroupHelpTextWarning": "Это работает только если пользователь Lidarr является владельцем файла. Проверьте, что программа для скачивания использует туже самую группу, что и Lidarr.", + "Clear": "Очистить", + "ClientPriority": "Приоритет клиента", + "CloneIndexer": "Клонировать индексер", + "CloneProfile": "Клонировать профиль", + "Columns": "Столбцы", + "CompletedDownloadHandling": "Обработка завершенных скачиваний", + "Component": "Компонент", + "Connections": "Соединения", + "ConnectSettings": "Настройки соединения", + "CopyUsingHardlinksHelpText": "Используйте встроенные ссылки при попытке скопировать файлы с торрентов, которые все еще загружаются", + "CopyUsingHardlinksHelpTextWarning": "Блокировка файлов иногда может мешать переименованию файлов во время раздачи. Можно временно приостановить раздачу и использовать функции Lidarr для переименования.", + "CreateEmptyArtistFolders": "Создать пустые папки для фильмов", + "CreateEmptyArtistFoldersHelpText": "Создать папки для не найденных фильмов при сканировании", + "CreateGroup": "Создать группу", + "CutoffHelpText": "Lidarr перестанет скачивать фильмы после достижения указанного качества", + "Dates": "Даты", + "DelayProfile": "Профиль приостановки", + "DelayProfiles": "Профиль задержки", + "Delete": "Удалить", + "DeleteBackup": "Удалить резервную копию", + "DeleteBackupMessageText": "Вы уверены, что хотите удалить резервную копию '{0}'?", + "DeleteDelayProfile": "Удалить профиль задержки", + "DeleteDelayProfileMessageText": "Вы уверены, что хотите удалить этот профиль задержки?", + "DeleteDownloadClient": "Удалить программу для скачивания", + "DeleteDownloadClientMessageText": "Вы уверены, что хотите удалить программу для скачивания '{0}'?", + "DeleteEmptyFolders": "Удалить пустые папки", + "DeleteImportListExclusion": "Удалить лист исключения для импорта", + "DeleteImportListExclusionMessageText": "Вы уверены, что хотите удалить этот лист исключений?", + "DeleteImportListMessageText": "Вы уверены, что хотите удалить список '{0}'?", + "DeleteIndexer": "Удалить индексер", + "DeleteIndexerMessageText": "Вы уверены что хотите удалить индексер '{0}'?", + "DeleteMetadataProfileMessageText": "Вы действительно хотите удалить профиль качества {0}", + "DeleteNotification": "Удалить уведомление", + "DeleteNotificationMessageText": "Вы уверены, что хотите удалить уведомление '{0}'?", + "DeleteQualityProfile": "Удалить качественный профиль", + "DeleteQualityProfileMessageText": "Вы действительно хотите удалить профиль качества {0}", + "DeleteReleaseProfile": "Удалить профиль задержки", + "DeleteReleaseProfileMessageText": "Вы уверены, что хотите удалить этот профиль задержки?", + "DeleteRootFolderMessageText": "Вы уверены что хотите удалить индексер '{0}'?", + "DeleteSelectedTrackFiles": "Удалить выбранные файлы фильма", + "DeleteSelectedTrackFilesMessageText": "Вы уверены, что хотите удалить выбранные файлы?", + "DeleteTag": "Удалить тэг", + "DeleteTagMessageText": "Вы уверены, что хотите удалить тэг '{0}'?", + "DestinationPath": "Путь назначения", + "DetailedProgressBar": "Подробный индикатор выполнения", + "DetailedProgressBarHelpText": "Показать текст на индикаторе выполнения", + "DiskSpace": "Дисковое пространство", + "DownloadClient": "Загрузчик", + "DownloadClients": "Клиенты для скачивания", + "DownloadClientSettings": "Настройки клиента скачиваний", + "DownloadFailedCheckDownloadClientForMoreDetails": "Неудачное скачивание: подробности в программе для скачивания", + "DownloadFailedInterp": "Неудачное скачивание: {0}", + "Downloading": "Скачивается", + "DownloadPropersAndRepacksHelpTexts1": "Следует ли автоматически обновляться до Propers / Repacks", + "DownloadWarningCheckDownloadClientForMoreDetails": "Предупреждения по скачиванию: подробности в программе для скачивания", + "Edit": "Редактирование", + "Enable": "Включить", + "EnableAutomaticAdd": "Включить автоматическое добавление", + "EnableAutomaticSearch": "Включить автоматический поиск", + "EnableColorImpairedMode": "Включить режим для слабовидящих", + "EnableColorImpairedModeHelpText": "Измененный стиль, позволяющий пользователям с нарушением цвета лучше различать информацию с цветовой кодировкой", + "EnableCompletedDownloadHandlingHelpText": "Автоматически импортировать завершенные скачивания", + "EnableHelpText": "Создавать файл метаданных для это типа метаданных", + "EnableInteractiveSearch": "Включить интерактивный поиск", + "EnableRSS": "Включить RSS", + "EnableSSL": "Включить SSL", + "EnableSslHelpText": " Требуется перезапуск от администратора", + "Ended": "Закончился", + "ErrorLoadingContents": "Ошибка при загрузке содержимого", + "ErrorLoadingPreviews": "Ошибка при загрузке предпросмотра", + "Exception": "Исключение", + "Fixed": "Исправлено", + "Folder": "Папка", + "Folders": "Папки", + "ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "Для дополнительной информации по индексаторам нажмите кнопку с информацией.", + "ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "Для дополнительной информации по спискам импорта нажмите эту кнопку.", + "Global": "Глобальный", + "GoToInterp": "Перейти {0}", + "Grab": "Захватить", + "GrabID": "Захватить ID", + "GrabRelease": "Захватить релиз", + "GrabReleaseMessageText": "Lidarr не смог определить для какого фильма был релиз. Lidarr не сможет автоматически его импортировать. Хотите захватить '{0}'?", + "GrabSelected": "Захватить выбранные", + "Group": "Группа", + "HasPendingChangesNoChanges": "Нет изменений", + "HasPendingChangesSaveChanges": "Сохранить изменения", + "History": "История", + "Host": "Хост", + "HostHelpText": "Тот же хост, который вы указали для удаленного клиента загрузки", + "Hostname": "Имя хоста", + "ICalFeed": "Лента iCal", + "ICalHttpUrlHelpText": "Скопировать URL или нажать чтобы подписаться, если ваш браузер поддерживает webcal", + "ICalLink": "Ссылка iCal", + "IconForCutoffUnmet": "Значок \"Не выполнено отсечение\"", + "IconTooltip": "Запланировано", + "IgnoredAddresses": "Проигнорированные адреса", + "IgnoredHelpText": "Релиз будет не принят, если он содержит один или несколько терминов (регистрозависимы)", + "IgnoredPlaceHolder": "Добавить новое ограничение", + "IllRestartLater": "Перезапущу позднее", + "ImportedTo": "Импортировано в", + "ImportExtraFilesHelpText": "Импортировать совпадающие экстра файлы (субтитры, nfo, и т.д.) после импорта фильма", + "ImportFailedInterp": "Неудачный импорт: {0}", + "Importing": "Импортирование", + "IncludeHealthWarningsHelpText": "Включая предупреждения о здоровье", + "IncludeUnknownArtistItemsHelpText": "Показывать без фильма в очереди. Может включать в себя удалённые фильмы или что-то еще в списке", + "IncludeUnmonitored": "Включая неотслеживаемые", + "Indexer": "Индексатор", + "InteractiveSearch": "Интерактивный поиск", + "IsCutoffCutoff": "Прекращение", + "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Обновлять пока это качество не будет достигнуто или превышено", + "IsTagUsedCannotBeDeletedWhileInUse": "Невозможно удалить во время использования", + "LaunchBrowserHelpText": " Открывать браузер и переходить на страницу Lidarr при запуске программы.", + "Level": "Уровень", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr поддерживает любой индексатор, использующий стандарт Newznab, а также другие индексаторы, перечисленные ниже.", + "LidarrTags": "Теги Lidarr-а", + "Reload": "Перезагрузить", + "RemotePathHelpText": "Корневой путь к каталогу, к которому имеет доступ клиент загрузки", + "RemotePathMappings": "Сопоставления удаленного пути", + "Remove": "Удалить", + "RemovedFromTaskQueue": "Удалено из очереди задач", + "Analytics": "Аналитика", + "AnalyticsEnabledHelpText": "Отправлять в Lidarr информацию о использовании и ошибках. Анонимная статистика включает в себя информацию о браузере, какие страницы загружены, сообщения об ошибках, а так же операционной системе. Мы используем эту информацию для выявления ошибок, а так же для разработки нового функционала.", + "AnalyticsEnabledHelpTextWarning": "Для вступления в силу требуется перезапуск", + "RestartNow": "Перезапустить сейчас", + "ShowUnknownArtistItems": "Показать неизвестные элементы фильма", + "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Используется при интерактивном поиске", + "Updates": "Обновления", + "MaximumSize": "Максимальный размер", + "Mechanism": "Механизм", + "MediaInfo": "Медиа данные", + "MediaManagementSettings": "Насторйки медиа управлением", + "Medium": "Средний", + "MetadataSettings": "Настройки метаданных", + "MIA": "MIA", + "Mode": "Режим", + "Monitored": "Отслеживается", + "NoLimitForAnyRuntime": "Нет ограничений для любого времени", + "NoLogFiles": "Нет файлов журнала", + "NoUpdatesAreAvailable": "Нет обновлений", + "Tracks": "След", + "OnApplicationUpdate": "О обновлении приложения", + "Proxy": "Прокси", + "RemoveFromBlocklist": "Удалить из черного списка", + "RemoveHelpTextWarning": "Удаление приведет к удалению загрузки и файла(ов) из клиента загрузки.", + "RemoveSelected": "Удалить выбранное", + "SslCertPathHelpTextWarning": "Для вступления в силу требуется перезапуск", + "SSLPort": "SSL порт", + "Time": "Время", + "UrlBaseHelpTextWarning": "Для вступления в силу требуется перезапуск", + "UILanguageHelpText": "Язык, который Lidarr будет использовать для пользовательского интерфейса", + "UILanguageHelpTextWarning": "Требуется перезагрузка браузера", + "UnableToLoadBackups": "Невозможно загрузить резервные копии", + "UnableToLoadDownloadClients": "Невозможно загрузить загрузчики", + "UnableToLoadGeneralSettings": "Невозможно загрузить общие настройки", + "UnableToLoadHistory": "Не удалось загрузить историю", + "UnableToLoadImportListExclusions": "Не удалось загрузить исключения из списка", + "UnableToLoadReleaseProfiles": "Невозможно загрузить профили задержки", + "UnableToLoadRemotePathMappings": "Не удалось загрузить сопоставления удаленного пути", + "UnableToLoadRootFolders": "Невозможно загрузить корневые папки", + "UnableToLoadTags": "Невозможно загрузить теги", + "Ungroup": "Разгруппировать", + "Actions": "Действия", + "ApiKeyHelpTextWarning": "Для вступления в силу требуется перезапуск", + "AppDataDirectory": "AppData директория", + "ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Для получения дополнительной информации о каждом из клиентов загрузки нажмите на кнопки с дополнительной информацией.", + "ExtraFileExtensionsHelpTexts1": "Список экстра файлов для импорта разделенных двоеточием(.info будет заменено на .nfo-orig)", + "FileManagement": "Управление файлами", + "Filename": "Имя файла", + "FileNames": "Имена файлов", + "Files": "Файлы", + "FirstDayOfWeek": "Первый день недели", + "GeneralSettings": "Основные настройки", + "IndexerPriority": "Приоритет индексаторов", + "Indexers": "Индексаторы", + "IndexerSettings": "Настройки индексатора", + "Interval": "Интервал", + "LoadingTrackFilesFailed": "Неудачная загрузка файлов фильма", + "Local": "Местный", + "LocalPathHelpText": "Путь, который Lidarr должен использовать для локального доступа к удаленному пути", + "LogFiles": "Файлы журнала", + "Logging": "Журналирование", + "LogLevel": "Уровень журнала", + "LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "Отслеживание журнала желательно включать только на короткое время", + "LongDateFormat": "Длинный формат даты", + "ManualImport": "Ручной импорт", + "MarkAsFailed": "Пометить как неудачный", + "MarkAsFailedMessageText": "Вы уверены, что хотите отметить '{0}' как неудачный?", + "MaximumLimits": "Максимальные ограничения", + "NETCore": ".NET", + "MustContain": "Должен содержать", + "MustNotContain": "Не должен содержать", + "Name": "Имя", + "NamingSettings": "Настройки именования", + "New": "Новый", + "NoBackupsAreAvailable": "Нет резервных копий", + "NoHistory": "Нет истории", + "NoLeaveIt": "Нет, оставить", + "Port": "Порт", + "ReleaseRejected": "Релиз отклонен", + "ReleaseStatuses": "Статус релиза", + "ReleaseWillBeProcessedInterp": "Выпуск будет обработан {0}", + "Status": "Статус", + "CutoffUnmet": "Порог невыполнен", + "DBMigration": "Перенос БД", + "20MinutesTwenty": "60 минут: {0}", + "45MinutesFourtyFive": "60 минут: {0}", + "60MinutesSixty": "60 минут: {0}", + "APIKey": "API ключ", + "About": "Подробности", + "AdvancedSettingsHiddenClickToShow": "Скрыто, нажмите чтобы показать", + "AdvancedSettingsShownClickToHide": "Показано, нажмите чтобы скрыть", + "AgeWhenGrabbed": "Возраст (когда захвачен)", + "AlbumIsDownloadingInterp": "Фильм скачивается - {0}% {1}", + "AlreadyInYourLibrary": "Уже в вашей библиотеке", + "AlternateTitles": "Альтернативное название", + "AlternateTitleslength1Title": "Название", + "AlternateTitleslength1Titles": "Названия", + "Automatic": "Автоматически", + "DelayingDownloadUntilInterp": "Приостановить скачивание до {0} в {1}", + "Logs": "Журналы", + "BindAddressHelpText": "Действительный IP4-адрес или '*' для всех интерфейсов", + "CertificateValidationHelpText": "Изменить строгое подтверждение сертификации НТТР", + "MonoVersion": "Моно версия", + "AddedArtistSettings": "Добавленные Авторские Настройки", + "AllExpandedCollapseAll": "Свернуть Все", + "AllExpandedExpandAll": "Развернуть Все", + "AllowArtistChangeClickToChangeArtist": "Нажмите, чтобы изменить автора", + "AddMissing": "Добавить отсутствующие", + "AddNewItem": "Добавить Новый Элемент", + "Label": "Ярлык" } diff --git a/src/NzbDrone.Core/Localization/Core/sk.json b/src/NzbDrone.Core/Localization/Core/sk.json index 0967ef424..09992b2a4 100644 --- a/src/NzbDrone.Core/Localization/Core/sk.json +++ b/src/NzbDrone.Core/Localization/Core/sk.json @@ -1 +1,48 @@ -{} +{ + "ResetAPIKeyMessageText": "Naozaj chcete obnoviť kľúč API?", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Vetva, ktorá sa má použiť k aktualizácií Radarru", + "ArtistAlbumClickToChangeTrack": "Kliknutím zmeníte film", + "Authentication": "Overenie", + "AuthenticationMethodHelpText": "Vyžadovať používateľské meno a heslo pre prístup k Radarru", + "AutoRedownloadFailedHelpText": "Automaticky vyhľadať a pokúsiť sa stiahnuť iné vydanie", + "BackupFolderHelpText": "Relatívne cesty budú v priečinku AppData Radarru", + "BackupNow": "Zálohovať teraz", + "BackupRetentionHelpText": "Automatické zálohy staršie než doba uchovania budú automaticky vyčistené", + "Backups": "Zálohy", + "Branch": "Vetva", + "BypassProxyForLocalAddresses": "Obísť proxy pre miestne adresy", + "Calendar": "Kalendár", + "Cancel": "Zrušiť", + "DeleteBackupMessageText": "Naozaj chcete zmazať značku formátu {0} ?", + "DeleteDelayProfileMessageText": "Naozaj chcete zmazať tento profil oneskorenia?", + "DeleteDownloadClientMessageText": "Naozaj chcete zmazať značku formátu {0} ?", + "ShowQualityProfile": "Pridajte profil kvality", + "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Vetva používaná externým mechanizmom aktualizácie", + "Actions": "Akcie", + "ApplyTags": "Použiť značky", + "ApplyTagsHelpTexts1": "Ako použiť značky na vybrané filmy", + "ApplyTagsHelpTexts2": "Pridať: Pridajte značky do existujúceho zoznamu značiek", + "ApplyTagsHelpTexts3": "Odobrať: Odoberie zadané značky", + "ApplyTagsHelpTexts4": "Nahradiť: Nahradí značky zadanými značkami (pre vymazanie všetkých značiek, nezadávajte žiadne)", + "DeleteImportListExclusionMessageText": "Naozaj chcete toto vylúčenie importného zoznamu zmazať?", + "DeleteImportListMessageText": "Naozaj chcete zmazať značku formátu {0} ?", + "DeleteIndexerMessageText": "Naozaj chcete zmazať značku formátu {0} ?", + "DeleteMetadataProfileMessageText": "Naozaj chcete zmazať tento profil oneskorenia?", + "DeleteNotificationMessageText": "Naozaj chcete zmazať značku formátu {0} ?", + "DeleteQualityProfileMessageText": "Naozaj chcete zmazať tento profil oneskorenia?", + "DeleteReleaseProfileMessageText": "Naozaj chcete zmazať tento profil oneskorenia?", + "DeleteRootFolderMessageText": "Naozaj chcete zmazať značku formátu {0} ?", + "DeleteTagMessageText": "Naozaj chcete zmazať značku formátu {0} ?", + "APIKey": "Kľúč API", + "About": "O", + "AddListExclusion": "Pridať vylúčenie zoznamu", + "AddingTag": "Pridávanie značky", + "AgeWhenGrabbed": "Vek (po uchopení)", + "AlreadyInYourLibrary": "Už vo vašej knižnici", + "AlternateTitles": "Alternatívny názov", + "Analytics": "Analytika", + "AnalyticsEnabledHelpText": "Odosielajte anonymné informácie o používaní a chybách na servery Readarru. To zahŕňa informácie o vašom prehliadači, ktoré stránky Lidarr WebUI používate, hlásenia chýb a taktiež verziu operačného systému a spúšťacieho prostredia. Tieto informácie použijeme k uprednostňovaniu funkcií a oprav chýb.", + "Automatic": "Automatický", + "AppDataDirectory": "Priečinok AppData", + "BindAddressHelpText": "Platná adresa IP4 alebo '*' pre všetky rozhrania" +} diff --git a/src/NzbDrone.Core/Localization/Core/sv.json b/src/NzbDrone.Core/Localization/Core/sv.json index bce36d4c4..6ea518a50 100644 --- a/src/NzbDrone.Core/Localization/Core/sv.json +++ b/src/NzbDrone.Core/Localization/Core/sv.json @@ -324,7 +324,7 @@ "IndexerIdHelpText": "Specificera vilken indexer profilen appliceras på", "IndexerIdvalue0OnlySupportedWhenIndexerIsSetToAll": "Stödjer endast när Indexer är inställt på (Alla)", "MetadataProviderSource": "Metadata Utgivare Källa", - "UILanguageHelpText": "Språk som Radarr kommer att använda för användargränssnitt", + "UILanguageHelpText": "Språk som Lidarr kommer att använda för användargränssnitt", "UnableToLoadLists": "Det gick inte att ladda listor", "UnableToLoadMediaManagementSettings": "Det gick inte att läsa in inställningar för Media Management", "UpdateScriptPathHelpText": "Sökväg till ett anpassat skript som tar ett extraherat uppdateringspaket och hanterar resten av uppdateringsprocessen", @@ -337,7 +337,7 @@ "UsenetDelayHelpText": "Fördröja på några minuter för att vänta innan du hämtar en utgåva från Usenet", "UseProxy": "Använd proxy", "Username": "Användarnamn", - "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Gren att använda för att uppdatera Radarr", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Gren att använda för att uppdatera Lidarr", "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Gren som används av extern uppdateringsmekanism", "Version": "Version", "WeekColumnHeader": "Rubrik för veckokolumn", @@ -376,7 +376,7 @@ "Scheduled": "Schemalagt", "Score": "Göra", "ScriptPath": "Skriptsökväg", - "ScrubAudioTagsHelpText": "Ta bort befintliga taggar från filer, lämnar endas dem som är tillagda av Readarr.", + "ScrubAudioTagsHelpText": "Ta bort befintliga taggar från filer, lämnar endas dem som är tillagda av Lidarr.", "ScrubExistingTags": "Skrubba Befintliga Taggar", "Search": "Sök", "SearchAll": "Sök samtliga", @@ -404,8 +404,8 @@ "ShownAboveEachColumnWhenWeekIsTheActiveView": "Visas ovanför varje kolumn när veckan är den aktiva vyn", "Size": " Storlek", "SkipFreeSpaceCheck": "Hoppa över ledig platskontroll", - "SkipFreeSpaceCheckWhenImportingHelpText": "Används när Radarr inte kan upptäcka ledigt utrymme från filmappens rotmapp", - "SkipredownloadHelpText": "Förhindrar Readarr från att försöka ladda ned alternativa utgåvor för borttagna saker", + "SkipFreeSpaceCheckWhenImportingHelpText": "Används när Lidarr inte kan upptäcka ledigt utrymme från filmappens rotmapp", + "SkipredownloadHelpText": "Förhindrar Lidarr från att försöka ladda ned alternativa utgåvor för borttagna saker", "SorryThatAlbumCannotBeFound": "Tyvärr kan den filmen inte hittas.", "SorryThatArtistCannotBeFound": "Tyvärr kan den filmen inte hittas.", "Source": "Källa", @@ -438,16 +438,16 @@ "IsCutoffCutoff": "Avskärning", "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Uppgradera tills den här kvaliteten uppfylls eller överskrids", "IsExpandedHideAlbums": "Dölj Albums", - "LaunchBrowserHelpText": " Öppna en webbläsare och navigera till Radarr-hemsidan vid appstart.", + "LaunchBrowserHelpText": " Öppna en webbläsare och navigera till Lidarr-hemsidan vid appstart.", "Level": "Nivå", - "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Radarr stöder alla nedladdningsklienter som använder Newznab-standarden samt andra nedladdningsklienter som anges nedan.", - "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr stöder alla indexerare som använder Newznab-standarden, liksom andra indexerare som anges nedan.", - "LidarrSupportsMultipleListsForImportingAlbumsAndArtistsIntoTheDatabase": "Readarr stödjer flera listor för att importera Böcker och Författare in i databasen.", - "LidarrTags": "Radarr taggar", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr stöder alla nedladdningsklienter som använder Newznab-standarden samt andra nedladdningsklienter som anges nedan.", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr stöder alla indexerare som använder Newznab-standarden, liksom andra indexerare som anges nedan.", + "LidarrSupportsMultipleListsForImportingAlbumsAndArtistsIntoTheDatabase": "Lidarr stödjer flera listor för att importera Böcker och Författare in i databasen.", + "LidarrTags": "Lidarr taggar", "LoadingTrackFilesFailed": "Det gick inte att ladda filmfiler", "Local": "Lokal", "LocalPath": "Lokal sökväg", - "LocalPathHelpText": "Sökväg som Radarr ska använda för att komma åt fjärrvägen lokalt", + "LocalPathHelpText": "Sökväg som Lidarr ska använda för att komma åt fjärrvägen lokalt", "LogFiles": "Loggfiler", "Logging": "Loggning", "LogLevel": "Loggnivå", @@ -511,7 +511,6 @@ "QualityProfiles": "Kvalitetsprofiler", "QualitySettings": "Kvalitetsalternativ", "Queue": "Kö", - "Radarr": "Radarr", "ReadTheWikiForMoreInformation": "Läs Wiki för mer information", "Real": "Verklig", "Reason": "Anledning", @@ -543,17 +542,17 @@ "RemoveSelected": "Radera markerade", "RemoveTagExistingTag": "Befintlig tagg", "RemoveTagRemovingTag": "Ta bort taggen", - "RenameTracksHelpText": "Radarr kommer att använda det befintliga filnamnet om namnbyte är avaktiverat", + "RenameTracksHelpText": "Lidarr kommer att använda det befintliga filnamnet om namnbyte är avaktiverat", "Reorder": "Ändra ordning", "ReplaceIllegalCharacters": "Ersätt otillåtna tecken", - "ReplaceIllegalCharactersHelpText": "Byt ut olagliga tecken. Om det inte är markerat tar Radarr bort dem istället", + "ReplaceIllegalCharactersHelpText": "Byt ut olagliga tecken. Om det inte är markerat tar Lidarr bort dem istället", "RequiredHelpText": "Utgåvan måste innehålla minst en av dessa termer (skiftlägeskänsliga)", "RequiredPlaceHolder": "Lägg till ny restriktion", "RescanAfterRefreshHelpText": "Scanna om författarmappen efter uppdaterat författaren", - "RescanAfterRefreshHelpTextWarning": "Radarr upptäcker inte automatiskt ändringar av filer när de inte är inställda på 'Alltid'", + "RescanAfterRefreshHelpTextWarning": "Lidarr upptäcker inte automatiskt ändringar av filer när de inte är inställda på 'Alltid'", "RescanArtistFolderAfterRefresh": "Skanna igenom filmmapp efter uppdatering", "ResetAPIKey": "Återställa API-nyckel", - "RestartLidarr": "Starta om Radarr", + "RestartLidarr": "Starta om Lidarr", "Restore": "Återställ", "RestoreBackup": "Återställ säkerhetskopia", "Retention": "Bibehållande", @@ -575,7 +574,7 @@ "SuccessMyWorkIsDoneNoFilesToRetag": "Klart! Jobbet avslutat, inga fler filer att byta namn på.", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS stöds inte av denna indexerare", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Sökning stöds ej av denna indexerare", - "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Används när automatiska sökningar utförs via användargränssnittet eller av Radarr", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Används när automatiska sökningar utförs via användargränssnittet eller av Lidarr", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Används när interaktiv sökning används", "TagAudioFilesWithMetadata": "Tagga Ljudfiler med Metadata", "TagIsNotUsedAndCanBeDeleted": "Taggen används inte och kan raderas", @@ -638,5 +637,15 @@ "Style": "Stil", "Tags": "Taggar", "TestAll": "Testa samtliga", - "TestAllClients": "Testa samtliga klienter" + "TestAllClients": "Testa samtliga klienter", + "ThisCannotBeCancelled": "Detta kan inte avbrytas en gång startat utan att Whisparr startas om.", + "Tracks": "Spår", + "OnHealthIssue": "På hälsofrågan", + "OnImportFailure": "När Import Misslyckas", + "OnReleaseImport": "När Utgåvo-Import", + "OnRename": "På Byt namn", + "OnUpgrade": "Vid uppgradering", + "OnDownloadFailure": "När Nedladdning Misslyckas", + "OnGrab": "Vid hämtning", + "MonoVersion": "Mono version" } diff --git a/src/NzbDrone.Core/Localization/Core/th.json b/src/NzbDrone.Core/Localization/Core/th.json index 10ac28e1b..ec8bfb333 100644 --- a/src/NzbDrone.Core/Localization/Core/th.json +++ b/src/NzbDrone.Core/Localization/Core/th.json @@ -1,4 +1,477 @@ { "Language": "ภาษา", - "UILanguage": "ภาษา UI" + "UILanguage": "ภาษา UI", + "ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "สำหรับข้อมูลเพิ่มเติมเกี่ยวกับไคลเอนต์ดาวน์โหลดแต่ละรายการคลิกที่ปุ่มข้อมูล", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Whisparr รองรับไคลเอนต์ดาวน์โหลดใด ๆ ที่ใช้มาตรฐาน Newznab รวมถึงไคลเอนต์ดาวน์โหลดอื่น ๆ ตามรายการด้านล่าง", + "RemoveFilter": "ลบตัวกรอง", + "RemovedFromTaskQueue": "ลบออกจากคิวงาน", + "RemoveSelected": "ลบรายการที่เลือก", + "UnableToLoadRemotePathMappings": "ไม่สามารถโหลด Remote Path Mappings", + "MaximumSizeHelpText": "ขนาดสูงสุดสำหรับรีลีสที่จะคว้าเป็น MB ตั้งค่าเป็นศูนย์เพื่อตั้งค่าเป็นไม่ จำกัด", + "Mechanism": "กลไก", + "MediaInfo": "ข้อมูลสื่อ", + "MediaManagementSettings": "การตั้งค่าการจัดการสื่อ", + "Medium": "ปานกลาง", + "Message": "ข้อความ", + "MetadataSettings": "การตั้งค่าข้อมูลเมตา", + "MIA": "MIA", + "MinimumAge": "อายุขั้นต่ำ", + "MinimumAgeHelpText": "Usenet เท่านั้น: อายุต่ำสุดเป็นนาทีของ NZB ก่อนที่จะถูกคว้า ใช้สิ่งนี้เพื่อให้เวลารุ่นใหม่เผยแพร่ไปยังผู้ให้บริการ usenet ของคุณ", + "MinimumFreeSpace": "พื้นที่ว่างขั้นต่ำ", + "MinimumFreeSpaceWhenImportingHelpText": "ป้องกันการนำเข้าหากเหลือพื้นที่ดิสก์น้อยกว่าจำนวนนี้", + "MinimumLimits": "ขีด จำกัด ขั้นต่ำ", + "Missing": "หายไป", + "Mode": "โหมด", + "Monitored": "ตรวจสอบ", + "MoreInfo": "ข้อมูลเพิ่มเติม", + "MustContain": "ต้องมี", + "MustNotContain": "ต้องไม่มี", + "Name": "ชื่อ", + "NamingSettings": "การตั้งชื่อการตั้งค่า", + "New": "ใหม่", + "NoBackupsAreAvailable": "ไม่มีการสำรองข้อมูล", + "NoLogFiles": "ไม่มีไฟล์บันทึก", + "NoMinimumForAnyRuntime": "ไม่มีขั้นต่ำสำหรับรันไทม์ใด ๆ", + "None": "ไม่มี", + "NotificationTriggers": "ทริกเกอร์การแจ้งเตือน", + "NoUpdatesAreAvailable": "ไม่มีการอัปเดต", + "OnUpgrade": "ในการอัพเกรด", + "OnUpgradeHelpText": "ในการอัพเกรด", + "OpenBrowserOnStart": "เปิดเบราว์เซอร์เมื่อเริ่มต้น", + "Options": "ตัวเลือก", + "Original": "ต้นฉบับ", + "PackageVersion": "เวอร์ชันแพ็คเกจ", + "PageSize": "ขนาดหน้า", + "PageSizeHelpText": "จำนวนรายการที่จะแสดงในแต่ละหน้า", + "Password": "รหัสผ่าน", + "Path": "เส้นทาง", + "Permissions": "สิทธิ์", + "Port": "ท่าเรือ", + "PortNumber": "หมายเลขพอร์ต", + "PosterSize": "ขนาดโปสเตอร์", + "Preferred": "ที่ต้องการ", + "PreviewRename": "ดูตัวอย่างการเปลี่ยนชื่อ", + "PriorityHelpText": "ลำดับความสำคัญของดัชนีจาก 1 (สูงสุด) ถึง 50 (ต่ำสุด) ค่าเริ่มต้น: 25.", + "Profiles": "โปรไฟล์", + "Proper": "เหมาะสม", + "PropersAndRepacks": "ใบพัดและ Repacks", + "Protocol": "มาตรการ", + "ProtocolHelpText": "เลือกโปรโตคอลที่จะใช้และต้องการใช้โปรโตคอลใดเมื่อเลือกระหว่างรีลีสที่เท่าเทียมกัน", + "Proxy": "พร็อกซี", + "ProxyPasswordHelpText": "คุณจะต้องป้อนชื่อผู้ใช้และรหัสผ่านหากจำเป็นเท่านั้น เว้นว่างไว้เป็นอย่างอื่น", + "ProxyType": "ประเภทพร็อกซี", + "ProxyUsernameHelpText": "คุณจะต้องป้อนชื่อผู้ใช้และรหัสผ่านหากจำเป็นเท่านั้น เว้นว่างไว้เป็นอย่างอื่น", + "PublishedDate": "วันที่เผยแพร่", + "Quality": "คุณภาพ", + "QualityDefinitions": "คำจำกัดความคุณภาพ", + "QualityProfile": "โปรไฟล์คุณภาพ", + "QualityProfiles": "โปรไฟล์คุณภาพ", + "QualitySettings": "การตั้งค่าคุณภาพ", + "Queue": "คิว", + "Reload": "โหลดซ้ำ", + "RemotePathHelpText": "พา ธ รูทไปยังไดเร็กทอรีที่ Download Client เข้าถึง", + "RemotePathMappings": "การแมปเส้นทางระยะไกล", + "Remove": "ลบ", + "RemoveCompletedDownloadsHelpText": "ลบการดาวน์โหลดที่นำเข้าจากประวัติไคลเอนต์ดาวน์โหลด", + "RemoveFailedDownloadsHelpText": "ลบการดาวน์โหลดที่ล้มเหลวออกจากประวัติไคลเอนต์ดาวน์โหลด", + "RemoveFromBlocklist": "ลบออกจากบัญชีดำ", + "RemoveFromQueue": "ลบออกจากคิว", + "RemoveHelpTextWarning": "การลบจะลบการดาวน์โหลดและไฟล์ออกจากไคลเอนต์ดาวน์โหลด", + "RemoveSelectedMessageText": "แน่ใจหรือไม่ว่าต้องการลบรายการที่เลือกออกจากบัญชีดำ?", + "RemoveTagExistingTag": "แท็กที่มีอยู่", + "ReplaceIllegalCharacters": "แทนที่อักขระที่ผิดกฎหมาย", + "ReplaceIllegalCharactersHelpText": "แทนที่อักขระที่ผิดกฎหมาย หากไม่เลือก Lidarr จะลบออกแทน", + "RequiredHelpText": "การเผยแพร่ต้องมีข้อกำหนดเหล่านี้อย่างน้อยหนึ่งข้อ (ไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่)", + "RequiredPlaceHolder": "เพิ่มข้อ จำกัด ใหม่", + "RequiresRestartToTakeEffect": "ต้องรีสตาร์ทเพื่อให้มีผล", + "RescanAfterRefreshHelpText": "สแกนโฟลเดอร์ภาพยนตร์อีกครั้งหลังจากรีเฟรชภาพยนตร์", + "RescanAfterRefreshHelpTextWarning": "Lidarr จะไม่ตรวจจับการเปลี่ยนแปลงของไฟล์โดยอัตโนมัติเมื่อไม่ได้ตั้งค่าเป็น 'เสมอ'", + "RescanArtistFolderAfterRefresh": "สแกนโฟลเดอร์ภาพยนตร์อีกครั้งหลังจากรีเฟรช", + "Reset": "รีเซ็ต", + "ResetAPIKey": "รีเซ็ตคีย์ API", + "ResetAPIKeyMessageText": "แน่ใจไหมว่าต้องการรีเซ็ตคีย์ API", + "Restart": "เริ่มต้นใหม่", + "RestartLidarr": "รีสตาร์ท Lidarr", + "RestartNow": "เริ่มต้นใหม่เดี๋ยวนี้", + "Restore": "คืนค่า", + "RestoreBackup": "คืนค่าการสำรองข้อมูล", + "Result": "ผลลัพธ์", + "Retention": "การเก็บรักษา", + "RetentionHelpText": "Usenet เท่านั้น: ตั้งค่าเป็นศูนย์เพื่อตั้งค่าสำหรับการเก็บรักษาแบบไม่ จำกัด", + "RetryingDownloadInterp": "ลองดาวน์โหลดอีกครั้ง {0} ที่ {1}", + "RootFolder": "โฟลเดอร์รูท", + "RootFolders": "โฟลเดอร์รูท", + "RSSSync": "RSS Sync", + "RSSSyncInterval": "RSS Sync Interval", + "RssSyncIntervalHelpText": "ช่วงเวลาเป็นนาที ตั้งค่าเป็นศูนย์เพื่อปิดใช้งาน (สิ่งนี้จะหยุดการจับรุ่นอัตโนมัติทั้งหมด)", + "Scheduled": "กำหนดเวลา", + "SearchAll": "ค้นหาทั้งหมด", + "SearchForMissing": "ค้นหา Missing", + "SearchSelected": "ค้นหาที่เลือก", + "Security": "ความปลอดภัย", + "SendAnonymousUsageData": "ส่งข้อมูลการใช้งานแบบไม่ระบุตัวตน", + "SetPermissions": "ตั้งค่าสิทธิ์", + "SetPermissionsLinuxHelpText": "ควรรัน chmod เมื่อนำเข้า / เปลี่ยนชื่อไฟล์หรือไม่", + "SetPermissionsLinuxHelpTextWarning": "หากคุณไม่แน่ใจว่าการตั้งค่าเหล่านี้ใช้ทำอะไรอย่าแก้ไข", + "Settings": "การตั้งค่า", + "ShortDateFormat": "รูปแบบวันที่สั้น", + "ShowMonitoredHelpText": "แสดงสถานะการตรวจสอบภายใต้โปสเตอร์", + "ShowQualityProfile": "แสดงโปรไฟล์คุณภาพ", + "ShowQualityProfileHelpText": "แสดงโปรไฟล์คุณภาพใต้โปสเตอร์", + "ShowRelativeDates": "แสดงวันที่สัมพัทธ์", + "ShowRelativeDatesHelpText": "แสดงญาติ (วันนี้ / เมื่อวาน / ฯลฯ ) หรือวันที่แน่นอน", + "ShowSearch": "แสดงการค้นหา", + "ShowSearchActionHelpText": "แสดงปุ่มค้นหาเมื่อวางเมาส์เหนือ", + "ShowSizeOnDisk": "แสดงขนาดบนดิสก์", + "ShowUnknownArtistItems": "แสดงรายการภาพยนตร์ที่ไม่รู้จัก", + "Size": " ขนาด", + "SkipFreeSpaceCheck": "ข้ามการตรวจสอบพื้นที่ว่าง", + "SkipFreeSpaceCheckWhenImportingHelpText": "ใช้เมื่อ Lidarr ไม่สามารถตรวจจับพื้นที่ว่างจากโฟลเดอร์รากภาพยนตร์ของคุณ", + "SorryThatAlbumCannotBeFound": "ขออภัยไม่พบภาพยนตร์เรื่องนั้น", + "Source": "ที่มา", + "SourcePath": "เส้นทางแหล่งที่มา", + "SSLCertPassword": "รหัสผ่านใบรับรอง SSL", + "SslCertPasswordHelpText": "รหัสผ่านสำหรับไฟล์ pfx", + "SslCertPasswordHelpTextWarning": "ต้องรีสตาร์ทเพื่อให้มีผล", + "SSLCertPath": "เส้นทางใบรับรอง SSL", + "SslCertPathHelpTextWarning": "ต้องรีสตาร์ทเพื่อให้มีผล", + "SSLPort": "พอร์ต SSL", + "SslPortHelpTextWarning": "ต้องรีสตาร์ทเพื่อให้มีผล", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "จะใช้เมื่อทำการค้นหาอัตโนมัติผ่าน UI หรือโดย Lidarr", + "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "จะใช้เมื่อใช้การค้นหาแบบโต้ตอบ", + "TagIsNotUsedAndCanBeDeleted": "ไม่ได้ใช้แท็กและสามารถลบได้", + "Tags": "แท็ก", + "Tasks": "งาน", + "TestAll": "ทดสอบทั้งหมด", + "TestAllClients": "ทดสอบลูกค้าทั้งหมด", + "TestAllIndexers": "ทดสอบดัชนีทั้งหมด", + "ThisCannotBeCancelled": "ไม่สามารถยกเลิกได้เมื่อเริ่มต้นโดยไม่ต้องรีสตาร์ท Whisparr", + "ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "สิ่งนี้จะนำไปใช้กับผู้จัดทำดัชนีทั้งหมดโปรดปฏิบัติตามกฎที่กำหนดโดยพวกเขา", + "Time": "เวลา", + "TimeFormat": "รูปแบบเวลา", + "TorrentDelay": "ความล่าช้าของ Torrent", + "TorrentDelayHelpText": "รอเป็นนาทีเพื่อรอก่อนที่จะคว้า torrent", + "Torrents": "Torrents", + "TotalFileSize": "ขนาดไฟล์ทั้งหมด", + "Type": "ประเภท", + "UISettings": "การตั้งค่า UI", + "UnableToAddANewDownloadClientPleaseTryAgain": "ไม่สามารถเพิ่มไคลเอนต์ดาวน์โหลดใหม่ได้โปรดลองอีกครั้ง", + "UnableToAddANewNotificationPleaseTryAgain": "ไม่สามารถเพิ่มการแจ้งเตือนใหม่โปรดลองอีกครั้ง", + "UnableToAddANewQualityProfilePleaseTryAgain": "ไม่สามารถเพิ่มโปรไฟล์คุณภาพใหม่ได้โปรดลองอีกครั้ง", + "UnableToAddANewRemotePathMappingPleaseTryAgain": "ไม่สามารถเพิ่มการแมปเส้นทางระยะไกลใหม่โปรดลองอีกครั้ง", + "UnableToAddANewRootFolderPleaseTryAgain": "ไม่สามารถเพิ่มรูปแบบที่กำหนดเองใหม่โปรดลองอีกครั้ง", + "UnableToLoadBlocklist": "ไม่สามารถโหลดบัญชีดำ", + "UnableToLoadDelayProfiles": "ไม่สามารถโหลด Delay Profiles", + "UnableToLoadDownloadClientOptions": "ไม่สามารถโหลดตัวเลือกไคลเอนต์ดาวน์โหลด", + "UnableToLoadDownloadClients": "ไม่สามารถโหลดไคลเอนต์ดาวน์โหลด", + "UnableToLoadGeneralSettings": "ไม่สามารถโหลดการตั้งค่าทั่วไป", + "UnableToLoadHistory": "ไม่สามารถโหลดประวัติ", + "UnableToLoadImportListExclusions": "ไม่สามารถโหลดการยกเว้นรายการ", + "UnableToLoadIndexerOptions": "ไม่สามารถโหลดตัวเลือกดัชนี", + "UnableToLoadLists": "ไม่สามารถโหลดรายการ", + "UnableToLoadMediaManagementSettings": "ไม่สามารถโหลดการตั้งค่าการจัดการสื่อ", + "UnableToLoadMetadata": "ไม่สามารถโหลดข้อมูลเมตา", + "UnableToLoadMetadataProfiles": "ไม่สามารถโหลด Delay Profiles", + "UnableToLoadQualityDefinitions": "ไม่สามารถโหลดคำจำกัดความคุณภาพ", + "UnableToLoadQualityProfiles": "ไม่สามารถโหลดโปรไฟล์คุณภาพ", + "UnableToLoadReleaseProfiles": "ไม่สามารถโหลด Delay Profiles", + "UnableToLoadRootFolders": "ไม่สามารถโหลดโฟลเดอร์รูท", + "UnableToLoadTags": "ไม่สามารถโหลดแท็ก", + "UnableToLoadTheCalendar": "ไม่สามารถโหลดปฏิทิน", + "UnableToLoadUISettings": "ไม่สามารถโหลดการตั้งค่า UI", + "Unmonitored": "ไม่ได้รับการตรวจสอบ", + "UnmonitoredHelpText": "รวมภาพยนตร์ที่ไม่มีการตรวจสอบในฟีด iCal", + "UpdateAll": "อัพเดททั้งหมด", + "UpdateAutomaticallyHelpText": "ดาวน์โหลดและติดตั้งการอัปเดตโดยอัตโนมัติ คุณจะยังติดตั้งได้จาก System: Updates", + "UpdateMechanismHelpText": "ใช้ตัวอัปเดตหรือสคริปต์ในตัวของ Lidarr", + "Updates": "อัปเดต", + "UpdateScriptPathHelpText": "พา ธ ไปยังสคริปต์แบบกำหนดเองที่ใช้แพ็กเกจโปรแกรมปรับปรุงที่แยกออกมาและจัดการส่วนที่เหลือของกระบวนการอัพเดต", + "UpgradeAllowedHelpText": "หากปิดใช้งานคุณสมบัติจะไม่ได้รับการอัปเกรด", + "Uptime": "เวลาทำงาน", + "URLBase": "ฐาน URL", + "UrlBaseHelpText": "สำหรับการสนับสนุน reverse proxy ค่าเริ่มต้นจะว่างเปล่า", + "UseHardlinksInsteadOfCopy": "ใช้ Hardlinks แทน Copy", + "Usenet": "Usenet", + "UsenetDelay": "Usenet ล่าช้า", + "UsenetDelayHelpText": "รอเป็นนาทีก่อนที่จะคว้ารุ่นจาก Usenet", + "UseProxy": "ใช้ Proxy", + "Username": "ชื่อผู้ใช้", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "สาขาที่จะใช้ในการอัปเดต Lidarr", + "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "สาขาที่ใช้โดยกลไกการอัพเดตภายนอก", + "Version": "เวอร์ชัน", + "WeekColumnHeader": "ส่วนหัวคอลัมน์สัปดาห์", + "Year": "ปี", + "YesCancel": "ใช่ยกเลิก", + "OnHealthIssueHelpText": "เกี่ยวกับปัญหาสุขภาพ", + "OnRename": "ในการเปลี่ยนชื่อ", + "OnRenameHelpText": "ในการเปลี่ยนชื่อ", + "RecycleBinCleanupDaysHelpTextWarning": "ไฟล์ในถังรีไซเคิลที่เก่ากว่าจำนวนวันที่เลือกจะถูกล้างโดยอัตโนมัติ", + "RecycleBinHelpText": "ไฟล์ภาพยนตร์จะอยู่ที่นี่เมื่อถูกลบแทนที่จะถูกลบอย่างถาวร", + "RecyclingBin": "ถังขยะรีไซเคิล", + "RecyclingBinCleanup": "การล้างถังขยะรีไซเคิล", + "Redownload": "ดาวน์โหลดอีกครั้ง", + "Refresh": "รีเฟรช", + "RefreshInformationAndScanDisk": "รีเฟรชข้อมูลและสแกนดิสก์", + "RefreshScan": "รีเฟรชและสแกน", + "ReleaseDate": "วันที่วางจำหน่าย", + "ReleaseGroup": "กลุ่มเผยแพร่", + "ReleaseRejected": "การเปิดตัวถูกปฏิเสธ", + "ReleaseStatuses": "สถานะการเปิดตัว", + "ReleaseWillBeProcessedInterp": "การเผยแพร่จะได้รับการดำเนินการ {0}", + "StartTypingOrSelectAPathBelow": "เริ่มพิมพ์หรือเลือกเส้นทางด้านล่าง", + "StartupDirectory": "ไดเร็กทอรีเริ่มต้น", + "Status": "สถานะ", + "Style": "สไตล์", + "SuccessMyWorkIsDoneNoFilesToRename": "สำเร็จ! งานของฉันเสร็จแล้วไม่มีไฟล์ให้เปลี่ยนชื่อ", + "SuccessMyWorkIsDoneNoFilesToRetag": "สำเร็จ! งานของฉันเสร็จแล้วไม่มีไฟล์ให้เปลี่ยนชื่อ", + "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS ไม่ได้รับการสนับสนุนกับตัวสร้างดัชนีนี้", + "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "ตัวสร้างดัชนีนี้ไม่รองรับการค้นหา", + "TestAllLists": "ทดสอบรายการทั้งหมด", + "UnableToLoadNamingSettings": "ไม่สามารถโหลดการตั้งค่าการตั้งชื่อ", + "UnableToLoadNotifications": "ไม่สามารถโหลดการแจ้งเตือน", + "UnableToLoadQualities": "ไม่สามารถโหลดคุณภาพได้", + "UnableToAddANewMetadataProfilePleaseTryAgain": "ไม่สามารถเพิ่มโปรไฟล์คุณภาพใหม่ได้โปรดลองอีกครั้ง", + "AppDataDirectory": "ไดเรกทอรี AppData", + "ApplyTagsHelpTexts1": "วิธีใช้แท็กกับภาพยนตร์ที่เลือก", + "ApplyTagsHelpTexts2": "เพิ่ม: เพิ่มแท็กในรายการแท็กที่มีอยู่", + "ApplyTagsHelpTexts3": "ลบ: ลบแท็กที่ป้อน", + "ApplyTagsHelpTexts4": "แทนที่: แทนที่แท็กด้วยแท็กที่ป้อน (ป้อนไม่มีแท็กเพื่อล้างแท็กทั้งหมด)", + "ArtistAlbumClickToChangeTrack": "คลิกเพื่อเปลี่ยนภาพยนตร์", + "Authentication": "การรับรองความถูกต้อง", + "AuthenticationMethodHelpText": "ต้องการชื่อผู้ใช้และรหัสผ่านเพื่อเข้าถึงเรดาร์", + "AutoRedownloadFailedHelpText": "ค้นหาและพยายามดาวน์โหลดรุ่นอื่นโดยอัตโนมัติ", + "BackupFolderHelpText": "เส้นทางสัมพัทธ์จะอยู่ภายใต้ไดเรกทอรี AppData ของ Lidarr", + "BackupNow": "การสำรองข้อมูลในขณะนี้", + "BackupRetentionHelpText": "การสำรองข้อมูลอัตโนมัติที่เก่ากว่าระยะเวลาการเก็บรักษาจะถูกล้างโดยอัตโนมัติ", + "Backups": "การสำรองข้อมูล", + "BindAddress": "ผูกที่อยู่", + "BindAddressHelpTextWarning": "ต้องรีสตาร์ทเพื่อให้มีผล", + "BlocklistRelease": "Blacklist Release", + "Branch": "สาขา", + "Calendar": "ปฏิทิน", + "CalendarWeekColumnHeaderHelpText": "แสดงอยู่เหนือแต่ละคอลัมน์เมื่อสัปดาห์เป็นมุมมองที่ใช้งานอยู่", + "Cancel": "ยกเลิก", + "CancelMessageText": "แน่ใจไหมว่าต้องการยกเลิกงานที่รอดำเนินการนี้", + "CertificateValidation": "การตรวจสอบใบรับรอง", + "ChangeFileDate": "เปลี่ยนวันที่ของไฟล์", + "ChangeHasNotBeenSavedYet": "ยังไม่ได้บันทึกการเปลี่ยนแปลง", + "ChmodFolder": "โฟลเดอร์ chmod", + "ChmodFolderHelpText": "ฐานแปดใช้ในระหว่างการนำเข้า / เปลี่ยนชื่อไปยังโฟลเดอร์สื่อและไฟล์ (โดยไม่ต้องดำเนินการบิต)", + "ChmodFolderHelpTextWarning": "สิ่งนี้ใช้ได้เฉพาะเมื่อผู้ใช้ที่เรียกใช้ Lidarr เป็นเจ้าของไฟล์ เป็นการดีกว่าเพื่อให้แน่ใจว่าไคลเอนต์ดาวน์โหลดตั้งค่าการอนุญาตอย่างถูกต้อง", + "ChownGroupHelpText": "ชื่อกลุ่มหรือ gid ใช้ gid สำหรับระบบไฟล์ระยะไกล", + "ChownGroupHelpTextWarning": "สิ่งนี้ใช้ได้เฉพาะเมื่อผู้ใช้ที่เรียกใช้ Lidarr เป็นเจ้าของไฟล์ เป็นการดีกว่าที่จะตรวจสอบให้แน่ใจว่าไคลเอนต์ดาวน์โหลดใช้กลุ่มเดียวกับ Lidarr", + "Clear": "ชัดเจน", + "ClickToChangeQuality": "คลิกเพื่อเปลี่ยนคุณภาพ", + "ClientPriority": "ลำดับความสำคัญของลูกค้า", + "CloneIndexer": "Clone Indexer", + "CloneProfile": "โปรไฟล์โคลน", + "Columns": "คอลัมน์", + "CompletedDownloadHandling": "การจัดการการดาวน์โหลดเสร็จสมบูรณ์", + "Component": "ส่วนประกอบ", + "Connections": "การเชื่อมต่อ", + "ConnectSettings": "เชื่อมต่อการตั้งค่า", + "CopyUsingHardlinksHelpText": "ใช้ฮาร์ดลิงก์เมื่อพยายามคัดลอกไฟล์จากเพลงที่ยังคงอยู่ในระหว่างการเพาะเมล็ด", + "CopyUsingHardlinksHelpTextWarning": "ในบางครั้งการล็อกไฟล์อาจป้องกันการเปลี่ยนชื่อไฟล์ที่กำลังถูก seed คุณสามารถปิดใช้งานการเริ่มต้นชั่วคราวและใช้ฟังก์ชันเปลี่ยนชื่อของ Lidarr เป็นการแก้ปัญหา", + "CreateEmptyArtistFolders": "สร้างโฟลเดอร์ภาพยนตร์เปล่า", + "CreateEmptyArtistFoldersHelpText": "สร้างโฟลเดอร์ภาพยนตร์ที่หายไประหว่างการสแกนดิสก์", + "CreateGroup": "สร้างกลุ่ม", + "CutoffHelpText": "เมื่อถึงคุณภาพนี้แล้ว Lidarr จะไม่ดาวน์โหลดภาพยนตร์อีกต่อไป", + "CutoffUnmet": "ตัด Unmet", + "Dates": "วันที่", + "DBMigration": "การย้ายฐานข้อมูล", + "DelayProfile": "โปรไฟล์ล่าช้า", + "DelayProfiles": "โปรไฟล์ล่าช้า", + "Delete": "ลบ", + "MaximumSize": "ขนาดสูงสุด", + "ReadTheWikiForMoreInformation": "อ่าน Wiki สำหรับข้อมูลเพิ่มเติม", + "Real": "จริง", + "DeleteBackup": "ลบข้อมูลสำรอง", + "RemoveFromDownloadClient": "ลบออกจากไคลเอนต์ดาวน์โหลด", + "Score": "คะแนน", + "ScriptPath": "เส้นทางสคริปต์", + "Search": "ค้นหา", + "ShowCutoffUnmetIconHelpText": "แสดงไอคอนสำหรับไฟล์เมื่อไม่ตรงตามจุดตัด", + "ShowDateAdded": "แสดงวันที่เพิ่ม", + "ShowMonitored": "แสดงการตรวจสอบ", + "ShowPath": "แสดงเส้นทาง", + "DeleteBackupMessageText": "แน่ใจไหมว่าต้องการลบข้อมูลสำรอง \"{0}\"", + "DeleteDelayProfile": "ลบโปรไฟล์ความล่าช้า", + "DeleteDelayProfileMessageText": "แน่ใจไหมว่าต้องการลบโปรไฟล์การหน่วงเวลานี้", + "SorryThatArtistCannotBeFound": "ขออภัยไม่พบภาพยนตร์เรื่องนั้น", + "DeleteDownloadClient": "ลบไคลเอนต์ดาวน์โหลด", + "Track": "ติดตาม", + "UnableToLoadBackups": "ไม่สามารถโหลดข้อมูลสำรอง", + "UnableToLoadIndexers": "ไม่สามารถโหลด Indexers", + "UrlBaseHelpTextWarning": "ต้องรีสตาร์ทเพื่อให้มีผล", + "DeleteDownloadClientMessageText": "แน่ใจไหมว่าต้องการลบไคลเอนต์ดาวน์โหลด \"{0}\"", + "DeleteEmptyFolders": "ลบโฟลเดอร์ว่าง", + "DeleteImportListExclusion": "ลบการยกเว้นรายการนำเข้า", + "DeleteImportListExclusionMessageText": "แน่ใจไหมว่าต้องการลบการยกเว้นรายการนำเข้านี้", + "DeleteImportListMessageText": "แน่ใจไหมว่าต้องการลบรายการ \"{0}\"", + "DeleteIndexer": "ลบ Indexer", + "DeleteIndexerMessageText": "แน่ใจไหมว่าต้องการลบตัวสร้างดัชนี \"{0}\"", + "DeleteMetadataProfileMessageText": "แน่ใจไหมว่าต้องการลบโปรไฟล์คุณภาพ {0}", + "DeleteNotification": "ลบการแจ้งเตือน", + "DeleteNotificationMessageText": "แน่ใจไหมว่าต้องการลบการแจ้งเตือน \"{0}\"", + "DeleteQualityProfile": "ลบโปรไฟล์คุณภาพ", + "DeleteQualityProfileMessageText": "แน่ใจไหมว่าต้องการลบโปรไฟล์คุณภาพ {0}", + "DeleteReleaseProfile": "ลบโปรไฟล์ความล่าช้า", + "DeleteReleaseProfileMessageText": "แน่ใจไหมว่าต้องการลบโปรไฟล์การหน่วงเวลานี้", + "DeleteRootFolderMessageText": "แน่ใจไหมว่าต้องการลบตัวสร้างดัชนี \"{0}\"", + "DeleteSelectedTrackFiles": "ลบไฟล์ภาพยนตร์ที่เลือก", + "DeleteSelectedTrackFilesMessageText": "แน่ใจไหมว่าต้องการลบไฟล์ภาพยนตร์ที่เลือก", + "DeleteTag": "ลบแท็ก", + "DeleteTagMessageText": "แน่ใจไหมว่าต้องการลบแท็ก \"{0}\"", + "DestinationPath": "เส้นทางปลายทาง", + "DetailedProgressBar": "แถบความคืบหน้าโดยละเอียด", + "DetailedProgressBarHelpText": "แสดงข้อความบนแถบความคืบหน้า", + "DiskSpace": "พื้นที่ดิสก์", + "Docker": "นักเทียบท่า", + "DownloadClient": "ดาวน์โหลดไคลเอนต์", + "DownloadClients": "ดาวน์โหลดไคลเอนต์", + "DownloadClientSettings": "ดาวน์โหลด Client Settings", + "DownloadFailedCheckDownloadClientForMoreDetails": "ดาวน์โหลดล้มเหลว: ตรวจสอบไคลเอนต์ดาวน์โหลดสำหรับรายละเอียดเพิ่มเติม", + "DownloadFailedInterp": "ดาวน์โหลดล้มเหลว: {0}", + "Downloading": "กำลังดาวน์โหลด", + "DownloadPropersAndRepacksHelpTexts1": "ไม่ว่าจะอัปเกรดเป็น Propers / Repacks โดยอัตโนมัติหรือไม่", + "DownloadWarningCheckDownloadClientForMoreDetails": "คำเตือนการดาวน์โหลด: ตรวจสอบไคลเอนต์ดาวน์โหลดสำหรับรายละเอียดเพิ่มเติม", + "Edit": "แก้ไข", + "Enable": "เปิดใช้งาน", + "EnableAutomaticAdd": "เปิดใช้งานการเพิ่มอัตโนมัติ", + "EnableAutomaticSearch": "เปิดใช้งานการค้นหาอัตโนมัติ", + "EnableColorImpairedMode": "เปิดใช้งานโหมดไร้สี", + "EnableColorImpairedModeHelpText": "รูปแบบที่เปลี่ยนแปลงเพื่อให้ผู้ใช้ที่มีความบกพร่องทางสีแยกแยะข้อมูลรหัสสีได้ดีขึ้น", + "EnableCompletedDownloadHandlingHelpText": "นำเข้าการดาวน์โหลดที่เสร็จสมบูรณ์โดยอัตโนมัติจากไคลเอนต์ดาวน์โหลด", + "EnableHelpText": "เปิดใช้งานการสร้างไฟล์ข้อมูลเมตาสำหรับประเภทข้อมูลเมตานี้", + "EnableInteractiveSearch": "เปิดใช้งานการค้นหาแบบโต้ตอบ", + "EnableRSS": "เปิดใช้ RSS", + "EnableSSL": "เปิดใช้งาน SSL", + "EnableSslHelpText": " ต้องรีสตาร์ทในฐานะผู้ดูแลระบบจึงจะมีผล", + "Ended": "สิ้นสุดแล้ว", + "ErrorLoadingContents": "เกิดข้อผิดพลาดในการโหลดเนื้อหา", + "ErrorLoadingPreviews": "เกิดข้อผิดพลาดในการโหลดตัวอย่าง", + "Exception": "ข้อยกเว้น", + "ExtraFileExtensionsHelpTexts1": "รายการไฟล์พิเศษที่คั่นด้วยจุลภาคที่จะนำเข้า (.nfo จะนำเข้าเป็น. nfo-orig)", + "FileDateHelpText": "เปลี่ยนวันที่ของไฟล์ในการนำเข้า / สแกนใหม่", + "FileManagement": "การจัดการไฟล์", + "Filename": "ชื่อไฟล์", + "FileNames": "ชื่อไฟล์", + "Files": "ไฟล์", + "FirstDayOfWeek": "วันแรกของสัปดาห์", + "Fixed": "แก้ไขแล้ว", + "Folder": "โฟลเดอร์", + "Folders": "โฟลเดอร์", + "ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "สำหรับข้อมูลเพิ่มเติมเกี่ยวกับตัวสร้างดัชนีแต่ละตัวคลิกที่ปุ่มข้อมูล", + "ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "สำหรับข้อมูลเพิ่มเติมเกี่ยวกับรายการนำเข้าแต่ละรายการให้คลิกที่ปุ่มข้อมูล", + "Global": "ทั่วโลก", + "GoToInterp": "ไปที่ {0}", + "Grab": "คว้า", + "GrabID": "Grab ID", + "GrabRelease": "คว้ารีลีส", + "GrabReleaseMessageText": "Lidarr ไม่สามารถระบุได้ว่าภาพยนตร์เรื่องนี้เป็นภาพยนตร์เรื่องใด Lidarr อาจไม่สามารถนำเข้ารุ่นนี้โดยอัตโนมัติได้ คุณต้องการคว้า \"{0}\" ไหม", + "GrabSelected": "Grab Selected", + "Group": "กลุ่ม", + "HasPendingChangesNoChanges": "ไม่มีการเปลี่ยนแปลง", + "HasPendingChangesSaveChanges": "บันทึกการเปลี่ยนแปลง", + "History": "ประวัติศาสตร์", + "Host": "โฮสต์", + "HostHelpText": "โฮสต์เดียวกับที่คุณระบุสำหรับไคลเอ็นต์การดาวน์โหลดระยะไกล", + "Hostname": "ชื่อโฮสต์", + "ICalFeed": "ฟีด iCal", + "ICalHttpUrlHelpText": "คัดลอก URL นี้ไปยังลูกค้าของคุณหรือคลิกเพื่อสมัครหากเบราว์เซอร์ของคุณรองรับ webcal", + "ICalLink": "ลิงค์ iCal", + "IconForCutoffUnmet": "ไอคอนสำหรับ Cutoff Unmet", + "IconTooltip": "กำหนดเวลา", + "IgnoredAddresses": "ที่อยู่ที่ถูกละเว้น", + "IgnoredHelpText": "การเปิดตัวจะถูกปฏิเสธหากมีข้อกำหนดอย่างน้อยหนึ่งข้อ (ไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่)", + "IgnoredPlaceHolder": "เพิ่มข้อ จำกัด ใหม่", + "IllRestartLater": "ฉันจะรีสตาร์ทในภายหลัง", + "ImportedTo": "นำเข้าสู่", + "ImportExtraFiles": "นำเข้าไฟล์พิเศษ", + "ImportExtraFilesHelpText": "นำเข้าไฟล์พิเศษที่ตรงกัน (คำบรรยาย nfo ฯลฯ ) หลังจากนำเข้าไฟล์ภาพยนตร์", + "ImportFailedInterp": "การนำเข้าล้มเหลว: {0}", + "Importing": "กำลังนำเข้า", + "IncludeHealthWarningsHelpText": "รวมคำเตือนด้านสุขภาพ", + "IncludeUnknownArtistItemsHelpText": "แสดงรายการที่ไม่มีภาพยนตร์อยู่ในคิว ซึ่งอาจรวมถึงภาพยนตร์ที่ถูกนำออกหรือสิ่งอื่นใดในหมวดหมู่ของ Lidarr", + "Indexer": "Indexer", + "IndexerPriority": "ลำดับความสำคัญของ Indexer", + "Indexers": "ดัชนี", + "IndexerSettings": "การตั้งค่าดัชนี", + "InteractiveSearch": "การค้นหาแบบโต้ตอบ", + "Interval": "ช่วงเวลา", + "IsCutoffCutoff": "คัทออฟ", + "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "อัปเกรดจนกว่าคุณภาพจะตรงหรือเกิน", + "IsTagUsedCannotBeDeletedWhileInUse": "ไม่สามารถลบได้ขณะใช้งาน", + "LaunchBrowserHelpText": " เปิดเว็บเบราว์เซอร์และไปที่หน้าแรกของ Lidarr เมื่อเริ่มแอป", + "Level": "ระดับ", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr รองรับตัวสร้างดัชนีใด ๆ ที่ใช้มาตรฐาน Newznab รวมถึงดัชนีอื่น ๆ ที่แสดงด้านล่าง", + "LidarrTags": "แท็กเรดาร์", + "LoadingTrackFilesFailed": "การโหลดไฟล์ภาพยนตร์ล้มเหลว", + "Local": "ท้องถิ่น", + "LocalPath": "เส้นทางท้องถิ่น", + "LocalPathHelpText": "เส้นทางที่ Lidarr ควรใช้เพื่อเข้าถึงเส้นทางระยะไกลในเครื่อง", + "LogFiles": "ล็อกไฟล์", + "Logging": "การบันทึก", + "LogLevel": "ระดับบันทึก", + "LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "ควรเปิดใช้งานการบันทึกการติดตามชั่วคราวเท่านั้น", + "Logs": "บันทึก", + "LongDateFormat": "รูปแบบวันที่ยาว", + "NoLimitForAnyRuntime": "ไม่มีขีด จำกัด สำหรับรันไทม์ใด ๆ", + "ProxyBypassFilterHelpText": "ใช้ \",\" เป็นตัวคั่นและ \"*.\" เป็นสัญลักษณ์แทนสำหรับโดเมนย่อย", + "RemoveTagRemovingTag": "กำลังลบแท็ก", + "RenameTracksHelpText": "Lidarr จะใช้ชื่อไฟล์ที่มีอยู่หากปิดใช้งานการเปลี่ยนชื่อ", + "ShownAboveEachColumnWhenWeekIsTheActiveView": "แสดงอยู่เหนือแต่ละคอลัมน์เมื่อสัปดาห์เป็นมุมมองที่ใช้งานอยู่", + "BindAddressHelpText": "ที่อยู่ IP4 ที่ถูกต้องหรือ \"*\" สำหรับอินเทอร์เฟซทั้งหมด", + "CertificateValidationHelpText": "เปลี่ยนวิธีการตรวจสอบการรับรอง HTTPS ที่เข้มงวด", + "NETCore": ".NET Core", + "OnGrab": "บน Grab", + "OnGrabHelpText": "บน Grab", + "OnHealthIssue": "เกี่ยวกับปัญหาสุขภาพ", + "Reason": "เหตุผล", + "RecycleBinCleanupDaysHelpText": "ตั้งค่าเป็น 0 เพื่อปิดใช้งานการล้างข้อมูลอัตโนมัติ", + "RemotePath": "เส้นทางระยะไกล", + "StandardTrackFormat": "รูปแบบภาพยนตร์มาตรฐาน", + "Ungroup": "ยกเลิกการจัดกลุ่ม", + "Tracks": "ติดตาม", + "UILanguageHelpText": "ภาษาที่ Lidarr จะใช้สำหรับ UI", + "UILanguageHelpTextWarning": "จำเป็นต้องโหลดเบราว์เซอร์ใหม่", + "UnableToAddANewImportListExclusionPleaseTryAgain": "ไม่สามารถเพิ่มการยกเว้นรายการใหม่ได้โปรดลองอีกครั้ง", + "UnableToAddANewIndexerPleaseTryAgain": "ไม่สามารถเพิ่มตัวสร้างดัชนีใหม่ได้โปรดลองอีกครั้ง", + "UnableToAddANewListPleaseTryAgain": "ไม่สามารถเพิ่มรายชื่อใหม่ได้โปรดลองอีกครั้ง", + "AgeWhenGrabbed": "อายุ (เมื่อคว้า)", + "20MinutesTwenty": "60 นาที: {0}", + "45MinutesFourtyFive": "60 นาที: {0}", + "60MinutesSixty": "60 นาที: {0}", + "APIKey": "คีย์ API", + "About": "เกี่ยวกับ", + "AddListExclusion": "เพิ่มการยกเว้นรายการ", + "AddingTag": "กำลังเพิ่มแท็ก", + "AdvancedSettingsHiddenClickToShow": "ซ่อนอยู่คลิกเพื่อแสดง", + "AdvancedSettingsShownClickToHide": "แสดงคลิกเพื่อซ่อน", + "AlbumIsDownloadingInterp": "กำลังดาวน์โหลดภาพยนตร์ - {0}% {1}", + "AlreadyInYourLibrary": "อยู่ในห้องสมุดของคุณแล้ว", + "AlternateTitles": "ชื่อทางเลือก", + "AlternateTitleslength1Title": "หัวข้อ", + "AlternateTitleslength1Titles": "ชื่อเรื่อง", + "Analytics": "การวิเคราะห์", + "AnalyticsEnabledHelpText": "ส่งข้อมูลการใช้งานและข้อผิดพลาดโดยไม่ระบุชื่อไปยังเซิร์ฟเวอร์ของ Lidarr ซึ่งรวมถึงข้อมูลบนเบราว์เซอร์ของคุณเพจ Lidarr WebUI ที่คุณใช้การรายงานข้อผิดพลาดตลอดจนระบบปฏิบัติการและเวอร์ชันรันไทม์ เราจะใช้ข้อมูลนี้เพื่อจัดลำดับความสำคัญของคุณสมบัติและการแก้ไขข้อบกพร่อง", + "AnalyticsEnabledHelpTextWarning": "ต้องรีสตาร์ทเพื่อให้มีผล", + "Automatic": "อัตโนมัติ", + "Blocklist": "บัญชีดำ", + "DelayingDownloadUntilInterp": "ชะลอการดาวน์โหลดจนถึง {0} เวลา {1}", + "Reorder": "จัดลำดับใหม่", + "Actions": "การดำเนินการ", + "ApiKeyHelpTextWarning": "ต้องรีสตาร์ทเพื่อให้มีผล", + "ApplyTags": "ใช้แท็ก", + "BypassProxyForLocalAddresses": "บายพาสพร็อกซีสำหรับที่อยู่ท้องถิ่น", + "GeneralSettings": "การตั้งค่าทั่วไป", + "IncludeUnmonitored": "รวม Unmonitored", + "ManualImport": "นำเข้าด้วยตนเอง", + "MarkAsFailed": "ทำเครื่องหมายว่าล้มเหลว", + "MarkAsFailedMessageText": "แน่ใจหรือไม่ว่าต้องการทำเครื่องหมาย \"{0}\" ว่าล้มเหลว", + "MaximumLimits": "ขีด จำกัด สูงสุด", + "NoHistory": "ไม่มีประวัติ", + "NoLeaveIt": "ไม่ปล่อยไว้", + "SslCertPathHelpText": "พา ธ ไปยังไฟล์ pfx", + "MonoVersion": "เวอร์ชันโมโน" } diff --git a/src/NzbDrone.Core/Localization/Core/tr.json b/src/NzbDrone.Core/Localization/Core/tr.json index bdf142fec..b364bb844 100644 --- a/src/NzbDrone.Core/Localization/Core/tr.json +++ b/src/NzbDrone.Core/Localization/Core/tr.json @@ -8,7 +8,7 @@ "UnmonitoredHelpText": "İCal akışına izlenmeyen filmleri dahil et", "UpdateAll": "Tümünü Güncelle", "UpdateAutomaticallyHelpText": "Güncellemeleri otomatik olarak indirin ve yükleyin. Yine de Sistem'den yükleyebileceksiniz: Güncellemeler", - "UpdateMechanismHelpText": "Radarr'ın yerleşik güncelleyicisini veya bir komut dosyasını kullanın", + "UpdateMechanismHelpText": "Lidarr'ın yerleşik güncelleyicisini veya bir komut dosyasını kullanın", "UpdateScriptPathHelpText": "Çıkarılan bir güncelleme paketini alan ve güncelleme işleminin geri kalanını işleyen özel bir komut dosyasına giden yol", "UpgradeAllowedHelpText": "Devre dışı bırakılırsa nitelikler yükseltilmez", "Uptime": "Uptime", @@ -21,7 +21,7 @@ "UsenetDelayHelpText": "Usenet'ten bir sürüm almadan önce beklemek için dakika cinsinden gecikme", "UseProxy": "Proxy kullan", "Username": "Kullanıcı adı", - "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Radarr'ı güncellemek için kullanılacak dal", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Lidarr'ı güncellemek için kullanılacak dal", "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Harici güncelleme mekanizması tarafından kullanılan dal", "Version": "Sürüm", "WeekColumnHeader": "Hafta Sütun Başlığı", @@ -42,7 +42,7 @@ "AlternateTitleslength1Title": "Başlık", "AlternateTitleslength1Titles": "Başlıklar", "Analytics": "Analitik", - "AnalyticsEnabledHelpText": "Anonim kullanım ve hata bilgilerini Radarr sunucularına gönderin. Bu, tarayıcınızla ilgili bilgileri, kullandığınız Radarr WebUI sayfalarını, hata raporlamasının yanı sıra işletim sistemi ve çalışma zamanı sürümünü içerir. Bu bilgileri, özellikleri ve hata düzeltmelerini önceliklendirmek için kullanacağız.", + "AnalyticsEnabledHelpText": "Anonim kullanım ve hata bilgilerini Lidarr sunucularına gönderin. Bu, tarayıcınızla ilgili bilgileri, kullandığınız Lidarr WebUI sayfalarını, hata raporlamasının yanı sıra işletim sistemi ve çalışma zamanı sürümünü içerir. Bu bilgileri, özellikleri ve hata düzeltmelerini önceliklendirmek için kullanacağız.", "AnalyticsEnabledHelpTextWarning": "Etkili olması için yeniden başlatma gerektirir", "ApiKeyHelpTextWarning": "Etkili olması için yeniden başlatma gerektirir", "Scheduled": "Tarifeli", @@ -64,7 +64,7 @@ "ShowMonitoredHelpText": "Posterin altında izlenen durumu göster", "Size": " Ölçü", "SkipFreeSpaceCheck": "Boş Alan Kontrolünü Atla", - "SkipFreeSpaceCheckWhenImportingHelpText": "Radarr film kök klasörünüzden boş alan algılayamadığında kullanın", + "SkipFreeSpaceCheckWhenImportingHelpText": "Lidarr film kök klasörünüzden boş alan algılayamadığında kullanın", "SorryThatAlbumCannotBeFound": "Maalesef o film bulunamıyor.", "SorryThatArtistCannotBeFound": "Maalesef o film bulunamıyor.", "Source": "Kaynak", @@ -76,9 +76,9 @@ "ApplyTagsHelpTexts4": "Değiştir: Etiketleri girilen etiketlerle değiştirin (tüm etiketleri temizlemek için hiçbir etiket girmeyin)", "ArtistAlbumClickToChangeTrack": "Filmi değiştirmek için tıklayın", "Authentication": "Doğrulama", - "AuthenticationMethodHelpText": "Radarr'a erişmek için Kullanıcı Adı ve Şifre gerektir", + "AuthenticationMethodHelpText": "Lidarr'a erişmek için Kullanıcı Adı ve Şifre gerektir", "AutoRedownloadFailedHelpText": "Otomatik olarak farklı bir sürüm arayın ve indirmeye çalışın", - "BackupFolderHelpText": "Göreli yollar Radarr'ın AppData dizini altında olacaktır", + "BackupFolderHelpText": "Göreli yollar Lidarr'ın AppData dizini altında olacaktır", "BackupNow": "Şimdi yedekle", "BackupRetentionHelpText": "Saklama süresinden daha eski olan otomatik yedeklemeler otomatik olarak temizlenecektir", "Backups": "Yedeklemeler", @@ -86,7 +86,7 @@ "BindAddressHelpText": "Tüm arayüzler için geçerli IP4 adresi veya '*'", "BindAddressHelpTextWarning": "Etkili olması için yeniden başlatma gerektirir", "Blocklist": "Kara liste", - "BlocklistHelpText": "Radarr'ın bu sürümü otomatik olarak tekrar yakalamasını engeller", + "BlocklistHelpText": "Lidarr'ın bu sürümü otomatik olarak tekrar yakalamasını engeller", "BlocklistRelease": "Kara Liste Yayını", "Branch": "Şube", "BypassProxyForLocalAddresses": "Yerel Adresler için Proxy'yi Atla", @@ -100,9 +100,9 @@ "ChangeHasNotBeenSavedYet": "Değişiklik henüz kaydedilmedi", "ChmodFolder": "chmod Klasörü", "ChmodFolderHelpText": "Sekizli, medya klasörlerine ve dosyalara içe aktarma / yeniden adlandırma sırasında uygulanır (yürütme bitleri olmadan)", - "ChmodFolderHelpTextWarning": "Bu, yalnızca Radarr'ı çalıştıran kullanıcı dosyanın sahibi ise çalışır. İndirme istemcisinin izinleri doğru şekilde ayarladığından emin olmak daha iyidir.", + "ChmodFolderHelpTextWarning": "Bu, yalnızca Lidarr'ı çalıştıran kullanıcı dosyanın sahibi ise çalışır. İndirme istemcisinin izinleri doğru şekilde ayarladığından emin olmak daha iyidir.", "ChownGroupHelpText": "Grup adı veya gid. Uzak dosya sistemleri için gid kullanın.", - "ChownGroupHelpTextWarning": "Bu, yalnızca Radarr'ı çalıştıran kullanıcı dosyanın sahibi ise çalışır. İndirme istemcisinin Radarr ile aynı grubu kullanmasını sağlamak daha iyidir.", + "ChownGroupHelpTextWarning": "Bu, yalnızca Lidarr'ı çalıştıran kullanıcı dosyanın sahibi ise çalışır. İndirme istemcisinin Lidarr ile aynı grubu kullanmasını sağlamak daha iyidir.", "Clear": "Temizle", "ClickToChangeQuality": "Kaliteyi değiştirmek için tıklayın", "ClientPriority": "Müşteri Önceliği", @@ -114,11 +114,11 @@ "Connections": "Bağlantılar", "ConnectSettings": "Bağlantı Ayarları", "CopyUsingHardlinksHelpText": "Hala tohumlanmakta olan torrentlerden dosya kopyalamaya çalışırken Sabit Bağlantıları kullanın", - "CopyUsingHardlinksHelpTextWarning": "Bazen, dosya kilitleri, başlatılan dosyaların yeniden adlandırılmasını engelleyebilir. Tohumlamayı geçici olarak devre dışı bırakabilir ve geçici olarak Radarr'ın yeniden adlandırma işlevini kullanabilirsiniz.", + "CopyUsingHardlinksHelpTextWarning": "Bazen, dosya kilitleri, başlatılan dosyaların yeniden adlandırılmasını engelleyebilir. Tohumlamayı geçici olarak devre dışı bırakabilir ve geçici olarak Lidarr'ın yeniden adlandırma işlevini kullanabilirsiniz.", "CreateEmptyArtistFolders": "Boş film klasörleri oluşturun", "CreateEmptyArtistFoldersHelpText": "Disk taraması sırasında eksik film klasörleri oluşturun", "CreateGroup": "Grup oluştur", - "CutoffHelpText": "Bu kaliteye ulaşıldığında Radarr artık film indirmeyecektir", + "CutoffHelpText": "Bu kaliteye ulaşıldığında Lidarr artık film indirmeyecektir", "CutoffUnmet": "Kesme Karşılanmadı", "DBMigration": "DB Geçişi", "DelayProfile": "Gecikme Profilleri", @@ -195,7 +195,7 @@ "Grab": "Kapmak", "GrabID": "Grab ID", "GrabRelease": "Bırakma", - "GrabReleaseMessageText": "Radarr, bu sürümün hangi film için olduğunu belirleyemedi. Radarr bu sürümü otomatik olarak içe aktaramayabilir. '{0}' almak istiyor musunuz?", + "GrabReleaseMessageText": "Lidarr, bu sürümün hangi film için olduğunu belirleyemedi. Lidarr bu sürümü otomatik olarak içe aktaramayabilir. '{0}' almak istiyor musunuz?", "GrabSelected": "Seçilenleri Kap", "Group": "Grup", "HasPendingChangesNoChanges": "Değişiklikler yok", @@ -219,7 +219,7 @@ "ImportFailedInterp": "İçe aktarma başarısız oldu: {0}", "Importing": "İçe aktarılıyor", "IncludeHealthWarningsHelpText": "Sağlık Uyarılarını Dahil Et", - "IncludeUnknownArtistItemsHelpText": "Kuyrukta film olmayan öğeleri gösterin. Bu, kaldırılan filmleri veya Radarr'ın kategorisindeki herhangi bir şeyi içerebilir", + "IncludeUnknownArtistItemsHelpText": "Kuyrukta film olmayan öğeleri gösterin. Bu, kaldırılan filmleri veya Lidarr'ın kategorisindeki herhangi bir şeyi içerebilir", "IncludeUnmonitored": "İzlenmeyenleri Dahil Et", "Indexer": "Dizin oluşturucu", "IndexerPriority": "Dizin Oluşturucu Önceliği", @@ -230,15 +230,15 @@ "IsCutoffCutoff": "Ayırmak", "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Bu kalite karşılanana veya aşılana kadar yükseltin", "IsTagUsedCannotBeDeletedWhileInUse": "Kullanımdayken silinemez", - "LaunchBrowserHelpText": " Bir web tarayıcısı açın ve uygulama başlangıcında Radarr ana sayfasına gidin.", + "LaunchBrowserHelpText": " Bir web tarayıcısı açın ve uygulama başlangıcında Lidarr ana sayfasına gidin.", "Level": "Seviye", - "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Radarr, Newznab standardını kullanan herhangi bir indirme istemcisinin yanı sıra aşağıda listelenen diğer indirme istemcilerini de destekler.", - "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr, Newznab standardını kullanan tüm indeksleyicileri ve aşağıda listelenen diğer indeksleyicileri destekler.", - "LidarrTags": "Radarr Etiketleri", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr, Newznab standardını kullanan herhangi bir indirme istemcisinin yanı sıra aşağıda listelenen diğer indirme istemcilerini de destekler.", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr, Newznab standardını kullanan tüm indeksleyicileri ve aşağıda listelenen diğer indeksleyicileri destekler.", + "LidarrTags": "Lidarr Etiketleri", "LoadingTrackFilesFailed": "Film dosyaları yüklenemedi", "Local": "Yerel", "LocalPath": "Yerel Yol", - "LocalPathHelpText": "Radarr'ın uzak yola yerel olarak erişmek için kullanması gereken yol", + "LocalPathHelpText": "Lidarr'ın uzak yola yerel olarak erişmek için kullanması gereken yol", "LogFiles": "Log dosyaları", "Logging": "Logging", "LogLevel": "Günlük Düzeyi", @@ -316,7 +316,6 @@ "QualityProfiles": "Kalite Profileri", "QualitySettings": "Kalite Ayarları", "Queue": "Sıra", - "Radarr": "Radarr", "ReadTheWikiForMoreInformation": "Daha fazla bilgi için Wiki'yi okuyun", "Real": "Gerçek", "Reason": "Nedeni", @@ -349,21 +348,21 @@ "RemoveSelectedMessageText": "Kara listeden seçili öğeleri kaldırmak istediğinizden emin misiniz?", "RemoveTagExistingTag": "Mevcut etiket", "RemoveTagRemovingTag": "Etiket kaldırılıyor", - "RenameTracksHelpText": "Yeniden adlandırma devre dışı bırakılırsa, Radarr mevcut dosya adını kullanacaktır", + "RenameTracksHelpText": "Yeniden adlandırma devre dışı bırakılırsa, Lidarr mevcut dosya adını kullanacaktır", "Reorder": "Yeniden sırala", "ReplaceIllegalCharacters": "Yasadışı Karakterleri Değiştirin", - "ReplaceIllegalCharactersHelpText": "Geçersiz karakterleri değiştirin. İşaretli değilse, Radarr onları kaldıracaktır.", + "ReplaceIllegalCharactersHelpText": "Geçersiz karakterleri değiştirin. İşaretli değilse, Lidarr onları kaldıracaktır.", "RequiredHelpText": "Sürüm, bu terimlerden en az birini içermelidir (büyük / küçük harfe duyarlı değildir)", "RequiredPlaceHolder": "Yeni kısıtlama ekle", "RequiresRestartToTakeEffect": "Etkili olması için yeniden başlatma gerektirir", "RescanAfterRefreshHelpText": "Filmi yeniledikten sonra film klasörünü yeniden tarayın", - "RescanAfterRefreshHelpTextWarning": "Radarr, 'Her Zaman' olarak ayarlanmadığında dosyalardaki değişiklikleri otomatik olarak algılamayacaktır", + "RescanAfterRefreshHelpTextWarning": "Lidarr, 'Her Zaman' olarak ayarlanmadığında dosyalardaki değişiklikleri otomatik olarak algılamayacaktır", "RescanArtistFolderAfterRefresh": "Yenilemeden Sonra Film Klasörünü Yeniden Tara", "Reset": "Sıfırla", "ResetAPIKey": "API Anahtarını Sıfırla", "ResetAPIKeyMessageText": "API Anahtarınızı sıfırlamak istediğinizden emin misiniz?", "Restart": "Tekrar başlat", - "RestartLidarr": "Radarr'ı yeniden başlatın", + "RestartLidarr": "Lidarr'ı yeniden başlatın", "RestartNow": "Şimdi yeniden başlat", "Restore": "Onarmak", "RestoreBackup": "Yedeği Geri Yükle", @@ -403,7 +402,7 @@ "SuccessMyWorkIsDoneNoFilesToRetag": "Başarılı! İşim bitti, yeniden adlandırılacak dosya yok.", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS, bu indeksleyici ile desteklenmiyor", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Bu indeksleyici ile arama desteklenmiyor", - "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Kullanıcı arayüzü veya Radarr tarafından otomatik aramalar yapıldığında kullanılacaktır", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Kullanıcı arayüzü veya Lidarr tarafından otomatik aramalar yapıldığında kullanılacaktır", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Etkileşimli arama kullanıldığında kullanılacak", "TagIsNotUsedAndCanBeDeleted": "Etiket kullanılmaz ve silinebilir", "Tags": "Etiketler", @@ -428,7 +427,7 @@ "ApplyTags": "Etiketleri Uygula", "Remove": "Kaldırmak", "SearchAll": "Tümünü ara", - "UILanguageHelpText": "Radarr'ın UI için kullanacağı dil", + "UILanguageHelpText": "Lidarr'ın UI için kullanacağı dil", "UILanguageHelpTextWarning": "Tarayıcının Yeniden Yüklenmesi Gerekiyor", "UnableToAddANewImportListExclusionPleaseTryAgain": "Yeni bir liste dışlaması eklenemiyor, lütfen tekrar deneyin.", "UnableToAddANewIndexerPleaseTryAgain": "Yeni bir dizinleyici eklenemiyor, lütfen tekrar deneyin.", @@ -467,5 +466,13 @@ "Docker": "Liman işçisi", "Dates": "Tarih", "Name": "İsim", - "RemoveFilter": "Filtreyi kaldır" + "RemoveFilter": "Filtreyi kaldır", + "ThisCannotBeCancelled": "Bu, Whisparr yeniden başlatılmadan başlatıldıktan sonra iptal edilemez.", + "OnGrab": "Yakalandığında", + "OnHealthIssue": "Sağlık Sorunu Hakkında", + "OnRename": "Yeniden Adlandırıldığında", + "OnUpgrade": "Yükseltme sırasında", + "Tracks": "İzleme", + "NETCore": ".NET Çekirdeği", + "MonoVersion": "Mono Versiyon" } diff --git a/src/NzbDrone.Core/Localization/Core/uk.json b/src/NzbDrone.Core/Localization/Core/uk.json index 25bbc613b..32c060301 100644 --- a/src/NzbDrone.Core/Localization/Core/uk.json +++ b/src/NzbDrone.Core/Localization/Core/uk.json @@ -51,5 +51,41 @@ "DeleteBackup": "Видалити резервну копію", "DeleteDelayProfile": "Видалити профіль затримки", "DeleteDownloadClient": "Видалити клієнт завантаження", - "DeleteEmptyFolders": "Видалити порожні папки" + "DeleteEmptyFolders": "Видалити порожні папки", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Гілка для оновлення Lidarr", + "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Гілка, що використовується зовнішнім механізмом оновлення", + "RemoveSelectedMessageText": "Ви впевнені, що хочете видалити вибрані елементи зі списку блокування?", + "ResetAPIKeyMessageText": "Ви впевнені, що хочете скинути свій ключ API?", + "ShowQualityProfile": "Додати профіль якості", + "AnalyticsEnabledHelpText": "Надсилайте анонімну інформацію про використання та помилки на сервери Lidarr. Це включає інформацію про ваш веб-переглядач, які сторінки Lidarr WebUI ви використовуєте, звіти про помилки, а також версію ОС і часу виконання. Ми будемо використовувати цю інформацію, щоб визначити пріоритети функцій і виправлення помилок.", + "ApplyTagsHelpTexts2": "Додати: додати теги до наявного списку тегів", + "DeleteMetadataProfileMessageText": "Ви впевнені, що хочете видалити цей профіль затримки?", + "DeleteNotificationMessageText": "Ви впевнені, що хочете видалити клієнт завантаження '{0}'?", + "DeleteQualityProfileMessageText": "Ви впевнені, що хочете видалити цей профіль затримки?", + "DeleteReleaseProfile": "Видалити профіль затримки", + "DeleteReleaseProfileMessageText": "Ви впевнені, що хочете видалити цей профіль затримки?", + "DeleteRootFolderMessageText": "Ви впевнені, що хочете видалити тег {0} ?", + "DeleteTagMessageText": "Ви впевнені, що хочете видалити тег {0} ?", + "IsCutoffCutoff": "Припинення", + "CertificateValidationHelpText": "Змініть сувору перевірку сертифікації HTTPS. Не змінюйте, якщо не зрозумієте ризики.", + "AlternateTitles": "Альтернативна назва", + "APIKey": "API Ключ", + "ApplyTagsHelpTexts1": "Як застосувати теги до вибраних фільмів", + "ArtistAlbumClickToChangeTrack": "Натисніть, щоб змінити фільм", + "AuthenticationMethodHelpText": "Для доступу до Lidarr потрібні ім’я користувача та пароль", + "BackupFolderHelpText": "Відносні шляхи будуть у каталозі AppData Lidarr", + "BlocklistHelpText": "Забороняє Lidarr знову автоматично захопити цей випуск", + "CancelMessageText": "Ви впевнені, що хочете скасувати це незавершене завдання?", + "ChmodFolderHelpTextWarning": "Це працює лише в тому випадку, якщо власником файлу є користувач, на якому працює Lidarr. Краще переконатися, що клієнт завантаження правильно встановлює дозволи.", + "ChownGroupHelpText": "Назва групи або gid. Використовуйте gid для віддалених файлових систем.", + "ChownGroupHelpTextWarning": "Це працює лише в тому випадку, якщо власником файлу є користувач, на якому працює Lidarr. Краще переконатися, що клієнт для завантаження використовує ту саму групу, що й Lidarr.", + "CopyUsingHardlinksHelpTextWarning": "Блокування файлів може заважати перейменуванню файлів під час роздачі. Можна тимчасово зупинити роздачу та використовувати функції Lidarr для перейменування.", + "CreateEmptyArtistFolders": "Створіть порожні папки для фільмів", + "CreateEmptyArtistFoldersHelpText": "Створити папки для не знайдених фільмів під час сканування", + "CutoffHelpText": "Після досягнення цієї якості Lidarr більше не завантажуватиме фільми", + "CutoffUnmet": "Поріг невиконаний", + "DeleteDelayProfileMessageText": "Ви впевнені, що хочете видалити цей профіль затримки?", + "DeleteImportListMessageText": "Ви впевнені, що хочете видалити тег {0} ?", + "DeleteIndexerMessageText": "Ви впевнені, що хочете видалити тег {0} ?", + "BindAddressHelpText": "Дійсна адреса IPv4 або '*' для всіх інтерфейсів" } diff --git a/src/NzbDrone.Core/Localization/Core/vi.json b/src/NzbDrone.Core/Localization/Core/vi.json index b762d7801..9eade105f 100644 --- a/src/NzbDrone.Core/Localization/Core/vi.json +++ b/src/NzbDrone.Core/Localization/Core/vi.json @@ -1,4 +1,477 @@ { "Language": "Ngôn ngữ", - "UILanguage": "Ngôn ngữ giao diện người dùng" + "UILanguage": "Ngôn ngữ giao diện người dùng", + "RescanArtistFolderAfterRefresh": "Quét lại thư mục phim sau khi làm mới", + "Reset": "Cài lại", + "ResetAPIKey": "Đặt lại khóa API", + "ResetAPIKeyMessageText": "Bạn có chắc chắn muốn đặt lại Khóa API của mình không?", + "Restart": "Khởi động lại", + "RestartLidarr": "Khởi động lại Lidarr", + "RestoreBackup": "Khôi phục lại bản sao lưu", + "ShowMonitored": "Hiển thị được Giám sát", + "ShowMonitoredHelpText": "Hiển thị trạng thái được giám sát dưới áp phích", + "ShowUnknownArtistItems": "Hiển thị các mục phim không xác định", + "Status": "Trạng thái", + "Analytics": "phân tích", + "Track": "Dấu vết", + "Tracks": "Dấu vết", + "UsenetDelayHelpText": "Trì hoãn vài phút để đợi trước khi lấy bản phát hành từ Usenet", + "UseProxy": "Sử dụng Proxy", + "YesCancel": "Có, Hủy bỏ", + "RestartNow": "Khởi động lại ngay", + "ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Để biết thêm thông tin về các ứng dụng khách tải xuống riêng lẻ, hãy nhấp vào các nút thông tin.", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Whisparr hỗ trợ bất kỳ ứng dụng khách tải xuống nào sử dụng tiêu chuẩn Newznab, cũng như các ứng dụng khách tải xuống khác được liệt kê bên dưới.", + "RemovedFromTaskQueue": "Đã xóa khỏi hàng đợi tác vụ", + "ShowCutoffUnmetIconHelpText": "Hiển thị biểu tượng cho các tệp khi điểm giới hạn chưa được đáp ứng", + "MaximumSize": "Kích thước tối đa", + "MaximumSizeHelpText": "Kích thước tối đa cho một bản phát hành được tính bằng MB. Đặt thành 0 để đặt thành không giới hạn", + "Mechanism": "Cơ chế", + "MediaInfo": "Thông tin truyền thông", + "MediaManagementSettings": "Cài đặt quản lý phương tiện", + "Medium": "Trung bình", + "Message": "Thông điệp", + "MetadataSettings": "Cài đặt siêu dữ liệu", + "MIA": "MIA", + "MinimumAge": "Tuổi tối thiểu", + "MinimumAgeHelpText": "Usenet only: Tuổi tối thiểu tính bằng phút của NZB trước khi chúng bị tóm. Sử dụng điều này để cho các bản phát hành mới có thời gian phổ biến tới nhà cung cấp mạng sử dụng của bạn.", + "MinimumFreeSpace": "Dung lượng trống tối thiểu", + "MinimumFreeSpaceWhenImportingHelpText": "Ngăn nhập nếu nó để lại ít hơn số lượng không gian đĩa khả dụng này", + "MinimumLimits": "Giới hạn tối thiểu", + "Missing": "Còn thiếu", + "Mode": "Chế độ", + "Monitored": "Giám sát", + "MoreInfo": "Thêm thông tin", + "MustContain": "Phải chứa", + "MustNotContain": "Không được chứa", + "Name": "Tên", + "NamingSettings": "Cài đặt đặt tên", + "NETCore": ".NET Core", + "New": "Mới", + "NoBackupsAreAvailable": "Không có bản sao lưu nào", + "NoHistory": "Không có lịch sử", + "NoLeaveIt": "Không để nó", + "NoLimitForAnyRuntime": "Không có giới hạn cho bất kỳ thời gian chạy nào", + "NoLogFiles": "Không có tệp nhật ký", + "NoMinimumForAnyRuntime": "Không có tối thiểu cho bất kỳ thời gian chạy nào", + "None": "không ai", + "NotificationTriggers": "Kích hoạt thông báo", + "NoUpdatesAreAvailable": "Không có bản cập nhật nào có sẵn", + "OnRename": "Khi đổi tên", + "OnRenameHelpText": "Khi đổi tên", + "OnUpgrade": "Đang nâng cấp", + "OnUpgradeHelpText": "Đang nâng cấp", + "OpenBrowserOnStart": "Mở trình duyệt khi bắt đầu", + "Options": "Tùy chọn", + "Original": "Nguyên", + "PageSizeHelpText": "Số mục hiển thị trên mỗi trang", + "Path": "Con đường", + "Permissions": "Quyền", + "Port": "Hải cảng", + "PortNumber": "Số cổng", + "PosterSize": "Kích thước áp phích", + "Preferred": "Ưu tiên", + "PreviewRename": "Xem trước Đổi tên", + "PriorityHelpText": "Mức độ ưu tiên của người lập chỉ mục từ 1 (Cao nhất) đến 50 (Thấp nhất). Mặc định: 25.", + "Profiles": "Hồ sơ", + "Proper": "Thích hợp", + "PropersAndRepacks": "Người ủng hộ và người đóng gói", + "Protocol": "Giao thức", + "ProtocolHelpText": "Chọn (các) giao thức để sử dụng và giao thức nào được ưu tiên khi lựa chọn giữa các bản phát hành ngang nhau", + "Proxy": "Ủy quyền", + "ProxyBypassFilterHelpText": "Sử dụng ',' làm dấu phân tách và '*.' làm ký tự đại diện cho các miền phụ", + "ProxyPasswordHelpText": "Bạn chỉ cần nhập tên người dùng và mật khẩu nếu được yêu cầu. Nếu không, hãy để trống chúng.", + "ProxyType": "Loại proxy", + "ProxyUsernameHelpText": "Bạn chỉ cần nhập tên người dùng và mật khẩu nếu được yêu cầu. Nếu không, hãy để trống chúng.", + "PublishedDate": "Ngày xuất bản", + "Quality": "Chất lượng", + "QualityDefinitions": "Định nghĩa chất lượng", + "QualityProfile": "Hồ sơ chất lượng", + "QualityProfiles": "Hồ sơ chất lượng", + "QualitySettings": "Cài đặt chất lượng", + "Queue": "Xếp hàng", + "ReadTheWikiForMoreInformation": "Đọc Wiki để biết thêm thông tin", + "Real": "Thực tế", + "Reason": "Lý do", + "RecyclingBinCleanup": "Thu dọn thùng rác tái chế", + "Redownload": "Tải lại", + "Refresh": "Làm tươi", + "RefreshInformationAndScanDisk": "Làm mới thông tin và quét đĩa", + "RefreshScan": "Làm mới và quét", + "ReleaseDate": "Ngày phát hành", + "ReleaseGroup": "Nhóm phát hành", + "ReleaseRejected": "Bản phát hành bị từ chối", + "ReleaseStatuses": "Tình trạng phát hành", + "ReleaseWillBeProcessedInterp": "Bản phát hành sẽ được xử lý {0}", + "Reload": "Nạp lại", + "RemotePath": "Đường đi từ xa", + "RemotePathHelpText": "Đường dẫn gốc đến thư mục mà Ứng dụng khách tải xuống truy cập", + "RemotePathMappings": "Ánh xạ đường dẫn từ xa", + "Remove": "Tẩy", + "RemoveCompletedDownloadsHelpText": "Xóa các bản tải xuống đã nhập khỏi lịch sử tải xuống của ứng dụng khách", + "RemoveFailedDownloadsHelpText": "Xóa các lần tải xuống không thành công khỏi lịch sử máy khách tải xuống", + "RemoveFilter": "Xóa bộ lọc", + "RemoveFromBlocklist": "Xóa khỏi danh sách đen", + "RemoveFromDownloadClient": "Xóa khỏi ứng dụng khách tải xuống", + "RemoveFromQueue": "Xóa khỏi hàng đợi", + "RemoveHelpTextWarning": "Việc xóa sẽ xóa phần tải xuống và (các) tệp khỏi ứng dụng khách tải xuống.", + "RemoveSelected": "Bỏ đã chọn", + "RemoveSelectedMessageText": "Bạn có chắc chắn muốn xóa các mục đã chọn khỏi danh sách đen không?", + "RemoveTagExistingTag": "Thẻ hiện có", + "RemoveTagRemovingTag": "Xóa thẻ", + "RenameTracksHelpText": "Lidarr sẽ sử dụng tên tệp hiện có nếu việc đổi tên bị tắt", + "Reorder": "Sắp xếp lại", + "ReplaceIllegalCharacters": "Thay thế các ký tự bất hợp pháp", + "ReplaceIllegalCharactersHelpText": "Thay thế các ký tự bất hợp pháp. Nếu không được chọn, Lidarr sẽ xóa chúng thay thế", + "RequiredHelpText": "Bản phát hành phải chứa ít nhất một trong các điều khoản này (không phân biệt chữ hoa chữ thường)", + "RequiredPlaceHolder": "Thêm hạn chế mới", + "RequiresRestartToTakeEffect": "Yêu cầu khởi động lại để có hiệu lực", + "RescanAfterRefreshHelpText": "Quét lại thư mục phim sau khi làm mới phim", + "RescanAfterRefreshHelpTextWarning": "Lidarr sẽ không tự động phát hiện các thay đổi đối với tệp khi không được đặt thành 'Luôn luôn'", + "Restore": "Khôi phục", + "Result": "Kết quả", + "Retention": "Giữ lại", + "RetentionHelpText": "Chỉ sử dụng mạng: Đặt thành 0 để đặt giữ chân không giới hạn", + "RetryingDownloadInterp": "Đang thử tải xuống lại {0} lúc {1}", + "RootFolder": "Thư mục gốc", + "RootFolders": "Thư mục gốc", + "RSSSync": "Đồng bộ hóa RSS", + "RSSSyncInterval": "Khoảng thời gian đồng bộ hóa RSS", + "RssSyncIntervalHelpText": "Khoảng thời gian tính bằng phút. Đặt thành 0 để tắt (điều này sẽ dừng tất cả việc lấy bản phát hành tự động)", + "SearchAll": "Tìm kiếm tất cả", + "SearchForMissing": "Tìm kiếm mất tích", + "SearchSelected": "Tìm kiếm đã chọn", + "Security": "Bảo vệ", + "ShowDateAdded": "Hiển thị Ngày đã Thêm", + "ShownAboveEachColumnWhenWeekIsTheActiveView": "Được hiển thị phía trên mỗi cột khi tuần là chế độ xem đang hoạt động", + "ShowPath": "Hiển thị đường dẫn", + "ShowQualityProfile": "Hiển thị hồ sơ chất lượng", + "ShowQualityProfileHelpText": "Hiển thị hồ sơ chất lượng dưới áp phích", + "ShowRelativeDates": "Hiển thị Ngày tương đối", + "ShowRelativeDatesHelpText": "Hiển thị ngày tương đối (Hôm nay / Hôm qua / v.v.) hoặc ngày tuyệt đối", + "ShowSearch": "Hiển thị Tìm kiếm", + "ShowSearchActionHelpText": "Hiển thị nút tìm kiếm khi di chuột", + "ShowSizeOnDisk": "Hiển thị kích thước trên đĩa", + "Size": " Kích thước", + "SkipFreeSpaceCheck": "Bỏ qua kiểm tra dung lượng trống", + "SkipFreeSpaceCheckWhenImportingHelpText": "Sử dụng khi Lidarr không thể phát hiện dung lượng trống từ thư mục gốc phim của bạn", + "SorryThatAlbumCannotBeFound": "Xin lỗi, không thể tìm thấy bộ phim đó.", + "SorryThatArtistCannotBeFound": "Xin lỗi, không thể tìm thấy bộ phim đó.", + "Source": "Nguồn", + "SourcePath": "Đường dẫn nguồn", + "SSLCertPassword": "Mật khẩu cảnh báo SSL", + "SslCertPasswordHelpText": "Mật khẩu cho tệp pfx", + "SslCertPasswordHelpTextWarning": "Yêu cầu khởi động lại để có hiệu lực", + "SSLCertPath": "Đường dẫn cảnh báo SSL", + "SslCertPathHelpText": "Đường dẫn đến tệp pfx", + "SslCertPathHelpTextWarning": "Yêu cầu khởi động lại để có hiệu lực", + "SSLPort": "Cổng SSL", + "SslPortHelpTextWarning": "Yêu cầu khởi động lại để có hiệu lực", + "StandardTrackFormat": "Định dạng phim tiêu chuẩn", + "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Trình lập chỉ mục này không hỗ trợ tìm kiếm", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Sẽ được sử dụng khi tìm kiếm tự động được thực hiện qua giao diện người dùng hoặc bằng Lidarr", + "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Sẽ được sử dụng khi tìm kiếm tương tác được sử dụng", + "TagIsNotUsedAndCanBeDeleted": "Thẻ không được sử dụng và có thể bị xóa", + "Tasks": "Nhiệm vụ", + "TestAll": "Kiểm tra tất cả", + "TestAllClients": "Kiểm tra tất cả khách hàng", + "TestAllIndexers": "Kiểm tra tất cả các chỉ mục", + "TestAllLists": "Kiểm tra tất cả danh sách", + "ThisCannotBeCancelled": "Điều này không thể bị hủy sau khi bắt đầu mà không khởi động lại Whisparr.", + "ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "Điều này sẽ áp dụng cho tất cả người lập chỉ mục, vui lòng tuân theo các quy tắc do họ đặt ra", + "Time": "Thời gian", + "TorrentDelayHelpText": "Trì hoãn trong vài phút để đợi trước khi lấy một torrent", + "Torrents": "Torrents", + "TotalFileSize": "Tổng kích thước tệp", + "Type": "Kiểu", + "UISettings": "Cài đặt giao diện người dùng", + "UnableToAddANewImportListExclusionPleaseTryAgain": "Không thể thêm loại trừ danh sách mới, vui lòng thử lại.", + "UnableToAddANewRemotePathMappingPleaseTryAgain": "Không thể thêm ánh xạ đường dẫn từ xa mới, vui lòng thử lại.", + "UnableToAddANewRootFolderPleaseTryAgain": "Không thể thêm định dạng tùy chỉnh mới, vui lòng thử lại.", + "UnableToLoadBackups": "Không thể tải các bản sao lưu", + "UnableToLoadBlocklist": "Không thể tải danh sách đen", + "UnableToLoadDelayProfiles": "Không thể tải hồ sơ độ trễ", + "UnableToLoadIndexers": "Không thể tải Trình chỉ mục", + "UnableToLoadNamingSettings": "Không thể tải cài đặt Đặt tên", + "UnableToLoadNotifications": "Không thể tải thông báo", + "UnableToLoadQualities": "Không thể tải chất lượng", + "UnableToLoadQualityDefinitions": "Không thể tải Định nghĩa chất lượng", + "UnableToLoadQualityProfiles": "Không thể tải Hồ sơ chất lượng", + "UnableToLoadReleaseProfiles": "Không thể tải hồ sơ độ trễ", + "UnableToLoadRemotePathMappings": "Không thể tải Ánh xạ đường dẫn từ xa", + "UnableToLoadRootFolders": "Không thể tải các thư mục gốc", + "UnableToLoadTags": "Không thể tải thẻ", + "UnableToLoadTheCalendar": "Không thể tải lịch", + "UnableToLoadUISettings": "Không thể tải cài đặt giao diện người dùng", + "Unmonitored": "Không giám sát", + "UnmonitoredHelpText": "Đưa các phim chưa được giám sát vào nguồn cấp dữ liệu iCal", + "UpdateAll": "Cập nhật tất cả", + "UpdateAutomaticallyHelpText": "Tự động tải xuống và cài đặt các bản cập nhật. Bạn vẫn có thể cài đặt từ Hệ thống: Cập nhật", + "UpdateMechanismHelpText": "Sử dụng trình cập nhật tích hợp của Lidarr hoặc một tập lệnh", + "Updates": "Cập nhật", + "UpdateScriptPathHelpText": "Đường dẫn đến tập lệnh tùy chỉnh có gói cập nhật được trích xuất và xử lý phần còn lại của quá trình cập nhật", + "UpgradeAllowedHelpText": "Nếu chất lượng bị vô hiệu hóa sẽ không được nâng cấp", + "Uptime": "Thời gian hoạt động", + "URLBase": "Cơ sở URL", + "UrlBaseHelpTextWarning": "Yêu cầu khởi động lại để có hiệu lực", + "UseHardlinksInsteadOfCopy": "Sử dụng liên kết cứng thay vì sao chép", + "UsenetDelay": "Sự chậm trễ của Usenet", + "OnGrab": "Trên Grab", + "OnGrabHelpText": "Trên Grab", + "OnHealthIssue": "Về vấn đề sức khỏe", + "OnHealthIssueHelpText": "Về vấn đề sức khỏe", + "Password": "Mật khẩu", + "RecycleBinCleanupDaysHelpText": "Đặt thành 0 để tắt tính năng dọn dẹp tự động", + "RecycleBinCleanupDaysHelpTextWarning": "Các tệp trong thùng rác cũ hơn số ngày đã chọn sẽ tự động được dọn dẹp", + "RecycleBinHelpText": "Các tệp phim sẽ chuyển đến đây khi bị xóa thay vì bị xóa vĩnh viễn", + "RecyclingBin": "Thùng tái chế", + "SetPermissionsLinuxHelpTextWarning": "Nếu bạn không chắc chắn những cài đặt này có tác dụng gì, đừng thay đổi chúng.", + "Settings": "Cài đặt", + "ShortDateFormat": "Định dạng ngày ngắn", + "StartTypingOrSelectAPathBelow": "Bắt đầu nhập hoặc chọn một đường dẫn bên dưới", + "StartupDirectory": "Thư mục khởi động", + "Style": "Phong cách", + "SuccessMyWorkIsDoneNoFilesToRename": "Sự thành công! Công việc của tôi đã xong, không có tệp nào để đổi tên.", + "SuccessMyWorkIsDoneNoFilesToRetag": "Sự thành công! Công việc của tôi đã xong, không có tệp nào để đổi tên.", + "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS không được hỗ trợ với trình chỉ mục này", + "Tags": "Thẻ", + "TimeFormat": "Định dạng thời gian", + "TorrentDelay": "Torrent Delay", + "UnableToLoadDownloadClientOptions": "Không thể tải các tùy chọn ứng dụng khách tải xuống", + "UnableToLoadDownloadClients": "Không thể tải ứng dụng khách tải xuống", + "UnableToLoadGeneralSettings": "Không thể tải Cài đặt chung", + "UnableToLoadHistory": "Không thể tải lịch sử", + "UnableToLoadImportListExclusions": "Không thể tải Danh sách Loại trừ", + "UnableToLoadIndexerOptions": "Không thể tải các tùy chọn trình lập chỉ mục", + "UnableToAddANewNotificationPleaseTryAgain": "Không thể thêm thông báo mới, vui lòng thử lại.", + "UnableToAddANewQualityProfilePleaseTryAgain": "Không thể thêm hồ sơ chất lượng mới, vui lòng thử lại.", + "45MinutesFourtyFive": "60 phút: {0}", + "60MinutesSixty": "60 phút: {0}", + "APIKey": "Mã API", + "About": "Trong khoảng", + "AddListExclusion": "Thêm loại trừ danh sách", + "AddingTag": "Thêm thẻ", + "AdvancedSettingsHiddenClickToShow": "Ẩn, bấm để hiển thị", + "AdvancedSettingsShownClickToHide": "Hiển thị, nhấp để ẩn", + "AgeWhenGrabbed": "Tuổi (khi nắm lấy)", + "AlbumIsDownloadingInterp": "Đang tải phim xuống - {0}% {1}", + "AlreadyInYourLibrary": "Đã có trong thư viện của bạn", + "AlternateTitles": "Tiêu đề thay thế", + "AlternateTitleslength1Title": "Tiêu đề", + "AlternateTitleslength1Titles": "Tiêu đề", + "ApplyTags": "Áp dụng thẻ", + "ApplyTagsHelpTexts1": "Cách áp dụng thẻ cho các phim đã chọn", + "ApplyTagsHelpTexts2": "Thêm: Thêm thẻ vào danh sách thẻ hiện có", + "ApplyTagsHelpTexts3": "Xóa: Xóa các thẻ đã nhập", + "ApplyTagsHelpTexts4": "Thay thế: Thay thế các thẻ bằng các thẻ đã nhập (không nhập thẻ nào để xóa tất cả các thẻ)", + "ArtistAlbumClickToChangeTrack": "Bấm để thay đổi phim", + "AutoRedownloadFailedHelpText": "Tự động tìm kiếm và cố gắng tải xuống một bản phát hành khác", + "BackupFolderHelpText": "Các đường dẫn tương đối sẽ nằm trong thư mục AppData của Lidarr", + "BackupNow": "Sao lưu ngay", + "BackupRetentionHelpText": "Các bản sao lưu tự động cũ hơn khoảng thời gian lưu giữ sẽ tự động được dọn dẹp", + "Backups": "Sao lưu", + "BindAddress": "Địa chỉ ràng buộc", + "BindAddressHelpTextWarning": "Yêu cầu khởi động lại để có hiệu lực", + "Blocklist": "Danh sách đen", + "BlocklistRelease": "Phát hành danh sách đen", + "Branch": "Chi nhánh", + "BypassProxyForLocalAddresses": "Bỏ qua proxy cho địa chỉ cục bộ", + "Calendar": "Lịch", + "CalendarWeekColumnHeaderHelpText": "Được hiển thị phía trên mỗi cột khi tuần là chế độ xem đang hoạt động", + "Cancel": "Huỷ bỏ", + "CancelMessageText": "Bạn có chắc chắn muốn hủy nhiệm vụ đang chờ xử lý này không?", + "CertificateValidation": "Xác thực chứng chỉ", + "ChangeFileDate": "Thay đổi ngày tệp", + "ChangeHasNotBeenSavedYet": "Thay đổi vẫn chưa được lưu", + "ChmodFolder": "Thư mục chmod", + "ChmodFolderHelpText": "Hệ bát phân, được áp dụng trong quá trình nhập / đổi tên cho các thư mục và tệp phương tiện (không có bit thực thi)", + "ChmodFolderHelpTextWarning": "Điều này chỉ hoạt động nếu người dùng chạy Lidarr là chủ sở hữu của tệp. Tốt hơn là đảm bảo ứng dụng khách tải xuống đặt các quyền đúng cách.", + "ChownGroupHelpText": "Tên nhóm hoặc gid. Sử dụng gid cho các hệ thống tệp từ xa.", + "ChownGroupHelpTextWarning": "Điều này chỉ hoạt động nếu người dùng chạy Lidarr là chủ sở hữu của tệp. Tốt hơn là đảm bảo rằng ứng dụng khách tải xuống sử dụng cùng một nhóm với Lidarr.", + "Clear": "Thông thoáng", + "ClickToChangeQuality": "Nhấp để thay đổi chất lượng", + "ClientPriority": "Ưu tiên khách hàng", + "CloneIndexer": "Trình lập chỉ mục nhân bản", + "CloneProfile": "Hồ sơ nhân bản", + "Columns": "Cột", + "CompletedDownloadHandling": "Đã hoàn thành xử lý tải xuống", + "Component": "Thành phần", + "Connections": "Kết nối", + "ConnectSettings": "Kết nối Cài đặt", + "CopyUsingHardlinksHelpText": "Sử dụng Liên kết cứng khi cố gắng sao chép tệp từ torrent vẫn đang được seed", + "CopyUsingHardlinksHelpTextWarning": "Đôi khi, khóa tệp có thể ngăn đổi tên các tệp đang được seed. Bạn có thể tạm thời tắt tính năng seeding và sử dụng chức năng đổi tên của Lidarr như một công việc xung quanh.", + "CreateEmptyArtistFolders": "Tạo thư mục phim trống", + "CreateEmptyArtistFoldersHelpText": "Tạo thư mục phim bị thiếu trong quá trình quét đĩa", + "CreateGroup": "Tạo nhóm", + "CutoffHelpText": "Khi đạt đến chất lượng này Lidarr sẽ không tải phim nữa", + "CutoffUnmet": "Cắt bỏ chưa được đáp ứng", + "DBMigration": "Di chuyển DB", + "DelayProfile": "Hồ sơ trì hoãn", + "DelayProfiles": "Hồ sơ trì hoãn", + "Delete": "Xóa bỏ", + "DeleteBackup": "Xóa bản sao lưu", + "DeleteBackupMessageText": "Bạn có chắc chắn muốn xóa bản sao lưu '{0}' không?", + "DeleteDelayProfile": "Xóa hồ sơ độ trễ", + "DeleteDelayProfileMessageText": "Bạn có chắc chắn muốn xóa hồ sơ trì hoãn này không?", + "DeleteDownloadClient": "Xóa ứng dụng khách tải xuống", + "DeleteDownloadClientMessageText": "Bạn có chắc chắn muốn xóa ứng dụng khách tải xuống '{0}' không?", + "DeleteEmptyFolders": "Xóa các thư mục trống", + "DeleteImportListExclusion": "Xóa loại trừ danh sách nhập", + "DeleteImportListExclusionMessageText": "Bạn có chắc chắn muốn xóa loại trừ danh sách nhập này không?", + "DeleteImportListMessageText": "Bạn có chắc chắn muốn xóa danh sách '{0}' không?", + "DeleteIndexer": "Xóa trình lập chỉ mục", + "DeleteIndexerMessageText": "Bạn có chắc chắn muốn xóa trình lập chỉ mục '{0}' không?", + "DeleteMetadataProfileMessageText": "Bạn có chắc chắn muốn xóa cấu hình chất lượng không {0}", + "DeleteNotification": "Xóa thông báo", + "DeleteNotificationMessageText": "Bạn có chắc chắn muốn xóa thông báo '{0}' không?", + "DeleteQualityProfile": "Xóa hồ sơ chất lượng", + "DeleteQualityProfileMessageText": "Bạn có chắc chắn muốn xóa cấu hình chất lượng không {0}", + "DeleteReleaseProfile": "Xóa hồ sơ độ trễ", + "DeleteReleaseProfileMessageText": "Bạn có chắc chắn muốn xóa hồ sơ trì hoãn này không?", + "DeleteRootFolderMessageText": "Bạn có chắc chắn muốn xóa trình lập chỉ mục '{0}' không?", + "DeleteSelectedTrackFiles": "Xóa các tệp phim đã chọn", + "DeleteSelectedTrackFilesMessageText": "Bạn có chắc chắn muốn xóa các tệp phim đã chọn không?", + "DeleteTag": "Xóa thẻ", + "DeleteTagMessageText": "Bạn có chắc chắn muốn xóa thẻ '{0}' không?", + "DestinationPath": "Đường dẫn đích", + "DetailedProgressBar": "Thanh tiến trình chi tiết", + "DetailedProgressBarHelpText": "Hiển thị văn bản trên thanh tiến trình", + "DiskSpace": "Dung lượng đĩa", + "Docker": "Docker", + "DownloadClient": "Tải xuống ứng dụng khách", + "DownloadClients": "Tải xuống ứng dụng khách", + "DownloadClientSettings": "Tải xuống cài đặt ứng dụng khách", + "DownloadFailedCheckDownloadClientForMoreDetails": "Tải xuống không thành công: kiểm tra ứng dụng khách tải xuống để biết thêm chi tiết", + "DownloadFailedInterp": "Tải xuống không thành công: {0}", + "Downloading": "Đang tải xuống", + "DownloadPropersAndRepacksHelpTexts1": "Có tự động nâng cấp lên Propers / Repacks hay không", + "DownloadWarningCheckDownloadClientForMoreDetails": "Cảnh báo tải xuống: kiểm tra ứng dụng khách tải xuống để biết thêm chi tiết", + "Edit": "Biên tập", + "Enable": "Kích hoạt", + "EnableAutomaticAdd": "Bật tính năng Thêm tự động", + "EnableAutomaticSearch": "Bật tìm kiếm tự động", + "EnableColorImpairedMode": "Bật Chế độ Khuyết màu", + "EnableColorImpairedModeHelpText": "Đã thay đổi kiểu để cho phép người dùng khiếm thị phân biệt rõ hơn thông tin mã màu", + "EnableCompletedDownloadHandlingHelpText": "Tự động nhập các bản tải xuống đã hoàn thành từ máy khách tải xuống", + "EnableHelpText": "Cho phép tạo tệp siêu dữ liệu cho loại siêu dữ liệu này", + "EnableInteractiveSearch": "Bật tìm kiếm tương tác", + "EnableRSS": "Bật RSS", + "EnableSSL": "Bật SSL", + "EnableSslHelpText": " Yêu cầu khởi động lại chạy với tư cách quản trị viên để có hiệu lực", + "Ended": "Đã kết thúc", + "ErrorLoadingContents": "Lỗi khi tải nội dung", + "ErrorLoadingPreviews": "Lỗi khi tải bản xem trước", + "Exception": "ngoại lệ", + "ExtraFileExtensionsHelpTexts1": "Danh sách các tệp bổ sung cần nhập được phân tách bằng dấu phẩy (.nfo sẽ được nhập dưới dạng .nfo-orig)", + "FileDateHelpText": "Thay đổi ngày tệp khi nhập / quét lại", + "FileManagement": "Quản lý tệp", + "Filename": "Tên tệp", + "FileNames": "Tên tệp", + "Files": "Các tập tin", + "FirstDayOfWeek": "Ngày đầu tuần", + "Fixed": "đã sửa", + "Folder": "Thư mục", + "Folders": "Thư mục", + "ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "Để biết thêm thông tin về các chỉ mục riêng lẻ, hãy nhấp vào các nút thông tin.", + "ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "Để biết thêm thông tin về các danh sách nhập riêng lẻ, hãy nhấp vào các nút thông tin.", + "GeneralSettings": "Cài đặt chung", + "Global": "Toàn cầu", + "GoToInterp": "Đi tới {0}", + "Grab": "Vồ lấy", + "GrabID": "Lấy ID", + "GrabRelease": "Lấy bản phát hành", + "GrabReleaseMessageText": "Lidarr không thể xác định bộ phim này được phát hành. Lidarr có thể không tự động nhập bản phát hành này. Bạn có muốn lấy '{0}' không?", + "GrabSelected": "Lấy đã chọn", + "Group": "Nhóm", + "HasPendingChangesNoChanges": "Không thay đổi", + "HasPendingChangesSaveChanges": "Lưu thay đổi", + "History": "Lịch sử", + "Host": "Tổ chức", + "HostHelpText": "Cùng một máy chủ lưu trữ mà bạn đã chỉ định cho Máy khách tải xuống từ xa", + "Hostname": "Tên máy chủ", + "ICalFeed": "iCal Feed", + "ICalHttpUrlHelpText": "Sao chép URL này vào (các) khách hàng của bạn hoặc nhấp để đăng ký nếu trình duyệt của bạn hỗ trợ webcal", + "ICalLink": "Liên kết iCal", + "IconForCutoffUnmet": "Biểu tượng cho Cutoff Unmet", + "IconTooltip": "Lên kế hoạch", + "IgnoredAddresses": "Địa chỉ bị Bỏ qua", + "IgnoredHelpText": "Bản phát hành sẽ bị từ chối nếu nó chứa một hoặc nhiều điều khoản (không phân biệt chữ hoa chữ thường)", + "IgnoredPlaceHolder": "Thêm hạn chế mới", + "IllRestartLater": "Tôi sẽ khởi động lại sau", + "ImportedTo": "Đã nhập vào", + "ImportExtraFiles": "Nhập tệp bổ sung", + "ImportExtraFilesHelpText": "Nhập các tệp bổ sung phù hợp (phụ đề, nfo, v.v.) sau khi nhập tệp phim", + "ImportFailedInterp": "Nhập không thành công: {0}", + "Importing": "Nhập khẩu", + "IncludeHealthWarningsHelpText": "Bao gồm các cảnh báo về sức khỏe", + "IncludeUnknownArtistItemsHelpText": "Hiển thị các mục không có phim trong hàng đợi. Điều này có thể bao gồm các phim đã bị xóa hoặc bất kỳ thứ gì khác trong danh mục của Lidarr", + "IncludeUnmonitored": "Bao gồm Không theo dõi", + "Indexer": "Người lập chỉ mục", + "IndexerPriority": "Mức độ ưu tiên của người lập chỉ mục", + "Indexers": "Người lập chỉ mục", + "IndexerSettings": "Cài đặt trình lập chỉ mục", + "InteractiveSearch": "Tìm kiếm tương tác", + "Interval": "Khoảng thời gian", + "IsCutoffCutoff": "Cắt", + "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Nâng cấp cho đến khi đáp ứng hoặc vượt quá chất lượng này", + "IsTagUsedCannotBeDeletedWhileInUse": "Không thể bị xóa khi đang sử dụng", + "LaunchBrowserHelpText": " Mở trình duyệt web và điều hướng đến trang chủ Lidarr khi khởi động ứng dụng.", + "Level": "Cấp độ", + "LidarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Lidarr hỗ trợ bất kỳ trình chỉ mục nào sử dụng tiêu chuẩn Newznab, cũng như các trình chỉ mục khác được liệt kê bên dưới.", + "LidarrTags": "Thẻ Lidarr", + "LoadingTrackFilesFailed": "Tải tệp phim không thành công", + "Local": "Địa phương", + "LocalPath": "Đường dẫn địa phương", + "LocalPathHelpText": "Đường dẫn mà Lidarr nên sử dụng để truy cập cục bộ đường dẫn từ xa", + "LogFiles": "Tệp nhật ký", + "Logging": "Ghi nhật ký", + "LogLevel": "Mức đăng nhập", + "LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "Ghi nhật ký theo dõi chỉ nên được bật tạm thời", + "Logs": "Nhật ký", + "LongDateFormat": "Định dạng ngày dài", + "ManualImport": "Nhập thủ công", + "MarkAsFailed": "Đánh dấu là Không thành công", + "MarkAsFailedMessageText": "Bạn có chắc chắn muốn đánh dấu '{0}' là không thành công không?", + "MaximumLimits": "Giới hạn tối đa", + "PackageVersion": "Phiên bản gói", + "PageSize": "Kích thước trang", + "Scheduled": "Lên kế hoạch", + "Score": "Ghi bàn", + "ScriptPath": "Đường dẫn tập lệnh", + "Search": "Tìm kiếm", + "SendAnonymousUsageData": "Gửi dữ liệu sử dụng ẩn danh", + "SetPermissions": "Đặt quyền", + "SetPermissionsLinuxHelpText": "Có nên chạy chmod khi tệp được nhập / đổi tên không?", + "UnableToLoadLists": "Không thể tải danh sách", + "UnableToLoadMediaManagementSettings": "Không thể tải cài đặt Quản lý phương tiện", + "UnableToLoadMetadata": "Không thể tải Siêu dữ liệu", + "UnableToLoadMetadataProfiles": "Không thể tải hồ sơ độ trễ", + "Ungroup": "Bỏ nhóm", + "UILanguageHelpText": "Ngôn ngữ mà Lidarr sẽ sử dụng cho giao diện người dùng", + "UILanguageHelpTextWarning": "Yêu cầu tải lại trình duyệt", + "UnableToAddANewDownloadClientPleaseTryAgain": "Không thể thêm ứng dụng khách tải xuống mới, vui lòng thử lại.", + "UnableToAddANewIndexerPleaseTryAgain": "Không thể thêm trình chỉ mục mới, vui lòng thử lại.", + "UnableToAddANewListPleaseTryAgain": "Không thể thêm danh sách mới, vui lòng thử lại.", + "UnableToAddANewMetadataProfilePleaseTryAgain": "Không thể thêm hồ sơ chất lượng mới, vui lòng thử lại.", + "Usenet": "Usenet", + "UrlBaseHelpText": "Đối với hỗ trợ proxy ngược, mặc định là trống", + "Username": "tên tài khoản", + "UsingExternalUpdateMechanismBranchToUseToUpdateLidarr": "Nhánh sử dụng để cập nhật Lidarr", + "UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Nhánh được sử dụng bởi cơ chế cập nhật bên ngoài", + "Version": "Phiên bản", + "WeekColumnHeader": "Tiêu đề cột tuần", + "Year": "Năm", + "20MinutesTwenty": "60 phút: {0}", + "AnalyticsEnabledHelpText": "Gửi thông tin sử dụng và lỗi ẩn danh đến máy chủ của Lidarr. Điều này bao gồm thông tin về trình duyệt của bạn, trang WebUI của Lidarr mà bạn sử dụng, báo cáo lỗi cũng như hệ điều hành và phiên bản thời gian chạy. Chúng tôi sẽ sử dụng thông tin này để ưu tiên các tính năng và sửa lỗi.", + "AnalyticsEnabledHelpTextWarning": "Yêu cầu khởi động lại để có hiệu lực", + "Automatic": "Tự động", + "DelayingDownloadUntilInterp": "Trì hoãn tải xuống cho đến {0} lúc {1}", + "Actions": "Hành động", + "ApiKeyHelpTextWarning": "Yêu cầu khởi động lại để có hiệu lực", + "AppDataDirectory": "Thư mục AppData", + "Authentication": "Xác thực", + "AuthenticationMethodHelpText": "Yêu cầu Tên người dùng và Mật khẩu để truy cập Lidarr", + "Dates": "ngày", + "BindAddressHelpText": "Địa chỉ IP4 hợp lệ hoặc '*' cho tất cả các giao diện", + "CertificateValidationHelpText": "Thay đổi cách xác thực chứng chỉ HTTPS nghiêm ngặt", + "MonoVersion": "Phiên bản Mono" } diff --git a/src/NzbDrone.Core/Localization/Core/zh_CN.json b/src/NzbDrone.Core/Localization/Core/zh_CN.json index 71985c1c7..bf5af7c66 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_CN.json +++ b/src/NzbDrone.Core/Localization/Core/zh_CN.json @@ -49,7 +49,7 @@ "BypassProxyForLocalAddresses": "对局域网地址不使用代理", "Cancel": "取消", "CertificateValidation": "验证证书", - "CertificateValidationHelpText": "改变HTTPS证书验证的严格程度", + "CertificateValidationHelpText": "改变HTTPS证书验证的严格程度。不要更改除非您了解风险。", "ChangeHasNotBeenSavedYet": "修改暂未保存", "Clear": "清除", "ClientPriority": "客户端优先级", @@ -327,7 +327,7 @@ "ShowUnknownArtistItems": "显示未知影片条目", "Size": " 文件大小", "SkipFreeSpaceCheck": "跳过剩余空间检查", - "SkipFreeSpaceCheckWhenImportingHelpText": "当Radarr无法检测您的影片根目录时使用", + "SkipFreeSpaceCheckWhenImportingHelpText": "当 Lidarr 无法从您的艺术家根文件夹中检测到空闲空间时使用", "SorryThatAlbumCannotBeFound": "对不起,未找到影片。", "SorryThatArtistCannotBeFound": "对不起,未找到影片。", "SourcePath": "来源路径", @@ -376,7 +376,7 @@ "UpdateAll": "全部更新", "PublishedDate": "发布日期", "Quality": "影片质量", - "QualityProfile": "影片质量配置", + "QualityProfile": "质量配置", "RescanArtistFolderAfterRefresh": "刷新后重新扫描影片文件夹", "ResetAPIKeyMessageText": "你确认希望重置API密钥吗?", "SearchForMissing": "搜索缺少", @@ -492,7 +492,7 @@ "UILanguageHelpText": "Radarr使用的UI界面语言", "CutoffUnmet": "终止未满足", "AgeWhenGrabbed": "发布时长", - "AnyReleaseOkHelpText": "Readarr 将会自动切换到与已下载文件最匹配的版本", + "AnyReleaseOkHelpText": "Lidarr 将会自动切换到与已下载文件最匹配的版本", "DelayingDownloadUntilInterp": "延时下载直到 {1} 在 {0} 之前 Delaying download until {0} at {1}", "SetPermissions": "设定权限", "ChmodFolderHelpText": "八进制,当导入和重命名媒体文件夹和文件时应用", @@ -511,5 +511,9 @@ "MarkAsFailedMessageText": "您确定要标记'{0}'为已失败?", "MaximumSize": "最大文件体积", "MaximumSizeHelpText": "抓取影片最大多少MB,设置为0则不限制", - "UnableToLoadBlocklist": "无法加载黑名单" + "UnableToLoadBlocklist": "无法加载黑名单", + "MetadataProfile": "元数据配置", + "MetadataProfiles": "元数据配置", + "Term": "项", + "MonoVersion": "Mono版本" } diff --git a/src/NzbDrone.Core/Localization/Core/zh_TW.json b/src/NzbDrone.Core/Localization/Core/zh_TW.json index 3c0a93077..7575c4004 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_TW.json +++ b/src/NzbDrone.Core/Localization/Core/zh_TW.json @@ -1,3 +1,6 @@ { - "About": "關於" + "About": "關於", + "Actions": "執行", + "AddingTag": "新增標籤", + "Analytics": "分析" } From d8407e74e785a26e3b13615d173e5abd32a5721e Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 4 Nov 2022 05:23:06 +0000 Subject: [PATCH 0058/1478] Translated using Weblate (Ukrainian) [skip ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 13.6% (120 of 878 strings) Translated using Weblate (Chinese (Traditional) (zh_TW)) [skip ci] Currently translated at 1.3% (12 of 878 strings) Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci] Currently translated at 86.9% (763 of 878 strings) Translated using Weblate (Vietnamese) [skip ci] Currently translated at 66.2% (582 of 878 strings) Translated using Weblate (Turkish) [skip ci] Currently translated at 66.1% (581 of 878 strings) Translated using Weblate (Thai) [skip ci] Currently translated at 66.2% (582 of 878 strings) Translated using Weblate (Swedish) [skip ci] Currently translated at 86.2% (757 of 878 strings) Translated using Weblate (Slovak) [skip ci] Currently translated at 18.5% (163 of 878 strings) Translated using Weblate (Russian) [skip ci] Currently translated at 69.1% (607 of 878 strings) Translated using Weblate (Romanian) [skip ci] Currently translated at 66.5% (584 of 878 strings) Translated using Weblate (Portuguese (Brazil)) [skip ci] Currently translated at 94.5% (830 of 878 strings) Translated using Weblate (Portuguese) [skip ci] Currently translated at 76.9% (676 of 878 strings) Translated using Weblate (Polish) [skip ci] Currently translated at 68.2% (599 of 878 strings) Translated using Weblate (Dutch) [skip ci] Currently translated at 67.8% (596 of 878 strings) Translated using Weblate (Norwegian Bokmål) [skip ci] Currently translated at 18.6% (164 of 878 strings) Translated using Weblate (Korean) [skip ci] Currently translated at 65.8% (578 of 878 strings) Translated using Weblate (Japanese) [skip ci] Currently translated at 65.8% (578 of 878 strings) Translated using Weblate (Italian) [skip ci] Currently translated at 71.9% (632 of 878 strings) Translated using Weblate (Icelandic) [skip ci] Currently translated at 65.9% (579 of 878 strings) Translated using Weblate (Hungarian) [skip ci] Currently translated at 94.5% (830 of 878 strings) Translated using Weblate (Hindi) [skip ci] Currently translated at 65.8% (578 of 878 strings) Translated using Weblate (Hebrew) [skip ci] Currently translated at 66.9% (588 of 878 strings) Translated using Weblate (French) [skip ci] Currently translated at 74.2% (652 of 878 strings) Translated using Weblate (Finnish) [skip ci] Currently translated at 76.3% (670 of 878 strings) Translated using Weblate (Spanish) [skip ci] Currently translated at 69.5% (611 of 878 strings) Translated using Weblate (Greek) [skip ci] Currently translated at 65.9% (579 of 878 strings) Translated using Weblate (German) [skip ci] Currently translated at 93.0% (817 of 878 strings) Translated using Weblate (Danish) [skip ci] Currently translated at 65.9% (579 of 878 strings) Translated using Weblate (Czech) [skip ci] Currently translated at 66.0% (580 of 878 strings) Translated using Weblate (Catalan) [skip ci] Currently translated at 69.3% (609 of 878 strings) Translated using Weblate (Bulgarian) [skip ci] Currently translated at 65.9% (579 of 878 strings) Translated using Weblate (Arabic) [skip ci] Currently translated at 66.5% (584 of 878 strings) Translated using Weblate (Japanese) [skip ci] Currently translated at 65.7% (577 of 878 strings) Translated using Weblate (Italian) [skip ci] Currently translated at 71.7% (630 of 878 strings) Translated using Weblate (Icelandic) [skip ci] Currently translated at 65.8% (578 of 878 strings) Translated using Weblate (Hungarian) [skip ci] Currently translated at 94.3% (828 of 878 strings) Translated using Weblate (Hindi) [skip ci] Currently translated at 65.7% (577 of 878 strings) Translated using Weblate (Hebrew) [skip ci] Currently translated at 66.8% (587 of 878 strings) Translated using Weblate (French) [skip ci] Currently translated at 73.9% (649 of 878 strings) Translated using Weblate (Spanish) [skip ci] Currently translated at 69.2% (608 of 878 strings) Translated using Weblate (Greek) [skip ci] Currently translated at 65.7% (577 of 878 strings) Translated using Weblate (German) [skip ci] Currently translated at 92.8% (815 of 878 strings) Translated using Weblate (Danish) [skip ci] Currently translated at 65.7% (577 of 878 strings) Translated using Weblate (Czech) [skip ci] Currently translated at 65.8% (578 of 878 strings) Translated using Weblate (Catalan) [skip ci] Currently translated at 68.9% (605 of 878 strings) Translated using Weblate (Bulgarian) [skip ci] Currently translated at 65.7% (577 of 878 strings) Translated using Weblate (Arabic) [skip ci] Currently translated at 66.2% (582 of 878 strings) Update translation files Updated by "Remove blank strings" hook in Weblate. Update translation files Updated by "Remove blank strings" hook in Weblate. Update translation files Updated by "Remove blank strings" hook in Weblate. Update translation files Updated by "Remove blank strings" hook in Weblate. Update translation files Updated by "Remove blank strings" hook in Weblate. Update translation files Updated by "Remove blank strings" hook in Weblate. Update translation files Updated by "Remove blank strings" hook in Weblate. Update translation files Updated by "Remove blank strings" hook in Weblate. Update translation files Updated by "Remove blank strings" hook in Weblate. Update translation files Updated by "Remove blank strings" hook in Weblate. Translated using Weblate (Latvian) [skip ci] Currently translated at 0.0% (0 of 878 strings) Translated using Weblate (Lithuanian) [skip ci] Currently translated at 0.0% (0 of 878 strings) Translated using Weblate (Ukrainian) [skip ci] Currently translated at 10.0% (88 of 878 strings) Translated using Weblate (Persian) [skip ci] Currently translated at 0.0% (0 of 878 strings) Translated using Weblate (Bengali) [skip ci] Currently translated at 0.0% (0 of 878 strings) Translated using Weblate (Chinese (Traditional) (zh_TW)) [skip ci] Currently translated at 0.4% (4 of 878 strings) Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci] Currently translated at 58.6% (515 of 878 strings) Translated using Weblate (Vietnamese) [skip ci] Currently translated at 53.9% (474 of 878 strings) Translated using Weblate (Turkish) [skip ci] Currently translated at 53.7% (472 of 878 strings) Translated using Weblate (Thai) [skip ci] Currently translated at 53.9% (474 of 878 strings) Translated using Weblate (Swedish) [skip ci] Currently translated at 73.1% (642 of 878 strings) Translated using Weblate (Slovak) [skip ci] Currently translated at 5.2% (46 of 878 strings) Translated using Weblate (Russian) [skip ci] Currently translated at 55.6% (489 of 878 strings) Translated using Weblate (Romanian) [skip ci] Currently translated at 54.1% (475 of 878 strings) Translated using Weblate (Portuguese (Brazil)) [skip ci] Currently translated at 79.3% (697 of 878 strings) Translated using Weblate (Portuguese (Brazil)) [skip ci] Currently translated at 79.3% (697 of 878 strings) Translated using Weblate (Portuguese) [skip ci] Currently translated at 62.0% (545 of 878 strings) Translated using Weblate (Polish) [skip ci] Currently translated at 54.5% (479 of 878 strings) Translated using Weblate (Dutch) [skip ci] Currently translated at 54.7% (481 of 878 strings) Translated using Weblate (Norwegian Bokmål) [skip ci] Currently translated at 13.6% (120 of 878 strings) Translated using Weblate (Korean) [skip ci] Currently translated at 53.4% (469 of 878 strings) Translated using Weblate (Japanese) [skip ci] Currently translated at 53.5% (470 of 878 strings) Translated using Weblate (Italian) [skip ci] Currently translated at 58.7% (516 of 878 strings) Translated using Weblate (Icelandic) [skip ci] Currently translated at 53.6% (471 of 878 strings) Translated using Weblate (Hungarian) [skip ci] Currently translated at 79.3% (697 of 878 strings) Translated using Weblate (Hungarian) [skip ci] Currently translated at 79.3% (697 of 878 strings) Translated using Weblate (Hindi) [skip ci] Currently translated at 53.5% (470 of 878 strings) Translated using Weblate (Hebrew) [skip ci] Currently translated at 54.1% (475 of 878 strings) Translated using Weblate (French) [skip ci] Currently translated at 59.9% (526 of 878 strings) Translated using Weblate (Finnish) [skip ci] Currently translated at 61.9% (544 of 878 strings) Translated using Weblate (Spanish) [skip ci] Currently translated at 55.9% (491 of 878 strings) Translated using Weblate (Greek) [skip ci] Currently translated at 53.5% (470 of 878 strings) Translated using Weblate (German) [skip ci] Currently translated at 78.3% (688 of 878 strings) Translated using Weblate (German) [skip ci] Currently translated at 78.3% (688 of 878 strings) Translated using Weblate (Danish) [skip ci] Currently translated at 53.5% (470 of 878 strings) Translated using Weblate (Czech) [skip ci] Currently translated at 53.6% (471 of 878 strings) Translated using Weblate (Catalan) [skip ci] Currently translated at 55.0% (483 of 878 strings) Translated using Weblate (Bulgarian) [skip ci] Currently translated at 53.5% (470 of 878 strings) Translated using Weblate (Arabic) [skip ci] Currently translated at 54.1% (475 of 878 strings) Co-authored-by: Anonymous Co-authored-by: Csaba Co-authored-by: Havok Dan Co-authored-by: Weblate Co-authored-by: reloxx Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ar/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/bg/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/bn/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ca/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/cs/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/da/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/de/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/el/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/es/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fa/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fi/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fr/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/he/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hi/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hu/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/is/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/it/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ja/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ko/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/lt/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/lv/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/nb_NO/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/nl/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pl/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ro/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ru/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/sk/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/sv/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/th/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/tr/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/uk/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/vi/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_CN/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_TW/ Translation: Servarr/Lidarr --- src/NzbDrone.Core/Localization/Core/ar.json | 111 +++++++- src/NzbDrone.Core/Localization/Core/bg.json | 111 +++++++- src/NzbDrone.Core/Localization/Core/ca.json | 128 ++++++++- src/NzbDrone.Core/Localization/Core/cs.json | 111 +++++++- src/NzbDrone.Core/Localization/Core/da.json | 111 +++++++- src/NzbDrone.Core/Localization/Core/de.json | 133 +++++++++- src/NzbDrone.Core/Localization/Core/el.json | 111 +++++++- src/NzbDrone.Core/Localization/Core/es.json | 122 ++++++++- src/NzbDrone.Core/Localization/Core/fi.json | 129 ++++++++- src/NzbDrone.Core/Localization/Core/fr.json | 128 ++++++++- src/NzbDrone.Core/Localization/Core/he.json | 115 +++++++- src/NzbDrone.Core/Localization/Core/hi.json | 110 +++++++- src/NzbDrone.Core/Localization/Core/hu.json | 137 +++++++++- src/NzbDrone.Core/Localization/Core/is.json | 110 +++++++- src/NzbDrone.Core/Localization/Core/it.json | 118 ++++++++- src/NzbDrone.Core/Localization/Core/ja.json | 110 +++++++- src/NzbDrone.Core/Localization/Core/ko.json | 111 +++++++- .../Localization/Core/nb_NO.json | 46 +++- src/NzbDrone.Core/Localization/Core/nl.json | 117 +++++++- src/NzbDrone.Core/Localization/Core/pl.json | 122 ++++++++- src/NzbDrone.Core/Localization/Core/pt.json | 133 +++++++++- .../Localization/Core/pt_BR.json | 137 +++++++++- src/NzbDrone.Core/Localization/Core/ro.json | 111 +++++++- src/NzbDrone.Core/Localization/Core/ru.json | 120 ++++++++- src/NzbDrone.Core/Localization/Core/sk.json | 119 ++++++++- src/NzbDrone.Core/Localization/Core/sv.json | 117 +++++++- src/NzbDrone.Core/Localization/Core/th.json | 110 +++++++- src/NzbDrone.Core/Localization/Core/tr.json | 111 +++++++- src/NzbDrone.Core/Localization/Core/uk.json | 34 ++- src/NzbDrone.Core/Localization/Core/vi.json | 110 +++++++- .../Localization/Core/zh_CN.json | 250 +++++++++++++++++- .../Localization/Core/zh_TW.json | 10 +- 32 files changed, 3614 insertions(+), 39 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/ar.json b/src/NzbDrone.Core/Localization/Core/ar.json index 9b52c57c6..d8cd20c20 100644 --- a/src/NzbDrone.Core/Localization/Core/ar.json +++ b/src/NzbDrone.Core/Localization/Core/ar.json @@ -474,5 +474,114 @@ "UnableToLoadTags": "تعذر تحميل العلامات", "MonoVersion": "نسخة أحادية", "Docker": "عامل ميناء", - "NETCore": ".شبكة" + "NETCore": ".شبكة", + "Added": "مضاف", + "AddIndexer": "أضف مفهرس", + "AddList": "اضف قائمة", + "AddNew": "اضف جديد", + "AddQualityProfile": "أضف ملف تعريف الجودة", + "AddRemotePathMapping": "إضافة تعيين المسار البعيد", + "AddRootFolder": "إضافة مجلد جذر", + "AfterManualRefresh": "بعد التحديث اليدوي", + "Age": "عمر", + "All": "الكل", + "AllFiles": "كل الملفات", + "AllResultsFiltered": "يتم إخفاء جميع النتائج بواسطة عامل التصفية المطبق", + "Apply": "تطبيق", + "AudioInfo": "معلومات الصوت", + "Backup": "دعم", + "BeforeUpdate": "قبل التحديث", + "Close": "قريب", + "Connect": "الاتصال", + "CustomFilters": "مرشحات مخصصة", + "Date": "تاريخ", + "DefaultDelayProfileHelpText": "هذا هو ملف التعريف الافتراضي. ينطبق هذا على جميع الأفلام التي ليس لها ملف تعريف صريح.", + "Deleted": "تم الحذف", + "Details": "تفاصيل", + "Donations": "التبرعات", + "DoNotPrefer": "لا تفضل", + "DoNotUpgradeAutomatically": "لا تقم بالترقية تلقائيًا", + "DownloadFailed": "التحميل فشل", + "EditDelayProfile": "تحرير ملف تعريف التأخير", + "EditImportListExclusion": "حذف استبعاد قائمة الاستيراد", + "EditIndexer": "تحرير المفهرس", + "EditQualityProfile": "تحرير ملف تعريف الجودة", + "EditRemotePathMapping": "تحرير تعيين المسار البعيد", + "EditRootFolder": "إضافة مجلد جذر", + "Error": "خطأ", + "ErrorRestoringBackup": "خطأ في استعادة النسخة الاحتياطية", + "Events": "الأحداث", + "EventType": "نوع الحدث", + "Filters": "منقي", + "FreeSpace": "مساحة فارغة", + "General": "جنرال لواء", + "Genres": "الأنواع", + "Grabbed": "اقتطف", + "HardlinkCopyFiles": "Hardlink / نسخ الملفات", + "HideAdvanced": "إخفاء الإعدادات المتقدمة", + "Ignored": "تم التجاهل", + "Import": "استيراد", + "Info": "معلومات", + "InteractiveImport": "الاستيراد التفاعلي", + "LastDuration": "المدة الماضية", + "LastExecution": "آخر تنفيذ", + "LastUsed": "آخر أستخدام", + "LastWriteTime": "وقت الكتابة الأخير", + "Location": "موقعك", + "MediaManagement": "إدارة وسائل الإعلام", + "Metadata": "البيانات الوصفية", + "MonitoredOnly": "مراقب فقط", + "NoTagsHaveBeenAddedYet": "لم يتم إضافة أي علامات حتى الان", + "OnlyTorrent": "فقط تورنت", + "OnlyUsenet": "يوزنت فقط", + "Organize": "تنظم", + "OutputPath": "مسار الاخراج", + "Peers": "الأقران", + "PreferAndUpgrade": "يفضل والترقية", + "Progress": "التقدم", + "QualityLimitsHelpText": "يتم ضبط الحدود تلقائيًا لوقت تشغيل الفيلم.", + "Queued": "في قائمة الانتظار", + "Rating": "التقييمات", + "RejectionCount": "عدد الرفض", + "ReleaseTitle": "عنوان الإصدار", + "Replace": "يحل محل", + "RestartRequiredHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول", + "RestoreBackupAdditionalInfo": "ملاحظة: سيتم إعادة تشغيل Radarr تلقائيًا وإعادة تحميل واجهة المستخدم أثناء عملية الاستعادة.", + "Seeders": "بزار", + "Select...": "'تحديد...", + "SelectFolder": "اختر مجلد", + "SelectQuality": "حدد الجودة", + "ShowAdvanced": "عرض متقدمة", + "SizeOnDisk": "الحجم على القرص", + "SomeResultsFiltered": "بعض النتائج مخفية بواسطة عامل التصفية المطبق", + "SourceTitle": "عنوان المصدر", + "Test": "اختبار", + "TimeLeft": "الوقت المتبقي", + "TotalSpace": "المساحة الكلية", + "UI": "واجهة المستخدم", + "UnableToLoadInteractiveSerach": "تعذر تحميل نتائج بحث هذا الفيلم. حاول مرة أخرى في وقت لاحق", + "UnmappedFilesOnly": "الملفات غير المعينة فقط", + "WouldYouLikeToRestoreBackup": "هل ترغب في استعادة النسخة الاحتياطية {0}؟", + "Activity": "نشاط", + "Add": "إضافة", + "AddDelayProfile": "إضافة ملف تعريف تأخير", + "Always": "دائما", + "Custom": "مخصص", + "Presets": "الإعدادات المسبقة", + "Renamed": "تمت إعادة تسميته", + "UnmonitoredOnly": "مراقب فقط", + "UpgradesAllowed": "الترقيات المسموح بها", + "Wanted": "مطلوب", + "Warn": "حذر", + "Manual": "كتيب", + "Save": "حفظ", + "System": "النظام", + "Title": "عنوان", + "MoveAutomatically": "التحرك تلقائيا", + "MoveFiles": "نقل الملفات", + "NextExecution": "التنفيذ القادم", + "NoResults": "لم يتم العثور على نتائج", + "Ok": "موافق", + "ImportListExclusions": "حذف استبعاد قائمة الاستيراد", + "AddImportListExclusion": "حذف استبعاد قائمة الاستيراد" } diff --git a/src/NzbDrone.Core/Localization/Core/bg.json b/src/NzbDrone.Core/Localization/Core/bg.json index 2330f4e80..6a09e27e7 100644 --- a/src/NzbDrone.Core/Localization/Core/bg.json +++ b/src/NzbDrone.Core/Localization/Core/bg.json @@ -472,5 +472,114 @@ "OnHealthIssue": "По здравен въпрос", "OnUpgrade": "При надстройка", "Usenet": "Usenet", - "MonoVersion": "Моно версия" + "MonoVersion": "Моно версия", + "Activity": "Дейност", + "Add": "Добавяне", + "AddDelayProfile": "Добавете профил за забавяне", + "Added": "Добавено", + "AddIndexer": "Добавете Indexer", + "AddList": "Добавяне на списък", + "AddNew": "Добави нов", + "AddQualityProfile": "Добавете качествен профил", + "AddRemotePathMapping": "Добавете отдалечено картографиране на пътя", + "AddRootFolder": "Добавяне на коренна папка", + "AfterManualRefresh": "След ръчно опресняване", + "Age": "Възраст", + "All": "всичко", + "AllFiles": "Всички файлове", + "AllResultsFiltered": "Всички резултати са скрити от приложения филтър", + "Always": "Винаги", + "Apply": "Приложи", + "AudioInfo": "Аудио информация", + "Backup": "Архивиране", + "Close": "Близо", + "Date": "Дата", + "DefaultDelayProfileHelpText": "Това е профилът по подразбиране. Прилага се за всички филми, които нямат изричен профил.", + "Deleted": "Изтрито", + "Details": "Подробности", + "Donations": "Дарения", + "DoNotPrefer": "Не предпочитайте", + "DoNotUpgradeAutomatically": "Не надграждайте автоматично", + "DownloadFailed": "Изтеглянето се провали", + "EditDelayProfile": "Редактиране на профил за забавяне", + "EditImportListExclusion": "Изтриване на изключването на списъка за импортиране", + "EditIndexer": "Редактиране на Indexer", + "EditQualityProfile": "Редактирайте качествения профил", + "EditRemotePathMapping": "Редактиране на отдалечено картографиране на пътя", + "EditRootFolder": "Добавяне на коренна папка", + "Error": "Грешка", + "ErrorRestoringBackup": "Грешка при възстановяване на архивиране", + "Events": "Събития", + "EventType": "Тип на събитието", + "FreeSpace": "Свободно пространство", + "HardlinkCopyFiles": "Твърда връзка / копиране на файлове", + "HideAdvanced": "Скрий Разширено", + "Import": "Внос", + "Location": "Местоположение", + "Manual": "Ръчно", + "Metadata": "Метаданни", + "MonitoredOnly": "Само наблюдавано", + "MoveAutomatically": "Бърз импорт", + "MoveFiles": "Преместване на файлове", + "NextExecution": "Следващо изпълнение", + "NoTagsHaveBeenAddedYet": "Все още не са добавени етикети", + "Ok": "Добре", + "Organize": "Организирайте", + "OutputPath": "Изходен път", + "Peers": "Връстници", + "PreferAndUpgrade": "Предпочитайте и надграждайте", + "Presets": "Предварителни настройки", + "QualityLimitsHelpText": "Ограниченията се коригират автоматично за времето на изпълнение на филма.", + "RejectionCount": "Брой на отхвърлянията", + "ReleaseTitle": "Заглавие на изданието", + "Replace": "Сменете", + "RestartRequiredHelpTextWarning": "Изисква рестартиране, за да влезе в сила", + "RestoreBackupAdditionalInfo": "Забележка: Radarr автоматично ще рестартира и презареди потребителския интерфейс по време на процеса на възстановяване.", + "Save": "Запазете", + "Seeders": "Сеялки", + "Select...": "„Изберете ...", + "SelectFolder": "Изберете папка", + "SelectQuality": "Изберете Качество", + "ShowAdvanced": "Показване на напреднали", + "SizeOnDisk": "Размер на диска", + "SomeResultsFiltered": "Някои резултати са скрити от прилагания филтър", + "SourceTitle": "Заглавие на източника", + "Test": "Тест", + "TimeLeft": "Оставащо време", + "Title": "Заглавие", + "TotalSpace": "Общо пространство", + "UI": "Потребителски интерфейс", + "UnableToLoadInteractiveSerach": "Не могат да се заредят резултати за това търсене на филм. Опитайте отново по-късно", + "UnmappedFilesOnly": "Само немапирани файлове", + "Warn": "Предупреждавайте", + "WouldYouLikeToRestoreBackup": "Искате ли да възстановите архива {0}?", + "BeforeUpdate": "Преди актуализация", + "OnlyTorrent": "Само Торент", + "OnlyUsenet": "Само Usenet", + "Queued": "На опашка", + "Rating": "Оценки", + "Renamed": "Преименуван", + "System": "Система", + "Connect": "Свържете", + "Custom": "Персонализиран", + "CustomFilters": "Персонализирани филтри", + "Filters": "Филтър", + "General": "Общ", + "Genres": "Жанрове", + "Grabbed": "Грабната", + "Ignored": "Игнориран", + "InteractiveImport": "Интерактивен импорт", + "LastDuration": "lastDuration", + "LastExecution": "Последно изпълнение", + "MediaManagement": "Управление на медиите", + "NoResults": "Няма намерени резултати", + "Progress": "Напредък", + "UnmonitoredOnly": "Само наблюдавано", + "UpgradesAllowed": "Позволени надстройки", + "Wanted": "Издирва се", + "Info": "Информация", + "LastUsed": "Последно използван", + "LastWriteTime": "Време за последно писане", + "ImportListExclusions": "Изтриване на изключването на списъка за импортиране", + "AddImportListExclusion": "Изтриване на изключването на списъка за импортиране" } diff --git a/src/NzbDrone.Core/Localization/Core/ca.json b/src/NzbDrone.Core/Localization/Core/ca.json index 9b731d8b7..026c47c69 100644 --- a/src/NzbDrone.Core/Localization/Core/ca.json +++ b/src/NzbDrone.Core/Localization/Core/ca.json @@ -481,5 +481,131 @@ "UpgradeAllowedHelpText": "Si les qualitats estan desactivades no s'actualitzaran", "Year": "Any", "YesCancel": "Si, cancel·la", - "BindAddressHelpText": "Adreça IPv4 vàlida o '*' per a totes les interfícies" + "BindAddressHelpText": "Adreça IPv4 vàlida o '*' per a totes les interfícies", + "Activity": "Activitat", + "AddDelayProfile": "Afegeix un perfil de retard", + "Added": "Afegit", + "All": "Tots", + "Progress": "Progrés", + "SizeLimit": "Límit de mida", + "Backup": "Còpia de seguretat", + "IndexerTagHelpText": "Utilitzeu aquest indexador només per a pel·lícules amb almenys una etiqueta coincident. Deixeu-ho en blanc per utilitzar-ho amb totes les pel·lícules.", + "Info": "Informació", + "InstanceName": "Nom de la instància", + "InteractiveImport": "Importació interactiva", + "Seeders": "Llavors", + "AddIndexer": "Afegeix un indexador", + "AddList": "Afegeix una llista", + "AddMetadataProfile": "perfil de metadades", + "AddNew": "Afegir nou", + "AddQualityProfile": "Afegeix un perfil de qualitat", + "AllFiles": "Tots els fitxers", + "AllResultsFiltered": "Tots els resultats estan ocults pel filtre aplicat", + "Always": "Sempre", + "ApplicationURL": "URL de l'aplicació", + "ApplicationUrlHelpText": "URL extern d'aquesta aplicació, inclòs http(s)://, port i URL base", + "Apply": "Aplica", + "BeforeUpdate": "Abans de l'actualització", + "Close": "Tanca", + "Connect": "Connecta", + "Custom": "Personalitzat", + "CustomFilters": "Filtres personalitzats", + "Date": "Data", + "DefaultDelayProfileHelpText": "Aquest és el perfil predeterminat. S'aplica a totes les pel·lícules que no tenen un perfil explícit.", + "Deleted": "S'ha suprimit", + "Details": "Detalls", + "EditConnection": "Edita la col·lecció", + "Error": "error", + "ErrorRestoringBackup": "S'ha produït un error en restaurar la còpia de seguretat", + "Events": "Esdeveniments", + "EventType": "Tipus d'esdeveniment", + "FreeSpace": "Espai lliure", + "General": "General", + "Genres": "Gèneres", + "Grabbed": "Capturat", + "HardlinkCopyFiles": "Enllaç dur/Copia fitxers", + "HideAdvanced": "Amaga avançat", + "Ignored": "Ignorat", + "Import": "Importa", + "IndexerDownloadClientHelpText": "Especifiqueu quin client de baixada s'utilitza per capturar des d'aquest indexador", + "InstanceNameHelpText": "Nom de la instància a la pestanya i per al nom de l'aplicació Syslog", + "LastUsed": "Darrer ús", + "LastWriteTime": "La darrera hora d'escriptura", + "Library": "Biblioteca", + "Location": "Ubicació", + "MediaManagement": "Gestió de mitjans", + "Metadata": "Metadada", + "MonitoredOnly": "Només monitoritzat", + "MoveAutomatically": "Mou automàticament", + "MoveFiles": "Mou arxius", + "Never": "Mai", + "NextExecution": "Propera execució", + "NoResults": "Sense resultats", + "NoTagsHaveBeenAddedYet": "Encara no s'ha afegit cap etiqueta", + "Ok": "D'acord", + "Organize": "Organitza", + "OutputPath": "Camí de sortida", + "PreferAndUpgrade": "Marca preferit i actualitza", + "PreferredProtocol": "Protocol preferit", + "Presets": "Predefinits", + "PriorityHelpText": "Prioritat de l'indexador d'1 (la més alta) a 50 (la més baixa). Per defecte: 25. S'utilitza quan s'agafa llançaments com a desempat per a versions iguals, Radarr encara utilitzarà tots els indexadors habilitats per a la sincronització i la cerca RSS", + "QualityLimitsHelpText": "Els límits s'ajusten automàticament per al temps d'execució de la pel·lícula.", + "Queued": "En cua", + "Rating": "Valoració", + "RejectionCount": "Recompte de rebuigs", + "ReleaseTitle": "Títol del llançament", + "RestartRequiredHelpTextWarning": "Cal reiniciar perquè tingui efecte", + "RestoreBackupAdditionalInfo": "Nota: Radarr es reiniciarà i tornarà a carregar automàticament la interfície d'usuari durant el procés de restauració.", + "Save": "Desa", + "SelectFolder": "Seleccioneu Carpeta", + "SelectQuality": "Seleccioneu Qualitat", + "ShowAdvanced": "Mostra característiques avançades", + "SizeOnDisk": "Mida al disc", + "SomeResultsFiltered": "Alguns resultats estan ocults pel filtre aplicat", + "SourceTitle": "Títol de la font", + "Title": "Títol", + "TotalSpace": "Espai total", + "Tracks": "Traça", + "UI": "Interfície", + "UnableToLoadInteractiveSerach": "No es poden carregar els resultats d'aquesta cerca de pel·lícules. Torna-ho a provar més tard", + "UnmappedFilesOnly": "Només fitxers sense mapejar", + "UpgradesAllowed": "Actualitzacions permeses", + "Wanted": "Demanat", + "Warn": "Avís", + "WouldYouLikeToRestoreBackup": "Voleu restaurar la còpia de seguretat {0} ?", + "Add": "Afegiu", + "AddRemotePathMapping": "Afegeix un mapa de camins remots", + "AddRootFolder": "Afegeix una carpeta arrel", + "AfterManualRefresh": "Després de l'actualització manual", + "Age": "Edat", + "AudioInfo": "Informació d'àudio", + "Donations": "Donacions", + "DoNotPrefer": "No ho prefereixo", + "DoNotUpgradeAutomatically": "No actualitzeu automàticament", + "DownloadFailed": "La baixada ha fallat", + "EditIndexer": "Edita l'indexador", + "EditDelayProfile": "Edita el perfil de retard", + "EditImportListExclusion": "Suprimeix l'exclusió de la llista d'importació", + "EditQualityProfile": "Edita el perfil de qualitat", + "EditRemotePathMapping": "Editeu el mapa de camins remots", + "EditRootFolder": "Afegeix una carpeta arrel", + "Filters": "Filtres", + "LastDuration": "Darrera durada", + "LastExecution": "Darrere execució", + "Manual": "Manual", + "OnlyTorrent": "Només Torrent", + "OnlyUsenet": "Només Usenet", + "Peers": "Parells", + "Renamed": "Reanomenat", + "Replace": "Substitueix", + "Select...": "Seleccioneu...", + "System": "Sistema", + "Test": "Prova", + "TimeLeft": "Temps restant", + "UnmonitoredOnly": "Només monitoritzat", + "Started": "Començat", + "EditMetadataProfile": "perfil de metadades", + "AddConnection": "Edita la col·lecció", + "AddImportListExclusion": "Suprimeix l'exclusió de la llista d'importació", + "ImportListExclusions": "Suprimeix l'exclusió de la llista d'importació" } diff --git a/src/NzbDrone.Core/Localization/Core/cs.json b/src/NzbDrone.Core/Localization/Core/cs.json index 824be02f9..f774e83d0 100644 --- a/src/NzbDrone.Core/Localization/Core/cs.json +++ b/src/NzbDrone.Core/Localization/Core/cs.json @@ -473,5 +473,114 @@ "ThisCannotBeCancelled": "Toto nelze zrušit po spuštění bez restartování Whisparru.", "OnGrab": "Chyť", "NETCore": ".NET Core", - "MonoVersion": "Mono verze" + "MonoVersion": "Mono verze", + "Activity": "Aktivita", + "Add": "Přidat", + "AddDelayProfile": "Přidat profil zpoždění", + "Added": "Přidané", + "AddIndexer": "Přidat indexátor", + "AddList": "Přidat seznam", + "OutputPath": "Výstupní cesta", + "Renamed": "Přejmenováno", + "UnableToLoadInteractiveSerach": "Nelze načíst výsledky pro toto hledání filmu. Zkuste to později", + "UnmappedFilesOnly": "Pouze nezmapované soubory", + "UnmonitoredOnly": "Pouze monitorováno", + "UpgradesAllowed": "Upgrady povoleny", + "Wanted": "Chtěl", + "Warn": "Varovat", + "WouldYouLikeToRestoreBackup": "Chcete obnovit zálohu {0}?", + "AddNew": "Přidat nový", + "AddQualityProfile": "Přidejte profil kvality", + "AddRemotePathMapping": "Přidejte vzdálené mapování cesty", + "AddRootFolder": "Přidat kořenovou složku", + "AfterManualRefresh": "Po ručním obnovení", + "Age": "Stáří", + "All": "Všechno", + "AllFiles": "Všechny soubory", + "AllResultsFiltered": "Všechny výsledky jsou skryty použitým filtrem", + "Always": "Vždy", + "Backup": "Záloha", + "BeforeUpdate": "Před aktualizací", + "Close": "Zavřít", + "Connect": "Připojit", + "Custom": "Zvyk", + "CustomFilters": "Vlastní filtry", + "Date": "datum", + "DefaultDelayProfileHelpText": "Toto je výchozí profil. Platí pro všechny filmy, které nemají explicitní profil.", + "DoNotPrefer": "Nepřednostňovat", + "DoNotUpgradeAutomatically": "Neupgradovat automaticky", + "DownloadFailed": "Stažení se nezdařilo", + "EditDelayProfile": "Upravit profil zpoždění", + "EditImportListExclusion": "Odstranit vyloučení seznamu importů", + "EditIndexer": "Upravit indexátor", + "EditQualityProfile": "Upravit profil kvality", + "EditRemotePathMapping": "Upravit vzdálené mapování cesty", + "EditRootFolder": "Přidat kořenovou složku", + "Error": "Chyba", + "Events": "Události", + "EventType": "Typ události", + "General": "Všeobecné", + "Genres": "Žánry", + "Grabbed": "Popadl", + "HardlinkCopyFiles": "Pevný odkaz / kopírování souborů", + "HideAdvanced": "Skrýt pokročilé", + "Import": "Import", + "Info": "Info", + "InteractiveImport": "Interaktivní import", + "LastDuration": "lastDuration", + "LastExecution": "Poslední poprava", + "LastUsed": "Naposledy použitý", + "LastWriteTime": "Čas posledního zápisu", + "Metadata": "Metadata", + "MonitoredOnly": "Pouze monitorováno", + "MoveAutomatically": "Rychlý import", + "MoveFiles": "Přesouvat soubory", + "NextExecution": "Další spuštění", + "NoResults": "Nebyly nalezeny žádné výsledky", + "NoTagsHaveBeenAddedYet": "Zatím nebyly přidány žádné značky", + "Ok": "OK", + "OnlyTorrent": "Pouze Torrent", + "OnlyUsenet": "Pouze Usenet", + "QualityLimitsHelpText": "Limity se automaticky upraví podle doby běhu filmu.", + "Rating": "Hodnocení", + "RejectionCount": "Počet odmítnutí", + "ReleaseTitle": "Uvolněte název", + "Replace": "Nahradit", + "RestartRequiredHelpTextWarning": "Vyžaduje restart, aby se projevilo", + "RestoreBackupAdditionalInfo": "Poznámka: Radarr se během procesu obnovy automaticky restartuje a znovu načte uživatelské rozhraní.", + "Save": "Uložit", + "SelectFolder": "Vybrat složku", + "SelectQuality": "Vyberte kvalitu", + "ShowAdvanced": "Zobrazit pokročilé", + "SizeOnDisk": "Velikost na disku", + "SomeResultsFiltered": "Některé výsledky jsou použitým filtrem skryty", + "SourceTitle": "Název zdroje", + "Test": "Test", + "TimeLeft": "Zbývající čas", + "Title": "Titul", + "TotalSpace": "Celkový prostor", + "UI": "UI", + "Apply": "Aplikovat", + "AudioInfo": "Zvukové informace", + "Deleted": "Smazáno", + "Details": "Detaily", + "Donations": "Dary", + "ErrorRestoringBackup": "Chyba při obnovování zálohy", + "Filters": "Filtr", + "FreeSpace": "Volný prostor", + "Ignored": "Ignorováno", + "Presets": "Předvolby", + "Progress": "Pokrok", + "Queued": "Ve frontě", + "Seeders": "Secí stroje", + "Select...": "'Vybrat...", + "System": "Systém", + "Location": "Umístění", + "Manual": "Manuál", + "MediaManagement": "Správa médií", + "Organize": "Organizovat", + "Peers": "Vrstevníci", + "PreferAndUpgrade": "Preferovat a upgradovat", + "AddImportListExclusion": "Odstranit vyloučení seznamu importů", + "ImportListExclusions": "Odstranit vyloučení seznamu importů" } diff --git a/src/NzbDrone.Core/Localization/Core/da.json b/src/NzbDrone.Core/Localization/Core/da.json index bd73b1867..ebabb6376 100644 --- a/src/NzbDrone.Core/Localization/Core/da.json +++ b/src/NzbDrone.Core/Localization/Core/da.json @@ -472,5 +472,114 @@ "OnRename": "Om omdøb", "OnUpgrade": "Ved opgradering", "ThisCannotBeCancelled": "Dette kan ikke annulleres en gang startet uden genstart af Whisparr.", - "MonoVersion": "Mono-version" + "MonoVersion": "Mono-version", + "Activity": "Aktivitet", + "Add": "Tilføj", + "AddDelayProfile": "Tilføj forsinkelsesprofil", + "Added": "Tilføjet", + "AddIndexer": "Tilføj indexer", + "AddList": "Tilføj Liste", + "AddRemotePathMapping": "Tilføj kortlægning af fjernsti", + "AddRootFolder": "Tilføj rodmappe", + "AfterManualRefresh": "Efter manuel opdatering", + "Age": "Alder", + "All": "Alt", + "AllFiles": "Alle filer", + "AllResultsFiltered": "Alle resultater skjules af det anvendte filter", + "Apply": "Anvend", + "AudioInfo": "Lyd Info", + "Backup": "Backup", + "BeforeUpdate": "Før opdatering", + "Close": "Luk", + "Connect": "Tilslut", + "Date": "Dato", + "DefaultDelayProfileHelpText": "Dette er standardprofilen. Det gælder for alle film, der ikke har en eksplicit profil.", + "Deleted": "Slettet", + "Details": "Detaljer", + "DownloadFailed": "Download fejlede", + "EditDelayProfile": "Rediger forsinkelsesprofil", + "EditImportListExclusion": "Slet udelukkelse af importliste", + "EditIndexer": "Rediger indekser", + "EditQualityProfile": "Rediger kvalitetsprofil", + "EditRemotePathMapping": "Rediger kortlægning af fjernsti", + "EditRootFolder": "Tilføj rodmappe", + "FreeSpace": "Frit Plads", + "General": "Generelt", + "Genres": "Genrer", + "Grabbed": "Grebet", + "HardlinkCopyFiles": "Hardlink/Kopir Filer", + "HideAdvanced": "Gemt Avancerede", + "Ignored": "Ignoreret", + "Info": "Information", + "LastUsed": "Sidst brugt", + "LastWriteTime": "Sidste Skrive Tid", + "Location": "Beliggenhed", + "Manual": "brugervejledning", + "MediaManagement": "Mediestyring", + "Metadata": "Metadata", + "MonitoredOnly": "Kun overvåget", + "NextExecution": "Næste udførelse", + "NoResults": "Ingen resultater fundet", + "Ok": "Okay", + "Organize": "Organisere", + "OutputPath": "Outputsti", + "Peers": "Kammerater", + "PreferAndUpgrade": "Foretrækker og opgraderer", + "Renamed": "Omdøbt", + "RestartRequiredHelpTextWarning": "Kræver genstart for at træde i kraft", + "RestoreBackupAdditionalInfo": "Bemærk: Radarr genstarter automatisk og genindlæser brugergrænsefladen under gendannelsesprocessen.", + "Save": "Gemme", + "Seeders": "Såmaskiner", + "Select...": "'Vælg...", + "SelectFolder": "Vælg Mappe", + "ShowAdvanced": "Vis avanceret", + "SourceTitle": "Kildetitel", + "System": "System", + "Test": "Prøve", + "TimeLeft": "Tid tilbage", + "Title": "Titel", + "TotalSpace": "Samlet plads", + "UI": "UI", + "UnableToLoadInteractiveSerach": "Kunne ikke indlæse resultater for denne filmsøgning. Prøv igen senere", + "UnmappedFilesOnly": "Kun kortlagte filer", + "UnmonitoredOnly": "Kun overvåget", + "Warn": "Advare", + "MoveAutomatically": "Hurtig import", + "Replace": "Erstatte", + "SomeResultsFiltered": "Nogle resultater skjules af det anvendte filter", + "AddNew": "Tilføj Ny", + "AddQualityProfile": "Tilføj kvalitetsprofil", + "Always": "Altid", + "Custom": "Brugerdefinerede", + "CustomFilters": "Bruger Tilpassede Filtere", + "Donations": "Donationer", + "DoNotPrefer": "Foretrækker ikke", + "DoNotUpgradeAutomatically": "Opgrader ikke automatisk", + "Error": "Fejl", + "ErrorRestoringBackup": "Fejl ved gendannelse af sikkerhedskopi", + "Events": "Begivenheder", + "EventType": "Begivenhed Type", + "Filters": "Filter", + "Import": "Importer", + "NoTagsHaveBeenAddedYet": "Der er ikke tilføjet nogen tags endnu", + "OnlyTorrent": "Kun Torrent", + "OnlyUsenet": "Kun Usenet", + "Presets": "Forudindstillinger", + "Progress": "Fremskridt", + "Queued": "I kø", + "Rating": "Bedømmelser", + "SelectQuality": "Vælg Kvalitet", + "SizeOnDisk": "Størrelse på disk", + "UpgradesAllowed": "Opgraderinger tilladt", + "Wanted": "Ønskede", + "WouldYouLikeToRestoreBackup": "Vil du gendanne sikkerhedskopien {0}?", + "InteractiveImport": "Interaktiv import", + "LastDuration": "lastDuration", + "LastExecution": "Sidste henrettelse", + "QualityLimitsHelpText": "Grænser justeres automatisk for filmens kørselstid.", + "RejectionCount": "Afvisningstal", + "ReleaseTitle": "Udgiv titel", + "MoveFiles": "Flyt filer", + "AddImportListExclusion": "Slet udelukkelse af importliste", + "ImportListExclusions": "Slet udelukkelse af importliste" } diff --git a/src/NzbDrone.Core/Localization/Core/de.json b/src/NzbDrone.Core/Localization/Core/de.json index 6c43ba008..be4e22f68 100644 --- a/src/NzbDrone.Core/Localization/Core/de.json +++ b/src/NzbDrone.Core/Localization/Core/de.json @@ -633,7 +633,6 @@ "MusicBrainzArtistID": "MusicBranz Künstler Id", "MusicBrainzReleaseID": "MusicBrainz Veröffentlichung Id", "MusicBrainzTrackID": "MusicBrainz Titel Id", - "NoTagsHaveBeenAddedYetAddTagsToLinkArtistsWithDelayProfilesRestrictionsOrNotificationsClickLinkTohttpswikiservarrcomlidarrsettingstagshereLinkToFindOutMoreAboutTagsInLidarr": "Es wurden noch keine Tags hinzugefügt. Füge Tags hinzu, um Künstler mit Verzögerungsprofilen, Einschränkungen oder Benachrichtigungen zu verknüpfen. Klicke hier um mehr über Tags in Lidarr zu erfahren.", "OnDownloadFailure": "Bei fehlgeschlagenem Download", "OnImportFailureHelpText": "Bei fehlgeschlagenem Import", "OnReleaseImport": "Bei Veröffentlichungsimport", @@ -668,7 +667,6 @@ "ShowTitleHelpText": "Zeige Künstlername unter Poster", "SkipRedownload": "Überspringe erneuten Download", "SkipredownloadHelpText": "Verhindert, dass Lidarr versucht alternative Veröffentlichungen für die entfernten Objekte herunterzuladen", - "Sonarr": "Sonarr", "SpecificAlbum": "Bestimmtes Album", "StatusEndedContinuing": "Fortfahren", "TagsHelpText": "Veröffentlichungsprofile gelten für Künstler mit mindestens einem passenden Tag. Leer lassen, damit es für alle Künstler gilt", @@ -693,5 +691,134 @@ "ThisCannotBeCancelled": "Nach dem Start kann dies nicht mehr abgebrochen werden ohne alle Indexer zu deaktivieren.", "AddedArtistSettings": "Autor Einstellungen hinzugefügt", "ImportListSpecificSettings": "Listenspezifische Einstellungen importieren", - "MonoVersion": "Mono Version" + "MonoVersion": "Mono Version", + "Activity": "Aktivität", + "Add": "Hinzufügen", + "AddDelayProfile": "Verzögerungsprofil hinzufügen", + "Added": "Hinzugefügt", + "AddImportListExclusion": "Ausschlüsse der Importliste", + "AddIndexer": "Indexer hinzufügen", + "AddList": "Liste hinzufügen", + "AddMetadataProfile": "Metadaten Profil", + "AddNew": "Hinzufügen", + "AddQualityProfile": "Qualitätsprofil hinzufügen", + "AddRemotePathMapping": "Entfernte Pfadzuordnung hinzufügen", + "AddRootFolder": "Stammordner hinzufügen", + "AfterManualRefresh": "Nach manuellen aktualisieren", + "Albums": "Alben", + "All": "Alle", + "AllFiles": "Alle Dateien", + "AllMonitoringOptionHelpText": "Autoren und alle Bücher für jeden Autor werden auf der Import-Liste miteinbezogen", + "AllResultsFiltered": "Keine Ergebnisse mit den ausgewählten Filtern", + "Always": "Immer", + "ApplicationURL": "Anwendungs-URL", + "ApplicationUrlHelpText": "Die externe URL der Anwendung inklusive http(s)://, Port und URL-Basis", + "Apply": "Anwenden", + "AudioInfo": "Audio-Info", + "Backup": "Backups", + "BeforeUpdate": "Vor dem Update", + "Close": "Schließen", + "Connect": "Verbinden", + "CustomFilters": "Filter anpassen", + "Date": "Datum", + "DefaultDelayProfileHelpText": "Dies ist das Standart Profil. Es wird auf alle Filme angewendet die kein expliziertes Profil haben.", + "Deleted": "Gelöscht", + "Details": "Details", + "Donations": "Spenden", + "DoNotPrefer": "Nicht bevorzugen", + "DoNotUpgradeAutomatically": "Nicht automatisch upgraden", + "DownloadFailed": "Download fehlgeschlagen", + "EditConnection": "Sammlung bearbeiten", + "EditDelayProfile": "Verzögerungsprofil bearbeiten", + "EditImportListExclusion": "Importlisten Ausschluss löschen", + "EditIndexer": "Indexer bearbeiten", + "EditList": "Liste bearbeiten", + "EditQualityProfile": "Qualitätsprofil bearbeiten", + "EditRemotePathMapping": "Entfernte Pfadzuordnung bearbeiten", + "EditRootFolder": "Stammordner hinzufügen", + "ErrorRestoringBackup": "Fehler beim Wiederherstellen", + "Events": "Events", + "EventType": "Event Typ", + "Filters": "Filter", + "FreeSpace": "Freier Speicher", + "General": "Allgemein", + "Genres": "Genres", + "Grabbed": "Erfasste", + "HardlinkCopyFiles": "Hardlink/Dateien kopieren", + "HideAdvanced": "Erweiterte Ansicht", + "Ignored": "Ignoriert", + "Import": "Importieren", + "IndexerDownloadClientHelpText": "Wähle aus, welcher Download-Client für diesen Indexer verwendet wird", + "IndexerTagHelpText": "Benutze den Indexer nur für Filme mit mindesens einen zutreffenden Tag. Leer lassen für alle Filme.", + "Info": "Info", + "InstanceName": "Instanzname", + "InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname", + "InteractiveImport": "Interaktiver Import", + "LastAlbum": "Neuestes Album", + "LastDuration": "Letzte Dauer", + "LastUsed": "Zuletzt benutzt", + "LastWriteTime": "Zuletzt beschrieben", + "Library": "Bibliothek", + "MassEditor": "Masseneditor", + "Metadata": "Metadaten", + "MonitoredOnly": "Nur beobachtete", + "MoveAutomatically": "Automatisch verschieben", + "MoveFiles": "Dateien verschieben", + "NextExecution": "Nächste Ausführung", + "NoTagsHaveBeenAddedYet": "Es wurden noch keine Tags erstellt", + "OnlyTorrent": "Nur Torrents", + "OnlyUsenet": "Nur Usenet", + "Organize": "Organisieren", + "Peers": "Peers", + "PreferAndUpgrade": "Bevorzugen und upgraden", + "PreferredProtocol": "Bevorzugtes Protokoll", + "Presets": "Voreinstellungen", + "Progress": "Fortschritt", + "QualityLimitsHelpText": "Limitierungen werden automatisch an die Filmlänge angepasst.", + "Queued": "In der Warteschlange", + "Rating": "Bewertung", + "RejectionCount": "Anzahl der Ablehnungen", + "ReleaseTitle": "Release Titel", + "Renamed": "Umbenannt", + "Replace": "Ersetzen", + "RestartRequiredHelpTextWarning": "Erfordert einen Neustart", + "Save": "Speichern", + "SecificMonitoringOptionHelpText": "Autoren überwachen aber nur Bücher überwachen, welche explizit in der Liste miteinbezogen wurden", + "Seeders": "Seeders", + "Select...": "Auswählen...", + "SelectQuality": "Qualität auswählen", + "ShowAdvanced": "Einfache Ansicht", + "SizeLimit": "Grössenlimit", + "SizeOnDisk": "Größe", + "SomeResultsFiltered": "Einige Ergebnisse werden wegen der aktiven Filter nicht angezeigt", + "Started": "gestartet", + "System": "System", + "Test": "Testen", + "TimeLeft": "Restzeit", + "Title": "Titel", + "TotalSpace": "Speicherkapazität", + "UI": "Oberfläche", + "UnableToLoadInteractiveSerach": "Suchergebnisse für diesen Film konnten nicht geladen werden. Versuche es später nocheinmal", + "UnmappedFilesOnly": "Nur nicht zugeordnete Dateien", + "UnmonitoredOnly": "Nur beobachtete", + "UpgradesAllowed": "Upgrades erlaubt", + "Wanted": "› Gesuchte", + "Warn": "Warnung", + "WouldYouLikeToRestoreBackup": "Willst du das Backup {0} wiederherstellen?", + "Age": "Alter", + "LastExecution": "Letzte Ausführung", + "Manual": "Manuell", + "Never": "Niemals", + "OutputPath": "Ausgabe-Pfad", + "SourceTitle": "Release Titel", + "Custom": "Benutzerdefiniert", + "Error": "Fehler", + "Location": "Speicherort", + "MediaManagement": "Medien", + "NoResults": "Keine Ergebnisse gefunden", + "Ok": "OK", + "RestoreBackupAdditionalInfo": "Hinweis: Während der wiederherstellung wird Radarr automatisch neugestartet und die Oberfläche neugelade.", + "SelectFolder": "Ordner auswählen", + "AddConnection": "Sammlung bearbeiten", + "EditMetadataProfile": "Metadaten Profil" } diff --git a/src/NzbDrone.Core/Localization/Core/el.json b/src/NzbDrone.Core/Localization/Core/el.json index b1b2f12ec..191900ff6 100644 --- a/src/NzbDrone.Core/Localization/Core/el.json +++ b/src/NzbDrone.Core/Localization/Core/el.json @@ -472,5 +472,114 @@ "OnHealthIssue": "Σχετικά με το θέμα της υγείας", "OnGrab": "Στο Grab", "Tracks": "Ιχνος", - "MonoVersion": "Μονο έκδοση" + "MonoVersion": "Μονο έκδοση", + "Activity": "Δραστηριότητα", + "Add": "Προσθήκη", + "AddDelayProfile": "Προσθήκη προφίλ καθυστέρησης", + "Added": "Προστέθηκε", + "AddIndexer": "Προσθήκη ευρετηρίου", + "AddList": "Προσθήκη Λίστας", + "AddNew": "Προσθήκη Νέων", + "AddQualityProfile": "Προσθήκη προφίλ ποιότητας", + "AddRemotePathMapping": "Προσθήκη απομακρυσμένης αντιστοίχισης διαδρομής", + "AddRootFolder": "Προσθήκη φακέλου ρίζας", + "AfterManualRefresh": "Μετά τη μη αυτόματη ανανέωση", + "Age": "Ηλικία", + "AllFiles": "Ολα τα αρχεία", + "AllResultsFiltered": "Όλα τα αποτελέσματα αποκρύπτονται από το εφαρμοσμένο φίλτρο", + "Always": "Πάντα", + "AudioInfo": "Στοιχεία ήχου", + "BeforeUpdate": "Πριν από την ενημέρωση", + "Close": "Κλείσιμο", + "Connect": "Σύνδεση", + "Custom": "Εθιμο", + "CustomFilters": "Custom Φιλτρα", + "Date": "Ημερομηνία", + "DefaultDelayProfileHelpText": "Αυτό είναι το προεπιλεγμένο προφίλ. Ισχύει για όλες τις ταινίες που δεν έχουν ρητό προφίλ", + "Deleted": "Διαγράφηκε", + "Details": "Λεπτομέρειες", + "Donations": "Δωρεές", + "DoNotPrefer": "Μην προτιμάτε", + "DoNotUpgradeAutomatically": "Μην κάνετε αυτόματη αναβάθμιση", + "EditDelayProfile": "Επεξεργασία προφίλ καθυστέρησης", + "EditImportListExclusion": "Διαγραφή εξαίρεσης λίστας εισαγωγής", + "EditIndexer": "Επεξεργασία ευρετηρίου", + "EditQualityProfile": "Επεξεργασία προφίλ ποιότητας", + "EditRemotePathMapping": "Επεξεργασία αντιστοίχισης απομακρυσμένης διαδρομής", + "Error": "Λάθος", + "ErrorRestoringBackup": "Σφάλμα κατά την επαναφορά του αντιγράφου ασφαλείας", + "Events": "Γεγονότα", + "EventType": "Είδος Γεγονότος", + "Filters": "Φίλτρο", + "FreeSpace": "Ελεύθερος Χώρος", + "General": "Γενικά", + "Grabbed": "Αρπαξε", + "HardlinkCopyFiles": "Hardlink / Αντιγραφή αρχείων", + "HideAdvanced": "Απόκρυψη Προχωρημένων", + "Ignored": "Αγνοήθηκε", + "InteractiveImport": "Διαδραστική εισαγωγή", + "LastDuration": "τελευταία Διάρκεια", + "LastExecution": "Τελευταία εκτέλεση", + "LastUsed": "Τελευταία χρήση", + "LastWriteTime": "Τελευταία ώρα εγγραφής", + "Metadata": "Μεταδεδομένα", + "MonitoredOnly": "Παρακολούθηση μόνο", + "MoveAutomatically": "Γρήγορη εισαγωγή", + "MoveFiles": "Μετακίνηση αρχείων", + "NextExecution": "Επόμενη εκτέλεση", + "NoResults": "Δεν βρέθηκαν αποτελέσματα", + "NoTagsHaveBeenAddedYet": "Δεν έχουν προστεθεί ετικέτες ακόμη", + "Ok": "Εντάξει", + "OnlyTorrent": "Μόνο Torrent", + "OnlyUsenet": "Μόνο Usenet", + "Organize": "Οργανώνω", + "OutputPath": "Διαδρομή εξόδου", + "Peers": "Ομότιμοι", + "PreferAndUpgrade": "Προτιμήστε και αναβαθμίστε", + "Presets": "Προεπιλογές", + "Progress": "Πρόοδος", + "QualityLimitsHelpText": "Τα όρια προσαρμόζονται αυτόματα για το χρόνο εκτέλεσης της ταινίας.", + "Queued": "Σε ουρά", + "Rating": "Ακροαματικότητα", + "RejectionCount": "Αριθμός απορρίψεων", + "ReleaseTitle": "Τίτλος έκδοσης", + "Renamed": "Μετονομάστηκε", + "Replace": "Αντικαθιστώ", + "Save": "Σώσει", + "Seeders": "Σπόροι", + "Select...": "'Επιλέγω...", + "SelectFolder": "Επιλέξτε φάκελο", + "SelectQuality": "Επιλέξτε Ποιότητα", + "ShowAdvanced": "Εμφάνιση για προχωρημένους", + "SourceTitle": "Τίτλος πηγής", + "System": "Σύστημα", + "Test": "Δοκιμή", + "TimeLeft": "Υπολειπόμενος χρόνος", + "Title": "Τίτλος", + "UI": "Διεπαφή χρήστη", + "UnmappedFilesOnly": "Μόνο μη αντιστοιχισμένα αρχεία", + "UnmonitoredOnly": "Παρακολούθηση μόνο", + "UpgradesAllowed": "Επιτρέπονται αναβαθμίσεις", + "Wanted": "Καταζητούμενος", + "Warn": "Προειδοποιώ", + "RestartRequiredHelpTextWarning": "Απαιτείται επανεκκίνηση για να τεθεί σε ισχύ", + "RestoreBackupAdditionalInfo": "Σημείωση: Το Radarr θα επανεκκινήσει αυτόματα και θα φορτώσει ξανά το περιβάλλον εργασίας χρήστη κατά τη διαδικασία επαναφοράς.", + "SizeOnDisk": "Μέγεθος στο δίσκο", + "SomeResultsFiltered": "Ορισμένα αποτελέσματα αποκρύπτονται από το εφαρμοσμένο φίλτρο", + "TotalSpace": "Συνολικός χώρος", + "Apply": "Εφαρμογή", + "Backup": "Αντίγραφο Ασφαλείας", + "DownloadFailed": "Η λήψη απέτυχε", + "EditRootFolder": "Προσθήκη φακέλου ρίζας", + "Genres": "Είδη", + "Import": "Εισαγωγή", + "Info": "Πληροφορίες", + "UnableToLoadInteractiveSerach": "Δεν είναι δυνατή η φόρτωση αποτελεσμάτων για αυτήν την αναζήτηση ταινιών. Προσπαθήστε ξανά αργότερα", + "WouldYouLikeToRestoreBackup": "Θέλετε να επαναφέρετε το αντίγραφο ασφαλείας {0};", + "All": "Όλα", + "Location": "Τοποθεσία", + "Manual": "Εγχειρίδιο", + "MediaManagement": "Διαχείριση μέσων", + "ImportListExclusions": "Διαγραφή εξαίρεσης λίστας εισαγωγής", + "AddImportListExclusion": "Διαγραφή εξαίρεσης λίστας εισαγωγής" } diff --git a/src/NzbDrone.Core/Localization/Core/es.json b/src/NzbDrone.Core/Localization/Core/es.json index 5c4e439aa..78e85166f 100644 --- a/src/NzbDrone.Core/Localization/Core/es.json +++ b/src/NzbDrone.Core/Localization/Core/es.json @@ -495,5 +495,125 @@ "Label": "Etiqueta", "AllowArtistChangeClickToChangeArtist": "Click para cambiar el autor", "AddedArtistSettings": "Añadidas las opciones de Autor", - "MonoVersion": "Version de Mono" + "MonoVersion": "Version de Mono", + "Activity": "Actividad", + "Add": "Añadir", + "AddDelayProfile": "Añadir Perfil de Retardo", + "Added": "An̄adida", + "AddIndexer": "Añadir Indexer", + "AddList": "Añadir lista", + "AddMetadataProfile": "perfil de metadatos", + "AddNew": "Añadir Nueva", + "AddQualityProfile": "Añadir Perfil de Calidad", + "AddRemotePathMapping": "Añadir Mapping de la Ruta Remota", + "AddRootFolder": "Añadir Carpeta Raíz", + "AfterManualRefresh": "Después de Refresco Manual", + "Age": "Edad", + "All": "Todas", + "AllFiles": "Todos los Archivos", + "AllResultsFiltered": "Todos los resultados están ocultos por el filtro aplicado", + "Always": "Siempre", + "Apply": "Aplicar", + "AudioInfo": "Audio Info", + "Backup": "Backup", + "Close": "Cerrar", + "Connect": "Conectar", + "Date": "Fecha", + "DefaultDelayProfileHelpText": "Este es el perfil predeterminado. Se aplica a todas las películas que no tienen un perfil explícito.", + "Deleted": "Borrado", + "Details": "Detalles", + "Donations": "Donaciones", + "DoNotPrefer": "No prefiero", + "DoNotUpgradeAutomatically": "No actualizar automáticamente", + "DownloadFailed": "La descarga ha fallado", + "EditImportListExclusion": "Borrar exclusión de lista de importación", + "EditIndexer": "Editar Indexer", + "EditQualityProfile": "Editar perfil de calidad", + "EditRemotePathMapping": "Editar Mapping de Ruta Remota", + "EditRootFolder": "Añadir Carpeta Raíz", + "Error": "Error", + "ErrorRestoringBackup": "Error al restaurar la copia de seguridad", + "Events": "Eventos", + "EventType": "Tipo de Evento", + "Filters": "Filtros", + "FreeSpace": "Espacio Libre", + "General": "General", + "Genres": "Géneros", + "Grabbed": "Capturado", + "HardlinkCopyFiles": "Hardlink/Copia de Archivos", + "HideAdvanced": "Ocultar Avanzado", + "Ignored": "Ignorado", + "Import": "Importar", + "IndexerDownloadClientHelpText": "Especifica qué cliente de descargas se utiliza para las descargas de este indexador", + "IndexerTagHelpText": "Solo utilizar este indexador para películas que coincidan con al menos una etiqueta. Déjelo en blanco para utilizarlo con todas las películas.", + "InstanceName": "Nombre de Instancia", + "InstanceNameHelpText": "Nombre de instancia en pestaña y para nombre de aplicación en Syslog", + "LastDuration": "Duración", + "LastExecution": "Última ejecución", + "LastWriteTime": "Última Fecha de Escritura", + "Library": "Biblioteca", + "MediaManagement": "Multimedia", + "Metadata": "Metadatos", + "MonitoredOnly": "Monitoreadas Solamente", + "Never": "Nunca", + "NextExecution": "Siguiente ejecución", + "NoResults": "No se han encontrado resultados", + "NoTagsHaveBeenAddedYet": "No se han añadido etiquetas todavía", + "Ok": "Ok", + "OnlyTorrent": "Solo Torrent", + "OnlyUsenet": "Solo Usenet", + "OutputPath": "Ruta de Output", + "Peers": "Pares", + "PreferAndUpgrade": "Preferir y actualizar", + "Presets": "Preajustes", + "Progress": "Progreso", + "QualityLimitsHelpText": "Los límites se ajustan automáticamente para el tiempo de ejecución de la película.", + "Queued": "En Cola", + "Rating": "Puntuación", + "RejectionCount": "Número de Rechazos", + "ReleaseTitle": "Título del Estreno", + "Renamed": "Renombrado", + "RestartRequiredHelpTextWarning": "Requiere reiniciar para que surta efecto", + "RestoreBackupAdditionalInfo": "Nota: Radarr se reiniciará y recargará automáticamente la IU durante el proceso de restauración.", + "Save": "Guardar", + "Seeders": "Semillas", + "Select...": "Seleccione...", + "SelectFolder": "Seleccionar Carpeta", + "SelectQuality": "Seleccionar Calidad", + "ShowAdvanced": "Mostrar Avanzado", + "SizeLimit": "Tamaño límite", + "SizeOnDisk": "Tamaño en Disco", + "SomeResultsFiltered": "Algunos resultados están ocultos por el filtro aplicado", + "SourceTitle": "Título de Origen", + "System": "Sistema", + "Test": "Test", + "TimeLeft": "Tiempo restante", + "Title": "Título", + "TotalSpace": "Espacio Total", + "UI": "UI", + "UnableToLoadInteractiveSerach": "No se pueden cargar los resultados de esta búsqueda de películas. Vuelve a intentarlo más tarde", + "UnmappedFilesOnly": "Solo archivos sin asignar", + "UnmonitoredOnly": "Monitoreadas Solamente", + "UpgradesAllowed": "Mejoras permitidas", + "Wanted": "Buscado", + "WouldYouLikeToRestoreBackup": "¿Le gustaría restaurar la copia de seguridad {0}?", + "Location": "Localización", + "Manual": "Manual", + "MoveAutomatically": "Mover autmáticamente", + "MoveFiles": "Mover Archivos", + "Organize": "Organizar", + "Warn": "Advertencia", + "BeforeUpdate": "Antes de actualizar", + "Custom": "Personalizado", + "CustomFilters": "Filtros Personalizados", + "EditConnection": "Editar Colección", + "EditDelayProfile": "Editar perfil de retraso", + "Info": "Información", + "InteractiveImport": "Importación Interactiva", + "LastUsed": "Utilizado por última vez", + "Replace": "Reemplazar", + "Started": "Iniciado", + "AddConnection": "Editar Colección", + "AddImportListExclusion": "Borrar exclusión de lista de importación", + "EditMetadataProfile": "perfil de metadatos" } diff --git a/src/NzbDrone.Core/Localization/Core/fi.json b/src/NzbDrone.Core/Localization/Core/fi.json index 60d314892..c9f5e18f5 100644 --- a/src/NzbDrone.Core/Localization/Core/fi.json +++ b/src/NzbDrone.Core/Localization/Core/fi.json @@ -26,7 +26,6 @@ "DefaultTagsHelpText": "Kansiosta löydetyille esittäjille oletusarvoisesti määritettävät tunnisteet.", "IsTagUsedCannotBeDeletedWhileInUse": "Tunnistetta ei voi poistaa, koska se on käytössä", "LidarrTags": "Lidarr-tunnisteet", - "NoTagsHaveBeenAddedYetAddTagsToLinkArtistsWithDelayProfilesRestrictionsOrNotificationsClickLinkTohttpswikiservarrcomlidarrsettingstagshereLinkToFindOutMoreAboutTagsInLidarr": "Tunnisteita ei ole vielä lisätty. Lisää tunnisteita määrittääksesi artisteille viiveprofiileja, rajoituksia tai ilmoituksia. Paina tästä lukeaksesi lisää tunnisteista Lidarrissa.", "RemoveTagRemovingTag": "Tunniste poistetaan", "ChmodFolderHelpText": "Octal, sovelletaan tuonnin/nimeämisen yhteydessä mediakansioihin ja -tiedostoihin (ilman suoritusbittejä).", "LaunchBrowserHelpText": " Avaa Lidarrin verkkokäyttöliittymä verkkoselaimeen sovelluksen käynnistyksen yhteydessä.", @@ -551,5 +550,131 @@ "MonitorAlbumExistingOnlyWarning": "Tämä on kirjakohtaisen valvonnan kertaluontoinen määritys. Käytä Kirjailija/Muokkaa-valintaa hallinnoidaksesi mitä uusille kirjalisäyksille tehdään.", "MonitoringOptionsHelpText": "Kansiosta löydetyille kirjailijoille oletusarvoisesti asetettava kirjojen valvontataso (kertaluontoinen määritys).", "MonitorNewItemsHelpText": "Uusien kirjojen valvontatapa.", - "MonoVersion": "Mono-versio" + "MonoVersion": "Mono-versio", + "AddDelayProfile": "Lisää viiveprofiili", + "Added": "Lisätty", + "AddImportListExclusion": "Tuotilistojen poikkeukset", + "AddIndexer": "Lisää tietolähde", + "AddList": "Lisää tuontilista", + "AddMetadataProfile": "Metatietoprofiili", + "AddNew": "Lisää uusi", + "AddQualityProfile": "Lisää laatuprofiili", + "AddRootFolder": "Lisää juurikansio", + "AfterManualRefresh": "Manuaalisen päivityksen jälkeen", + "Age": "Ikä", + "Albums": "Albumit", + "All": "Kaikki", + "AllFiles": "Kaikki tiedostot", + "AllResultsFiltered": "Kaikki tulokset on piilotettu aktiivisen suodattimen johdosta.", + "Always": "Aina", + "ApplicationURL": "Sovelluksen URL-osoite", + "ApplicationUrlHelpText": "Sovelluksen ulkoinen URL-osoite, johon sisältyy http(s)://, portti ja URL-perusta.", + "Apply": "Käytä", + "AudioInfo": "Äänitiedot", + "Backup": "Varmuuskopio", + "BeforeUpdate": "Ennen päivitystä", + "Close": "Sulje", + "Connect": "Kytkennät", + "Custom": "Mukautettu", + "CustomFilters": "Mukautetut suodattimet", + "Date": "Päiväys", + "DefaultDelayProfileHelpText": "Tämä on oletusprofiili, jota sovelletaan kaikkiin elokuviin, joille ei ole määritetty erillistä profiilia.", + "Deleted": "Poistettu", + "Details": "Tiedot", + "Donations": "Lahjoitukset", + "DoNotPrefer": "Älä suosi", + "DoNotUpgradeAutomatically": "Älä päivitä automaattisesti", + "DownloadFailed": "Lataus epäonnistui", + "EditImportListExclusion": "Poista poikkeussääntö", + "EditIndexer": "Muokkaa tietolähdettä", + "EditQualityProfile": "Muokkaa laatuprofiilia", + "EditRemotePathMapping": "Muokkaa etäreittien kartoitusta", + "EditRootFolder": "Lisää juurikansio", + "Error": "Virhe", + "ErrorRestoringBackup": "Varmuuskopion palautuksen virhe", + "Events": "Tapahtumat", + "EventType": "Tapahtumatyyppi", + "Filters": "Suodattimet", + "General": "Yleiset", + "Genres": "Tyylilajit", + "Grabbed": "Siepattu", + "HardlinkCopyFiles": "Hardlink/tiedostojen kopiointi", + "HideAdvanced": "Piilota edistyneet", + "Ignored": "Ohitettu", + "Import": "Tuonti", + "IndexerTagHelpText": "Tietolähdettä käytetään vähintään yhdellä täsmäävällä tunnisteella merkityille elokuville. Käytä kaikille jättämällä tyhjäksi.", + "Info": "Informatiivinen", + "InstanceName": "Instanssin nimi", + "InstanceNameHelpText": "Instanssin nimi välilehdellä ja järjestelmälokissa", + "InteractiveImport": "Vuorovaikutteinen tuonti", + "LastDuration": "Edellinen kesto", + "LastExecution": "Edellinen suoritus", + "LastUsed": "Viimeksi käytetty", + "LastWriteTime": "Viimeisin kirjoitusaika", + "Library": "kirjasto", + "Location": "Sijainti", + "Manual": "Manuaalinen", + "MassEditor": "Massamuokkaus", + "MediaManagement": "Median hallinta", + "Metadata": "Metatiedot", + "MonitoredOnly": "Valvottu", + "MoveAutomatically": "Siirrä automaattisesti", + "MoveFiles": "Siirrä tiedostoja", + "Never": "Ei koskaan", + "NextExecution": "Seuraava suoritus", + "NoResults": "Ei tuloksia", + "NoTagsHaveBeenAddedYet": "Tunnisteita ei ole vielä lisätty.", + "OnlyTorrent": "Vain Torrent", + "OnlyUsenet": "Vain Usenet", + "Peers": "Vertaiset", + "PreferAndUpgrade": "Valitse ja päivitä", + "PreferredProtocol": "Ensisijainen protokolla", + "Presets": "Esiasetukset", + "Progress": "Edistyminen", + "QualityLimitsHelpText": "Rajoitukset suhteutetaan automaattisesti elokuvan kestoon.", + "Queued": "Jonossa", + "Rating": "Arvio", + "RejectionCount": "Hylkäämisluku", + "ReleaseTitle": "Julkaisun otsikko", + "Replace": "Korvaa", + "RestartRequiredHelpTextWarning": "Käyttöönotto vaatii uudelleenkäynnistyksen.", + "RestoreBackupAdditionalInfo": "Huomautus: Radarr käynnistää ja lataa käyttöliittymän automaattisesti palautusprosessin aikana.", + "Save": "Tallenna", + "Seeders": "Jakajat", + "Select...": "Valitse...", + "SelectFolder": "Valitse kansio", + "SelectQuality": "Valitse Laatu", + "ShouldMonitorExistingHelpText": "Valvo automaattisesti tällä listalla olevia kirjoja, jotka ovat jo kirjastossasi.", + "ShowAdvanced": "Näytä edistyneet", + "SizeLimit": "Kokorajoitus", + "SizeOnDisk": "Koko levyllä", + "SomeResultsFiltered": "Joitakin tuloksia piilottaa käytetty suodatin", + "SourceTitle": "Lähteen otsikko", + "Started": "Alkoi", + "System": "Järjestelmä", + "Test": "Kokeile", + "TimeLeft": "Aikaa jäljellä", + "Title": "Nimike", + "TotalSpace": "Kokonaistila", + "UI": "Käyttöliittymä", + "UnableToLoadInteractiveSerach": "Elokuvahaun tuloksien lataus epäonnistui. Yritä myöhemmin uudelleen.", + "UnmappedFilesOnly": "Vain kartoittamattomat tiedostot", + "UnmonitoredOnly": "Valvottu", + "UpgradesAllowed": "Päivitykset sallitaan", + "Wanted": "Halutut", + "Warn": "Varoita", + "WouldYouLikeToRestoreBackup": "Haluatko palauttaa varmuuskopion {0}?", + "AddRemotePathMapping": "Lisää etäsijainnin kartoitus", + "EditConnection": "Muokkaa kokoelmaa", + "FreeSpace": "Vapaa tila", + "IndexerDownloadClientHelpText": "Määritä tämän tietolähteen kanssa käytettävä lataustyökalu", + "Ok": "Ok", + "Organize": "Järjestää", + "OutputPath": "Tallennussijainti", + "Renamed": "Nimetty uudelleen", + "Activity": "Tapahtumat", + "EditDelayProfile": "Muokkaa viiveprofiilia", + "Add": "Lisää", + "AddConnection": "Muokkaa kokoelmaa", + "EditMetadataProfile": "Metatietoprofiili" } diff --git a/src/NzbDrone.Core/Localization/Core/fr.json b/src/NzbDrone.Core/Localization/Core/fr.json index 5f9a8c663..366f76bb8 100644 --- a/src/NzbDrone.Core/Localization/Core/fr.json +++ b/src/NzbDrone.Core/Localization/Core/fr.json @@ -540,5 +540,131 @@ "StatusEndedContinuing": "Continuant", "Term": "Terme", "TrackFileCounttotalTrackCountTracksDownloadedInterp": "{0}/{1} livres téléchargés", - "WriteMetadataToAudioFiles": "Écrire des métadonnées dans les fichiers audio" + "WriteMetadataToAudioFiles": "Écrire des métadonnées dans les fichiers audio", + "Activity": "Activité", + "Add": "Ajouter", + "AddIndexer": "Ajouter un indexeur", + "AddList": "Ajouter la liste", + "AddMetadataProfile": "profil de métadonnées", + "AddNew": "Ajouter un nouveau", + "AddQualityProfile": "Ajouter un profil de qualité", + "AddRemotePathMapping": "Ajouter un chemin distant", + "AddRootFolder": "Ajouter un dossier racine", + "AfterManualRefresh": "Après un rafraichissement manuel", + "Age": "Âge", + "Albums": "Album", + "All": "Tout", + "AllFiles": "Tous les fichiers", + "AllMonitoringOptionHelpText": "Surveiller les auteurs et tous les livres pour chaque auteur inclus dans la liste d'importation", + "AllResultsFiltered": "Tous les résultats ont été dissimulés par le filtre actuellement appliqué", + "Always": "Toujours", + "AudioInfo": "Info audio", + "Backup": "Sauvegarde", + "BeforeUpdate": "Avant mise à jour", + "Close": "Fermer", + "Connect": "Connecter", + "Custom": "Personnalisé", + "CustomFilters": "Filtres personnalisés", + "Date": "Date", + "DefaultDelayProfileHelpText": "Ceci est le profil par défaut. Il est appliqué à tous les films qui n'ont pas de profils spécifiques.", + "Deleted": "Supprimé", + "Details": "Détails", + "Donations": "Dons", + "DoNotPrefer": "Ne pas préférer", + "DoNotUpgradeAutomatically": "Ne pas mettre à jour automatiquement", + "DownloadFailed": "Échec du téléchargement", + "EditConnection": "Modifier la collection", + "EditDelayProfile": "Modifier le profil de délai", + "EditImportListExclusion": "Supprimer les exclusions de liste d'imports", + "EditIndexer": "Modifier l'indexeur", + "EditQualityProfile": "Modifier les profils", + "EditRemotePathMapping": "Éditer le chemin distant", + "EditRootFolder": "Ajouter un dossier racine", + "ErrorRestoringBackup": "Erreur lors de la restauration de la sauvegarde", + "EventType": "Type d'événement", + "Filters": "Filtres", + "FreeSpace": "Espace libre", + "General": "Général", + "Genres": "Genres", + "Grabbed": "Attrapé", + "HardlinkCopyFiles": "Lier/copier les fichiers", + "HideAdvanced": "Masquer avancé", + "Ignored": "Ignoré", + "IndexerDownloadClientHelpText": "Spécifiez quel client de téléchargement est utilisé pour cet indexeur", + "IndexerTagHelpText": "Utiliser seulement cet indexeur pour les films avec au moins un tag correspondant. Laissez vide pour l'utiliser avec tous les films.", + "Info": "Info", + "InstanceName": "Nom de l'instance", + "InstanceNameHelpText": "Nom de l'instance dans l'onglet du navigateur et pour le nom d'application dans Syslog", + "InteractiveImport": "Importation interactive", + "LastExecution": "Dernière exécution", + "LastUsed": "Dernière utilisation", + "LastWriteTime": "Heure de la dernière écriture", + "Library": "Bibliothèque", + "Location": "Emplacement", + "Manual": "Manuel", + "MassEditor": "Éditer en masse", + "MediaManagement": "Gestion des médias", + "Metadata": "Métadonnées", + "MonitoredOnly": "Surveillé uniquement", + "MoveAutomatically": "Déplacer automatiquement", + "MoveFiles": "Déplacer les fichiers", + "OnlyTorrent": "Seulement Torrent", + "OnlyUsenet": "Seulement Usenet", + "Organize": "Organiser", + "OutputPath": "Chemin de sortie", + "Peers": "Pairs", + "PreferAndUpgrade": "Préférez et améliorez", + "PreferredProtocol": "Protocole préféré", + "Presets": "Préconfigurations", + "Progress": "Progression", + "Queued": "En file d'attente", + "Rating": "Note", + "RejectionCount": "Compteur de rejet", + "ReleaseTitle": "Titre de la version", + "Renamed": "Renommé", + "Replace": "Remplacer", + "RestartRequiredHelpTextWarning": "Nécessite un redémarrage pour prendre effet", + "RestoreBackupAdditionalInfo": "Remarque : Radarr redémarrera et rechargera automatiquement l'interface utilisateur pendant le processus de restauration.", + "Save": "Sauvegarder", + "SecificMonitoringOptionHelpText": "Surveiller les auteurs, mais seulement surveiller les livres explicitement inclus dans la liste", + "Seeders": "Seeders", + "Select...": "Sélectionner...", + "SelectFolder": "Sélectionner le dossier", + "SelectQuality": "Sélectionnez la qualité", + "ShowAdvanced": "Afficher avancés", + "SizeLimit": "Limite de taille", + "SizeOnDisk": "Taille sur le disque", + "SomeResultsFiltered": "Tous les résultats ont été dissimulés par le filtre actuellement appliqué", + "SourceTitle": "Titre de la source", + "Started": "Démarré", + "System": "Système", + "Test": "Tester", + "TimeLeft": "Temps restant", + "Title": "Titre", + "TotalSpace": "Espace total", + "UI": "UI", + "UnmappedFilesOnly": "Fichiers non mappés uniquement", + "UnmonitoredOnly": "Surveillé uniquement", + "UpgradesAllowed": "Mises à niveau autorisées", + "Wanted": "Recherché", + "Warn": "Avertissement", + "WouldYouLikeToRestoreBackup": "Souhaitez-vous restaurer la sauvegarde {0} ?", + "Added": "Ajoutée", + "ApplicationURL": "URL de l'application", + "ApplicationUrlHelpText": "URL externe de cette application, y compris http(s)://, le port et l'URL de base", + "Apply": "Appliquer", + "Error": "Erreur", + "Events": "Événements", + "Never": "Jamais", + "NextExecution": "Prochaine exécution", + "QualityLimitsHelpText": "Les limites sont automatiquement ajustées pour l'exécution du film.", + "UnableToLoadInteractiveSerach": "Impossible de charger les résultats de cette recherche de films. Réessayez plus tard", + "Import": "Importer", + "NoResults": "Aucun résultat trouvé", + "NoTagsHaveBeenAddedYet": "Aucune identification n'a été ajoutée pour l'instant", + "Ok": "OK", + "AddDelayProfile": "Ajouter un profil différé", + "AddImportListExclusion": "Supprimer les exclusions de liste d'imports", + "EditMetadataProfile": "profil de métadonnées", + "AddConnection": "Modifier la collection" } diff --git a/src/NzbDrone.Core/Localization/Core/he.json b/src/NzbDrone.Core/Localization/Core/he.json index 0a4a6ab2e..74868db69 100644 --- a/src/NzbDrone.Core/Localization/Core/he.json +++ b/src/NzbDrone.Core/Localization/Core/he.json @@ -477,5 +477,118 @@ "BlocklistHelpText": "מנע מראדרר להוסיף את ההוצאה הזאת שוב", "Duration": "אורך", "OnApplicationUpdate": "כשהאפליקציה מעדכנת גרסא", - "OnApplicationUpdateHelpText": "כשהאפליקציה מעדכנת גרסא" + "OnApplicationUpdateHelpText": "כשהאפליקציה מעדכנת גרסא", + "Activity": "פעילות", + "Add": "לְהוֹסִיף", + "AddDelayProfile": "הוסף פרופיל עיכוב", + "Added": "נוסף", + "AddIndexer": "הוסף אינדקס", + "AddList": "הוסף רשימה", + "AddNew": "הוסף חדש", + "AddQualityProfile": "הוסף פרופיל איכות", + "AddRemotePathMapping": "הוסף מיפוי נתיבים מרוחק", + "AddRootFolder": "הוסף תיקיית שורש", + "AfterManualRefresh": "לאחר רענון ידני", + "Age": "גיל", + "All": "את כל", + "AllFiles": "כל הקבצים", + "AllResultsFiltered": "כל התוצאות מוסתרות על ידי המסנן שהוחל", + "Always": "תמיד", + "Apply": "להגיש מועמדות", + "AudioInfo": "מידע שמע", + "BeforeUpdate": "לפני העדכון", + "Connect": "לְחַבֵּר", + "Custom": "המותאם אישית", + "CustomFilters": "מסננים מותאמים אישית", + "DefaultDelayProfileHelpText": "זהו פרופיל ברירת המחדל. זה חל על כל הסרטים שאין להם פרופיל מפורש.", + "Deleted": "נמחק", + "Donations": "תרומות", + "DoNotPrefer": "לא מעדיפים", + "DoNotUpgradeAutomatically": "אל תשדרג אוטומטית", + "DownloadFailed": "הורדה נכשלה", + "EditDelayProfile": "ערוך פרופיל עיכוב", + "EditImportListExclusion": "מחק אי הכללת רשימות ייבוא", + "EditIndexer": "ערוך אינדקס", + "EditQualityProfile": "ערוך פרופיל איכות", + "EditRemotePathMapping": "ערוך מיפוי נתיבים מרחוק", + "EditRootFolder": "הוסף תיקיית שורש", + "ErrorRestoringBackup": "שגיאה בשחזור הגיבוי", + "Events": "אירועים", + "EventType": "סוג אירוע", + "Filters": "לְסַנֵן", + "FreeSpace": "מקום פנוי", + "General": "כללי", + "Genres": "ז'אנרים", + "Grabbed": "תפס", + "HardlinkCopyFiles": "קישור קשיח / העתק קבצים", + "HideAdvanced": "הסתר מתקדם", + "Ignored": "התעלמו", + "Import": "יְבוּא", + "IndexerDownloadClientHelpText": "ציין איזה קליינט הורדה משתמש באינדקסר הזה להורדה", + "InstanceNameHelpText": "שם מופע בטאב ובשביל שם אפליקציית סיסלוג", + "InteractiveImport": "ייבוא אינטראקטיבי", + "LastDuration": "lastDuration", + "LastExecution": "ביצוע אחרון", + "LastUsed": "נעשה שימוש אחרון", + "LastWriteTime": "זמן כתיבה אחרון", + "Manual": "מדריך ל", + "MediaManagement": "ניהול מדיה", + "Metadata": "מטא נתונים", + "MonitoredOnly": "מנוטר בלבד", + "MoveAutomatically": "ייבוא מהיר", + "MoveFiles": "העבר קבצים", + "Never": "אף פעם", + "NextExecution": "הביצוע הבא", + "NoResults": "לא נמצאו תוצאות", + "NoTagsHaveBeenAddedYet": "עדיין לא נוספו תגים", + "OutputPath": "נתיב פלט", + "Peers": "עמיתים", + "PreferAndUpgrade": "העדפה ושדרג", + "Presets": "הגדרות קבועות מראש", + "Progress": "התקדמות", + "QualityLimitsHelpText": "המגבלות מותאמות אוטומטית לזמן הריצה של הסרט.", + "Rating": "דירוגים", + "RejectionCount": "ספירת דחייה", + "ReleaseTitle": "שחרר את הכותרת", + "Renamed": "שונה שם", + "Replace": "החלף", + "RestartRequiredHelpTextWarning": "נדרש הפעלה מחדש כדי להיכנס לתוקף", + "RestoreBackupAdditionalInfo": "הערה: Radarr יופעל מחדש אוטומטית וטען מחדש את ממשק המשתמש במהלך תהליך השחזור.", + "Save": "להציל", + "Seeders": "זריעים", + "Select...": "'בחר...", + "SelectFolder": "בחר תיקייה", + "SelectQuality": "בחר איכות", + "ShowAdvanced": "הצג מתקדם", + "SizeOnDisk": "גודל בדיסק", + "SomeResultsFiltered": "חלק מהתוצאות מוסתרות על ידי המסנן שהוחל", + "SourceTitle": "כותרת מקור", + "System": "מערכת", + "Test": "מִבְחָן", + "TimeLeft": "הזמן שנותר", + "Title": "כותרת", + "TotalSpace": "השטח הכולל", + "UI": "ממשק משתמש", + "UnableToLoadInteractiveSerach": "לא ניתן לטעון תוצאות לחיפוש הסרט הזה. נסה שוב מאוחר יותר", + "UnmappedFilesOnly": "קבצים שלא ממופים בלבד", + "UnmonitoredOnly": "מנוטר בלבד", + "UpgradesAllowed": "שדרוגים מותרים", + "Wanted": "מבוקש", + "Warn": "לְהַזהִיר", + "WouldYouLikeToRestoreBackup": "האם ברצונך לשחזר את הגיבוי {0}?", + "InstanceName": "שם מופע", + "Ok": "בסדר", + "Backup": "גיבוי", + "Date": "תַאֲרִיך", + "Location": "מקום", + "Queued": "בתור", + "Close": "סגור", + "Details": "פרטים", + "Error": "שְׁגִיאָה", + "IndexerTagHelpText": "השתמש באינדקסר זה רק לסרטים שתואמים לתויות. השאר ריק כדי לחפש את כל הסרטים.", + "Info": "מידע", + "OnlyTorrent": "רק סיקור", + "OnlyUsenet": "רק Usenet", + "Organize": "לְאַרגֵן", + "AddImportListExclusion": "מחק אי הכללת רשימות ייבוא" } diff --git a/src/NzbDrone.Core/Localization/Core/hi.json b/src/NzbDrone.Core/Localization/Core/hi.json index 8863b6725..d3ecc3e10 100644 --- a/src/NzbDrone.Core/Localization/Core/hi.json +++ b/src/NzbDrone.Core/Localization/Core/hi.json @@ -473,5 +473,113 @@ "OnUpgrade": "अपग्रेड पर", "Tracks": "निशान", "NETCore": ".NET कोर", - "MonoVersion": "मोनो संस्करण" + "MonoVersion": "मोनो संस्करण", + "Activity": "गतिविधि", + "Add": "जोड़ना", + "AddDelayProfile": "विलंब प्रोफ़ाइल जोड़ें", + "Added": "जोड़ा", + "AddIndexer": "सूचकांक जोड़ें", + "AddList": "सूची में जोड़ने", + "AddNew": "नया जोड़ें", + "AddQualityProfile": "गुणवत्ता प्रोफ़ाइल जोड़ें", + "AddRemotePathMapping": "रिमोट पाथ मैपिंग जोड़ें", + "AddRootFolder": "रूट फ़ोल्डर जोड़ें", + "AfterManualRefresh": "मैनुअल रिफ्रेश के बाद", + "Age": "उम्र", + "All": "सब", + "AllFiles": "सारे दस्तावेज", + "AllResultsFiltered": "सभी परिणाम लागू फ़िल्टर द्वारा छिपे हुए हैं", + "Always": "हमेशा", + "Apply": "लागू", + "AudioInfo": "ऑडियो जानकारी", + "BeforeUpdate": "अपडेट करने से पहले", + "Close": "बंद करे", + "Connect": "जुडिये", + "Custom": "रिवाज", + "CustomFilters": "कस्टम फ़िल्टर", + "Date": "दिनांक", + "DefaultDelayProfileHelpText": "यह डिफ़ॉल्ट प्रोफ़ाइल है। यह उन सभी फिल्मों पर लागू होता है जिनमें स्पष्ट प्रोफ़ाइल नहीं है।", + "Deleted": "हटाए गए", + "Details": "विवरण", + "Donations": "दान", + "DoNotPrefer": "प्रेफर न करें", + "DoNotUpgradeAutomatically": "स्वचालित रूप से अपग्रेड न करें", + "DownloadFailed": "डाउनलोड विफल", + "EditDelayProfile": "देरी प्रोफ़ाइल संपादित करें", + "EditImportListExclusion": "आयात सूची बहिष्करण हटाएं", + "EditIndexer": "अनुक्रमणिका संपादित करें", + "EditQualityProfile": "गुणवत्ता प्रोफ़ाइल संपादित करें", + "EditRemotePathMapping": "दूरस्थ पथ मानचित्रण संपादित करें", + "EditRootFolder": "रूट फ़ोल्डर जोड़ें", + "Error": "त्रुटि", + "ErrorRestoringBackup": "बैकअप बहाल करने में त्रुटि", + "Events": "आयोजन", + "EventType": "घटना प्रकार", + "Filters": "फ़िल्टर", + "FreeSpace": "खाली जगह", + "General": "आम", + "Genres": "शैलियां", + "Grabbed": "पकड़ा", + "HardlinkCopyFiles": "हार्डलिंक / कॉपी फाइलें", + "HideAdvanced": "उन्नत छिपाएँ", + "Ignored": "अवहेलना करना", + "Import": "आयात", + "Info": "जानकारी", + "InteractiveImport": "इंटरएक्टिव आयात", + "LastDuration": "lastDuration", + "LastExecution": "अंतिम निष्पादन", + "LastUsed": "आखरी इस्त्तमाल किया गया", + "LastWriteTime": "अंतिम समय लिखें", + "Location": "स्थान", + "MediaManagement": "मीडिया प्रबंधन", + "Metadata": "मेटाडाटा", + "MonitoredOnly": "केवल निगरानी की", + "MoveAutomatically": "त्वरित आयात", + "MoveFiles": "फ़ाइलें ले जाएँ", + "NextExecution": "अगला निष्पादन", + "NoResults": "कोई परिणाम नहीं मिला", + "NoTagsHaveBeenAddedYet": "अभी तक कोई टैग नहीं जोड़े गए हैं", + "Ok": "ठीक", + "OnlyTorrent": "केवल धार", + "OnlyUsenet": "केवल यूज़नेट", + "Presets": "प्रीसेट", + "Progress": "प्रगति", + "QualityLimitsHelpText": "फिल्म रनटाइम के लिए सीमाएं स्वचालित रूप से समायोजित की जाती हैं।", + "Queued": "कतारबद्ध", + "Rating": "रेटिंग्स", + "RejectionCount": "अस्वीकृति गणना", + "ReleaseTitle": "रिलीज का शीर्षक", + "Renamed": "नाम बदलकर", + "Replace": "बदलने के", + "RestartRequiredHelpTextWarning": "प्रभावी करने के लिए पुनरारंभ की आवश्यकता है", + "RestoreBackupAdditionalInfo": "नोट: रैडियर स्वचालित रूप से पुनः आरंभ करेगा और पुनर्स्थापना प्रक्रिया के दौरान UI को फिर से लोड करेगा।", + "Save": "सहेजें", + "Seeders": "बीज", + "Select...": "'चुनते हैं...", + "SelectFolder": "फोल्डर का चयन करें", + "SelectQuality": "गुणवत्ता का चयन करें", + "ShowAdvanced": "शो पहले होगा", + "SizeOnDisk": "डिस्क का माप", + "SomeResultsFiltered": "कुछ परिणाम लागू फ़िल्टर द्वारा छिपे हुए हैं", + "SourceTitle": "स्रोत शीर्षक", + "Test": "परीक्षा", + "TimeLeft": "शेष समय", + "Title": "शीर्षक", + "TotalSpace": "कुल स्थान", + "UI": "यूआई", + "UnableToLoadInteractiveSerach": "इस मूवी खोज के लिए परिणाम लोड करने में असमर्थ। बाद में पुन: प्रयास करें", + "UnmappedFilesOnly": "केवल फ़ाइलें अनमैप्ड हैं", + "UnmonitoredOnly": "केवल निगरानी की", + "UpgradesAllowed": "अपग्रेड की अनुमति है", + "Wanted": "चाहता था", + "Warn": "चेतावनी देना", + "WouldYouLikeToRestoreBackup": "क्या आप बैकअप {0} को पुनर्स्थापित करना चाहेंगे?", + "Backup": "बैकअप", + "Organize": "व्यवस्थित", + "Manual": "गाइड", + "OutputPath": "उत्पादन के पथ", + "Peers": "साथियों", + "PreferAndUpgrade": "प्राथमिकता और उन्नयन", + "System": "प्रणाली", + "AddImportListExclusion": "आयात सूची बहिष्करण हटाएं" } diff --git a/src/NzbDrone.Core/Localization/Core/hu.json b/src/NzbDrone.Core/Localization/Core/hu.json index 5bd248fe5..0171ec425 100644 --- a/src/NzbDrone.Core/Localization/Core/hu.json +++ b/src/NzbDrone.Core/Localization/Core/hu.json @@ -603,7 +603,6 @@ "Original": "Eredeti", "None": "Nincs", "NoneData": "Az albumok nem lesznek monitorozva", - "NoTagsHaveBeenAddedYetAddTagsToLinkArtistsWithDelayProfilesRestrictionsOrNotificationsClickLinkTohttpswikiservarrcomlidarrsettingstagshereLinkToFindOutMoreAboutTagsInLidarr": "Még nem adtak hozzá címkéket. Adjon hozzá címkéket az előadók összekapcsolásához késleltetési profilokkal, korlátozásokkal vagy értesítésekkel. Kattintson Ide ha többet szeretne megtudni a Lidarr címkéiről.", "OnDownloadFailureHelpText": "Letöltési hiba esetén", "OnGrabHelpText": "Kiválasztás alatt", "OnImportFailureHelpText": "Importálási hiba esetén", @@ -657,7 +656,6 @@ "ShowMonitored": "Monitorozottak mutatása", "ShownAboveEachColumnWhenWeekIsTheActiveView": "Minden oszlop felett jelenjen meg, hogy melyik hét az aktuális", "SkipRedownload": "Az újraletöltés átugrása", - "Sonarr": "Sonarr", "SorryThatAlbumCannotBeFound": "Sajnos az album nem található.", "SorryThatArtistCannotBeFound": "Sajnáljuk, az előadó nem található.", "SourcePath": "Forrás útvonala", @@ -700,5 +698,138 @@ "MonoVersion": "Mono Verzió", "MonitorAlbumExistingOnlyWarning": "Ez az egyes könyvek felügyelt beállításának egyszeri módosítása. A Szerző/Szerkesztés alatti lehetőség segítségével szabályozhatja, hogy mi történjen az újonnan hozzáadott könyvekkel", "MonitoringOptionsHelpText": "Mely könyveket érdemes figyelni a szerző hozzáadása után (egyszeri módosítás)", - "MonitorNewItemsHelpText": "Milyen új könyveket kell figyelni" + "MonitorNewItemsHelpText": "Milyen új könyveket kell figyelni", + "Activity": "Aktivitás", + "Add": "Hozzáadás", + "AddDelayProfile": "Késleltetési profil hozzáadása", + "Added": "Hozzáadva", + "AddImportListExclusion": "Lista kizárások importálása", + "AddIndexer": "Indexer hozzáadása", + "AddList": "Lista hozzáadása", + "AddMetadataProfile": "Metaadat-profil", + "AddNew": "Új hozzáadása", + "AddQualityProfile": "Minőségi profil hozzáadása", + "AddRemotePathMapping": "Adj Meg Egy Távoli Elérési Útvonalat", + "AddRootFolder": "Gyökérmappa hozzáadása", + "AfterManualRefresh": "A kézi frissítés után", + "Age": "Kora", + "Albums": "Albumok", + "All": "Összes", + "AllFiles": "Összes fájl", + "AllMonitoringOptionHelpText": "Az importálási listán lévő szerzők összes könyvének monitorozása", + "AllResultsFiltered": "Az alkalmazott szűrők miatt, az összes keresési eredmény rejtve marad", + "ApplicationURL": "Alkalmazás URL-je", + "ApplicationUrlHelpText": "Az alkalmazás külső URL-címe, beleértve a \"http(s)://\"-t, a \"Portot\" és az \"URL-alapot\" is", + "Apply": "Alkalmazás", + "AudioInfo": "Hang Infó", + "BeforeUpdate": "Alkalmazásfrissítés előtt", + "Close": "Bezárás", + "Connect": "Csatlakozás", + "Custom": "Egyedi", + "CustomFilters": "Egyéni Szűrők", + "Date": "Dátum", + "DefaultDelayProfileHelpText": "Ez az alapértelmezett profil. Minden filmre vonatkozik, amelynek nincs más profilja.", + "Deleted": "Törölve", + "Details": "Részletek", + "Donations": "Adományok", + "DoNotPrefer": "Nem preferált", + "DoNotUpgradeAutomatically": "Ne frissítsen automatikusan", + "DownloadFailed": "Letöltés Sikertelen", + "EditConnection": "Gyűjtemény módosítása", + "EditDelayProfile": "Késleltetési profil szerkesztése", + "EditImportListExclusion": "Az importlista kizárásásainak törlése", + "EditIndexer": "Indexer Szerkesztése", + "EditList": "Lista szerkesztése", + "EditQualityProfile": "Minőség profil szerkesztése", + "EditRemotePathMapping": "Távoli Elérési Útvonal Módosítása", + "EditRootFolder": "Gyökérmappa hozzáadása", + "ErrorRestoringBackup": "Hiba a mentés visszaállítása közben", + "Events": "Események", + "EventType": "Események Típusa", + "Filters": "Szűrők", + "FreeSpace": "Szabad Tárhely", + "General": "Általános", + "Genres": "Műfajok", + "Grabbed": "Megfogva", + "HardlinkCopyFiles": "Hardlinkelés/Fájl(ok) Másolása", + "HideAdvanced": "Haladó Elrejtése", + "Ignored": "Ignorált", + "Import": "Importálás", + "IndexerDownloadClientHelpText": "Adja meg, hogy melyik letöltési kliens használja az indexelőből történő megfogásokat", + "IndexerTagHelpText": "Csak olyan filmekhez használja ezt az indexelőt, amelyek legalább egy megfelelő címkével rendelkeznek. Hagyja üresen az összes filmhez való használathoz.", + "Info": "Infó", + "InstanceName": "Példány Neve", + "InstanceNameHelpText": "Példánynév a böngésző lapon és a syslog alkalmazás neve", + "InteractiveImport": "Interaktív Import", + "LastAlbum": "Legújabb album", + "LastDuration": "Utolsó időtartam", + "LastExecution": "Utolsó végrehajtás", + "LastUsed": "Utoljára használt", + "LastWriteTime": "Utolsó írási idő", + "Library": "Könyvtár", + "Location": "Lokáció", + "Manual": "Manuális", + "MassEditor": "Tömeges szerkesztő", + "MediaManagement": "Média Kezelés", + "Metadata": "Metaadat(ok)", + "MonitoredOnly": "Csak a Megfigyelt", + "MoveAutomatically": "Automatikus Áthelyezés", + "MoveFiles": "Fájl(ok) mozgatása", + "Never": "Soha", + "NextExecution": "Következő végrehajtás", + "NoResults": "Nem eredményezett találatot", + "NoTagsHaveBeenAddedYet": "Még nem adtál hozzá címkéket", + "Ok": "Ok", + "OnlyTorrent": "Csak torrent", + "OnlyUsenet": "Csak usenet", + "Organize": "Rendezés", + "OutputPath": "Kimeneti út", + "PreferAndUpgrade": "Preferálás és frissítés", + "PreferredProtocol": "Preferált protokoll", + "Presets": "Előbeállítások", + "Progress": "Folyamat", + "QualityLimitsHelpText": "A korlátozások automatikusan beállítódnak a film hossza szerint.", + "Queued": "Sorba helyezve", + "Rating": "Értékelés", + "RejectionCount": "Elutasítások száma", + "ReleaseTitle": "Kiadás címe", + "Renamed": "Átnevezve", + "Replace": "Kicserél", + "RestartRequiredHelpTextWarning": "Újraindítás szükséges a hatálybalépéshez", + "RestoreBackupAdditionalInfo": "Megjegyzés: A radarr automatikusan újraindítja és újratölti a felületet a visszaállítási folyamatban.", + "Save": "Mentés", + "SecificMonitoringOptionHelpText": "Monitorozza a szerzőket, de csak a listán kifejezetten szereplő könyveket", + "Seeders": "Seederek", + "Select...": "Válassza...", + "SelectFolder": "Mappa kiválasztása", + "SelectQuality": "Válasszon minőséget", + "ShouldMonitorExistingHelpText": "A listán szereplő azon könyvek automatikus figyelése, amelyek már szerepelnek a Readarrban", + "ShouldSearch": "Új elemek keresése", + "ShouldSearchHelpText": "Az indexelők keresése az újonnan hozzáadott elemek után. Óvatosan használja a nagy listákat.", + "ShowAdvanced": "Haladó nézet", + "SizeLimit": "Méretkorlát", + "SizeOnDisk": "Méret a lemezen", + "SomeResultsFiltered": "Néhány találat nem látható az alkalmazott szűrők miatt", + "SourceTitle": "Forrás címe", + "Started": "Elkezdődött", + "System": "Rendszer", + "Test": "Teszt", + "TimeLeft": "Hátralévő idő", + "Title": "Cím", + "TotalSpace": "Összes szabad hely", + "UnableToLoadInteractiveSerach": "Nem lehetséges betölteni a film keresés eredményeit. Próbálja meg később", + "UnmappedFilesOnly": "Kizárólag fel nem térképezett fájlokat", + "UnmonitoredOnly": "Csak a Megfigyelt", + "UpgradesAllowed": "Frissítések Engedélyezve", + "Wanted": "Keresett", + "Warn": "Figyelmeztet", + "WouldYouLikeToRestoreBackup": "Szeretnéd visszaállítani a {0} biztonsági mentést?", + "WriteMetadataTags": "Írjon metaadat-címkéket", + "Peers": "Peerek", + "UI": "Felület", + "Always": "Mindig", + "Backup": "Biztonsági Mentés", + "Error": "Hiba", + "AddConnection": "Gyűjtemény módosítása", + "EditMetadataProfile": "Metaadat-profil" } diff --git a/src/NzbDrone.Core/Localization/Core/is.json b/src/NzbDrone.Core/Localization/Core/is.json index ed131ef5e..a1f5e5797 100644 --- a/src/NzbDrone.Core/Localization/Core/is.json +++ b/src/NzbDrone.Core/Localization/Core/is.json @@ -473,5 +473,113 @@ "Tracks": "Spor", "NETCore": ".NET algerlega", "Docker": "Docker", - "MonoVersion": "Mónóútgáfa" + "MonoVersion": "Mónóútgáfa", + "Activity": "Virkni", + "Add": "Bæta við", + "AddDelayProfile": "Bæta við töf prófíl", + "Added": "Bætt við", + "AddIndexer": "Bættu við Indexer", + "AddList": "Bæta við lista", + "AddNew": "Bæta við nýju", + "AddQualityProfile": "Bæta við gæðaprófíl", + "AddRemotePathMapping": "Bæta við Remote Path Mapping", + "AddRootFolder": "Bæta við rótarmöppu", + "AfterManualRefresh": "Eftir handvirka endurnýjun", + "Age": "Aldur", + "All": "Allt", + "AllFiles": "Allar skrár", + "AllResultsFiltered": "Allar niðurstöður eru faldar af beittu síunni", + "Apply": "Sækja um", + "AudioInfo": "Hljóðupplýsingar", + "BeforeUpdate": "Fyrir uppfærslu", + "Close": "Lokaðu", + "Connect": "Tengjast", + "Custom": "Sérsniðin", + "CustomFilters": "Sérsniðin síur", + "Date": "Dagsetning", + "DefaultDelayProfileHelpText": "Þetta er sjálfgefið snið. Það á við um allar kvikmyndir sem hafa ekki skýran prófíl.", + "Deleted": "Eytt", + "Donations": "Framlög", + "DoNotPrefer": "Ekki frekar", + "DoNotUpgradeAutomatically": "Ekki uppfæra sjálfkrafa", + "DownloadFailed": "Niðurhal mistókst", + "EditDelayProfile": "Breyta seinkunarprófíl", + "EditImportListExclusion": "Eyða útilokun innflutningslista", + "EditIndexer": "Breyttu Indexer", + "EditQualityProfile": "Breyttu gæðaprófíl", + "EditRemotePathMapping": "Breyta kortlagningu fjarstígs", + "EditRootFolder": "Bæta við rótarmöppu", + "Error": "Villa", + "ErrorRestoringBackup": "Villa við að endurheimta afrit", + "Events": "Viðburðir", + "EventType": "Viðburðargerð", + "Filters": "Sía", + "FreeSpace": "Laust pláss", + "General": "Almennt", + "Genres": "Tegundir", + "Grabbed": "Greip", + "HardlinkCopyFiles": "Hardlink / afrita skrár", + "HideAdvanced": "Fela lengra komna", + "Ignored": "Hunsað", + "Import": "Flytja inn", + "LastExecution": "Síðasta aftaka", + "LastUsed": "Síðast notað", + "LastWriteTime": "Síðasti skrifatími", + "Location": "Staðsetning", + "Manual": "Handbók", + "MediaManagement": "Fjölmiðlastjórnun", + "Metadata": "Lýsigögn", + "MonitoredOnly": "Aðeins fylgst með", + "MoveAutomatically": "Fljótur innflutningur", + "MoveFiles": "Færa skrár", + "NextExecution": "Næsta framkvæmd", + "NoResults": "Engar niðurstöður fundust", + "NoTagsHaveBeenAddedYet": "Engum merkjum hefur verið bætt við ennþá", + "OnlyTorrent": "Aðeins Torrent", + "OnlyUsenet": "Aðeins Usenet", + "Organize": "Skipuleggðu", + "OutputPath": "Framleiðsla", + "Peers": "Jafningjar", + "PreferAndUpgrade": "Kjósa og uppfæra", + "Rating": "Einkunnir", + "RejectionCount": "Höfnunartalning", + "ReleaseTitle": "Slepptu titli", + "Renamed": "Endurnefnt", + "Replace": "Skipta um", + "RestartRequiredHelpTextWarning": "Krefst endurræsingar til að taka gildi", + "RestoreBackupAdditionalInfo": "Athugið: Radarr mun sjálfkrafa endurræsa og endurhlaða notendaviðmiðið meðan á endurreisnarferlinu stendur.", + "Save": "Vista", + "Seeders": "Plöntur", + "Select...": "'Veldu ...", + "SelectFolder": "Veldu Mappa", + "SelectQuality": "Veldu Gæði", + "ShowAdvanced": "Sýna lengra komna", + "SizeOnDisk": "Stærð á diski", + "SomeResultsFiltered": "Sumar niðurstöður eru faldar af beittu síunni", + "SourceTitle": "Heimildartitill", + "System": "Kerfi", + "Test": "Próf", + "Title": "Titill", + "TotalSpace": "Heildarrými", + "UI": "HÍ", + "UnableToLoadInteractiveSerach": "Ekki er hægt að hlaða niðurstöðum fyrir þessa kvikmyndaleit. Reyndu aftur seinna", + "UnmappedFilesOnly": "Aðeins ókortlagðar skrár", + "UnmonitoredOnly": "Aðeins fylgst með", + "UpgradesAllowed": "Uppfærsla leyfð", + "Wanted": "Óskað", + "Warn": "Vara við", + "WouldYouLikeToRestoreBackup": "Viltu endurheimta öryggisafritið {0}?", + "Ok": "Allt í lagi", + "Progress": "Framsókn", + "Always": "Alltaf", + "Backup": "Afritun", + "Details": "Upplýsingar", + "Info": "Upplýsingar", + "InteractiveImport": "Gagnvirkur innflutningur", + "LastDuration": "lastDuration", + "Presets": "Forstillingar", + "QualityLimitsHelpText": "Takmörkun er sjálfkrafa leiðrétt fyrir keyrslutíma kvikmyndarinnar.", + "Queued": "Í biðröð", + "TimeLeft": "Tími eftir", + "AddImportListExclusion": "Eyða útilokun innflutningslista" } diff --git a/src/NzbDrone.Core/Localization/Core/it.json b/src/NzbDrone.Core/Localization/Core/it.json index 74213ea76..47cb713d4 100644 --- a/src/NzbDrone.Core/Localization/Core/it.json +++ b/src/NzbDrone.Core/Localization/Core/it.json @@ -519,5 +519,121 @@ "MetadataProfiles": "profilo metadati", "Term": "Termine", "TrackFileCounttotalTrackCountTracksDownloadedInterp": "{0}/{1} libri scaricati", - "TrackFileCountTrackCountTotalTotalTrackCountInterp": "{0} / {1} (Totale: {2})" + "TrackFileCountTrackCountTotalTotalTrackCountInterp": "{0} / {1} (Totale: {2})", + "Activity": "Attività", + "AddDelayProfile": "Aggiungi Profilo di Ritardo", + "Added": "Aggiunto", + "AddIndexer": "Aggiungi Indicizzatore", + "AddList": "Aggiungi Lista", + "AddMetadataProfile": "profilo metadati", + "AddNew": "Aggiungi Nuovo", + "AddQualityProfile": "Aggiungi Profilo Qualità", + "AddRemotePathMapping": "Aggiungi Mappatura di un Percorso Remoto", + "AddRootFolder": "Aggiungi Cartella Radice", + "AfterManualRefresh": "Dopo l'aggiornamento manuale", + "Age": "Età", + "Albums": "Album", + "All": "Tutti", + "AllFiles": "Tutti i File", + "AllResultsFiltered": "Tutti i risultati sono nascosti dal filtro applicato", + "Always": "Sempre", + "Apply": "Applica", + "AudioInfo": "Informazioni Audio", + "Backup": "Backup", + "BeforeUpdate": "Prima di aggiornare", + "Close": "Chiudi", + "Connect": "Collega", + "Custom": "Personalizzato", + "CustomFilters": "Filtri Personalizzati", + "Date": "Data", + "DefaultDelayProfileHelpText": "Questo è il profilo predefinito. Si applica a tutti i film che non hanno un profilo esplicito.", + "Deleted": "Cancellato", + "Details": "Dettagli", + "DoNotPrefer": "Non Preferire", + "DoNotUpgradeAutomatically": "Non Aggiornare Automaticamente", + "DownloadFailed": "Download fallito", + "EditDelayProfile": "Modifica Profilo di Ritardo", + "EditImportListExclusion": "Cancellare la lista delle esclusioni", + "EditIndexer": "Modifica Indicizzatore", + "EditQualityProfile": "Modifica Profilo Qualità", + "EditRemotePathMapping": "Modifica la Mappatura dei Percorsi Remoti", + "EditRootFolder": "Aggiungi Cartella Radice", + "Error": "Errore", + "ErrorRestoringBackup": "Errore durante il ripristino del backup", + "Events": "Eventi", + "EventType": "Tipo di Evento", + "Filters": "Filtri", + "FreeSpace": "Spazio Libero", + "General": "Generale", + "Genres": "Generi", + "Grabbed": "Preso", + "HardlinkCopyFiles": "Hardlink/Copia Files", + "HideAdvanced": "Nascondi Avanzate", + "Ignored": "Ignorato", + "Import": "Importa", + "IndexerTagHelpText": "Usa questo indicizzatore per i film con almeno un tag corrispondente. Lascia in bianco per usarlo con tutti i film.", + "InstanceNameHelpText": "Nome dell'istanza nella scheda e per il nome dell'applicazione Syslog", + "LastDuration": "Ultima Durata", + "LastExecution": "Ultima esecuzione", + "LastUsed": "Ultimo uso", + "LastWriteTime": "Orario di Ultima Scrittura", + "Library": "Libreria", + "Location": "Posizione", + "Manual": "Manuale", + "MassEditor": "Editor di Massa", + "MediaManagement": "Gestione Media", + "Metadata": "Metadata", + "MonitoredOnly": "Solo Seguiti", + "MoveAutomatically": "Sposta automaticamente", + "MoveFiles": "Sposta Files", + "NextExecution": "Prossima esecuzione", + "NoResults": "nessun risultato trovato", + "NoTagsHaveBeenAddedYet": "Nessuna etichetta è ancora stata aggiunta", + "Ok": "Ok", + "OnlyTorrent": "Solo Torrent", + "OnlyUsenet": "Solo Usenet", + "Organize": "Organizza", + "OutputPath": "Percorso di Destinazione", + "PreferAndUpgrade": "Preferisci e aggiorna", + "Progress": "Avanzamento", + "QualityLimitsHelpText": "I limiti vengono regolati automaticamente per la durata del film.", + "Queued": "Messo in coda", + "Rating": "Valutazioni", + "RejectionCount": "Rifiuta il conteggio", + "ReleaseTitle": "Titolo Release", + "Renamed": "Rinominato", + "Replace": "Sostituire", + "RestartRequiredHelpTextWarning": "Richiede il riavvio per avere effetti", + "RestoreBackupAdditionalInfo": "Nota: Radarr si riavvierà automaticamente e ricaricherà l'interfaccia utente durante il processo di ripristino.", + "Save": "Salva", + "Seeders": "Seeders", + "Select...": "'Selezionare...", + "SelectFolder": "Seleziona cartella", + "SelectQuality": "Seleziona qualità", + "ShowAdvanced": "Mostra Avanzate", + "SizeOnDisk": "Dimensione su Disco", + "SomeResultsFiltered": "Tutti i risultati sono nascosti dai filtri applicati", + "SourceTitle": "Titolo Sorgente", + "System": "Sistema", + "Test": "Test", + "TimeLeft": "Tempo Rimanente", + "Title": "Titolo", + "TotalSpace": "Spazio Totale", + "UnableToLoadInteractiveSerach": "Impossibile caricare i risultati per questa ricerca di film. Riprovare più tardi", + "UnmonitoredOnly": "Solo Seguiti", + "UpgradesAllowed": "Aggiornamenti consentiti", + "Wanted": "Ricercato", + "Warn": "Attenzione", + "WouldYouLikeToRestoreBackup": "Ripristinare il backup {0}?", + "Add": "Aggiungi", + "UnmappedFilesOnly": "Solo file non mappati", + "Donations": "Donazioni", + "Peers": "Peers", + "UI": "Interfaccia", + "Info": "Info", + "InstanceName": "Nome Istanza", + "InteractiveImport": "Importazione interattiva", + "Presets": "Preset", + "AddImportListExclusion": "Cancellare la lista delle esclusioni", + "EditMetadataProfile": "profilo metadati" } diff --git a/src/NzbDrone.Core/Localization/Core/ja.json b/src/NzbDrone.Core/Localization/Core/ja.json index 4aba3f0ad..1844cdf6d 100644 --- a/src/NzbDrone.Core/Localization/Core/ja.json +++ b/src/NzbDrone.Core/Localization/Core/ja.json @@ -473,5 +473,113 @@ "OnUpgrade": "アップグレード時", "Tracks": "痕跡", "NETCore": ".NET Core", - "MonoVersion": "モノバージョン" + "MonoVersion": "モノバージョン", + "Activity": "アクティビティ", + "Add": "追加", + "AddDelayProfile": "遅延プロファイルの追加", + "AddIndexer": "インデクサーを追加", + "AddList": "リストを追加", + "AddNew": "新しく追加する", + "AddQualityProfile": "品質プロファイルを追加する", + "AddRemotePathMapping": "リモートパスマッピングを追加する", + "AddRootFolder": "ルートフォルダを追加する", + "AfterManualRefresh": "手動更新後", + "Age": "年齢", + "All": "すべて", + "AllFiles": "すべてのファイル", + "AllResultsFiltered": "すべての結果は、適用されたフィルターによって非表示になります", + "Always": "常に", + "Apply": "適用する", + "AudioInfo": "オーディオ情報", + "Backup": "バックアップ", + "BeforeUpdate": "更新前", + "Close": "閉じる", + "Connect": "接続する", + "Custom": "カスタム", + "CustomFilters": "カスタムフィルター", + "Date": "日付", + "DefaultDelayProfileHelpText": "これはデフォルトのプロファイルです。これは、明示的なプロファイルを持たないすべての映画に適用されます。", + "Deleted": "削除", + "Details": "詳細", + "Donations": "寄付", + "DoNotPrefer": "好まない", + "DoNotUpgradeAutomatically": "自動的にアップグレードしない", + "DownloadFailed": "ダウンロードに失敗しました", + "EditDelayProfile": "遅延プロファイルの編集", + "EditImportListExclusion": "インポートリストの除外を削除する", + "EditIndexer": "インデクサーの編集", + "EditQualityProfile": "品質プロファイルの編集", + "EditRemotePathMapping": "リモートパスマッピングの編集", + "EditRootFolder": "ルートフォルダを追加する", + "Error": "エラー", + "ErrorRestoringBackup": "バックアップの復元中にエラーが発生しました", + "Events": "イベント", + "EventType": "イベントタイプ", + "Filters": "フィルタ", + "FreeSpace": "フリースペース", + "General": "一般", + "Genres": "ジャンル", + "Grabbed": "掴んだ", + "HardlinkCopyFiles": "ファイルのハードリンク/コピー", + "HideAdvanced": "高度な非表示", + "Ignored": "無視されます", + "Import": "インポート", + "InteractiveImport": "インタラクティブインポート", + "LastDuration": "lastDuration", + "LastExecution": "最後の実行", + "LastUsed": "最後に使用した", + "LastWriteTime": "最終書き込み時間", + "Location": "ロケーション", + "Manual": "マニュアル", + "MediaManagement": "メディア管理", + "Metadata": "メタデータ", + "MonitoredOnly": "監視のみ", + "MoveAutomatically": "クイックインポート", + "MoveFiles": "ファイルの移動", + "NextExecution": "次の実行", + "NoResults": "結果が見つかりません", + "OnlyTorrent": "トレントのみ", + "OnlyUsenet": "Usenetのみ", + "Organize": "整理する", + "OutputPath": "出力パス", + "Peers": "ピア", + "PreferAndUpgrade": "優先してアップグレードする", + "Presets": "プリセット", + "Progress": "進捗", + "QualityLimitsHelpText": "制限は、ムービーの実行時間に合わせて自動的に調整されます。", + "Rating": "評価", + "RejectionCount": "拒否数", + "ReleaseTitle": "リリースタイトル", + "Renamed": "名前が変更されました", + "Replace": "交換", + "RestartRequiredHelpTextWarning": "有効にするには再起動が必要です", + "RestoreBackupAdditionalInfo": "注:Radarrは、復元プロセス中にUIを自動的に再起動して再読み込みします。", + "Save": "保存する", + "Seeders": "シーダー", + "Select...": "'選択する...", + "SelectFolder": "フォルダーを選択", + "SelectQuality": "品質を選択", + "ShowAdvanced": "高度な表示", + "SizeOnDisk": "ディスク上のサイズ", + "SomeResultsFiltered": "一部の結果は、適用されたフィルターによって非表示になります", + "SourceTitle": "ソースタイトル", + "Test": "テスト", + "TimeLeft": "残り時間", + "Title": "題名", + "UI": "UI", + "UnableToLoadInteractiveSerach": "この映画検索の結果を読み込めません。あとでもう一度試してみてください", + "UnmappedFilesOnly": "マップされていないファイルのみ", + "UnmonitoredOnly": "監視のみ", + "UpgradesAllowed": "許可されるアップグレード", + "Wanted": "ウォンテッド", + "Warn": "警告", + "WouldYouLikeToRestoreBackup": "バックアップ{0}を復元しますか?", + "Queued": "キューに入れられました", + "TotalSpace": "総スペース", + "Added": "追加", + "NoTagsHaveBeenAddedYet": "タグはまだ追加されていません", + "Ok": "OK", + "System": "システム", + "Info": "情報", + "AddImportListExclusion": "インポートリストの除外を削除する" } diff --git a/src/NzbDrone.Core/Localization/Core/ko.json b/src/NzbDrone.Core/Localization/Core/ko.json index 1e99cb6cf..2b5867f7f 100644 --- a/src/NzbDrone.Core/Localization/Core/ko.json +++ b/src/NzbDrone.Core/Localization/Core/ko.json @@ -472,5 +472,114 @@ "HostHelpText": "원격 다운로드 클라이언트에 지정한 것과 동일한 호스트", "ReleaseStatuses": "출시 상태", "RescanAfterRefreshHelpText": "영화를 새로 고친 후 영화 폴더를 다시 스캔하십시오.", - "MonoVersion": "모노 버전" + "MonoVersion": "모노 버전", + "Activity": "활동", + "Add": "추가", + "AddDelayProfile": "지연 프로필 추가", + "Added": "추가됨", + "AddIndexer": "인덱서 추가", + "AddList": "목록 추가", + "AddNew": "새로 추가", + "AddQualityProfile": "품질 프로필 추가", + "AddRemotePathMapping": "원격 경로 매핑 추가", + "AddRootFolder": "루트 폴더 추가", + "AfterManualRefresh": "수동 새로 고침 후", + "Age": "연령", + "All": "모두", + "AllFiles": "모든 파일", + "AllResultsFiltered": "적용된 필터에 의해 모든 결과가 숨겨집니다.", + "Apply": "적용", + "AudioInfo": "오디오 정보", + "Backup": "백업", + "BeforeUpdate": "업데이트 전", + "Close": "닫기", + "Connect": "연결", + "Custom": "사용자 지정", + "CustomFilters": "사용자 지정 필터", + "Date": "날짜", + "DefaultDelayProfileHelpText": "이것이 기본 프로필입니다. 명시적인 프로필이 없는 모든 영화에 적용됩니다.", + "Deleted": "삭제됨", + "Donations": "기부", + "DoNotPrefer": "선호하지 않음", + "DoNotUpgradeAutomatically": "자동 업그레이드 안함", + "EditDelayProfile": "지연 프로필 편집", + "EditImportListExclusion": "가져오기 목록 제외 삭제", + "EditIndexer": "인덱서 편집", + "EditQualityProfile": "품질 프로필 편집", + "EditRemotePathMapping": "원격 경로 매핑 편집", + "EditRootFolder": "루트 폴더 추가", + "Error": "오류", + "ErrorRestoringBackup": "백업 복원 오류", + "Events": "이벤트", + "EventType": "이벤트 유형", + "Filters": "필터", + "FreeSpace": "여유 공간", + "General": "일반", + "Genres": "장르", + "Grabbed": "잡았다", + "HardlinkCopyFiles": "하드 링크 / 복사 파일", + "HideAdvanced": "고급 숨기기", + "Ignored": "무시 됨", + "Import": "가져오기", + "Info": "정보", + "InteractiveImport": "대화형 가져오기", + "LastDuration": "lastDuration", + "LastExecution": "마지막 실행", + "LastUsed": "마지막 사용", + "LastWriteTime": "마지막 쓰기 시간", + "Location": "위치", + "Manual": "설명서", + "MediaManagement": "미디어 관리", + "Metadata": "메타 데이터", + "MonitoredOnly": "모니터링 만", + "MoveAutomatically": "빠른 가져오기", + "MoveFiles": "파일 이동", + "NextExecution": "다음 실행", + "Ok": "확인", + "OnlyTorrent": "토렌트 만", + "OnlyUsenet": "유즈넷 만", + "Organize": "구성", + "OutputPath": "출력 경로", + "Peers": "동료", + "PreferAndUpgrade": "선호 및 업그레이드", + "Presets": "사전 설정", + "Progress": "진행", + "QualityLimitsHelpText": "동영상 런타임에 대한 제한이 자동으로 조정됩니다.", + "Queued": "대기 중", + "Rating": "등급", + "RejectionCount": "거부 횟수", + "ReleaseTitle": "릴리스 제목", + "Replace": "바꾸다", + "RestartRequiredHelpTextWarning": "적용하려면 다시 시작해야합니다.", + "RestoreBackupAdditionalInfo": "참고 : Whisparr는 복원 프로세스 중에 UI를 자동으로 다시 시작하고 다시로드합니다.", + "Save": "저장", + "Seeders": "시더", + "Select...": "'선택...", + "SelectFolder": "폴더 선택", + "SelectQuality": "품질 선택", + "ShowAdvanced": "고급보기", + "SizeOnDisk": "디스크 크기", + "SomeResultsFiltered": "적용된 필터에 의해 일부 결과가 숨겨집니다.", + "SourceTitle": "소스 제목", + "System": "체계", + "Test": "테스트", + "TimeLeft": "Timeleft", + "Title": "표제", + "TotalSpace": "총 공간", + "UnableToLoadInteractiveSerach": "이 영화 검색 결과를 불러올 수 없습니다. 나중에 다시 시도", + "UnmappedFilesOnly": "매핑되지 않은 파일 만", + "UnmonitoredOnly": "모니터링 만", + "UpgradesAllowed": "허용되는 업그레이드", + "Wanted": "구함", + "Warn": "경고", + "WouldYouLikeToRestoreBackup": "{0} 백업을 복원 하시겠습니까?", + "Renamed": "이름이 변경됨", + "UI": "UI", + "Always": "항상", + "DownloadFailed": "다운로드 실패함", + "AddImportListExclusion": "가져오기 목록 제외 삭제", + "Details": "세부 정보", + "ImportListExclusions": "가져오기 목록 제외 삭제", + "NoResults": "검색 결과가 없습니다", + "NoTagsHaveBeenAddedYet": "아직 추가 된 태그가 없습니다." } diff --git a/src/NzbDrone.Core/Localization/Core/nb_NO.json b/src/NzbDrone.Core/Localization/Core/nb_NO.json index b579a589f..872bd771c 100644 --- a/src/NzbDrone.Core/Localization/Core/nb_NO.json +++ b/src/NzbDrone.Core/Localization/Core/nb_NO.json @@ -120,5 +120,49 @@ "Reload": "Likemenn", "RemotePathMappings": "Ekstern portmapping", "Remove": "Slett", - "Term": "Periode" + "Term": "Periode", + "Activity": "Aktivitet", + "AddDelayProfile": "Legg til forsinkelsesprofil", + "Added": "La til", + "AddIndexer": "Legg til indekser", + "AddList": "Legg til liste", + "AddMetadataProfile": "metadataprofil", + "AddNew": "Legg til ny", + "AddQualityProfile": "Legg til kvalitetsprofil", + "AddRemotePathMapping": "Legg til ekstern kartlegging", + "AddRootFolder": "Legg til rotmappe", + "AfterManualRefresh": "Etter manuell oppdatering", + "Age": "Alder", + "All": "Alle", + "AllFiles": "Alle filer", + "AllResultsFiltered": "Alle resultatene er skjult av det anvendte filteret", + "Always": "Alltid", + "Apply": "Bruk", + "AudioInfo": "Audio Info", + "Backup": "Sikkerhetskopiering", + "BeforeUpdate": "Før oppdatering", + "Close": "Lukk", + "Connect": "Koble til", + "Custom": "Tilpass", + "Deleted": "Slett", + "Details": "detaljer", + "EditQualityProfile": "Legg til kvalitetsprofil", + "EditRemotePathMapping": "Legg til ekstern kartlegging", + "EditRootFolder": "Legg til rotmappe", + "Events": "Hendelse", + "Filters": "Filtre", + "Info": "Info", + "Library": "Bibliotek", + "Metadata": "metadata", + "Rating": "Vurdering", + "Seeders": "Delere", + "SomeResultsFiltered": "Alle resultatene er skjult av det anvendte filteret", + "Title": "Tittel", + "UI": "Grensesnitt", + "Add": "Legge til", + "EditDelayProfile": "Legg til forsinkelsesprofil", + "Peers": "Likemenn", + "EditMetadataProfile": "metadataprofil", + "Queued": "Kø", + "Replace": "Erstatt" } diff --git a/src/NzbDrone.Core/Localization/Core/nl.json b/src/NzbDrone.Core/Localization/Core/nl.json index cfe1cf9a0..50b1105fc 100644 --- a/src/NzbDrone.Core/Localization/Core/nl.json +++ b/src/NzbDrone.Core/Localization/Core/nl.json @@ -484,5 +484,120 @@ "Tracks": "Spoor", "OnGrab": "Bij Ophalen", "MonoVersion": "Mono Versie", - "AddedArtistSettings": "Auteur instellingen toegevoegd" + "AddedArtistSettings": "Auteur instellingen toegevoegd", + "Activity": "Activiteit", + "Add": "Toevoegen", + "AddDelayProfile": "Voeg vertragingsprofiel toe", + "Added": "Toegevoegd", + "AddIndexer": "Voeg Indexeerder Toe", + "AddList": "Lijst Toevoegen", + "AddNew": "Toevoegen", + "AddQualityProfile": "Kwaliteitsprofiel toevoegen", + "AddRemotePathMapping": "Voeg Externe Pad Verwijzing Toe", + "AfterManualRefresh": "Na handmatig verversen", + "Age": "Leeftijd", + "All": "Alles", + "AllFiles": "Alle bestanden", + "Always": "Altijd", + "ApplicationURL": "Applicatie URL", + "ApplicationUrlHelpText": "De externe URL van deze applicatie inclusief http(s)://,Port en URL base", + "Apply": "Toepassen", + "AudioInfo": "Audio Info", + "Backup": "Veiligheidskopie", + "BeforeUpdate": "Voor de update", + "Close": "Sluit", + "Connect": "Connecties", + "Custom": "Aangepast", + "CustomFilters": "Aangepaste Filters", + "Date": "Datum", + "DefaultDelayProfileHelpText": "Dit is het standaard profiel. Het wordt toegepast op alle films die geen specifiek profiel hebben.", + "Donations": "Donaties", + "DoNotPrefer": "Niet De Voorkeur Geven", + "DoNotUpgradeAutomatically": "Niet Automatisch Upgraden", + "EditDelayProfile": "Bewerk Vertraginsprofiel", + "EditImportListExclusion": "Verwijder van Uitzonderingenlijst", + "EditIndexer": "Bewerk Indexeerder", + "EditQualityProfile": "Bewerk Kwaliteitsprofiel", + "EditRemotePathMapping": "Bewerk Externe Pad Verwijzing", + "EditRootFolder": "Voeg hoofdmap toe", + "Error": "Fout", + "ErrorRestoringBackup": "Fout bij het herstellen van de back-up", + "Events": "Gebeurtenissen", + "EventType": "Gebeurtenis Type", + "Filters": "Filter", + "FreeSpace": "Vrije Ruimte", + "General": "Algemeen", + "Genres": "Genres", + "Grabbed": "Opgehaalde", + "HardlinkCopyFiles": "Hardlink/Kopieer Bestanden", + "HideAdvanced": "Verberg Gevorderd", + "Ignored": "Genegeerde", + "Import": "Importeer", + "IndexerTagHelpText": "Gebruik deze indexer alleen voor films met ten minste één overeenkomende tag. Laat leeg om te gebruiken met alle films.", + "LastDuration": "Laatste Looptijd", + "LastExecution": "Laatste Uitvoering", + "LastUsed": "Laatst Gebruikt", + "LastWriteTime": "Laatste Modificatietijd", + "Library": "Bibliotheek", + "Location": "Locatie", + "Manual": "Manueel", + "MediaManagement": "Mediabeheer", + "Metadata": "Metadata", + "MonitoredOnly": "Bewaakt", + "MoveAutomatically": "Automatisch verplaatsen", + "MoveFiles": "Verplaats Bestanden", + "NextExecution": "Volgende uitvoering", + "NoResults": "Geen resultaten gevonden", + "NoTagsHaveBeenAddedYet": "Er zijn nog geen tags toegevoegd", + "Ok": "Ok", + "OnlyTorrent": "Alleen Torrent", + "OnlyUsenet": "Alleen Usenet", + "Organize": "Organiseer", + "OutputPath": "Uitvoer Pad", + "Peers": "Peers", + "PreferAndUpgrade": "Prefereer en Waardeer Op", + "Presets": "Voorinstellingen", + "QualityLimitsHelpText": "Limieten zijn automatisch aangepast voor de tijdsduur van de film.", + "Queued": "Afwachtend", + "Rating": "Waardering", + "RejectionCount": "Afwijzingsniveau", + "ReleaseTitle": "Uitgave Titel", + "Replace": "Vervang", + "RestartRequiredHelpTextWarning": "Herstarten vereist om in werking te treden", + "RestoreBackupAdditionalInfo": "Aantekening: Radarr zal automatisch de Ui herstarten en herladen gedurende het herstel proces.", + "Save": "Opslaan", + "Seeders": "Seeders", + "Select...": "'Selecteer...", + "SelectFolder": "Selecteer Map", + "SelectQuality": "Selecteer Kwaliteit", + "ShowAdvanced": "Toon Geavanceerd", + "SizeOnDisk": "Grootte op Schijf", + "SomeResultsFiltered": "Sommige resultaten zijn verborgen door de aangebrachte filter", + "SourceTitle": "Bron Titel", + "System": "Systeem", + "Test": "Test", + "TimeLeft": "Resterende Tijd", + "Title": "Titel", + "UnmappedFilesOnly": "Alleen niet-toegewezen bestanden", + "UnmonitoredOnly": "Bewaakt", + "UpgradesAllowed": "Upgrades toegestaan", + "Wanted": "Gezocht", + "Warn": "Waarschuwing", + "WouldYouLikeToRestoreBackup": "Wilt u de back-up {0} herstellen?", + "AddRootFolder": "Voeg hoofdmap toe", + "Info": "Info", + "Never": "Nooit", + "AllResultsFiltered": "Alle resultaten zijn verborgen door de toegepaste filter", + "Deleted": "Verwijderd", + "Details": "Details", + "DownloadFailed": "Download mislukt", + "Progress": "Voortgang", + "AddImportListExclusion": "Verwijder van Uitzonderingenlijst", + "IndexerDownloadClientHelpText": "Specificeer welke download cliënt wordt gebruikt voor deze indexer", + "InteractiveImport": "Interactieve Import", + "ImportListExclusions": "Verwijder van Uitzonderingenlijst", + "Renamed": "Hernoemd", + "TotalSpace": "Totale Ruimte", + "UI": "Gebruikersinterface", + "UnableToLoadInteractiveSerach": "Kan resultaten voor deze filmzoekopdracht niet laden. Probeer het later nogmaals" } diff --git a/src/NzbDrone.Core/Localization/Core/pl.json b/src/NzbDrone.Core/Localization/Core/pl.json index 29902582a..ffc587068 100644 --- a/src/NzbDrone.Core/Localization/Core/pl.json +++ b/src/NzbDrone.Core/Localization/Core/pl.json @@ -481,5 +481,125 @@ "OnApplicationUpdateHelpText": "Przy aktualizacji aplikacji", "Duration": "Czas trwania", "BlocklistHelpText": "Zapobiega ponownemu pobraniu tej wersji przez Lidarr", - "UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent podawany przez aplikację wywołującą API" + "UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent podawany przez aplikację wywołującą API", + "Add": "Dodaj", + "AddDelayProfile": "Dodaj profil opóźnienia", + "Added": "Dodane", + "AddIndexer": "Dodaj indeksator", + "AddList": "Dodaj listę", + "AddNew": "Dodaj nowy", + "AddQualityProfile": "Dodaj profil jakości", + "AddRemotePathMapping": "Dodaj zdalne mapowanie ścieżki", + "AddRootFolder": "Dodaj folder główny", + "AfterManualRefresh": "Po ręcznym odświeżeniu", + "Age": "Wiek", + "All": "Wszystkie", + "AllFiles": "Wszystkie pliki", + "AllResultsFiltered": "Wszystkie wyniki są ukrywane przez zastosowany filtr", + "Always": "Zawsze", + "ApplicationURL": "Link do aplikacji", + "ApplicationUrlHelpText": "Zewnętrzny URL tej aplikacji zawiera http(s)://, port i adres URL", + "Apply": "Zastosować", + "AudioInfo": "Informacje audio", + "Backup": "Kopia zapasowa", + "BeforeUpdate": "Przed aktualizacją", + "Close": "Blisko", + "DefaultDelayProfileHelpText": "To jest profil domyślny. Dotyczy to wszystkich filmów, które nie mają wyraźnego profilu.", + "Deleted": "Usunięto", + "Details": "Detale", + "Donations": "Darowizny", + "DoNotPrefer": "Nie preferuj", + "DoNotUpgradeAutomatically": "Nie aktualizuj automatycznie", + "DownloadFailed": "Pobieranie nie udane", + "EditConnection": "Edytuj kolekcję", + "EditDelayProfile": "Edytuj profil opóźnienia", + "EditImportListExclusion": "Usuń wykluczenie listy importu", + "EditIndexer": "Edytuj indeksator", + "EditQualityProfile": "Edytuj profil jakości", + "EditRemotePathMapping": "Edytuj zdalne mapowanie ścieżki", + "EditRootFolder": "Dodaj folder główny", + "Error": "Błąd", + "ErrorRestoringBackup": "Błąd podczas przywracania kopii zapasowej", + "Events": "Wydarzenia", + "EventType": "Typ wydarzenia", + "Filters": "Filtry", + "FreeSpace": "Wolna przestrzeń", + "Genres": "Gatunki", + "Grabbed": "Złapał", + "HardlinkCopyFiles": "Hardlink / Kopiuj pliki", + "HideAdvanced": "Ukryj zaawansowane", + "Ignored": "Zignorowano", + "Import": "Import", + "IndexerDownloadClientHelpText": "Określ, który klient pobiera będzie łapał filmy z tego indeksera", + "IndexerTagHelpText": "Korzystaj z tego indeksera wyłącznie w przypadku filmów z co najmniej jednym pasującym tagiem. Pozostaw pole puste, by używać do wszystkich filmów.", + "InstanceName": "Nazwa instancji", + "InstanceNameHelpText": "Nazwa instancji w zakładce i dla nazwy aplikacji Syslog", + "InteractiveImport": "Interaktywny import", + "LastDuration": "Ostatni czas trwania", + "LastExecution": "Ostatnia egzekucja", + "LastUsed": "Ostatnio używane", + "LastWriteTime": "Czas ostatniego zapisu", + "Location": "Lokalizacja", + "Manual": "podręcznik", + "MediaManagement": "Zarządzanie mediami", + "Metadata": "Metadane", + "MonitoredOnly": "Tylko monitorowane", + "MoveAutomatically": "Przenieś automatycznie", + "MoveFiles": "Przenieś pliki", + "Never": "Nigdy", + "NextExecution": "Następne wykonanie", + "NoResults": "Nie znaleziono wyników", + "NoTagsHaveBeenAddedYet": "Żadne tagi nie zostały jeszcze dodane", + "Ok": "Dobrze", + "OnlyTorrent": "Tylko Torrent", + "OnlyUsenet": "Tylko Usenet", + "Organize": "Zorganizować", + "Peers": "Rówieśnicy", + "PreferAndUpgrade": "Preferuj i aktualizuj", + "Presets": "Presety", + "Progress": "Postęp", + "QualityLimitsHelpText": "Limity są automatycznie dostosowywane do czasu odtwarzania filmu.", + "Queued": "W kolejce", + "Rating": "Ocena", + "RejectionCount": "Liczba odrzucenia", + "ReleaseTitle": "Tytuł wydania", + "Renamed": "Zmieniona nazwa", + "Replace": "Zastąpić", + "RestartRequiredHelpTextWarning": "Wymaga ponownego uruchomienia, aby odniosło skutek", + "RestoreBackupAdditionalInfo": "Uwaga: Radarr automatycznie uruchomi się ponownie i przeładuje interfejs użytkownika podczas procesu przywracania.", + "Save": "Zapisać", + "Seeders": "Siewniki", + "Select...": "Wybierz...", + "SelectFolder": "Wybierz katalog", + "SelectQuality": "Wybierz Jakość", + "ShowAdvanced": "Pokaż zaawansowane", + "SizeLimit": "Ograniczenie rozmiaru", + "SizeOnDisk": "Rozmiar dysku", + "SomeResultsFiltered": "Niektóre wyniki są ukrywane przez zastosowany filtr", + "SourceTitle": "Tytuł źródłowy", + "System": "System", + "Test": "Test", + "Title": "Tytuł", + "TotalSpace": "Powierzchnia całkowita", + "UI": "UI", + "UnableToLoadInteractiveSerach": "Nie można załadować wyników dla tego wyszukiwania filmów. Spróbuj ponownie później", + "UnmappedFilesOnly": "Tylko niezamapowane pliki", + "UnmonitoredOnly": "Tylko monitorowane", + "UpgradesAllowed": "Uaktualnienia dozwolone", + "Wanted": "Chciał", + "Warn": "Ostrzeż", + "WouldYouLikeToRestoreBackup": "Czy chcesz przywrócić kopię zapasową {0}?", + "ImportListExclusions": "Usuń wykluczenie listy importu", + "Connect": "Połączyć", + "Custom": "Zwyczaj", + "CustomFilters": "Filtry niestandardowe", + "Date": "Data", + "Info": "Informacje", + "OutputPath": "Ścieżka wyjściowa", + "Activity": "Aktywność", + "AddConnection": "Edytuj kolekcję", + "AddImportListExclusion": "Usuń wykluczenie listy importu", + "General": "Generał", + "Started": "Rozpoczęto", + "TimeLeft": "Pozostały czas" } diff --git a/src/NzbDrone.Core/Localization/Core/pt.json b/src/NzbDrone.Core/Localization/Core/pt.json index ef8602640..7573fb372 100644 --- a/src/NzbDrone.Core/Localization/Core/pt.json +++ b/src/NzbDrone.Core/Localization/Core/pt.json @@ -544,5 +544,136 @@ "WriteMetadataToAudioFiles": "Escrever metadados em ficheiros de áudio", "Year": "Ano", "BindAddressHelpText": "Endereço IPv4 válido ou \"*\" para todas as interfaces", - "MonoVersion": "Versão do Mono" + "MonoVersion": "Versão do Mono", + "OutputPath": "Caminho de saída", + "MetadataProfiles": "perfil de metadados", + "AddMissing": "Adicionar ausentes", + "Activity": "Atividade", + "Add": "Adicionar", + "AddDelayProfile": "Adicionar perfil de atraso", + "Added": "Adicionado", + "AddIndexer": "Adicionar indexador", + "AddList": "Adicionar lista", + "AddNew": "Adicionar novo", + "AddQualityProfile": "Adicionar perfil de qualidade", + "AddRemotePathMapping": "Adicionar mapeamento de caminho remoto", + "AddRootFolder": "Adicionar pasta raiz", + "AfterManualRefresh": "Após a atualização manual", + "Age": "Tempo de vida", + "AllFiles": "Todos os ficheiros", + "AllResultsFiltered": "Todos os resultados estão ocultos pelo filtro aplicado", + "Always": "Sempre", + "ApplicationURL": "URL da aplicação", + "ApplicationUrlHelpText": "O URL desta aplicação externa, incluindo http(s)://, porta e URL base", + "Apply": "Aplicar", + "AudioInfo": "Informações do áudio", + "Backup": "Cópia de segurança", + "BeforeUpdate": "Antes de atualizar", + "Close": "Fechar", + "Connect": "Conexões", + "Custom": "Personalizado", + "CustomFilters": "Filtros personalizados", + "DefaultDelayProfileHelpText": "Este é o perfil padrão. Isso se aplica a todos os filmes que não têm um perfil explícito.", + "Deleted": "Eliminado", + "Details": "Detalhes", + "Donations": "Doações", + "DoNotPrefer": "Não preferir", + "DoNotUpgradeAutomatically": "Não atualizar automaticamente", + "DownloadFailed": "Falha na transferência", + "EditConnection": "Editar Coleção", + "EditDelayProfile": "Editar perfil de atraso", + "EditImportListExclusion": "Eliminar exclusão da lista de importação", + "EditIndexer": "Editar indexador", + "EditQualityProfile": "Editar perfil de qualidade", + "EditRemotePathMapping": "Editar mapeamento de caminho remoto", + "EditRootFolder": "Adicionar pasta raiz", + "Error": "Erro", + "ErrorRestoringBackup": "Erro ao restaurar cópia de segurança", + "Events": "Eventos", + "EventType": "Tipo de evento", + "Filters": "Filtros", + "FreeSpace": "Espaço livre", + "General": "Geral", + "Genres": "Gêneros", + "Grabbed": "Capturado", + "HardlinkCopyFiles": "Realizar ligação fixa/copiar ficheiros", + "HideAdvanced": "Ocultar avançado", + "Ignored": "Ignorado", + "Import": "Importar", + "IndexerDownloadClientHelpText": "Especificar que cliente de transferências quer usar para as transferências deste indexador", + "IndexerTagHelpText": "Só use este indexador para filmes com pelo menos uma etiqueta correspondente. Deixe em branco para usar com todos os filmes.", + "Info": "Informações", + "InstanceName": "Nome da Instancia", + "InstanceNameHelpText": "Nome da instância na aba e nome da aplicação para Syslog", + "InteractiveImport": "Importação interativa", + "LastDuration": "Última Duração", + "LastExecution": "Execução mais recente", + "LastUsed": "Uso mais recente", + "LastWriteTime": "Hora da última escrita", + "Library": "Biblioteca", + "Location": "Localização", + "Manual": "Manual", + "MediaManagement": "Gestão de multimédia", + "Metadata": "Metadados", + "MoveFiles": "Mover ficheiros", + "Never": "Nunca", + "NextExecution": "Próxima execução", + "NoResults": "Nenhum resultado encontrado", + "NoTagsHaveBeenAddedYet": "Você ainda não adicionou etiquetas", + "Ok": "Ok", + "OnlyTorrent": "Apenas torrent", + "OnlyUsenet": "Apenas Usenet", + "Organize": "Organizar", + "Peers": "Elementos", + "PreferAndUpgrade": "Preferir e atualizar", + "Presets": "Pré-sintonizações", + "Progress": "Progresso", + "QualityLimitsHelpText": "Os limites são ajustados automaticamente para o tempo de execução do filme.", + "Queued": "Em fila", + "Rating": "Classificação", + "RejectionCount": "Número de rejeições", + "ReleaseTitle": "Título da versão", + "Renamed": "Renomeado", + "Replace": "Substituir", + "RestartRequiredHelpTextWarning": "Requer reinício para aplicar alterações", + "RestoreBackupAdditionalInfo": "Nota: o Radarr reiniciará e recarregará automaticamente a IU durante o processo de restauração.", + "Select...": "Selecione...", + "SelectFolder": "Selecionar pasta", + "SelectQuality": "Selecionar qualidade", + "ShouldSearch": "Pesquisar novos itens", + "ShouldSearchHelpText": "Pesquisar indexadores para novos itens adicionados. Utilize com cuidado para listas grandes.", + "ShowAdvanced": "Mostrar avançado", + "SizeLimit": "Tamanho Limite", + "SizeOnDisk": "Tamanho em disco", + "SomeResultsFiltered": "Alguns resultados estão ocultos pelo filtro aplicado", + "SourceTitle": "Título original", + "Started": "Começado", + "System": "Sistema", + "Test": "Testar", + "TimeLeft": "Tempo restante", + "Title": "Título", + "TotalSpace": "Espaço total", + "UI": "IU", + "UnableToLoadInteractiveSerach": "Não foi possível carregar os resultados para esta pesquisa de filme. Tenta novamente mais tarde", + "UnmappedFilesOnly": "Somente ficheiros não mapeados", + "UnmonitoredOnly": "Apenas monitorado", + "UpgradesAllowed": "Atualizações permitidas", + "Wanted": "Desejado", + "Warn": "Avisar", + "WouldYouLikeToRestoreBackup": "Deseja restaurar a cópia de segurança {0}?", + "MetadataProfile": "perfil de metadados", + "Term": "Termo", + "AddedArtistSettings": "Definições de Autor adicionadas", + "AddNewItem": "Adicionar novo item", + "All": "Todos", + "Date": "Data", + "MonitoredOnly": "Apenas monitorado", + "MoveAutomatically": "Mover automaticamente", + "Save": "Guardar", + "AddConnection": "Editar Coleção", + "AddImportListExclusion": "Eliminar exclusão da lista de importação", + "AddMetadataProfile": "perfil de metadados", + "ImportListExclusions": "Eliminar exclusão da lista de importação", + "EditMetadataProfile": "perfil de metadados", + "Seeders": "Semeadores" } diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index 165d10889..99fe19705 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -615,7 +615,6 @@ "DefaultMonitorOptionHelpText": "Opções de Monitoramento Padrão para álbuns de artistas detectados nesta pasta", "CollapseMultipleAlbums": "Agrupar Múltiplos Álbuns", "ContinuingMoreAlbumsAreExpected": "Mais álbuns são esperados", - "NoTagsHaveBeenAddedYetAddTagsToLinkArtistsWithDelayProfilesRestrictionsOrNotificationsClickLinkTohttpswikiservarrcomlidarrsettingstagshereLinkToFindOutMoreAboutTagsInLidarr": "Nenhuma etiqueta foi adicionada ainda. Adicione etiquetas para ligar artistas com perfis de atraso, restrições ou notificações. Clique aqui para encontrar mais a respeito de etiquetas no Lidarr.", "RemoveCompleted": "Remover Completos", "RemoveDownloadsAlert": "As configurações de remoção foram movidas para configurações individuais do cliente na tabela acima.", "RemoveFailed": "Falha na Remoção", @@ -680,7 +679,6 @@ "ExpandEPByDefaultHelpText": "EPs", "ExpandSingleByDefaultHelpText": "Singles", "ExtraFileExtensionsHelpTexts2": "\"Exemplos: \".sub", - "Sonarr": "Sonarr", "OnApplicationUpdateHelpText": "Ao Atualizar o Aplicativo", "OnApplicationUpdate": "Ao Atualizar o Aplicativo", "OnRename": "Ao Renomear", @@ -700,5 +698,138 @@ "MonitorAlbumExistingOnlyWarning": "Este é um ajuste único da configuração monitoramento para cada livro. Use a opção em Autor/Editar para controlar o que acontece com os livros recém-adicionados", "MonitoringOptionsHelpText": "Quais livros devem ser monitorados após o autor ser adicionado (ajuste único)", "MonitorNewItemsHelpText": "Quais novos livros devem ser monitorados", - "MonoVersion": "Versão do Mono" + "MonoVersion": "Versão do Mono", + "Add": "Adicionar", + "AddDelayProfile": "Adicionar perfil de atraso", + "Added": "Adicionado", + "AddImportListExclusion": "Exclusões da lista de importação", + "AddIndexer": "Adicionar indexador", + "AddList": "Adicionar à Lista", + "AddMetadataProfile": "Perfil de metadados", + "AddNew": "Adicionar novo", + "AddQualityProfile": "Adicionar perfil de qualidade", + "AddRemotePathMapping": "Adicionar mapeamento de caminho remoto", + "AddRootFolder": "Adicionar pasta raiz", + "AfterManualRefresh": "Após atualização manual", + "Age": "Tempo de vida", + "Albums": "Álbum", + "All": "Todos", + "AllFiles": "Todos os arquivos", + "AllMonitoringOptionHelpText": "Monitorar autores e todos os livros para cada autor incluído na lista de importação", + "AllResultsFiltered": "Todos os resultados estão ocultos pelo filtro aplicado", + "ApplicationURL": "URL da Aplicação", + "ApplicationUrlHelpText": "O URL externa deste aplicativo, incluindo http(s)://, porta e base de URL", + "Apply": "Aplicar", + "AudioInfo": "Informações do áudio", + "Backup": "Backup", + "BeforeUpdate": "Antes da atualização", + "Close": "Fechar", + "Connect": "Conectar", + "Custom": "Personalizado", + "CustomFilters": "Filtros personalizados", + "Date": "Data", + "DefaultDelayProfileHelpText": "Este é o perfil padrão. Ele se aplica a todos os filmes que não possuem um perfil explícito.", + "Deleted": "Excluído", + "Details": "Detalhes", + "Donations": "Doações", + "DoNotPrefer": "Não preferir", + "DoNotUpgradeAutomatically": "Não atualizar automaticamente", + "DownloadFailed": "Falha no download", + "EditConnection": "Editar Coleção", + "EditDelayProfile": "Editar perfil de atraso", + "EditImportListExclusion": "Remover exclusão da lista de importação", + "EditIndexer": "Editar indexador", + "EditList": "Editar Lista", + "EditQualityProfile": "Editar perfil de qualidade", + "EditRemotePathMapping": "Editar mapeamento de caminho remoto", + "EditRootFolder": "Adicionar pasta raiz", + "Error": "Erro", + "ErrorRestoringBackup": "Erro ao restaurar o backup", + "Events": "Eventos", + "EventType": "Tipo de evento", + "Filters": "Filtros", + "FreeSpace": "Espaço livre", + "General": "Geral", + "Genres": "Gêneros", + "Grabbed": "Obtido", + "HardlinkCopyFiles": "Vínculo real/Copiar arquivos", + "HideAdvanced": "Ocultar avançado", + "Ignored": "Ignorado", + "IndexerDownloadClientHelpText": "Especificar em que cliente de download é usado para baixar deste indexador", + "IndexerTagHelpText": "Só use este indexador para filmes com ao menos uma etiqueta correspondente. Deixe em branco para usar com todos os filmes.", + "InstanceName": "Nome da instância", + "InstanceNameHelpText": "Nome da instância na guia e para o nome do aplicativo Syslog", + "InteractiveImport": "Importação interativa", + "LastAlbum": "Último Álbum", + "LastDuration": "Última Duração", + "LastExecution": "Última Execução", + "LastUsed": "Uso mais recente", + "LastWriteTime": "Hora da última gravação", + "Library": "Biblioteca", + "Location": "Localização", + "Manual": "Manual", + "MassEditor": "Editor em Massa", + "MediaManagement": "Gerenciamento de Mídia", + "Metadata": "Metadados", + "MonitoredOnly": "Somente monitorado", + "MoveAutomatically": "Mover automaticamente", + "MoveFiles": "Mover arquivos", + "Never": "Nunca", + "NextExecution": "Próxima Execução", + "NoResults": "Nenhum resultado", + "NoTagsHaveBeenAddedYet": "Você ainda não adicionou tags", + "Ok": "Ok", + "OnlyTorrent": "Apenas torrents", + "OnlyUsenet": "Apenas Usenet", + "Organize": "Organizar", + "OutputPath": "Caminho de saída", + "Peers": "Pares", + "PreferAndUpgrade": "Preferir e atualizar", + "PreferredProtocol": "Protocolo Preferido", + "Presets": "Predefinições", + "Progress": "Progresso", + "QualityLimitsHelpText": "Os limites são ajustados automaticamente para o tempo de execução do filme.", + "Queued": "Enfileirado", + "Rating": "Avaliação", + "RejectionCount": "Número de rejeições", + "ReleaseTitle": "Título da versão", + "Renamed": "Renomeado", + "Replace": "Substituir", + "RestartRequiredHelpTextWarning": "Requer reinício para ter efeito", + "RestoreBackupAdditionalInfo": "Observação: o Radarr reiniciará automaticamente e recarregará a interface durante o processo de restauração.", + "Save": "Salvar", + "SecificMonitoringOptionHelpText": "Monitorar autores, mas só monitorar livros explicitamente incluídos na lista", + "Seeders": "Semeadores", + "Select...": "Selecionar...", + "SelectFolder": "Selecionar pasta", + "SelectQuality": "Selecionar qualidade", + "ShouldMonitorExistingHelpText": "Monitorar automaticamente os livros nesta lista que já estão no Readarr", + "ShouldSearch": "Pesquisar novos itens", + "ShouldSearchHelpText": "Pesquisar indexadores para novos itens adicionados. Tenha cuidado ao usar com listas grandes.", + "ShowAdvanced": "Mostrar avançado", + "SizeLimit": "Tamanho Limite", + "SizeOnDisk": "Tamanho em disco", + "SomeResultsFiltered": "Alguns resultados estão ocultos pelo filtro aplicado", + "SourceTitle": "Título da fonte", + "Started": "Iniciado", + "System": "Sistema", + "Test": "Testar", + "TimeLeft": "Tempo restante", + "Title": "Título", + "TotalSpace": "Espaço total", + "UI": "Interface", + "UnableToLoadInteractiveSerach": "Não foi possível carregar os resultados para esta pesquisa de filme. Tente novamente mais tarde", + "UnmappedFilesOnly": "Apenas arquivos não mapeados", + "UnmonitoredOnly": "Somente monitorado", + "UpgradesAllowed": "Atualizações permitidas", + "Wanted": "Desejado", + "Warn": "Avisar", + "WouldYouLikeToRestoreBackup": "Gostaria de restaurar o backup {0}?", + "WriteMetadataTags": "Salvar Etiquetas de Metadados", + "Import": "Importar", + "Activity": "Atividade", + "Always": "Sempre", + "Info": "Informações", + "AddConnection": "Editar Coleção", + "EditMetadataProfile": "Perfil de metadados" } diff --git a/src/NzbDrone.Core/Localization/Core/ro.json b/src/NzbDrone.Core/Localization/Core/ro.json index 24622edff..42c3120e6 100644 --- a/src/NzbDrone.Core/Localization/Core/ro.json +++ b/src/NzbDrone.Core/Localization/Core/ro.json @@ -474,5 +474,114 @@ "BindAddressHelpText": "Adresă IP4 validă sau „*” pentru toate interfețele", "CertificateValidationHelpText": "Modificați cât de strictă este validarea certificării HTTPS", "MonoVersion": "Versiune mono", - "Label": "Etichetă" + "Label": "Etichetă", + "Activity": "Activitate", + "Add": "Adaugă", + "AddDelayProfile": "Adăugați un profil de întârziere", + "Added": "Adăugat", + "AddIndexer": "Adăugați Indexator", + "AddList": "Adaugă listă", + "AddNew": "Adaugă", + "AddQualityProfile": "Adăugați un profil de calitate", + "AddRemotePathMapping": "Adăugați maparea căilor la distanță", + "AddRootFolder": "Adăugați folderul rădăcină", + "AfterManualRefresh": "După reîmprospătarea manuală", + "Age": "Vechime", + "All": "Toate", + "AllFiles": "Toate filele", + "AllResultsFiltered": "Toate rezultatele sunt ascunse de filtrul aplicat", + "Always": "Mereu", + "Apply": "Aplică", + "AudioInfo": "Info Audio", + "Backup": "Copie de siguranță", + "BeforeUpdate": "Înainte de actualizare", + "Close": "Închide", + "Connect": "Conectează", + "Custom": "Personalizat", + "CustomFilters": "Filtre personalizate", + "DefaultDelayProfileHelpText": "Acesta este profilul implicit. Se aplică tuturor filmelor care nu au un profil explicit.", + "Deleted": "Șters", + "Details": "Detalii", + "Donations": "Donații", + "DoNotPrefer": "Nu preferați", + "DoNotUpgradeAutomatically": "Nu faceți upgrade automat", + "DownloadFailed": "Descarcare esuata", + "EditDelayProfile": "Editați profilul de întârziere", + "EditImportListExclusion": "Ștergeți excluderea listei de import", + "EditIndexer": "Editați indexator", + "EditQualityProfile": "Editați profilul de calitate", + "EditRemotePathMapping": "Editați maparea căilor la distanță", + "EditRootFolder": "Adăugați folderul rădăcină", + "Error": "Eroare", + "ErrorRestoringBackup": "Eroare la restaurarea copiei de rezervă", + "Events": "Evenimente", + "EventType": "Tip de eveniment", + "Filters": "Filtre", + "FreeSpace": "Spațiu Liber", + "General": "General", + "Genres": "Genuri", + "Grabbed": "Prins", + "Ignored": "Ignorat", + "Import": "Importă", + "Info": "Info", + "InteractiveImport": "Import interactiv", + "LastDuration": "lastDuration", + "LastExecution": "Ultima executare", + "LastUsed": "Folosit ultima data", + "LastWriteTime": "Data ultimei scrieri", + "Library": "Bibliotecă", + "Location": "Locație", + "Manual": "Manual", + "MediaManagement": "Administrare media", + "Metadata": "Metadata", + "MonitoredOnly": "Doar monitorizate", + "MoveAutomatically": "Import rapid", + "MoveFiles": "Mută Fișiere", + "NextExecution": "Următoarea execuție", + "OnlyUsenet": "Doar Usenet", + "Organize": "Organizează", + "OutputPath": "Calea pentru Output", + "Peers": "Parteneri", + "PreferAndUpgrade": "Prefer și upgrade", + "Presets": "Presetări", + "Progress": "Progres", + "QualityLimitsHelpText": "Limitele sunt ajustate automat pentru durata de rulare a filmului.", + "Queued": "În așteptare", + "Rating": "Recenzii", + "RejectionCount": "Numărul Respingerilor", + "ReleaseTitle": "Titlul Apariției", + "Renamed": "Redenumit", + "Replace": "A inlocui", + "RestartRequiredHelpTextWarning": "Necesită repornire pentru a intra în vigoare", + "RestoreBackupAdditionalInfo": "Notă: Radarr va reporni și reîncărca automat interfața de utilizare în timpul procesului de restaurare.", + "Save": "Salvează", + "Seeders": "Partajatori", + "Select...": "'Selectați...", + "SelectFolder": "Selectați Folder", + "SelectQuality": "Selectați Calitate", + "ShowAdvanced": "Arată setări avansate", + "SizeOnDisk": "Mărime pe disc", + "SomeResultsFiltered": "Unele rezultate sunt ascunse de filtrul aplicat", + "SourceTitle": "Titlul sursei", + "System": "Sistem", + "Test": "Testează", + "TimeLeft": "Timp rămas", + "Title": "Titlu", + "TotalSpace": "Spațiu Total", + "UI": "Interfață Grafica", + "UnableToLoadInteractiveSerach": "Nu se pot încărca rezultatele pentru această căutare de filme. Încercați mai târziu", + "UnmappedFilesOnly": "Numai fișiere nemapate", + "UnmonitoredOnly": "Doar monitorizate", + "UpgradesAllowed": "Sunt permise upgrade-uri", + "Warn": "Atenționare", + "WouldYouLikeToRestoreBackup": "Doriți să restaurați copia de rezervă {0}?", + "Ok": "Ok", + "Wanted": "Dorite", + "Date": "Dată", + "HardlinkCopyFiles": "Hardlink/Copiază Fișiere", + "HideAdvanced": "Ascunde Avansat", + "AddImportListExclusion": "Ștergeți excluderea listei de import", + "NoResults": "Nici un rezultat gasit", + "NoTagsHaveBeenAddedYet": "Nu s-au adăugat încă etichete", + "OnlyTorrent": "Numai Torrent" } diff --git a/src/NzbDrone.Core/Localization/Core/ru.json b/src/NzbDrone.Core/Localization/Core/ru.json index d9c956740..9e08ab13c 100644 --- a/src/NzbDrone.Core/Localization/Core/ru.json +++ b/src/NzbDrone.Core/Localization/Core/ru.json @@ -488,5 +488,123 @@ "AllowArtistChangeClickToChangeArtist": "Нажмите, чтобы изменить автора", "AddMissing": "Добавить отсутствующие", "AddNewItem": "Добавить Новый Элемент", - "Label": "Ярлык" + "Label": "Ярлык", + "Activity": "Активность", + "Add": "Добавить", + "AddDelayProfile": "Добавить профиль задержки", + "Added": "Добавлено", + "AddIndexer": "Добавить индексер", + "AddList": "Добавить список", + "AddNew": "Добавить", + "AddQualityProfile": "Добавить профиль качества", + "AddRemotePathMapping": "Добавить удаленный путь", + "AddRootFolder": "Добавить корневой каталог", + "AfterManualRefresh": "После обновления в ручную", + "Age": "Возраст", + "All": "Все", + "AllFiles": "Все файлы", + "AllResultsFiltered": "Все результаты скрыты фильтром", + "Always": "Всегда", + "Apply": "Применить", + "AudioInfo": "Информация о аудио", + "BeforeUpdate": "До обновления", + "Close": "Закрыть", + "Connect": "Подключить", + "Custom": "Настраиваемый", + "CustomFilters": "Настраиваемые фильтры", + "DefaultDelayProfileHelpText": "Это профиль по умолчанию. Он относится ко всем фильмам, у которых нет явного профиля.", + "Deleted": "Удалено", + "Donations": "Пожертвования", + "DoNotPrefer": "Не предпочитать", + "DoNotUpgradeAutomatically": "Не обновлять автоматически", + "DownloadFailed": "Неудачное скачивание", + "EditConnection": "Редактировать коллекцию", + "EditDelayProfile": "Редактировать профиль задержки", + "EditImportListExclusion": "Удалить лист исключения для импорта", + "EditIndexer": "Редактировать индексатор", + "EditQualityProfile": "Редактировать качественный профиль", + "EditRemotePathMapping": "Редактировать расположение подключенной папки", + "EditRootFolder": "Добавить корневой каталог", + "Error": "Ошибка", + "ErrorRestoringBackup": "Ошибка при восстановлении данных", + "Events": "События", + "EventType": "Тип события", + "Filters": "Фильтры", + "FreeSpace": "Свободное место", + "General": "Основное", + "Genres": "Жанры", + "Grabbed": "Захвачено", + "HardlinkCopyFiles": "Встроенные ссылки/копирование файлов", + "HideAdvanced": "Скрыть расширенные", + "Ignored": "Проигнорировано", + "Import": "Внести", + "IndexerDownloadClientHelpText": "Укажите, какой клиент загрузки используется для захвата из этого индексатора", + "IndexerTagHelpText": "Используйте этот индексатор только для фильмов с хотя бы одним совпадающим тегом. Оставьте пустым, чтобы использовать для всех фильмов.", + "Info": "Информация", + "InstanceName": "Имя экземпляра", + "InstanceNameHelpText": "Имя экземпляра на вкладке и для имени приложения системного журнала", + "InteractiveImport": "Интерактивный импорт", + "LastDuration": "Последняя длительность", + "LastExecution": "Последнее выполнение", + "LastUsed": "Использовано последний раз", + "LastWriteTime": "Последнее время записи", + "Library": "Библиотека", + "Location": "Месторасположение", + "MediaManagement": "Управление медиа", + "Metadata": "Мета данные", + "MonitoredOnly": "Только отслеживаемые", + "MoveAutomatically": "Автоматическое перемещение", + "MoveFiles": "Переместить файлы", + "Never": "Никогда", + "NextExecution": "Следующее выполнение", + "NoResults": "Нет результатов", + "NoTagsHaveBeenAddedYet": "Теги еще не добавлены", + "Ok": "Ok", + "OnlyTorrent": "Только торрент", + "OnlyUsenet": "Только Usenet", + "Organize": "Организовать", + "OutputPath": "Выходной путь", + "Peers": "Пиры", + "PreferAndUpgrade": "Предпочитать и улучшать", + "Presets": "Предустановки", + "Progress": "Прогресс", + "QualityLimitsHelpText": "Пределы автоматически регулируются для времени воспроизведения фильма.", + "Queued": "В очереди", + "Rating": "Рейтинг", + "RejectionCount": "Количество отказов", + "ReleaseTitle": "Название релиза", + "Renamed": "Переименовано", + "Replace": "Заменить", + "RestartRequiredHelpTextWarning": "Для вступления в силу требуется перезапуск", + "RestoreBackupAdditionalInfo": "Примечание: Radarr автоматически перезапустится и перезагрузит пользовательский интерфейс во время процесса восстановления.", + "Save": "Сохранить", + "Seeders": "Сиды", + "Select...": "Выбрать...", + "SelectFolder": "Выбрать папку", + "SelectQuality": "Выбрать качество", + "ShowAdvanced": "Показать расширенные", + "SizeLimit": "Ограничение по размеру", + "SizeOnDisk": "Объём на диске", + "SomeResultsFiltered": "Некоторые результаты скрыты примененным фильтром", + "SourceTitle": "Название источника", + "Started": "Запущено", + "System": "Система", + "Test": "Тест", + "Title": "Название", + "TotalSpace": "Общее сводное место", + "UI": "Пользовательский интерфейс", + "UnableToLoadInteractiveSerach": "Невозможно загрузить результаты поиска по этому фильму. Попробуйте позже", + "UnmappedFilesOnly": "Только несопоставленные файлы", + "UnmonitoredOnly": "Только отслеживаемые", + "UpgradesAllowed": "Обновления разрешены", + "Wanted": "Разыскиваемый", + "Warn": "Предупреждать", + "WouldYouLikeToRestoreBackup": "Желаете восстановить резервную копию {0} ?", + "Backup": "Резервное копирование", + "Details": "Подробности", + "TimeLeft": "Оставшееся время", + "AddConnection": "Редактировать коллекцию", + "AddImportListExclusion": "Удалить лист исключения для импорта", + "Date": "Дата", + "Manual": "Ручной" } diff --git a/src/NzbDrone.Core/Localization/Core/sk.json b/src/NzbDrone.Core/Localization/Core/sk.json index 09992b2a4..0e623080e 100644 --- a/src/NzbDrone.Core/Localization/Core/sk.json +++ b/src/NzbDrone.Core/Localization/Core/sk.json @@ -44,5 +44,122 @@ "AnalyticsEnabledHelpText": "Odosielajte anonymné informácie o používaní a chybách na servery Readarru. To zahŕňa informácie o vašom prehliadači, ktoré stránky Lidarr WebUI používate, hlásenia chýb a taktiež verziu operačného systému a spúšťacieho prostredia. Tieto informácie použijeme k uprednostňovaniu funkcií a oprav chýb.", "Automatic": "Automatický", "AppDataDirectory": "Priečinok AppData", - "BindAddressHelpText": "Platná adresa IP4 alebo '*' pre všetky rozhrania" + "BindAddressHelpText": "Platná adresa IP4 alebo '*' pre všetky rozhrania", + "UpdateMechanismHelpText": "Použiť vstavaný Prowlarr aktualizátor alebo skript", + "CertificateValidationHelpText": "Zmeňte, aké prísne je overenie certifikácie HTTPS. Nemeňte, pokiaľ nerozumiete rizikám.", + "Username": "Používateľské meno", + "Language": "jazyk", + "MetadataProfiles": "profil metadát", + "Quality": "kvalita", + "QualityProfile": "Profil kvality", + "RecyclingBin": "Kôš", + "RemoveSelectedMessageText": "Naozaj si prajete odstrániť označené položky z blocklistu?", + "Search": "Hľadať", + "Password": "Heslo", + "Host": "Hostiteľ", + "DownloadClient": "Klient na sťahovanie", + "Enable": "Povoliť", + "Files": "Súbor", + "IconTooltip": "Naplánované", + "Label": "Štítok", + "New": "Nový", + "Activity": "Aktivita", + "Add": "Pridať", + "AddDelayProfile": "Pridať profil oneskorenia", + "Added": "Pridané", + "AddIndexer": "Pridať indexer", + "AddList": "Pridať zoznam", + "AddNew": "Pridať nový", + "AddRemotePathMapping": "Pridajte vzdialené mapovanie ciest", + "AddRootFolder": "Pridať koreňový priečinok", + "AfterManualRefresh": "Po ručnom obnovení", + "Age": "Vek", + "All": "Všetko", + "AllFiles": "Všetky súbory", + "AllResultsFiltered": "Všetky výsledky sú skryté použitým filtrom", + "AlternateTitleslength1Title": "Názov", + "AlternateTitleslength1Titles": "Názov", + "Always": "Vždy", + "ApplicationURL": "URL aplikácie", + "ApplicationUrlHelpText": "Externá URL tejto aplikácie vrátane http(s)://, portu a URL základu", + "Apply": "Použiť", + "AudioInfo": "Informácie o zvuku", + "Backup": "Záloha", + "BeforeUpdate": "Pred aktualizáciou", + "Blocklist": "Blocklist", + "BlocklistHelpText": "Zabráni Radarru, aby znova automaticky grabol toto vydanie", + "BlocklistRelease": "Blocklistnúť vydanie", + "CancelMessageText": "Naozaj chcete zrušiť túto prebiehajúcu úlohu?", + "CertificateValidation": "Overenie certifikátu", + "ChangeFileDate": "Zmeniť dátum súboru", + "ChangeHasNotBeenSavedYet": "Zmena ešte nebola uložená", + "ChmodFolder": "chmod Priečinok", + "ChmodFolderHelpTextWarning": "Toto funguje iba ak používateľ bežiaci Radarr je vlastníkom súboru. Je lepšie zaistiť, že klient na sťahovanie správne nastaví oprávnenia.", + "ChownGroupHelpText": "Názov skupiny alebo gid. Použite gid pre vzdialené súborové systémy.", + "ChownGroupHelpTextWarning": "Toto funguje iba ak použivateľ bežiaci Radarr je vlastníkom súboru. Je to lepšie zaistite, že klient na sťahovanie používa tú istú skupinu ako Radarr.", + "Clear": "Vymazať", + "ClickToChangeQuality": "Kliknutím zmeníte kvalitu", + "ClientPriority": "Priorita klienta", + "CloneIndexer": "Klonovať indexer", + "CloneProfile": "Klonovať profil", + "Close": "Zatvoriť", + "Connect": "Pripojiť", + "Custom": "Vlastné", + "DelayProfile": "Profil oneskorenia", + "DelayProfiles": "Profil oneskorenia", + "Delete": "Vymazať", + "Deleted": "Vymazať", + "Details": "podrobnosti", + "DownloadClients": "Klient na sťahovanie", + "EditDelayProfile": "Pridať profil oneskorenia", + "EditQualityProfile": "Pridajte profil kvality", + "EditRemotePathMapping": "Pridajte vzdialené mapovanie ciest", + "EditRootFolder": "Pridať koreňový priečinok", + "Filters": "Filtre", + "Hostname": "Názov hostiteľa", + "ICalFeed": "iCal Feed", + "ICalLink": "iCal Link", + "Indexer": "Indexer", + "Indexers": "Indexery", + "IsCutoffCutoff": "Hranica", + "Library": "Knižnica", + "LocalPath": "Miestna cesta", + "Metadata": "metadáta", + "MetadataProfile": "profil metadát", + "Peers": "Peeri", + "Protocol": "Protokol", + "QualityProfiles": "Profil kvality", + "Queue": "Fronta", + "Queued": "Fronta", + "Rating": "Hodnotenie", + "Reload": "Obnoviť", + "RemotePathMappings": "Vzdialené mapovanie cesty", + "Remove": "Odstrániť", + "Replace": "Nahradiť", + "RootFolder": "Koreňový priečinok", + "RootFolders": "Koreňový priečinok", + "Scheduled": "Naplánované", + "Season": "Séria", + "Seeders": "Seederi", + "Settings": "Nastavenia", + "SomeResultsFiltered": "Všetky výsledky sú skryté použitým filtrom", + "Term": "Termín", + "Title": "Názov", + "Torrents": "Torrenty", + "UI": "UI", + "URLBase": "Základ URL", + "Updates": "Aktualizovať", + "Refresh": "Obnoviť", + "BindAddress": "Viazať adresu", + "Columns": "Stĺpce", + "CompletedDownloadHandling": "Zaobchádzanie s dokončenými sťahovaniami", + "Component": "Komponent", + "Connections": "Spojenia", + "Grab": "Grab", + "Port": "Port", + "Usenet": "Usenet", + "AddQualityProfile": "Pridajte profil kvality", + "AddMetadataProfile": "profil metadát", + "Events": "Udalosť", + "Info": "Info" } diff --git a/src/NzbDrone.Core/Localization/Core/sv.json b/src/NzbDrone.Core/Localization/Core/sv.json index 6ea518a50..3b0916736 100644 --- a/src/NzbDrone.Core/Localization/Core/sv.json +++ b/src/NzbDrone.Core/Localization/Core/sv.json @@ -647,5 +647,120 @@ "OnUpgrade": "Vid uppgradering", "OnDownloadFailure": "När Nedladdning Misslyckas", "OnGrab": "Vid hämtning", - "MonoVersion": "Mono version" + "MonoVersion": "Mono version", + "Replace": "Ersätta", + "RestartRequiredHelpTextWarning": "Kräver omstart för att träda i kraft", + "RestoreBackupAdditionalInfo": "Obs! Radarr startar automatiskt om och laddar om användargränssnittet under återställningsprocessen.", + "Save": "Spara", + "Seeders": "Seeders", + "Select...": "'Välj...", + "SelectFolder": "Välj mapp", + "SelectQuality": "Välj kvalitet", + "ShouldSearch": "Sök efter Nya Saker", + "ShouldSearchHelpText": "Sök indexer för nyligen tillagda saker. Använd med försiktighet vid stora listor.", + "SizeOnDisk": "Storlek på disk", + "SomeResultsFiltered": "Vissa resultat döljs av det applicerade filtret", + "SourceTitle": "Källtitel", + "System": "System", + "Test": "Test", + "TimeLeft": "Tid kvar", + "Title": "Titel", + "TotalSpace": "Totalt utrymme", + "UI": "Användargränssnitt", + "UnableToLoadInteractiveSerach": "Det gick inte att ladda resultat för den här filmsökningen. Försök igen senare", + "UnmappedFilesOnly": "Endast omappade filer", + "UnmonitoredOnly": "Endast Bevakade", + "Wanted": "Önskade", + "Warn": "Varna", + "WouldYouLikeToRestoreBackup": "Skulle du vilja återställa säkerhetskopian {0} ?", + "Activity": "Aktivitet", + "AddDelayProfile": "Lägg till fördröjnings profil", + "Added": "Tillagd", + "AddImportListExclusion": "Importera List-Exkluderingar", + "AddIndexer": "Lägg till indexerare", + "AddList": "Lägg till lista", + "AddMetadataProfile": "Metadata Profil", + "AddNew": "Lägg till ny", + "AddQualityProfile": "Lägg till kvalitetsprofil", + "AddRemotePathMapping": "Lägg till fjärrsökväg", + "AddRootFolder": "Lägg till rotmapp", + "AfterManualRefresh": "Efter manuell uppdatering", + "Age": "Ålder", + "Albums": "Albums", + "All": "Samtliga", + "AllFiles": "Samtliga filer", + "AllResultsFiltered": "Alla resultat döljs av det tillämpade filtret", + "Always": "Alltid", + "AudioInfo": "Ljudinformation", + "Backup": "Säkerhetskopiering", + "BeforeUpdate": "Innan uppdatering", + "Close": "Avsluta", + "Connect": "Anslut", + "Custom": "Beställnings", + "CustomFilters": "Anpassade Filter", + "Date": "Datum", + "DefaultDelayProfileHelpText": "Detta är standardprofilen. Det gäller alla filmer som inte har en uttrycklig profil.", + "Deleted": "Raderad", + "Details": "Detaljer", + "Donations": "Donationer", + "DoNotPrefer": "Föredrar inte", + "DoNotUpgradeAutomatically": "Uppgradera inte automatiskt", + "DownloadFailed": "Misslyckad nedladdning", + "EditDelayProfile": "Redigera fördröjningsprofil", + "EditImportListExclusion": "Ta bort undantag för importlista", + "EditIndexer": "Redigera indexerare", + "EditQualityProfile": "Redigera kvalitetsprofil", + "EditRemotePathMapping": "Redigera fjärrsökväg", + "EditRootFolder": "Lägg till rotmapp", + "ErrorRestoringBackup": "Fel vid återställning av säkerhetskopian", + "Events": "Händelser", + "EventType": "Händelsetyp", + "Filters": "Filter", + "FreeSpace": "Ledigt utrymme", + "General": "Generell", + "Genres": "Genrer", + "Grabbed": "Hämtad", + "HardlinkCopyFiles": "Hardlink/kopiera filer", + "HideAdvanced": "Dölj avancerat", + "Ignored": "Ignorerad", + "Import": "Import", + "InteractiveImport": "Interaktiv import", + "LastDuration": "lastDuration", + "LastExecution": "Senaste avrättningen", + "LastUsed": "Senast använd", + "LastWriteTime": "Senast skriven tid", + "Library": "Bibliotek", + "Location": "Lagringsplats", + "Manual": "Manuell", + "MediaManagement": "Mediahantering", + "Metadata": "Metadata", + "MonitoredOnly": "Endast Bevakade", + "MoveAutomatically": "Snabbimport", + "MoveFiles": "Flytta filer", + "Never": "Aldrig", + "NextExecution": "Nästa utförande", + "NoResults": "Inga resultat funna", + "NoTagsHaveBeenAddedYet": "Inga taggar har lagts till ännu", + "Ok": "Ok", + "OnlyTorrent": "Endast Torrent", + "OnlyUsenet": "Endast Usenet", + "Organize": "Organisera", + "OutputPath": "Output-sökväg", + "Peers": "Peers", + "PreferAndUpgrade": "Föredra och uppgradera", + "Presets": "Förinställningar", + "Progress": "Progress", + "QualityLimitsHelpText": "Gränserna justeras automatiskt för filmens körtid.", + "Queued": "Köad", + "Rating": "Betyg", + "RejectionCount": "Antal avslag", + "ReleaseTitle": "Releasetitel", + "Error": "Fel", + "ShowAdvanced": "Visa avancerat", + "UpgradesAllowed": "Uppgraderingar tillåtna", + "Apply": "TIllämpa", + "Renamed": "Omdöpt", + "Add": "Lägg till", + "EditMetadataProfile": "Metadata Profil", + "Info": "Info" } diff --git a/src/NzbDrone.Core/Localization/Core/th.json b/src/NzbDrone.Core/Localization/Core/th.json index ec8bfb333..21b05a744 100644 --- a/src/NzbDrone.Core/Localization/Core/th.json +++ b/src/NzbDrone.Core/Localization/Core/th.json @@ -473,5 +473,113 @@ "NoHistory": "ไม่มีประวัติ", "NoLeaveIt": "ไม่ปล่อยไว้", "SslCertPathHelpText": "พา ธ ไปยังไฟล์ pfx", - "MonoVersion": "เวอร์ชันโมโน" + "MonoVersion": "เวอร์ชันโมโน", + "NextExecution": "การดำเนินการถัดไป", + "OnlyTorrent": "เฉพาะ Torrent", + "OnlyUsenet": "เฉพาะ Usenet", + "QualityLimitsHelpText": "ขีด จำกัด จะถูกปรับโดยอัตโนมัติสำหรับรันไทม์ของภาพยนตร์", + "Queued": "อยู่ในคิว", + "RejectionCount": "จำนวนการปฏิเสธ", + "ReleaseTitle": "ชื่อรุ่น", + "ShowAdvanced": "แสดงขั้นสูง", + "UI": "UI", + "UpgradesAllowed": "อนุญาตให้อัปเกรดได้", + "Wanted": "ต้องการ", + "Warn": "เตือน", + "Activity": "กิจกรรม", + "Add": "เพิ่ม", + "AddDelayProfile": "เพิ่มโปรไฟล์ความล่าช้า", + "Added": "เพิ่มแล้ว", + "AddIndexer": "เพิ่ม Indexer", + "AddList": "เพิ่มรายการ", + "AddNew": "เพิ่มใหม่", + "AddQualityProfile": "เพิ่มโปรไฟล์คุณภาพ", + "AddRemotePathMapping": "เพิ่มการแมปเส้นทางระยะไกล", + "AddRootFolder": "เพิ่มโฟลเดอร์รูท", + "AfterManualRefresh": "หลังจากรีเฟรชด้วยตนเอง", + "Age": "อายุ", + "All": "ทั้งหมด", + "AllFiles": "เอกสารทั้งหมด", + "AllResultsFiltered": "ผลลัพธ์ทั้งหมดถูกซ่อนโดยตัวกรองที่ใช้", + "Always": "เสมอ", + "Apply": "สมัคร", + "AudioInfo": "ข้อมูลเสียง", + "Backup": "การสำรองข้อมูล", + "BeforeUpdate": "ก่อนการอัปเดต", + "Connect": "เชื่อมต่อ", + "Custom": "กำหนดเอง", + "CustomFilters": "ตัวกรองที่กำหนดเอง", + "Date": "วันที่", + "DefaultDelayProfileHelpText": "นี่คือโปรไฟล์เริ่มต้น ใช้กับภาพยนตร์ทุกเรื่องที่ไม่มีโปรไฟล์โจ่งแจ้ง", + "Deleted": "ลบแล้ว", + "Details": "รายละเอียด", + "Donations": "การบริจาค", + "DoNotPrefer": "ไม่ชอบ", + "DoNotUpgradeAutomatically": "อย่าอัปเกรดโดยอัตโนมัติ", + "DownloadFailed": "ดาวน์โหลดล้มเหลว", + "EditDelayProfile": "แก้ไขโปรไฟล์ความล่าช้า", + "EditImportListExclusion": "ลบการยกเว้นรายการนำเข้า", + "EditIndexer": "แก้ไข Indexer", + "EditQualityProfile": "แก้ไขโปรไฟล์คุณภาพ", + "EditRemotePathMapping": "แก้ไขการแมปเส้นทางระยะไกล", + "EditRootFolder": "เพิ่มโฟลเดอร์รูท", + "ErrorRestoringBackup": "เกิดข้อผิดพลาดในการกู้คืนข้อมูลสำรอง", + "Events": "เหตุการณ์", + "EventType": "ประเภทเหตุการณ์", + "Filters": "กรอง", + "FreeSpace": "ที่ว่าง", + "Grabbed": "คว้า", + "HardlinkCopyFiles": "ฮาร์ดลิงค์ / คัดลอกไฟล์", + "HideAdvanced": "ซ่อนขั้นสูง", + "Ignored": "ละเว้น", + "Import": "นำเข้า", + "Info": "ข้อมูล", + "InteractiveImport": "การนำเข้าแบบโต้ตอบ", + "LastDuration": "lastDuration", + "LastExecution": "การดำเนินการล่าสุด", + "LastUsed": "ใช้ล่าสุด", + "LastWriteTime": "เวลาเขียนล่าสุด", + "Location": "สถานที่", + "MediaManagement": "การจัดการสื่อ", + "Metadata": "ข้อมูลเมตา", + "MonitoredOnly": "ตรวจสอบเท่านั้น", + "MoveAutomatically": "นำเข้าด่วน", + "MoveFiles": "ย้ายไฟล์", + "OutputPath": "เส้นทางเอาต์พุต", + "Peers": "เพื่อนร่วมงาน", + "PreferAndUpgrade": "ชอบและอัพเกรด", + "Presets": "ค่าที่ตั้งไว้ล่วงหน้า", + "Progress": "ความคืบหน้า", + "Rating": "การให้คะแนน", + "Renamed": "เปลี่ยนชื่อ", + "RestartRequiredHelpTextWarning": "ต้องรีสตาร์ทเพื่อให้มีผล", + "RestoreBackupAdditionalInfo": "หมายเหตุ: Radarr จะรีสตาร์ทและโหลด UI ใหม่โดยอัตโนมัติในระหว่างกระบวนการกู้คืน", + "Save": "บันทึก", + "Seeders": "Seeders", + "Select...": "'เลือก...", + "SelectFolder": "เลือกโฟลเดอร์", + "SelectQuality": "เลือกคุณภาพ", + "SizeOnDisk": "ขนาดบนดิสก์", + "SomeResultsFiltered": "ผลลัพธ์บางอย่างถูกซ่อนไว้โดยตัวกรองที่ใช้", + "SourceTitle": "ชื่อแหล่งที่มา", + "System": "ระบบ", + "Test": "ทดสอบ", + "TimeLeft": "Timeleft", + "Title": "หัวข้อ", + "TotalSpace": "พื้นที่ทั้งหมด", + "UnableToLoadInteractiveSerach": "ไม่สามารถโหลดผลการค้นหาภาพยนตร์นี้ โปรดลองอีกครั้งในภายหลัง", + "UnmappedFilesOnly": "ไฟล์ที่ไม่ได้แมปเท่านั้น", + "UnmonitoredOnly": "ตรวจสอบเท่านั้น", + "WouldYouLikeToRestoreBackup": "คุณต้องการกู้คืนข้อมูลสำรอง {0} หรือไม่", + "Error": "ข้อผิดพลาด", + "General": "ทั่วไป", + "Genres": "ประเภท", + "Ok": "ตกลง", + "Replace": "แทนที่", + "AddImportListExclusion": "ลบการยกเว้นรายการนำเข้า", + "NoResults": "ไม่พบผลลัพธ์", + "NoTagsHaveBeenAddedYet": "ยังไม่มีการเพิ่มแท็ก", + "Organize": "จัดระเบียบ", + "Close": "ปิด", + "Manual": "คู่มือ" } diff --git a/src/NzbDrone.Core/Localization/Core/tr.json b/src/NzbDrone.Core/Localization/Core/tr.json index b364bb844..21b21e0f6 100644 --- a/src/NzbDrone.Core/Localization/Core/tr.json +++ b/src/NzbDrone.Core/Localization/Core/tr.json @@ -474,5 +474,114 @@ "OnUpgrade": "Yükseltme sırasında", "Tracks": "İzleme", "NETCore": ".NET Çekirdeği", - "MonoVersion": "Mono Versiyon" + "MonoVersion": "Mono Versiyon", + "Ok": "Tamam", + "Test": "Ölçek", + "UnableToLoadInteractiveSerach": "Bu film araması için sonuçlar yüklenemiyor. Daha sonra tekrar deneyin", + "UnmappedFilesOnly": "Yalnızca Eşlenmemiş Dosyalar", + "Activity": "Aktivite", + "Add": "Ekle", + "AddDelayProfile": "Gecikme Profili Ekle", + "Replace": "Değiştir", + "RestartRequiredHelpTextWarning": "Etkili olması için yeniden başlatma gerektirir", + "ShowAdvanced": "Gelişmiş'i Göster", + "SizeOnDisk": "Diskteki ölçü", + "SomeResultsFiltered": "Bazı sonuçlar, uygulanan filtre tarafından gizlendi", + "SourceTitle": "Kaynak başlığı", + "System": "Sistem", + "TimeLeft": "Kalan zaman", + "Title": "Başlık", + "TotalSpace": "Toplam alan", + "UnmonitoredOnly": "Sadece İzlenenler", + "UpgradesAllowed": "Yükseltmelere İzin Verildi", + "Wanted": "İstenenler", + "Warn": "Uyar", + "Connect": "Bağlan", + "Added": "Eklendi", + "AddIndexer": "Dizin Oluşturucu Ekle", + "AddList": "Liste ekle", + "AddNew": "Yeni ekle", + "AddQualityProfile": "Kalite Profili Ekle", + "AddRemotePathMapping": "Uzak Yol Eşleme Ekle", + "AddRootFolder": "Kök Klasör Ekle", + "AfterManualRefresh": "Manuel Yenilemeden Sonra", + "Age": "Yaş", + "All": "Herşey", + "AllFiles": "Tüm dosyalar", + "AllResultsFiltered": "Tüm sonuçlar, uygulanan filtre tarafından gizlenir", + "Always": "Her zaman", + "AudioInfo": "Ses Bilgileri", + "BeforeUpdate": "Güncellemeden önce", + "Close": "Kapat", + "Custom": "Özel", + "CustomFilters": "Özel Filtreler", + "Date": "Tarih", + "DefaultDelayProfileHelpText": "Bu varsayılan profildir. Açık bir profili olmayan tüm filmler için geçerlidir.", + "Deleted": "Silindi", + "Donations": "Bağışlar", + "DoNotPrefer": "Tercih etmeme", + "DoNotUpgradeAutomatically": "Otomatik Olarak Yükseltme", + "DownloadFailed": "Yükleme başarısız", + "EditDelayProfile": "Gecikme Profilini Düzenle", + "EditImportListExclusion": "İçe Aktarma Listesi Hariç Tutmasını Sil", + "EditIndexer": "Dizinleyiciyi Düzenle", + "EditQualityProfile": "Kalite Profilini Düzenle", + "EditRemotePathMapping": "Uzak Yol Eşlemeyi Düzenle", + "EditRootFolder": "Kök Klasör Ekle", + "Error": "Hata", + "ErrorRestoringBackup": "Yedeği geri yüklerken hata", + "Events": "Etkinlikler", + "EventType": "Etkinlik tipi", + "Filters": "Filtre", + "FreeSpace": "Boş alan", + "General": "Genel", + "Grabbed": "Yakalandı", + "HardlinkCopyFiles": "Hardlink / Dosyaları Kopyala", + "HideAdvanced": "Gelişmiş'i Gizle", + "Ignored": "Yok sayıldı", + "Import": "İthalat", + "InteractiveImport": "Etkileşimli İçe Aktarma", + "Location": "yer", + "Manual": "Manuel", + "MediaManagement": "Medya işletme", + "Metadata": "Meta veri", + "MonitoredOnly": "Sadece İzlenenler", + "MoveAutomatically": "Hızlı İçe Aktarma", + "MoveFiles": "Dosyaları Taşı", + "NextExecution": "Sonraki Yürütme", + "NoResults": "Sonuç bulunamadı", + "NoTagsHaveBeenAddedYet": "Henüz etiket eklenmedi", + "OnlyTorrent": "Sadece Torrent", + "OnlyUsenet": "Sadece Usenet", + "Organize": "Düzenlemek", + "OutputPath": "Çıkış yolu", + "Peers": "Akranlar", + "PreferAndUpgrade": "Tercih Et ve Yükselt", + "Presets": "Ön ayarlar", + "QualityLimitsHelpText": "Sınırlar, film çalışma süresine göre otomatik olarak ayarlanır.", + "RejectionCount": "Reddetme Sayısı", + "ReleaseTitle": "Yayin Başlığı", + "Renamed": "Yeniden adlandırıldı", + "RestoreBackupAdditionalInfo": "Not: Radarr, geri yükleme işlemi sırasında kullanıcı arayüzünü otomatik olarak yeniden başlatacak ve yeniden yükleyecektir.", + "Save": "Kayıt etmek", + "Seeders": "Ekme makineleri", + "Select...": "'Seçin ...", + "SelectFolder": "Dosya Seç", + "SelectQuality": "Kaliteyi Seçin", + "WouldYouLikeToRestoreBackup": "{0} yedeğini geri yüklemek ister misiniz?", + "Apply": "Uygulamak", + "Backup": "Destek olmak", + "Details": "Detaylar", + "Genres": "Türler", + "Info": "Bilgi", + "LastDuration": "lastDuration", + "LastExecution": "Son Yürütme", + "LastUsed": "Son kullanılan", + "LastWriteTime": "Son Yazma Zamanı", + "Progress": "İlerleme", + "Queued": "Sıraya alındı", + "Rating": "Puanlar", + "UI": "UI", + "AddImportListExclusion": "İçe Aktarma Listesi Hariç Tutmasını Sil", + "ImportListExclusions": "İçe Aktarma Listesi Hariç Tutmasını Sil" } diff --git a/src/NzbDrone.Core/Localization/Core/uk.json b/src/NzbDrone.Core/Localization/Core/uk.json index 32c060301..aab1366b3 100644 --- a/src/NzbDrone.Core/Localization/Core/uk.json +++ b/src/NzbDrone.Core/Localization/Core/uk.json @@ -87,5 +87,37 @@ "DeleteDelayProfileMessageText": "Ви впевнені, що хочете видалити цей профіль затримки?", "DeleteImportListMessageText": "Ви впевнені, що хочете видалити тег {0} ?", "DeleteIndexerMessageText": "Ви впевнені, що хочете видалити тег {0} ?", - "BindAddressHelpText": "Дійсна адреса IPv4 або '*' для всіх інтерфейсів" + "BindAddressHelpText": "Дійсна адреса IPv4 або '*' для всіх інтерфейсів", + "AudioInfo": "Аудіо інформація", + "Deleted": "Видалено", + "Activity": "Активність", + "Add": "Додати", + "AddDelayProfile": "Додати профіль затримки", + "Added": "Додано", + "AddIndexer": "Додати індексатор", + "AddList": "Додати список", + "AddNew": "Додати", + "AddQualityProfile": "Додати профіль якості", + "AddRemotePathMapping": "Додати віддалений шлях", + "AddRootFolder": "Додати корневий каталог", + "AfterManualRefresh": "Після ручного оновлення", + "All": "Всі", + "AllFiles": "Всі файли", + "AllResultsFiltered": "Всі результати приховані фільтром", + "Always": "Завжди", + "Apply": "Застосувати", + "Backup": "Резервне копіювання", + "BeforeUpdate": "Перед оновленням", + "Close": "Закрити", + "Connect": "Підключити", + "Custom": "Настроюваний", + "CustomFilters": "Користувацькі фільтри", + "Date": "Дата", + "DefaultDelayProfileHelpText": "Це профіль за замовчуванням. Це стосується всіх фільмів, які не мають явного профілю.", + "EditDelayProfile": "Додати профіль затримки", + "EditQualityProfile": "Додати профіль якості", + "EditRemotePathMapping": "Додати віддалений шлях", + "EditRootFolder": "Додати корневий каталог", + "SomeResultsFiltered": "Всі результати приховані фільтром", + "Age": "Вік" } diff --git a/src/NzbDrone.Core/Localization/Core/vi.json b/src/NzbDrone.Core/Localization/Core/vi.json index 9eade105f..be551d240 100644 --- a/src/NzbDrone.Core/Localization/Core/vi.json +++ b/src/NzbDrone.Core/Localization/Core/vi.json @@ -473,5 +473,113 @@ "Dates": "ngày", "BindAddressHelpText": "Địa chỉ IP4 hợp lệ hoặc '*' cho tất cả các giao diện", "CertificateValidationHelpText": "Thay đổi cách xác thực chứng chỉ HTTPS nghiêm ngặt", - "MonoVersion": "Phiên bản Mono" + "MonoVersion": "Phiên bản Mono", + "EventType": "Loại sự kiện", + "Progress": "Phát triển", + "RejectionCount": "Số lượng từ chối", + "Title": "Tiêu đề", + "UnableToLoadInteractiveSerach": "Không thể tải kết quả cho tìm kiếm phim này. Thử lại sau", + "Activity": "Hoạt động", + "Add": "Thêm vào", + "AddDelayProfile": "Thêm cấu hình trễ", + "Added": "Thêm", + "AddIndexer": "Thêm trình lập chỉ mục", + "AddList": "Thêm vào danh sách", + "AddNew": "Thêm mới", + "AddQualityProfile": "Thêm hồ sơ chất lượng", + "AddRemotePathMapping": "Thêm ánh xạ đường dẫn từ xa", + "AddRootFolder": "Thêm thư mục gốc", + "AfterManualRefresh": "Sau khi làm mới thủ công", + "All": "Tất cả", + "AllFiles": "Tất cả các tệp", + "AllResultsFiltered": "Tất cả kết quả bị ẩn bởi bộ lọc được áp dụng", + "Always": "Luôn luôn", + "Apply": "Ứng dụng", + "AudioInfo": "Thông tin âm thanh", + "BeforeUpdate": "Trước khi cập nhật", + "Close": "Đóng", + "Connect": "Kết nối", + "Custom": "Tập quán", + "CustomFilters": "Bộ lọc tùy chỉnh", + "Date": "Ngày", + "DefaultDelayProfileHelpText": "Đây là cấu hình mặc định. Nó áp dụng cho tất cả các phim không có hồ sơ rõ ràng.", + "Deleted": "Đã xóa", + "Details": "Chi tiết", + "Donations": "Quyên góp", + "DoNotPrefer": "Không thích", + "DoNotUpgradeAutomatically": "Không nâng cấp tự động", + "DownloadFailed": "Tải xuống không thành công", + "EditDelayProfile": "Chỉnh sửa hồ sơ độ trễ", + "EditImportListExclusion": "Xóa loại trừ danh sách nhập", + "EditIndexer": "Chỉnh sửa trình lập chỉ mục", + "EditQualityProfile": "Chỉnh sửa hồ sơ chất lượng", + "EditRemotePathMapping": "Chỉnh sửa ánh xạ đường dẫn từ xa", + "EditRootFolder": "Thêm thư mục gốc", + "ErrorRestoringBackup": "Lỗi khi khôi phục bản sao lưu", + "Events": "Sự kiện", + "FreeSpace": "Không gian trông", + "General": "Chung", + "Grabbed": "Nắm lấy", + "HardlinkCopyFiles": "Hardlink / Sao chép tệp", + "HideAdvanced": "Ẩn nâng cao", + "Ignored": "Mặc kệ", + "Import": "Nhập khẩu", + "Info": "Thông tin", + "InteractiveImport": "Nhập tương tác", + "LastDuration": "lastDuration", + "LastExecution": "Lần thực hiện cuối cùng", + "LastUsed": "Được sử dụng lần cuối", + "LastWriteTime": "Lần viết cuối cùng", + "Location": "Vị trí", + "MediaManagement": "Quản lý truyền thông", + "Metadata": "metadata", + "MonitoredOnly": "Chỉ giám sát", + "MoveAutomatically": "Nhập nhanh", + "MoveFiles": "Di chuyển tệp", + "NextExecution": "Thực hiện tiếp theo", + "OnlyTorrent": "Chỉ Torrent", + "OnlyUsenet": "Chỉ Usenet", + "Organize": "Tổ chức", + "OutputPath": "Đường đầu ra", + "Peers": "Ngang hàng", + "PreferAndUpgrade": "Ưu tiên và nâng cấp", + "Presets": "Cài đặt trước", + "QualityLimitsHelpText": "Các giới hạn được tự động điều chỉnh cho thời gian chạy phim.", + "Queued": "Đã xếp hàng", + "Rating": "Xếp hạng", + "ReleaseTitle": "Tiêu đề phát hành", + "Renamed": "Đã đổi tên", + "Replace": "Thay thế", + "RestartRequiredHelpTextWarning": "Yêu cầu khởi động lại để có hiệu lực", + "RestoreBackupAdditionalInfo": "Lưu ý: Radarr sẽ tự động khởi động lại và tải lại giao diện người dùng trong quá trình khôi phục.", + "Save": "Tiết kiệm", + "Seeders": "Máy gieo hạt", + "Select...": "'Lựa chọn...", + "SelectFolder": "Chọn thư mục", + "SelectQuality": "Chọn chất lượng", + "ShowAdvanced": "Hiển thị nâng cao", + "SizeOnDisk": "Kích thước trên đĩa", + "SomeResultsFiltered": "Một số kết quả bị ẩn bởi bộ lọc được áp dụng", + "SourceTitle": "Tiêu đề nguồn", + "TimeLeft": "Thời gian còn lại", + "TotalSpace": "Tổng không gian", + "UnmappedFilesOnly": "Chỉ các tệp chưa được ánh xạ", + "UnmonitoredOnly": "Chỉ giám sát", + "UpgradesAllowed": "Nâng cấp được phép", + "Wanted": "Muốn", + "Warn": "Cảnh báo", + "WouldYouLikeToRestoreBackup": "Bạn có muốn khôi phục bản sao lưu {0} không?", + "Manual": "Thủ công", + "Test": "Kiểm tra", + "UI": "Giao diện người dùng", + "Backup": "Sao lưu", + "Filters": "Bộ lọc", + "Genres": "Thể loại", + "AddImportListExclusion": "Xóa loại trừ danh sách nhập", + "Age": "Tuổi tác", + "Error": "lỗi", + "NoResults": "không tìm thấy kết quả nào", + "NoTagsHaveBeenAddedYet": "Chưa có thẻ nào được thêm vào", + "Ok": "Đồng ý", + "System": "Hệ thống" } diff --git a/src/NzbDrone.Core/Localization/Core/zh_CN.json b/src/NzbDrone.Core/Localization/Core/zh_CN.json index bf5af7c66..86241eacc 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_CN.json +++ b/src/NzbDrone.Core/Localization/Core/zh_CN.json @@ -515,5 +515,253 @@ "MetadataProfile": "元数据配置", "MetadataProfiles": "元数据配置", "Term": "项", - "MonoVersion": "Mono版本" + "MonoVersion": "Mono版本", + "AddMetadataProfile": "元数据配置", + "AddNew": "添加电影", + "AddRootFolder": "添加根目录", + "Albums": "专辑", + "Connect": "通知连接", + "DoNotPrefer": "不要首选", + "ExistingAlbumsData": "监控书籍有文件或尚未发布", + "Ignored": "已忽略", + "IncludePreferredWhenRenaming": "重命名时包括首选", + "Location": "位置", + "Ok": "完成", + "Presets": "预设", + "ShowAdvanced": "显示高级设置", + "SizeOnDisk": "占用磁盘体积", + "MetadataConsumers": "用户元数据", + "MetadataProfileIdHelpText": "元数据配置文件列表项应添加", + "PreviewRetag": "预览重新标记", + "TrackNumber": "跟踪编号", + "WatchRootFoldersForFileChanges": "监测根目录文件夹的文件更改", + "WriteAudioTagsHelpTextWarning": "选择“所有文件”将在导入时更改现有文件。", + "MissingAlbumsData": "监控没有文件或尚未发布的书籍", + "OnReleaseImport": "在发行导入时", + "PreferredHelpTexts3": "低分将不优先", + "QualityProfileIdHelpText": "质量配置列表项应该被添加", + "ReleaseProfiles": "刷新配置文件", + "RootFolderPathHelpText": "根目录文件夹列表项需添加", + "ScrubAudioTagsHelpText": "从文件中删除现有标签,只留下Readarr添加的标签。", + "ScrubExistingTags": "覆盖现有标签", + "SearchForAllCutoffUnmetAlbums": "搜索所有Cutoff Unmet书籍", + "SearchForMonitoredAlbums": "搜索监控中的书籍", + "SearchMonitored": "搜索已监控", + "ShowTitleHelpText": "在海报下显示作者姓名", + "TrackFileCounttotalTrackCountTracksDownloadedInterp": "{0}/{1} 书籍已下载", + "TrackFileCountTrackCountTotalTotalTrackCountInterp": "{0} / {1} (全部: {2})", + "UnableToLoadMetadataProviderSettings": "无法加载元数据源设置", + "UnmappedFiles": "未映射文件", + "UpdatingIsDisabledInsideADockerContainerUpdateTheContainerImageInstead": "更新在docker容器内被禁用. 改为更新容器映像。", + "OnDownloadFailure": "在下载失败时", + "OnDownloadFailureHelpText": "在下载失败时", + "PathHelpText": "根目录文件夹包含您的书籍库", + "PathHelpTextWarning": "这必须与下载客户端放置文件的目录不同", + "SearchForAllMissingAlbums": "搜索所有丢失的书籍", + "WatchLibraryForChangesHelpText": "当根目录文件夹中的文件发生变化时,会自动重新扫描", + "MusicbrainzId": "Musicbrainz ID", + "DefaultLidarrTags": "默认Readarr标签", + "DefaultMetadataProfileIdHelpText": "此文件夹中检测到的作者的默认元数据配置文件", + "DefaultQualityProfileIdHelpText": "此文件夹中检测到的作者的默认质量配置", + "DefaultTagsHelpText": "对于在此文件夹中检测到的作者使用默认Readarr标签", + "DefaultMonitorOptionHelpText": "对于在此文件夹中检测到的作者,应在初始添加时监控哪些书籍", + "ExpandOtherByDefaultHelpText": "其他", + "FirstAlbumData": "监控第一本书。其他所有书籍都将被忽略", + "ForeignIdHelpText": "作者/书籍的Musicbrainz ID排除", + "FutureAlbumsData": "监控尚未发布的书籍", + "FutureDays": "未来天数", + "FutureDaysHelpText": "iCal订阅地址等待天数", + "GoToArtistListing": "转到作者列表", + "IsShowingMonitoredMonitorSelected": "监控选中的", + "IsShowingMonitoredUnmonitorSelected": "未监控选中的", + "PreferredHelpTexts2": "高得分将更优先", + "Activity": "活动", + "Add": "添加", + "AddDelayProfile": "添加延时配置", + "Added": "已添加", + "AddImportListExclusion": "导入列表例外", + "AddIndexer": "添加索引器", + "AddList": "添加列表", + "AddQualityProfile": "添加电影质量配置", + "AddRemotePathMapping": "添加远程目录映射", + "AfterManualRefresh": "在手动更新之后", + "Age": "年龄", + "All": "全部", + "AllFiles": "全部文件", + "AllMonitoringOptionHelpText": "监控作者们及导入列表中每个作者的所有书籍", + "AllResultsFiltered": "根据过滤条件所有结果已隐藏", + "Always": "总是", + "ApplicationURL": "程序URL", + "ApplicationUrlHelpText": "此应用的外部URL包含 http(s)://,端口和基本URL", + "Apply": "应用", + "ArtistNameHelpText": "要排除的作者/书籍的名称(任何有涵义的均可)", + "AudioInfo": "音频编码", + "Backup": "备份", + "BeforeUpdate": "更新前", + "CatalogNumber": "目录编号", + "ChownGroup": "修改组权限", + "Close": "关闭", + "CollapseMultipleAlbumsHelpText": "折叠在同日发行的多本书籍", + "Continuing": "仍在继续", + "ContinuingAllTracksDownloaded": "仍在继续(所有书籍已下载)", + "ContinuingNoAdditionalAlbumsAreExpected": "预计不会有其他书籍", + "Country": "国家", + "CustomFilters": "自定义过滤", + "Date": "日期", + "DefaultDelayProfileHelpText": "这是默认配置档案,它适用于所有没有明确配置档案的影片。", + "Deleted": "已删除", + "DeleteFilesHelpText": "删除书籍文件及作者文件夹", + "DeleteImportList": "删除导入的列表", + "DeleteMetadataProfile": "删除元数据配置", + "DeleteRootFolder": "删除根目录", + "Details": "详情", + "Donations": "捐赠", + "DoNotUpgradeAutomatically": "不要自动升级", + "DownloadFailed": "下载失败", + "DownloadPropersAndRepacksHelpTexts2": "使用“不要偏好”按首字母而不是专有/重新打包进行排序", + "EditConnection": "编辑集合", + "EditDelayProfile": "编辑延时配置", + "EditImportListExclusion": "删除导入排除列表", + "EditIndexer": "编辑索引器", + "EditList": "编辑列表", + "EditQualityProfile": "编辑质量档案", + "EditRemotePathMapping": "编辑远程映射路径", + "EditRootFolder": "添加根目录", + "EnableAutomaticAddHelpText": "当通过UI或Readarr执行同步时,将作者/书籍添加到Readarr", + "EnabledHelpText": "检查以启用发布配置文件", + "EnableProfile": "启用配置文件", + "EndedAllTracksDownloaded": "完成(所以书籍已下载)", + "EntityName": "实体名称", + "Error": "错误", + "ErrorRestoringBackup": "恢复备份错误", + "Events": "事件", + "EventType": "事件类型", + "ExistingTagsScrubbed": "已删除现有标签", + "Filters": "过滤器", + "FreeSpace": "剩余空间", + "General": "通用", + "Genres": "风格", + "Grabbed": "已抓取", + "HardlinkCopyFiles": "硬链接/复制文件", + "HideAdvanced": "隐藏高级设置", + "IfYouDontAddAnImportListExclusionAndTheArtistHasAMetadataProfileOtherThanNoneThenThisAlbumMayBeReaddedDuringTheNextArtistRefresh": "如果您不添加导入列表例外,并且作者元数据配置不是“无”,那这本书可能会在下次作者刷新是重新添加", + "Import": "导入", + "ImportFailures": "导入失败", + "ImportListExclusions": "导入列表例外", + "ImportLists": "导入列表", + "ImportListSettings": "常规导入列表设置", + "ImportListSpecificSettings": "导入列表特定设置", + "IndexerDownloadClientHelpText": "指定索引器的下载客户端", + "IndexerIdHelpText": "指定配置文件应用于哪个索引器", + "IndexerIdHelpTextWarning": "使用带有首字母的特定索引器可能会导致复制版本被抓取", + "IndexerIdvalue0IncludeInPreferredWordsRenamingFormat": "以 {Preferred Words} 重命名格式包含", + "IndexerIdvalue0OnlySupportedWhenIndexerIsSetToAll": "仅当索引器设置为 (全部)时才支持", + "IndexerTagHelpText": "仅对至少有一个匹配标记的电影使用此索引器。留空则适用于所有电影。", + "Info": "信息", + "InstanceName": "中文", + "InstanceNameHelpText": "选项卡及日志应用名称", + "InteractiveImport": "手动导入", + "LastDuration": "上一次用时", + "LastExecution": "上一次执行", + "LastUsed": "上次使用", + "LastWriteTime": "最后写入时间", + "Library": "库", + "LidarrSupportsMultipleListsForImportingAlbumsAndArtistsIntoTheDatabase": "Readarr支持将书籍和作者导入数据库的多个列表。", + "Manual": "手动", + "ManualDownload": "手动下载", + "MassEditor": "批量编辑器", + "MediaManagement": "媒体管理", + "Metadata": "元数据", + "MetadataProviderSource": "元数据提供源", + "MetadataSource": "元数据源", + "MetadataSourceHelpText": "备选元数据源(默认为空)", + "MonitorAlbumExistingOnlyWarning": "这是对每本书籍的监控设置的一次性调整 使用作者/编辑下的选项来控制新添加的书籍将如何", + "MonitoredOnly": "监控中", + "MonitoringOptions": "监控选项", + "MonitoringOptionsHelpText": "添加作者后应该监控哪些书籍(一次性调整)", + "MonitorNewItemsHelpText": "哪些新书应被监控", + "MoveFiles": "移动文件", + "MusicBrainzArtistID": "MusicBrainz作者ID", + "MusicBrainzRecordingID": "MusicBrainz 唱片ID", + "MusicBrainzReleaseID": "MusicBrainz发行ID", + "MusicBrainzTrackID": "MusicBrainz曲目ID", + "Never": "永不", + "NextExecution": "接下来执行", + "NoneData": "不会监控任何书籍", + "NoResults": "无结果", + "NoTagsHaveBeenAddedYet": "未添加标签", + "OnImportFailure": "在导入失败时", + "OnlyTorrent": "只有torrent", + "OnlyUsenet": "只有usenet", + "OnReleaseImportHelpText": "在发行导入时", + "PastDays": "过去天数", + "PastDaysHelpText": "iCal订阅地址 过去天数", + "Peers": "用户", + "PreferAndUpgrade": "首选并升级", + "PreferredHelpTexts1": "发行将优先根据每个期间评分(不区分大小写)", + "PreferredProtocol": "首选协议", + "Progress": "进度", + "QualityLimitsHelpText": "影片运行时会自动调整限制。", + "Queued": "队列中", + "Rating": "评分", + "RejectionCount": "拒绝次数", + "ReleaseTitle": "影片发布标题", + "Renamed": "已重命名", + "Replace": "替换", + "RestartRequiredHelpTextWarning": "需重启以生效", + "RestoreBackupAdditionalInfo": "注意:Radarr将在恢复过程中自动重启并重新加载UI。", + "Save": "保存", + "SecificMonitoringOptionHelpText": "监控作者,但只监控明确包含在列表中的书籍", + "Seeders": "种子", + "Select...": "'选择...", + "SelectedCountArtistsSelectedInterp": "{0} 作者选中", + "SelectFolder": "选择文件夹", + "SelectQuality": "选择质量", + "ShouldMonitorExistingHelpText": "自动监控此列表中已经在Readarr中的书籍", + "ShouldSearch": "搜索新项目", + "ShouldSearchHelpText": "在索引器中搜索新添加的项目, 小心使用长列表。", + "ShowBanners": "显示横幅", + "ShowBannersHelpText": "显示横幅而不是名称", + "ShowName": "显示名称", + "SizeLimit": "尺寸限制", + "SomeResultsFiltered": "部分结果已被过滤隐藏", + "SourceTitle": "来源标题", + "Started": "已开始", + "System": "系统", + "TagAudioFilesWithMetadata": "使用元数据标记音频文件", + "Test": "测试", + "TheAlbumsFilesWillBeDeleted": "此书籍文件将删除。", + "TimeLeft": "剩余时间", + "Title": "标题", + "TotalSpace": "总空间", + "TotalTrackCountTracksTotalTrackFileCountTracksWithFilesInterp": "共{0}本书籍 . {1} 有文件.", + "TrackTitle": "跟踪标题", + "UI": "UI界面", + "UnableToLoadInteractiveSerach": "无法加载该影片的搜索结果,请稍后重试", + "UnmappedFilesOnly": "未映射的文件", + "UnmonitoredOnly": "监控中", + "UpgradesAllowed": "允许升级", + "Wanted": "想要的", + "Warn": "警告", + "WouldYouLikeToRestoreBackup": "想要恢复备份 {0} 吗?", + "WriteMetadataTags": "编写元数据标签", + "WriteMetadataToAudioFiles": "将元数据写入音频文件", + "OnImportFailureHelpText": "在导入失败时", + "SkipRedownload": "跳过重新下载", + "SkipredownloadHelpText": "阻止Readarr尝试为删除的项目下载替代版本", + "DiscCount": "磁盘数量", + "DiscNumber": "磁盘编号", + "AddedArtistSettings": "已添加作者设置", + "IsExpandedHideFileInfo": "隐藏文件信息", + "IsExpandedShowFileInfo": "显示文件信息", + "IsInUseCantDeleteAMetadataProfileThatIsAttachedToAnArtistOrImportList": "无法删除元数据配置文件,其已经附加到作者或为导入列表", + "IsInUseCantDeleteAQualityProfileThatIsAttachedToAnArtistOrImportList": "无法删除质量配置文件,其已附加到作者或为导入列表", + "MoveAutomatically": "自动移动", + "StatusEndedContinuing": "仍在继续", + "AddConnection": "编辑集合", + "Custom": "自定义", + "EditMetadataProfile": "元数据配置", + "Organize": "整理", + "Other": "其他", + "OutputPath": "输出路径" } diff --git a/src/NzbDrone.Core/Localization/Core/zh_TW.json b/src/NzbDrone.Core/Localization/Core/zh_TW.json index 7575c4004..52d1cdffd 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_TW.json +++ b/src/NzbDrone.Core/Localization/Core/zh_TW.json @@ -2,5 +2,13 @@ "About": "關於", "Actions": "執行", "AddingTag": "新增標籤", - "Analytics": "分析" + "Analytics": "分析", + "All": "全部", + "EditDelayProfile": "新增延時配置", + "Add": "新增", + "AddDelayProfile": "新增延時配置", + "Added": "已新增", + "AddIndexer": "新增索引", + "AddList": "新增列表", + "Age": "年齡" } From 6c7a578cb762287222c6b59944df3aaf1b3a9753 Mon Sep 17 00:00:00 2001 From: Vincent Simon Date: Tue, 10 May 2022 22:24:14 +0200 Subject: [PATCH 0059/1478] Added missing Audio drama type --- .../Files/Identification/CorruptFile.json | 7 ++ .../Files/Identification/FilesWithMBIds.json | 7 ++ .../Identification/FilesWithoutTags.json | 7 ++ .../InconsistentTyposInAlbum.json | 7 ++ .../Identification/PenalizeUnknownMedia.json | 7 ++ .../PreferMissingToBadMatch.json | 7 ++ .../SucceedWhenManyAlbumsHaveSameTitle.json | 7 ++ .../Migration/060_update_audio_types.cs | 73 +++++++++++++++++++ .../MetadataSource/SkyHook/SkyHookProxy.cs | 2 + .../Music/Model/SecondaryAlbumType.cs | 4 +- 10 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 src/NzbDrone.Core/Datastore/Migration/060_update_audio_types.cs diff --git a/src/NzbDrone.Core.Test/Files/Identification/CorruptFile.json b/src/NzbDrone.Core.Test/Files/Identification/CorruptFile.json index 540e661c1..55962fb84 100644 --- a/src/NzbDrone.Core.Test/Files/Identification/CorruptFile.json +++ b/src/NzbDrone.Core.Test/Files/Identification/CorruptFile.json @@ -114,6 +114,13 @@ "name": "Compilation" }, "allowed": false + }, + { + "secondaryAlbumType": { + "id": 11, + "name": "Audio drama" + }, + "allowed": false } ], "releaseStatuses": [ diff --git a/src/NzbDrone.Core.Test/Files/Identification/FilesWithMBIds.json b/src/NzbDrone.Core.Test/Files/Identification/FilesWithMBIds.json index c2d76c358..a4e24b413 100644 --- a/src/NzbDrone.Core.Test/Files/Identification/FilesWithMBIds.json +++ b/src/NzbDrone.Core.Test/Files/Identification/FilesWithMBIds.json @@ -116,6 +116,13 @@ "name": "Compilation" }, "allowed": false + }, + { + "secondaryAlbumType": { + "id": 11, + "name": "Audio drama" + }, + "allowed": false } ], "releaseStatuses": [ diff --git a/src/NzbDrone.Core.Test/Files/Identification/FilesWithoutTags.json b/src/NzbDrone.Core.Test/Files/Identification/FilesWithoutTags.json index a28e3a162..766326355 100644 --- a/src/NzbDrone.Core.Test/Files/Identification/FilesWithoutTags.json +++ b/src/NzbDrone.Core.Test/Files/Identification/FilesWithoutTags.json @@ -114,6 +114,13 @@ "name": "Compilation" }, "allowed": false + }, + { + "secondaryAlbumType": { + "id": 11, + "name": "Audio drama" + }, + "allowed": false } ], "releaseStatuses": [ diff --git a/src/NzbDrone.Core.Test/Files/Identification/InconsistentTyposInAlbum.json b/src/NzbDrone.Core.Test/Files/Identification/InconsistentTyposInAlbum.json index 218b5918f..b2822ce3b 100644 --- a/src/NzbDrone.Core.Test/Files/Identification/InconsistentTyposInAlbum.json +++ b/src/NzbDrone.Core.Test/Files/Identification/InconsistentTyposInAlbum.json @@ -114,6 +114,13 @@ "name": "Compilation" }, "allowed": false + }, + { + "secondaryAlbumType": { + "id": 11, + "name": "Audio drama" + }, + "allowed": false } ], "releaseStatuses": [ diff --git a/src/NzbDrone.Core.Test/Files/Identification/PenalizeUnknownMedia.json b/src/NzbDrone.Core.Test/Files/Identification/PenalizeUnknownMedia.json index f9b486449..ae1c91be8 100644 --- a/src/NzbDrone.Core.Test/Files/Identification/PenalizeUnknownMedia.json +++ b/src/NzbDrone.Core.Test/Files/Identification/PenalizeUnknownMedia.json @@ -114,6 +114,13 @@ "name": "Compilation" }, "allowed": false + }, + { + "secondaryAlbumType": { + "id": 11, + "name": "Audio drama" + }, + "allowed": false } ], "releaseStatuses": [ diff --git a/src/NzbDrone.Core.Test/Files/Identification/PreferMissingToBadMatch.json b/src/NzbDrone.Core.Test/Files/Identification/PreferMissingToBadMatch.json index 6f3302637..0259e5edf 100644 --- a/src/NzbDrone.Core.Test/Files/Identification/PreferMissingToBadMatch.json +++ b/src/NzbDrone.Core.Test/Files/Identification/PreferMissingToBadMatch.json @@ -114,6 +114,13 @@ "name": "Studio" }, "allowed": true + }, + { + "secondaryAlbumType": { + "id": 11, + "name": "Audio drama" + }, + "allowed": false } ], "releaseStatuses": [ diff --git a/src/NzbDrone.Core.Test/Files/Identification/SucceedWhenManyAlbumsHaveSameTitle.json b/src/NzbDrone.Core.Test/Files/Identification/SucceedWhenManyAlbumsHaveSameTitle.json index d8fcdd9f5..a5213ec5c 100644 --- a/src/NzbDrone.Core.Test/Files/Identification/SucceedWhenManyAlbumsHaveSameTitle.json +++ b/src/NzbDrone.Core.Test/Files/Identification/SucceedWhenManyAlbumsHaveSameTitle.json @@ -114,6 +114,13 @@ "name": "Compilation" }, "allowed": false + }, + { + "secondaryAlbumType": { + "id": 11, + "name": "Audio drama" + }, + "allowed": false } ], "releaseStatuses": [ diff --git a/src/NzbDrone.Core/Datastore/Migration/060_update_audio_types.cs b/src/NzbDrone.Core/Datastore/Migration/060_update_audio_types.cs new file mode 100644 index 000000000..1db54ebba --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/060_update_audio_types.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using System.Data; +using System.Text.Json; +using System.Text.Json.Serialization; +using Dapper; +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(060)] + public class update_audio_types : NzbDroneMigrationBase + { + private readonly JsonSerializerOptions _serializerSettings; + + public update_audio_types() + { + _serializerSettings = new JsonSerializerOptions + { + AllowTrailingCommas = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true + }; + } + + protected override void MainDbUpgrade() + { + Execute.WithConnection(GetEntries); + } + + private void GetEntries(IDbConnection conn, IDbTransaction tran) + { + var profiles = conn.Query($"SELECT \"Id\", \"SecondaryAlbumTypes\" FROM \"MetadataProfiles\""); + + var corrected = new List(); + + foreach (var profile in profiles) + { + var oldTypes = JsonSerializer.Deserialize>(profile.SecondaryAlbumTypes, _serializerSettings); + + oldTypes.Add(new SecondaryAlbumType059 + { + SecondaryAlbumType = 11, + Allowed = false + }); + + corrected.Add(new MetadataProfile059 + { + Id = profile.Id, + SecondaryAlbumTypes = JsonSerializer.Serialize(oldTypes, _serializerSettings) + }); + } + + var updateSql = $"UPDATE \"MetadataProfiles\" SET \"SecondaryAlbumTypes\" = @SecondaryAlbumTypes WHERE \"Id\" = @Id"; + conn.Execute(updateSql, corrected, transaction: tran); + } + + private class MetadataProfile059 + { + public int Id { get; set; } + public string SecondaryAlbumTypes { get; set; } + } + + private class SecondaryAlbumType059 + { + public int SecondaryAlbumType { get; set; } + public bool Allowed { get; set; } + } + } +} diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index 103bcf49e..3701c0509 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -652,6 +652,8 @@ namespace NzbDrone.Core.MetadataSource.SkyHook return SecondaryAlbumType.Mixtape; case "demo": return SecondaryAlbumType.Demo; + case "audio drama": + return SecondaryAlbumType.Audiodrama; default: return SecondaryAlbumType.Studio; } diff --git a/src/NzbDrone.Core/Music/Model/SecondaryAlbumType.cs b/src/NzbDrone.Core/Music/Model/SecondaryAlbumType.cs index ac9202eda..9b19b1ee2 100644 --- a/src/NzbDrone.Core/Music/Model/SecondaryAlbumType.cs +++ b/src/NzbDrone.Core/Music/Model/SecondaryAlbumType.cs @@ -76,6 +76,7 @@ namespace NzbDrone.Core.Music public static SecondaryAlbumType DJMix => new SecondaryAlbumType(8, "DJ-mix"); public static SecondaryAlbumType Mixtape => new SecondaryAlbumType(9, "Mixtape/Street"); public static SecondaryAlbumType Demo => new SecondaryAlbumType(10, "Demo"); + public static SecondaryAlbumType Audiodrama => new SecondaryAlbumType(11, "Audio drama"); public static readonly List All = new List { @@ -88,7 +89,8 @@ namespace NzbDrone.Core.Music Remix, DJMix, Mixtape, - Demo + Demo, + Audiodrama }; public static SecondaryAlbumType FindById(int id) From 56ca149df90a0f8046888d523c9a0be46dfa795e Mon Sep 17 00:00:00 2001 From: Qstick Date: Fri, 4 Nov 2022 15:31:47 -0500 Subject: [PATCH 0060/1478] Bump version 1.1.2 --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 01944fc6b..f6c3cd2bd 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: '1.1.1' + majorVersion: '1.1.2' minorVersion: $[counter('minorVersion', 1076)] lidarrVersion: '$(majorVersion).$(minorVersion)' buildName: '$(Build.SourceBranchName).$(lidarrVersion)' From 727526196028780903459d3f400f448f3b197f05 Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 31 Oct 2022 19:37:05 -0500 Subject: [PATCH 0061/1478] New: Base API info endpoint (cherry picked from commit 5e57ffbcf9ac3a346d4bf2b692248393215bad89) --- src/Lidarr.Http/ApiInfoController.cs | 23 +++++++++++++++++++++++ src/Lidarr.Http/ApiInfoResource.cs | 14 ++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/Lidarr.Http/ApiInfoController.cs create mode 100644 src/Lidarr.Http/ApiInfoResource.cs diff --git a/src/Lidarr.Http/ApiInfoController.cs b/src/Lidarr.Http/ApiInfoController.cs new file mode 100644 index 000000000..01ac5ab79 --- /dev/null +++ b/src/Lidarr.Http/ApiInfoController.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; + +namespace Lidarr.Http +{ + public class ApiInfoController : Controller + { + public ApiInfoController() + { + } + + [HttpGet("/api")] + [Produces("application/json")] + public object GetApiInfo() + { + return new ApiInfoResource + { + Current = "v1", + Deprecated = new List() + }; + } + } +} diff --git a/src/Lidarr.Http/ApiInfoResource.cs b/src/Lidarr.Http/ApiInfoResource.cs new file mode 100644 index 000000000..f516daabf --- /dev/null +++ b/src/Lidarr.Http/ApiInfoResource.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Lidarr.Http +{ + public class ApiInfoResource + { + public string Current { get; set; } + public List Deprecated { get; set; } + } +} From 39c3993759e37db6a702505f9458d2169304eabc Mon Sep 17 00:00:00 2001 From: Servarr Date: Mon, 7 Nov 2022 01:10:37 +0000 Subject: [PATCH 0062/1478] Automated API Docs update --- src/Lidarr.Api.V1/openapi.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Lidarr.Api.V1/openapi.json b/src/Lidarr.Api.V1/openapi.json index bb8fbc8b3..20328102a 100644 --- a/src/Lidarr.Api.V1/openapi.json +++ b/src/Lidarr.Api.V1/openapi.json @@ -347,6 +347,18 @@ } } }, + "/api": { + "get": { + "tags": [ + "ApiInfo" + ], + "responses": { + "200": { + "description": "Success" + } + } + } + }, "/api/v1/artist/{id}": { "get": { "tags": [ From 4eb6a15bc79416f05222e3fcb11a310835441ce8 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 7 Nov 2022 18:58:40 -0800 Subject: [PATCH 0063/1478] Fixed: Testing SABnzbd when no categories are configured (cherry picked from commit 0e31281828c737e3f6eecbb870960194888a970a) --- .../Download/Clients/Sabnzbd/SabnzbdCategory.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs index 61b5f9228..e25a91701 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Newtonsoft.Json; using NzbDrone.Common.Disk; using NzbDrone.Core.Download.Clients.Sabnzbd.JsonConverters; @@ -7,10 +7,14 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd { public class SabnzbdConfig { + public SabnzbdConfig() + { + Categories = new List(); + Servers = new List(); + } + public SabnzbdConfigMisc Misc { get; set; } - public List Categories { get; set; } - public List Servers { get; set; } } From 11ba7454398c52434a91bbb6d28fcfbc7f8cfba6 Mon Sep 17 00:00:00 2001 From: Weblate Date: Wed, 16 Nov 2022 12:09:44 +0000 Subject: [PATCH 0064/1478] Translated using Weblate (Portuguese (Brazil)) [skip ci] Currently translated at 99.5% (874 of 878 strings) Translated using Weblate (Portuguese (Brazil)) [skip ci] Currently translated at 99.3% (872 of 878 strings) Translated using Weblate (French) [skip ci] Currently translated at 74.3% (653 of 878 strings) Translated using Weblate (Vietnamese) [skip ci] Currently translated at 66.4% (583 of 878 strings) Translated using Weblate (Thai) [skip ci] Currently translated at 66.4% (583 of 878 strings) Translated using Weblate (Slovak) [skip ci] Currently translated at 18.6% (164 of 878 strings) Translated using Weblate (Russian) [skip ci] Currently translated at 69.2% (608 of 878 strings) Translated using Weblate (Romanian) [skip ci] Currently translated at 66.6% (585 of 878 strings) Translated using Weblate (Japanese) [skip ci] Currently translated at 65.9% (579 of 878 strings) Translated using Weblate (Italian) [skip ci] Currently translated at 72.0% (633 of 878 strings) Translated using Weblate (Icelandic) [skip ci] Currently translated at 66.0% (580 of 878 strings) Translated using Weblate (Hungarian) [skip ci] Currently translated at 100.0% (878 of 878 strings) Translated using Weblate (Hindi) [skip ci] Currently translated at 65.9% (579 of 878 strings) Translated using Weblate (Hebrew) [skip ci] Currently translated at 67.0% (589 of 878 strings) Translated using Weblate (French) [skip ci] Currently translated at 74.3% (653 of 878 strings) Translated using Weblate (Finnish) [skip ci] Currently translated at 77.2% (678 of 878 strings) Translated using Weblate (Spanish) [skip ci] Currently translated at 69.7% (612 of 878 strings) Co-authored-by: Anonymous Co-authored-by: Csaba Co-authored-by: Havok Dan Co-authored-by: Oskari Lavinto Co-authored-by: Weblate Co-authored-by: gougoumag06 Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/es/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fi/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fr/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/he/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hi/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hu/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/is/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/it/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ja/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ro/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ru/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/sk/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/th/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/vi/ Translation: Servarr/Lidarr --- src/NzbDrone.Core/Localization/Core/es.json | 3 +- src/NzbDrone.Core/Localization/Core/fi.json | 30 +++++------ src/NzbDrone.Core/Localization/Core/fr.json | 4 +- src/NzbDrone.Core/Localization/Core/he.json | 3 +- src/NzbDrone.Core/Localization/Core/hi.json | 3 +- src/NzbDrone.Core/Localization/Core/hu.json | 53 +++++++++++++++++-- src/NzbDrone.Core/Localization/Core/is.json | 3 +- src/NzbDrone.Core/Localization/Core/it.json | 3 +- src/NzbDrone.Core/Localization/Core/ja.json | 3 +- .../Localization/Core/pt_BR.json | 46 +++++++++++++++- src/NzbDrone.Core/Localization/Core/ro.json | 3 +- src/NzbDrone.Core/Localization/Core/ru.json | 3 +- src/NzbDrone.Core/Localization/Core/sk.json | 3 +- src/NzbDrone.Core/Localization/Core/th.json | 3 +- src/NzbDrone.Core/Localization/Core/vi.json | 3 +- 15 files changed, 134 insertions(+), 32 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/es.json b/src/NzbDrone.Core/Localization/Core/es.json index 78e85166f..9cbc93dfb 100644 --- a/src/NzbDrone.Core/Localization/Core/es.json +++ b/src/NzbDrone.Core/Localization/Core/es.json @@ -615,5 +615,6 @@ "Started": "Iniciado", "AddConnection": "Editar Colección", "AddImportListExclusion": "Borrar exclusión de lista de importación", - "EditMetadataProfile": "perfil de metadatos" + "EditMetadataProfile": "perfil de metadatos", + "ImportListExclusions": "Borrar exclusión de lista de importación" } diff --git a/src/NzbDrone.Core/Localization/Core/fi.json b/src/NzbDrone.Core/Localization/Core/fi.json index c9f5e18f5..36083d4d2 100644 --- a/src/NzbDrone.Core/Localization/Core/fi.json +++ b/src/NzbDrone.Core/Localization/Core/fi.json @@ -19,7 +19,7 @@ "ProxyBypassFilterHelpText": "Käytä erottimena ',' ja '*.' jokerimerkkinä aliverkkotunnuksille (esim. www.esimerkki.fi,*.esimerkki.fi)", "UnableToAddANewIndexerPleaseTryAgain": "Uuden tietolähteen lisäys epäonnistui. Yritä uudelleen.", "ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "Lue lisää tietolähteestä painamalla 'Lisätietoja'.", - "ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Lue lisää lataustyökalusta painamalla 'Lisätietoja'.", + "ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Lataustyökalukohtaisia tietoja saat painamalla lisätietopainikkeita.", "RssSyncIntervalHelpText": "Aikaväli minuutteina. Poista käytöstä asettamalla arvoksi '0' (tämä lopettaa julkaisujen automaattisen sieppauksen).", "ApplyTagsHelpTexts1": "Tunnisteisiin kohdistettavat toimenpiteet:", "DefaultLidarrTags": "Oletustunnisteet", @@ -81,7 +81,7 @@ "AutoRedownloadFailedHelpText": "Etsi ja yritä ladata toinen julkaisu automaattisesti", "BackupFolderHelpText": "Suhteelliset polut kohdistuvat sovelluksen AppData-kansioon.", "BackupIntervalHelpText": "Lidarrin tietokannan ja asetusten automaattisen varmuuskopioinnin suoritusaikaväli.", - "BindAddressHelpText": "Toimiva IPv4-osoite tai jokerimerkkinä '*' (tähti) kaikille yhteyksille.", + "BindAddressHelpText": "Toimiva IPv4-osoite tai '*' (tähti) kaikille yhteyksille.", "BindAddressHelpTextWarning": "Käyttöönotto vaatii uudelleenkäynnistyksen.", "BackupNow": "Varmuuskopioi nyt", "Backups": "Varmuuskopiointi", @@ -100,7 +100,7 @@ "CloneIndexer": "Kloonaa tietolähde", "CancelMessageText": "Haluatko varmasti perua tämän odottavan tehtävän?", "CertificateValidation": "Varmenteen vahvistus", - "CertificateValidationHelpText": "Valitse HTTPS-varmenteen vahvistuksen tarkkuus. Älä muuta, jollet ymmärrä tähän liittyviä riskejä.", + "CertificateValidationHelpText": "Muuta HTTPS-varmennevahvistuksen tarkkuutta. Älä muuta, jollet ymmärrä tähän liittyviä riskejä.", "ClientPriority": "Lataustyökalun painotus", "CloneProfile": "Kloonaa profiili", "ChangeFileDate": "Muuta tiedoston päiväys", @@ -166,7 +166,7 @@ "IncludeUnknownArtistItemsHelpText": "Näytä jonossa kohteet, joissa ei ole esittäjää. Tämä voi sisältää poistettuja esittäjiä, kappaleita tai mitä tahansa muuta Lidarrille luokiteltua.", "Interval": "Aikaväli", "IndexerSettings": "Tietolähteiden asetukset", - "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr tukee kaikkien Newznab-yhteensopivien lataustyökalujen ohella myös monia muita alla listattuja torrent- ja Usenet-lataustyökaluja.", + "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "Lidarr tukee monia suosittuja torrent- ja Usenet-lataustyökaluja.", "MetadataSettings": "Metatietojen asetukset", "MediaManagementSettings": "Medianhallinnan asetukset", "MaximumSizeHelpText": "Haettavan julkaisun enimmäiskoko megatavuina. Aseta nollaan asettamaan rajoittamaton.", @@ -252,7 +252,7 @@ "Search": "Haku", "Size": " Koko", "SkipFreeSpaceCheck": "Ohita vapaan levytilan tarkistus", - "SkipFreeSpaceCheckWhenImportingHelpText": "Käytä, kun vaapaata tilaa ei tunnisteta esittäjiesi pääkansiosta", + "SkipFreeSpaceCheckWhenImportingHelpText": "Käytä, kun vapaata tallennustilaa ei tunnisteta esittäjien juurikansiosta.", "WeekColumnHeader": "Viikkosarakkeen otsikko", "ChmodFolder": "chmod-kansio", "UnableToAddANewNotificationPleaseTryAgain": "Kytkennän lisäys epäonnistui. Yritä uudelleen.", @@ -333,7 +333,7 @@ "UseHardlinksInsteadOfCopy": "Käytä hardlink-kytköksiä", "MinimumFreeSpaceWhenImportingHelpText": "Estä tuonti, jos vapaan levytilan määrä sen jälkeen jää tässä määritetyn arvon alle.", "Folders": "Kansioiden käsittely", - "RecycleBinCleanupDaysHelpText": "Älä tyhjennä automaattisesti asettamalla arvoksi '0'.", + "RecycleBinCleanupDaysHelpText": "Poista automaattinen tyhjennys käytöstä asettamalla arvoksi '0'.", "RecycleBinCleanupDaysHelpTextWarning": "Tässä määritettyä aikaa vanhemmat tiedostot poistetaan roskakorista pysyvästi automaattisesti.", "RootFolders": "Pääkansiot", "Path": "Sijainti", @@ -354,7 +354,7 @@ "UnableToAddANewListPleaseTryAgain": "Tuontilistan lisäys epäonnistui. Yritä uudelleen.", "IncludeUnmonitored": "Sisällytä ei valvotut", "AllExpandedExpandAll": "Laajenna kaikki", - "Absolute": "Tarkka", + "Absolute": "Absoluuttinen", "AddMissing": "Lisää puuttuvat", "AddNewItem": "Lisää uusi kohde", "AllExpandedCollapseAll": "Supista kaikki", @@ -468,7 +468,7 @@ "RemoveFromQueue": "Poista jonosta", "RemoveHelpTextWarning": "Poistaminen poistaa latauksen ja tiedostot latausohjelmasta.", "RemoveSelected": "Poista valitut", - "RenameTracksHelpText": "Lidarr käyttää olemassa olevaa tiedostonimeä, jos uudelleennimeäminen ei ole käytössä.", + "RenameTracksHelpText": "Jos uudelleennimeäminen ei ole käytössä, käytetään nykyistä tiedostonimeä.", "Reorder": "Järjestä uudelleen", "RescanArtistFolderAfterRefresh": "Tarkista kirjailijakansio päivityksen jälkeen uudelleen", "ResetAPIKeyMessageText": "Haluatko varmasti uudistaa API-avaimesi?", @@ -484,7 +484,7 @@ "SuccessMyWorkIsDoneNoFilesToRename": "Menestys! Työni on valmis, ei nimettäviä tiedostoja.", "SuccessMyWorkIsDoneNoFilesToRetag": "Menestys! Työni on valmis, ei nimettäviä tiedostoja.", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Hakemistoa ei tueta tällä hakemistolla", - "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Profiilia käytetään automaattihaun yhteydessä, kun sellainen suoritetaan käyttöliittymästä tai Radarrin toimesta.", + "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByLidarr": "Profiilia käytetään automaattihaun yhteydessä, kun haku suoritetaan käyttöliittymästä tai Lidarrin toimesta.", "Tasks": "Tehtävät", "Term": "Ehto", "Type": "Tyyppi", @@ -547,9 +547,9 @@ "Tracks": "Jäljitys", "WatchLibraryForChangesHelpText": "Suorita automaattinen uudelleentutkinta, kun juurikansiossa havaitaan tiedostomuutoksia.", "AddedArtistSettings": "Uuden kirjailijan oletusasetukset", - "MonitorAlbumExistingOnlyWarning": "Tämä on kirjakohtaisen valvonnan kertaluontoinen määritys. Käytä Kirjailija/Muokkaa-valintaa hallinnoidaksesi mitä uusille kirjalisäyksille tehdään.", - "MonitoringOptionsHelpText": "Kansiosta löydetyille kirjailijoille oletusarvoisesti asetettava kirjojen valvontataso (kertaluontoinen määritys).", - "MonitorNewItemsHelpText": "Uusien kirjojen valvontatapa.", + "MonitorAlbumExistingOnlyWarning": "Tämä on albumikohtaisen valvonnan kertaluontoinen määritys. Määritä Esittäjä/Muokkaa-valinnalla mitä uusille albumilisäyksille tehdään.", + "MonitoringOptionsHelpText": "Mitkä albumit asetetaan valvottaviksi esittäjän lisäyksen yhteydessä (kertaluontoinen määritys).", + "MonitorNewItemsHelpText": "Uusien albumien valvontatapa.", "MonoVersion": "Mono-versio", "AddDelayProfile": "Lisää viiveprofiili", "Added": "Lisätty", @@ -644,7 +644,7 @@ "Select...": "Valitse...", "SelectFolder": "Valitse kansio", "SelectQuality": "Valitse Laatu", - "ShouldMonitorExistingHelpText": "Valvo automaattisesti tällä listalla olevia kirjoja, jotka ovat jo kirjastossasi.", + "ShouldMonitorExistingHelpText": "Valvo automaattisesti tällä listalla olevia albumeita, jotka ovat jo kirjastossasi.", "ShowAdvanced": "Näytä edistyneet", "SizeLimit": "Kokorajoitus", "SizeOnDisk": "Koko levyllä", @@ -657,7 +657,7 @@ "Title": "Nimike", "TotalSpace": "Kokonaistila", "UI": "Käyttöliittymä", - "UnableToLoadInteractiveSerach": "Elokuvahaun tuloksien lataus epäonnistui. Yritä myöhemmin uudelleen.", + "UnableToLoadInteractiveSerach": "Albumihaun tuloksien lataus epäonnistui. Yritä myöhemmin uudelleen.", "UnmappedFilesOnly": "Vain kartoittamattomat tiedostot", "UnmonitoredOnly": "Valvottu", "UpgradesAllowed": "Päivitykset sallitaan", @@ -675,6 +675,6 @@ "Activity": "Tapahtumat", "EditDelayProfile": "Muokkaa viiveprofiilia", "Add": "Lisää", - "AddConnection": "Muokkaa kokoelmaa", + "AddConnection": "Lisää yhteys", "EditMetadataProfile": "Metatietoprofiili" } diff --git a/src/NzbDrone.Core/Localization/Core/fr.json b/src/NzbDrone.Core/Localization/Core/fr.json index 366f76bb8..77d6b43ac 100644 --- a/src/NzbDrone.Core/Localization/Core/fr.json +++ b/src/NzbDrone.Core/Localization/Core/fr.json @@ -666,5 +666,7 @@ "AddDelayProfile": "Ajouter un profil différé", "AddImportListExclusion": "Supprimer les exclusions de liste d'imports", "EditMetadataProfile": "profil de métadonnées", - "AddConnection": "Modifier la collection" + "AddConnection": "Modifier la collection", + "ImportListExclusions": "Supprimer les exclusions de liste d'imports", + "LastDuration": "Dernière durée" } diff --git a/src/NzbDrone.Core/Localization/Core/he.json b/src/NzbDrone.Core/Localization/Core/he.json index 74868db69..a4e359f4b 100644 --- a/src/NzbDrone.Core/Localization/Core/he.json +++ b/src/NzbDrone.Core/Localization/Core/he.json @@ -590,5 +590,6 @@ "OnlyTorrent": "רק סיקור", "OnlyUsenet": "רק Usenet", "Organize": "לְאַרגֵן", - "AddImportListExclusion": "מחק אי הכללת רשימות ייבוא" + "AddImportListExclusion": "מחק אי הכללת רשימות ייבוא", + "ImportListExclusions": "מחק אי הכללת רשימות ייבוא" } diff --git a/src/NzbDrone.Core/Localization/Core/hi.json b/src/NzbDrone.Core/Localization/Core/hi.json index d3ecc3e10..eace1bc53 100644 --- a/src/NzbDrone.Core/Localization/Core/hi.json +++ b/src/NzbDrone.Core/Localization/Core/hi.json @@ -581,5 +581,6 @@ "Peers": "साथियों", "PreferAndUpgrade": "प्राथमिकता और उन्नयन", "System": "प्रणाली", - "AddImportListExclusion": "आयात सूची बहिष्करण हटाएं" + "AddImportListExclusion": "आयात सूची बहिष्करण हटाएं", + "ImportListExclusions": "आयात सूची बहिष्करण हटाएं" } diff --git a/src/NzbDrone.Core/Localization/Core/hu.json b/src/NzbDrone.Core/Localization/Core/hu.json index 0171ec425..8c82ad4d6 100644 --- a/src/NzbDrone.Core/Localization/Core/hu.json +++ b/src/NzbDrone.Core/Localization/Core/hu.json @@ -254,7 +254,7 @@ "CutoffUnmet": "Küszöbszint nincs elérve", "DefaultLidarrTags": "Alapértelmezett Lidarr címkék", "DefaultMetadataProfileIdHelpText": "Alapértelmezett metaadat-profil a mappában észlelt előadók számára", - "DefaultMonitorOptionHelpText": "A mappában észlelt előadók albumainak alapértelmezett megfigyelési beállításai", + "DefaultMonitorOptionHelpText": "Az ebben a mappában észlelt előadók első hozzáadásakor mely albumokat kell figyelni", "DefaultQualityProfileIdHelpText": "Alapértelmezett minőségi profil a mappában észlelt előadók számára", "DelayProfiles": "Késleltetési Profilok", "DelayingDownloadUntilInterp": "Késleltetni a letöltést {0} -tól {1} -ig", @@ -306,7 +306,7 @@ "GoToArtistListing": "Ugrás az előadók listájára", "GrabReleaseMessageText": "Lidarr nem tudta meghatározni, hogy melyik filmhez készült ez a kiadás. Lehet, hogy a Lidarr nem tudja automatikusan importálni ezt a kiadást. Meg szeretnéd ragadni a (z) „{0}”-t?", "IfYouDontAddAnImportListExclusionAndTheArtistHasAMetadataProfileOtherThanNoneThenThisAlbumMayBeReaddedDuringTheNextArtistRefresh": "Ha nem ad hozzá importlista-kizárást, és az előadó metaadat-profilja a „Nincs”-től eltérő, akkor az albumot a következő előadói frissítés során újra hozzáadhatja.", - "ImportListSettings": "Listabeállítások importálása", + "ImportListSettings": "Általános importálási lista beállításai", "IsInUseCantDeleteAMetadataProfileThatIsAttachedToAnArtistOrImportList": "Előadóhoz vagy importáló listához csatolt metaadatprofil nem törölhető", "LidarrSupportsAnyDownloadClientThatUsesTheNewznabStandardAsWellAsOtherDownloadClientsListedBelow": "A Lidarr számos népszerű torrent és usenet letöltő klienst támogat.", "LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "A nyomkövetést csak ideiglenesen szabad engedélyezni", @@ -472,7 +472,7 @@ "PreviewRetag": "Előnézet újrataggelése", "PrimaryAlbumTypes": "Elsődleges albumtípusok", "PrimaryTypes": "Elsődleges típusok", - "PriorityHelpText": "Indexelő prioritás 1-től (legmagasabb) 50-ig (legalacsonyabb). Alapértelmezés: 25.", + "PriorityHelpText": "Indexelő prioritás 1-től (legmagasabb) 50-ig (legalacsonyabb). Alapértelmezés: 25. A Lidarr továbbra is az összes engedélyezett indexelőt fogja használni az RSS-szinkronizáláshoz és a kereséshez, amikor a kiadásokat megragadja az egyébként egyenlő kiadásokhoz.", "Profiles": "Profil(ok)", "Proper": "Proper", "PropersAndRepacks": "Properek és Repackok", @@ -831,5 +831,50 @@ "Backup": "Biztonsági Mentés", "Error": "Hiba", "AddConnection": "Gyűjtemény módosítása", - "EditMetadataProfile": "Metaadat-profil" + "EditMetadataProfile": "Metaadat-profil", + "AddReleaseProfile": "Kiadási profil hozzáadása", + "AlbumRelease": "Album megjelenése", + "AlbumReleaseDate": "Album megjelenési dátuma", + "AlbumStatus": "Album állapota", + "AlbumTitle": "Album címe", + "AlbumType": "Album típusa", + "AreYouSure": "Biztos vagy ebben?", + "ArtistName": "Művész neve", + "ArtistType": "Művész típusa", + "CombineWithExistingFiles": "Kombinálja a meglévő fájlokkal", + "DateAdded": "Hozzáadás dátuma", + "DeleteArtist": "A kiválasztott előadó(k) törlése", + "Discography": "Diszkográfia", + "DownloadImported": "Letöltés Importálva", + "EditMetadata": "Metaadat szerkesztése", + "EditReleaseProfile": "Kiadási profil szerkesztése", + "ForNewImportsOnly": "Csak új importokra", + "EndedOnly": "Csak a véget értek", + "ContinuingOnly": "Csak folytatva", + "ImportFailed": "Az importálás sikertelen", + "MediaCount": "Médiaszám", + "MissingTracks": "Hiányzó számok", + "MonitorNewItems": "Új albumok monitorozása", + "NewAlbums": "Új albumok", + "NextAlbum": "Következő album", + "NoneMonitoringOptionHelpText": "Ne figyeljen előadókat vagy albumokat", + "NotDiscography": "Nem diszkográfia", + "OrganizeArtist": "A kiválasztott előadó(k) rendezése", + "Playlist": "Lejátszási lista", + "PreferredWordScore": "Előnyben részesített szó pontszáma", + "Proceed": "Folytassa", + "ReplaceExistingFiles": "Cserélje ki a meglévő fájlokat", + "Retag": "Újrataggelni", + "Retagged": "Újrataggelve", + "RootFolderPath": "Gyökérmappa elérési útja", + "SelectAlbum": "Album kiválasztása", + "SelectArtist": "Előadó kiválasztása", + "SelectTracks": "Szám kiválasztása", + "ShouldMonitorExisting": "Meglévő albumok figyelése", + "Theme": "Téma", + "ThemeHelpText": "Alkamazás kinézetének változtatása", + "TrackCount": "Darabszám", + "TrackImported": "Szám importálva", + "TrackProgress": "Szám folyamata", + "SelectAlbumRelease": "Album kiadásának kiválasztása" } diff --git a/src/NzbDrone.Core/Localization/Core/is.json b/src/NzbDrone.Core/Localization/Core/is.json index a1f5e5797..c78a1d81f 100644 --- a/src/NzbDrone.Core/Localization/Core/is.json +++ b/src/NzbDrone.Core/Localization/Core/is.json @@ -581,5 +581,6 @@ "QualityLimitsHelpText": "Takmörkun er sjálfkrafa leiðrétt fyrir keyrslutíma kvikmyndarinnar.", "Queued": "Í biðröð", "TimeLeft": "Tími eftir", - "AddImportListExclusion": "Eyða útilokun innflutningslista" + "AddImportListExclusion": "Eyða útilokun innflutningslista", + "ImportListExclusions": "Eyða útilokun innflutningslista" } diff --git a/src/NzbDrone.Core/Localization/Core/it.json b/src/NzbDrone.Core/Localization/Core/it.json index 47cb713d4..a9d9b4005 100644 --- a/src/NzbDrone.Core/Localization/Core/it.json +++ b/src/NzbDrone.Core/Localization/Core/it.json @@ -635,5 +635,6 @@ "InteractiveImport": "Importazione interattiva", "Presets": "Preset", "AddImportListExclusion": "Cancellare la lista delle esclusioni", - "EditMetadataProfile": "profilo metadati" + "EditMetadataProfile": "profilo metadati", + "ImportListExclusions": "Cancellare la lista delle esclusioni" } diff --git a/src/NzbDrone.Core/Localization/Core/ja.json b/src/NzbDrone.Core/Localization/Core/ja.json index 1844cdf6d..30c70fa37 100644 --- a/src/NzbDrone.Core/Localization/Core/ja.json +++ b/src/NzbDrone.Core/Localization/Core/ja.json @@ -581,5 +581,6 @@ "Ok": "OK", "System": "システム", "Info": "情報", - "AddImportListExclusion": "インポートリストの除外を削除する" + "AddImportListExclusion": "インポートリストの除外を削除する", + "ImportListExclusions": "インポートリストの除外を削除する" } diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index 99fe19705..dfb5b7071 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -831,5 +831,49 @@ "Always": "Sempre", "Info": "Informações", "AddConnection": "Editar Coleção", - "EditMetadataProfile": "Perfil de metadados" + "EditMetadataProfile": "Perfil de metadados", + "AddReleaseProfile": "Adicionar Perfil de Lançamento", + "AlbumRelease": "Lançamento do Álbum", + "AlbumReleaseDate": "Data do Lançamento do Álbum", + "AlbumStatus": "Estado do Álbum", + "AlbumTitle": "Título do Álbum", + "AlbumType": "Tipo do Álbum", + "AreYouSure": "Tem certeza?", + "ArtistName": "Nome do Artista", + "ArtistType": "Tipo do Artista", + "CombineWithExistingFiles": "Combinar com Arquivos Existentes", + "MonitorNewItems": "Monitorar Novos Álbuns", + "DateAdded": "Data da Adição", + "DeleteArtist": "Excluir Artista Selecionado", + "Discography": "Discografia", + "DownloadImported": "Download Importado", + "EditMetadata": "Editar Metadados", + "EditReleaseProfile": "Editar Perfil de Lançamento", + "ForNewImportsOnly": "Para novas importações somente", + "ImportFailed": "Importação Falhou", + "MissingTracks": "Faixas Ausentes", + "NewAlbums": "Novos Álbuns", + "NextAlbum": "Próximo Álbum", + "NoneMonitoringOptionHelpText": "Não monitorar artistas ou álbuns", + "NotDiscography": "Sem Discografia", + "OrganizeArtist": "Organizar Artista Selecionado", + "Playlist": "Lista de Reprodução", + "PreferredWordScore": "Pontuação de palavras preferidas", + "Proceed": "Proceder", + "ReplaceExistingFiles": "Substituir Arquivos Existentes", + "Retag": "Re-etiquetar", + "Retagged": "Re-etiquetado", + "RootFolderPath": "Caminho da Pasta Raiz", + "SelectAlbum": "Selecionar Álbum", + "SelectAlbumRelease": "Selecionar Lançamento do Álbum", + "SelectArtist": "Selecionar Artista", + "SelectTracks": "Selecionar Faixas", + "ShouldMonitorExisting": "Monitorar álbuns existentes", + "Theme": "Tema", + "ThemeHelpText": "Mudar Tema da UI do Aplicativo", + "TrackCount": "Número da Faixa", + "TrackImported": "Faixa Importada", + "TrackProgress": "Progresso da Faixa", + "EndedOnly": "Concluídos Somente", + "MediaCount": "Número de Mídias" } diff --git a/src/NzbDrone.Core/Localization/Core/ro.json b/src/NzbDrone.Core/Localization/Core/ro.json index 42c3120e6..1a40cbdd0 100644 --- a/src/NzbDrone.Core/Localization/Core/ro.json +++ b/src/NzbDrone.Core/Localization/Core/ro.json @@ -583,5 +583,6 @@ "AddImportListExclusion": "Ștergeți excluderea listei de import", "NoResults": "Nici un rezultat gasit", "NoTagsHaveBeenAddedYet": "Nu s-au adăugat încă etichete", - "OnlyTorrent": "Numai Torrent" + "OnlyTorrent": "Numai Torrent", + "ImportListExclusions": "Ștergeți excluderea listei de import" } diff --git a/src/NzbDrone.Core/Localization/Core/ru.json b/src/NzbDrone.Core/Localization/Core/ru.json index 9e08ab13c..2c6ab176c 100644 --- a/src/NzbDrone.Core/Localization/Core/ru.json +++ b/src/NzbDrone.Core/Localization/Core/ru.json @@ -606,5 +606,6 @@ "AddConnection": "Редактировать коллекцию", "AddImportListExclusion": "Удалить лист исключения для импорта", "Date": "Дата", - "Manual": "Ручной" + "Manual": "Ручной", + "ImportListExclusions": "Удалить лист исключения для импорта" } diff --git a/src/NzbDrone.Core/Localization/Core/sk.json b/src/NzbDrone.Core/Localization/Core/sk.json index 0e623080e..f4b296cbf 100644 --- a/src/NzbDrone.Core/Localization/Core/sk.json +++ b/src/NzbDrone.Core/Localization/Core/sk.json @@ -161,5 +161,6 @@ "AddQualityProfile": "Pridajte profil kvality", "AddMetadataProfile": "profil metadát", "Events": "Udalosť", - "Info": "Info" + "Info": "Info", + "EditMetadataProfile": "profil metadát" } diff --git a/src/NzbDrone.Core/Localization/Core/th.json b/src/NzbDrone.Core/Localization/Core/th.json index 21b05a744..b4a10e073 100644 --- a/src/NzbDrone.Core/Localization/Core/th.json +++ b/src/NzbDrone.Core/Localization/Core/th.json @@ -581,5 +581,6 @@ "NoTagsHaveBeenAddedYet": "ยังไม่มีการเพิ่มแท็ก", "Organize": "จัดระเบียบ", "Close": "ปิด", - "Manual": "คู่มือ" + "Manual": "คู่มือ", + "ImportListExclusions": "ลบการยกเว้นรายการนำเข้า" } diff --git a/src/NzbDrone.Core/Localization/Core/vi.json b/src/NzbDrone.Core/Localization/Core/vi.json index be551d240..cdeeab661 100644 --- a/src/NzbDrone.Core/Localization/Core/vi.json +++ b/src/NzbDrone.Core/Localization/Core/vi.json @@ -581,5 +581,6 @@ "NoResults": "không tìm thấy kết quả nào", "NoTagsHaveBeenAddedYet": "Chưa có thẻ nào được thêm vào", "Ok": "Đồng ý", - "System": "Hệ thống" + "System": "Hệ thống", + "ImportListExclusions": "Xóa loại trừ danh sách nhập" } From f7839adc386baa581c75509e29820f45ab482c2b Mon Sep 17 00:00:00 2001 From: ta264 Date: Tue, 25 Feb 2020 21:53:40 +0000 Subject: [PATCH 0065/1478] Cache database for Unit tests to avoid repeated migrations (cherry picked from commit f3308827d0ede7895b0b8b3b251a17cda3a54120) --- src/NzbDrone.Core.Test/Framework/DbTest.cs | 47 ++++++++++++++++++- .../Framework/DbTestCleanup.cs | 26 ++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 src/NzbDrone.Core.Test/Framework/DbTestCleanup.cs diff --git a/src/NzbDrone.Core.Test/Framework/DbTest.cs b/src/NzbDrone.Core.Test/Framework/DbTest.cs index afb6dbdb6..e38336375 100644 --- a/src/NzbDrone.Core.Test/Framework/DbTest.cs +++ b/src/NzbDrone.Core.Test/Framework/DbTest.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Data.SQLite; +using System.IO; using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using NUnit.Framework; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore.Migration.Framework; @@ -65,8 +67,7 @@ namespace NzbDrone.Core.Test.Framework protected virtual ITestDatabase WithTestDb(MigrationContext migrationContext) { - var factory = Mocker.Resolve(); - var database = factory.Create(migrationContext); + var database = CreateDatabase(migrationContext); Mocker.SetConstant(database); switch (MigrationType) @@ -98,6 +99,48 @@ namespace NzbDrone.Core.Test.Framework return testDb; } + private IDatabase CreateDatabase(MigrationContext migrationContext) + { + var factory = Mocker.Resolve(); + + // If a special migration test or log migration then create new + if (migrationContext.BeforeMigration != null) + { + return factory.Create(migrationContext); + } + + // Otherwise try to use a cached migrated db + var cachedDb = GetCachedDatabase(migrationContext.MigrationType); + var testDb = GetTestDb(migrationContext.MigrationType); + if (File.Exists(cachedDb)) + { + TestLogger.Info($"Using cached initial database {cachedDb}"); + File.Copy(cachedDb, testDb); + return factory.Create(migrationContext); + } + else + { + var db = factory.Create(migrationContext); + GC.Collect(); + GC.WaitForPendingFinalizers(); + SQLiteConnection.ClearAllPools(); + + TestLogger.Info("Caching database"); + File.Copy(testDb, cachedDb); + return db; + } + } + + private string GetCachedDatabase(MigrationType type) + { + return Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_{type}.db"); + } + + private string GetTestDb(MigrationType type) + { + return type == MigrationType.Main ? TestFolderInfo.GetDatabase() : TestFolderInfo.GetLogDatabase(); + } + protected virtual void SetupLogging() { Mocker.SetConstant(NullLoggerProvider.Instance); diff --git a/src/NzbDrone.Core.Test/Framework/DbTestCleanup.cs b/src/NzbDrone.Core.Test/Framework/DbTestCleanup.cs new file mode 100644 index 000000000..587043e95 --- /dev/null +++ b/src/NzbDrone.Core.Test/Framework/DbTestCleanup.cs @@ -0,0 +1,26 @@ +using System.IO; +using NUnit.Framework; + +namespace NzbDrone.Core.Test +{ + [SetUpFixture] + public class RemoveCachedDatabase + { + [OneTimeSetUp] + [OneTimeTearDown] + public void ClearCachedDatabase() + { + var mainCache = Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_Main.db"); + if (File.Exists(mainCache)) + { + File.Delete(mainCache); + } + + var logCache = Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_Log.db"); + if (File.Exists(logCache)) + { + File.Delete(logCache); + } + } + } +} From 8f6e099794e1856f26c3befc00f130ccd46d677d Mon Sep 17 00:00:00 2001 From: Robin Dadswell <19610103+RobinDadswell@users.noreply.github.com> Date: Wed, 26 Jan 2022 00:08:27 +0000 Subject: [PATCH 0066/1478] New: Postgres Database Support Co-Authored-By: Qstick <376117+Qstick@users.noreply.github.com> --- azure-pipelines.yml | 117 ++++++ frontend/src/System/Status/About/About.js | 9 + src/Lidarr.Api.V1/System/SystemController.cs | 3 +- .../AutomationTest.cs | 2 +- .../CleanseLogMessageFixture.cs | 17 + .../ServiceFactoryFixture.cs | 5 +- .../Instrumentation/CleanseLogMessage.cs | 1 + .../Processes/ProcessProvider.cs | 13 +- .../Datastore/DatabaseFixture.cs | 2 +- .../Datastore/DatabaseRelationshipFixture.cs | 25 +- .../Datastore/LazyLoadingFixture.cs | 12 +- ...add_various_qualities_in_profileFixture.cs | 4 +- .../023_add_release_groups_etcFixture.cs | 26 +- ...30_add_mediafilerepository_mtimeFixture.cs | 14 +- ..._add_artistmetadataid_constraintFixture.cs | 6 +- ...036_add_download_client_priorityFixture.cs | 20 +- .../049_email_multiple_addressesFixture.cs | 2 +- .../051_cdh_per_downloadclientFixture.cs | 12 +- ...ture.cs => WhereBuilderPostgresFixture.cs} | 36 +- .../Datastore/WhereBuilderSqliteFixture.cs | 203 +++++++++ src/NzbDrone.Core.Test/Framework/DbTest.cs | 56 ++- .../Framework/DbTestCleanup.cs | 6 +- .../Framework/TestDatabase.cs | 3 + .../CleanupOrphanedReleasesFixture.cs | 43 ++ .../AlbumRepositoryFixture.cs | 17 +- .../ArtistRepositoryFixture.cs | 11 +- .../RefreshAlbumReleaseServiceFixture.cs | 6 +- .../MusicTests/RefreshAlbumServiceFixture.cs | 12 +- .../MusicTests/RefreshArtistServiceFixture.cs | 6 +- .../ArtistStats/ArtistStatisticsRepository.cs | 50 ++- .../Blocklisting/BlocklistRepository.cs | 2 +- .../Configuration/ConfigFileProvider.cs | 19 +- .../Datastore/BasicRepository.cs | 21 +- .../Datastore/ConnectionStringFactory.cs | 29 +- src/NzbDrone.Core/Datastore/Database.cs | 43 +- src/NzbDrone.Core/Datastore/DbFactory.cs | 17 +- .../Datastore/Extensions/BuilderExtensions.cs | 46 ++- src/NzbDrone.Core/Datastore/LogDatabase.cs | 4 + src/NzbDrone.Core/Datastore/MainDatabase.cs | 4 + .../Datastore/Migration/001_initial_setup.cs | 4 +- .../Migration/003_add_medium_support.cs | 2 +- .../004_add_various_qualities_in_profile.cs | 17 +- ...parate_automatic_and_interactive_search.cs | 2 +- .../008_change_quality_size_mb_to_kb.cs | 2 +- .../Migration/012_add_release_status.cs | 2 +- .../013_album_download_notification.cs | 2 +- .../014_fix_language_metadata_profiles.cs | 16 +- .../Datastore/Migration/015_remove_fanzub.cs | 2 +- .../019_add_ape_quality_in_profiles.cs | 4 +- .../Migration/023_add_release_groups_etc.cs | 127 +++--- ...me_quality_profiles_add_upgrade_allowed.cs | 4 +- .../028_clean_artistmetadata_table.cs | 62 +-- .../029_health_issue_notification.cs | 10 +- .../030_add_mediafilerepository_mtime.cs | 58 +-- .../031_add_artistmetadataid_constraint.cs | 10 +- .../Migration/033_download_propers_config.cs | 6 +- .../Migration/035_multi_disc_naming_format.cs | 2 +- .../036_add_download_client_priority.cs | 55 ++- .../039_add_root_folder_add_defaults.cs | 2 +- .../Migration/042_remove_album_folders.cs | 4 +- ...045_remove_chown_and_folderchmod_config.cs | 4 +- .../Migration/047_update_notifiarr.cs | 2 +- .../Migration/049_email_multiple_addresses.cs | 4 +- .../Migration/051_cdh_per_downloadclient.cs | 26 +- .../Migration/052_download_history.cs | 2 +- .../Migration/057_import_list_search.cs | 2 +- .../058_import_list_monitor_existing.cs | 2 +- .../Datastore/Migration/059_indexer_tags.cs | 4 +- .../Framework/MigrationController.cs | 16 +- .../Datastore/PostgresOptions.cs | 26 ++ src/NzbDrone.Core/Datastore/SqlBuilder.cs | 8 + src/NzbDrone.Core/Datastore/TableMapper.cs | 37 +- src/NzbDrone.Core/Datastore/TableMapping.cs | 16 +- src/NzbDrone.Core/Datastore/WhereBuilder.cs | 389 +----------------- .../Datastore/WhereBuilderPostgres.cs | 389 ++++++++++++++++++ .../Datastore/WhereBuilderSqlite.cs | 389 ++++++++++++++++++ .../History/EntityHistoryRepository.cs | 6 +- .../CleanupAbsolutePathMetadataFiles.cs | 34 +- .../CleanupAdditionalNamingSpecs.cs | 8 +- .../Housekeepers/CleanupAdditionalUsers.cs | 6 +- ...ownloadClientUnavailablePendingReleases.cs | 33 +- .../CleanupDuplicateMetadataFiles.cs | 48 +-- .../Housekeepers/CleanupOrphanedAlbums.cs | 12 +- .../CleanupOrphanedArtistMetadata.cs | 14 +- .../Housekeepers/CleanupOrphanedBlacklist.cs | 12 +- .../CleanupOrphanedDownloadClientStatus.cs | 12 +- .../CleanupOrphanedHistoryItems.cs | 24 +- .../CleanupOrphanedImportListStatus.cs | 12 +- .../CleanupOrphanedIndexerStatus.cs | 12 +- .../CleanupOrphanedMetadataFiles.cs | 60 +-- .../CleanupOrphanedPendingReleases.cs | 12 +- .../Housekeepers/CleanupOrphanedReleases.cs | 12 +- .../Housekeepers/CleanupOrphanedTrackFiles.cs | 28 +- .../Housekeepers/CleanupOrphanedTracks.cs | 12 +- .../Housekeepers/CleanupUnusedTags.cs | 21 +- .../FixFutureRunScheduledTasks.cs | 6 +- .../Instrumentation/DatabaseTarget.cs | 68 ++- src/NzbDrone.Core/Lidarr.Core.csproj | 2 + src/NzbDrone.Core/Localization/Core/en.json | 1 + .../MediaFiles/MediaFileRepository.cs | 8 +- .../Messaging/Commands/CommandRepository.cs | 2 +- .../Music/Repositories/AlbumRepository.cs | 97 +++-- .../Music/Repositories/ArtistRepository.cs | 8 +- .../Music/Repositories/ReleaseRepository.cs | 17 +- .../Music/Repositories/TrackRepository.cs | 4 +- .../Music/Services/AlbumService.cs | 4 +- .../Services/RefreshAlbumReleaseService.cs | 2 +- .../Music/Services/RefreshAlbumService.cs | 2 +- .../Music/Services/RefreshArtistService.cs | 2 +- .../Music/Services/ReleaseService.cs | 4 +- .../Music/Services/TrackService.cs | 4 +- .../Update/UpdatePackageProvider.cs | 6 +- src/NzbDrone.Host.Test/ContainerFixture.cs | 3 + src/NzbDrone.Host/Bootstrap.cs | 9 +- .../IntegrationTest.cs | 33 +- .../Datastore/PostgresDatabase.cs | 69 ++++ .../Datastore/SqliteDatabase.cs | 14 + src/NzbDrone.Test.Common/NzbDroneRunner.cs | 26 +- src/postgres.runsettings | 11 + 119 files changed, 2420 insertions(+), 993 deletions(-) rename src/NzbDrone.Core.Test/Datastore/{WhereBuilderFixture.cs => WhereBuilderPostgresFixture.cs} (86%) create mode 100644 src/NzbDrone.Core.Test/Datastore/WhereBuilderSqliteFixture.cs create mode 100644 src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedReleasesFixture.cs create mode 100644 src/NzbDrone.Core/Datastore/PostgresOptions.cs create mode 100644 src/NzbDrone.Core/Datastore/WhereBuilderPostgres.cs create mode 100644 src/NzbDrone.Core/Datastore/WhereBuilderSqlite.cs create mode 100644 src/NzbDrone.Test.Common/Datastore/PostgresDatabase.cs create mode 100644 src/NzbDrone.Test.Common/Datastore/SqliteDatabase.cs create mode 100644 src/postgres.runsettings diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f6c3cd2bd..6fd2d382a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -534,6 +534,62 @@ stages: testResultsFiles: '**/TestResult.xml' testRunTitle: '$(testName) Unit Tests' failTaskOnFailedTests: true + + - job: Unit_LinuxCore_Postgres + displayName: Unit Native LinuxCore with Postgres Database + dependsOn: Prepare + condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0')) + variables: + pattern: 'Lidarr.*.linux-core-x64.tar.gz' + artifactName: linux-x64-tests + Lidarr__Postgres__Host: 'localhost' + Lidarr__Postgres__Port: '5432' + Lidarr__Postgres__User: 'lidarr' + Lidarr__Postgres__Password: 'lidarr' + + pool: + vmImage: ${{ variables.linuxImage }} + + timeoutInMinutes: 10 + + steps: + - task: UseDotNet@2 + displayName: 'Install .net core' + inputs: + version: $(dotnetVersion) + - checkout: none + - task: DownloadPipelineArtifact@2 + displayName: Download Test Artifact + inputs: + buildType: 'current' + artifactName: $(artifactName) + targetPath: $(testsFolder) + - bash: | + chmod a+x _tests/fpcalc + displayName: Make fpcalc Executable + condition: and(succeeded(), ne(variables['osName'], 'Windows')) + - bash: find ${TESTSFOLDER} -name "Lidarr.Test.Dummy" -exec chmod a+x {} \; + displayName: Make Test Dummy Executable + condition: and(succeeded(), ne(variables['osName'], 'Windows')) + - bash: | + docker run -d --name=postgres14 \ + -e POSTGRES_PASSWORD=lidarr \ + -e POSTGRES_USER=lidarr \ + -p 5432:5432/tcp \ + postgres:14 + displayName: Start postgres + - bash: | + chmod a+x ${TESTSFOLDER}/test.sh + ls -lR ${TESTSFOLDER} + ${TESTSFOLDER}/test.sh Linux Unit Test + displayName: Run Tests + - task: PublishTestResults@2 + displayName: Publish Test Results + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '**/TestResult.xml' + testRunTitle: 'LinuxCore Postgres Unit Tests' + failTaskOnFailedTests: true - stage: Integration displayName: Integration @@ -617,6 +673,67 @@ stages: failTaskOnFailedTests: true displayName: Publish Test Results + - job: Integration_LinuxCore_Postgres + displayName: Integration Native LinuxCore with Postgres Database + dependsOn: Prepare + condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0')) + variables: + pattern: 'Lidarr.*.linux-core-x64.tar.gz' + Lidarr__Postgres__Host: 'localhost' + Lidarr__Postgres__Port: '5432' + Lidarr__Postgres__User: 'lidarr' + Lidarr__Postgres__Password: 'lidarr' + + pool: + vmImage: ${{ variables.linuxImage }} + + steps: + - task: UseDotNet@2 + displayName: 'Install .net core' + inputs: + version: $(dotnetVersion) + - checkout: none + - task: DownloadPipelineArtifact@2 + displayName: Download Test Artifact + inputs: + buildType: 'current' + artifactName: 'linux-x64-tests' + targetPath: $(testsFolder) + - task: DownloadPipelineArtifact@2 + displayName: Download Build Artifact + inputs: + buildType: 'current' + artifactName: Packages + itemPattern: '**/$(pattern)' + targetPath: $(Build.ArtifactStagingDirectory) + - task: ExtractFiles@1 + inputs: + archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)' + destinationFolder: '$(Build.ArtifactStagingDirectory)/bin' + displayName: Extract Package + - bash: | + mkdir -p ./bin/ + cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Lidarr/. ./bin/ + displayName: Move Package Contents + - bash: | + docker run -d --name=postgres14 \ + -e POSTGRES_PASSWORD=lidarr \ + -e POSTGRES_USER=lidarr \ + -p 5432:5432/tcp \ + postgres:14 + displayName: Start postgres + - bash: | + chmod a+x ${TESTSFOLDER}/test.sh + ${TESTSFOLDER}/test.sh Linux Integration Test + displayName: Run Integration Tests + - task: PublishTestResults@2 + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '**/TestResult.xml' + testRunTitle: 'Integration LinuxCore Postgres Database Integration Tests' + failTaskOnFailedTests: true + displayName: Publish Test Results + - job: Integration_FreeBSD displayName: Integration Native FreeBSD dependsOn: Prepare diff --git a/frontend/src/System/Status/About/About.js b/frontend/src/System/Status/About/About.js index 1acd44b11..4635fef68 100644 --- a/frontend/src/System/Status/About/About.js +++ b/frontend/src/System/Status/About/About.js @@ -23,6 +23,8 @@ class About extends Component { isDocker, runtimeVersion, migrationVersion, + databaseVersion, + databaseType, appData, startupPath, mode, @@ -68,6 +70,11 @@ class About extends Component { data={migrationVersion} /> + + (new Mock().Object); + container.RegisterInstance(new Mock().Object); + container.RegisterInstance(new Mock>().Object); var serviceProvider = container.GetServiceProvider(); diff --git a/src/NzbDrone.Common/Instrumentation/CleanseLogMessage.cs b/src/NzbDrone.Common/Instrumentation/CleanseLogMessage.cs index df0f49e1e..aa9692fb0 100644 --- a/src/NzbDrone.Common/Instrumentation/CleanseLogMessage.cs +++ b/src/NzbDrone.Common/Instrumentation/CleanseLogMessage.cs @@ -18,6 +18,7 @@ namespace NzbDrone.Common.Instrumentation new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"/fetch/[a-z0-9]{32}/(?[a-z0-9]{32})", RegexOptions.Compiled), new Regex(@"getnzb.*?(?<=\?|&)(r)=(?[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase), + new Regex(@"\b(\w*)?(_?(?[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase), // Trackers Announce Keys; Designed for Qbit Json; should work for all in theory new Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?[a-z0-9]{16,})|(?[a-z0-9]{16,})(/|%2f)announce"), diff --git a/src/NzbDrone.Common/Processes/ProcessProvider.cs b/src/NzbDrone.Common/Processes/ProcessProvider.cs index 9336248bc..27fd09299 100644 --- a/src/NzbDrone.Common/Processes/ProcessProvider.cs +++ b/src/NzbDrone.Common/Processes/ProcessProvider.cs @@ -127,7 +127,18 @@ namespace NzbDrone.Common.Processes try { _logger.Trace("Setting environment variable '{0}' to '{1}'", environmentVariable.Key, environmentVariable.Value); - startInfo.EnvironmentVariables.Add(environmentVariable.Key.ToString(), environmentVariable.Value.ToString()); + + var key = environmentVariable.Key.ToString(); + var value = environmentVariable.Value?.ToString(); + + if (startInfo.EnvironmentVariables.ContainsKey(key)) + { + startInfo.EnvironmentVariables[key] = value; + } + else + { + startInfo.EnvironmentVariables.Add(key, value); + } } catch (Exception e) { diff --git a/src/NzbDrone.Core.Test/Datastore/DatabaseFixture.cs b/src/NzbDrone.Core.Test/Datastore/DatabaseFixture.cs index 807057837..f6c182660 100644 --- a/src/NzbDrone.Core.Test/Datastore/DatabaseFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/DatabaseFixture.cs @@ -15,7 +15,7 @@ namespace NzbDrone.Core.Test.Datastore public void SingleOrDefault_should_return_null_on_empty_db() { Mocker.Resolve() - .OpenConnection().Query("SELECT * FROM Artists") + .OpenConnection().Query("SELECT * FROM \"Artists\"") .SingleOrDefault(c => c.CleanName == "SomeTitle") .Should() .BeNull(); diff --git a/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs b/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs index ab17a1550..6c073b18f 100644 --- a/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs @@ -2,7 +2,9 @@ using System; using System.Linq; using FizzWare.NBuilder; using FluentAssertions; +using FluentAssertions.Equivalency; using NUnit.Framework; +using NzbDrone.Core.Datastore; using NzbDrone.Core.History; using NzbDrone.Core.Music; using NzbDrone.Core.Qualities; @@ -13,6 +15,17 @@ namespace NzbDrone.Core.Test.Datastore [TestFixture] public class DatabaseRelationshipFixture : DbTest { + [SetUp] + public void Setup() + { + AssertionOptions.AssertEquivalencyUsing(options => + { + options.Using(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation.ToUniversalTime())).WhenTypeIs(); + options.Using(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation.Value.ToUniversalTime())).WhenTypeIs(); + return options; + }); + } + [Test] public void one_to_one() { @@ -33,13 +46,7 @@ namespace NzbDrone.Core.Test.Datastore var loadedAlbum = Db.Single().Album.Value; loadedAlbum.Should().NotBeNull(); - loadedAlbum.Should().BeEquivalentTo(album, - options => options - .IncludingAllRuntimeProperties() - .Excluding(c => c.Artist) - .Excluding(c => c.ArtistId) - .Excluding(c => c.ArtistMetadata) - .Excluding(c => c.AlbumReleases)); + loadedAlbum.Should().BeEquivalentTo(album, AlbumComparerOptions); } [Test] @@ -86,5 +93,9 @@ namespace NzbDrone.Core.Test.Datastore returnedHistory[0].Quality.Quality.Should().Be(Quality.MP3_320); } + + private EquivalencyAssertionOptions AlbumComparerOptions(EquivalencyAssertionOptions opts) => opts.ComparingByMembers() + .Excluding(ctx => ctx.SelectedMemberInfo.MemberType.IsGenericType && ctx.SelectedMemberInfo.MemberType.GetGenericTypeDefinition() == typeof(LazyLoaded<>)) + .Excluding(x => x.ArtistId); } } diff --git a/src/NzbDrone.Core.Test/Datastore/LazyLoadingFixture.cs b/src/NzbDrone.Core.Test/Datastore/LazyLoadingFixture.cs index 5241eefcd..427f62f2b 100644 --- a/src/NzbDrone.Core.Test/Datastore/LazyLoadingFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/LazyLoadingFixture.cs @@ -104,7 +104,7 @@ namespace NzbDrone.Core.Test.Datastore public void should_lazy_load_artist_for_trackfile() { var db = Mocker.Resolve(); - var tracks = db.Query(new SqlBuilder()).ToList(); + var tracks = db.Query(new SqlBuilder(db.DatabaseType)).ToList(); Assert.IsNotEmpty(tracks); foreach (var track in tracks) @@ -120,7 +120,7 @@ namespace NzbDrone.Core.Test.Datastore public void should_lazy_load_trackfile_if_not_joined() { var db = Mocker.Resolve(); - var tracks = db.Query(new SqlBuilder()).ToList(); + var tracks = db.Query(new SqlBuilder(db.DatabaseType)).ToList(); foreach (var track in tracks) { @@ -135,7 +135,7 @@ namespace NzbDrone.Core.Test.Datastore { var db = Mocker.Resolve(); var files = MediaFileRepository.Query(db, - new SqlBuilder() + new SqlBuilder(db.DatabaseType) .Join((f, t) => f.Id == t.TrackFileId) .Join((t, a) => t.AlbumId == a.Id) .Join((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId) @@ -157,7 +157,7 @@ namespace NzbDrone.Core.Test.Datastore { var db = Mocker.Resolve(); var files = db.QueryJoined( - new SqlBuilder() + new SqlBuilder(db.DatabaseType) .Join((t, a) => t.AlbumId == a.Id) .Join((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId) .Join((a, m) => a.ArtistMetadataId == m.Id), @@ -186,7 +186,7 @@ namespace NzbDrone.Core.Test.Datastore public void should_lazy_load_tracks_if_not_joined() { var db = Mocker.Resolve(); - var release = db.Query(new SqlBuilder().Where(x => x.Id == 1)).SingleOrDefault(); + var release = db.Query(new SqlBuilder(db.DatabaseType).Where(x => x.Id == 1)).SingleOrDefault(); Assert.IsFalse(release.Tracks.IsLoaded); Assert.IsNotNull(release.Tracks.Value); @@ -198,7 +198,7 @@ namespace NzbDrone.Core.Test.Datastore public void should_lazy_load_track_if_not_joined() { var db = Mocker.Resolve(); - var tracks = db.Query(new SqlBuilder()).ToList(); + var tracks = db.Query(new SqlBuilder(db.DatabaseType)).ToList(); foreach (var track in tracks) { diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/004_add_various_qualities_in_profileFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/004_add_various_qualities_in_profileFixture.cs index cba53eb71..c13f542e8 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/004_add_various_qualities_in_profileFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/004_add_various_qualities_in_profileFixture.cs @@ -29,7 +29,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration }); }); - var profiles = db.Query("SELECT Items FROM Profiles LIMIT 1"); + var profiles = db.Query("SELECT \"Items\" FROM \"Profiles\" LIMIT 1"); var items = profiles.First().Items; items.Should().HaveCount(7); @@ -52,7 +52,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration }); }); - var profiles = db.Query("SELECT Items FROM Profiles LIMIT 1"); + var profiles = db.Query("SELECT \"Items\" FROM \"Profiles\" LIMIT 1"); var items = profiles.First().Items; items.Should().HaveCount(7); diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/023_add_release_groups_etcFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/023_add_release_groups_etcFixture.cs index 233968f4d..0278fbb79 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/023_add_release_groups_etcFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/023_add_release_groups_etcFixture.cs @@ -24,8 +24,8 @@ namespace NzbDrone.Core.Test.Datastore.Migration Status = 1, Images = "", Path = $"/mnt/data/path/{name}", - Monitored = 1, - AlbumFolder = 1, + Monitored = true, + AlbumFolder = true, LanguageProfileId = 1, MetadataProfileId = 1 }); @@ -41,7 +41,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration Title = title, CleanTitle = title, Images = "", - Monitored = 1, + Monitored = true, AlbumType = "Studio", Duration = 100, Media = "", @@ -61,9 +61,9 @@ namespace NzbDrone.Core.Test.Datastore.Migration ForeignTrackId = id.ToString(), ArtistId = artistid, AlbumId = albumid, - Explicit = 0, - Compilation = 0, - Monitored = 0, + Explicit = false, + Compilation = false, + Monitored = false, Duration = 100, MediumNumber = 1, AbsoluteTrackNumber = i, @@ -74,8 +74,8 @@ namespace NzbDrone.Core.Test.Datastore.Migration private IEnumerable VerifyAlbumReleases(IDirectDataMapper db) { - var releases = db.Query("SELECT * FROM AlbumReleases"); - var albums = db.Query("SELECT * FROM Albums"); + var releases = db.Query("SELECT * FROM \"AlbumReleases\""); + var albums = db.Query("SELECT * FROM \"Albums\""); // we only put in one release per album releases.Count().Should().Be(albums.Count()); @@ -91,12 +91,12 @@ namespace NzbDrone.Core.Test.Datastore.Migration private void VerifyTracks(IDirectDataMapper db, int albumId, int albumReleaseId, int expectedCount) { - var tracks = db.Query("SELECT Tracks.* FROM Tracks " + - "JOIN AlbumReleases ON Tracks.AlbumReleaseId = AlbumReleases.Id " + - "JOIN Albums ON AlbumReleases.AlbumId = Albums.Id " + - "WHERE Albums.Id = " + albumId).ToList(); + var tracks = db.Query("SELECT \"Tracks\".* FROM \"Tracks\" " + + "JOIN \"AlbumReleases\" ON \"Tracks\".\"AlbumReleaseId\" = \"AlbumReleases\".\"Id\" " + + "JOIN \"Albums\" ON \"AlbumReleases\".\"AlbumId\" = \"Albums\".\"Id\" " + + "WHERE \"Albums\".\"Id\" = " + albumId).ToList(); - var album = db.Query("SELECT * FROM Albums WHERE Albums.Id = " + albumId).ToList().Single(); + var album = db.Query("SELECT * FROM \"Albums\" WHERE \"Albums\".\"Id\" = " + albumId).ToList().Single(); tracks.Count.Should().Be(expectedCount); tracks.First().AlbumReleaseId.Should().Be(albumReleaseId); diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/030_add_mediafilerepository_mtimeFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/030_add_mediafilerepository_mtimeFixture.cs index 201b6a99f..67a82d38f 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/030_add_mediafilerepository_mtimeFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/030_add_mediafilerepository_mtimeFixture.cs @@ -25,8 +25,8 @@ namespace NzbDrone.Core.Test.Datastore.Migration Id = id, CleanName = name, Path = _artistPath, - Monitored = 1, - AlbumFolder = 1, + Monitored = true, + AlbumFolder = true, LanguageProfileId = 1, MetadataProfileId = 1, ArtistMetadataId = id @@ -43,9 +43,9 @@ namespace NzbDrone.Core.Test.Datastore.Migration Title = title, CleanTitle = title, Images = "", - Monitored = 1, + Monitored = true, AlbumType = "Studio", - AnyReleaseOk = 1 + AnyReleaseOk = true }); } @@ -85,7 +85,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration { Id = id, ForeignTrackId = id.ToString(), - Explicit = 0, + Explicit = false, TrackFileId = id, Duration = 100, MediumNumber = 1, @@ -102,8 +102,8 @@ namespace NzbDrone.Core.Test.Datastore.Migration private void VerifyTracksFiles(IDirectDataMapper db, int albumId, List expectedPaths) { - var tracks = db.Query("SELECT TrackFiles.* FROM TrackFiles " + - "WHERE TrackFiles.AlbumId = " + albumId); + var tracks = db.Query("SELECT \"TrackFiles\".* FROM \"TrackFiles\" " + + "WHERE \"TrackFiles\".\"AlbumId\" = " + albumId); TestLogger.Debug($"Got {tracks.Count} tracks"); diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/031_add_artistmetadataid_constraintFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/031_add_artistmetadataid_constraintFixture.cs index 4af321c33..b83e38e74 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/031_add_artistmetadataid_constraintFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/031_add_artistmetadataid_constraintFixture.cs @@ -34,8 +34,8 @@ namespace NzbDrone.Core.Test.Datastore.Migration ArtistMetadataId = artistMetadataId, CleanName = name, Path = _artistPath, - Monitored = 1, - AlbumFolder = 1, + Monitored = true, + AlbumFolder = true, LanguageProfileId = 1, MetadataProfileId = 1, }); @@ -43,7 +43,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration private void VerifyArtists(IDirectDataMapper db, List ids) { - var artists = db.Query("SELECT Artists.* from Artists"); + var artists = db.Query("SELECT \"Artists\".* from \"Artists\""); artists.Select(x => x["Id"]).Should().BeEquivalentTo(ids); diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/036_add_download_client_priorityFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/036_add_download_client_priorityFixture.cs index 965c37973..038adf911 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/036_add_download_client_priorityFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/036_add_download_client_priorityFixture.cs @@ -18,7 +18,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration { c.Insert.IntoTable("DownloadClients").Row(new { - Enable = 1, + Enable = true, Name = "Deluge", Implementation = "Deluge", Settings = new DelugeSettings36 @@ -31,7 +31,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration }); }); - var items = db.Query("SELECT * FROM DownloadClients"); + var items = db.Query("SELECT * FROM \"DownloadClients\""); items.Should().HaveCount(1); items.First().Priority.Should().Be(1); @@ -44,7 +44,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration { c.Insert.IntoTable("DownloadClients").Row(new { - Enable = 1, + Enable = true, Name = "Deluge", Implementation = "Deluge", Settings = new DelugeSettings36 @@ -56,7 +56,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration ConfigContract = "DelugeSettings" }).Row(new { - Enable = 1, + Enable = true, Name = "Deluge2", Implementation = "Deluge", Settings = new DelugeSettings36 @@ -68,7 +68,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration ConfigContract = "DelugeSettings" }).Row(new { - Enable = 1, + Enable = true, Name = "sab", Implementation = "Sabnzbd", Settings = new SabnzbdSettings36 @@ -80,7 +80,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration }); }); - var items = db.Query("SELECT * FROM DownloadClients"); + var items = db.Query("SELECT * FROM \"DownloadClients\""); items.Should().HaveCount(3); items[0].Priority.Should().Be(1); @@ -95,7 +95,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration { c.Insert.IntoTable("DownloadClients").Row(new { - Enable = 0, + Enable = false, Name = "Deluge", Implementation = "Deluge", Settings = new DelugeSettings36 @@ -107,7 +107,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration ConfigContract = "DelugeSettings" }).Row(new { - Enable = 0, + Enable = false, Name = "Deluge2", Implementation = "Deluge", Settings = new DelugeSettings36 @@ -119,7 +119,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration ConfigContract = "DelugeSettings" }).Row(new { - Enable = 0, + Enable = false, Name = "sab", Implementation = "Sabnzbd", Settings = new SabnzbdSettings36 @@ -131,7 +131,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration }); }); - var items = db.Query("SELECT * FROM DownloadClients"); + var items = db.Query("SELECT * FROM \"DownloadClients\""); items.Should().HaveCount(3); items[0].Priority.Should().Be(1); diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/049_email_multiple_addressesFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/049_email_multiple_addressesFixture.cs index 4cad9f2ef..2fbc7fb5d 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/049_email_multiple_addressesFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/049_email_multiple_addressesFixture.cs @@ -40,7 +40,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration }); }); - var items = db.Query("SELECT * FROM Notifications"); + var items = db.Query("SELECT * FROM \"Notifications\""); items.Should().HaveCount(1); items.First().Implementation.Should().Be("Email"); diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/051_cdh_per_downloadclientFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/051_cdh_per_downloadclientFixture.cs index e1e374e2f..9591947b3 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/051_cdh_per_downloadclientFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/051_cdh_per_downloadclientFixture.cs @@ -20,7 +20,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration { c.Insert.IntoTable("DownloadClients").Row(new { - Enable = 1, + Enable = true, Name = "Deluge", Implementation = "Deluge", Priority = 1, @@ -34,7 +34,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration }); }); - var items = db.Query("SELECT * FROM DownloadClients"); + var items = db.Query("SELECT * FROM \"DownloadClients\""); items.Should().HaveCount(1); items.First().RemoveCompletedDownloads.Should().BeFalse(); @@ -54,7 +54,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration c.Insert.IntoTable("DownloadClients").Row(new { - Enable = 1, + Enable = true, Name = "Deluge", Implementation = "Deluge", Priority = 1, @@ -68,7 +68,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration }); }); - var items = db.Query("SELECT * FROM DownloadClients"); + var items = db.Query("SELECT * FROM \"DownloadClients\""); items.Should().HaveCount(1); items.First().RemoveCompletedDownloads.Should().BeTrue(); @@ -82,7 +82,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration { c.Insert.IntoTable("DownloadClients").Row(new { - Enable = 1, + Enable = true, Name = "RTorrent", Implementation = "RTorrent", Priority = 1, @@ -96,7 +96,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration }); }); - var items = db.Query("SELECT * FROM DownloadClients"); + var items = db.Query("SELECT * FROM \"DownloadClients\""); items.Should().HaveCount(1); items.First().RemoveCompletedDownloads.Should().BeFalse(); diff --git a/src/NzbDrone.Core.Test/Datastore/WhereBuilderFixture.cs b/src/NzbDrone.Core.Test/Datastore/WhereBuilderPostgresFixture.cs similarity index 86% rename from src/NzbDrone.Core.Test/Datastore/WhereBuilderFixture.cs rename to src/NzbDrone.Core.Test/Datastore/WhereBuilderPostgresFixture.cs index ae0a62725..700a6f4c0 100644 --- a/src/NzbDrone.Core.Test/Datastore/WhereBuilderFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/WhereBuilderPostgresFixture.cs @@ -11,9 +11,9 @@ using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.Datastore { [TestFixture] - public class WhereBuilderFixture : CoreTest + public class WhereBuilderPostgresFixture : CoreTest { - private WhereBuilder _subject; + private WhereBuilderPostgres _subject; [OneTimeSetUp] public void MapTables() @@ -22,14 +22,14 @@ namespace NzbDrone.Core.Test.Datastore Mocker.Resolve(); } - private WhereBuilder Where(Expression> filter) + private WhereBuilderPostgres Where(Expression> filter) { - return new WhereBuilder(filter, true, 0); + return new WhereBuilderPostgres(filter, true, 0); } - private WhereBuilder WhereMetadata(Expression> filter) + private WhereBuilderPostgres WhereMetadata(Expression> filter) { - return new WhereBuilder(filter, true, 0); + return new WhereBuilderPostgres(filter, true, 0); } [Test] @@ -76,7 +76,7 @@ namespace NzbDrone.Core.Test.Datastore public void where_throws_without_concrete_condition_if_requiresConcreteCondition() { Expression> filter = (x, y) => x.Id == y.Id; - _subject = new WhereBuilder(filter, true, 0); + _subject = new WhereBuilderPostgres(filter, true, 0); Assert.Throws(() => _subject.ToString()); } @@ -84,7 +84,7 @@ namespace NzbDrone.Core.Test.Datastore public void where_allows_abstract_condition_if_not_requiresConcreteCondition() { Expression> filter = (x, y) => x.Id == y.Id; - _subject = new WhereBuilder(filter, false, 0); + _subject = new WhereBuilderPostgres(filter, false, 0); _subject.ToString().Should().Be($"(\"Artists\".\"Id\" = \"Artists\".\"Id\")"); } @@ -120,7 +120,7 @@ namespace NzbDrone.Core.Test.Datastore var test = "small"; _subject = Where(x => x.CleanName.Contains(test)); - _subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" LIKE '%' || @Clause1_P1 || '%')"); + _subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" ILIKE '%' || @Clause1_P1 || '%')"); _subject.Parameters.Get("Clause1_P1").Should().Be(test); } @@ -130,7 +130,7 @@ namespace NzbDrone.Core.Test.Datastore var test = "small"; _subject = Where(x => test.Contains(x.CleanName)); - _subject.ToString().Should().Be($"(@Clause1_P1 LIKE '%' || \"Artists\".\"CleanName\" || '%')"); + _subject.ToString().Should().Be($"(@Clause1_P1 ILIKE '%' || \"Artists\".\"CleanName\" || '%')"); _subject.Parameters.Get("Clause1_P1").Should().Be(test); } @@ -140,7 +140,7 @@ namespace NzbDrone.Core.Test.Datastore var test = "small"; _subject = Where(x => x.CleanName.StartsWith(test)); - _subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" LIKE @Clause1_P1 || '%')"); + _subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" ILIKE @Clause1_P1 || '%')"); _subject.Parameters.Get("Clause1_P1").Should().Be(test); } @@ -150,7 +150,7 @@ namespace NzbDrone.Core.Test.Datastore var test = "small"; _subject = Where(x => x.CleanName.EndsWith(test)); - _subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" LIKE '%' || @Clause1_P1)"); + _subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" ILIKE '%' || @Clause1_P1)"); _subject.Parameters.Get("Clause1_P1").Should().Be(test); } @@ -160,7 +160,7 @@ namespace NzbDrone.Core.Test.Datastore var list = new List { 1, 2, 3 }; _subject = Where(x => list.Contains(x.Id)); - _subject.ToString().Should().Be($"(\"Artists\".\"Id\" IN (1, 2, 3))"); + _subject.ToString().Should().Be($"(\"Artists\".\"Id\" = ANY (('{{1, 2, 3}}')))"); } [Test] @@ -169,7 +169,7 @@ namespace NzbDrone.Core.Test.Datastore var list = new List { 1, 2, 3 }; _subject = Where(x => x.CleanName == "test" && list.Contains(x.Id)); - _subject.ToString().Should().Be($"((\"Artists\".\"CleanName\" = @Clause1_P1) AND (\"Artists\".\"Id\" IN (1, 2, 3)))"); + _subject.ToString().Should().Be($"((\"Artists\".\"CleanName\" = @Clause1_P1) AND (\"Artists\".\"Id\" = ANY (('{{1, 2, 3}}'))))"); } [Test] @@ -179,7 +179,7 @@ namespace NzbDrone.Core.Test.Datastore _subject = Where(x => list.Contains(x.CleanName)); - _subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" IN @Clause1_P1)"); + _subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" = ANY (@Clause1_P1))"); } [Test] @@ -187,7 +187,7 @@ namespace NzbDrone.Core.Test.Datastore { _subject = WhereMetadata(x => x.OldForeignArtistIds.Contains("foreignId")); - _subject.ToString().Should().Be($"(\"ArtistMetadata\".\"OldForeignArtistIds\" LIKE '%' || @Clause1_P1 || '%')"); + _subject.ToString().Should().Be($"(\"ArtistMetadata\".\"OldForeignArtistIds\" ILIKE '%' || @Clause1_P1 || '%')"); } [Test] @@ -204,7 +204,7 @@ namespace NzbDrone.Core.Test.Datastore var allowed = new List { ArtistStatusType.Continuing, ArtistStatusType.Ended }; _subject = WhereMetadata(x => allowed.Contains(x.Status)); - _subject.ToString().Should().Be($"(\"ArtistMetadata\".\"Status\" IN @Clause1_P1)"); + _subject.ToString().Should().Be($"(\"ArtistMetadata\".\"Status\" = ANY (@Clause1_P1))"); } [Test] @@ -213,7 +213,7 @@ namespace NzbDrone.Core.Test.Datastore var allowed = new ArtistStatusType[] { ArtistStatusType.Continuing, ArtistStatusType.Ended }; _subject = WhereMetadata(x => allowed.Contains(x.Status)); - _subject.ToString().Should().Be($"(\"ArtistMetadata\".\"Status\" IN @Clause1_P1)"); + _subject.ToString().Should().Be($"(\"ArtistMetadata\".\"Status\" = ANY (@Clause1_P1))"); } } } diff --git a/src/NzbDrone.Core.Test/Datastore/WhereBuilderSqliteFixture.cs b/src/NzbDrone.Core.Test/Datastore/WhereBuilderSqliteFixture.cs new file mode 100644 index 000000000..cee2029d6 --- /dev/null +++ b/src/NzbDrone.Core.Test/Datastore/WhereBuilderSqliteFixture.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Music; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.Datastore +{ + [TestFixture] + public class WhereBuilderSqliteFixture : CoreTest + { + private WhereBuilderSqlite _subject; + + [OneTimeSetUp] + public void MapTables() + { + // Generate table mapping + Mocker.Resolve(); + } + + private WhereBuilderSqlite Where(Expression> filter) + { + return new WhereBuilderSqlite(filter, true, 0); + } + + private WhereBuilderSqlite WhereMetadata(Expression> filter) + { + return new WhereBuilderSqlite(filter, true, 0); + } + + [Test] + public void where_equal_const() + { + _subject = Where(x => x.Id == 10); + + _subject.ToString().Should().Be($"(\"Artists\".\"Id\" = @Clause1_P1)"); + _subject.Parameters.Get("Clause1_P1").Should().Be(10); + } + + [Test] + public void where_equal_variable() + { + var id = 10; + _subject = Where(x => x.Id == id); + + _subject.ToString().Should().Be($"(\"Artists\".\"Id\" = @Clause1_P1)"); + _subject.Parameters.Get("Clause1_P1").Should().Be(id); + } + + [Test] + public void where_equal_property() + { + var author = new Artist { Id = 10 }; + _subject = Where(x => x.Id == author.Id); + + _subject.Parameters.ParameterNames.Should().HaveCount(1); + _subject.ToString().Should().Be($"(\"Artists\".\"Id\" = @Clause1_P1)"); + _subject.Parameters.Get("Clause1_P1").Should().Be(author.Id); + } + + [Test] + public void where_equal_lazy_property() + { + _subject = Where(x => x.QualityProfile.Value.Id == 1); + + _subject.Parameters.ParameterNames.Should().HaveCount(1); + _subject.ToString().Should().Be($"(\"QualityProfiles\".\"Id\" = @Clause1_P1)"); + _subject.Parameters.Get("Clause1_P1").Should().Be(1); + } + + [Test] + public void where_throws_without_concrete_condition_if_requiresConcreteCondition() + { + Expression> filter = (x, y) => x.Id == y.Id; + _subject = new WhereBuilderSqlite(filter, true, 0); + Assert.Throws(() => _subject.ToString()); + } + + [Test] + public void where_allows_abstract_condition_if_not_requiresConcreteCondition() + { + Expression> filter = (x, y) => x.Id == y.Id; + _subject = new WhereBuilderSqlite(filter, false, 0); + _subject.ToString().Should().Be($"(\"Artists\".\"Id\" = \"Artists\".\"Id\")"); + } + + [Test] + public void where_string_is_null() + { + _subject = Where(x => x.CleanName == null); + + _subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" IS NULL)"); + } + + [Test] + public void where_string_is_null_value() + { + string imdb = null; + _subject = Where(x => x.CleanName == imdb); + + _subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" IS NULL)"); + } + + [Test] + public void where_equal_null_property() + { + var author = new Artist { CleanName = null }; + _subject = Where(x => x.CleanName == author.CleanName); + + _subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" IS NULL)"); + } + + [Test] + public void where_column_contains_string() + { + var test = "small"; + _subject = Where(x => x.CleanName.Contains(test)); + + _subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" LIKE '%' || @Clause1_P1 || '%')"); + _subject.Parameters.Get("Clause1_P1").Should().Be(test); + } + + [Test] + public void where_string_contains_column() + { + var test = "small"; + _subject = Where(x => test.Contains(x.CleanName)); + + _subject.ToString().Should().Be($"(@Clause1_P1 LIKE '%' || \"Artists\".\"CleanName\" || '%')"); + _subject.Parameters.Get("Clause1_P1").Should().Be(test); + } + + [Test] + public void where_column_starts_with_string() + { + var test = "small"; + _subject = Where(x => x.CleanName.StartsWith(test)); + + _subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" LIKE @Clause1_P1 || '%')"); + _subject.Parameters.Get("Clause1_P1").Should().Be(test); + } + + [Test] + public void where_column_ends_with_string() + { + var test = "small"; + _subject = Where(x => x.CleanName.EndsWith(test)); + + _subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" LIKE '%' || @Clause1_P1)"); + _subject.Parameters.Get("Clause1_P1").Should().Be(test); + } + + [Test] + public void where_in_list() + { + var list = new List { 1, 2, 3 }; + _subject = Where(x => list.Contains(x.Id)); + + _subject.ToString().Should().Be($"(\"Artists\".\"Id\" IN (1, 2, 3))"); + + _subject.Parameters.ParameterNames.Should().BeEmpty(); + } + + [Test] + public void where_in_list_2() + { + var list = new List { 1, 2, 3 }; + _subject = Where(x => x.CleanName == "test" && list.Contains(x.Id)); + + _subject.ToString().Should().Be($"((\"Artists\".\"CleanName\" = @Clause1_P1) AND (\"Artists\".\"Id\" IN (1, 2, 3)))"); + } + + [Test] + public void enum_as_int() + { + _subject = WhereMetadata(x => x.Status == ArtistStatusType.Continuing); + + _subject.ToString().Should().Be($"(\"ArtistMetadata\".\"Status\" = @Clause1_P1)"); + } + + [Test] + public void enum_in_list() + { + var allowed = new List { ArtistStatusType.Continuing, ArtistStatusType.Ended }; + _subject = WhereMetadata(x => allowed.Contains(x.Status)); + + _subject.ToString().Should().Be($"(\"ArtistMetadata\".\"Status\" IN @Clause1_P1)"); + } + + [Test] + public void enum_in_array() + { + var allowed = new ArtistStatusType[] { ArtistStatusType.Continuing, ArtistStatusType.Ended }; + _subject = WhereMetadata(x => allowed.Contains(x.Status)); + + _subject.ToString().Should().Be($"(\"ArtistMetadata\".\"Status\" IN @Clause1_P1)"); + } + } +} diff --git a/src/NzbDrone.Core.Test/Framework/DbTest.cs b/src/NzbDrone.Core.Test/Framework/DbTest.cs index e38336375..fba791b15 100644 --- a/src/NzbDrone.Core.Test/Framework/DbTest.cs +++ b/src/NzbDrone.Core.Test/Framework/DbTest.cs @@ -1,14 +1,18 @@ -using System; +using System; using System.Collections.Generic; using System.Data.SQLite; using System.IO; using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Npgsql; using NUnit.Framework; using NzbDrone.Common.Extensions; +using NzbDrone.Core.Configuration; using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore.Migration.Framework; +using NzbDrone.Test.Common.Datastore; namespace NzbDrone.Core.Test.Framework { @@ -49,6 +53,7 @@ namespace NzbDrone.Core.Test.Framework public abstract class DbTest : CoreTest { private ITestDatabase _db; + private DatabaseType _databaseType; protected virtual MigrationType MigrationType => MigrationType.Main; @@ -101,17 +106,39 @@ namespace NzbDrone.Core.Test.Framework private IDatabase CreateDatabase(MigrationContext migrationContext) { + if (_databaseType == DatabaseType.PostgreSQL) + { + CreatePostgresDb(); + } + var factory = Mocker.Resolve(); // If a special migration test or log migration then create new - if (migrationContext.BeforeMigration != null) + if (migrationContext.BeforeMigration != null || _databaseType == DatabaseType.PostgreSQL) { return factory.Create(migrationContext); } + return CreateSqliteDatabase(factory, migrationContext); + } + + private void CreatePostgresDb() + { + var options = Mocker.Resolve>().Value; + PostgresDatabase.Create(options, MigrationType); + } + + private void DropPostgresDb() + { + var options = Mocker.Resolve>().Value; + PostgresDatabase.Drop(options, MigrationType); + } + + private IDatabase CreateSqliteDatabase(IDbFactory factory, MigrationContext migrationContext) + { // Otherwise try to use a cached migrated db - var cachedDb = GetCachedDatabase(migrationContext.MigrationType); - var testDb = GetTestDb(migrationContext.MigrationType); + var cachedDb = SqliteDatabase.GetCachedDb(migrationContext.MigrationType); + var testDb = GetTestSqliteDb(migrationContext.MigrationType); if (File.Exists(cachedDb)) { TestLogger.Info($"Using cached initial database {cachedDb}"); @@ -131,12 +158,7 @@ namespace NzbDrone.Core.Test.Framework } } - private string GetCachedDatabase(MigrationType type) - { - return Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_{type}.db"); - } - - private string GetTestDb(MigrationType type) + private string GetTestSqliteDb(MigrationType type) { return type == MigrationType.Main ? TestFolderInfo.GetDatabase() : TestFolderInfo.GetLogDatabase(); } @@ -151,6 +173,13 @@ namespace NzbDrone.Core.Test.Framework WithTempAsAppPath(); SetupLogging(); + // populate the possible postgres options + var postgresOptions = PostgresDatabase.GetTestOptions(); + _databaseType = postgresOptions.Host.IsNotNullOrWhiteSpace() ? DatabaseType.PostgreSQL : DatabaseType.SQLite; + + // Set up remaining container services + Mocker.SetConstant(Options.Create(postgresOptions)); + Mocker.SetConstant(Mocker.Resolve()); Mocker.SetConstant(Mocker.Resolve()); Mocker.SetConstant(Mocker.Resolve()); @@ -170,12 +199,19 @@ namespace NzbDrone.Core.Test.Framework // Make sure there are no lingering connections. (When this happens it means we haven't disposed something properly) GC.Collect(); GC.WaitForPendingFinalizers(); + SQLiteConnection.ClearAllPools(); + NpgsqlConnection.ClearAllPools(); if (TestFolderInfo != null) { DeleteTempFolder(TestFolderInfo.AppDataFolder); } + + if (_databaseType == DatabaseType.PostgreSQL) + { + DropPostgresDb(); + } } } } diff --git a/src/NzbDrone.Core.Test/Framework/DbTestCleanup.cs b/src/NzbDrone.Core.Test/Framework/DbTestCleanup.cs index 587043e95..ae6b102a7 100644 --- a/src/NzbDrone.Core.Test/Framework/DbTestCleanup.cs +++ b/src/NzbDrone.Core.Test/Framework/DbTestCleanup.cs @@ -1,5 +1,7 @@ using System.IO; using NUnit.Framework; +using NzbDrone.Core.Datastore.Migration.Framework; +using NzbDrone.Test.Common.Datastore; namespace NzbDrone.Core.Test { @@ -10,13 +12,13 @@ namespace NzbDrone.Core.Test [OneTimeTearDown] public void ClearCachedDatabase() { - var mainCache = Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_Main.db"); + var mainCache = SqliteDatabase.GetCachedDb(MigrationType.Main); if (File.Exists(mainCache)) { File.Delete(mainCache); } - var logCache = Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_Log.db"); + var logCache = SqliteDatabase.GetCachedDb(MigrationType.Log); if (File.Exists(logCache)) { File.Delete(logCache); diff --git a/src/NzbDrone.Core.Test/Framework/TestDatabase.cs b/src/NzbDrone.Core.Test/Framework/TestDatabase.cs index 3fbfdf028..5391cfb1f 100644 --- a/src/NzbDrone.Core.Test/Framework/TestDatabase.cs +++ b/src/NzbDrone.Core.Test/Framework/TestDatabase.cs @@ -23,6 +23,7 @@ namespace NzbDrone.Core.Test.Framework where T : ModelBase, new(); IDirectDataMapper GetDirectDataMapper(); IDbConnection OpenConnection(); + DatabaseType DatabaseType { get; } } public class TestDatabase : ITestDatabase @@ -30,6 +31,8 @@ namespace NzbDrone.Core.Test.Framework private readonly IDatabase _dbConnection; private readonly IEventAggregator _eventAggregator; + public DatabaseType DatabaseType => _dbConnection.DatabaseType; + public TestDatabase(IDatabase dbConnection) { _eventAggregator = new Mock().Object; diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedReleasesFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedReleasesFixture.cs new file mode 100644 index 000000000..4b57909a2 --- /dev/null +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedReleasesFixture.cs @@ -0,0 +1,43 @@ +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Housekeeping.Housekeepers; +using NzbDrone.Core.Music; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.Housekeeping.Housekeepers +{ + [TestFixture] + public class CleanupOrphanedReleasesFixture : DbTest + { + [Test] + public void should_delete_orphaned_releases() + { + var albumRelease = Builder.CreateNew() + .BuildNew(); + + Db.Insert(albumRelease); + Subject.Clean(); + AllStoredModels.Should().BeEmpty(); + } + + [Test] + public void should_not_delete_unorphaned_albums() + { + var album = Builder.CreateNew() + .BuildNew(); + + Db.Insert(album); + + var albumReleases = Builder.CreateListOfSize(2) + .TheFirst(1) + .With(e => e.AlbumId = album.Id) + .BuildListOfNew(); + + Db.InsertMany(albumReleases); + Subject.Clean(); + AllStoredModels.Should().HaveCount(1); + AllStoredModels.Should().Contain(e => e.AlbumId == album.Id); + } + } +} diff --git a/src/NzbDrone.Core.Test/MusicTests/AlbumRepositoryTests/AlbumRepositoryFixture.cs b/src/NzbDrone.Core.Test/MusicTests/AlbumRepositoryTests/AlbumRepositoryFixture.cs index c6bc5cbb9..5d57b2755 100644 --- a/src/NzbDrone.Core.Test/MusicTests/AlbumRepositoryTests/AlbumRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/AlbumRepositoryTests/AlbumRepositoryFixture.cs @@ -3,7 +3,9 @@ using System.Collections.Generic; using System.Linq; using FizzWare.NBuilder; using FluentAssertions; +using FluentAssertions.Equivalency; using NUnit.Framework; +using NzbDrone.Core.Datastore; using NzbDrone.Core.Music; using NzbDrone.Core.Test.Framework; @@ -23,6 +25,13 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests [SetUp] public void Setup() { + AssertionOptions.AssertEquivalencyUsing(options => + { + options.Using(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation.ToUniversalTime())).WhenTypeIs(); + options.Using(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation.Value.ToUniversalTime())).WhenTypeIs(); + return options; + }); + _artist = new Artist { Name = "Alien Ant Farm", @@ -184,7 +193,7 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests GivenMultipleAlbums(); var result = _albumRepo.GetNextAlbums(new[] { _artist.ArtistMetadataId }); - result.Should().BeEquivalentTo(_albums.Take(1)); + result.Should().BeEquivalentTo(_albums.Take(1), AlbumComparerOptions); } [Test] @@ -193,7 +202,11 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests GivenMultipleAlbums(); var result = _albumRepo.GetLastAlbums(new[] { _artist.ArtistMetadataId }); - result.Should().BeEquivalentTo(_albums.Skip(2).Take(1)); + result.Should().BeEquivalentTo(_albums.Skip(2).Take(1), AlbumComparerOptions); } + + private EquivalencyAssertionOptions AlbumComparerOptions(EquivalencyAssertionOptions opts) => opts.ComparingByMembers() + .Excluding(ctx => ctx.SelectedMemberInfo.MemberType.IsGenericType && ctx.SelectedMemberInfo.MemberType.GetGenericTypeDefinition() == typeof(LazyLoaded<>)) + .Excluding(x => x.ArtistId); } } diff --git a/src/NzbDrone.Core.Test/MusicTests/ArtistRepositoryTests/ArtistRepositoryFixture.cs b/src/NzbDrone.Core.Test/MusicTests/ArtistRepositoryTests/ArtistRepositoryFixture.cs index 48b5530cf..50c28fe30 100644 --- a/src/NzbDrone.Core.Test/MusicTests/ArtistRepositoryTests/ArtistRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/ArtistRepositoryTests/ArtistRepositoryFixture.cs @@ -3,8 +3,10 @@ using System.Collections.Generic; using System.Data.SQLite; using FizzWare.NBuilder; using FluentAssertions; +using Npgsql; using NUnit.Framework; using NzbDrone.Common.Extensions; +using NzbDrone.Core.Datastore; using NzbDrone.Core.Music; using NzbDrone.Core.Profiles.Metadata; using NzbDrone.Core.Profiles.Qualities; @@ -159,7 +161,14 @@ namespace NzbDrone.Core.Test.MusicTests.ArtistRepositoryTests _artistRepo.Insert(artist1); Action insertDupe = () => _artistRepo.Insert(artist2); - insertDupe.Should().Throw(); + if (Db.DatabaseType == DatabaseType.PostgreSQL) + { + insertDupe.Should().Throw(); + } + else + { + insertDupe.Should().Throw(); + } } } } diff --git a/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumReleaseServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumReleaseServiceFixture.cs index f1df30bb2..cd9a83bfc 100644 --- a/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumReleaseServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumReleaseServiceFixture.cs @@ -39,7 +39,7 @@ namespace NzbDrone.Core.Test.MusicTests .BuildList(); Mocker.GetMock() - .Setup(s => s.GetTracksForRefresh(_release.Id, It.IsAny>())) + .Setup(s => s.GetTracksForRefresh(_release.Id, It.IsAny>())) .Returns(_tracks); } @@ -78,7 +78,7 @@ namespace NzbDrone.Core.Test.MusicTests .Returns(clash); Mocker.GetMock() - .Setup(x => x.GetTracksForRefresh(It.IsAny(), It.IsAny>())) + .Setup(x => x.GetTracksForRefresh(It.IsAny(), It.IsAny>())) .Returns(_tracks); var newInfo = existing.JsonClone(); @@ -117,7 +117,7 @@ namespace NzbDrone.Core.Test.MusicTests newInfo.Tracks = new List { newTrack }; Mocker.GetMock() - .Setup(s => s.GetTracksForRefresh(_release.Id, It.IsAny>())) + .Setup(s => s.GetTracksForRefresh(_release.Id, It.IsAny>())) .Returns(new List { oldTrack }); Subject.RefreshEntityInfo(_release, new List { newInfo }, false, false, null); diff --git a/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs index 56b98c2b7..0ad534b64 100644 --- a/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs @@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.MusicTests .Returns(_artist); Mocker.GetMock() - .Setup(s => s.GetReleasesForRefresh(album1.Id, It.IsAny>())) + .Setup(s => s.GetReleasesForRefresh(album1.Id, It.IsAny>())) .Returns(new List { release }); Mocker.GetMock() @@ -133,7 +133,7 @@ namespace NzbDrone.Core.Test.MusicTests .Returns(new List()); Mocker.GetMock() - .Setup(x => x.GetReleasesForRefresh(It.IsAny(), It.IsAny>())) + .Setup(x => x.GetReleasesForRefresh(It.IsAny(), It.IsAny>())) .Returns(_releases); var newAlbumInfo = existing.JsonClone(); @@ -209,7 +209,7 @@ namespace NzbDrone.Core.Test.MusicTests .Build() as List; Mocker.GetMock() - .Setup(x => x.GetReleasesForRefresh(It.IsAny(), It.IsAny>())) + .Setup(x => x.GetReleasesForRefresh(It.IsAny(), It.IsAny>())) .Returns(existingReleases); Mocker.GetMock() @@ -263,7 +263,7 @@ namespace NzbDrone.Core.Test.MusicTests .Build() as List; Mocker.GetMock() - .Setup(x => x.GetReleasesForRefresh(It.IsAny(), It.IsAny>())) + .Setup(x => x.GetReleasesForRefresh(It.IsAny(), It.IsAny>())) .Returns(existingReleases); Mocker.GetMock() @@ -318,7 +318,7 @@ namespace NzbDrone.Core.Test.MusicTests .Build() as List; Mocker.GetMock() - .Setup(x => x.GetReleasesForRefresh(It.IsAny(), It.IsAny>())) + .Setup(x => x.GetReleasesForRefresh(It.IsAny(), It.IsAny>())) .Returns(existingReleases); Mocker.GetMock() @@ -376,7 +376,7 @@ namespace NzbDrone.Core.Test.MusicTests .Build() as List; Mocker.GetMock() - .Setup(x => x.GetReleasesForRefresh(It.IsAny(), It.IsAny>())) + .Setup(x => x.GetReleasesForRefresh(It.IsAny(), It.IsAny>())) .Returns(existingReleases); Mocker.GetMock() diff --git a/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs index f6a865f52..aacbde747 100644 --- a/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs @@ -97,7 +97,7 @@ namespace NzbDrone.Core.Test.MusicTests private void GivenAlbumsForRefresh(List albums) { Mocker.GetMock(MockBehavior.Strict) - .Setup(s => s.GetAlbumsForRefresh(It.IsAny(), It.IsAny>())) + .Setup(s => s.GetAlbumsForRefresh(It.IsAny(), It.IsAny>())) .Returns(albums); } @@ -229,7 +229,7 @@ namespace NzbDrone.Core.Test.MusicTests Mocker.GetMock(MockBehavior.Strict) .InSequence(seq) - .Setup(x => x.GetAlbumsForRefresh(It.IsAny(), It.IsAny>())) + .Setup(x => x.GetAlbumsForRefresh(It.IsAny(), It.IsAny>())) .Returns(new List()); // Update called twice for a move/merge @@ -289,7 +289,7 @@ namespace NzbDrone.Core.Test.MusicTests Mocker.GetMock(MockBehavior.Strict) .InSequence(seq) - .Setup(x => x.GetAlbumsForRefresh(clash.ArtistMetadataId, It.IsAny>())) + .Setup(x => x.GetAlbumsForRefresh(clash.ArtistMetadataId, It.IsAny>())) .Returns(_albums); // Update called twice for a move/merge diff --git a/src/NzbDrone.Core/ArtistStats/ArtistStatisticsRepository.cs b/src/NzbDrone.Core/ArtistStats/ArtistStatisticsRepository.cs index 29ec139ba..17fee9dc2 100644 --- a/src/NzbDrone.Core/ArtistStats/ArtistStatisticsRepository.cs +++ b/src/NzbDrone.Core/ArtistStats/ArtistStatisticsRepository.cs @@ -16,7 +16,7 @@ namespace NzbDrone.Core.ArtistStats public class ArtistStatisticsRepository : IArtistStatisticsRepository { - private const string _selectTemplate = "SELECT /**select**/ FROM Tracks /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/"; + private const string _selectTemplate = "SELECT /**select**/ FROM \"Tracks\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/"; private readonly IMainDatabase _database; @@ -28,14 +28,27 @@ namespace NzbDrone.Core.ArtistStats public List ArtistStatistics() { var time = DateTime.UtcNow; + + if (_database.DatabaseType == DatabaseType.PostgreSQL) + { + return Query(Builder().WherePostgres(x => x.ReleaseDate < time)); + } + return Query(Builder().Where(x => x.ReleaseDate < time)); } public List ArtistStatistics(int artistId) { var time = DateTime.UtcNow; + + if (_database.DatabaseType == DatabaseType.PostgreSQL) + { + return Query(Builder().WherePostgres(x => x.ReleaseDate < time) + .WherePostgres(x => x.Id == artistId)); + } + return Query(Builder().Where(x => x.ReleaseDate < time) - .Where(x => x.Id == artistId)); + .Where(x => x.Id == artistId)); } private List Query(SqlBuilder builder) @@ -48,20 +61,23 @@ namespace NzbDrone.Core.ArtistStats } } - private SqlBuilder Builder() => new SqlBuilder() - .Select(@"Artists.Id AS ArtistId, - Albums.Id AS AlbumId, - SUM(COALESCE(TrackFiles.Size, 0)) AS SizeOnDisk, - COUNT(Tracks.Id) AS TotalTrackCount, - SUM(CASE WHEN Tracks.TrackFileId > 0 THEN 1 ELSE 0 END) AS AvailableTrackCount, - SUM(CASE WHEN Albums.Monitored = 1 OR Tracks.TrackFileId > 0 THEN 1 ELSE 0 END) AS TrackCount, - SUM(CASE WHEN TrackFiles.Id IS NULL THEN 0 ELSE 1 END) AS TrackFileCount") - .Join((t, r) => t.AlbumReleaseId == r.Id) - .Join((r, a) => r.AlbumId == a.Id) - .Join((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId) - .LeftJoin((t, f) => t.TrackFileId == f.Id) - .Where(x => x.Monitored == true) - .GroupBy(x => x.Id) - .GroupBy(x => x.Id); + private SqlBuilder Builder() + { + return new SqlBuilder(_database.DatabaseType) + .Select(@"""Artists"".""Id"" AS ""ArtistId"", + ""Albums"".""Id"" AS ""AlbumId"", + SUM(COALESCE(""TrackFiles"".""Size"", 0)) AS ""SizeOnDisk"", + COUNT(""Tracks"".""Id"") AS ""TotalTrackCount"", + SUM(CASE WHEN ""Tracks"".""TrackFileId"" > 0 THEN 1 ELSE 0 END) AS ""AvailableTrackCount"", + SUM(CASE WHEN ""Albums"".""Monitored"" = true OR ""Tracks"".""TrackFileId"" > 0 THEN 1 ELSE 0 END) AS ""TrackCount"", + SUM(CASE WHEN ""TrackFiles"".""Id"" IS NULL THEN 0 ELSE 1 END) AS ""TrackFileCount""") + .Join((t, r) => t.AlbumReleaseId == r.Id) + .Join((r, a) => r.AlbumId == a.Id) + .Join((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId) + .LeftJoin((t, f) => t.TrackFileId == f.Id) + .Where(x => x.Monitored == true) + .GroupBy(x => x.Id) + .GroupBy(x => x.Id); + } } } diff --git a/src/NzbDrone.Core/Blocklisting/BlocklistRepository.cs b/src/NzbDrone.Core/Blocklisting/BlocklistRepository.cs index f80e997d9..d2368d400 100644 --- a/src/NzbDrone.Core/Blocklisting/BlocklistRepository.cs +++ b/src/NzbDrone.Core/Blocklisting/BlocklistRepository.cs @@ -40,7 +40,7 @@ namespace NzbDrone.Core.Blocklisting Delete(x => artistIds.Contains(x.ArtistId)); } - protected override SqlBuilder PagedBuilder() => new SqlBuilder().Join((b, m) => b.ArtistId == m.Id); + protected override SqlBuilder PagedBuilder() => new SqlBuilder(_database.DatabaseType).Join((b, m) => b.ArtistId == m.Id); protected override IEnumerable PagedQuery(SqlBuilder builder) => _database.QueryJoined(builder, (bl, artist) => { bl.Artist = artist; diff --git a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs index 99b11aba0..a69101a9d 100644 --- a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs +++ b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs @@ -4,12 +4,14 @@ using System.IO; using System.Linq; using System.Xml; using System.Xml.Linq; +using Microsoft.Extensions.Options; using NzbDrone.Common.Cache; using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Core.Authentication; using NzbDrone.Core.Configuration.Events; +using NzbDrone.Core.Datastore; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; @@ -49,6 +51,12 @@ namespace NzbDrone.Core.Configuration int SyslogPort { get; } string SyslogLevel { get; } string Theme { get; } + string PostgresHost { get; } + int PostgresPort { get; } + string PostgresUser { get; } + string PostgresPassword { get; } + string PostgresMainDb { get; } + string PostgresLogDb { get; } } public class ConfigFileProvider : IConfigFileProvider @@ -58,6 +66,7 @@ namespace NzbDrone.Core.Configuration private readonly IEventAggregator _eventAggregator; private readonly IDiskProvider _diskProvider; private readonly ICached _cache; + private readonly PostgresOptions _postgresOptions; private readonly string _configFile; @@ -66,12 +75,14 @@ namespace NzbDrone.Core.Configuration public ConfigFileProvider(IAppFolderInfo appFolderInfo, ICacheManager cacheManager, IEventAggregator eventAggregator, - IDiskProvider diskProvider) + IDiskProvider diskProvider, + IOptions postgresOptions) { _cache = cacheManager.GetCache(GetType()); _eventAggregator = eventAggregator; _diskProvider = diskProvider; _configFile = appFolderInfo.GetConfigPath(); + _postgresOptions = postgresOptions.Value; } public Dictionary GetConfigDictionary() @@ -186,6 +197,12 @@ namespace NzbDrone.Core.Configuration public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false); public string Theme => GetValue("Theme", "light", persist: false); + public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false); + public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false); + public string PostgresPassword => _postgresOptions?.Password ?? GetValue("PostgresPassword", string.Empty, persist: false); + public string PostgresMainDb => _postgresOptions?.MainDb ?? GetValue("PostgresMainDb", "lidarr-main", persist: false); + public string PostgresLogDb => _postgresOptions?.LogDb ?? GetValue("PostgresLogDb", "lidarr-log", persist: false); + public int PostgresPort => (_postgresOptions?.Port ?? 0) != 0 ? _postgresOptions.Port : GetValueInt("PostgresPort", 5432, persist: false); public bool LogSql => GetValueBoolean("LogSql", false, persist: false); public int LogRotate => GetValueInt("LogRotate", 50, persist: false); public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false); diff --git a/src/NzbDrone.Core/Datastore/BasicRepository.cs b/src/NzbDrone.Core/Datastore/BasicRepository.cs index 02e16f2ab..8f997ad8f 100644 --- a/src/NzbDrone.Core/Datastore/BasicRepository.cs +++ b/src/NzbDrone.Core/Datastore/BasicRepository.cs @@ -67,7 +67,7 @@ namespace NzbDrone.Core.Datastore _updateSql = GetUpdateSql(_properties); } - protected virtual SqlBuilder Builder() => new SqlBuilder(); + protected virtual SqlBuilder Builder() => new SqlBuilder(_database.DatabaseType); protected virtual List Query(SqlBuilder builder) => _database.Query(builder).ToList(); @@ -79,7 +79,7 @@ namespace NzbDrone.Core.Datastore { using (var conn = _database.OpenConnection()) { - return conn.ExecuteScalar($"SELECT COUNT(*) FROM {_table}"); + return conn.ExecuteScalar($"SELECT COUNT(*) FROM \"{_table}\""); } } @@ -175,6 +175,11 @@ namespace NzbDrone.Core.Datastore } } + if (_database.DatabaseType == DatabaseType.PostgreSQL) + { + return $"INSERT INTO \"{_table}\" ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}) RETURNING \"Id\""; + } + return $"INSERT INTO {_table} ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}); SELECT last_insert_rowid() id"; } @@ -182,7 +187,8 @@ namespace NzbDrone.Core.Datastore { SqlBuilderExtensions.LogQuery(_insertSql, model); var multi = connection.QueryMultiple(_insertSql, model, transaction); - var id = (int)multi.Read().First().id; + var multiRead = multi.Read(); + var id = (int)(multiRead.First().id ?? multiRead.First().Id); _keyProperty.SetValue(model, id); return model; @@ -293,7 +299,7 @@ namespace NzbDrone.Core.Datastore { using (var conn = _database.OpenConnection()) { - conn.Execute($"DELETE FROM [{_table}]"); + conn.Execute($"DELETE FROM \"{_table}\""); } if (vacuum) @@ -352,7 +358,7 @@ namespace NzbDrone.Core.Datastore private string GetUpdateSql(List propertiesToUpdate) { var sb = new StringBuilder(); - sb.AppendFormat("UPDATE {0} SET ", _table); + sb.AppendFormat("UPDATE \"{0}\" SET ", _table); for (var i = 0; i < propertiesToUpdate.Count; i++) { @@ -420,9 +426,10 @@ namespace NzbDrone.Core.Datastore pagingSpec.SortKey = $"{_table}.{_keyProperty.Name}"; } + var sortKey = TableMapping.Mapper.GetSortKey(pagingSpec.SortKey); var sortDirection = pagingSpec.SortDirection == SortDirection.Descending ? "DESC" : "ASC"; - var pagingOffset = (pagingSpec.Page - 1) * pagingSpec.PageSize; - builder.OrderBy($"{pagingSpec.SortKey} {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}"); + var pagingOffset = Math.Max(pagingSpec.Page - 1, 0) * pagingSpec.PageSize; + builder.OrderBy($"\"{sortKey}\" {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}"); return queryFunc(builder).ToList(); } diff --git a/src/NzbDrone.Core/Datastore/ConnectionStringFactory.cs b/src/NzbDrone.Core/Datastore/ConnectionStringFactory.cs index cc087b450..bf53f907c 100644 --- a/src/NzbDrone.Core/Datastore/ConnectionStringFactory.cs +++ b/src/NzbDrone.Core/Datastore/ConnectionStringFactory.cs @@ -1,7 +1,9 @@ using System; using System.Data.SQLite; +using Npgsql; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; +using NzbDrone.Core.Configuration; namespace NzbDrone.Core.Datastore { @@ -14,10 +16,17 @@ namespace NzbDrone.Core.Datastore public class ConnectionStringFactory : IConnectionStringFactory { - public ConnectionStringFactory(IAppFolderInfo appFolderInfo) + private readonly IConfigFileProvider _configFileProvider; + + public ConnectionStringFactory(IAppFolderInfo appFolderInfo, IConfigFileProvider configFileProvider) { - MainDbConnectionString = GetConnectionString(appFolderInfo.GetDatabase()); - LogDbConnectionString = GetConnectionString(appFolderInfo.GetLogDatabase()); + _configFileProvider = configFileProvider; + + MainDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresMainDb) : + GetConnectionString(appFolderInfo.GetDatabase()); + + LogDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresLogDb) : + GetConnectionString(appFolderInfo.GetLogDatabase()); } public string MainDbConnectionString { get; private set; } @@ -48,5 +57,19 @@ namespace NzbDrone.Core.Datastore return connectionBuilder.ConnectionString; } + + private string GetPostgresConnectionString(string dbName) + { + var connectionBuilder = new NpgsqlConnectionStringBuilder(); + + connectionBuilder.Database = dbName; + connectionBuilder.Host = _configFileProvider.PostgresHost; + connectionBuilder.Username = _configFileProvider.PostgresUser; + connectionBuilder.Password = _configFileProvider.PostgresPassword; + connectionBuilder.Port = _configFileProvider.PostgresPort; + connectionBuilder.Enlist = false; + + return connectionBuilder.ConnectionString; + } } } diff --git a/src/NzbDrone.Core/Datastore/Database.cs b/src/NzbDrone.Core/Datastore/Database.cs index a9b6e807f..433fef0b6 100644 --- a/src/NzbDrone.Core/Datastore/Database.cs +++ b/src/NzbDrone.Core/Datastore/Database.cs @@ -1,5 +1,6 @@ using System; using System.Data; +using System.Text.RegularExpressions; using Dapper; using NLog; using NzbDrone.Common.Instrumentation; @@ -11,6 +12,7 @@ namespace NzbDrone.Core.Datastore IDbConnection OpenConnection(); Version Version { get; } int Migration { get; } + DatabaseType DatabaseType { get; } void Vacuum(); } @@ -32,13 +34,44 @@ namespace NzbDrone.Core.Datastore return _datamapperFactory(); } + public DatabaseType DatabaseType + { + get + { + using (var db = _datamapperFactory()) + { + if (db.ConnectionString.Contains(".db")) + { + return DatabaseType.SQLite; + } + else + { + return DatabaseType.PostgreSQL; + } + } + } + } + public Version Version { get { using (var db = _datamapperFactory()) { - var version = db.QueryFirstOrDefault("SELECT sqlite_version()"); + string version; + + try + { + version = db.QueryFirstOrDefault("SHOW server_version"); + + //Postgres can return extra info about operating system on version call, ignore this + version = Regex.Replace(version, @"\(.*?\)", ""); + } + catch + { + version = db.QueryFirstOrDefault("SELECT sqlite_version()"); + } + return new Version(version); } } @@ -50,7 +83,7 @@ namespace NzbDrone.Core.Datastore { using (var db = _datamapperFactory()) { - return db.QueryFirstOrDefault("SELECT version from VersionInfo ORDER BY version DESC LIMIT 1"); + return db.QueryFirstOrDefault("SELECT \"Version\" from \"VersionInfo\" ORDER BY \"Version\" DESC LIMIT 1"); } } } @@ -73,4 +106,10 @@ namespace NzbDrone.Core.Datastore } } } + + public enum DatabaseType + { + SQLite, + PostgreSQL + } } diff --git a/src/NzbDrone.Core/Datastore/DbFactory.cs b/src/NzbDrone.Core/Datastore/DbFactory.cs index 9fa0181d9..1b21589a2 100644 --- a/src/NzbDrone.Core/Datastore/DbFactory.cs +++ b/src/NzbDrone.Core/Datastore/DbFactory.cs @@ -1,6 +1,8 @@ using System; +using System.Data.Common; using System.Data.SQLite; using NLog; +using Npgsql; using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Exceptions; @@ -85,10 +87,19 @@ namespace NzbDrone.Core.Datastore var db = new Database(migrationContext.MigrationType.ToString(), () => { - var conn = SQLiteFactory.Instance.CreateConnection(); - conn.ConnectionString = connectionString; - conn.Open(); + DbConnection conn; + if (connectionString.Contains(".db")) + { + conn = SQLiteFactory.Instance.CreateConnection(); + conn.ConnectionString = connectionString; + } + else + { + conn = new NpgsqlConnection(connectionString); + } + + conn.Open(); return conn; }); diff --git a/src/NzbDrone.Core/Datastore/Extensions/BuilderExtensions.cs b/src/NzbDrone.Core/Datastore/Extensions/BuilderExtensions.cs index 97ce5d731..3bff36b7a 100644 --- a/src/NzbDrone.Core/Datastore/Extensions/BuilderExtensions.cs +++ b/src/NzbDrone.Core/Datastore/Extensions/BuilderExtensions.cs @@ -20,12 +20,12 @@ namespace NzbDrone.Core.Datastore public static SqlBuilder Select(this SqlBuilder builder, params Type[] types) { - return builder.Select(types.Select(x => TableMapping.Mapper.TableNameMapping(x) + ".*").Join(", ")); + return builder.Select(types.Select(x => $"\"{TableMapping.Mapper.TableNameMapping(x)}\".*").Join(", ")); } public static SqlBuilder SelectDistinct(this SqlBuilder builder, params Type[] types) { - return builder.Select("DISTINCT " + types.Select(x => TableMapping.Mapper.TableNameMapping(x) + ".*").Join(", ")); + return builder.Select("DISTINCT " + types.Select(x => $"\"{TableMapping.Mapper.TableNameMapping(x)}\".*").Join(", ")); } public static SqlBuilder SelectCount(this SqlBuilder builder) @@ -42,41 +42,48 @@ namespace NzbDrone.Core.Datastore public static SqlBuilder Where(this SqlBuilder builder, Expression> filter) { - var wb = new WhereBuilder(filter, true, builder.Sequence); + var wb = GetWhereBuilder(builder.DatabaseType, filter, true, builder.Sequence); + + return builder.Where(wb.ToString(), wb.Parameters); + } + + public static SqlBuilder WherePostgres(this SqlBuilder builder, Expression> filter) + { + var wb = new WhereBuilderPostgres(filter, true, builder.Sequence); return builder.Where(wb.ToString(), wb.Parameters); } public static SqlBuilder OrWhere(this SqlBuilder builder, Expression> filter) { - var wb = new WhereBuilder(filter, true, builder.Sequence); + var wb = GetWhereBuilder(builder.DatabaseType, filter, true, builder.Sequence); return builder.OrWhere(wb.ToString(), wb.Parameters); } public static SqlBuilder Join(this SqlBuilder builder, Expression> filter) { - var wb = new WhereBuilder(filter, false, builder.Sequence); + var wb = GetWhereBuilder(builder.DatabaseType, filter, false, builder.Sequence); var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight)); - return builder.Join($"{rightTable} ON {wb.ToString()}"); + return builder.Join($"\"{rightTable}\" ON {wb.ToString()}"); } public static SqlBuilder LeftJoin(this SqlBuilder builder, Expression> filter) { - var wb = new WhereBuilder(filter, false, builder.Sequence); + var wb = GetWhereBuilder(builder.DatabaseType, filter, false, builder.Sequence); var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight)); - return builder.LeftJoin($"{rightTable} ON {wb.ToString()}"); + return builder.LeftJoin($"\"{rightTable}\" ON {wb.ToString()}"); } public static SqlBuilder GroupBy(this SqlBuilder builder, Expression> property) { var table = TableMapping.Mapper.TableNameMapping(typeof(TModel)); var propName = property.GetMemberName().Name; - return builder.GroupBy($"{table}.{propName}"); + return builder.GroupBy($"\"{table}\".\"{propName}\""); } public static SqlBuilder.Template AddSelectTemplate(this SqlBuilder builder, Type type) @@ -138,6 +145,18 @@ namespace NzbDrone.Core.Datastore return sb.ToString(); } + private static WhereBuilder GetWhereBuilder(DatabaseType databaseType, Expression filter, bool requireConcrete, int seq) + { + if (databaseType == DatabaseType.PostgreSQL) + { + return new WhereBuilderPostgres(filter, requireConcrete, seq); + } + else + { + return new WhereBuilderSqlite(filter, requireConcrete, seq); + } + } + private static Dictionary ToDictionary(this DynamicParameters dynamicParams) { var argsDictionary = new Dictionary(); @@ -150,11 +169,14 @@ namespace NzbDrone.Core.Datastore } var templates = dynamicParams.GetType().GetField("templates", BindingFlags.NonPublic | BindingFlags.Instance); - if (templates != null && templates.GetValue(dynamicParams) is List list) + if (templates != null) { - foreach (var objProps in list.Select(obj => obj.GetPropertyValuePairs().ToList())) + if (templates.GetValue(dynamicParams) is List list) { - objProps.ForEach(p => argsDictionary.Add(p.Key, p.Value)); + foreach (var objProps in list.Select(obj => obj.GetPropertyValuePairs().ToList())) + { + objProps.ForEach(p => argsDictionary.Add(p.Key, p.Value)); + } } } diff --git a/src/NzbDrone.Core/Datastore/LogDatabase.cs b/src/NzbDrone.Core/Datastore/LogDatabase.cs index f992c8bbe..a770c2661 100644 --- a/src/NzbDrone.Core/Datastore/LogDatabase.cs +++ b/src/NzbDrone.Core/Datastore/LogDatabase.cs @@ -10,10 +10,12 @@ namespace NzbDrone.Core.Datastore public class LogDatabase : ILogDatabase { private readonly IDatabase _database; + private readonly DatabaseType _databaseType; public LogDatabase(IDatabase database) { _database = database; + _databaseType = _database == null ? DatabaseType.SQLite : _database.DatabaseType; } public IDbConnection OpenConnection() @@ -25,6 +27,8 @@ namespace NzbDrone.Core.Datastore public int Migration => _database.Migration; + public DatabaseType DatabaseType => _databaseType; + public void Vacuum() { _database.Vacuum(); diff --git a/src/NzbDrone.Core/Datastore/MainDatabase.cs b/src/NzbDrone.Core/Datastore/MainDatabase.cs index 4a9d3298c..521293299 100644 --- a/src/NzbDrone.Core/Datastore/MainDatabase.cs +++ b/src/NzbDrone.Core/Datastore/MainDatabase.cs @@ -10,10 +10,12 @@ namespace NzbDrone.Core.Datastore public class MainDatabase : IMainDatabase { private readonly IDatabase _database; + private readonly DatabaseType _databaseType; public MainDatabase(IDatabase database) { _database = database; + _databaseType = _database == null ? DatabaseType.SQLite : _database.DatabaseType; } public IDbConnection OpenConnection() @@ -25,6 +27,8 @@ namespace NzbDrone.Core.Datastore public int Migration => _database.Migration; + public DatabaseType DatabaseType => _databaseType; + public void Vacuum() { _database.Vacuum(); diff --git a/src/NzbDrone.Core/Datastore/Migration/001_initial_setup.cs b/src/NzbDrone.Core/Datastore/Migration/001_initial_setup.cs index 7d6d9a190..a38e84073 100644 --- a/src/NzbDrone.Core/Datastore/Migration/001_initial_setup.cs +++ b/src/NzbDrone.Core/Datastore/Migration/001_initial_setup.cs @@ -288,8 +288,8 @@ namespace NzbDrone.Core.Datastore.Migration Insert.IntoTable("DelayProfiles").Row(new { - EnableUsenet = 1, - EnableTorrent = 1, + EnableUsenet = true, + EnableTorrent = true, PreferredProtocol = 1, UsenetDelay = 0, TorrentDelay = 0, diff --git a/src/NzbDrone.Core/Datastore/Migration/003_add_medium_support.cs b/src/NzbDrone.Core/Datastore/Migration/003_add_medium_support.cs index f0b68d390..9e0ddf501 100644 --- a/src/NzbDrone.Core/Datastore/Migration/003_add_medium_support.cs +++ b/src/NzbDrone.Core/Datastore/Migration/003_add_medium_support.cs @@ -12,7 +12,7 @@ namespace NzbDrone.Core.Datastore.Migration Alter.Table("Tracks").AddColumn("MediumNumber").AsInt32().WithDefaultValue(0); Alter.Table("Tracks").AddColumn("AbsoluteTrackNumber").AsInt32().WithDefaultValue(0); - Execute.Sql("UPDATE Tracks SET AbsoluteTrackNumber = TrackNumber"); + Execute.Sql("UPDATE \"Tracks\" SET \"AbsoluteTrackNumber\" = \"TrackNumber\""); Delete.Column("TrackNumber").FromTable("Tracks"); Alter.Table("Tracks").AddColumn("TrackNumber").AsString().Nullable(); diff --git a/src/NzbDrone.Core/Datastore/Migration/004_add_various_qualities_in_profile.cs b/src/NzbDrone.Core/Datastore/Migration/004_add_various_qualities_in_profile.cs index cabed21e5..ae58e81f5 100644 --- a/src/NzbDrone.Core/Datastore/Migration/004_add_various_qualities_in_profile.cs +++ b/src/NzbDrone.Core/Datastore/Migration/004_add_various_qualities_in_profile.cs @@ -13,7 +13,7 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - Execute.Sql("UPDATE QualityDefinitions SET Title = 'MP3-160' WHERE Quality = 5"); // Change MP3-512 to MP3-160 + Execute.Sql("UPDATE \"QualityDefinitions\" SET \"Title\" = 'MP3-160' WHERE \"Quality\" = 5"); // Change MP3-512 to MP3-160 Execute.WithConnection(ConvertProfile); } @@ -172,8 +172,17 @@ namespace NzbDrone.Core.Datastore.Migration using (var updateProfileCmd = _connection.CreateCommand()) { updateProfileCmd.Transaction = _transaction; - updateProfileCmd.CommandText = - "UPDATE Profiles SET Name = ?, Cutoff = ?, Items = ? WHERE Id = ?"; + if (_connection.GetType().FullName == "Npgsql.NpgsqlConnection") + { + updateProfileCmd.CommandText = + "UPDATE \"Profiles\" SET \"Name\" = $1, \"Cutoff\" = $2, \"Items\" = $3 WHERE \"Id\" = $4"; + } + else + { + updateProfileCmd.CommandText = + "UPDATE \"Profiles\" SET \"Name\" = ?, \"Cutoff\" = ?, \"Items\" = ? WHERE \"Id\" = ?"; + } + updateProfileCmd.AddParameter(profile.Name); updateProfileCmd.AddParameter(profile.Cutoff); updateProfileCmd.AddParameter(profile.Items.ToJson()); @@ -323,7 +332,7 @@ namespace NzbDrone.Core.Datastore.Migration using (var getProfilesCmd = _connection.CreateCommand()) { getProfilesCmd.Transaction = _transaction; - getProfilesCmd.CommandText = @"SELECT Id, Name, Cutoff, Items FROM Profiles"; + getProfilesCmd.CommandText = @"SELECT ""Id"", ""Name"", ""Cutoff"", ""Items"" FROM ""Profiles"""; using (var profileReader = getProfilesCmd.ExecuteReader()) { diff --git a/src/NzbDrone.Core/Datastore/Migration/006_separate_automatic_and_interactive_search.cs b/src/NzbDrone.Core/Datastore/Migration/006_separate_automatic_and_interactive_search.cs index 3f03f8207..7b5a49849 100644 --- a/src/NzbDrone.Core/Datastore/Migration/006_separate_automatic_and_interactive_search.cs +++ b/src/NzbDrone.Core/Datastore/Migration/006_separate_automatic_and_interactive_search.cs @@ -11,7 +11,7 @@ namespace NzbDrone.Core.Datastore.Migration Rename.Column("EnableSearch").OnTable("Indexers").To("EnableAutomaticSearch"); Alter.Table("Indexers").AddColumn("EnableInteractiveSearch").AsBoolean().Nullable(); - Execute.Sql("UPDATE Indexers SET EnableInteractiveSearch = EnableAutomaticSearch"); + Execute.Sql("UPDATE \"Indexers\" SET \"EnableInteractiveSearch\" = \"EnableAutomaticSearch\""); Alter.Table("Indexers").AlterColumn("EnableInteractiveSearch").AsBoolean().NotNullable(); } diff --git a/src/NzbDrone.Core/Datastore/Migration/008_change_quality_size_mb_to_kb.cs b/src/NzbDrone.Core/Datastore/Migration/008_change_quality_size_mb_to_kb.cs index 07cb91c3d..43a62161a 100644 --- a/src/NzbDrone.Core/Datastore/Migration/008_change_quality_size_mb_to_kb.cs +++ b/src/NzbDrone.Core/Datastore/Migration/008_change_quality_size_mb_to_kb.cs @@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - Execute.Sql("UPDATE QualityDefinitions SET MaxSize = CASE " + + IfDatabase("sqlite").Execute.Sql("UPDATE QualityDefinitions SET MaxSize = CASE " + "WHEN (CAST(MaxSize AS FLOAT) / 60) * 8 * 1024 < 1500 THEN " + "ROUND((CAST(MaxSize AS FLOAT) / 60) * 8 * 1024, 0) " + "ELSE NULL " + diff --git a/src/NzbDrone.Core/Datastore/Migration/012_add_release_status.cs b/src/NzbDrone.Core/Datastore/Migration/012_add_release_status.cs index cd1de385b..4a3423e95 100644 --- a/src/NzbDrone.Core/Datastore/Migration/012_add_release_status.cs +++ b/src/NzbDrone.Core/Datastore/Migration/012_add_release_status.cs @@ -115,7 +115,7 @@ namespace NzbDrone.Core.Datastore.Migration using (var getProfilesCmd = _connection.CreateCommand()) { getProfilesCmd.Transaction = _transaction; - getProfilesCmd.CommandText = @"SELECT Id, Name FROM MetadataProfiles"; + getProfilesCmd.CommandText = @"SELECT ""Id"", ""Name"" FROM ""MetadataProfiles"""; using (var profileReader = getProfilesCmd.ExecuteReader()) { diff --git a/src/NzbDrone.Core/Datastore/Migration/013_album_download_notification.cs b/src/NzbDrone.Core/Datastore/Migration/013_album_download_notification.cs index 39bba97be..ec13cd84f 100644 --- a/src/NzbDrone.Core/Datastore/Migration/013_album_download_notification.cs +++ b/src/NzbDrone.Core/Datastore/Migration/013_album_download_notification.cs @@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - Alter.Table("Notifications").AddColumn("OnAlbumDownload").AsBoolean().WithDefaultValue(0); + Alter.Table("Notifications").AddColumn("OnAlbumDownload").AsBoolean().WithDefaultValue(false); } } } diff --git a/src/NzbDrone.Core/Datastore/Migration/014_fix_language_metadata_profiles.cs b/src/NzbDrone.Core/Datastore/Migration/014_fix_language_metadata_profiles.cs index 20f62fa65..7072fa881 100644 --- a/src/NzbDrone.Core/Datastore/Migration/014_fix_language_metadata_profiles.cs +++ b/src/NzbDrone.Core/Datastore/Migration/014_fix_language_metadata_profiles.cs @@ -8,17 +8,17 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - Execute.Sql("UPDATE artists SET metadataProfileId = " + - "CASE WHEN ((SELECT COUNT(*) FROM metadataprofiles) > 0) " + - "THEN (SELECT id FROM metadataprofiles ORDER BY id ASC LIMIT 1) " + + Execute.Sql("UPDATE \"Artists\" SET \"MetadataProfileId\" = " + + "CASE WHEN ((SELECT COUNT(*) FROM \"MetadataProfiles\") > 0) " + + "THEN (SELECT \"Id\" FROM \"MetadataProfiles\" ORDER BY \"Id\" ASC LIMIT 1) " + "ELSE 0 END " + - "WHERE artists.metadataProfileId == 0"); + "WHERE \"Artists\".\"MetadataProfileId\" = 0"); - Execute.Sql("UPDATE artists SET languageProfileId = " + - "CASE WHEN ((SELECT COUNT(*) FROM languageprofiles) > 0) " + - "THEN (SELECT id FROM languageprofiles ORDER BY id ASC LIMIT 1) " + + Execute.Sql("UPDATE \"Artists\" SET \"LanguageProfileId\" = " + + "CASE WHEN ((SELECT COUNT(*) FROM \"LanguageProfiles\") > 0) " + + "THEN (SELECT \"Id\" FROM \"LanguageProfiles\" ORDER BY \"Id\" ASC LIMIT 1) " + "ELSE 0 END " + - "WHERE artists.languageProfileId == 0"); + "WHERE \"Artists\".\"LanguageProfileId\" = 0"); } } } diff --git a/src/NzbDrone.Core/Datastore/Migration/015_remove_fanzub.cs b/src/NzbDrone.Core/Datastore/Migration/015_remove_fanzub.cs index 75f4dc2c9..edb798f94 100644 --- a/src/NzbDrone.Core/Datastore/Migration/015_remove_fanzub.cs +++ b/src/NzbDrone.Core/Datastore/Migration/015_remove_fanzub.cs @@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - Execute.Sql("DELETE FROM Indexers WHERE Implementation = 'Fanzub';"); + Execute.Sql("DELETE FROM \"Indexers\" WHERE \"Implementation\" = 'Fanzub';"); } } } diff --git a/src/NzbDrone.Core/Datastore/Migration/019_add_ape_quality_in_profiles.cs b/src/NzbDrone.Core/Datastore/Migration/019_add_ape_quality_in_profiles.cs index 1bb698528..97266331b 100644 --- a/src/NzbDrone.Core/Datastore/Migration/019_add_ape_quality_in_profiles.cs +++ b/src/NzbDrone.Core/Datastore/Migration/019_add_ape_quality_in_profiles.cs @@ -71,7 +71,7 @@ namespace NzbDrone.Core.Datastore.Migration using (var updateProfileCmd = _connection.CreateCommand()) { updateProfileCmd.Transaction = _transaction; - updateProfileCmd.CommandText = "UPDATE Profiles SET Name = ?, Cutoff = ?, Items = ? WHERE Id = ?"; + updateProfileCmd.CommandText = "UPDATE \"Profiles\" SET \"Name\" = ?, \"Cutoff\" = ?, \"Items\" = ? WHERE \"Id\" = ?"; updateProfileCmd.AddParameter(profile.Name); updateProfileCmd.AddParameter(profile.Cutoff); updateProfileCmd.AddParameter(profile.Items.ToJson()); @@ -115,7 +115,7 @@ namespace NzbDrone.Core.Datastore.Migration using (var getProfilesCmd = _connection.CreateCommand()) { getProfilesCmd.Transaction = _transaction; - getProfilesCmd.CommandText = @"SELECT Id, Name, Cutoff, Items FROM Profiles"; + getProfilesCmd.CommandText = @"SELECT ""Id"", ""Name"", ""Cutoff"", ""Items"" FROM ""Profiles"""; using (var profileReader = getProfilesCmd.ExecuteReader()) { diff --git a/src/NzbDrone.Core/Datastore/Migration/023_add_release_groups_etc.cs b/src/NzbDrone.Core/Datastore/Migration/023_add_release_groups_etc.cs index 5e5a58c0a..0b095f234 100644 --- a/src/NzbDrone.Core/Datastore/Migration/023_add_release_groups_etc.cs +++ b/src/NzbDrone.Core/Datastore/Migration/023_add_release_groups_etc.cs @@ -2,11 +2,11 @@ using System; using System.Collections.Generic; using System.Data; using System.Linq; +using Dapper; using FluentMigrator; using NzbDrone.Common.Extensions; using NzbDrone.Common.Serializer; using NzbDrone.Core.Datastore.Migration.Framework; -using NzbDrone.Core.Music; namespace NzbDrone.Core.Datastore.Migration { @@ -30,18 +30,18 @@ namespace NzbDrone.Core.Datastore.Migration .WithColumn("Members").AsString().Nullable(); // we want to preserve the artist ID. Shove all the metadata into the metadata table. - Execute.Sql(@"INSERT INTO ArtistMetadata (ForeignArtistId, Name, Overview, Disambiguation, Type, Status, Images, Links, Genres, Ratings, Members) - SELECT ForeignArtistId, Name, Overview, Disambiguation, ArtistType, Status, Images, Links, Genres, Ratings, Members - FROM Artists"); + Execute.Sql(@"INSERT INTO ""ArtistMetadata"" (""ForeignArtistId"", ""Name"", ""Overview"", ""Disambiguation"", ""Type"", ""Status"", ""Images"", ""Links"", ""Genres"", ""Ratings"", ""Members"") + SELECT ""ForeignArtistId"", ""Name"", ""Overview"", ""Disambiguation"", ""ArtistType"", ""Status"", ""Images"", ""Links"", ""Genres"", ""Ratings"", ""Members"" + FROM ""Artists"""); // Add an ArtistMetadataId column to Artists Alter.Table("Artists").AddColumn("ArtistMetadataId").AsInt32().WithDefaultValue(0); // Update artistmetadataId - Execute.Sql(@"UPDATE Artists - SET ArtistMetadataId = (SELECT ArtistMetadata.Id - FROM ArtistMetadata - WHERE ArtistMetadata.ForeignArtistId = Artists.ForeignArtistId)"); + Execute.Sql(@"UPDATE ""Artists"" + SET ""ArtistMetadataId"" = (SELECT ""ArtistMetadata"".""Id"" + FROM ""ArtistMetadata"" + WHERE ""ArtistMetadata"".""ForeignArtistId"" = ""Artists"".""ForeignArtistId"")"); // ALBUM RELEASES TABLE - Do this before we mess with the Albums table Create.TableForModel("AlbumReleases") @@ -68,11 +68,11 @@ namespace NzbDrone.Core.Datastore.Migration Alter.Table("Albums").AddColumn("Links").AsString().Nullable(); // Set metadata ID - Execute.Sql(@"UPDATE Albums - SET ArtistMetadataId = (SELECT ArtistMetadata.Id - FROM ArtistMetadata - JOIN Artists ON ArtistMetadata.Id = Artists.ArtistMetadataId - WHERE Albums.ArtistId = Artists.Id)"); + Execute.Sql(@"UPDATE ""Albums"" + SET ""ArtistMetadataId"" = (SELECT ""ArtistMetadata"".""Id"" + FROM ""ArtistMetadata"" + JOIN ""Artists"" ON ""ArtistMetadata"".""Id"" = ""Artists"".""ArtistMetadataId"" + WHERE ""Albums"".""ArtistId"" = ""Artists"".""Id"")"); // TRACKS TABLE Alter.Table("Tracks").AddColumn("ForeignRecordingId").AsString().WithDefaultValue("0"); @@ -80,18 +80,18 @@ namespace NzbDrone.Core.Datastore.Migration Alter.Table("Tracks").AddColumn("ArtistMetadataId").AsInt32().WithDefaultValue(0); // Set track release to the only release we've bothered populating - Execute.Sql(@"UPDATE Tracks - SET AlbumReleaseId = (SELECT AlbumReleases.Id - FROM AlbumReleases - JOIN Albums ON AlbumReleases.AlbumId = Albums.Id - WHERE Albums.Id = Tracks.AlbumId)"); + Execute.Sql(@"UPDATE ""Tracks"" + SET ""AlbumReleaseId"" = (SELECT ""AlbumReleases"".""Id"" + FROM ""AlbumReleases"" + JOIN ""Albums"" ON ""AlbumReleases"".""AlbumId"" = ""Albums"".""Id"" + WHERE ""Albums"".""Id"" = ""Tracks"".""AlbumId"")"); // Set metadata ID - Execute.Sql(@"UPDATE Tracks - SET ArtistMetadataId = (SELECT ArtistMetadata.Id - FROM ArtistMetadata - JOIN Albums ON ArtistMetadata.Id = Albums.ArtistMetadataId - WHERE Tracks.AlbumId = Albums.Id)"); + Execute.Sql(@"UPDATE ""Tracks"" + SET ""ArtistMetadataId"" = (SELECT ""ArtistMetadata"".""Id"" + FROM ""ArtistMetadata"" + JOIN ""Albums"" ON ""ArtistMetadata"".""Id"" = ""Albums"".""ArtistMetadataId"" + WHERE ""Tracks"".""AlbumId"" = ""Albums"".""Id"")"); // CLEAR OUT OLD COLUMNS @@ -188,15 +188,15 @@ namespace NzbDrone.Core.Datastore.Migration public List Label { get; set; } } - private List ReadReleasesFromAlbums(IDbConnection conn, IDbTransaction tran) + private List ReadReleasesFromAlbums(IDbConnection conn, IDbTransaction tran) { // need to get all the old albums - var releases = new List(); + var releases = new List(); using (var getReleasesCmd = conn.CreateCommand()) { getReleasesCmd.Transaction = tran; - getReleasesCmd.CommandText = @"SELECT Id, CurrentRelease FROM Albums"; + getReleasesCmd.CommandText = @"SELECT ""Id"", ""CurrentRelease"" FROM ""Albums"""; using (var releaseReader = getReleasesCmd.ExecuteReader()) { @@ -205,16 +205,16 @@ namespace NzbDrone.Core.Datastore.Migration int albumId = releaseReader.GetInt32(0); var albumRelease = Json.Deserialize(releaseReader.GetString(1)); - AlbumRelease toInsert = null; + AlbumRelease023 toInsert = null; if (albumRelease != null) { - var media = new List(); + var media = new List(); for (var i = 1; i <= Math.Max(albumRelease.MediaCount, 1); i++) { - media.Add(new Medium { Number = i, Name = "", Format = albumRelease.Format ?? "Unknown" }); + media.Add(new Medium023 { Number = i, Name = "", Format = albumRelease.Format ?? "Unknown" }); } - toInsert = new AlbumRelease + toInsert = new AlbumRelease023 { AlbumId = albumId, ForeignReleaseId = albumRelease.Id.IsNotNullOrWhiteSpace() ? albumRelease.Id : albumId.ToString(), @@ -231,7 +231,7 @@ namespace NzbDrone.Core.Datastore.Migration } else { - toInsert = new AlbumRelease + toInsert = new AlbumRelease023 { AlbumId = albumId, ForeignReleaseId = albumId.ToString(), @@ -239,7 +239,7 @@ namespace NzbDrone.Core.Datastore.Migration Status = "", Label = new List(), Country = new List(), - Media = new List { new Medium { Name = "Unknown", Number = 1, Format = "Unknown" } }, + Media = new List { new Medium023 { Name = "Unknown", Number = 1, Format = "Unknown" } }, Monitored = true }; } @@ -252,31 +252,54 @@ namespace NzbDrone.Core.Datastore.Migration return releases; } - private void WriteReleasesToReleases(List releases, IDbConnection conn, IDbTransaction tran) + private void WriteReleasesToReleases(List releases, IDbConnection conn, IDbTransaction tran) { + var dbReleases = new List(); + foreach (var release in releases) { - using (var writeReleaseCmd = conn.CreateCommand()) + dbReleases.Add(new { - writeReleaseCmd.Transaction = tran; - writeReleaseCmd.CommandText = - "INSERT INTO AlbumReleases (AlbumId, ForeignReleaseId, Title, Status, Duration, Label, Disambiguation, Country, Media, TrackCount, Monitored) " + - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - writeReleaseCmd.AddParameter(release.AlbumId); - writeReleaseCmd.AddParameter(release.ForeignReleaseId); - writeReleaseCmd.AddParameter(release.Title); - writeReleaseCmd.AddParameter(release.Status); - writeReleaseCmd.AddParameter(release.Duration); - writeReleaseCmd.AddParameter(release.Label.ToJson()); - writeReleaseCmd.AddParameter(release.Disambiguation); - writeReleaseCmd.AddParameter(release.Country.ToJson()); - writeReleaseCmd.AddParameter(release.Media.ToJson()); - writeReleaseCmd.AddParameter(release.TrackCount); - writeReleaseCmd.AddParameter(release.Monitored); - - writeReleaseCmd.ExecuteNonQuery(); - } + AlbumId = release.AlbumId, + ForeignReleaseId = release.ForeignReleaseId, + Title = release.Title, + Status = release.Status, + Duration = release.Duration, + Label = release.Label.ToJson(), + Disambiguation = release.Disambiguation, + Country = release.Country.ToJson(), + Media = release.Media.ToJson(), + TrackCount = release.TrackCount, + Monitored = release.Monitored + }); } + + var updateSql = "INSERT INTO \"AlbumReleases\" (\"AlbumId\", \"ForeignReleaseId\", \"Title\", \"Status\", \"Duration\", \"Label\", \"Disambiguation\", \"Country\", \"Media\", \"TrackCount\", \"Monitored\") " + + "VALUES (@AlbumId, @ForeignReleaseId, @Title, @Status, @Duration, @Label, @Disambiguation, @Country, @Media, @TrackCount, @Monitored)"; + + conn.Execute(updateSql, dbReleases, transaction: tran); + } + + public class AlbumRelease023 + { + public int AlbumId { get; set; } + public string ForeignReleaseId { get; set; } + public string Title { get; set; } + public string Status { get; set; } + public int Duration { get; set; } + public List Label { get; set; } + public string Disambiguation { get; set; } + public List Country { get; set; } + public List Media { get; set; } + public int TrackCount { get; set; } + public bool Monitored { get; set; } + } + + public class Medium023 + { + public int Number { get; set; } + public string Name { get; set; } + public string Format { get; set; } } } } diff --git a/src/NzbDrone.Core/Datastore/Migration/026_rename_quality_profiles_add_upgrade_allowed.cs b/src/NzbDrone.Core/Datastore/Migration/026_rename_quality_profiles_add_upgrade_allowed.cs index 7263f9217..4bf67b5d7 100644 --- a/src/NzbDrone.Core/Datastore/Migration/026_rename_quality_profiles_add_upgrade_allowed.cs +++ b/src/NzbDrone.Core/Datastore/Migration/026_rename_quality_profiles_add_upgrade_allowed.cs @@ -10,8 +10,8 @@ namespace NzbDrone.Core.Datastore.Migration { Rename.Table("Profiles").To("QualityProfiles"); - Alter.Table("QualityProfiles").AddColumn("UpgradeAllowed").AsInt32().Nullable(); - Alter.Table("LanguageProfiles").AddColumn("UpgradeAllowed").AsInt32().Nullable(); + Alter.Table("QualityProfiles").AddColumn("UpgradeAllowed").AsBoolean().Nullable(); + Alter.Table("LanguageProfiles").AddColumn("UpgradeAllowed").AsBoolean().Nullable(); // Set upgrade allowed for existing profiles (default will be false for new profiles) Update.Table("QualityProfiles").Set(new { UpgradeAllowed = true }).AllRows(); diff --git a/src/NzbDrone.Core/Datastore/Migration/028_clean_artistmetadata_table.cs b/src/NzbDrone.Core/Datastore/Migration/028_clean_artistmetadata_table.cs index d5e491517..b1d33eda3 100644 --- a/src/NzbDrone.Core/Datastore/Migration/028_clean_artistmetadata_table.cs +++ b/src/NzbDrone.Core/Datastore/Migration/028_clean_artistmetadata_table.cs @@ -9,47 +9,47 @@ namespace NzbDrone.Core.Datastore.Migration protected override void MainDbUpgrade() { // Remove any artists linked to missing metadata - Execute.Sql(@"DELETE FROM Artists - WHERE Id in ( - SELECT Artists.Id from Artists - LEFT OUTER JOIN ArtistMetadata ON Artists.ArtistMetadataId = ArtistMetadata.Id - WHERE ArtistMetadata.Id IS NULL)"); + Execute.Sql(@"DELETE FROM ""Artists"" + WHERE ""Id"" in ( + SELECT ""Artists"".""Id"" from ""Artists"" + LEFT OUTER JOIN ""ArtistMetadata"" ON ""Artists"".""ArtistMetadataId"" = ""ArtistMetadata"".""Id"" + WHERE ""ArtistMetadata"".""Id"" IS NULL)"); // Remove any albums linked to missing metadata - Execute.Sql(@"DELETE FROM Albums - WHERE Id in ( - SELECT Albums.Id from Albums - LEFT OUTER JOIN ArtistMetadata ON Albums.ArtistMetadataId = ArtistMetadata.Id - WHERE ArtistMetadata.Id IS NULL)"); + Execute.Sql(@"DELETE FROM ""Albums"" + WHERE ""Id"" in ( + SELECT ""Albums"".""Id"" from ""Albums"" + LEFT OUTER JOIN ""ArtistMetadata"" ON ""Albums"".""ArtistMetadataId"" = ""ArtistMetadata"".""Id"" + WHERE ""ArtistMetadata"".""Id"" IS NULL)"); // Remove any album releases linked to albums that were deleted - Execute.Sql(@"DELETE FROM AlbumReleases - WHERE Id in ( - SELECT AlbumReleases.Id from AlbumReleases - LEFT OUTER JOIN Albums ON Albums.Id = AlbumReleases.AlbumId - WHERE Albums.Id IS NULL)"); + Execute.Sql(@"DELETE FROM ""AlbumReleases"" + WHERE ""Id"" in ( + SELECT ""AlbumReleases"".""Id"" from ""AlbumReleases"" + LEFT OUTER JOIN ""Albums"" ON ""Albums"".""Id"" = ""AlbumReleases"".""AlbumId"" + WHERE ""Albums"".""Id"" IS NULL)"); // Remove any tracks linked to album releases that were deleted - Execute.Sql(@"DELETE FROM Tracks - WHERE Id in ( - SELECT Tracks.Id from Tracks - LEFT OUTER JOIN AlbumReleases ON Tracks.AlbumReleaseId = AlbumReleases.Id - WHERE AlbumReleases.Id IS NULL)"); + Execute.Sql(@"DELETE FROM ""Tracks"" + WHERE ""Id"" in ( + SELECT ""Tracks"".""Id"" from ""Tracks"" + LEFT OUTER JOIN ""AlbumReleases"" ON ""Tracks"".""AlbumReleaseId"" = ""AlbumReleases"".""Id"" + WHERE ""AlbumReleases"".""Id"" IS NULL)"); // Remove any tracks linked to the original missing metadata - Execute.Sql(@"DELETE FROM Tracks - WHERE Id in ( - SELECT Tracks.Id from Tracks - LEFT OUTER JOIN ArtistMetadata ON Tracks.ArtistMetadataId = ArtistMetadata.Id - WHERE ArtistMetadata.Id IS NULL)"); + Execute.Sql(@"DELETE FROM ""Tracks"" + WHERE ""Id"" in ( + SELECT ""Tracks"".""Id"" from ""Tracks"" + LEFT OUTER JOIN ""ArtistMetadata"" ON ""Tracks"".""ArtistMetadataId"" = ""ArtistMetadata"".""Id"" + WHERE ""ArtistMetadata"".""Id"" IS NULL)"); // Remove any trackfiles linked to the deleted tracks - Execute.Sql(@"DELETE FROM TrackFiles - WHERE Id IN ( - SELECT TrackFiles.Id FROM TrackFiles - LEFT OUTER JOIN Tracks - ON TrackFiles.Id = Tracks.TrackFileId - WHERE Tracks.Id IS NULL)"); + Execute.Sql(@"DELETE FROM ""TrackFiles"" + WHERE ""Id"" IN ( + SELECT ""TrackFiles"".""Id"" FROM ""TrackFiles"" + LEFT OUTER JOIN ""Tracks"" + ON ""TrackFiles"".""Id"" = ""Tracks"".""TrackFileId"" + WHERE ""Tracks"".""Id"" IS NULL)"); } } } diff --git a/src/NzbDrone.Core/Datastore/Migration/029_health_issue_notification.cs b/src/NzbDrone.Core/Datastore/Migration/029_health_issue_notification.cs index 4407daef6..4efbf3d4b 100644 --- a/src/NzbDrone.Core/Datastore/Migration/029_health_issue_notification.cs +++ b/src/NzbDrone.Core/Datastore/Migration/029_health_issue_notification.cs @@ -9,11 +9,11 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - Alter.Table("Notifications").AddColumn("OnHealthIssue").AsBoolean().WithDefaultValue(0); - Alter.Table("Notifications").AddColumn("IncludeHealthWarnings").AsBoolean().WithDefaultValue(0); - Alter.Table("Notifications").AddColumn("OnDownloadFailure").AsBoolean().WithDefaultValue(0); - Alter.Table("Notifications").AddColumn("OnImportFailure").AsBoolean().WithDefaultValue(0); - Alter.Table("Notifications").AddColumn("OnTrackRetag").AsBoolean().WithDefaultValue(0); + Alter.Table("Notifications").AddColumn("OnHealthIssue").AsBoolean().WithDefaultValue(false); + Alter.Table("Notifications").AddColumn("IncludeHealthWarnings").AsBoolean().WithDefaultValue(false); + Alter.Table("Notifications").AddColumn("OnDownloadFailure").AsBoolean().WithDefaultValue(false); + Alter.Table("Notifications").AddColumn("OnImportFailure").AsBoolean().WithDefaultValue(false); + Alter.Table("Notifications").AddColumn("OnTrackRetag").AsBoolean().WithDefaultValue(false); Delete.Column("OnDownload").FromTable("Notifications"); diff --git a/src/NzbDrone.Core/Datastore/Migration/030_add_mediafilerepository_mtime.cs b/src/NzbDrone.Core/Datastore/Migration/030_add_mediafilerepository_mtime.cs index 971f1cc41..32d76bf76 100644 --- a/src/NzbDrone.Core/Datastore/Migration/030_add_mediafilerepository_mtime.cs +++ b/src/NzbDrone.Core/Datastore/Migration/030_add_mediafilerepository_mtime.cs @@ -13,45 +13,45 @@ namespace NzbDrone.Core.Datastore.Migration Alter.Table("TrackFiles").AddColumn("Path").AsString().Nullable(); // Remove anything where RelativePath is null - Execute.Sql(@"DELETE FROM TrackFiles WHERE RelativePath IS NULL"); + Execute.Sql(@"DELETE FROM ""TrackFiles"" WHERE ""RelativePath"" IS NULL"); // Remove anything not linked to a track (these shouldn't be present in version < 30) - Execute.Sql(@"DELETE FROM TrackFiles - WHERE Id IN ( - SELECT TrackFiles.Id FROM TrackFiles - LEFT JOIN Tracks ON TrackFiles.Id = Tracks.TrackFileId - WHERE Tracks.Id IS NULL)"); + Execute.Sql(@"DELETE FROM ""TrackFiles"" + WHERE ""Id"" IN ( + SELECT ""TrackFiles"".""Id"" FROM ""TrackFiles"" + LEFT JOIN ""Tracks"" ON ""TrackFiles"".""Id"" = ""Tracks"".""TrackFileId"" + WHERE ""Tracks"".""Id"" IS NULL)"); // Remove anything where we can't get an artist path (i.e. we don't know where it is) - Execute.Sql(@"DELETE FROM TrackFiles - WHERE Id IN ( - SELECT TrackFiles.Id FROM TrackFiles - LEFT JOIN Albums ON TrackFiles.AlbumId = Albums.Id - LEFT JOIN Artists on Artists.ArtistMetadataId = Albums.ArtistMetadataId - WHERE Artists.Path IS NULL)"); + Execute.Sql(@"DELETE FROM ""TrackFiles"" + WHERE ""Id"" IN ( + SELECT ""TrackFiles"".""Id"" FROM ""TrackFiles"" + LEFT JOIN ""Albums"" ON ""TrackFiles"".""AlbumId"" = ""Albums"".""Id"" + LEFT JOIN ""Artists"" ON ""Artists"".""ArtistMetadataId"" = ""Albums"".""ArtistMetadataId"" + WHERE ""Artists"".""Path"" IS NULL)"); // Remove anything linked to unmonitored or unidentified releases. This should ensure uniqueness of track files. - Execute.Sql(@"DELETE FROM TrackFiles - WHERE Id IN ( - SELECT TrackFiles.Id FROM TrackFiles - LEFT JOIN Tracks ON TrackFiles.Id = Tracks.TrackFileId - LEFT JOIN AlbumReleases ON Tracks.AlbumReleaseId = AlbumReleases.Id - WHERE AlbumReleases.Monitored = 0 - OR AlbumReleases.Monitored IS NULL)"); + Execute.Sql(@"DELETE FROM ""TrackFiles"" + WHERE ""Id"" IN ( + SELECT ""TrackFiles"".""Id"" FROM ""TrackFiles"" + LEFT JOIN ""Tracks"" ON ""TrackFiles"".""Id"" = ""Tracks"".""TrackFileId"" + LEFT JOIN ""AlbumReleases"" ON ""Tracks"".""AlbumReleaseId"" = ""AlbumReleases"".""Id"" + WHERE ""AlbumReleases"".""Monitored"" = false + OR ""AlbumReleases"".""Monitored"" IS NULL)"); // Populate the full paths - Execute.Sql(@"UPDATE TrackFiles - SET Path = (SELECT Artists.Path || '" + System.IO.Path.DirectorySeparatorChar + @"' || TrackFiles.RelativePath - FROM Artists - JOIN Albums ON Albums.ArtistMetadataId = Artists.ArtistMetadataId - WHERE TrackFiles.AlbumId = Albums.Id)"); + Execute.Sql(@"UPDATE ""TrackFiles"" + SET ""Path"" = (SELECT ""Artists"".""Path"" || '" + System.IO.Path.DirectorySeparatorChar + @"' || ""TrackFiles"".""RelativePath"" + FROM ""Artists"" + JOIN ""Albums"" ON ""Albums"".""ArtistMetadataId"" = ""Artists"".""ArtistMetadataId"" + WHERE ""TrackFiles"".""AlbumId"" = ""Albums"".""Id"")"); // Belt and braces to ensure uniqueness - Execute.Sql(@"DELETE FROM TrackFiles - WHERE rowid NOT IN ( - SELECT min(rowid) - FROM TrackFiles - GROUP BY Path + Execute.Sql(@"DELETE FROM ""TrackFiles"" + WHERE ""Id"" NOT IN ( + SELECT MIN(""Id"") + FROM ""TrackFiles"" + GROUP BY ""Path"" )"); // Now enforce the uniqueness constraint diff --git a/src/NzbDrone.Core/Datastore/Migration/031_add_artistmetadataid_constraint.cs b/src/NzbDrone.Core/Datastore/Migration/031_add_artistmetadataid_constraint.cs index 0f7fda558..a6056a23b 100644 --- a/src/NzbDrone.Core/Datastore/Migration/031_add_artistmetadataid_constraint.cs +++ b/src/NzbDrone.Core/Datastore/Migration/031_add_artistmetadataid_constraint.cs @@ -9,11 +9,11 @@ namespace NzbDrone.Core.Datastore.Migration protected override void MainDbUpgrade() { // Remove any duplicate artists - Execute.Sql(@"DELETE FROM Artists - WHERE Id NOT IN ( - SELECT MIN(Artists.id) from Artists - JOIN ArtistMetadata ON Artists.ArtistMetadataId = ArtistMetadata.Id - GROUP BY ArtistMetadata.Id)"); + Execute.Sql(@"DELETE FROM ""Artists"" + WHERE ""Id"" NOT IN ( + SELECT MIN(""Artists"".""Id"") from ""Artists"" + JOIN ""ArtistMetadata"" ON ""Artists"".""ArtistMetadataId"" = ""ArtistMetadata"".""Id"" + GROUP BY ""ArtistMetadata"".""Id"")"); // The index exists but will be recreated as part of unique constraint Delete.Index().OnTable("Artists").OnColumn("ArtistMetadataId"); diff --git a/src/NzbDrone.Core/Datastore/Migration/033_download_propers_config.cs b/src/NzbDrone.Core/Datastore/Migration/033_download_propers_config.cs index d47a4b4d1..1f6f793c9 100644 --- a/src/NzbDrone.Core/Datastore/Migration/033_download_propers_config.cs +++ b/src/NzbDrone.Core/Datastore/Migration/033_download_propers_config.cs @@ -10,7 +10,7 @@ namespace NzbDrone.Core.Datastore.Migration protected override void MainDbUpgrade() { Execute.WithConnection(SetConfigValue); - Execute.Sql("DELETE FROM Config WHERE Key = 'autodownloadpropers'"); + Execute.Sql("DELETE FROM \"Config\" WHERE \"Key\" = 'autodownloadpropers'"); } private void SetConfigValue(IDbConnection conn, IDbTransaction tran) @@ -18,7 +18,7 @@ namespace NzbDrone.Core.Datastore.Migration using (var cmd = conn.CreateCommand()) { cmd.Transaction = tran; - cmd.CommandText = "SELECT Value FROM Config WHERE Key = 'autodownloadpropers'"; + cmd.CommandText = "SELECT \"Value\" FROM \"Config\" WHERE \"Key\" = 'autodownloadpropers'"; using (var reader = cmd.ExecuteReader()) { @@ -30,7 +30,7 @@ namespace NzbDrone.Core.Datastore.Migration using (var updateCmd = conn.CreateCommand()) { updateCmd.Transaction = tran; - updateCmd.CommandText = "INSERT INTO Config (key, value) VALUES ('downloadpropersandrepacks', ?)"; + updateCmd.CommandText = "INSERT INTO \"Config\" (\"key\", \"value\") VALUES ('downloadpropersandrepacks', ?)"; updateCmd.AddParameter(newValue); updateCmd.ExecuteNonQuery(); diff --git a/src/NzbDrone.Core/Datastore/Migration/035_multi_disc_naming_format.cs b/src/NzbDrone.Core/Datastore/Migration/035_multi_disc_naming_format.cs index 493b60013..6dd2c8873 100644 --- a/src/NzbDrone.Core/Datastore/Migration/035_multi_disc_naming_format.cs +++ b/src/NzbDrone.Core/Datastore/Migration/035_multi_disc_naming_format.cs @@ -9,7 +9,7 @@ namespace NzbDrone.Core.Datastore.Migration protected override void MainDbUpgrade() { Alter.Table("NamingConfig").AddColumn("MultiDiscTrackFormat").AsString().Nullable(); - Execute.Sql("UPDATE NamingConfig SET MultiDiscTrackFormat = '{Medium Format} {medium:00}/{Artist Name} - {Album Title} - {track:00} - {Track Title}'"); + Execute.Sql("UPDATE \"NamingConfig\" SET \"MultiDiscTrackFormat\" = '{Medium Format} {medium:00}/{Artist Name} - {Album Title} - {track:00} - {Track Title}'"); } } } diff --git a/src/NzbDrone.Core/Datastore/Migration/036_add_download_client_priority.cs b/src/NzbDrone.Core/Datastore/Migration/036_add_download_client_priority.cs index 3c3e1192e..53149727a 100644 --- a/src/NzbDrone.Core/Datastore/Migration/036_add_download_client_priority.cs +++ b/src/NzbDrone.Core/Datastore/Migration/036_add_download_client_priority.cs @@ -1,11 +1,13 @@ using System.Collections.Generic; using System.Data; +using System.Linq; +using Dapper; using FluentMigrator; using NzbDrone.Core.Datastore.Migration.Framework; namespace NzbDrone.Core.Datastore.Migration { - [Migration(36)] + [Migration(036)] public class add_download_client_priority : NzbDroneMigrationBase { // Need snapshot in time without having to instantiate. @@ -22,34 +24,43 @@ namespace NzbDrone.Core.Datastore.Migration private void InitPriorityForBackwardCompatibility(IDbConnection conn, IDbTransaction tran) { - using (var cmd = conn.CreateCommand()) + var downloadClients = conn.Query($"SELECT \"Id\", \"Implementation\" FROM \"DownloadClients\" WHERE \"Enable\""); + + if (!downloadClients.Any()) { - cmd.Transaction = tran; - cmd.CommandText = "SELECT Id, Implementation FROM DownloadClients WHERE Enable = 1"; + return; + } - using (var reader = cmd.ExecuteReader()) + var nextUsenet = 1; + var nextTorrent = 1; + + foreach (var downloadClient in downloadClients) + { + var isUsenet = _usenetImplementations.Contains(downloadClient.Implementation); + using (var updateCmd = conn.CreateCommand()) { - int nextUsenet = 1; - int nextTorrent = 1; - while (reader.Read()) + updateCmd.Transaction = tran; + if (conn.GetType().FullName == "Npgsql.NpgsqlConnection") { - var id = reader.GetInt32(0); - var implName = reader.GetString(1); - - var isUsenet = _usenetImplementations.Contains(implName); - - using (var updateCmd = conn.CreateCommand()) - { - updateCmd.Transaction = tran; - updateCmd.CommandText = "UPDATE DownloadClients SET Priority = ? WHERE Id = ?"; - updateCmd.AddParameter(isUsenet ? nextUsenet++ : nextTorrent++); - updateCmd.AddParameter(id); - - updateCmd.ExecuteNonQuery(); - } + updateCmd.CommandText = "UPDATE \"DownloadClients\" SET \"Priority\" = $1 WHERE \"Id\" = $2"; } + else + { + updateCmd.CommandText = "UPDATE \"DownloadClients\" SET \"Priority\" = ? WHERE \"Id\" = ?"; + } + + updateCmd.AddParameter(isUsenet ? nextUsenet++ : nextTorrent++); + updateCmd.AddParameter(downloadClient.Id); + + updateCmd.ExecuteNonQuery(); } } } } + + public class DownloadClients036 + { + public int Id { get; set; } + public string Implementation { get; set; } + } } diff --git a/src/NzbDrone.Core/Datastore/Migration/039_add_root_folder_add_defaults.cs b/src/NzbDrone.Core/Datastore/Migration/039_add_root_folder_add_defaults.cs index 9815e1ab2..87570fd48 100644 --- a/src/NzbDrone.Core/Datastore/Migration/039_add_root_folder_add_defaults.cs +++ b/src/NzbDrone.Core/Datastore/Migration/039_add_root_folder_add_defaults.cs @@ -15,7 +15,7 @@ namespace NzbDrone.Core.Datastore.Migration Alter.Table("RootFolders").AddColumn("DefaultMonitorOption").AsInt32().WithDefaultValue(0); Alter.Table("RootFolders").AddColumn("DefaultTags").AsString().Nullable(); - Execute.WithConnection(SetDefaultOptions); + IfDatabase("sqlite").Execute.WithConnection(SetDefaultOptions); } private void SetDefaultOptions(IDbConnection conn, IDbTransaction tran) diff --git a/src/NzbDrone.Core/Datastore/Migration/042_remove_album_folders.cs b/src/NzbDrone.Core/Datastore/Migration/042_remove_album_folders.cs index a0a54f90a..eccaf4209 100644 --- a/src/NzbDrone.Core/Datastore/Migration/042_remove_album_folders.cs +++ b/src/NzbDrone.Core/Datastore/Migration/042_remove_album_folders.cs @@ -10,8 +10,8 @@ namespace NzbDrone.Core.Datastore.Migration { Delete.Column("AlbumFolder").FromTable("Artists"); - Execute.Sql("UPDATE NamingConfig SET StandardTrackFormat = AlbumFolderFormat || '/' || StandardTrackFormat"); - Execute.Sql("UPDATE NamingConfig SET MultiDiscTrackFormat = AlbumFolderFormat || '/' || MultiDiscTrackFormat"); + Execute.Sql("UPDATE \"NamingConfig\" SET \"StandardTrackFormat\" = \"AlbumFolderFormat\" || '/' || \"StandardTrackFormat\""); + Execute.Sql("UPDATE \"NamingConfig\" SET \"MultiDiscTrackFormat\" = \"AlbumFolderFormat\" || '/' || \"MultiDiscTrackFormat\""); Delete.Column("AlbumFolderFormat").FromTable("NamingConfig"); } diff --git a/src/NzbDrone.Core/Datastore/Migration/045_remove_chown_and_folderchmod_config.cs b/src/NzbDrone.Core/Datastore/Migration/045_remove_chown_and_folderchmod_config.cs index 7e4df00ce..f98400841 100644 --- a/src/NzbDrone.Core/Datastore/Migration/045_remove_chown_and_folderchmod_config.cs +++ b/src/NzbDrone.Core/Datastore/Migration/045_remove_chown_and_folderchmod_config.cs @@ -11,8 +11,8 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - Execute.Sql("DELETE FROM config WHERE Key IN ('folderchmod', 'chownuser')"); - Execute.WithConnection(ConvertFileChmodToFolderChmod); + IfDatabase("sqlite").Execute.Sql("DELETE FROM config WHERE Key IN ('folderchmod', 'chownuser')"); + IfDatabase("sqlite").Execute.WithConnection(ConvertFileChmodToFolderChmod); } private void ConvertFileChmodToFolderChmod(IDbConnection conn, IDbTransaction tran) diff --git a/src/NzbDrone.Core/Datastore/Migration/047_update_notifiarr.cs b/src/NzbDrone.Core/Datastore/Migration/047_update_notifiarr.cs index 15d145df1..826ebc4db 100644 --- a/src/NzbDrone.Core/Datastore/Migration/047_update_notifiarr.cs +++ b/src/NzbDrone.Core/Datastore/Migration/047_update_notifiarr.cs @@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - Execute.Sql("UPDATE Notifications SET Implementation = Replace(Implementation, 'DiscordNotifier', 'Notifiarr'),ConfigContract = Replace(ConfigContract, 'DiscordNotifierSettings', 'NotifiarrSettings') WHERE Implementation = 'DiscordNotifier';"); + Execute.Sql("UPDATE \"Notifications\" SET \"Implementation\" = Replace(\"Implementation\", 'DiscordNotifier', 'Notifiarr'),\"ConfigContract\" = Replace(\"ConfigContract\", 'DiscordNotifierSettings', 'NotifiarrSettings') WHERE \"Implementation\" = 'DiscordNotifier';"); } } } diff --git a/src/NzbDrone.Core/Datastore/Migration/049_email_multiple_addresses.cs b/src/NzbDrone.Core/Datastore/Migration/049_email_multiple_addresses.cs index 0ac6ad989..a1c11fcc6 100644 --- a/src/NzbDrone.Core/Datastore/Migration/049_email_multiple_addresses.cs +++ b/src/NzbDrone.Core/Datastore/Migration/049_email_multiple_addresses.cs @@ -34,7 +34,7 @@ namespace NzbDrone.Core.Datastore.Migration private void ChangeEmailAddressType(IDbConnection conn, IDbTransaction tran) { - var rows = conn.Query($"SELECT Id, Settings FROM Notifications WHERE Implementation = 'Email'"); + var rows = conn.Query($"SELECT \"Id\", \"Settings\" FROM \"Notifications\" WHERE \"Implementation\" = 'Email'"); var corrected = new List(); @@ -62,7 +62,7 @@ namespace NzbDrone.Core.Datastore.Migration }); } - var updateSql = "UPDATE Notifications SET Settings = @Settings WHERE Id = @Id"; + var updateSql = "UPDATE \"Notifications\" SET \"Settings\" = @Settings WHERE \"Id\" = @Id"; conn.Execute(updateSql, corrected, transaction: tran); } diff --git a/src/NzbDrone.Core/Datastore/Migration/051_cdh_per_downloadclient.cs b/src/NzbDrone.Core/Datastore/Migration/051_cdh_per_downloadclient.cs index ed1a44e44..a49770ff2 100644 --- a/src/NzbDrone.Core/Datastore/Migration/051_cdh_per_downloadclient.cs +++ b/src/NzbDrone.Core/Datastore/Migration/051_cdh_per_downloadclient.cs @@ -1,8 +1,5 @@ using System.Data; -using System.Linq; using FluentMigrator; -using Newtonsoft.Json.Linq; -using NzbDrone.Common.Serializer; using NzbDrone.Core.Datastore.Migration.Framework; namespace NzbDrone.Core.Datastore.Migration @@ -24,7 +21,7 @@ namespace NzbDrone.Core.Datastore.Migration var removeCompletedDownloads = false; var removeFailedDownloads = true; - using (var removeCompletedDownloadsCmd = conn.CreateCommand(tran, "SELECT Value FROM Config WHERE Key = 'removecompleteddownloads'")) + using (var removeCompletedDownloadsCmd = conn.CreateCommand(tran, "SELECT \"Value\" FROM \"Config\" WHERE \"Key\" = 'removecompleteddownloads'")) { if ((removeCompletedDownloadsCmd.ExecuteScalar() as string)?.ToLower() == "true") { @@ -32,7 +29,7 @@ namespace NzbDrone.Core.Datastore.Migration } } - using (var removeFailedDownloadsCmd = conn.CreateCommand(tran, "SELECT Value FROM Config WHERE Key = 'removefaileddownloads'")) + using (var removeFailedDownloadsCmd = conn.CreateCommand(tran, "SELECT \"Value\" FROM \"Config\" WHERE \"Key\" = 'removefaileddownloads'")) { if ((removeFailedDownloadsCmd.ExecuteScalar() as string)?.ToLower() == "false") { @@ -40,14 +37,25 @@ namespace NzbDrone.Core.Datastore.Migration } } - using (var updateClientCmd = conn.CreateCommand(tran, $"UPDATE DownloadClients SET RemoveCompletedDownloads = (CASE WHEN Implementation IN (\"RTorrent\", \"Flood\") THEN 0 ELSE ? END), RemoveFailedDownloads = ?")) + string commandText; + + if (conn.GetType().FullName == "Npgsql.NpgsqlConnection") { - updateClientCmd.AddParameter(removeCompletedDownloads ? 1 : 0); - updateClientCmd.AddParameter(removeFailedDownloads ? 1 : 0); + commandText = $"UPDATE \"DownloadClients\" SET \"RemoveCompletedDownloads\" = (CASE WHEN \"Implementation\" IN ('RTorrent', 'Flood') THEN 'false' ELSE $1 END), \"RemoveFailedDownloads\" = $2"; + } + else + { + commandText = $"UPDATE \"DownloadClients\" SET \"RemoveCompletedDownloads\" = (CASE WHEN \"Implementation\" IN ('RTorrent', 'Flood') THEN 'false' ELSE ? END), \"RemoveFailedDownloads\" = ?"; + } + + using (var updateClientCmd = conn.CreateCommand(tran, commandText)) + { + updateClientCmd.AddParameter(removeCompletedDownloads); + updateClientCmd.AddParameter(removeFailedDownloads); updateClientCmd.ExecuteNonQuery(); } - using (var removeConfigCmd = conn.CreateCommand(tran, $"DELETE FROM Config WHERE Key IN ('removecompleteddownloads', 'removefaileddownloads')")) + using (var removeConfigCmd = conn.CreateCommand(tran, $"DELETE FROM \"Config\" WHERE \"Key\" IN ('removecompleteddownloads', 'removefaileddownloads')")) { removeConfigCmd.ExecuteNonQuery(); } diff --git a/src/NzbDrone.Core/Datastore/Migration/052_download_history.cs b/src/NzbDrone.Core/Datastore/Migration/052_download_history.cs index b536534f1..8c6319103 100644 --- a/src/NzbDrone.Core/Datastore/Migration/052_download_history.cs +++ b/src/NzbDrone.Core/Datastore/Migration/052_download_history.cs @@ -28,7 +28,7 @@ namespace NzbDrone.Core.Datastore.Migration Create.Index().OnTable("DownloadHistory").OnColumn("ArtistId"); Create.Index().OnTable("DownloadHistory").OnColumn("DownloadId"); - Execute.WithConnection(InitialImportedDownloadHistory); + IfDatabase("sqlite").Execute.WithConnection(InitialImportedDownloadHistory); } private static readonly Dictionary EventTypeMap = new Dictionary() diff --git a/src/NzbDrone.Core/Datastore/Migration/057_import_list_search.cs b/src/NzbDrone.Core/Datastore/Migration/057_import_list_search.cs index 8f1d6a5c7..be9ffce19 100644 --- a/src/NzbDrone.Core/Datastore/Migration/057_import_list_search.cs +++ b/src/NzbDrone.Core/Datastore/Migration/057_import_list_search.cs @@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - Alter.Table("ImportLists").AddColumn("ShouldSearch").AsInt32().WithDefaultValue(1); + Alter.Table("ImportLists").AddColumn("ShouldSearch").AsBoolean().WithDefaultValue(true); } } } diff --git a/src/NzbDrone.Core/Datastore/Migration/058_import_list_monitor_existing.cs b/src/NzbDrone.Core/Datastore/Migration/058_import_list_monitor_existing.cs index f578ab013..c928a6930 100644 --- a/src/NzbDrone.Core/Datastore/Migration/058_import_list_monitor_existing.cs +++ b/src/NzbDrone.Core/Datastore/Migration/058_import_list_monitor_existing.cs @@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - Alter.Table("ImportLists").AddColumn("ShouldMonitorExisting").AsInt32().WithDefaultValue(0); + Alter.Table("ImportLists").AddColumn("ShouldMonitorExisting").AsBoolean().WithDefaultValue(false); } } } diff --git a/src/NzbDrone.Core/Datastore/Migration/059_indexer_tags.cs b/src/NzbDrone.Core/Datastore/Migration/059_indexer_tags.cs index e45340a3c..764108246 100644 --- a/src/NzbDrone.Core/Datastore/Migration/059_indexer_tags.cs +++ b/src/NzbDrone.Core/Datastore/Migration/059_indexer_tags.cs @@ -8,8 +8,8 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - Execute.Sql("DELETE FROM Indexers WHERE Implementation = 'Omgwtfnzbs'"); - Execute.Sql("DELETE FROM Indexers WHERE Implementation = 'Waffles'"); + Delete.FromTable("Indexers").Row(new { Implementation = "Omgwtfnzbs" }); + Delete.FromTable("Indexers").Row(new { Implementation = "Waffles" }); Alter.Table("Indexers").AddColumn("Tags").AsString().Nullable(); } diff --git a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs b/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs index 35c5c6b59..1249dfd8b 100644 --- a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs +++ b/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; using System.Reflection; using FluentMigrator.Runner; +using FluentMigrator.Runner.Generators; using FluentMigrator.Runner.Initialization; using FluentMigrator.Runner.Processors; using Microsoft.Extensions.DependencyInjection; @@ -34,11 +35,16 @@ namespace NzbDrone.Core.Datastore.Migration.Framework _logger.Info("*** Migrating {0} ***", connectionString); - var serviceProvider = new ServiceCollection() + ServiceProvider serviceProvider; + + var db = connectionString.Contains(".db") ? "sqlite" : "postgres"; + + serviceProvider = new ServiceCollection() .AddLogging(b => b.AddNLog()) .AddFluentMigratorCore() .ConfigureRunner( builder => builder + .AddPostgres() .AddNzbDroneSQLite() .WithGlobalConnectionString(connectionString) .WithMigrationsIn(Assembly.GetExecutingAssembly())) @@ -48,6 +54,14 @@ namespace NzbDrone.Core.Datastore.Migration.Framework opt.PreviewOnly = false; opt.Timeout = TimeSpan.FromSeconds(60); }) + .Configure(cfg => + { + cfg.ProcessorId = db; + }) + .Configure(cfg => + { + cfg.GeneratorId = db; + }) .BuildServiceProvider(); using (var scope = serviceProvider.CreateScope()) diff --git a/src/NzbDrone.Core/Datastore/PostgresOptions.cs b/src/NzbDrone.Core/Datastore/PostgresOptions.cs new file mode 100644 index 000000000..1daa94500 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/PostgresOptions.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Configuration; + +namespace NzbDrone.Core.Datastore +{ + public class PostgresOptions + { + public string Host { get; set; } + public int Port { get; set; } + public string User { get; set; } + public string Password { get; set; } + public string MainDb { get; set; } + public string LogDb { get; set; } + + public static PostgresOptions GetOptions() + { + var config = new ConfigurationBuilder() + .AddEnvironmentVariables() + .Build(); + + var postgresOptions = new PostgresOptions(); + config.GetSection("Lidarr:Postgres").Bind(postgresOptions); + + return postgresOptions; + } + } +} diff --git a/src/NzbDrone.Core/Datastore/SqlBuilder.cs b/src/NzbDrone.Core/Datastore/SqlBuilder.cs index e686d4852..8febd5b6a 100644 --- a/src/NzbDrone.Core/Datastore/SqlBuilder.cs +++ b/src/NzbDrone.Core/Datastore/SqlBuilder.cs @@ -8,9 +8,17 @@ namespace NzbDrone.Core.Datastore public class SqlBuilder { private readonly Dictionary _data = new Dictionary(); + private readonly DatabaseType _databaseType; + + public SqlBuilder(DatabaseType databaseType) + { + _databaseType = databaseType; + } public int Sequence { get; private set; } + public DatabaseType DatabaseType => _databaseType; + public Template AddTemplate(string sql, dynamic parameters = null) => new Template(this, sql, parameters); diff --git a/src/NzbDrone.Core/Datastore/TableMapper.cs b/src/NzbDrone.Core/Datastore/TableMapper.cs index f1c3ef7e1..24ab5869e 100644 --- a/src/NzbDrone.Core/Datastore/TableMapper.cs +++ b/src/NzbDrone.Core/Datastore/TableMapper.cs @@ -49,17 +49,17 @@ namespace NzbDrone.Core.Datastore public string SelectTemplate(Type x) { - return $"SELECT /**select**/ FROM {TableMap[x]} /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/"; + return $"SELECT /**select**/ FROM \"{TableMap[x]}\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/"; } public string DeleteTemplate(Type x) { - return $"DELETE FROM {TableMap[x]} /**where**/"; + return $"DELETE FROM \"{TableMap[x]}\" /**where**/"; } public string PageCountTemplate(Type x) { - return $"SELECT /**select**/ FROM {TableMap[x]} /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/"; + return $"SELECT /**select**/ FROM \"{TableMap[x]}\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/"; } public bool IsValidSortKey(string sortKey) @@ -90,6 +90,35 @@ namespace NzbDrone.Core.Datastore return true; } + + public string GetSortKey(string sortKey) + { + string table = null; + + if (sortKey.Contains('.')) + { + var split = sortKey.Split('.'); + if (split.Length != 2) + { + return sortKey; + } + + table = split[0]; + sortKey = split[1]; + } + + if (table != null && !TableMap.Values.Contains(table, StringComparer.OrdinalIgnoreCase)) + { + return sortKey; + } + + if (!_allowedOrderBy.Contains(sortKey)) + { + return sortKey; + } + + return _allowedOrderBy.First(x => x.Equals(sortKey, StringComparison.OrdinalIgnoreCase)); + } } public class LazyLoadedProperty @@ -154,7 +183,7 @@ namespace NzbDrone.Core.Datastore (db, parent) => { var id = childIdSelector(parent); - return db.Query(new SqlBuilder().Where(x => x.Id == id)).SingleOrDefault(); + return db.Query(new SqlBuilder(db.DatabaseType).Where(x => x.Id == id)).SingleOrDefault(); }, parent => childIdSelector(parent) > 0); } diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index 096ec53b1..53f41f44e 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -105,24 +105,24 @@ namespace NzbDrone.Core.Datastore .HasOne(a => a.Metadata, a => a.ArtistMetadataId) .HasOne(a => a.QualityProfile, a => a.QualityProfileId) .HasOne(s => s.MetadataProfile, s => s.MetadataProfileId) - .LazyLoad(a => a.Albums, (db, a) => db.Query(new SqlBuilder().Where(rg => rg.ArtistMetadataId == a.Id)).ToList(), a => a.Id > 0); + .LazyLoad(a => a.Albums, (db, a) => db.Query(new SqlBuilder(db.DatabaseType).Where(rg => rg.ArtistMetadataId == a.Id)).ToList(), a => a.Id > 0); Mapper.Entity("ArtistMetadata").RegisterModel(); Mapper.Entity("Albums").RegisterModel() .Ignore(x => x.ArtistId) .HasOne(r => r.ArtistMetadata, r => r.ArtistMetadataId) - .LazyLoad(a => a.AlbumReleases, (db, album) => db.Query(new SqlBuilder().Where(r => r.AlbumId == album.Id)).ToList(), a => a.Id > 0) + .LazyLoad(a => a.AlbumReleases, (db, album) => db.Query(new SqlBuilder(db.DatabaseType).Where(r => r.AlbumId == album.Id)).ToList(), a => a.Id > 0) .LazyLoad(a => a.Artist, (db, album) => ArtistRepository.Query(db, - new SqlBuilder() + new SqlBuilder(db.DatabaseType) .Join((a, m) => a.ArtistMetadataId == m.Id) .Where(a => a.ArtistMetadataId == album.ArtistMetadataId)).SingleOrDefault(), a => a.ArtistMetadataId > 0); Mapper.Entity("AlbumReleases").RegisterModel() .HasOne(r => r.Album, r => r.AlbumId) - .LazyLoad(x => x.Tracks, (db, release) => db.Query(new SqlBuilder().Where(t => t.AlbumReleaseId == release.Id)).ToList(), r => r.Id > 0); + .LazyLoad(x => x.Tracks, (db, release) => db.Query(new SqlBuilder(db.DatabaseType).Where(t => t.AlbumReleaseId == release.Id)).ToList(), r => r.Id > 0); Mapper.Entity("Tracks").RegisterModel() .Ignore(t => t.HasFile) @@ -131,7 +131,7 @@ namespace NzbDrone.Core.Datastore .HasOne(track => track.ArtistMetadata, track => track.ArtistMetadataId) .LazyLoad(t => t.TrackFile, (db, track) => MediaFileRepository.Query(db, - new SqlBuilder() + new SqlBuilder(db.DatabaseType) .Join((l, r) => l.Id == r.TrackFileId) .Join((l, r) => l.AlbumId == r.Id) .Join((l, r) => l.ArtistMetadataId == r.ArtistMetadataId) @@ -140,7 +140,7 @@ namespace NzbDrone.Core.Datastore t => t.TrackFileId > 0) .LazyLoad(x => x.Artist, (db, t) => ArtistRepository.Query(db, - new SqlBuilder() + new SqlBuilder(db.DatabaseType) .Join((a, m) => a.ArtistMetadataId == m.Id) .Join((l, r) => l.ArtistMetadataId == r.ArtistMetadataId) .Join((l, r) => l.Id == r.AlbumId) @@ -149,10 +149,10 @@ namespace NzbDrone.Core.Datastore Mapper.Entity("TrackFiles").RegisterModel() .HasOne(f => f.Album, f => f.AlbumId) - .LazyLoad(x => x.Tracks, (db, file) => db.Query(new SqlBuilder().Where(t => t.TrackFileId == file.Id)).ToList(), x => x.Id > 0) + .LazyLoad(x => x.Tracks, (db, file) => db.Query(new SqlBuilder(db.DatabaseType).Where(t => t.TrackFileId == file.Id)).ToList(), x => x.Id > 0) .LazyLoad(x => x.Artist, (db, f) => ArtistRepository.Query(db, - new SqlBuilder() + new SqlBuilder(db.DatabaseType) .Join((a, m) => a.ArtistMetadataId == m.Id) .Join((l, r) => l.ArtistMetadataId == r.ArtistMetadataId) .Where(a => a.Id == f.AlbumId)).SingleOrDefault(), diff --git a/src/NzbDrone.Core/Datastore/WhereBuilder.cs b/src/NzbDrone.Core/Datastore/WhereBuilder.cs index 4524ab289..0917e9d32 100644 --- a/src/NzbDrone.Core/Datastore/WhereBuilder.cs +++ b/src/NzbDrone.Core/Datastore/WhereBuilder.cs @@ -1,391 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Text; -using Dapper; +using Dapper; +using NzbDrone.Common.Extensions; namespace NzbDrone.Core.Datastore { - public class WhereBuilder : ExpressionVisitor + public abstract class WhereBuilder : ExpressionVisitor { - protected StringBuilder _sb; - - private const DbType EnumerableMultiParameter = (DbType)(-1); - private readonly string _paramNamePrefix; - private readonly bool _requireConcreteValue = false; - private int _paramCount = 0; - private bool _gotConcreteValue = false; - - public WhereBuilder(Expression filter, bool requireConcreteValue, int seq) - { - _paramNamePrefix = string.Format("Clause{0}", seq + 1); - _requireConcreteValue = requireConcreteValue; - _sb = new StringBuilder(); - - Parameters = new DynamicParameters(); - - if (filter != null) - { - Visit(filter); - } - } - - public DynamicParameters Parameters { get; private set; } - - private string AddParameter(object value, DbType? dbType = null) - { - _gotConcreteValue = true; - _paramCount++; - var name = _paramNamePrefix + "_P" + _paramCount; - Parameters.Add(name, value, dbType); - return '@' + name; - } - - protected override Expression VisitBinary(BinaryExpression expression) - { - _sb.Append("("); - - Visit(expression.Left); - - _sb.AppendFormat(" {0} ", Decode(expression)); - - Visit(expression.Right); - - _sb.Append(")"); - - return expression; - } - - protected override Expression VisitMethodCall(MethodCallExpression expression) - { - var method = expression.Method.Name; - - switch (expression.Method.Name) - { - case "Contains": - ParseContainsExpression(expression); - break; - - case "StartsWith": - ParseStartsWith(expression); - break; - - case "EndsWith": - ParseEndsWith(expression); - break; - - default: - var msg = string.Format("'{0}' expressions are not yet implemented in the where clause expression tree parser.", method); - throw new NotImplementedException(msg); - } - - return expression; - } - - protected override Expression VisitMemberAccess(MemberExpression expression) - { - var tableName = expression?.Expression?.Type != null ? TableMapping.Mapper.TableNameMapping(expression.Expression.Type) : null; - var gotValue = TryGetRightValue(expression, out var value); - - // Only use the SQL condition if the expression didn't resolve to an actual value - if (tableName != null && !gotValue) - { - _sb.Append($"\"{tableName}\".\"{expression.Member.Name}\""); - } - else - { - if (value != null) - { - // string is IEnumerable but we don't want to pick up that case - var type = value.GetType(); - var typeInfo = type.GetTypeInfo(); - var isEnumerable = - type != typeof(string) && ( - typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || - (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>))); - - var paramName = isEnumerable ? AddParameter(value, EnumerableMultiParameter) : AddParameter(value); - _sb.Append(paramName); - } - else - { - _gotConcreteValue = true; - _sb.Append("NULL"); - } - } - - return expression; - } - - protected override Expression VisitConstant(ConstantExpression expression) - { - if (expression.Value != null) - { - var paramName = AddParameter(expression.Value); - _sb.Append(paramName); - } - else - { - _gotConcreteValue = true; - _sb.Append("NULL"); - } - - return expression; - } - - private bool TryGetConstantValue(Expression expression, out object result) - { - result = null; - - if (expression is ConstantExpression constExp) - { - result = constExp.Value; - return true; - } - - return false; - } - - private bool TryGetPropertyValue(MemberExpression expression, out object result) - { - result = null; - - if (expression.Expression is MemberExpression nested) - { - // Value is passed in as a property on a parent entity - var container = (nested.Expression as ConstantExpression)?.Value; - - if (container == null) - { - return false; - } - - var entity = GetFieldValue(container, nested.Member); - result = GetFieldValue(entity, expression.Member); - return true; - } - - return false; - } - - private bool TryGetVariableValue(MemberExpression expression, out object result) - { - result = null; - - // Value is passed in as a variable - if (expression.Expression is ConstantExpression nested) - { - result = GetFieldValue(nested.Value, expression.Member); - return true; - } - - return false; - } - - private bool TryGetRightValue(Expression expression, out object value) - { - value = null; - - if (TryGetConstantValue(expression, out value)) - { - return true; - } - - var memberExp = expression as MemberExpression; - - if (TryGetPropertyValue(memberExp, out value)) - { - return true; - } - - if (TryGetVariableValue(memberExp, out value)) - { - return true; - } - - return false; - } - - private object GetFieldValue(object entity, MemberInfo member) - { - if (member.MemberType == MemberTypes.Field) - { - return (member as FieldInfo).GetValue(entity); - } - - if (member.MemberType == MemberTypes.Property) - { - return (member as PropertyInfo).GetValue(entity); - } - - throw new ArgumentException(string.Format("WhereBuilder could not get the value for {0}.{1}.", entity.GetType().Name, member.Name)); - } - - private bool IsNullVariable(Expression expression) - { - if (expression.NodeType == ExpressionType.Constant && - TryGetConstantValue(expression, out var constResult) && - constResult == null) - { - return true; - } - - if (expression.NodeType == ExpressionType.MemberAccess && - expression is MemberExpression member && - ((TryGetPropertyValue(member, out var result) && result == null) || - (TryGetVariableValue(member, out result) && result == null))) - { - return true; - } - - return false; - } - - private string Decode(BinaryExpression expression) - { - if (IsNullVariable(expression.Right)) - { - switch (expression.NodeType) - { - case ExpressionType.Equal: return "IS"; - case ExpressionType.NotEqual: return "IS NOT"; - } - } - - switch (expression.NodeType) - { - case ExpressionType.AndAlso: return "AND"; - case ExpressionType.And: return "AND"; - case ExpressionType.Equal: return "="; - case ExpressionType.GreaterThan: return ">"; - case ExpressionType.GreaterThanOrEqual: return ">="; - case ExpressionType.LessThan: return "<"; - case ExpressionType.LessThanOrEqual: return "<="; - case ExpressionType.NotEqual: return "<>"; - case ExpressionType.OrElse: return "OR"; - case ExpressionType.Or: return "OR"; - default: throw new NotSupportedException(string.Format("{0} statement is not supported", expression.NodeType.ToString())); - } - } - - private void ParseContainsExpression(MethodCallExpression expression) - { - var list = expression.Object; - - if (list != null && - (list.Type == typeof(string) || - (list.Type == typeof(List) && !TryGetRightValue(list, out var _)))) - { - ParseStringContains(expression); - return; - } - - ParseEnumerableContains(expression); - } - - private void ParseEnumerableContains(MethodCallExpression body) - { - // Fish out the list and the item to compare - // It's in a different form for arrays and Lists - var list = body.Object; - Expression item; - - if (list != null) - { - // Generic collection - item = body.Arguments[0]; - } - else - { - // Static method - // Must be Enumerable.Contains(source, item) - if (body.Method.DeclaringType != typeof(Enumerable) || body.Arguments.Count != 2) - { - throw new NotSupportedException("Unexpected form of Enumerable.Contains"); - } - - list = body.Arguments[0]; - item = body.Arguments[1]; - } - - _sb.Append("("); - - Visit(item); - - _sb.Append(" IN "); - - // hardcode the integer list if it exists to bypass parameter limit - if (item.Type == typeof(int) && TryGetRightValue(list, out var value)) - { - var items = (IEnumerable)value; - _sb.Append("("); - _sb.Append(string.Join(", ", items)); - _sb.Append(")"); - - _gotConcreteValue = true; - } - else - { - Visit(list); - } - - _sb.Append(")"); - } - - private void ParseStringContains(MethodCallExpression body) - { - _sb.Append("("); - - Visit(body.Object); - - _sb.Append(" LIKE '%' || "); - - Visit(body.Arguments[0]); - - _sb.Append(" || '%')"); - } - - private void ParseStartsWith(MethodCallExpression body) - { - _sb.Append("("); - - Visit(body.Object); - - _sb.Append(" LIKE "); - - Visit(body.Arguments[0]); - - _sb.Append(" || '%')"); - } - - private void ParseEndsWith(MethodCallExpression body) - { - _sb.Append("("); - - Visit(body.Object); - - _sb.Append(" LIKE '%' || "); - - Visit(body.Arguments[0]); - - _sb.Append(")"); - } - - public override string ToString() - { - var sql = _sb.ToString(); - - if (_requireConcreteValue && !_gotConcreteValue) - { - var e = new InvalidOperationException("WhereBuilder requires a concrete condition"); - e.Data.Add("sql", sql); - throw e; - } - - return sql; - } + public DynamicParameters Parameters { get; protected set; } } } diff --git a/src/NzbDrone.Core/Datastore/WhereBuilderPostgres.cs b/src/NzbDrone.Core/Datastore/WhereBuilderPostgres.cs new file mode 100644 index 000000000..b63e861c4 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/WhereBuilderPostgres.cs @@ -0,0 +1,389 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using Dapper; + +namespace NzbDrone.Core.Datastore +{ + public class WhereBuilderPostgres : WhereBuilder + { + protected StringBuilder _sb; + + private const DbType EnumerableMultiParameter = (DbType)(-1); + private readonly string _paramNamePrefix; + private readonly bool _requireConcreteValue = false; + private int _paramCount = 0; + private bool _gotConcreteValue = false; + + public WhereBuilderPostgres(Expression filter, bool requireConcreteValue, int seq) + { + _paramNamePrefix = string.Format("Clause{0}", seq + 1); + _requireConcreteValue = requireConcreteValue; + _sb = new StringBuilder(); + + Parameters = new DynamicParameters(); + + if (filter != null) + { + Visit(filter); + } + } + + private string AddParameter(object value, DbType? dbType = null) + { + _gotConcreteValue = true; + _paramCount++; + var name = _paramNamePrefix + "_P" + _paramCount; + Parameters.Add(name, value, dbType); + return '@' + name; + } + + protected override Expression VisitBinary(BinaryExpression expression) + { + _sb.Append('('); + + Visit(expression.Left); + + _sb.AppendFormat(" {0} ", Decode(expression)); + + Visit(expression.Right); + + _sb.Append(')'); + + return expression; + } + + protected override Expression VisitMethodCall(MethodCallExpression expression) + { + var method = expression.Method.Name; + + switch (expression.Method.Name) + { + case "Contains": + ParseContainsExpression(expression); + break; + + case "StartsWith": + ParseStartsWith(expression); + break; + + case "EndsWith": + ParseEndsWith(expression); + break; + + default: + var msg = string.Format("'{0}' expressions are not yet implemented in the where clause expression tree parser.", method); + throw new NotImplementedException(msg); + } + + return expression; + } + + protected override Expression VisitMemberAccess(MemberExpression expression) + { + var tableName = expression?.Expression?.Type != null ? TableMapping.Mapper.TableNameMapping(expression.Expression.Type) : null; + var gotValue = TryGetRightValue(expression, out var value); + + // Only use the SQL condition if the expression didn't resolve to an actual value + if (tableName != null && !gotValue) + { + _sb.Append($"\"{tableName}\".\"{expression.Member.Name}\""); + } + else + { + if (value != null) + { + // string is IEnumerable but we don't want to pick up that case + var type = value.GetType(); + var typeInfo = type.GetTypeInfo(); + var isEnumerable = + type != typeof(string) && ( + typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || + (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>))); + + var paramName = isEnumerable ? AddParameter(value, EnumerableMultiParameter) : AddParameter(value); + _sb.Append(paramName); + } + else + { + _gotConcreteValue = true; + _sb.Append("NULL"); + } + } + + return expression; + } + + protected override Expression VisitConstant(ConstantExpression expression) + { + if (expression.Value != null) + { + var paramName = AddParameter(expression.Value); + _sb.Append(paramName); + } + else + { + _gotConcreteValue = true; + _sb.Append("NULL"); + } + + return expression; + } + + private bool TryGetConstantValue(Expression expression, out object result) + { + result = null; + + if (expression is ConstantExpression constExp) + { + result = constExp.Value; + return true; + } + + return false; + } + + private bool TryGetPropertyValue(MemberExpression expression, out object result) + { + result = null; + + if (expression.Expression is MemberExpression nested) + { + // Value is passed in as a property on a parent entity + var container = (nested.Expression as ConstantExpression)?.Value; + + if (container == null) + { + return false; + } + + var entity = GetFieldValue(container, nested.Member); + result = GetFieldValue(entity, expression.Member); + return true; + } + + return false; + } + + private bool TryGetVariableValue(MemberExpression expression, out object result) + { + result = null; + + // Value is passed in as a variable + if (expression.Expression is ConstantExpression nested) + { + result = GetFieldValue(nested.Value, expression.Member); + return true; + } + + return false; + } + + private bool TryGetRightValue(Expression expression, out object value) + { + value = null; + + if (TryGetConstantValue(expression, out value)) + { + return true; + } + + var memberExp = expression as MemberExpression; + + if (TryGetPropertyValue(memberExp, out value)) + { + return true; + } + + if (TryGetVariableValue(memberExp, out value)) + { + return true; + } + + return false; + } + + private object GetFieldValue(object entity, MemberInfo member) + { + if (member.MemberType == MemberTypes.Field) + { + return (member as FieldInfo).GetValue(entity); + } + + if (member.MemberType == MemberTypes.Property) + { + return (member as PropertyInfo).GetValue(entity); + } + + throw new ArgumentException(string.Format("WhereBuilder could not get the value for {0}.{1}.", entity.GetType().Name, member.Name)); + } + + private bool IsNullVariable(Expression expression) + { + if (expression.NodeType == ExpressionType.Constant && + TryGetConstantValue(expression, out var constResult) && + constResult == null) + { + return true; + } + + if (expression.NodeType == ExpressionType.MemberAccess && + expression is MemberExpression member && + ((TryGetPropertyValue(member, out var result) && result == null) || + (TryGetVariableValue(member, out result) && result == null))) + { + return true; + } + + return false; + } + + private string Decode(BinaryExpression expression) + { + if (IsNullVariable(expression.Right)) + { + switch (expression.NodeType) + { + case ExpressionType.Equal: return "IS"; + case ExpressionType.NotEqual: return "IS NOT"; + } + } + + switch (expression.NodeType) + { + case ExpressionType.AndAlso: return "AND"; + case ExpressionType.And: return "AND"; + case ExpressionType.Equal: return "="; + case ExpressionType.GreaterThan: return ">"; + case ExpressionType.GreaterThanOrEqual: return ">="; + case ExpressionType.LessThan: return "<"; + case ExpressionType.LessThanOrEqual: return "<="; + case ExpressionType.NotEqual: return "<>"; + case ExpressionType.OrElse: return "OR"; + case ExpressionType.Or: return "OR"; + default: throw new NotSupportedException(string.Format("{0} statement is not supported", expression.NodeType.ToString())); + } + } + + private void ParseContainsExpression(MethodCallExpression expression) + { + var list = expression.Object; + + if (list != null && + (list.Type == typeof(string) || + (list.Type == typeof(List) && !TryGetRightValue(list, out var _)))) + { + ParseStringContains(expression); + return; + } + + ParseEnumerableContains(expression); + } + + private void ParseEnumerableContains(MethodCallExpression body) + { + // Fish out the list and the item to compare + // It's in a different form for arrays and Lists + var list = body.Object; + Expression item; + + if (list != null) + { + // Generic collection + item = body.Arguments[0]; + } + else + { + // Static method + // Must be Enumerable.Contains(source, item) + if (body.Method.DeclaringType != typeof(Enumerable) || body.Arguments.Count != 2) + { + throw new NotSupportedException("Unexpected form of Enumerable.Contains"); + } + + list = body.Arguments[0]; + item = body.Arguments[1]; + } + + _sb.Append('('); + + Visit(item); + + _sb.Append(" = ANY ("); + + // hardcode the integer list if it exists to bypass parameter limit + if (item.Type == typeof(int) && TryGetRightValue(list, out var value)) + { + var items = (IEnumerable)value; + _sb.Append("('{"); + _sb.Append(string.Join(", ", items)); + _sb.Append("}')"); + + _gotConcreteValue = true; + } + else + { + Visit(list); + } + + _sb.Append("))"); + } + + private void ParseStringContains(MethodCallExpression body) + { + _sb.Append('('); + + Visit(body.Object); + + _sb.Append(" ILIKE '%' || "); + + Visit(body.Arguments[0]); + + _sb.Append(" || '%')"); + } + + private void ParseStartsWith(MethodCallExpression body) + { + _sb.Append('('); + + Visit(body.Object); + + _sb.Append(" ILIKE "); + + Visit(body.Arguments[0]); + + _sb.Append(" || '%')"); + } + + private void ParseEndsWith(MethodCallExpression body) + { + _sb.Append('('); + + Visit(body.Object); + + _sb.Append(" ILIKE '%' || "); + + Visit(body.Arguments[0]); + + _sb.Append(')'); + } + + public override string ToString() + { + var sql = _sb.ToString(); + + if (_requireConcreteValue && !_gotConcreteValue) + { + var e = new InvalidOperationException("WhereBuilder requires a concrete condition"); + e.Data.Add("sql", sql); + throw e; + } + + return sql; + } + } +} diff --git a/src/NzbDrone.Core/Datastore/WhereBuilderSqlite.cs b/src/NzbDrone.Core/Datastore/WhereBuilderSqlite.cs new file mode 100644 index 000000000..576182f7d --- /dev/null +++ b/src/NzbDrone.Core/Datastore/WhereBuilderSqlite.cs @@ -0,0 +1,389 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using Dapper; + +namespace NzbDrone.Core.Datastore +{ + public class WhereBuilderSqlite : WhereBuilder + { + protected StringBuilder _sb; + + private const DbType EnumerableMultiParameter = (DbType)(-1); + private readonly string _paramNamePrefix; + private readonly bool _requireConcreteValue = false; + private int _paramCount = 0; + private bool _gotConcreteValue = false; + + public WhereBuilderSqlite(Expression filter, bool requireConcreteValue, int seq) + { + _paramNamePrefix = string.Format("Clause{0}", seq + 1); + _requireConcreteValue = requireConcreteValue; + _sb = new StringBuilder(); + + Parameters = new DynamicParameters(); + + if (filter != null) + { + Visit(filter); + } + } + + private string AddParameter(object value, DbType? dbType = null) + { + _gotConcreteValue = true; + _paramCount++; + var name = _paramNamePrefix + "_P" + _paramCount; + Parameters.Add(name, value, dbType); + return '@' + name; + } + + protected override Expression VisitBinary(BinaryExpression expression) + { + _sb.Append("("); + + Visit(expression.Left); + + _sb.AppendFormat(" {0} ", Decode(expression)); + + Visit(expression.Right); + + _sb.Append(")"); + + return expression; + } + + protected override Expression VisitMethodCall(MethodCallExpression expression) + { + var method = expression.Method.Name; + + switch (expression.Method.Name) + { + case "Contains": + ParseContainsExpression(expression); + break; + + case "StartsWith": + ParseStartsWith(expression); + break; + + case "EndsWith": + ParseEndsWith(expression); + break; + + default: + var msg = string.Format("'{0}' expressions are not yet implemented in the where clause expression tree parser.", method); + throw new NotImplementedException(msg); + } + + return expression; + } + + protected override Expression VisitMemberAccess(MemberExpression expression) + { + var tableName = expression?.Expression?.Type != null ? TableMapping.Mapper.TableNameMapping(expression.Expression.Type) : null; + var gotValue = TryGetRightValue(expression, out var value); + + // Only use the SQL condition if the expression didn't resolve to an actual value + if (tableName != null && !gotValue) + { + _sb.Append($"\"{tableName}\".\"{expression.Member.Name}\""); + } + else + { + if (value != null) + { + // string is IEnumerable but we don't want to pick up that case + var type = value.GetType(); + var typeInfo = type.GetTypeInfo(); + var isEnumerable = + type != typeof(string) && ( + typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || + (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>))); + + var paramName = isEnumerable ? AddParameter(value, EnumerableMultiParameter) : AddParameter(value); + _sb.Append(paramName); + } + else + { + _gotConcreteValue = true; + _sb.Append("NULL"); + } + } + + return expression; + } + + protected override Expression VisitConstant(ConstantExpression expression) + { + if (expression.Value != null) + { + var paramName = AddParameter(expression.Value); + _sb.Append(paramName); + } + else + { + _gotConcreteValue = true; + _sb.Append("NULL"); + } + + return expression; + } + + private bool TryGetConstantValue(Expression expression, out object result) + { + result = null; + + if (expression is ConstantExpression constExp) + { + result = constExp.Value; + return true; + } + + return false; + } + + private bool TryGetPropertyValue(MemberExpression expression, out object result) + { + result = null; + + if (expression.Expression is MemberExpression nested) + { + // Value is passed in as a property on a parent entity + var container = (nested.Expression as ConstantExpression)?.Value; + + if (container == null) + { + return false; + } + + var entity = GetFieldValue(container, nested.Member); + result = GetFieldValue(entity, expression.Member); + return true; + } + + return false; + } + + private bool TryGetVariableValue(MemberExpression expression, out object result) + { + result = null; + + // Value is passed in as a variable + if (expression.Expression is ConstantExpression nested) + { + result = GetFieldValue(nested.Value, expression.Member); + return true; + } + + return false; + } + + private bool TryGetRightValue(Expression expression, out object value) + { + value = null; + + if (TryGetConstantValue(expression, out value)) + { + return true; + } + + var memberExp = expression as MemberExpression; + + if (TryGetPropertyValue(memberExp, out value)) + { + return true; + } + + if (TryGetVariableValue(memberExp, out value)) + { + return true; + } + + return false; + } + + private object GetFieldValue(object entity, MemberInfo member) + { + if (member.MemberType == MemberTypes.Field) + { + return (member as FieldInfo).GetValue(entity); + } + + if (member.MemberType == MemberTypes.Property) + { + return (member as PropertyInfo).GetValue(entity); + } + + throw new ArgumentException(string.Format("WhereBuilder could not get the value for {0}.{1}.", entity.GetType().Name, member.Name)); + } + + private bool IsNullVariable(Expression expression) + { + if (expression.NodeType == ExpressionType.Constant && + TryGetConstantValue(expression, out var constResult) && + constResult == null) + { + return true; + } + + if (expression.NodeType == ExpressionType.MemberAccess && + expression is MemberExpression member && + ((TryGetPropertyValue(member, out var result) && result == null) || + (TryGetVariableValue(member, out result) && result == null))) + { + return true; + } + + return false; + } + + private string Decode(BinaryExpression expression) + { + if (IsNullVariable(expression.Right)) + { + switch (expression.NodeType) + { + case ExpressionType.Equal: return "IS"; + case ExpressionType.NotEqual: return "IS NOT"; + } + } + + switch (expression.NodeType) + { + case ExpressionType.AndAlso: return "AND"; + case ExpressionType.And: return "AND"; + case ExpressionType.Equal: return "="; + case ExpressionType.GreaterThan: return ">"; + case ExpressionType.GreaterThanOrEqual: return ">="; + case ExpressionType.LessThan: return "<"; + case ExpressionType.LessThanOrEqual: return "<="; + case ExpressionType.NotEqual: return "<>"; + case ExpressionType.OrElse: return "OR"; + case ExpressionType.Or: return "OR"; + default: throw new NotSupportedException(string.Format("{0} statement is not supported", expression.NodeType.ToString())); + } + } + + private void ParseContainsExpression(MethodCallExpression expression) + { + var list = expression.Object; + + if (list != null && + (list.Type == typeof(string) || + (list.Type == typeof(List) && !TryGetRightValue(list, out var _)))) + { + ParseStringContains(expression); + return; + } + + ParseEnumerableContains(expression); + } + + private void ParseEnumerableContains(MethodCallExpression body) + { + // Fish out the list and the item to compare + // It's in a different form for arrays and Lists + var list = body.Object; + Expression item; + + if (list != null) + { + // Generic collection + item = body.Arguments[0]; + } + else + { + // Static method + // Must be Enumerable.Contains(source, item) + if (body.Method.DeclaringType != typeof(Enumerable) || body.Arguments.Count != 2) + { + throw new NotSupportedException("Unexpected form of Enumerable.Contains"); + } + + list = body.Arguments[0]; + item = body.Arguments[1]; + } + + _sb.Append("("); + + Visit(item); + + _sb.Append(" IN "); + + // hardcode the integer list if it exists to bypass parameter limit + if (item.Type == typeof(int) && TryGetRightValue(list, out var value)) + { + var items = (IEnumerable)value; + _sb.Append("("); + _sb.Append(string.Join(", ", items)); + _sb.Append(")"); + + _gotConcreteValue = true; + } + else + { + Visit(list); + } + + _sb.Append(")"); + } + + private void ParseStringContains(MethodCallExpression body) + { + _sb.Append("("); + + Visit(body.Object); + + _sb.Append(" LIKE '%' || "); + + Visit(body.Arguments[0]); + + _sb.Append(" || '%')"); + } + + private void ParseStartsWith(MethodCallExpression body) + { + _sb.Append("("); + + Visit(body.Object); + + _sb.Append(" LIKE "); + + Visit(body.Arguments[0]); + + _sb.Append(" || '%')"); + } + + private void ParseEndsWith(MethodCallExpression body) + { + _sb.Append("("); + + Visit(body.Object); + + _sb.Append(" LIKE '%' || "); + + Visit(body.Arguments[0]); + + _sb.Append(")"); + } + + public override string ToString() + { + var sql = _sb.ToString(); + + if (_requireConcreteValue && !_gotConcreteValue) + { + var e = new InvalidOperationException("WhereBuilder requires a concrete condition"); + e.Data.Add("sql", sql); + throw e; + } + + return sql; + } + } +} diff --git a/src/NzbDrone.Core/History/EntityHistoryRepository.cs b/src/NzbDrone.Core/History/EntityHistoryRepository.cs index 07eded0fa..030a40e82 100644 --- a/src/NzbDrone.Core/History/EntityHistoryRepository.cs +++ b/src/NzbDrone.Core/History/EntityHistoryRepository.cs @@ -90,11 +90,11 @@ namespace NzbDrone.Core.History public List FindDownloadHistory(int idArtistId, QualityModel quality) { - var allowed = new[] { EntityHistoryEventType.Grabbed, EntityHistoryEventType.DownloadFailed, EntityHistoryEventType.TrackFileImported }; + var allowed = new[] { (int)EntityHistoryEventType.Grabbed, (int)EntityHistoryEventType.DownloadFailed, (int)EntityHistoryEventType.TrackFileImported }; return Query(h => h.ArtistId == idArtistId && h.Quality == quality && - allowed.Contains(h.EventType)); + allowed.Contains((int)h.EventType)); } public void DeleteForArtists(List artistIds) @@ -102,7 +102,7 @@ namespace NzbDrone.Core.History Delete(c => artistIds.Contains(c.ArtistId)); } - protected override SqlBuilder PagedBuilder() => new SqlBuilder() + protected override SqlBuilder PagedBuilder() => new SqlBuilder(_database.DatabaseType) .Join((h, a) => h.ArtistId == a.Id) .Join((h, a) => h.AlbumId == a.Id) .LeftJoin((h, t) => h.TrackId == t.Id); diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAbsolutePathMetadataFiles.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAbsolutePathMetadataFiles.cs index 1293d4f07..8d50f05de 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAbsolutePathMetadataFiles.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAbsolutePathMetadataFiles.cs @@ -1,4 +1,4 @@ -using Dapper; +using Dapper; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Housekeeping.Housekeepers @@ -16,16 +16,32 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM MetadataFiles - WHERE Id IN ( - SELECT Id FROM MetadataFiles - WHERE RelativePath - LIKE '_:\%' - OR RelativePath - LIKE '\%' - OR RelativePath + if (_database.DatabaseType == DatabaseType.PostgreSQL) + { + mapper.Execute(@"DELETE FROM ""MetadataFiles"" + WHERE ""Id"" = ANY ( + SELECT ""Id"" FROM ""MetadataFiles"" + WHERE ""RelativePath"" + LIKE '_:\\%' + OR ""RelativePath"" + LIKE '\\%' + OR ""RelativePath"" LIKE '/%' )"); + } + else + { + mapper.Execute(@"DELETE FROM ""MetadataFiles"" + WHERE ""Id"" IN ( + SELECT ""Id"" FROM ""MetadataFiles"" + WHERE ""RelativePath"" + LIKE '_:\%' + OR ""RelativePath"" + LIKE '\%' + OR ""RelativePath"" + LIKE '/%' + )"); + } } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAdditionalNamingSpecs.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAdditionalNamingSpecs.cs index a1d9c56b7..33184f1fb 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAdditionalNamingSpecs.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAdditionalNamingSpecs.cs @@ -1,4 +1,4 @@ -using Dapper; +using Dapper; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Housekeeping.Housekeepers @@ -16,9 +16,9 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM NamingConfig - WHERE ID NOT IN ( - SELECT ID FROM NamingConfig + mapper.Execute(@"DELETE FROM ""NamingConfig"" + WHERE ""Id"" NOT IN ( + SELECT ""Id"" FROM ""NamingConfig"" LIMIT 1)"); } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAdditionalUsers.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAdditionalUsers.cs index 4460ac83c..3845110f4 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAdditionalUsers.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAdditionalUsers.cs @@ -16,9 +16,9 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM Users - WHERE ID NOT IN ( - SELECT ID FROM Users + mapper.Execute(@"DELETE FROM ""Users"" + WHERE ""Id"" NOT IN ( + SELECT ""Id"" FROM ""Users"" LIMIT 1)"); } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupDownloadClientUnavailablePendingReleases.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupDownloadClientUnavailablePendingReleases.cs index 1e331c98b..7e3cb6d81 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupDownloadClientUnavailablePendingReleases.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupDownloadClientUnavailablePendingReleases.cs @@ -1,4 +1,4 @@ -using System; +using System; using Dapper; using NzbDrone.Core.Datastore; using NzbDrone.Core.Download.Pending; @@ -16,16 +16,29 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers public void Clean() { - using (var mapper = _database.OpenConnection()) + var mapper = _database.OpenConnection(); + + if (_database.DatabaseType == DatabaseType.PostgreSQL) { - mapper.Execute(@"DELETE FROM PendingReleases - WHERE Added < @TwoWeeksAgo - AND REASON IN @Reasons", - new - { - TwoWeeksAgo = DateTime.UtcNow.AddDays(-14), - Reasons = new[] { (int)PendingReleaseReason.DownloadClientUnavailable, (int)PendingReleaseReason.Fallback } - }); + mapper.Execute(@"DELETE FROM ""PendingReleases"" + WHERE ""Added"" < @TwoWeeksAgo + AND ""Reason"" = ANY (@Reasons)", + new + { + TwoWeeksAgo = DateTime.UtcNow.AddDays(-14), + Reasons = new[] { (int)PendingReleaseReason.DownloadClientUnavailable, (int)PendingReleaseReason.Fallback } + }); + } + else + { + mapper.Execute(@"DELETE FROM ""PendingReleases"" + WHERE ""Added"" < @TwoWeeksAgo + AND ""REASON"" IN @Reasons", + new + { + TwoWeeksAgo = DateTime.UtcNow.AddDays(-14), + Reasons = new[] { (int)PendingReleaseReason.DownloadClientUnavailable, (int)PendingReleaseReason.Fallback } + }); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupDuplicateMetadataFiles.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupDuplicateMetadataFiles.cs index 3b06fdba4..b46310850 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupDuplicateMetadataFiles.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupDuplicateMetadataFiles.cs @@ -24,12 +24,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM MetadataFiles - WHERE Id IN ( - SELECT Id FROM MetadataFiles - WHERE Type = 1 - GROUP BY ArtistId, Consumer - HAVING COUNT(ArtistId) > 1 + mapper.Execute(@"DELETE FROM ""MetadataFiles"" + WHERE ""Id"" IN ( + SELECT MIN(""Id"") FROM ""MetadataFiles"" + WHERE ""Type"" = 1 + GROUP BY ""ArtistId"", ""Consumer"" + HAVING COUNT(""ArtistId"") > 1 )"); } } @@ -38,12 +38,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM MetadataFiles - WHERE Id IN ( - SELECT Id FROM MetadataFiles - WHERE Type = 6 - GROUP BY AlbumId, Consumer - HAVING COUNT(AlbumId) > 1 + mapper.Execute(@"DELETE FROM ""MetadataFiles"" + WHERE ""Id"" IN ( + SELECT MIN(""Id"") FROM ""MetadataFiles"" + WHERE ""Type"" = 6 + GROUP BY ""AlbumId"", ""Consumer"" + HAVING COUNT(""AlbumId"") > 1 )"); } } @@ -52,12 +52,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM MetadataFiles - WHERE Id IN ( - SELECT Id FROM MetadataFiles - WHERE Type = 2 - GROUP BY TrackFileId, Consumer - HAVING COUNT(TrackFileId) > 1 + mapper.Execute(@"DELETE FROM ""MetadataFiles"" + WHERE ""Id"" IN ( + SELECT MIN(""Id"") FROM ""MetadataFiles"" + WHERE ""Type"" = 2 + GROUP BY ""TrackFileId"", ""Consumer"" + HAVING COUNT(""TrackFileId"") > 1 )"); } } @@ -66,12 +66,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM MetadataFiles - WHERE Id IN ( - SELECT Id FROM MetadataFiles - WHERE Type = 5 - GROUP BY TrackFileId, Consumer - HAVING COUNT(TrackFileId) > 1 + mapper.Execute(@"DELETE FROM ""MetadataFiles"" + WHERE ""Id"" IN ( + SELECT MIN(""Id"") FROM ""MetadataFiles"" + WHERE ""Type"" = 5 + GROUP BY ""TrackFileId"", ""Consumer"" + HAVING COUNT(""TrackFileId"") > 1 )"); } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedAlbums.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedAlbums.cs index f80b93f07..ab921af81 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedAlbums.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedAlbums.cs @@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM Albums - WHERE Id IN ( - SELECT Albums.Id FROM Albums - LEFT OUTER JOIN Artists - ON Albums.ArtistMetadataId = Artists.ArtistMetadataId - WHERE Artists.Id IS NULL)"); + mapper.Execute(@"DELETE FROM ""Albums"" + WHERE ""Id"" IN ( + SELECT ""Albums"".""Id"" FROM ""Albums"" + LEFT OUTER JOIN ""Artists"" + ON ""Albums"".""ArtistMetadataId"" = ""Artists"".""ArtistMetadataId"" + WHERE ""Artists"".""Id"" IS NULL)"); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedArtistMetadata.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedArtistMetadata.cs index 52f674312..500d85df8 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedArtistMetadata.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedArtistMetadata.cs @@ -16,13 +16,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM ArtistMetadata - WHERE Id IN ( - SELECT ArtistMetadata.Id FROM ArtistMetadata - LEFT OUTER JOIN Albums ON Albums.ArtistMetadataId = ArtistMetadata.Id - LEFT OUTER JOIN Tracks ON Tracks.ArtistMetadataId = ArtistMetadata.Id - LEFT OUTER JOIN Artists ON Artists.ArtistMetadataId = ArtistMetadata.Id - WHERE Albums.Id IS NULL AND Tracks.Id IS NULL AND Artists.Id IS NULL)"); + mapper.Execute(@"DELETE FROM ""ArtistMetadata"" + WHERE ""Id"" IN ( + SELECT ""ArtistMetadata"".""Id"" FROM ""ArtistMetadata"" + LEFT OUTER JOIN ""Albums"" ON ""Albums"".""ArtistMetadataId"" = ""ArtistMetadata"".""Id"" + LEFT OUTER JOIN ""Tracks"" ON ""Tracks"".""ArtistMetadataId"" = ""ArtistMetadata"".""Id"" + LEFT OUTER JOIN ""Artists"" ON ""Artists"".""ArtistMetadataId"" = ""ArtistMetadata"".""Id"" + WHERE ""Albums"".""Id"" IS NULL AND ""Tracks"".""Id"" IS NULL AND ""Artists"".""Id"" IS NULL)"); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedBlacklist.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedBlacklist.cs index bc02d1b50..7beeddd13 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedBlacklist.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedBlacklist.cs @@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM Blocklist - WHERE Id IN ( - SELECT Blocklist.Id FROM Blocklist - LEFT OUTER JOIN Artists - ON Blocklist.ArtistId = Artists.Id - WHERE Artists.Id IS NULL)"); + mapper.Execute(@"DELETE FROM ""Blocklist"" + WHERE ""Id"" IN ( + SELECT ""Blocklist"".""Id"" FROM ""Blocklist"" + LEFT OUTER JOIN ""Artists"" + ON ""Blocklist"".""ArtistId"" = ""Artists"".""Id"" + WHERE ""Artists"".""Id"" IS NULL)"); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedDownloadClientStatus.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedDownloadClientStatus.cs index 91598ace5..124f4476d 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedDownloadClientStatus.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedDownloadClientStatus.cs @@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM DownloadClientStatus - WHERE Id IN ( - SELECT DownloadClientStatus.Id FROM DownloadClientStatus - LEFT OUTER JOIN DownloadClients - ON DownloadClientStatus.ProviderId = DownloadClients.Id - WHERE DownloadClients.Id IS NULL)"); + mapper.Execute(@"DELETE FROM ""DownloadClientStatus"" + WHERE ""Id"" IN ( + SELECT ""DownloadClientStatus"".""Id"" FROM ""DownloadClientStatus"" + LEFT OUTER JOIN ""DownloadClients"" + ON ""DownloadClientStatus"".""ProviderId"" = ""DownloadClients"".""Id"" + WHERE ""DownloadClients"".""Id"" IS NULL)"); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedHistoryItems.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedHistoryItems.cs index 43b298605..8fedcd873 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedHistoryItems.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedHistoryItems.cs @@ -22,12 +22,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM History - WHERE Id IN ( - SELECT History.Id FROM History - LEFT OUTER JOIN Artists - ON History.ArtistId = Artists.Id - WHERE Artists.Id IS NULL)"); + mapper.Execute(@"DELETE FROM ""History"" + WHERE ""Id"" IN ( + SELECT ""History"".""Id"" FROM ""History"" + LEFT OUTER JOIN ""Artists"" + ON ""History"".""ArtistId"" = ""Artists"".""Id"" + WHERE ""Artists"".""Id"" IS NULL)"); } } @@ -35,12 +35,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM History - WHERE Id IN ( - SELECT History.Id FROM History - LEFT OUTER JOIN Albums - ON History.AlbumId = Albums.Id - WHERE Albums.Id IS NULL)"); + mapper.Execute(@"DELETE FROM ""History"" + WHERE ""Id"" IN ( + SELECT ""History"".""Id"" FROM ""History"" + LEFT OUTER JOIN ""Albums"" + ON ""History"".""AlbumId"" = ""Albums"".""Id"" + WHERE ""Albums"".""Id"" IS NULL)"); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedImportListStatus.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedImportListStatus.cs index f40ec672f..1ceb0f396 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedImportListStatus.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedImportListStatus.cs @@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM ImportListStatus - WHERE Id IN ( - SELECT ImportListStatus.Id FROM ImportListStatus - LEFT OUTER JOIN ImportLists - ON ImportListStatus.ProviderId = ImportLists.Id - WHERE ImportLists.Id IS NULL)"); + mapper.Execute(@"DELETE FROM ""ImportListStatus"" + WHERE ""Id"" IN ( + SELECT ""ImportListStatus"".""Id"" FROM ""ImportListStatus"" + LEFT OUTER JOIN ""ImportLists"" + ON ""ImportListStatus"".""ProviderId"" = ""ImportLists"".""Id"" + WHERE ""ImportLists"".""Id"" IS NULL)"); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedIndexerStatus.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedIndexerStatus.cs index 039db9b52..059f059e4 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedIndexerStatus.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedIndexerStatus.cs @@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM IndexerStatus - WHERE Id IN ( - SELECT IndexerStatus.Id FROM IndexerStatus - LEFT OUTER JOIN Indexers - ON IndexerStatus.ProviderId = Indexers.Id - WHERE Indexers.Id IS NULL)"); + mapper.Execute(@"DELETE FROM ""IndexerStatus"" + WHERE ""Id"" IN ( + SELECT ""IndexerStatus"".""Id"" FROM ""IndexerStatus"" + LEFT OUTER JOIN ""Indexers"" + ON ""IndexerStatus"".""ProviderId"" = ""Indexers"".""Id"" + WHERE ""Indexers"".""Id"" IS NULL)"); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedMetadataFiles.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedMetadataFiles.cs index 2afd5feea..989cf9de4 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedMetadataFiles.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedMetadataFiles.cs @@ -25,12 +25,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM MetadataFiles - WHERE Id IN ( - SELECT MetadataFiles.Id FROM MetadataFiles - LEFT OUTER JOIN Artists - ON MetadataFiles.ArtistId = Artists.Id - WHERE Artists.Id IS NULL)"); + mapper.Execute(@"DELETE FROM ""MetadataFiles"" + WHERE ""Id"" IN ( + SELECT ""MetadataFiles"".""Id"" FROM ""MetadataFiles"" + LEFT OUTER JOIN ""Artists"" + ON ""MetadataFiles"".""ArtistId"" = ""Artists"".""Id"" + WHERE ""Artists"".""Id"" IS NULL)"); } } @@ -38,13 +38,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM MetadataFiles - WHERE Id IN ( - SELECT MetadataFiles.Id FROM MetadataFiles - LEFT OUTER JOIN Albums - ON MetadataFiles.AlbumId = Albums.Id - WHERE MetadataFiles.AlbumId > 0 - AND Albums.Id IS NULL)"); + mapper.Execute(@"DELETE FROM ""MetadataFiles"" + WHERE ""Id"" IN ( + SELECT ""MetadataFiles"".""Id"" FROM ""MetadataFiles"" + LEFT OUTER JOIN ""Albums"" + ON ""MetadataFiles"".""AlbumId"" = ""Albums"".""Id"" + WHERE ""MetadataFiles"".""AlbumId"" > 0 + AND ""Albums"".""Id"" IS NULL)"); } } @@ -52,13 +52,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM MetadataFiles - WHERE Id IN ( - SELECT MetadataFiles.Id FROM MetadataFiles - LEFT OUTER JOIN TrackFiles - ON MetadataFiles.TrackFileId = TrackFiles.Id - WHERE MetadataFiles.TrackFileId > 0 - AND TrackFiles.Id IS NULL)"); + mapper.Execute(@"DELETE FROM ""MetadataFiles"" + WHERE ""Id"" IN ( + SELECT ""MetadataFiles"".""Id"" FROM ""MetadataFiles"" + LEFT OUTER JOIN ""TrackFiles"" + ON ""MetadataFiles"".""TrackFileId"" = ""TrackFiles"".""Id"" + WHERE ""MetadataFiles"".""TrackFileId"" > 0 + AND ""TrackFiles"".""Id"" IS NULL)"); } } @@ -66,11 +66,11 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM MetadataFiles - WHERE Id IN ( - SELECT Id FROM MetadataFiles - WHERE Type IN (4, 6) - AND AlbumId = 0)"); + mapper.Execute(@"DELETE FROM ""MetadataFiles"" + WHERE ""Id"" IN ( + SELECT ""Id"" FROM ""MetadataFiles"" + WHERE ""Type"" IN (4, 6) + AND ""AlbumId"" = 0)"); } } @@ -78,11 +78,11 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM MetadataFiles - WHERE Id IN ( - SELECT Id FROM MetadataFiles - WHERE Type IN (2, 5) - AND TrackFileId = 0)"); + mapper.Execute(@"DELETE FROM ""MetadataFiles"" + WHERE ""Id"" IN ( + SELECT ""Id"" FROM ""MetadataFiles"" + WHERE ""Type"" IN (2, 5) + AND ""TrackFileId"" = 0)"); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedPendingReleases.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedPendingReleases.cs index d1ce0fc3b..1dccc8f2a 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedPendingReleases.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedPendingReleases.cs @@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM PendingReleases - WHERE Id IN ( - SELECT PendingReleases.Id FROM PendingReleases - LEFT OUTER JOIN Artists - ON PendingReleases.ArtistId = Artists.Id - WHERE Artists.Id IS NULL)"); + mapper.Execute(@"DELETE FROM ""PendingReleases"" + WHERE ""Id"" IN ( + SELECT ""PendingReleases"".""Id"" FROM ""PendingReleases"" + LEFT OUTER JOIN ""Artists"" + ON ""PendingReleases"".""ArtistId"" = ""Artists"".""Id"" + WHERE ""Artists"".""Id"" IS NULL)"); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedReleases.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedReleases.cs index e2613f220..d401794c1 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedReleases.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedReleases.cs @@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM AlbumReleases - WHERE Id IN ( - SELECT AlbumReleases.Id FROM AlbumReleases - LEFT OUTER JOIN Albums - ON AlbumReleases.AlbumId = Albums.Id - WHERE Albums.Id IS NULL)"); + mapper.Execute(@"DELETE FROM ""AlbumReleases"" + WHERE ""Id"" IN ( + SELECT ""AlbumReleases"".""Id"" FROM ""AlbumReleases"" + LEFT OUTER JOIN ""Albums"" + ON ""AlbumReleases"".""AlbumId"" = ""Albums"".""Id"" + WHERE ""Albums"".""Id"" IS NULL)"); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedTrackFiles.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedTrackFiles.cs index 57657de83..62286c667 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedTrackFiles.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedTrackFiles.cs @@ -17,22 +17,22 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers using (var mapper = _database.OpenConnection()) { // Unlink where track no longer exists - mapper.Execute(@"UPDATE TrackFiles - SET AlbumId = 0 - WHERE Id IN ( - SELECT TrackFiles.Id FROM TrackFiles - LEFT OUTER JOIN Tracks - ON TrackFiles.Id = Tracks.TrackFileId - WHERE Tracks.Id IS NULL)"); + mapper.Execute(@"UPDATE ""TrackFiles"" + SET ""AlbumId"" = 0 + WHERE ""Id"" IN ( + SELECT ""TrackFiles"".""Id"" FROM ""TrackFiles"" + LEFT OUTER JOIN ""Tracks"" + ON ""TrackFiles"".""Id"" = ""Tracks"".""TrackFileId"" + WHERE ""Tracks"".""Id"" IS NULL)"); // Unlink Tracks where the Trackfiles entry no longer exists - mapper.Execute(@"UPDATE Tracks - SET TrackFileId = 0 - WHERE Id IN ( - SELECT Tracks.Id FROM Tracks - LEFT OUTER JOIN TrackFiles - ON Tracks.TrackFileId = TrackFiles.Id - WHERE TrackFiles.Id IS NULL)"); + mapper.Execute(@"UPDATE ""Tracks"" + SET ""TrackFileId"" = 0 + WHERE ""Id"" IN ( + SELECT ""Tracks"".""Id"" FROM ""Tracks"" + LEFT OUTER JOIN ""TrackFiles"" + ON ""Tracks"".""TrackFileId"" = ""TrackFiles"".""Id"" + WHERE ""TrackFiles"".""Id"" IS NULL)"); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedTracks.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedTracks.cs index ff2017c5c..6b26e5da9 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedTracks.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedTracks.cs @@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers { using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"DELETE FROM Tracks - WHERE Id IN ( - SELECT Tracks.Id FROM Tracks - LEFT OUTER JOIN AlbumReleases - ON Tracks.AlbumReleaseId = AlbumReleases.Id - WHERE AlbumReleases.Id IS NULL)"); + mapper.Execute(@"DELETE FROM ""Tracks"" + WHERE ""Id"" IN ( + SELECT ""Tracks"".""Id"" FROM ""Tracks"" + LEFT OUTER JOIN ""AlbumReleases"" + ON ""Tracks"".""AlbumReleaseId"" = ""AlbumReleases"".""Id"" + WHERE ""AlbumReleases"".""Id"" IS NULL)"); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupUnusedTags.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupUnusedTags.cs index e164325e4..9431d04ff 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupUnusedTags.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupUnusedTags.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Data; using System.Linq; using Dapper; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Housekeeping.Housekeepers @@ -24,15 +25,29 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers .Distinct() .ToArray(); - var usedTagsList = string.Join(",", usedTags.Select(d => d.ToString()).ToArray()); + if (usedTags.Any()) + { + var usedTagsList = usedTags.Select(d => d.ToString()).Join(","); - mapper.Execute($"DELETE FROM Tags WHERE NOT Id IN ({usedTagsList})"); + if (_database.DatabaseType == DatabaseType.PostgreSQL) + { + mapper.Execute($"DELETE FROM \"Tags\" WHERE NOT \"Id\" = ANY (\'{{{usedTagsList}}}\'::int[])"); + } + else + { + mapper.Execute($"DELETE FROM \"Tags\" WHERE NOT \"Id\" IN ({usedTagsList})"); + } + } + else + { + mapper.Execute("DELETE FROM \"Tags\""); + } } } private int[] GetUsedTags(string table, IDbConnection mapper) { - return mapper.Query>($"SELECT DISTINCT Tags FROM {table} WHERE NOT Tags = '[]' AND NOT Tags IS NULL") + return mapper.Query>($"SELECT DISTINCT \"Tags\" FROM \"{table}\" WHERE NOT \"Tags\" = '[]' AND NOT \"Tags\" IS NULL") .SelectMany(x => x) .Distinct() .ToArray(); diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/FixFutureRunScheduledTasks.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/FixFutureRunScheduledTasks.cs index 82a7af7fa..47333e596 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/FixFutureRunScheduledTasks.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/FixFutureRunScheduledTasks.cs @@ -26,9 +26,9 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers using (var mapper = _database.OpenConnection()) { - mapper.Execute(@"UPDATE ScheduledTasks - SET LastExecution = @time - WHERE LastExecution > @time", + mapper.Execute(@"UPDATE ""ScheduledTasks"" + SET ""LastExecution"" = @time + WHERE ""LastExecution"" > @time", new { time = DateTime.UtcNow }); } } diff --git a/src/NzbDrone.Core/Instrumentation/DatabaseTarget.cs b/src/NzbDrone.Core/Instrumentation/DatabaseTarget.cs index ccca19073..0e0eee611 100644 --- a/src/NzbDrone.Core/Instrumentation/DatabaseTarget.cs +++ b/src/NzbDrone.Core/Instrumentation/DatabaseTarget.cs @@ -1,9 +1,11 @@ -using System.Data; +using System; +using System.Data; using System.Data.SQLite; using NLog; using NLog.Common; using NLog.Config; using NLog.Targets; +using Npgsql; using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Datastore; using NzbDrone.Core.Lifecycle; @@ -13,7 +15,7 @@ namespace NzbDrone.Core.Instrumentation { public class DatabaseTarget : TargetWithLayout, IHandle { - private const string INSERT_COMMAND = "INSERT INTO [Logs]([Message],[Time],[Logger],[Exception],[ExceptionType],[Level]) " + + private const string INSERT_COMMAND = "INSERT INTO \"Logs\" (\"Message\",\"Time\",\"Logger\",\"Exception\",\"ExceptionType\",\"Level\") " + "VALUES(@Message,@Time,@Logger,@Exception,@ExceptionType,@Level)"; private readonly IConnectionStringFactory _connectionStringFactory; @@ -55,7 +57,6 @@ namespace NzbDrone.Core.Instrumentation { try { - using var connection = new SQLiteConnection(_connectionStringFactory.LogDbConnectionString).OpenAndReturn(); var log = new Log(); log.Time = logEvent.TimeStamp; log.Message = CleanseLogMessage.Cleanse(logEvent.FormattedMessage); @@ -84,16 +85,17 @@ namespace NzbDrone.Core.Instrumentation log.Level = logEvent.Level.Name; - var sqlCommand = new SQLiteCommand(INSERT_COMMAND, connection); + var connectionString = _connectionStringFactory.LogDbConnectionString; - sqlCommand.Parameters.Add(new SQLiteParameter("Message", DbType.String) { Value = log.Message }); - sqlCommand.Parameters.Add(new SQLiteParameter("Time", DbType.DateTime) { Value = log.Time.ToUniversalTime() }); - sqlCommand.Parameters.Add(new SQLiteParameter("Logger", DbType.String) { Value = log.Logger }); - sqlCommand.Parameters.Add(new SQLiteParameter("Exception", DbType.String) { Value = log.Exception }); - sqlCommand.Parameters.Add(new SQLiteParameter("ExceptionType", DbType.String) { Value = log.ExceptionType }); - sqlCommand.Parameters.Add(new SQLiteParameter("Level", DbType.String) { Value = log.Level }); - - sqlCommand.ExecuteNonQuery(); + //TODO: Probably need more robust way to differentiate what's being used + if (connectionString.Contains(".db")) + { + WriteSqliteLog(log, connectionString); + } + else + { + WritePostgresLog(log, connectionString); + } } catch (SQLiteException ex) { @@ -102,6 +104,48 @@ namespace NzbDrone.Core.Instrumentation } } + private void WritePostgresLog(Log log, string connectionString) + { + using (var connection = + new NpgsqlConnection(connectionString)) + { + connection.Open(); + using (var sqlCommand = connection.CreateCommand()) + { + sqlCommand.CommandText = INSERT_COMMAND; + sqlCommand.Parameters.Add(new NpgsqlParameter("Message", DbType.String) { Value = log.Message }); + sqlCommand.Parameters.Add(new NpgsqlParameter("Time", DbType.DateTime) { Value = log.Time.ToUniversalTime() }); + sqlCommand.Parameters.Add(new NpgsqlParameter("Logger", DbType.String) { Value = log.Logger }); + sqlCommand.Parameters.Add(new NpgsqlParameter("Exception", DbType.String) { Value = log.Exception == null ? DBNull.Value : log.Exception }); + sqlCommand.Parameters.Add(new NpgsqlParameter("ExceptionType", DbType.String) { Value = log.ExceptionType == null ? DBNull.Value : log.ExceptionType }); + sqlCommand.Parameters.Add(new NpgsqlParameter("Level", DbType.String) { Value = log.Level }); + + sqlCommand.ExecuteNonQuery(); + } + } + } + + private void WriteSqliteLog(Log log, string connectionString) + { + using (var connection = + SQLiteFactory.Instance.CreateConnection()) + { + connection.ConnectionString = connectionString; + connection.Open(); + using (var sqlCommand = connection.CreateCommand()) + { + sqlCommand.CommandText = INSERT_COMMAND; + sqlCommand.Parameters.Add(new SQLiteParameter("Message", DbType.String) { Value = log.Message }); + sqlCommand.Parameters.Add(new SQLiteParameter("Time", DbType.DateTime) { Value = log.Time.ToUniversalTime() }); + sqlCommand.Parameters.Add(new SQLiteParameter("Logger", DbType.String) { Value = log.Logger }); + sqlCommand.Parameters.Add(new SQLiteParameter("Exception", DbType.String) { Value = log.Exception }); + sqlCommand.Parameters.Add(new SQLiteParameter("ExceptionType", DbType.String) { Value = log.ExceptionType }); + sqlCommand.Parameters.Add(new SQLiteParameter("Level", DbType.String) { Value = log.Level }); + sqlCommand.ExecuteNonQuery(); + } + } + } + public void Handle(ApplicationShutdownRequested message) { if (LogManager.Configuration != null && LogManager.Configuration.LoggingRules.Contains(Rule)) diff --git a/src/NzbDrone.Core/Lidarr.Core.csproj b/src/NzbDrone.Core/Lidarr.Core.csproj index 34b027680..5b9b9adb8 100644 --- a/src/NzbDrone.Core/Lidarr.Core.csproj +++ b/src/NzbDrone.Core/Lidarr.Core.csproj @@ -11,6 +11,7 @@ + @@ -20,6 +21,7 @@ + diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 4dc606652..1a11a83a1 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -152,6 +152,7 @@ "CustomFilters": "Custom Filters", "CutoffHelpText": "Once this quality is reached Lidarr will no longer download albums", "CutoffUnmet": "Cutoff Unmet", + "Database": "Database", "Date": "Date", "DateAdded": "Date Added", "Dates": "Dates", diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs index 5148be7c4..cac31f4cc 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs @@ -31,7 +31,7 @@ namespace NzbDrone.Core.MediaFiles // always join with all the other good stuff // needed more often than not so better to load it all now - protected override SqlBuilder Builder() => new SqlBuilder() + protected override SqlBuilder Builder() => new SqlBuilder(_database.DatabaseType) .LeftJoin((t, x) => t.Id == x.TrackFileId) .LeftJoin((t, a) => t.AlbumId == a.Id) .LeftJoin((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId) @@ -90,7 +90,7 @@ namespace NzbDrone.Core.MediaFiles { //x.Id == null is converted to SQL, so warning incorrect #pragma warning disable CS0472 - return _database.Query(new SqlBuilder().Select(typeof(TrackFile)) + return _database.Query(new SqlBuilder(_database.DatabaseType).Select(typeof(TrackFile)) .LeftJoin((f, t) => f.Id == t.TrackFileId) .Where(t => t.Id == null)).ToList(); #pragma warning restore CS0472 @@ -117,7 +117,7 @@ namespace NzbDrone.Core.MediaFiles { // ensure path ends with a single trailing path separator to avoid matching partial paths var safePath = path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; - return _database.Query(new SqlBuilder().Where(x => x.Path.StartsWith(safePath))).ToList(); + return _database.Query(new SqlBuilder(_database.DatabaseType).Where(x => x.Path.StartsWith(safePath))).ToList(); } public TrackFile GetFileWithPath(string path) @@ -128,7 +128,7 @@ namespace NzbDrone.Core.MediaFiles public List GetFileWithPath(List paths) { // use more limited join for speed - var builder = new SqlBuilder() + var builder = new SqlBuilder(_database.DatabaseType) .LeftJoin((f, t) => f.Id == t.TrackFileId); var dict = new Dictionary(); diff --git a/src/NzbDrone.Core/Messaging/Commands/CommandRepository.cs b/src/NzbDrone.Core/Messaging/Commands/CommandRepository.cs index 7bb1416cb..00b24ad59 100644 --- a/src/NzbDrone.Core/Messaging/Commands/CommandRepository.cs +++ b/src/NzbDrone.Core/Messaging/Commands/CommandRepository.cs @@ -31,7 +31,7 @@ namespace NzbDrone.Core.Messaging.Commands public void OrphanStarted() { - var sql = @"UPDATE Commands SET Status = @Orphaned, EndedAt = @Ended WHERE Status = @Started"; + var sql = @"UPDATE ""Commands"" SET ""Status"" = @Orphaned, ""EndedAt"" = @Ended WHERE ""Status"" = @Started"; var args = new { Orphaned = (int)CommandStatus.Orphaned, diff --git a/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs b/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs index a2419c382..4dbb18f22 100644 --- a/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs +++ b/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs @@ -15,7 +15,7 @@ namespace NzbDrone.Core.Music List GetLastAlbums(IEnumerable artistMetadataIds); List GetNextAlbums(IEnumerable artistMetadataIds); List GetAlbumsByArtistMetadataId(int artistMetadataId); - List GetAlbumsForRefresh(int artistMetadataId, IEnumerable foreignIds); + List GetAlbumsForRefresh(int artistMetadataId, List foreignIds); Album FindByTitle(int artistMetadataId, string title); Album FindById(string foreignAlbumId); PagingSpec AlbumsWithoutFiles(PagingSpec pagingSpec); @@ -44,17 +44,35 @@ namespace NzbDrone.Core.Music public List GetLastAlbums(IEnumerable artistMetadataIds) { var now = DateTime.UtcNow; - return Query(Builder().Where(x => artistMetadataIds.Contains(x.ArtistMetadataId) && x.ReleaseDate < now) - .GroupBy(x => x.ArtistMetadataId) - .Having("Albums.ReleaseDate = MAX(Albums.ReleaseDate)")); + + var inner = Builder() + .Select("MIN(\"Albums\".\"Id\") as id, MAX(\"Albums\".\"ReleaseDate\") as date") + .Where(x => artistMetadataIds.Contains(x.ArtistMetadataId) && x.ReleaseDate < now) + .GroupBy(x => x.ArtistMetadataId) + .AddSelectTemplate(typeof(Album)); + + var outer = Builder() + .Join($"({inner.RawSql}) ids on ids.id = \"Albums\".\"Id\" and ids.date = \"Albums\".\"ReleaseDate\"") + .AddParameters(inner.Parameters); + + return Query(outer); } public List GetNextAlbums(IEnumerable artistMetadataIds) { var now = DateTime.UtcNow; - return Query(Builder().Where(x => artistMetadataIds.Contains(x.ArtistMetadataId) && x.ReleaseDate > now) - .GroupBy(x => x.ArtistMetadataId) - .Having("Albums.ReleaseDate = MIN(Albums.ReleaseDate)")); + + var inner = Builder() + .Select("MIN(\"Albums\".\"Id\") as id, MIN(\"Albums\".\"ReleaseDate\") as date") + .Where(x => artistMetadataIds.Contains(x.ArtistMetadataId) && x.ReleaseDate > now) + .GroupBy(x => x.ArtistMetadataId) + .AddSelectTemplate(typeof(Album)); + + var outer = Builder() + .Join($"({inner.RawSql}) ids on ids.id = \"Albums\".\"Id\" and ids.date = \"Albums\".\"ReleaseDate\"") + .AddParameters(inner.Parameters); + + return Query(outer); } public List GetAlbumsByArtistMetadataId(int artistMetadataId) @@ -62,7 +80,7 @@ namespace NzbDrone.Core.Music return Query(s => s.ArtistMetadataId == artistMetadataId); } - public List GetAlbumsForRefresh(int artistMetadataId, IEnumerable foreignIds) + public List GetAlbumsForRefresh(int artistMetadataId, List foreignIds) { return Query(a => a.ArtistMetadataId == artistMetadataId || foreignIds.Contains(a.ForeignAlbumId)); } @@ -74,15 +92,17 @@ namespace NzbDrone.Core.Music //x.Id == null is converted to SQL, so warning incorrect #pragma warning disable CS0472 - private SqlBuilder AlbumsWithoutFilesBuilder(DateTime currentTime) => Builder() - .Join((l, r) => l.ArtistMetadataId == r.ArtistMetadataId) - .Join((a, r) => a.Id == r.AlbumId) - .Join((r, t) => r.Id == t.AlbumReleaseId) - .LeftJoin((t, f) => t.TrackFileId == f.Id) - .Where(f => f.Id == null) - .Where(r => r.Monitored == true) - .Where(a => a.ReleaseDate <= currentTime) - .GroupBy(a => a.Id); + private SqlBuilder AlbumsWithoutFilesBuilder(DateTime currentTime) + { + return Builder() + .Join((l, r) => l.ArtistMetadataId == r.ArtistMetadataId) + .Join((a, r) => a.Id == r.AlbumId) + .Join((r, t) => r.Id == t.AlbumReleaseId) + .LeftJoin((t, f) => t.TrackFileId == f.Id) + .Where(f => f.Id == null) + .Where(r => r.Monitored == true) + .Where(a => a.ReleaseDate <= currentTime); + } #pragma warning restore CS0472 public PagingSpec AlbumsWithoutFiles(PagingSpec pagingSpec) @@ -95,14 +115,16 @@ namespace NzbDrone.Core.Music return pagingSpec; } - private SqlBuilder AlbumsWhereCutoffUnmetBuilder(List qualitiesBelowCutoff) => Builder() - .Join((l, r) => l.ArtistMetadataId == r.ArtistMetadataId) - .Join((a, r) => a.Id == r.AlbumId) - .Join((r, t) => r.Id == t.AlbumReleaseId) - .LeftJoin((t, f) => t.TrackFileId == f.Id) - .Where(r => r.Monitored == true) - .GroupBy(a => a.Id) - .Having(BuildQualityCutoffWhereClause(qualitiesBelowCutoff)); + private SqlBuilder AlbumsWhereCutoffUnmetBuilder(List qualitiesBelowCutoff) + { + return Builder() + .Join((l, r) => l.ArtistMetadataId == r.ArtistMetadataId) + .Join((a, r) => a.Id == r.AlbumId) + .Join((r, t) => r.Id == t.AlbumReleaseId) + .LeftJoin((t, f) => t.TrackFileId == f.Id) + .Where(r => r.Monitored == true) + .Where(BuildQualityCutoffWhereClause(qualitiesBelowCutoff)); + } private string BuildQualityCutoffWhereClause(List qualitiesBelowCutoff) { @@ -112,7 +134,7 @@ namespace NzbDrone.Core.Music { foreach (var belowCutoff in profile.QualityIds) { - clauses.Add(string.Format("(Artists.[QualityProfileId] = {0} AND MIN(TrackFiles.Quality) LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff)); + clauses.Add(string.Format("(\"Artists\".\"QualityProfileId\" = {0} AND \"TrackFiles\".\"Quality\" LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff)); } } @@ -123,7 +145,7 @@ namespace NzbDrone.Core.Music { pagingSpec.Records = GetPagedRecords(AlbumsWhereCutoffUnmetBuilder(qualitiesBelowCutoff), pagingSpec, PagedQuery); - var countTemplate = $"SELECT COUNT(*) FROM (SELECT /**select**/ FROM {TableMapping.Mapper.TableNameMapping(typeof(Album))} /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/)"; + var countTemplate = $"SELECT COUNT(*) FROM (SELECT /**select**/ FROM \"{TableMapping.Mapper.TableNameMapping(typeof(Album))}\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/) AS \"Inner\""; pagingSpec.TotalRecords = GetPagedRecordCount(AlbumsWhereCutoffUnmetBuilder(qualitiesBelowCutoff).Select(typeof(Album)), pagingSpec, countTemplate); return pagingSpec; @@ -131,13 +153,15 @@ namespace NzbDrone.Core.Music public List AlbumsBetweenDates(DateTime startDate, DateTime endDate, bool includeUnmonitored) { - var builder = Builder().Where(rg => rg.ReleaseDate >= startDate && rg.ReleaseDate <= endDate); + SqlBuilder builder; + + builder = Builder().Where(rg => rg.ReleaseDate >= startDate && rg.ReleaseDate <= endDate); if (!includeUnmonitored) { builder = builder.Where(e => e.Monitored == true) - .Join((l, r) => l.ArtistMetadataId == r.ArtistMetadataId) - .Where(e => e.Monitored == true); + .Join((l, r) => l.ArtistMetadataId == r.ArtistMetadataId) + .Where(e => e.Monitored == true); } return Query(builder); @@ -145,15 +169,17 @@ namespace NzbDrone.Core.Music public List ArtistAlbumsBetweenDates(Artist artist, DateTime startDate, DateTime endDate, bool includeUnmonitored) { - var builder = Builder().Where(rg => rg.ReleaseDate >= startDate && - rg.ReleaseDate <= endDate && - rg.ArtistMetadataId == artist.ArtistMetadataId); + SqlBuilder builder; + + builder = Builder().Where(rg => rg.ReleaseDate >= startDate && + rg.ReleaseDate <= endDate && + rg.ArtistMetadataId == artist.ArtistMetadataId); if (!includeUnmonitored) { builder = builder.Where(e => e.Monitored == true) - .Join((l, r) => l.ArtistMetadataId == r.ArtistMetadataId) - .Where(e => e.Monitored == true); + .Join((l, r) => l.ArtistMetadataId == r.ArtistMetadataId) + .Where(e => e.Monitored == true); } return Query(builder); @@ -200,6 +226,7 @@ namespace NzbDrone.Core.Music public List GetArtistAlbumsWithFiles(Artist artist) { var id = artist.ArtistMetadataId; + return Query(Builder().Join((a, r) => a.Id == r.AlbumId) .Join((r, t) => r.Id == t.AlbumReleaseId) .Join((t, f) => t.TrackFileId == f.Id) diff --git a/src/NzbDrone.Core/Music/Repositories/ArtistRepository.cs b/src/NzbDrone.Core/Music/Repositories/ArtistRepository.cs index dfe0699e9..3e5d73765 100644 --- a/src/NzbDrone.Core/Music/Repositories/ArtistRepository.cs +++ b/src/NzbDrone.Core/Music/Repositories/ArtistRepository.cs @@ -25,7 +25,7 @@ namespace NzbDrone.Core.Music { } - protected override SqlBuilder Builder() => new SqlBuilder() + protected override SqlBuilder Builder() => new SqlBuilder(_database.DatabaseType) .Join((a, m) => a.ArtistMetadataId == m.Id); protected override List Query(SqlBuilder builder) => Query(_database, builder).ToList(); @@ -46,7 +46,9 @@ namespace NzbDrone.Core.Music public Artist FindById(string foreignArtistId) { - var artist = Query(Builder().Where(m => m.ForeignArtistId == foreignArtistId)).SingleOrDefault(); + Artist artist; + + artist = Query(Builder().Where(m => m.ForeignArtistId == foreignArtistId)).SingleOrDefault(); if (artist == null) { @@ -72,7 +74,7 @@ namespace NzbDrone.Core.Music { using (var conn = _database.OpenConnection()) { - var strSql = "SELECT Id AS [Key], Path AS [Value] FROM Artists"; + var strSql = "SELECT \"Id\" AS \"Key\", \"Path\" AS \"Value\" FROM \"Artists\""; return conn.Query>(strSql).ToDictionary(x => x.Key, x => x.Value); } } diff --git a/src/NzbDrone.Core/Music/Repositories/ReleaseRepository.cs b/src/NzbDrone.Core/Music/Repositories/ReleaseRepository.cs index 034480eba..32b8bc706 100644 --- a/src/NzbDrone.Core/Music/Repositories/ReleaseRepository.cs +++ b/src/NzbDrone.Core/Music/Repositories/ReleaseRepository.cs @@ -1,8 +1,9 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using NzbDrone.Common.EnsureThat; using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; +using TagLib.Riff; namespace NzbDrone.Core.Music { @@ -11,7 +12,7 @@ namespace NzbDrone.Core.Music AlbumRelease FindByForeignReleaseId(string foreignReleaseId, bool checkRedirect = false); List FindByAlbum(int id); List FindByRecordingId(List recordingIds); - List GetReleasesForRefresh(int albumId, IEnumerable foreignReleaseIds); + List GetReleasesForRefresh(int albumId, List foreignReleaseIds); List SetMonitored(AlbumRelease release); } @@ -35,7 +36,7 @@ namespace NzbDrone.Core.Music return release; } - public List GetReleasesForRefresh(int albumId, IEnumerable foreignReleaseIds) + public List GetReleasesForRefresh(int albumId, List foreignReleaseIds) { return Query(r => r.AlbumId == albumId || foreignReleaseIds.Contains(r.ForeignReleaseId)); } @@ -44,10 +45,12 @@ namespace NzbDrone.Core.Music { // populate the albums and artist metadata also // this hopefully speeds up the track matching a lot - var builder = new SqlBuilder() - .LeftJoin((r, a) => r.AlbumId == a.Id) - .LeftJoin((a, m) => a.ArtistMetadataId == m.Id) - .Where(r => r.AlbumId == id); + SqlBuilder builder; + + builder = new SqlBuilder(_database.DatabaseType) + .LeftJoin((r, a) => r.AlbumId == a.Id) + .LeftJoin((a, m) => a.ArtistMetadataId == m.Id) + .Where(r => r.AlbumId == id); return _database.QueryJoined(builder, (release, album, metadata) => { diff --git a/src/NzbDrone.Core/Music/Repositories/TrackRepository.cs b/src/NzbDrone.Core/Music/Repositories/TrackRepository.cs index ce5906378..75c8b7a1b 100644 --- a/src/NzbDrone.Core/Music/Repositories/TrackRepository.cs +++ b/src/NzbDrone.Core/Music/Repositories/TrackRepository.cs @@ -12,7 +12,7 @@ namespace NzbDrone.Core.Music List GetTracksByAlbum(int albumId); List GetTracksByRelease(int albumReleaseId); List GetTracksByReleases(List albumReleaseIds); - List GetTracksForRefresh(int albumReleaseId, IEnumerable foreignTrackIds); + List GetTracksForRefresh(int albumReleaseId, List foreignTrackIds); List GetTracksByFileId(int fileId); List GetTracksByFileId(IEnumerable ids); List TracksWithFiles(int artistId); @@ -64,7 +64,7 @@ namespace NzbDrone.Core.Music }).ToList(); } - public List GetTracksForRefresh(int albumReleaseId, IEnumerable foreignTrackIds) + public List GetTracksForRefresh(int albumReleaseId, List foreignTrackIds) { return Query(a => a.AlbumReleaseId == albumReleaseId || foreignTrackIds.Contains(a.ForeignTrackId)); } diff --git a/src/NzbDrone.Core/Music/Services/AlbumService.cs b/src/NzbDrone.Core/Music/Services/AlbumService.cs index 14b1af6d7..abce226ce 100644 --- a/src/NzbDrone.Core/Music/Services/AlbumService.cs +++ b/src/NzbDrone.Core/Music/Services/AlbumService.cs @@ -18,7 +18,7 @@ namespace NzbDrone.Core.Music List GetNextAlbumsByArtistMetadataId(IEnumerable artistMetadataIds); List GetLastAlbumsByArtistMetadataId(IEnumerable artistMetadataIds); List GetAlbumsByArtistMetadataId(int artistMetadataId); - List GetAlbumsForRefresh(int artistMetadataId, IEnumerable foreignIds); + List GetAlbumsForRefresh(int artistMetadataId, List foreignIds); Album AddAlbum(Album newAlbum, bool doRefresh); Album FindById(string foreignId); Album FindByTitle(int artistMetadataId, string title); @@ -185,7 +185,7 @@ namespace NzbDrone.Core.Music return _albumRepository.GetAlbumsByArtistMetadataId(artistMetadataId).ToList(); } - public List GetAlbumsForRefresh(int artistMetadataId, IEnumerable foreignIds) + public List GetAlbumsForRefresh(int artistMetadataId, List foreignIds) { return _albumRepository.GetAlbumsForRefresh(artistMetadataId, foreignIds); } diff --git a/src/NzbDrone.Core/Music/Services/RefreshAlbumReleaseService.cs b/src/NzbDrone.Core/Music/Services/RefreshAlbumReleaseService.cs index a02791501..983ca0c07 100644 --- a/src/NzbDrone.Core/Music/Services/RefreshAlbumReleaseService.cs +++ b/src/NzbDrone.Core/Music/Services/RefreshAlbumReleaseService.cs @@ -78,7 +78,7 @@ namespace NzbDrone.Core.Music { return _trackService.GetTracksForRefresh(entity.Id, remoteChildren.Select(x => x.ForeignTrackId) - .Concat(remoteChildren.SelectMany(x => x.OldForeignTrackIds))); + .Concat(remoteChildren.SelectMany(x => x.OldForeignTrackIds)).ToList()); } protected override Tuple> GetMatchingExistingChildren(List existingChildren, Track remote) diff --git a/src/NzbDrone.Core/Music/Services/RefreshAlbumService.cs b/src/NzbDrone.Core/Music/Services/RefreshAlbumService.cs index a9a271f8f..2be37b499 100644 --- a/src/NzbDrone.Core/Music/Services/RefreshAlbumService.cs +++ b/src/NzbDrone.Core/Music/Services/RefreshAlbumService.cs @@ -247,7 +247,7 @@ namespace NzbDrone.Core.Music { var children = _releaseService.GetReleasesForRefresh(entity.Id, remoteChildren.Select(x => x.ForeignReleaseId) - .Concat(remoteChildren.SelectMany(x => x.OldForeignReleaseIds))); + .Concat(remoteChildren.SelectMany(x => x.OldForeignReleaseIds)).ToList()); // Make sure trackfiles point to the new album where we are grabbing a release from another album var files = new List(); diff --git a/src/NzbDrone.Core/Music/Services/RefreshArtistService.cs b/src/NzbDrone.Core/Music/Services/RefreshArtistService.cs index bd6b82a87..50882e9ac 100644 --- a/src/NzbDrone.Core/Music/Services/RefreshArtistService.cs +++ b/src/NzbDrone.Core/Music/Services/RefreshArtistService.cs @@ -213,7 +213,7 @@ namespace NzbDrone.Core.Music { return _albumService.GetAlbumsForRefresh(entity.ArtistMetadataId, remoteChildren.Select(x => x.ForeignAlbumId) - .Concat(remoteChildren.SelectMany(x => x.OldForeignAlbumIds))); + .Concat(remoteChildren.SelectMany(x => x.OldForeignAlbumIds)).ToList()); } protected override Tuple> GetMatchingExistingChildren(List existingChildren, Album remote) diff --git a/src/NzbDrone.Core/Music/Services/ReleaseService.cs b/src/NzbDrone.Core/Music/Services/ReleaseService.cs index 69b33613c..4365472ac 100644 --- a/src/NzbDrone.Core/Music/Services/ReleaseService.cs +++ b/src/NzbDrone.Core/Music/Services/ReleaseService.cs @@ -13,7 +13,7 @@ namespace NzbDrone.Core.Music void InsertMany(List releases); void UpdateMany(List releases); void DeleteMany(List releases); - List GetReleasesForRefresh(int albumId, IEnumerable foreignReleaseIds); + List GetReleasesForRefresh(int albumId, List foreignReleaseIds); List GetReleasesByAlbum(int releaseGroupId); List GetReleasesByRecordingIds(List recordingIds); List SetMonitored(AlbumRelease release); @@ -66,7 +66,7 @@ namespace NzbDrone.Core.Music } } - public List GetReleasesForRefresh(int albumId, IEnumerable foreignReleaseIds) + public List GetReleasesForRefresh(int albumId, List foreignReleaseIds) { return _releaseRepository.GetReleasesForRefresh(albumId, foreignReleaseIds); } diff --git a/src/NzbDrone.Core/Music/Services/TrackService.cs b/src/NzbDrone.Core/Music/Services/TrackService.cs index 50c4d8891..38ee2da24 100644 --- a/src/NzbDrone.Core/Music/Services/TrackService.cs +++ b/src/NzbDrone.Core/Music/Services/TrackService.cs @@ -15,7 +15,7 @@ namespace NzbDrone.Core.Music List GetTracksByAlbum(int albumId); List GetTracksByRelease(int albumReleaseId); List GetTracksByReleases(List albumReleaseIds); - List GetTracksForRefresh(int albumReleaseId, IEnumerable foreignTrackIds); + List GetTracksForRefresh(int albumReleaseId, List foreignTrackIds); List TracksWithFiles(int artistId); List TracksWithoutFiles(int albumId); List GetTracksByFileId(int trackFileId); @@ -72,7 +72,7 @@ namespace NzbDrone.Core.Music return _trackRepository.GetTracksByReleases(albumReleaseIds); } - public List GetTracksForRefresh(int albumReleaseId, IEnumerable foreignTrackIds) + public List GetTracksForRefresh(int albumReleaseId, List foreignTrackIds) { return _trackRepository.GetTracksForRefresh(albumReleaseId, foreignTrackIds); } diff --git a/src/NzbDrone.Core/Update/UpdatePackageProvider.cs b/src/NzbDrone.Core/Update/UpdatePackageProvider.cs index a2001b2da..3c4d11564 100644 --- a/src/NzbDrone.Core/Update/UpdatePackageProvider.cs +++ b/src/NzbDrone.Core/Update/UpdatePackageProvider.cs @@ -6,6 +6,7 @@ using NzbDrone.Common.Cloud; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Http; using NzbDrone.Core.Analytics; +using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Update { @@ -21,13 +22,15 @@ namespace NzbDrone.Core.Update private readonly IHttpRequestBuilderFactory _requestBuilder; private readonly IPlatformInfo _platformInfo; private readonly IAnalyticsService _analyticsService; + private readonly IMainDatabase _mainDatabase; - public UpdatePackageProvider(IHttpClient httpClient, ILidarrCloudRequestBuilder requestBuilder, IAnalyticsService analyticsService, IPlatformInfo platformInfo) + public UpdatePackageProvider(IHttpClient httpClient, ILidarrCloudRequestBuilder requestBuilder, IAnalyticsService analyticsService, IPlatformInfo platformInfo, IMainDatabase mainDatabase) { _platformInfo = platformInfo; _analyticsService = analyticsService; _requestBuilder = requestBuilder.Services; _httpClient = httpClient; + _mainDatabase = mainDatabase; } public UpdatePackage GetLatestUpdate(string branch, Version currentVersion) @@ -39,6 +42,7 @@ namespace NzbDrone.Core.Update .AddQueryParam("arch", RuntimeInformation.OSArchitecture) .AddQueryParam("runtime", "netcore") .AddQueryParam("runtimeVer", _platformInfo.Version) + .AddQueryParam("dbType", _mainDatabase.DatabaseType) .SetSegment("branch", branch); if (_analyticsService.IsEnabled) diff --git a/src/NzbDrone.Host.Test/ContainerFixture.cs b/src/NzbDrone.Host.Test/ContainerFixture.cs index f2aeb5039..b266df217 100644 --- a/src/NzbDrone.Host.Test/ContainerFixture.cs +++ b/src/NzbDrone.Host.Test/ContainerFixture.cs @@ -5,12 +5,14 @@ using DryIoc.Microsoft.DependencyInjection; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using NzbDrone.Common; using NzbDrone.Common.Composition.Extensions; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Instrumentation.Extensions; +using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore.Extensions; using NzbDrone.Core.Download; using NzbDrone.Core.Download.TrackedDownloads; @@ -43,6 +45,7 @@ namespace NzbDrone.App.Test // dummy lifetime and broadcaster so tests resolve container.RegisterInstance(new Mock().Object); container.RegisterInstance(new Mock().Object); + container.RegisterInstance>(new Mock>().Object); _container = container.GetServiceProvider(); } diff --git a/src/NzbDrone.Host/Bootstrap.cs b/src/NzbDrone.Host/Bootstrap.cs index fc24058ad..e8699228f 100644 --- a/src/NzbDrone.Host/Bootstrap.cs +++ b/src/NzbDrone.Host/Bootstrap.cs @@ -8,7 +8,6 @@ using System.Security.Cryptography.X509Certificates; using System.Text; using DryIoc; using DryIoc.Microsoft.DependencyInjection; -using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -25,6 +24,9 @@ using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Core.Configuration; using NzbDrone.Core.Datastore.Extensions; +using NzbDrone.Host; +using PostgresOptions = NzbDrone.Core.Datastore.PostgresOptions; + namespace NzbDrone.Host { public static class Bootstrap @@ -138,6 +140,10 @@ namespace NzbDrone.Host .AddDatabase() .AddStartupContext(context); }) + .ConfigureServices(services => + { + services.Configure(config.GetSection("Lidarr:Postgres")); + }) .ConfigureWebHost(builder => { builder.UseConfiguration(config); @@ -208,6 +214,7 @@ namespace NzbDrone.Host return new ConfigurationBuilder() .AddXmlFile(appFolder.GetConfigPath(), optional: true, reloadOnChange: false) .AddInMemoryCollection(new List> { new ("dataProtectionFolder", appFolder.GetDataProtectionPath()) }) + .AddEnvironmentVariables() .Build(); } diff --git a/src/NzbDrone.Integration.Test/IntegrationTest.cs b/src/NzbDrone.Integration.Test/IntegrationTest.cs index a177efb97..c8e0f45fc 100644 --- a/src/NzbDrone.Integration.Test/IntegrationTest.cs +++ b/src/NzbDrone.Integration.Test/IntegrationTest.cs @@ -1,10 +1,16 @@ +using System.Collections.Generic; using System.Threading; using Lidarr.Http.ClientSchema; +using Microsoft.Extensions.Configuration; using NLog; +using Npgsql; using NUnit.Framework; using NzbDrone.Common.Extensions; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Datastore.Migration.Framework; using NzbDrone.Core.Indexers.Newznab; using NzbDrone.Test.Common; +using NzbDrone.Test.Common.Datastore; namespace NzbDrone.Integration.Test { @@ -19,6 +25,8 @@ namespace NzbDrone.Integration.Test protected int Port { get; private set; } + protected PostgresOptions PostgresOptions { get; set; } = new (); + protected override string RootUrl => $"http://localhost:{Port}/"; protected override string ApiKey => _runner.ApiKey; @@ -27,7 +35,14 @@ namespace NzbDrone.Integration.Test { Port = Interlocked.Increment(ref StaticPort); - _runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), Port); + PostgresOptions = PostgresDatabase.GetTestOptions(); + + if (PostgresOptions?.Host != null) + { + CreatePostgresDb(PostgresOptions); + } + + _runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), PostgresOptions, Port); _runner.Kill(); _runner.Start(); @@ -59,6 +74,22 @@ namespace NzbDrone.Integration.Test protected override void StopTestTarget() { _runner.Kill(); + if (PostgresOptions?.Host != null) + { + DropPostgresDb(PostgresOptions); + } + } + + private static void CreatePostgresDb(PostgresOptions options) + { + PostgresDatabase.Create(options, MigrationType.Main); + PostgresDatabase.Create(options, MigrationType.Log); + } + + private static void DropPostgresDb(PostgresOptions options) + { + PostgresDatabase.Drop(options, MigrationType.Main); + PostgresDatabase.Drop(options, MigrationType.Log); } } } diff --git a/src/NzbDrone.Test.Common/Datastore/PostgresDatabase.cs b/src/NzbDrone.Test.Common/Datastore/PostgresDatabase.cs new file mode 100644 index 000000000..940334ca2 --- /dev/null +++ b/src/NzbDrone.Test.Common/Datastore/PostgresDatabase.cs @@ -0,0 +1,69 @@ +using System; +using Npgsql; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Test.Common.Datastore +{ + public static class PostgresDatabase + { + public static PostgresOptions GetTestOptions() + { + var options = PostgresOptions.GetOptions(); + + var uid = TestBase.GetUID(); + options.MainDb = uid + "_main"; + options.LogDb = uid + "_log"; + + return options; + } + + public static void Create(PostgresOptions options, MigrationType migrationType) + { + var db = GetDatabaseName(options, migrationType); + var connectionString = GetConnectionString(options); + using var conn = new NpgsqlConnection(connectionString); + conn.Open(); + + using var cmd = conn.CreateCommand(); + cmd.CommandText = $"CREATE DATABASE \"{db}\" WITH OWNER = {options.User} ENCODING = 'UTF8' CONNECTION LIMIT = -1;"; + cmd.ExecuteNonQuery(); + } + + public static void Drop(PostgresOptions options, MigrationType migrationType) + { + var db = GetDatabaseName(options, migrationType); + var connectionString = GetConnectionString(options); + using var conn = new NpgsqlConnection(connectionString); + conn.Open(); + + using var cmd = conn.CreateCommand(); + cmd.CommandText = $"DROP DATABASE \"{db}\" WITH (FORCE);"; + cmd.ExecuteNonQuery(); + } + + private static string GetConnectionString(PostgresOptions options) + { + var builder = new NpgsqlConnectionStringBuilder() + { + Host = options.Host, + Port = options.Port, + Username = options.User, + Password = options.Password, + Enlist = false + }; + + return builder.ConnectionString; + } + + private static string GetDatabaseName(PostgresOptions options, MigrationType migrationType) + { + return migrationType switch + { + MigrationType.Main => options.MainDb, + MigrationType.Log => options.LogDb, + _ => throw new NotImplementedException("Unknown migration type") + }; + } + } +} diff --git a/src/NzbDrone.Test.Common/Datastore/SqliteDatabase.cs b/src/NzbDrone.Test.Common/Datastore/SqliteDatabase.cs new file mode 100644 index 000000000..151e245fc --- /dev/null +++ b/src/NzbDrone.Test.Common/Datastore/SqliteDatabase.cs @@ -0,0 +1,14 @@ +using System.IO; +using NUnit.Framework; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Test.Common.Datastore +{ + public static class SqliteDatabase + { + public static string GetCachedDb(MigrationType type) + { + return Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_{type}.db"); + } + } +} diff --git a/src/NzbDrone.Test.Common/NzbDroneRunner.cs b/src/NzbDrone.Test.Common/NzbDroneRunner.cs index 639931cb3..fbeef901f 100644 --- a/src/NzbDrone.Test.Common/NzbDroneRunner.cs +++ b/src/NzbDrone.Test.Common/NzbDroneRunner.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.Threading; @@ -9,7 +10,9 @@ using NUnit.Framework; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Common.Processes; +using NzbDrone.Common.Serializer; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Datastore; using RestSharp; namespace NzbDrone.Test.Common @@ -23,13 +26,15 @@ namespace NzbDrone.Test.Common public string AppData { get; private set; } public string ApiKey { get; private set; } + public PostgresOptions PostgresOptions { get; private set; } public int Port { get; private set; } - public NzbDroneRunner(Logger logger, int port = 8686) + public NzbDroneRunner(Logger logger, PostgresOptions postgresOptions, int port = 8686) { _processProvider = new ProcessProvider(logger); _restClient = new RestClient($"http://localhost:{port}/api/v1"); + PostgresOptions = postgresOptions; Port = port; } @@ -136,12 +141,25 @@ namespace NzbDrone.Test.Common TestBase.DeleteTempFolder(AppData); } - private void Start(string outputNzbdroneConsoleExe) + private void Start(string outputLidarrConsoleExe) { - TestContext.Progress.WriteLine("Starting instance from {0} on port {1}", outputNzbdroneConsoleExe, Port); + StringDictionary envVars = new (); + if (PostgresOptions?.Host != null) + { + envVars.Add("Lidarr__Postgres__Host", PostgresOptions.Host); + envVars.Add("Lidarr__Postgres__Port", PostgresOptions.Port.ToString()); + envVars.Add("Lidarr__Postgres__User", PostgresOptions.User); + envVars.Add("Lidarr__Postgres__Password", PostgresOptions.Password); + envVars.Add("Lidarr__Postgres__MainDb", PostgresOptions.MainDb); + envVars.Add("Lidarr__Postgres__LogDb", PostgresOptions.LogDb); + + TestContext.Progress.WriteLine("Using env vars:\n{0}", envVars.ToJson()); + } + + TestContext.Progress.WriteLine("Starting instance from {0} on port {1}", outputLidarrConsoleExe, Port); var args = "-nobrowser -nosingleinstancecheck -data=\"" + AppData + "\""; - _nzbDroneProcess = _processProvider.Start(outputNzbdroneConsoleExe, args, null, OnOutputDataReceived, OnOutputDataReceived); + _nzbDroneProcess = _processProvider.Start(outputLidarrConsoleExe, args, envVars, OnOutputDataReceived, OnOutputDataReceived); } private void OnOutputDataReceived(string data) diff --git a/src/postgres.runsettings b/src/postgres.runsettings new file mode 100644 index 000000000..2285b4395 --- /dev/null +++ b/src/postgres.runsettings @@ -0,0 +1,11 @@ + + + + + 192.168.100.5 + 5432 + abc + abc + + + From c006b66aa470616ec30994d0b95e3bd307aa82bd Mon Sep 17 00:00:00 2001 From: Robin Dadswell <19610103+RobinDadswell@users.noreply.github.com> Date: Mon, 18 Jul 2022 14:57:15 +0100 Subject: [PATCH 0067/1478] Fixed: Postgres timezone issues Co-authored-by: ta264 (cherry picked from commit d55864f86914199aa0c4ee37df1e42e6ad71ef4f) --- src/NuGet.config | 1 + .../Datastore/DatabaseFixture.cs | 14 +++++ ...date_timestamp_columns_to_with_timezone.cs | 56 +++++++++++++++++++ src/NzbDrone.Core/Lidarr.Core.csproj | 6 +- 4 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 src/NzbDrone.Core/Datastore/Migration/061_postgres_update_timestamp_columns_to_with_timezone.cs diff --git a/src/NuGet.config b/src/NuGet.config index 111d40b89..05dc7873f 100644 --- a/src/NuGet.config +++ b/src/NuGet.config @@ -8,5 +8,6 @@ + diff --git a/src/NzbDrone.Core.Test/Datastore/DatabaseFixture.cs b/src/NzbDrone.Core.Test/Datastore/DatabaseFixture.cs index f6c182660..e5222ca0f 100644 --- a/src/NzbDrone.Core.Test/Datastore/DatabaseFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/DatabaseFixture.cs @@ -27,6 +27,20 @@ namespace NzbDrone.Core.Test.Datastore Mocker.Resolve().Vacuum(); } + [Test] + public void postgres_should_not_contain_timestamp_without_timezone_columns() + { + if (Db.DatabaseType != DatabaseType.PostgreSQL) + { + return; + } + + Mocker.Resolve() + .OpenConnection().Query("SELECT table_name, column_name, data_type FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = 'public' AND data_type = 'timestamp without time zone'") + .Should() + .BeNullOrEmpty(); + } + [Test] public void get_version() { diff --git a/src/NzbDrone.Core/Datastore/Migration/061_postgres_update_timestamp_columns_to_with_timezone.cs b/src/NzbDrone.Core/Datastore/Migration/061_postgres_update_timestamp_columns_to_with_timezone.cs new file mode 100644 index 000000000..642cdfc02 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/061_postgres_update_timestamp_columns_to_with_timezone.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(061)] + public class postgres_update_timestamp_columns_to_with_timezone : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("AlbumReleases").AlterColumn("ReleaseDate").AsDateTimeOffset().NotNullable(); + Alter.Table("Albums").AlterColumn("LastInfoSync").AsDateTimeOffset().Nullable(); + Alter.Table("Albums").AlterColumn("ReleaseDate").AsDateTimeOffset().Nullable(); + Alter.Table("Albums").AlterColumn("Added").AsDateTimeOffset().Nullable(); + Alter.Table("Artists").AlterColumn("LastInfoSync").AsDateTimeOffset().Nullable(); + Alter.Table("Artists").AlterColumn("Added").AsDateTimeOffset().Nullable(); + Alter.Table("Blocklist").AlterColumn("Date").AsDateTimeOffset().NotNullable(); + Alter.Table("Blocklist").AlterColumn("PublishedDate").AsDateTimeOffset().Nullable(); + Alter.Table("Commands").AlterColumn("QueuedAt").AsDateTimeOffset().NotNullable(); + Alter.Table("Commands").AlterColumn("StartedAt").AsDateTimeOffset().Nullable(); + Alter.Table("Commands").AlterColumn("EndedAt").AsDateTimeOffset().Nullable(); + Alter.Table("DownloadClientStatus").AlterColumn("InitialFailure").AsDateTimeOffset().Nullable(); + Alter.Table("DownloadClientStatus").AlterColumn("MostRecentFailure").AsDateTimeOffset().Nullable(); + Alter.Table("DownloadClientStatus").AlterColumn("DisabledTill").AsDateTimeOffset().Nullable(); + Alter.Table("DownloadHistory").AlterColumn("Date").AsDateTimeOffset().NotNullable(); + Alter.Table("ExtraFiles").AlterColumn("Added").AsDateTimeOffset().NotNullable(); + Alter.Table("ExtraFiles").AlterColumn("LastUpdated").AsDateTimeOffset().NotNullable(); + Alter.Table("History").AlterColumn("Date").AsDateTimeOffset().NotNullable(); + Alter.Table("ImportListStatus").AlterColumn("InitialFailure").AsDateTimeOffset().Nullable(); + Alter.Table("ImportListStatus").AlterColumn("MostRecentFailure").AsDateTimeOffset().Nullable(); + Alter.Table("ImportListStatus").AlterColumn("DisabledTill").AsDateTimeOffset().Nullable(); + Alter.Table("IndexerStatus").AlterColumn("InitialFailure").AsDateTimeOffset().Nullable(); + Alter.Table("IndexerStatus").AlterColumn("MostRecentFailure").AsDateTimeOffset().Nullable(); + Alter.Table("IndexerStatus").AlterColumn("DisabledTill").AsDateTimeOffset().Nullable(); + Alter.Table("LyricFiles").AlterColumn("LastUpdated").AsDateTimeOffset().NotNullable(); + Alter.Table("LyricFiles").AlterColumn("Added").AsDateTimeOffset().Nullable(); + Alter.Table("MetadataFiles").AlterColumn("LastUpdated").AsDateTimeOffset().NotNullable(); + Alter.Table("MetadataFiles").AlterColumn("Added").AsDateTimeOffset().Nullable(); + Alter.Table("PendingReleases").AlterColumn("Added").AsDateTimeOffset().NotNullable(); + Alter.Table("ScheduledTasks").AlterColumn("LastExecution").AsDateTimeOffset().NotNullable(); + Alter.Table("ScheduledTasks").AlterColumn("LastStartTime").AsDateTimeOffset().Nullable(); + Alter.Table("TrackFiles").AlterColumn("DateAdded").AsDateTimeOffset().NotNullable(); + Alter.Table("TrackFiles").AlterColumn("Modified").AsDateTimeOffset().NotNullable(); + Alter.Table("VersionInfo").AlterColumn("AppliedOn").AsDateTimeOffset().Nullable(); + } + + protected override void LogDbUpgrade() + { + Alter.Table("Logs").AlterColumn("Time").AsDateTimeOffset().NotNullable(); + Alter.Table("UpdateHistory").AlterColumn("Date").AsDateTimeOffset().NotNullable(); + Alter.Table("VersionInfo").AlterColumn("AppliedOn").AsDateTimeOffset().Nullable(); + } + } +} diff --git a/src/NzbDrone.Core/Lidarr.Core.csproj b/src/NzbDrone.Core/Lidarr.Core.csproj index 5b9b9adb8..203dc1393 100644 --- a/src/NzbDrone.Core/Lidarr.Core.csproj +++ b/src/NzbDrone.Core/Lidarr.Core.csproj @@ -9,9 +9,9 @@ - - - + + + From 48d7a227f39fe6a0e0394641c2c22e512144ff7b Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 10 Oct 2022 22:30:00 -0500 Subject: [PATCH 0068/1478] New: Retry Postgres connection 3 times (with 5 second sleep) on Startup (cherry picked from commit dbca393772d7f558b45a780a6767187bf5900a23) --- src/NzbDrone.Core/Datastore/DbFactory.cs | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/NzbDrone.Core/Datastore/DbFactory.cs b/src/NzbDrone.Core/Datastore/DbFactory.cs index 1b21589a2..bd2097d1a 100644 --- a/src/NzbDrone.Core/Datastore/DbFactory.cs +++ b/src/NzbDrone.Core/Datastore/DbFactory.cs @@ -1,6 +1,7 @@ using System; using System.Data.Common; using System.Data.SQLite; +using System.Net.Sockets; using NLog; using Npgsql; using NzbDrone.Common.Disk; @@ -124,6 +125,37 @@ namespace NzbDrone.Core.Datastore throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/lidarr/faq#i-am-getting-an-error-database-disk-image-is-malformed", e, fileName); } + catch (NpgsqlException e) + { + if (e.InnerException is SocketException) + { + var retryCount = 3; + + while (true) + { + Logger.Error(e, "Failure to connect to Postgres DB, {0} retries remaining", retryCount); + + try + { + _migrationController.Migrate(connectionString, migrationContext); + } + catch (Exception ex) + { + if (--retryCount > 0) + { + System.Threading.Thread.Sleep(5000); + continue; + } + + throw new LidarrStartupException(ex, "Error creating main database"); + } + } + } + else + { + throw new LidarrStartupException(e, "Error creating main database"); + } + } catch (Exception e) { throw new LidarrStartupException(e, "Error creating main database"); From e7481fa0450330263dcc1c88cf47ac08d72314cf Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 20 Nov 2022 15:04:39 -0600 Subject: [PATCH 0069/1478] Fixed: AlbumReleases ReleaseDate should be nullable --- .../061_postgres_update_timestamp_columns_to_with_timezone.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/Datastore/Migration/061_postgres_update_timestamp_columns_to_with_timezone.cs b/src/NzbDrone.Core/Datastore/Migration/061_postgres_update_timestamp_columns_to_with_timezone.cs index 642cdfc02..274d2e389 100644 --- a/src/NzbDrone.Core/Datastore/Migration/061_postgres_update_timestamp_columns_to_with_timezone.cs +++ b/src/NzbDrone.Core/Datastore/Migration/061_postgres_update_timestamp_columns_to_with_timezone.cs @@ -10,7 +10,7 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - Alter.Table("AlbumReleases").AlterColumn("ReleaseDate").AsDateTimeOffset().NotNullable(); + Alter.Table("AlbumReleases").AlterColumn("ReleaseDate").AsDateTimeOffset().Nullable(); Alter.Table("Albums").AlterColumn("LastInfoSync").AsDateTimeOffset().Nullable(); Alter.Table("Albums").AlterColumn("ReleaseDate").AsDateTimeOffset().Nullable(); Alter.Table("Albums").AlterColumn("Added").AsDateTimeOffset().Nullable(); From d95d17b9b7a90f9d874d6652f606341152554fac Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 18 Nov 2022 22:05:34 -0800 Subject: [PATCH 0070/1478] Added SECURITY.md (cherry picked from commit 80af164385d9087a627142ca2281ae74ac0572af) (cherry picked from commit aa8e886dab3ffedc835900db39787d47dc12465c) --- SECURITY.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..765e24fbc --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,8 @@ +# Security Policy + +## Reporting a Vulnerability + +Please report (suspected) security vulnerabilities on Discord (preferred) to +any of the Servarr Dev role holders (red names) or via email: development@servarr.com. You will receive a response from +us within 72 hours. If the issue is confirmed, we will release a patch as soon +as possible depending on complexity/severity. From 58a697f81a9b97fbda9f77c3f35ca2d157c40f27 Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 21 Nov 2022 19:45:19 -0600 Subject: [PATCH 0071/1478] Improve page scrollbar Fixed: Scrolling in Firefox in small window (requires refresh) New: Style scrollbar in Firefox Fixed: Scrolling with click and drag Fixes #3088 Fixes #2706 --- .../src/Components/Form/EnhancedSelectInput.js | 2 +- frontend/src/Components/Modal/Modal.js | 2 +- frontend/src/Components/Page/PageContentBody.js | 17 ++--------------- frontend/src/Components/Tooltip/Tooltip.js | 2 +- frontend/src/Styles/Mixins/scroller.css | 3 +++ .../src/Utilities/{mobile.js => browser.js} | 4 ++++ 6 files changed, 12 insertions(+), 18 deletions(-) rename frontend/src/Utilities/{mobile.js => browser.js} (69%) diff --git a/frontend/src/Components/Form/EnhancedSelectInput.js b/frontend/src/Components/Form/EnhancedSelectInput.js index fd8e48b9b..709eb740c 100644 --- a/frontend/src/Components/Form/EnhancedSelectInput.js +++ b/frontend/src/Components/Form/EnhancedSelectInput.js @@ -12,9 +12,9 @@ import ModalBody from 'Components/Modal/ModalBody'; import Portal from 'Components/Portal'; import Scroller from 'Components/Scroller/Scroller'; import { icons, scrollDirections, sizes } from 'Helpers/Props'; +import { isMobile as isMobileUtil } from 'Utilities/browser'; import * as keyCodes from 'Utilities/Constants/keyCodes'; import getUniqueElememtId from 'Utilities/getUniqueElementId'; -import { isMobile as isMobileUtil } from 'Utilities/mobile'; import HintedSelectInputOption from './HintedSelectInputOption'; import HintedSelectInputSelectedValue from './HintedSelectInputSelectedValue'; import TextInput from './TextInput'; diff --git a/frontend/src/Components/Modal/Modal.js b/frontend/src/Components/Modal/Modal.js index 6868d70b3..2a0e56fa7 100644 --- a/frontend/src/Components/Modal/Modal.js +++ b/frontend/src/Components/Modal/Modal.js @@ -6,9 +6,9 @@ import ReactDOM from 'react-dom'; import FocusLock from 'react-focus-lock'; import ErrorBoundary from 'Components/Error/ErrorBoundary'; import { sizes } from 'Helpers/Props'; +import { isIOS } from 'Utilities/browser'; import * as keyCodes from 'Utilities/Constants/keyCodes'; import getUniqueElememtId from 'Utilities/getUniqueElementId'; -import { isIOS } from 'Utilities/mobile'; import { setScrollLock } from 'Utilities/scrollLock'; import ModalError from './ModalError'; import styles from './Modal.css'; diff --git a/frontend/src/Components/Page/PageContentBody.js b/frontend/src/Components/Page/PageContentBody.js index 69b6c79d5..1c93e575b 100644 --- a/frontend/src/Components/Page/PageContentBody.js +++ b/frontend/src/Components/Page/PageContentBody.js @@ -1,23 +1,12 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import OverlayScroller from 'Components/Scroller/OverlayScroller'; import Scroller from 'Components/Scroller/Scroller'; import { scrollDirections } from 'Helpers/Props'; -import { isMobile as isMobileUtil } from 'Utilities/mobile'; import { isLocked } from 'Utilities/scrollLock'; import styles from './PageContentBody.css'; class PageContentBody extends Component { - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - this._isMobile = isMobileUtil(); - } - // // Listeners @@ -41,10 +30,8 @@ class PageContentBody extends Component { ...otherProps } = this.props; - const ScrollerComponent = this._isMobile ? Scroller : OverlayScroller; - return ( - {children} - + ); } } diff --git a/frontend/src/Components/Tooltip/Tooltip.js b/frontend/src/Components/Tooltip/Tooltip.js index 1867efe68..1499e7451 100644 --- a/frontend/src/Components/Tooltip/Tooltip.js +++ b/frontend/src/Components/Tooltip/Tooltip.js @@ -5,7 +5,7 @@ import { Manager, Popper, Reference } from 'react-popper'; import Portal from 'Components/Portal'; import { kinds, tooltipPositions } from 'Helpers/Props'; import dimensions from 'Styles/Variables/dimensions'; -import { isMobile as isMobileUtil } from 'Utilities/mobile'; +import { isMobile as isMobileUtil } from 'Utilities/browser'; import styles from './Tooltip.css'; let maxWidth = null; diff --git a/frontend/src/Styles/Mixins/scroller.css b/frontend/src/Styles/Mixins/scroller.css index 09d26d083..29b2016b9 100644 --- a/frontend/src/Styles/Mixins/scroller.css +++ b/frontend/src/Styles/Mixins/scroller.css @@ -1,4 +1,7 @@ @define-mixin scrollbar { + scrollbar-color: var(--scrollbarBackgroundColor) transparent; + scrollbar-width: thin; + &::-webkit-scrollbar { width: 10px; height: 10px; diff --git a/frontend/src/Utilities/mobile.js b/frontend/src/Utilities/browser.js similarity index 69% rename from frontend/src/Utilities/mobile.js rename to frontend/src/Utilities/browser.js index e52975963..ff896e801 100644 --- a/frontend/src/Utilities/mobile.js +++ b/frontend/src/Utilities/browser.js @@ -10,3 +10,7 @@ export function isMobile() { export function isIOS() { return mobileDetect.is('iOS'); } + +export function isFirefox() { + return window.navigator.userAgent.toLowerCase().indexOf('firefox/') >= 0; +} From 52fc5ae1eae2f3b85a548107359e8208a0b4f8a5 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 20 Nov 2022 19:09:29 -0800 Subject: [PATCH 0072/1478] Publish ApplicationStartingEvent during startup Fixes #3102 (cherry picked from commit 5400bce1295bdc4198d2cfe0b9258bbb7ccf0852) --- src/NzbDrone.Host/Startup.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Host/Startup.cs b/src/NzbDrone.Host/Startup.cs index 70408c945..84e187c46 100644 --- a/src/NzbDrone.Host/Startup.cs +++ b/src/NzbDrone.Host/Startup.cs @@ -25,6 +25,9 @@ using NzbDrone.Common.Serializer; using NzbDrone.Core.Configuration; using NzbDrone.Core.Datastore; using NzbDrone.Core.Instrumentation; +using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Host; using NzbDrone.Host.AccessControl; using NzbDrone.Http.Authentication; using NzbDrone.SignalR; @@ -215,7 +218,8 @@ namespace NzbDrone.Host IConfigFileProvider configFileProvider, IRuntimeInfo runtimeInfo, IFirewallAdapter firewallAdapter, - LidarrErrorPipeline errorHandler) + LidarrErrorPipeline errorHandler, + IEventAggregator eventAggregator) { initializeLogger.Initialize(); appFolderFactory.Register(); @@ -236,6 +240,8 @@ namespace NzbDrone.Host Console.CancelKeyPress += (sender, eventArgs) => NLog.LogManager.Configuration = null; } + eventAggregator.PublishEvent(new ApplicationStartingEvent()); + if (OsInfo.IsWindows && runtimeInfo.IsAdmin) { firewallAdapter.MakeAccessible(); From c02b66ec44b51125e39949f7e7e83cbdd93337df Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 21 Nov 2022 19:51:00 -0600 Subject: [PATCH 0073/1478] Enforce comment spacing with Stylecop Fixes #3104 --- .editorconfig | 1 - src/Lidarr.Api.V1/Albums/AlbumResource.cs | 2 +- src/Lidarr.Api.V1/Artist/ArtistController.cs | 20 +++--- src/Lidarr.Api.V1/Artist/ArtistResource.cs | 14 ++--- .../Calendar/CalendarController.cs | 2 +- .../Calendar/CalendarFeedController.cs | 2 +- .../Config/HostConfigResource.cs | 4 +- src/Lidarr.Api.V1/Config/UiConfigResource.cs | 4 +- .../FileSystem/FileSystemController.cs | 2 +- src/Lidarr.Api.V1/History/HistoryResource.cs | 6 +- src/Lidarr.Api.V1/Indexers/ReleaseResource.cs | 8 +-- .../ManualImport/ManualImportResource.cs | 2 +- src/Lidarr.Api.V1/ProviderResource.cs | 2 +- .../Qualities/QualityDefinitionController.cs | 2 +- src/Lidarr.Api.V1/Tracks/TrackResource.cs | 2 +- src/Lidarr.Api.V1/Update/UpdateResource.cs | 6 +- .../Extensions/RequestExtensions.cs | 2 +- .../PageModel/PageBase.cs | 2 +- .../DiskTests/DiskTransferServiceFixture.cs | 2 +- .../CleanseLogMessageFixture.cs | 2 +- .../ServiceProviderFixture.cs | 2 +- .../EnvironmentInfo/RuntimeInfo.cs | 2 +- .../Extensions/PathExtensions.cs | 4 +- src/NzbDrone.Common/OAuth/OAuthTools.cs | 2 +- .../Datastore/SortKeyValidationFixture.cs | 2 +- .../HistorySpecificationFixture.cs | 2 +- .../DownloadClientFixtureBase.cs | 18 +++--- src/NzbDrone.Core.Test/FluentTest.cs | 16 ++--- .../IndexerTests/NyaaTests/NyaaFixture.cs | 2 +- .../SkyHook/SkyHookProxyFixture.cs | 2 +- .../AlbumMonitoredServiceFixture.cs | 4 +- .../MonitorNewAlbumServiceFixture.cs | 4 +- .../FileNameBuilderTests/TitleTheFixture.cs | 2 +- .../ParserTests/HashedReleaseFixture.cs | 2 +- .../ParserTests/MusicParserFixture.cs | 36 +++++------ .../ParserTests/ParserFixture.cs | 28 ++++----- .../ParserTests/PathParserFixture.cs | 12 ++-- .../ParserTests/QualityParserFixture.cs | 14 ++--- .../ParserTests/ReleaseGroupParserFixture.cs | 8 +-- .../Metadata/MetadataProfileServiceFixture.cs | 4 +- .../Profiles/ProfileServiceFixture.cs | 4 +- .../Configuration/ConfigFileProvider.cs | 4 +- .../Configuration/IConfigService.cs | 20 +++--- src/NzbDrone.Core/Datastore/Database.cs | 2 +- .../Migration/Framework/SqliteSchemaDumper.cs | 2 +- .../DecisionEngine/DownloadDecisionMaker.cs | 12 ++-- .../AcceptableSizeSpecification.cs | 8 +-- .../Download/Clients/Aria2/Aria2.cs | 2 +- .../Blackhole/TorrentBlackholeSettings.cs | 2 +- .../Download/Clients/Deluge/DelugeProxy.cs | 4 +- .../Download/Clients/Pneumatic/Pneumatic.cs | 2 +- .../Download/Clients/Sabnzbd/SabnzbdProxy.cs | 2 +- .../Clients/rTorrent/RTorrentProxy.cs | 2 +- .../Download/CompletedDownloadService.cs | 4 +- .../Download/Pending/PendingRelease.cs | 2 +- .../Download/Pending/PendingReleaseService.cs | 6 +- .../Download/ProcessDownloadDecisions.cs | 6 +- .../TrackedDownloadService.cs | 2 +- .../TrackedDownloadStatusMessage.cs | 2 +- .../Consumers/Roksbox/RoksboxMetadata.cs | 4 +- .../Metadata/Consumers/Wdtv/WdtvMetadata.cs | 2 +- .../Extras/Metadata/MetadataService.cs | 2 +- .../History/EntityHistoryService.cs | 6 +- .../Http/HttpProxySettingsProvider.cs | 2 +- .../Definitions/SearchCriteriaBase.cs | 2 +- .../Indexers/FileList/FileListParser.cs | 2 +- .../Instrumentation/DatabaseTarget.cs | 2 +- .../Instrumentation/ReconfigureLogging.cs | 8 +-- .../DownloadedAlbumsCommandService.cs | 2 +- .../MediaFiles/MediaFileAttributeService.cs | 2 +- .../MediaFiles/MediaFileRepository.cs | 2 +- .../TrackImport/Identification/Munkres.cs | 50 +++++++-------- .../TrackImport/ImportApprovedTracks.cs | 4 +- .../Messaging/Events/EventAggregator.cs | 2 +- .../MetadataSource/SkyHook/SkyHookProxy.cs | 2 +- src/NzbDrone.Core/Music/Model/Album.cs | 2 +- src/NzbDrone.Core/Music/Model/Artist.cs | 2 +- .../Music/Repositories/AlbumRepository.cs | 2 +- .../Music/Repositories/TrackRepository.cs | 2 +- .../Music/Services/AlbumCutoffService.cs | 2 +- .../Music/Services/AlbumService.cs | 2 +- .../Notifications/Discord/Discord.cs | 2 +- .../Notifications/Discord/DiscordSettings.cs | 2 +- .../MediaBrowser/MediaBrowserProxy.cs | 2 +- .../Notifications/NotificationService.cs | 2 +- .../Pushover/PushoverSettings.cs | 2 +- .../Notifications/Telegram/TelegramService.cs | 2 +- .../Notifications/Twitter/TwitterSettings.cs | 2 +- .../Organizer/FileNameBuilder.cs | 16 ++--- .../Organizer/FileNameValidation.cs | 2 +- .../Organizer/FileNameValidationService.cs | 16 ++--- src/NzbDrone.Core/Parser/IsoLanguages.cs | 4 +- .../Parser/Model/ParsedTrackInfo.cs | 2 +- src/NzbDrone.Core/Parser/Parser.cs | 62 +++++++++---------- src/NzbDrone.Core/Parser/QualityParser.cs | 10 +-- src/NzbDrone.Core/Parser/SceneChecker.cs | 4 +- .../RootFolders/RootFolderService.cs | 2 +- .../ThingiProvider/IProviderRepository.cs | 2 +- .../ThingiProvider/ProviderFactory.cs | 2 +- .../AccessControl/FirewallAdapter.cs | 2 +- .../ApiTests/ReleaseFixture.cs | 10 +-- src/NzbDrone.Test.Common/LoggingTest.cs | 4 +- 102 files changed, 299 insertions(+), 300 deletions(-) diff --git a/.editorconfig b/.editorconfig index 1cdc7b36e..b208caf04 100644 --- a/.editorconfig +++ b/.editorconfig @@ -42,7 +42,6 @@ csharp_style_var_elsewhere = true:suggestion # Stylecop Rules dotnet_diagnostic.SA0001.severity = none -dotnet_diagnostic.SA1005.severity = none dotnet_diagnostic.SA1025.severity = none dotnet_diagnostic.SA1101.severity = none dotnet_diagnostic.SA1116.severity = none diff --git a/src/Lidarr.Api.V1/Albums/AlbumResource.cs b/src/Lidarr.Api.V1/Albums/AlbumResource.cs index 92ced5726..06548e334 100644 --- a/src/Lidarr.Api.V1/Albums/AlbumResource.cs +++ b/src/Lidarr.Api.V1/Albums/AlbumResource.cs @@ -47,7 +47,7 @@ namespace Lidarr.Api.V1.Albums public AddAlbumOptions AddOptions { get; set; } public string RemoteCover { get; set; } - //Hiding this so people don't think its usable (only used to set the initial state) + // Hiding this so people don't think its usable (only used to set the initial state) [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public bool Grabbed { get; set; } } diff --git a/src/Lidarr.Api.V1/Artist/ArtistController.cs b/src/Lidarr.Api.V1/Artist/ArtistController.cs index 1c185a35b..23b481451 100644 --- a/src/Lidarr.Api.V1/Artist/ArtistController.cs +++ b/src/Lidarr.Api.V1/Artist/ArtistController.cs @@ -117,7 +117,7 @@ namespace Lidarr.Api.V1.Artist FetchAndLinkArtistStatistics(resource); LinkNextPreviousAlbums(resource); - //PopulateAlternateTitles(resource); + // PopulateAlternateTitles(resource); LinkRootFolderPath(resource); return resource; @@ -143,7 +143,7 @@ namespace Lidarr.Api.V1.Artist LinkArtistStatistics(artistsResources, artistStats); artistsResources.ForEach(LinkRootFolderPath); - //PopulateAlternateTitles(seriesResources); + // PopulateAlternateTitles(seriesResources); return artistsResources; } @@ -235,22 +235,22 @@ namespace Lidarr.Api.V1.Artist resource.Statistics = artistStatistics.ToResource(); } - //private void PopulateAlternateTitles(List resources) - //{ + // private void PopulateAlternateTitles(List resources) + // { // foreach (var resource in resources) // { // PopulateAlternateTitles(resource); // } - //} + // } - //private void PopulateAlternateTitles(ArtistResource resource) - //{ + // private void PopulateAlternateTitles(ArtistResource resource) + // { // var mappings = _sceneMappingService.FindByTvdbId(resource.TvdbId); - // if (mappings == null) return; + // if (mappings == null) return; - // resource.AlternateTitles = mappings.Select(v => new AlternateTitleResource { Title = v.Title, SeasonNumber = v.SeasonNumber, SceneSeasonNumber = v.SceneSeasonNumber }).ToList(); - //} + // resource.AlternateTitles = mappings.Select(v => new AlternateTitleResource { Title = v.Title, SeasonNumber = v.SeasonNumber, SceneSeasonNumber = v.SceneSeasonNumber }).ToList(); + // } private void LinkRootFolderPath(ArtistResource resource) { resource.RootFolderPath = _rootFolderService.GetBestRootFolderPath(resource.Path); diff --git a/src/Lidarr.Api.V1/Artist/ArtistResource.cs b/src/Lidarr.Api.V1/Artist/ArtistResource.cs index 4d6977104..5797a3bfa 100644 --- a/src/Lidarr.Api.V1/Artist/ArtistResource.cs +++ b/src/Lidarr.Api.V1/Artist/ArtistResource.cs @@ -11,9 +11,9 @@ namespace Lidarr.Api.V1.Artist { public class ArtistResource : RestResource { - //Todo: Sorters should be done completely on the client - //Todo: Is there an easy way to keep IgnoreArticlesWhenSorting in sync between, Series, History, Missing? - //Todo: We should get the entire Profile instead of ID and Name separately + // Todo: Sorters should be done completely on the client + // Todo: Is there an easy way to keep IgnoreArticlesWhenSorting in sync between, Series, History, Missing? + // Todo: We should get the entire Profile instead of ID and Name separately [JsonIgnore] public int ArtistMetadataId { get; set; } public ArtistStatusType Status { get; set; } @@ -39,12 +39,12 @@ namespace Lidarr.Api.V1.Artist public string RemotePoster { get; set; } - //View & Edit + // View & Edit public string Path { get; set; } public int QualityProfileId { get; set; } public int MetadataProfileId { get; set; } - //Editing Only + // Editing Only public bool Monitored { get; set; } public NewItemMonitorTypes MonitorNewItems { get; set; } @@ -76,7 +76,7 @@ namespace Lidarr.Api.V1.Artist ArtistName = model.Name, - //AlternateTitles + // AlternateTitles SortName = model.SortName, Status = model.Metadata.Value.Status, @@ -133,7 +133,7 @@ namespace Lidarr.Api.V1.Artist Type = resource.ArtistType }, - //AlternateTitles + // AlternateTitles SortName = resource.SortName, Path = resource.Path, QualityProfileId = resource.QualityProfileId, diff --git a/src/Lidarr.Api.V1/Calendar/CalendarController.cs b/src/Lidarr.Api.V1/Calendar/CalendarController.cs index af29f0f31..d499607be 100644 --- a/src/Lidarr.Api.V1/Calendar/CalendarController.cs +++ b/src/Lidarr.Api.V1/Calendar/CalendarController.cs @@ -28,7 +28,7 @@ namespace Lidarr.Api.V1.Calendar [HttpGet] public List GetCalendar(DateTime? start, DateTime? end, bool unmonitored = false, bool includeArtist = false) { - //TODO: Add Album Image support to AlbumControllerWithSignalR + // TODO: Add Album Image support to AlbumControllerWithSignalR var includeAlbumImages = Request.GetBooleanQueryParameter("includeAlbumImages"); var startUse = start ?? DateTime.Today; diff --git a/src/Lidarr.Api.V1/Calendar/CalendarFeedController.cs b/src/Lidarr.Api.V1/Calendar/CalendarFeedController.cs index 89159a1fc..0637a0967 100644 --- a/src/Lidarr.Api.V1/Calendar/CalendarFeedController.cs +++ b/src/Lidarr.Api.V1/Calendar/CalendarFeedController.cs @@ -61,7 +61,7 @@ namespace Lidarr.Api.V1.Calendar var occurrence = calendar.Create(); occurrence.Uid = "Lidarr_album_" + album.Id; - //occurrence.Status = album.HasFile ? EventStatus.Confirmed : EventStatus.Tentative; + // occurrence.Status = album.HasFile ? EventStatus.Confirmed : EventStatus.Tentative; occurrence.Description = album.Overview; occurrence.Categories = album.Genres; diff --git a/src/Lidarr.Api.V1/Config/HostConfigResource.cs b/src/Lidarr.Api.V1/Config/HostConfigResource.cs index ba4504be8..cf0178018 100644 --- a/src/Lidarr.Api.V1/Config/HostConfigResource.cs +++ b/src/Lidarr.Api.V1/Config/HostConfigResource.cs @@ -59,8 +59,8 @@ namespace Lidarr.Api.V1.Config AuthenticationMethod = model.AuthenticationMethod, AnalyticsEnabled = model.AnalyticsEnabled, - //Username - //Password + // Username + // Password LogLevel = model.LogLevel, ConsoleLogLevel = model.ConsoleLogLevel, Branch = model.Branch, diff --git a/src/Lidarr.Api.V1/Config/UiConfigResource.cs b/src/Lidarr.Api.V1/Config/UiConfigResource.cs index d972ea390..de443c0e4 100644 --- a/src/Lidarr.Api.V1/Config/UiConfigResource.cs +++ b/src/Lidarr.Api.V1/Config/UiConfigResource.cs @@ -5,11 +5,11 @@ namespace Lidarr.Api.V1.Config { public class UiConfigResource : RestResource { - //Calendar + // Calendar public int FirstDayOfWeek { get; set; } public string CalendarWeekColumnHeader { get; set; } - //Dates + // Dates public string ShortDateFormat { get; set; } public string LongDateFormat { get; set; } public string TimeFormat { get; set; } diff --git a/src/Lidarr.Api.V1/FileSystem/FileSystemController.cs b/src/Lidarr.Api.V1/FileSystem/FileSystemController.cs index 948d3b76b..353f06ad5 100644 --- a/src/Lidarr.Api.V1/FileSystem/FileSystemController.cs +++ b/src/Lidarr.Api.V1/FileSystem/FileSystemController.cs @@ -36,7 +36,7 @@ namespace Lidarr.Api.V1.FileSystem return new { type = "file" }; } - //Return folder even if it doesn't exist on disk to avoid leaking anything from the UI about the underlying system + // Return folder even if it doesn't exist on disk to avoid leaking anything from the UI about the underlying system return new { type = "folder" }; } diff --git a/src/Lidarr.Api.V1/History/HistoryResource.cs b/src/Lidarr.Api.V1/History/HistoryResource.cs index eca074ae3..c34ad98e7 100644 --- a/src/Lidarr.Api.V1/History/HistoryResource.cs +++ b/src/Lidarr.Api.V1/History/HistoryResource.cs @@ -48,7 +48,7 @@ namespace Lidarr.Api.V1.History SourceTitle = model.SourceTitle, Quality = model.Quality, - //QualityCutoffNotMet + // QualityCutoffNotMet Date = model.Date, DownloadId = model.DownloadId, @@ -56,8 +56,8 @@ namespace Lidarr.Api.V1.History Data = model.Data - //Episode - //Series + // Episode + // Series }; } } diff --git a/src/Lidarr.Api.V1/Indexers/ReleaseResource.cs b/src/Lidarr.Api.V1/Indexers/ReleaseResource.cs index de363cec7..d9a17c4b1 100644 --- a/src/Lidarr.Api.V1/Indexers/ReleaseResource.cs +++ b/src/Lidarr.Api.V1/Indexers/ReleaseResource.cs @@ -51,12 +51,12 @@ namespace Lidarr.Api.V1.Indexers // Sent when queuing an unknown release [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - // [JsonIgnore] + // [JsonIgnore] public int? ArtistId { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - // [JsonIgnore] + // [JsonIgnore] public int? AlbumId { get; set; } } @@ -75,7 +75,7 @@ namespace Lidarr.Api.V1.Indexers Guid = releaseInfo.Guid, Quality = parsedAlbumInfo.Quality, - //QualityWeight + // QualityWeight Age = releaseInfo.Age, AgeHours = releaseInfo.AgeHours, AgeMinutes = releaseInfo.AgeMinutes, @@ -98,7 +98,7 @@ namespace Lidarr.Api.V1.Indexers InfoUrl = releaseInfo.InfoUrl, DownloadAllowed = remoteAlbum.DownloadAllowed, - //ReleaseWeight + // ReleaseWeight PreferredWordScore = remoteAlbum.PreferredWordScore, MagnetUrl = torrentInfo.MagnetUrl, diff --git a/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs b/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs index 8470ae13b..960e59262 100644 --- a/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs +++ b/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs @@ -51,7 +51,7 @@ namespace Lidarr.Api.V1.ManualImport Tracks = model.Tracks.ToResource(), Quality = model.Quality, - //QualityWeight + // QualityWeight DownloadId = model.DownloadId, Rejections = model.Rejections, AudioTags = model.Tags, diff --git a/src/Lidarr.Api.V1/ProviderResource.cs b/src/Lidarr.Api.V1/ProviderResource.cs index 444d64d53..b2d501a27 100644 --- a/src/Lidarr.Api.V1/ProviderResource.cs +++ b/src/Lidarr.Api.V1/ProviderResource.cs @@ -38,7 +38,7 @@ namespace Lidarr.Api.V1 Tags = definition.Tags, Fields = SchemaBuilder.ToSchema(definition.Settings), - //lidarr/supported is an disambagation page. the # should be a header on the page with appropriate details/link + // lidarr/supported is an disambagation page. the # should be a header on the page with appropriate details/link InfoLink = string.Format("https://wiki.servarr.com/lidarr/supported#{0}", definition.Implementation.ToLower()) }; diff --git a/src/Lidarr.Api.V1/Qualities/QualityDefinitionController.cs b/src/Lidarr.Api.V1/Qualities/QualityDefinitionController.cs index cf36f422e..9d7a261cf 100644 --- a/src/Lidarr.Api.V1/Qualities/QualityDefinitionController.cs +++ b/src/Lidarr.Api.V1/Qualities/QualityDefinitionController.cs @@ -40,7 +40,7 @@ namespace Lidarr.Api.V1.Qualities [HttpPut("update")] public object UpdateMany([FromBody] List resource) { - //Read from request + // Read from request var qualityDefinitions = resource .ToModel() .ToList(); diff --git a/src/Lidarr.Api.V1/Tracks/TrackResource.cs b/src/Lidarr.Api.V1/Tracks/TrackResource.cs index 4540b74ea..47f58811d 100644 --- a/src/Lidarr.Api.V1/Tracks/TrackResource.cs +++ b/src/Lidarr.Api.V1/Tracks/TrackResource.cs @@ -27,7 +27,7 @@ namespace Lidarr.Api.V1.Tracks public ArtistResource Artist { get; set; } public Ratings Ratings { get; set; } - //Hiding this so people don't think its usable (only used to set the initial state) + // Hiding this so people don't think its usable (only used to set the initial state) [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public bool Grabbed { get; set; } } diff --git a/src/Lidarr.Api.V1/Update/UpdateResource.cs b/src/Lidarr.Api.V1/Update/UpdateResource.cs index 18b07537a..ab62ff870 100644 --- a/src/Lidarr.Api.V1/Update/UpdateResource.cs +++ b/src/Lidarr.Api.V1/Update/UpdateResource.cs @@ -40,9 +40,9 @@ namespace Lidarr.Api.V1.Update FileName = model.FileName, Url = model.Url, - //Installed - //Installable - //Latest + // Installed + // Installable + // Latest Changes = model.Changes, Hash = model.Hash, }; diff --git a/src/Lidarr.Http/Extensions/RequestExtensions.cs b/src/Lidarr.Http/Extensions/RequestExtensions.cs index a614dd839..7fe891dec 100644 --- a/src/Lidarr.Http/Extensions/RequestExtensions.cs +++ b/src/Lidarr.Http/Extensions/RequestExtensions.cs @@ -15,7 +15,7 @@ namespace Lidarr.Http.Extensions // See src/Lidarr.Api.V1/Queue/QueueModule.cs private static readonly HashSet VALID_SORT_KEYS = new HashSet(StringComparer.OrdinalIgnoreCase) { - "artists.sortname", //Workaround authors table properties not being added on isValidSortKey call + "artists.sortname", // Workaround authors table properties not being added on isValidSortKey call "timeleft", "estimatedCompletionTime", "protocol", diff --git a/src/NzbDrone.Automation.Test/PageModel/PageBase.cs b/src/NzbDrone.Automation.Test/PageModel/PageBase.cs index fbb0bcf43..32bfe2477 100644 --- a/src/NzbDrone.Automation.Test/PageModel/PageBase.cs +++ b/src/NzbDrone.Automation.Test/PageModel/PageBase.cs @@ -29,7 +29,7 @@ namespace NzbDrone.Automation.Test.PageModel public void WaitForNoSpinner(int timeout = 30) { - //give the spinner some time to show up. + // give the spinner some time to show up. Thread.Sleep(200); var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(timeout)); diff --git a/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs b/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs index d966e419c..542138ca1 100644 --- a/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs +++ b/src/NzbDrone.Common.Test/DiskTests/DiskTransferServiceFixture.cs @@ -395,7 +395,7 @@ namespace NzbDrone.Common.Test.DiskTests var destination = new DirectoryInfo(GetTempFilePath()); Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy); - //Delete Random File + // Delete Random File destination.GetFiles("*.*", SearchOption.AllDirectories).First().Delete(); Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy); diff --git a/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs b/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs index 0230c09eb..2f598b971 100644 --- a/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs +++ b/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs @@ -18,7 +18,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests [TestCase(@"http://127.0.0.1:9117/dl/indexername?jackett_apikey=flwjiefewklfjacketmySecretsdfldskjfsdlk&path=we0re9f0sdfbase64sfdkfjsdlfjk&file=The+Torrent+File+Name.torrent")] [TestCase(@"http://nzb.su/getnzb/2b51db35e1912ffc138825a12b9933d2.nzb&i=37292&r=2b51db35e1910123321025a12b9933d2")] - //Indexer Responses + // Indexer Responses [TestCase(@"""download"":""https:\/\/avistaz.to\/rss\/download\/2b51db35e1910123321025a12b9933d2\/tb51db35e1910123321025a12b9933d2.torrent"",")] [TestCase(@",""info_hash"":""2b51db35e1910123321025a12b9933d2"",")] [TestCase(@",""rsskey"":""2b51db35e1910123321025a12b9933d2"",")] diff --git a/src/NzbDrone.Common.Test/ServiceProviderFixture.cs b/src/NzbDrone.Common.Test/ServiceProviderFixture.cs index fd48c49ce..0c9b0beb2 100644 --- a/src/NzbDrone.Common.Test/ServiceProviderFixture.cs +++ b/src/NzbDrone.Common.Test/ServiceProviderFixture.cs @@ -14,7 +14,7 @@ namespace NzbDrone.Common.Test [TestFixture] public class ServiceProviderFixture : TestBase { - private const string ALWAYS_INSTALLED_SERVICE = "SCardSvr"; //Smart Card + private const string ALWAYS_INSTALLED_SERVICE = "SCardSvr"; // Smart Card private const string TEMP_SERVICE_NAME = "NzbDrone_Nunit"; [SetUp] diff --git a/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs b/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs index dd47136a0..6751df66a 100644 --- a/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs +++ b/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs @@ -205,7 +205,7 @@ namespace NzbDrone.Common.EnvironmentInfo private static bool InternalIsOfficialBuild() { - //Official builds will never have such a high revision + // Official builds will never have such a high revision if (BuildInfo.Version.Major >= 10 || BuildInfo.Version.Revision > 10000) { return false; diff --git a/src/NzbDrone.Common/Extensions/PathExtensions.cs b/src/NzbDrone.Common/Extensions/PathExtensions.cs index 07e67f5f0..3911f6eb5 100644 --- a/src/NzbDrone.Common/Extensions/PathExtensions.cs +++ b/src/NzbDrone.Common/Extensions/PathExtensions.cs @@ -38,7 +38,7 @@ namespace NzbDrone.Common.Extensions public static string CleanFilePathBasic(this string path) { - //UNC + // UNC if (OsInfo.IsWindows && path.StartsWith(@"\\")) { return path.TrimEnd('/', '\\', ' '); @@ -167,7 +167,7 @@ namespace NzbDrone.Common.Extensions var parentDirInfo = dirInfo.Parent; if (parentDirInfo == null) { - //Drive letter + // Drive letter return dirInfo.Name.ToUpper(); } diff --git a/src/NzbDrone.Common/OAuth/OAuthTools.cs b/src/NzbDrone.Common/OAuth/OAuthTools.cs index fb012bd1a..ef8a8a343 100644 --- a/src/NzbDrone.Common/OAuth/OAuthTools.cs +++ b/src/NzbDrone.Common/OAuth/OAuthTools.cs @@ -256,7 +256,7 @@ namespace NzbDrone.Common.OAuth sb.Append(!basic && !secure ? qualified : ""); sb.Append(url.AbsolutePath); - return sb.ToString(); //.ToLower(); + return sb.ToString(); // .ToLower(); } /// diff --git a/src/NzbDrone.Core.Test/Datastore/SortKeyValidationFixture.cs b/src/NzbDrone.Core.Test/Datastore/SortKeyValidationFixture.cs index 2fe925548..9b6af6687 100644 --- a/src/NzbDrone.Core.Test/Datastore/SortKeyValidationFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/SortKeyValidationFixture.cs @@ -17,7 +17,7 @@ namespace NzbDrone.Core.Test.Datastore TableMapping.Mapper.IsValidSortKey(sortKey).Should().BeFalse(); } - //[TestCase("artists.sortName")] TODO: Figure out why Artists table properties don't get mapped + // [TestCase("artists.sortName")] TODO: Figure out why Artists table properties don't get mapped [TestCase("Id")] [TestCase("id")] [TestCase("commands.id")] diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs index 42c9dc1a5..514b0b178 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs @@ -109,7 +109,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue(); } - // [Test] + // [Test] // public void should_return_true_if_latest_history_has_a_download_id_and_cdh_is_enabled() // { // GivenMostRecentForEpisode(FIRST_EPISODE_ID, "test", _notupgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed); diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs index 3915cddac..48464e8e9 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs @@ -72,8 +72,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests VerifyIdentifiable(downloadClientItem); downloadClientItem.RemainingSize.Should().NotBe(0); - //downloadClientItem.RemainingTime.Should().NotBe(TimeSpan.Zero); - //downloadClientItem.OutputPath.Should().NotBeNullOrEmpty(); + // downloadClientItem.RemainingTime.Should().NotBe(TimeSpan.Zero); + // downloadClientItem.OutputPath.Should().NotBeNullOrEmpty(); downloadClientItem.Status.Should().Be(DownloadItemStatus.Queued); } @@ -83,8 +83,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests downloadClientItem.RemainingSize.Should().NotBe(0); - //downloadClientItem.RemainingTime.Should().NotBe(TimeSpan.Zero); - //downloadClientItem.OutputPath.Should().NotBeNullOrEmpty(); + // downloadClientItem.RemainingTime.Should().NotBe(TimeSpan.Zero); + // downloadClientItem.OutputPath.Should().NotBeNullOrEmpty(); downloadClientItem.Status.Should().Be(DownloadItemStatus.Paused); } @@ -94,8 +94,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests downloadClientItem.RemainingSize.Should().NotBe(0); - //downloadClientItem.RemainingTime.Should().NotBe(TimeSpan.Zero); - //downloadClientItem.OutputPath.Should().NotBeNullOrEmpty(); + // downloadClientItem.RemainingTime.Should().NotBe(TimeSpan.Zero); + // downloadClientItem.OutputPath.Should().NotBeNullOrEmpty(); downloadClientItem.Status.Should().Be(DownloadItemStatus.Downloading); } @@ -103,8 +103,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests { VerifyIdentifiable(downloadClientItem); - //downloadClientItem.RemainingTime.Should().NotBe(TimeSpan.Zero); - //downloadClientItem.OutputPath.Should().NotBeNullOrEmpty(); + // downloadClientItem.RemainingTime.Should().NotBe(TimeSpan.Zero); + // downloadClientItem.OutputPath.Should().NotBeNullOrEmpty(); downloadClientItem.Status.Should().Be(DownloadItemStatus.Downloading); } @@ -116,7 +116,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests downloadClientItem.RemainingSize.Should().Be(0); downloadClientItem.RemainingTime.Should().Be(TimeSpan.Zero); - //downloadClientItem.OutputPath.Should().NotBeNullOrEmpty(); + // downloadClientItem.OutputPath.Should().NotBeNullOrEmpty(); downloadClientItem.Status.Should().Be(DownloadItemStatus.Completed); } diff --git a/src/NzbDrone.Core.Test/FluentTest.cs b/src/NzbDrone.Core.Test/FluentTest.cs index a17f142e3..df4adae3a 100644 --- a/src/NzbDrone.Core.Test/FluentTest.cs +++ b/src/NzbDrone.Core.Test/FluentTest.cs @@ -89,7 +89,7 @@ namespace NzbDrone.Core.Test { var result = new List().MaxOrDefault(); - //Resolve + // Resolve result.Should().Be(0); } @@ -100,7 +100,7 @@ namespace NzbDrone.Core.Test var result = list.MaxOrDefault(); - //Resolve + // Resolve result.Should().Be(10); } @@ -111,7 +111,7 @@ namespace NzbDrone.Core.Test var result = list.MaxOrDefault(); - //Resolve + // Resolve result.Should().Be(0); } @@ -122,7 +122,7 @@ namespace NzbDrone.Core.Test var resultString = str.Truncate(1000); - //Resolve + // Resolve var result = new UTF8Encoding().GetBytes(resultString); result.Length.Should().BeLessOrEqualTo(1000); } @@ -134,7 +134,7 @@ namespace NzbDrone.Core.Test var resultString = str.Truncate(1000); - //Resolve + // Resolve var result = new UTF8Encoding().GetBytes(resultString); result.Length.Should().Be(11); } @@ -144,7 +144,7 @@ namespace NzbDrone.Core.Test { var result = new List().MinOrDefault(); - //Resolve + // Resolve result.Should().Be(0); } @@ -155,7 +155,7 @@ namespace NzbDrone.Core.Test var result = list.MinOrDefault(); - //Resolve + // Resolve result.Should().Be(3); } @@ -166,7 +166,7 @@ namespace NzbDrone.Core.Test var result = list.MinOrDefault(); - //Resolve + // Resolve result.Should().Be(0); } diff --git a/src/NzbDrone.Core.Test/IndexerTests/NyaaTests/NyaaFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/NyaaTests/NyaaFixture.cs index 2d2d957d3..14e926ce2 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/NyaaTests/NyaaFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/NyaaTests/NyaaFixture.cs @@ -48,7 +48,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NyaaTests torrentInfo.CommentUrl.Should().BeNullOrEmpty(); torrentInfo.Indexer.Should().Be(Subject.Definition.Name); torrentInfo.PublishDate.Should().Be(DateTime.Parse("2014/08/14 18:10:36")); - torrentInfo.Size.Should().Be(2523293286); //2.35 GiB + torrentInfo.Size.Should().Be(2523293286); // 2.35 GiB torrentInfo.InfoHash.Should().Be(null); torrentInfo.MagnetUrl.Should().Be(null); torrentInfo.Peers.Should().Be(2 + 1); diff --git a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs index 5b818ff2e..933eb1009 100644 --- a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs +++ b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs @@ -224,7 +224,7 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook } } - //if atleast one album has title it means parse it working. + // if atleast one album has title it means parse it working. if (!idOnly) { albums.Should().Contain(c => !string.IsNullOrWhiteSpace(c.Title)); diff --git a/src/NzbDrone.Core.Test/MusicTests/AlbumMonitoredServiceTests/AlbumMonitoredServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/AlbumMonitoredServiceTests/AlbumMonitoredServiceFixture.cs index 2dc67fff2..ef5be9b57 100644 --- a/src/NzbDrone.Core.Test/MusicTests/AlbumMonitoredServiceTests/AlbumMonitoredServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/AlbumMonitoredServiceTests/AlbumMonitoredServiceFixture.cs @@ -29,11 +29,11 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumMonitoredServiceTests .With(e => e.Monitored = true) .With(e => e.ReleaseDate = DateTime.UtcNow.AddDays(-7)) - //Future + // Future .TheFirst(1) .With(e => e.ReleaseDate = DateTime.UtcNow.AddDays(7)) - //Future/TBA + // Future/TBA .TheNext(1) .With(e => e.ReleaseDate = null) .Build() diff --git a/src/NzbDrone.Core.Test/MusicTests/MonitorNewAlbumServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/MonitorNewAlbumServiceFixture.cs index 83e937e1e..e0cee525b 100644 --- a/src/NzbDrone.Core.Test/MusicTests/MonitorNewAlbumServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/MonitorNewAlbumServiceFixture.cs @@ -22,11 +22,11 @@ namespace NzbDrone.Core.Test.AlbumTests .With(e => e.Monitored = true) .With(e => e.ReleaseDate = DateTime.UtcNow.AddDays(-7)) - //Future + // Future .TheFirst(1) .With(e => e.ReleaseDate = DateTime.UtcNow.AddDays(7)) - //Future/TBA + // Future/TBA .TheNext(1) .With(e => e.ReleaseDate = null) .Build() diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/TitleTheFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/TitleTheFixture.cs index acc7440e6..1c47ef371 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/TitleTheFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/TitleTheFixture.cs @@ -69,7 +69,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests [TestCase("The Rat Pack (A&E)", "Rat Pack, The (A&E)")] [TestCase("The Climax: I (Almost) Got Away With It (2016)", "Climax- I (Almost) Got Away With It, The (2016)")] - //[TestCase("", "")] + // [TestCase("", "")] public void should_get_expected_title_back(string name, string expected) { _artist.Name = name; diff --git a/src/NzbDrone.Core.Test/ParserTests/HashedReleaseFixture.cs b/src/NzbDrone.Core.Test/ParserTests/HashedReleaseFixture.cs index df542e252..f835a7305 100644 --- a/src/NzbDrone.Core.Test/ParserTests/HashedReleaseFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/HashedReleaseFixture.cs @@ -90,7 +90,7 @@ namespace NzbDrone.Core.Test.ParserTests { var result = Parser.Parser.ParseMusicPath(path); - //result.SeriesTitle.Should().Be(title); + // result.SeriesTitle.Should().Be(title); result.Quality.Quality.Should().Be(quality); } } diff --git a/src/NzbDrone.Core.Test/ParserTests/MusicParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/MusicParserFixture.cs index 82a9473db..87f992a5c 100644 --- a/src/NzbDrone.Core.Test/ParserTests/MusicParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/MusicParserFixture.cs @@ -8,24 +8,24 @@ namespace NzbDrone.Core.Test.ParserTests [TestFixture] public class MusicParserFixture : CoreTest { - //[TestCase("___▲▲▲___")] - //[TestCase("Add N to (X)")] - //[TestCase("Animal Collective")] - //[TestCase("D12")] - //[TestCase("David Sylvian[Discography]")] - //[TestCase("Eagle-Eye Cherry")] - //[TestCase("Erlend Øye")] - //[TestCase("Adult.")] // Not sure if valid, not openable in Windows OS - //[TestCase("Maroon 5")] - //[TestCase("Moimir Papalescu & The Nihilists")] - //[TestCase("N.W.A")] - //[TestCase("oOoOO")] - //[TestCase("Panic! at the Disco")] - //[TestCase("The 5 6 7 8's")] - //[TestCase("tUnE-yArDs")] - //[TestCase("U2")] - //[TestCase("Белые Братья")] - //[TestCase("Zog Bogbean - From The Marcy Playground")] + // [TestCase("___▲▲▲___")] + // [TestCase("Add N to (X)")] + // [TestCase("Animal Collective")] + // [TestCase("D12")] + // [TestCase("David Sylvian[Discography]")] + // [TestCase("Eagle-Eye Cherry")] + // [TestCase("Erlend Øye")] + // [TestCase("Adult.")] // Not sure if valid, not openable in Windows OS + // [TestCase("Maroon 5")] + // [TestCase("Moimir Papalescu & The Nihilists")] + // [TestCase("N.W.A")] + // [TestCase("oOoOO")] + // [TestCase("Panic! at the Disco")] + // [TestCase("The 5 6 7 8's")] + // [TestCase("tUnE-yArDs")] + // [TestCase("U2")] + // [TestCase("Белые Братья")] + // [TestCase("Zog Bogbean - From The Marcy Playground")] // TODO: Rewrite this test to something that makes sense. public void should_parse_artist_names(string title) diff --git a/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs index 921d1246f..f4ab2f0ed 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ParserFixture.cs @@ -101,18 +101,18 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("VA - The Best 101 Love Ballads (2017) MP3 [192 kbps]", "VA", "The Best 101 Love Ballads")] [TestCase("ATCQ - The Love Movement 1998 2CD 192kbps RIP", "ATCQ", "The Love Movement")] - //[TestCase("A Tribe Called Quest - The Love Movement 1998 2CD [192kbps] RIP", "A Tribe Called Quest", "The Love Movement")] + // [TestCase("A Tribe Called Quest - The Love Movement 1998 2CD [192kbps] RIP", "A Tribe Called Quest", "The Love Movement")] [TestCase("Maula - Jism 2 [2012] Mp3 - 192Kbps [Extended]- TK", "Maula", "Jism 2")] [TestCase("VA - Complete Clubland - The Ultimate Ride Of Your Lfe [2014][MP3][192 kbps]", "VA", "Complete Clubland - The Ultimate Ride Of Your Lfe")] [TestCase("Complete Clubland - The Ultimate Ride Of Your Lfe [2014][MP3](192kbps)", "Complete Clubland", "The Ultimate Ride Of Your Lfe")] - //[TestCase("The Ultimate Ride Of Your Lfe [192 KBPS][2014][MP3]", "", "The Ultimate Ride Of Your Lfe")] + // [TestCase("The Ultimate Ride Of Your Lfe [192 KBPS][2014][MP3]", "", "The Ultimate Ride Of Your Lfe")] [TestCase("Gary Clark Jr - Live North America 2016 (2017) MP3 192kbps", "Gary Clark Jr", "Live North America 2016")] - //[TestCase("Beyoncé Lemonade [320] 2016 Beyonce Lemonade [320] 2016", "Beyoncé", "Lemonade")] + // [TestCase("Beyoncé Lemonade [320] 2016 Beyonce Lemonade [320] 2016", "Beyoncé", "Lemonade")] [TestCase("Childish Gambino - Awaken, My Love Album 2016 mp3 320 Kbps", "Childish Gambino", "Awaken, My Love Album")] - //[TestCase("Maluma – Felices Los 4 MP3 320 Kbps 2017 Download", "Maluma", "Felices Los 4")] + // [TestCase("Maluma – Felices Los 4 MP3 320 Kbps 2017 Download", "Maluma", "Felices Los 4")] [TestCase("Ricardo Arjona - APNEA (Single 2014) (320 kbps)", "Ricardo Arjona", "APNEA")] [TestCase("Kehlani - SweetSexySavage (Deluxe Edition) (2017) 320", "Kehlani", "SweetSexySavage")] [TestCase("Anderson Paak - Malibu (320)(2016)", "Anderson Paak", "Malibu")] @@ -125,9 +125,9 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("Armin van Buuren - A State Of Trance 810 (20.04.2017) 256 kbps", "Armin van Buuren", "A State Of Trance 810")] [TestCase("PJ Harvey - Let England Shake [mp3-256-2011][trfkad]", "PJ Harvey", "Let England Shake")] - //[TestCase("X-Men Soundtracks (2006-2014) AAC, 256 kbps", "", "")] - //[TestCase("Walk the Line Soundtrack (2005) [AAC, 256 kbps]", "", "Walk the Line Soundtrack")] - //[TestCase("Emeli Sande Next To Me (512 Kbps)", "Emeli", "Next To Me")] + // [TestCase("X-Men Soundtracks (2006-2014) AAC, 256 kbps", "", "")] + // [TestCase("Walk the Line Soundtrack (2005) [AAC, 256 kbps]", "", "Walk the Line Soundtrack")] + // [TestCase("Emeli Sande Next To Me (512 Kbps)", "Emeli", "Next To Me")] [TestCase("Kendrick Lamar - DAMN (2017) FLAC", "Kendrick Lamar", "DAMN")] [TestCase("Alicia Keys - Vault Playlist Vol. 1 (2017) [FLAC CD]", "Alicia Keys", "Vault Playlist Vol 1")] [TestCase("Gorillaz - Humanz (Deluxe) - lossless FLAC Tracks - 2017 - CDrip", "Gorillaz", "Humanz")] @@ -138,11 +138,11 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("The Rolling Stones - The Very Best Of '75-'94 (1995) {FLAC}", "The Rolling Stones", "The Very Best Of '75-'94")] [TestCase("Migos-No_Label_II-CD-FLAC-2014-FORSAKEN", "Migos", "No Label II")] - //[TestCase("ADELE 25 CD FLAC 2015 PERFECT", "Adele", "25")] + // [TestCase("ADELE 25 CD FLAC 2015 PERFECT", "Adele", "25")] [TestCase("A.I. - Sex & Robots [2007/MP3/V0(VBR)]", "A I", "Sex & Robots")] [TestCase("Jay-Z - 4:44 (Deluxe Edition) (2017) 320", "Jay-Z", "444")] - //[TestCase("Roberta Flack 2006 - The Very Best of", "Roberta Flack", "The Very Best of")] + // [TestCase("Roberta Flack 2006 - The Very Best of", "Roberta Flack", "The Very Best of")] [TestCase("VA - NOW Thats What I Call Music 96 (2017) [Mp3~Kbps]", "VA", "NOW Thats What I Call Music 96")] [TestCase("Queen - The Ultimate Best Of Queen(2011)[mp3]", "Queen", "The Ultimate Best Of Queen")] [TestCase("Little Mix - Salute [Deluxe Edition] [2013] [M4A-256]-V3nom [GLT]", "Little Mix", "Salute")] @@ -164,16 +164,16 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("(Folk Rock / Pop) Aztec Two-Step - Naked - 2017, MP3, 320 kbps", "Aztec Two-Step", "Naked")] [TestCase("(Zeuhl / Progressive Rock) [WEB] Dai Kaht - Dai Kaht - 2017, FLAC (tracks), lossless", "Dai Kaht", "Dai Kaht")] - //[TestCase("(Industrial Folk) Bumblebee(Shmely, AntiVirus) - Discography, 23 albums - 1998-2011, FLAC(image + .cue), lossless")] - //[TestCase("(Heavy Metal) Sergey Mavrin(Mavrik) - Discography(14 CD) [1998-2010], FLAC(image + .cue), lossless")] + // [TestCase("(Industrial Folk) Bumblebee(Shmely, AntiVirus) - Discography, 23 albums - 1998-2011, FLAC(image + .cue), lossless")] + // [TestCase("(Heavy Metal) Sergey Mavrin(Mavrik) - Discography(14 CD) [1998-2010], FLAC(image + .cue), lossless")] [TestCase("(Heavy Metal) [CD] Black Obelisk - Discography - 1991-2015 (36 releases, 32 CDs), FLAC(image + .cue), lossless", "Black Obelisk", "Discography", true)] - //[TestCase("(R'n'B / Soul) Moyton - One of the Sta(2014) + Ocean(2014), MP3, 320 kbps", "Moyton", "")] + // [TestCase("(R'n'B / Soul) Moyton - One of the Sta(2014) + Ocean(2014), MP3, 320 kbps", "Moyton", "")] [TestCase("(Heavy Metal) Aria - Discography(46 CD) [1985 - 2015], FLAC(image + .cue), lossless", "Aria", "Discography", true)] [TestCase("(Heavy Metal) [CD] Forces United - Discography(6 CDs), 2014-2016, FLAC(image + .cue), lossless", "Forces United", "Discography", true)] [TestCase("Gorillaz - The now now - 2018 [FLAC]", "Gorillaz", "The now now")] - //Regex Works on below, but ParseAlbumMatchCollection cleans the "..." and converts it to spaces + // Regex Works on below, but ParseAlbumMatchCollection cleans the "..." and converts it to spaces // [TestCase("Metallica - ...And Justice for All (1988) [FLAC Lossless]", "Metallica", "...And Justice for All")] public void should_parse_artist_name_and_album_title(string postTitle, string name, string title, bool discography = false) { @@ -222,7 +222,7 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("Ed Sheeran", "Divide", "Ed Sheeran ? Divide FLAC")] [TestCase("Ed Sheeran", "+", "Ed Sheeran + FLAC")] - //[TestCase("Glasvegas", @"EUPHORIC /// HEARTBREAK \\\", @"EUPHORIC /// HEARTBREAK \\\ FLAC")] // slashes not being escaped properly + // [TestCase("Glasvegas", @"EUPHORIC /// HEARTBREAK \\\", @"EUPHORIC /// HEARTBREAK \\\ FLAC")] // slashes not being escaped properly [TestCase("XXXTENTACION", "?", "XXXTENTACION ? FLAC")] [TestCase("Hey", "BŁYSK", "Hey - BŁYSK FLAC")] public void should_escape_albums(string artist, string album, string releaseTitle) diff --git a/src/NzbDrone.Core.Test/ParserTests/PathParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/PathParserFixture.cs index d8b00613e..e09486cc0 100644 --- a/src/NzbDrone.Core.Test/ParserTests/PathParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/PathParserFixture.cs @@ -29,16 +29,16 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase(@"C:\Test\Series\Season 1\02 Honor Thy Father (1080p HD).m4v", 1, 2)] [TestCase(@"C:\Test\Series\Season 1\2 Honor Thy Father (1080p HD).m4v", 1, 2)] - // [TestCase(@"C:\CSI.NY.S02E04.720p.WEB-DL.DD5.1.H.264\73696S02-04.mkv", 2, 4)] //Gets treated as S01E04 (because it gets parsed as anime) + // [TestCase(@"C:\CSI.NY.S02E04.720p.WEB-DL.DD5.1.H.264\73696S02-04.mkv", 2, 4)] //Gets treated as S01E04 (because it gets parsed as anime) public void should_parse_from_path(string path, int season, int episode) { var result = Parser.Parser.ParseMusicPath(path.AsOsAgnostic()); - //result.EpisodeNumbers.Should().HaveCount(1); - //result.SeasonNumber.Should().Be(season); - //result.EpisodeNumbers[0].Should().Be(episode); - //result.AbsoluteEpisodeNumbers.Should().BeEmpty(); - //result.FullSeason.Should().BeFalse(); + // result.EpisodeNumbers.Should().HaveCount(1); + // result.SeasonNumber.Should().Be(season); + // result.EpisodeNumbers[0].Should().Be(episode); + // result.AbsoluteEpisodeNumbers.Should().BeEmpty(); + // result.FullSeason.Should().BeFalse(); ExceptionVerification.IgnoreWarns(); } } diff --git a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs index 0dafda8d0..ee556b69d 100644 --- a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs @@ -92,7 +92,7 @@ namespace NzbDrone.Core.Test.ParserTests ParseAndVerifyQuality(title, desc, bitrate, Quality.MP3_VBR); } - //TODO Parser should look at bitrate range for quality to determine level of VBR + // TODO Parser should look at bitrate range for quality to determine level of VBR [TestCase("", "MPEG Version 1 Audio, Layer 3 VBR", 298)] [Ignore("Parser should look at bitrate range for quality to determine level of VBR")] public void should_parse_mp3_vbr_v2_quality(string title, string desc, int bitrate) @@ -260,8 +260,8 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("The Chainsmokers & Coldplay - Something Just Like This")] [TestCase("Frank Ocean Blonde 2016")] - //TODO: This should be parsed as Unknown and not MP3-96 - //[TestCase("A - NOW Thats What I Call Music 96 (2017) [Mp3~Kbps]")] + // TODO: This should be parsed as Unknown and not MP3-96 + // [TestCase("A - NOW Thats What I Call Music 96 (2017) [Mp3~Kbps]")] [TestCase("Queen - The Ultimate Best Of Queen(2011)[mp3]")] [TestCase("Maroon 5 Ft Kendrick Lamar -Dont Wanna Know MP3 2016")] public void quality_parse(string title) @@ -287,10 +287,10 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("01. Kanye West - Ultralight Beam.mp3")] [TestCase("01. Kanye West - Ultralight Beam.ogg")] - //These get detected by name as we are looking for the extensions as identifiers for release names - //[TestCase("01. Kanye West - Ultralight Beam.m4a")] - //[TestCase("01. Kanye West - Ultralight Beam.wma")] - //[TestCase("01. Kanye West - Ultralight Beam.wav")] + // These get detected by name as we are looking for the extensions as identifiers for release names + // [TestCase("01. Kanye West - Ultralight Beam.m4a")] + // [TestCase("01. Kanye West - Ultralight Beam.wma")] + // [TestCase("01. Kanye West - Ultralight Beam.wav")] public void should_parse_quality_from_extension(string title) { QualityParser.ParseQuality(title, null, 0).QualityDetectionSource.Should().Be(QualityDetectionSource.Extension); diff --git a/src/NzbDrone.Core.Test/ParserTests/ReleaseGroupParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ReleaseGroupParserFixture.cs index 24bdc6ad3..d498de1bb 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ReleaseGroupParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ReleaseGroupParserFixture.cs @@ -15,7 +15,7 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("Olafur.Arnalds-Remember-WEB-2018-ENTiTLED-postbot", "ENTiTLED")] [TestCase("Olafur.Arnalds-Remember-WEB-2018-ENTiTLED-xpost", "ENTiTLED")] - //[TestCase("", "")] + // [TestCase("", "")] public void should_parse_release_group(string title, string expected) { Parser.Parser.ParseReleaseGroup(title).Should().Be(expected); @@ -34,7 +34,7 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("Olafur.Arnalds-Remember-WEB-2018-SKGTV_English", "SKGTV")] [TestCase("Olafur.Arnalds-Remember-WEB-2018-SKGTV.English", "SKGTV")] - //[TestCase("", "")] + // [TestCase("", "")] public void should_not_include_language_in_release_group(string title, string expected) { Parser.Parser.ParseReleaseGroup(title).Should().Be(expected); @@ -60,8 +60,8 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("[Anime-Koi] Barakamon - S01E07 - A High-Grade Fish", "Anime-Koi")] [TestCase("[Anime-Koi] Kami-sama Hajimemashita 2 - 01 [h264-720p][28D54E2C]", "Anime-Koi")] - //[TestCase("Tokyo.Ghoul.02x01.013.HDTV-720p-Anime-Koi", "Anime-Koi")] - //[TestCase("", "")] + // [TestCase("Tokyo.Ghoul.02x01.013.HDTV-720p-Anime-Koi", "Anime-Koi")] + // [TestCase("", "")] public void should_parse_anime_release_groups(string title, string expected) { Parser.Parser.ParseReleaseGroup(title).Should().Be(expected); diff --git a/src/NzbDrone.Core.Test/Profiles/Metadata/MetadataProfileServiceFixture.cs b/src/NzbDrone.Core.Test/Profiles/Metadata/MetadataProfileServiceFixture.cs index 00c40579f..c680ddcec 100644 --- a/src/NzbDrone.Core.Test/Profiles/Metadata/MetadataProfileServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Profiles/Metadata/MetadataProfileServiceFixture.cs @@ -28,8 +28,8 @@ namespace NzbDrone.Core.Test.Profiles.Metadata [Test] - //This confirms that new profiles are added only if no other profiles exists. - //We don't want to keep adding them back if a user deleted them on purpose. + // This confirms that new profiles are added only if no other profiles exists. + // We don't want to keep adding them back if a user deleted them on purpose. public void Init_should_skip_if_any_profiles_already_exist() { Mocker.GetMock() diff --git a/src/NzbDrone.Core.Test/Profiles/ProfileServiceFixture.cs b/src/NzbDrone.Core.Test/Profiles/ProfileServiceFixture.cs index 4044f658f..6ee2c0938 100644 --- a/src/NzbDrone.Core.Test/Profiles/ProfileServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Profiles/ProfileServiceFixture.cs @@ -26,8 +26,8 @@ namespace NzbDrone.Core.Test.Profiles [Test] - //This confirms that new profiles are added only if no other profiles exists. - //We don't want to keep adding them back if a user deleted them on purpose. + // This confirms that new profiles are added only if no other profiles exists. + // We don't want to keep adding them back if a user deleted them on purpose. public void Init_should_skip_if_any_profiles_already_exist() { Mocker.GetMock() diff --git a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs index a69101a9d..2156381a6 100644 --- a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs +++ b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs @@ -270,13 +270,13 @@ namespace NzbDrone.Core.Configuration return valueHolder.First().Value.Trim(); } - //Save the value + // Save the value if (persist) { SetValue(key, defaultValue); } - //return the default value + // return the default value return defaultValue.ToString(); }); } diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs index 3d1be0eb1..6cef2ad88 100644 --- a/src/NzbDrone.Core/Configuration/IConfigService.cs +++ b/src/NzbDrone.Core/Configuration/IConfigService.cs @@ -12,16 +12,16 @@ namespace NzbDrone.Core.Configuration bool IsDefined(string key); - //Download Client + // Download Client string DownloadClientWorkingFolders { get; set; } int DownloadClientHistoryLimit { get; set; } - //Completed/Failed Download Handling (Download client) + // Completed/Failed Download Handling (Download client) bool EnableCompletedDownloadHandling { get; set; } bool AutoRedownloadFailed { get; set; } - //Media Management + // Media Management bool AutoUnmonitorPreviouslyDownloadedTracks { get; set; } string RecycleBin { get; set; } int RecycleBinCleanupDays { get; set; } @@ -38,18 +38,18 @@ namespace NzbDrone.Core.Configuration RescanAfterRefreshType RescanAfterRefresh { get; set; } AllowFingerprinting AllowFingerprinting { get; set; } - //Permissions (Media Management) + // Permissions (Media Management) bool SetPermissionsLinux { get; set; } string ChmodFolder { get; set; } string ChownGroup { get; set; } - //Indexers + // Indexers int Retention { get; set; } int RssSyncInterval { get; set; } int MaximumSize { get; set; } int MinimumAge { get; set; } - //UI + // UI int FirstDayOfWeek { get; set; } string CalendarWeekColumnHeader { get; set; } @@ -66,23 +66,23 @@ namespace NzbDrone.Core.Configuration bool ExpandBroadcastByDefault { get; set; } bool ExpandOtherByDefault { get; set; } - //Internal + // Internal bool CleanupMetadataImages { get; set; } string PlexClientIdentifier { get; } - //Metadata + // Metadata string MetadataSource { get; set; } WriteAudioTagsType WriteAudioTags { get; set; } bool ScrubAudioTags { get; set; } - //Forms Auth + // Forms Auth string RijndaelPassphrase { get; } string HmacPassphrase { get; } string RijndaelSalt { get; } string HmacSalt { get; } - //Proxy + // Proxy bool ProxyEnabled { get; } ProxyType ProxyType { get; } string ProxyHostname { get; } diff --git a/src/NzbDrone.Core/Datastore/Database.cs b/src/NzbDrone.Core/Datastore/Database.cs index 433fef0b6..e36241ef0 100644 --- a/src/NzbDrone.Core/Datastore/Database.cs +++ b/src/NzbDrone.Core/Datastore/Database.cs @@ -64,7 +64,7 @@ namespace NzbDrone.Core.Datastore { version = db.QueryFirstOrDefault("SHOW server_version"); - //Postgres can return extra info about operating system on version call, ignore this + // Postgres can return extra info about operating system on version call, ignore this version = Regex.Replace(version, @"\(.*?\)", ""); } catch diff --git a/src/NzbDrone.Core/Datastore/Migration/Framework/SqliteSchemaDumper.cs b/src/NzbDrone.Core/Datastore/Migration/Framework/SqliteSchemaDumper.cs index 4ae4e6e68..c46be41ee 100644 --- a/src/NzbDrone.Core/Datastore/Migration/Framework/SqliteSchemaDumper.cs +++ b/src/NzbDrone.Core/Datastore/Migration/Framework/SqliteSchemaDumper.cs @@ -206,7 +206,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework { table.Indexes = ReadIndexes(table.SchemaName, table.Name); - //table.ForeignKeys = ReadForeignKeys(table.SchemaName, table.Name); + // table.ForeignKeys = ReadForeignKeys(table.SchemaName, table.Name); } return tables; diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs index 28dea9b07..1bcdcfcbf 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs @@ -80,19 +80,19 @@ namespace NzbDrone.Core.DecisionEngine if (parsedAlbumInfo != null) { // TODO: Artist Data Augment without calling to parse title again - //if (!report.Artist.IsNullOrWhiteSpace()) - //{ + // if (!report.Artist.IsNullOrWhiteSpace()) + // { // if (parsedAlbumInfo.ArtistName.IsNullOrWhiteSpace() || _parsingService.GetArtist(parsedAlbumInfo.ArtistName) == null) // { // parsedAlbumInfo.ArtistName = report.Artist; // } - //} + // } // TODO: Replace Parsed AlbumTitle with metadata Title if Parsed AlbumTitle not a valid match - //if (!report.Album.IsNullOrWhiteSpace()) - //{ + // if (!report.Album.IsNullOrWhiteSpace()) + // { // parsedAlbumInfo.AlbumTitle = report.Album; - //} + // } if (!parsedAlbumInfo.ArtistName.IsNullOrWhiteSpace()) { var remoteAlbum = _parsingService.Map(parsedAlbumInfo, searchCriteria); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs index d19323c91..1cfc12c6d 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs @@ -48,10 +48,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications var minSize = qualityDefinition.MinSize.Value.Kilobits(); - //Multiply minSize by smallest release duration + // Multiply minSize by smallest release duration minSize = minSize * minReleaseDuration; - //If the parsed size is smaller than minSize we don't want it + // If the parsed size is smaller than minSize we don't want it if (subject.Release.Size < minSize) { var runtimeMessage = $"{minReleaseDuration}sec"; @@ -74,10 +74,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { var maxSize = qualityDefinition.MaxSize.Value.Kilobits(); - //Multiply maxSize by Album.Duration + // Multiply maxSize by Album.Duration maxSize = maxSize * maxReleaseDuration; - //If the parsed size is greater than maxSize we don't want it + // If the parsed size is greater than maxSize we don't want it if (subject.Release.Size > maxSize) { var runtimeMessage = $"{maxReleaseDuration}sec"; diff --git a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs index 0af3f4a95..5a33b736b 100644 --- a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs +++ b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs @@ -77,7 +77,7 @@ namespace NzbDrone.Core.Download.Clients.Aria2 { var firstFile = torrent.Files?.FirstOrDefault(); - //skip metadata download + // skip metadata download if (firstFile?.Path?.Contains("[METADATA]") == true) { continue; diff --git a/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackholeSettings.cs b/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackholeSettings.cs index e08d89e39..4d6135a70 100644 --- a/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackholeSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackholeSettings.cs @@ -12,7 +12,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole { public TorrentBlackholeSettingsValidator() { - //Todo: Validate that the path actually exists + // Todo: Validate that the path actually exists RuleFor(c => c.TorrentFolder).IsValidPath(); RuleFor(c => c.MagnetFileExtension).NotEmpty(); } diff --git a/src/NzbDrone.Core/Download/Clients/Deluge/DelugeProxy.cs b/src/NzbDrone.Core/Download/Clients/Deluge/DelugeProxy.cs index 370cdffb2..9dbdccb24 100644 --- a/src/NzbDrone.Core/Download/Clients/Deluge/DelugeProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/Deluge/DelugeProxy.cs @@ -82,7 +82,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge var filter = new Dictionary(); // TODO: get_torrents_status returns the files as well, which starts to cause deluge timeouts when you get enough season packs. - //var response = ProcessRequest>(settings, "core.get_torrents_status", filter, new String[0]); + // var response = ProcessRequest>(settings, "core.get_torrents_status", filter, new String[0]); var response = ProcessRequest(settings, "web.update_ui", RequiredProperties, filter); return GetTorrents(response); @@ -93,7 +93,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge var filter = new Dictionary(); filter.Add("label", label); - //var response = ProcessRequest>(settings, "core.get_torrents_status", filter, new String[0]); + // var response = ProcessRequest>(settings, "core.get_torrents_status", filter, new String[0]); var response = ProcessRequest(settings, "web.update_ui", RequiredProperties, filter); return GetTorrents(response); diff --git a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs index 02ea2968f..09e890146 100644 --- a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs +++ b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs @@ -44,7 +44,7 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic title = FileNameBuilder.CleanFileName(title); - //Save to the Pneumatic directory (The user will need to ensure its accessible by XBMC) + // Save to the Pneumatic directory (The user will need to ensure its accessible by XBMC) var nzbFile = Path.Combine(Settings.NzbFolder, title + ".nzb"); _logger.Debug("Downloading NZB from: {0} to: {1}", url, nzbFile); diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdProxy.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdProxy.cs index 3bb48d785..8ff7bd344 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdProxy.cs @@ -209,7 +209,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd if (!Json.TryDeserialize(response.Content, out result)) { - //Handle plain text responses from SAB + // Handle plain text responses from SAB result = new SabnzbdJsonError(); if (response.Content.StartsWith("error", StringComparison.InvariantCultureIgnoreCase)) diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs index fb32c0598..00f0ede06 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs @@ -55,7 +55,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent "d.ratio=", // long "d.is_open=", // long "d.is_active=", // long - "d.complete=", //long + "d.complete=", // long "d.timestamp.finished="); // long (unix timestamp) var torrents = document.XPathSelectElement("./methodResponse/params/param/value/array/data") diff --git a/src/NzbDrone.Core/Download/CompletedDownloadService.cs b/src/NzbDrone.Core/Download/CompletedDownloadService.cs index 9e0518cb1..0f27bda99 100644 --- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs +++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs @@ -142,7 +142,7 @@ namespace NzbDrone.Core.Download if (importResults.Any(c => c.Result != ImportResultType.Imported)) { - //Mark as failed to prevent further attempts at processing + // Mark as failed to prevent further attempts at processing trackedDownload.State = TrackedDownloadState.ImportFailed; statusMessages.AddRange( @@ -158,7 +158,7 @@ namespace NzbDrone.Core.Download trackedDownload.Warn(statusMessages.ToArray()); } - //Publish event to notify Album was imported incompelte + // Publish event to notify Album was imported incompelte _eventAggregator.PublishEvent(new AlbumImportIncompleteEvent(trackedDownload)); return; } diff --git a/src/NzbDrone.Core/Download/Pending/PendingRelease.cs b/src/NzbDrone.Core/Download/Pending/PendingRelease.cs index a9273ec2e..15a7cfefc 100644 --- a/src/NzbDrone.Core/Download/Pending/PendingRelease.cs +++ b/src/NzbDrone.Core/Download/Pending/PendingRelease.cs @@ -13,7 +13,7 @@ namespace NzbDrone.Core.Download.Pending public ReleaseInfo Release { get; set; } public PendingReleaseReason Reason { get; set; } - //Not persisted + // Not persisted public RemoteAlbum RemoteAlbum { get; set; } } } diff --git a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs index c521870cb..e66a09d43 100644 --- a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs +++ b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs @@ -210,7 +210,7 @@ namespace NzbDrone.Core.Download.Pending } } - //Return best quality release for each album + // Return best quality release for each album var deduped = queued.GroupBy(q => q.Album.Id).Select(g => { var artist = g.First().Artist; @@ -376,8 +376,8 @@ namespace NzbDrone.Core.Download.Pending var compare = new QualityModelComparer(profile).Compare(remoteAlbum.ParsedAlbumInfo.Quality, existingReport.RemoteAlbum.ParsedAlbumInfo.Quality); - //Only remove lower/equal quality pending releases - //It is safer to retry these releases on the next round than remove it and try to re-add it (if its still in the feed) + // Only remove lower/equal quality pending releases + // It is safer to retry these releases on the next round than remove it and try to re-add it (if its still in the feed) if (compare >= 0) { _logger.Debug("Removing previously pending release, as it was grabbed."); diff --git a/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs b/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs index c47cc44f2..ab7c67f3d 100644 --- a/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs +++ b/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs @@ -40,7 +40,7 @@ namespace NzbDrone.Core.Download var grabbed = new List(); var pending = new List(); - //var failed = new List(); + // var failed = new List(); var rejected = decisions.Where(d => d.Rejected).ToList(); var pendingAddQueue = new List>(); @@ -53,7 +53,7 @@ namespace NzbDrone.Core.Download var remoteAlbum = report.RemoteAlbum; var downloadProtocol = report.RemoteAlbum.Release.DownloadProtocol; - //Skip if already grabbed + // Skip if already grabbed if (IsAlbumProcessed(grabbed, report)) { continue; @@ -116,7 +116,7 @@ namespace NzbDrone.Core.Download internal List GetQualifiedReports(IEnumerable decisions) { - //Process both approved and temporarily rejected + // Process both approved and temporarily rejected return decisions.Where(c => (c.Approved || c.TemporarilyRejected) && c.RemoteAlbum.Albums.Any()).ToList(); } diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs index 3e20d2287..e207a1598 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs @@ -124,7 +124,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads .OrderByDescending(h => h.Date) .ToList(); - //TODO: Create release info from history and use that here, so we don't loose indexer flags! + // TODO: Create release info from history and use that here, so we don't loose indexer flags! var parsedAlbumInfo = Parser.Parser.ParseAlbumTitle(trackedDownload.DownloadItem.Title); if (parsedAlbumInfo != null) diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadStatusMessage.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadStatusMessage.cs index 5ce6532d4..a8a0e661c 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadStatusMessage.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadStatusMessage.cs @@ -19,7 +19,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads Messages = new List { message }; } - //Constructor for use when deserializing JSON + // Constructor for use when deserializing JSON public TrackedDownloadStatusMessage() { } diff --git a/src/NzbDrone.Core/Extras/Metadata/Consumers/Roksbox/RoksboxMetadata.cs b/src/NzbDrone.Core/Extras/Metadata/Consumers/Roksbox/RoksboxMetadata.cs index fb64e6d6b..0ef36d545 100644 --- a/src/NzbDrone.Core/Extras/Metadata/Consumers/Roksbox/RoksboxMetadata.cs +++ b/src/NzbDrone.Core/Extras/Metadata/Consumers/Roksbox/RoksboxMetadata.cs @@ -66,7 +66,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox RelativePath = artist.Path.GetRelativePath(path) }; - //Series and season images are both named folder.jpg, only season ones sit in season folders + // Series and season images are both named folder.jpg, only season ones sit in season folders if (Path.GetFileNameWithoutExtension(filename).Equals(parentdir.Name, StringComparison.InvariantCultureIgnoreCase)) { var seasonMatch = SeasonImagesRegex.Match(parentdir.Name); @@ -109,7 +109,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox public override MetadataFileResult ArtistMetadata(Artist artist) { - //Artist metadata is not supported + // Artist metadata is not supported return null; } diff --git a/src/NzbDrone.Core/Extras/Metadata/Consumers/Wdtv/WdtvMetadata.cs b/src/NzbDrone.Core/Extras/Metadata/Consumers/Wdtv/WdtvMetadata.cs index 609f8b6dd..1b3b34e12 100644 --- a/src/NzbDrone.Core/Extras/Metadata/Consumers/Wdtv/WdtvMetadata.cs +++ b/src/NzbDrone.Core/Extras/Metadata/Consumers/Wdtv/WdtvMetadata.cs @@ -77,7 +77,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Wdtv public override MetadataFileResult ArtistMetadata(Artist artist) { - //Artist metadata is not supported + // Artist metadata is not supported return null; } diff --git a/src/NzbDrone.Core/Extras/Metadata/MetadataService.cs b/src/NzbDrone.Core/Extras/Metadata/MetadataService.cs index a9912ee16..87a70dc8f 100644 --- a/src/NzbDrone.Core/Extras/Metadata/MetadataService.cs +++ b/src/NzbDrone.Core/Extras/Metadata/MetadataService.cs @@ -509,7 +509,7 @@ namespace NzbDrone.Core.Extras.Metadata return null; } - //Remove duplicate metadata files from DB and disk + // Remove duplicate metadata files from DB and disk foreach (var file in matchingMetadataFiles.Skip(1)) { var path = Path.Combine(artist.Path, file.RelativePath); diff --git a/src/NzbDrone.Core/History/EntityHistoryService.cs b/src/NzbDrone.Core/History/EntityHistoryService.cs index a50e296fb..4d2e8a4d0 100644 --- a/src/NzbDrone.Core/History/EntityHistoryService.cs +++ b/src/NzbDrone.Core/History/EntityHistoryService.cs @@ -100,7 +100,7 @@ namespace NzbDrone.Core.History var allHistory = _historyRepository.FindDownloadHistory(trackedDownload.TrackInfo.Artist.Id, trackedDownload.ImportedTrack.Quality); - //Find download related items for these episodes + // Find download related items for these episodes var albumsHistory = allHistory.Where(h => albumIds.Contains(h.AlbumId)).ToList(); var processedDownloadId = albumsHistory @@ -230,8 +230,8 @@ namespace NzbDrone.Core.History DownloadId = downloadId }; - //Won't have a value since we publish this event before saving to DB. - //history.Data.Add("FileId", message.ImportedEpisode.Id.ToString()); + // Won't have a value since we publish this event before saving to DB. + // history.Data.Add("FileId", message.ImportedEpisode.Id.ToString()); history.Data.Add("DroppedPath", message.TrackInfo.Path); history.Data.Add("ImportedPath", message.ImportedTrack.Path); history.Data.Add("DownloadClient", message.DownloadClientInfo.Name); diff --git a/src/NzbDrone.Core/Http/HttpProxySettingsProvider.cs b/src/NzbDrone.Core/Http/HttpProxySettingsProvider.cs index a56bcf2e6..33ed6e31d 100644 --- a/src/NzbDrone.Core/Http/HttpProxySettingsProvider.cs +++ b/src/NzbDrone.Core/Http/HttpProxySettingsProvider.cs @@ -49,7 +49,7 @@ namespace NzbDrone.Core.Http public bool ShouldProxyBeBypassed(HttpProxySettings proxySettings, HttpUri url) { - //We are utilizing the WebProxy implementation here to save us having to re-implement it. This way we use Microsofts implementation + // We are utilizing the WebProxy implementation here to save us having to re-implement it. This way we use Microsofts implementation var proxy = new WebProxy(proxySettings.Host + ":" + proxySettings.Port, proxySettings.BypassLocalAddress, proxySettings.BypassListAsArray); return proxy.IsBypassed((Uri)url); diff --git a/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs b/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs index 087087658..d69da507b 100644 --- a/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs +++ b/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs @@ -39,7 +39,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions cleanTitle = SpecialCharacter.Replace(cleanTitle, ""); cleanTitle = NonWord.Replace(cleanTitle, "+"); - //remove any repeating +s + // remove any repeating +s cleanTitle = Regex.Replace(cleanTitle, @"\+{2,}", "+"); cleanTitle = cleanTitle.RemoveAccent(); cleanTitle = cleanTitle.Trim('+', ' '); diff --git a/src/NzbDrone.Core/Indexers/FileList/FileListParser.cs b/src/NzbDrone.Core/Indexers/FileList/FileListParser.cs index 5b3425480..1770d40d3 100644 --- a/src/NzbDrone.Core/Indexers/FileList/FileListParser.cs +++ b/src/NzbDrone.Core/Indexers/FileList/FileListParser.cs @@ -34,7 +34,7 @@ namespace NzbDrone.Core.Indexers.FileList { var id = result.Id; - //if (result.FreeLeech) + // if (result.FreeLeech) torrentInfos.Add(new TorrentInfo() { Guid = $"FileList-{id}", diff --git a/src/NzbDrone.Core/Instrumentation/DatabaseTarget.cs b/src/NzbDrone.Core/Instrumentation/DatabaseTarget.cs index 0e0eee611..212f41b58 100644 --- a/src/NzbDrone.Core/Instrumentation/DatabaseTarget.cs +++ b/src/NzbDrone.Core/Instrumentation/DatabaseTarget.cs @@ -87,7 +87,7 @@ namespace NzbDrone.Core.Instrumentation var connectionString = _connectionStringFactory.LogDbConnectionString; - //TODO: Probably need more robust way to differentiate what's being used + // TODO: Probably need more robust way to differentiate what's being used if (connectionString.Contains(".db")) { WriteSqliteLog(log, connectionString); diff --git a/src/NzbDrone.Core/Instrumentation/ReconfigureLogging.cs b/src/NzbDrone.Core/Instrumentation/ReconfigureLogging.cs index 38b695ccc..38d4e627a 100644 --- a/src/NzbDrone.Core/Instrumentation/ReconfigureLogging.cs +++ b/src/NzbDrone.Core/Instrumentation/ReconfigureLogging.cs @@ -50,19 +50,19 @@ namespace NzbDrone.Core.Instrumentation var rules = LogManager.Configuration.LoggingRules; - //Console + // Console SetMinimumLogLevel(rules, "consoleLogger", minimumConsoleLogLevel); - //Log Files + // Log Files SetMinimumLogLevel(rules, "appFileInfo", minimumLogLevel <= LogLevel.Info ? LogLevel.Info : LogLevel.Off); SetMinimumLogLevel(rules, "appFileDebug", minimumLogLevel <= LogLevel.Debug ? LogLevel.Debug : LogLevel.Off); SetMinimumLogLevel(rules, "appFileTrace", minimumLogLevel <= LogLevel.Trace ? LogLevel.Trace : LogLevel.Off); SetLogRotation(); - //Log Sql + // Log Sql SqlBuilderExtensions.LogSql = _configFileProvider.LogSql; - //Sentry + // Sentry ReconfigureSentry(); LogManager.ReconfigExistingLoggers(); diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedAlbumsCommandService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedAlbumsCommandService.cs index 470061340..1a0a3e853 100644 --- a/src/NzbDrone.Core/MediaFiles/DownloadedAlbumsCommandService.cs +++ b/src/NzbDrone.Core/MediaFiles/DownloadedAlbumsCommandService.cs @@ -79,7 +79,7 @@ namespace NzbDrone.Core.MediaFiles { // Atm we don't report it as a command failure, coz that would cause the download to be failed. // Changing the message won't do a thing either, coz it will get set to 'Completed' a msec later. - //message.SetMessage("Failed to import"); + // message.SetMessage("Failed to import"); } } } diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileAttributeService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileAttributeService.cs index d3f3961c5..67b49e435 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileAttributeService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileAttributeService.cs @@ -33,7 +33,7 @@ namespace NzbDrone.Core.MediaFiles { if (OsInfo.IsWindows) { - //Wrapped in Try/Catch to prevent this from causing issues with remote NAS boxes + // Wrapped in Try/Catch to prevent this from causing issues with remote NAS boxes try { _diskProvider.InheritFolderPermissions(path); diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs index cac31f4cc..7eaaf1f5d 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs @@ -88,7 +88,7 @@ namespace NzbDrone.Core.MediaFiles public List GetUnmappedFiles() { - //x.Id == null is converted to SQL, so warning incorrect + // x.Id == null is converted to SQL, so warning incorrect #pragma warning disable CS0472 return _database.Query(new SqlBuilder(_database.DatabaseType).Select(typeof(TrackFile)) .LeftJoin((f, t) => f.Id == t.TrackFileId) diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Identification/Munkres.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Identification/Munkres.cs index 02348ee10..52590f260 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/Identification/Munkres.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Identification/Munkres.cs @@ -118,8 +118,8 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification return outp; } - //For each row of the cost matrix, find the smallest element and subtract - //it from every element in its row. When finished, Go to Step 2. + // For each row of the cost matrix, find the smallest element and subtract + // it from every element in its row. When finished, Go to Step 2. private void step_one(ref int step) { double min_in_row; @@ -144,9 +144,9 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification step = 2; } - //Find a zero (Z) in the resulting matrix. If there is no starred - //zero in its row or column, star Z. Repeat for each element in the - //matrix. Go to Step 3. + // Find a zero (Z) in the resulting matrix. If there is no starred + // zero in its row or column, star Z. Repeat for each element in the + // matrix. Go to Step 3. private void step_two(ref int step) { for (int r = 0; r < n; r++) @@ -175,9 +175,9 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification step = 3; } - //Cover each column containing a starred zero. If K columns are covered, - //the starred zeros describe a complete set of unique assignments. In this - //case, Go to DONE, otherwise, Go to Step 4. + // Cover each column containing a starred zero. If K columns are covered, + // the starred zeros describe a complete set of unique assignments. In this + // case, Go to DONE, otherwise, Go to Step 4. private void step_three(ref int step) { int colcount; @@ -211,7 +211,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification } } - //methods to support step 4 + // methods to support step 4 private void find_a_zero(ref int row, ref int col) { int r = 0; @@ -273,11 +273,11 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification } } - //Find a noncovered zero and prime it. If there is no starred zero - //in the row containing this primed zero, Go to Step 5. Otherwise, - //cover this row and uncover the column containing the starred zero. - //Continue in this manner until there are no uncovered zeros left. - //Save the smallest uncovered value and Go to Step 6. + // Find a noncovered zero and prime it. If there is no starred zero + // in the row containing this primed zero, Go to Step 5. Otherwise, + // cover this row and uncover the column containing the starred zero. + // Continue in this manner until there are no uncovered zeros left. + // Save the smallest uncovered value and Go to Step 6. private void step_four(ref int step) { int row = -1; @@ -379,13 +379,13 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification } } - //Construct a series of alternating primed and starred zeros as follows. - //Let Z0 represent the uncovered primed zero found in Step 4. Let Z1 denote - //the starred zero in the column of Z0 (if any). Let Z2 denote the primed zero - //in the row of Z1 (there will always be one). Continue until the series - //terminates at a primed zero that has no starred zero in its column. - //Unstar each starred zero of the series, star each primed zero of the series, - //erase all primes and uncover every line in the matrix. Return to Step 3. + // Construct a series of alternating primed and starred zeros as follows. + // Let Z0 represent the uncovered primed zero found in Step 4. Let Z1 denote + // the starred zero in the column of Z0 (if any). Let Z2 denote the primed zero + // in the row of Z1 (there will always be one). Continue until the series + // terminates at a primed zero that has no starred zero in its column. + // Unstar each starred zero of the series, star each primed zero of the series, + // erase all primes and uncover every line in the matrix. Return to Step 3. private void step_five(ref int step) { bool done; @@ -425,7 +425,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification step = 3; } - //methods to support step 6 + // methods to support step 6 private void find_smallest(ref double minval) { for (int r = 0; r < n; r++) @@ -443,9 +443,9 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification } } - //Add the value found in Step 4 to every element of each covered row, and subtract - //it from every element of each uncovered column. Return to Step 4 without - //altering any stars, primes, or covered lines. + // Add the value found in Step 4 to every element of each covered row, and subtract + // it from every element of each uncovered column. Return to Step 4 without + // altering any stars, primes, or covered lines. private void step_six(ref int step) { double minval = double.MaxValue; diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs index 9b769b66a..26e717acd 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs @@ -149,7 +149,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport try { - //check if already imported + // check if already imported if (importResults.SelectMany(r => r.ImportDecision.Item.Tracks) .Select(e => e.Id) .Intersect(localTrack.Tracks.Select(e => e.Id)) @@ -307,7 +307,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport } } - //Adding all the rejected decisions + // Adding all the rejected decisions importResults.AddRange(decisions.Where(c => !c.Approved) .Select(d => new ImportResult(d, d.Rejections.Select(r => r.Reason).ToArray()))); diff --git a/src/NzbDrone.Core/Messaging/Events/EventAggregator.cs b/src/NzbDrone.Core/Messaging/Events/EventAggregator.cs index e492381de..275cf8f7e 100644 --- a/src/NzbDrone.Core/Messaging/Events/EventAggregator.cs +++ b/src/NzbDrone.Core/Messaging/Events/EventAggregator.cs @@ -85,7 +85,7 @@ namespace NzbDrone.Core.Messaging.Events subscribers = target as EventSubscribers; } - //call synchronous handlers first. + // call synchronous handlers first. var handlers = subscribers._syncHandlers; foreach (var handler in handlers) diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index 3701c0509..bd4694156 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -207,7 +207,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook return new List { existingArtist }; } - var metadataProfile = _metadataProfileService.All().First().Id; //Change this to Use last Used profile? + var metadataProfile = _metadataProfileService.All().First().Id; // Change this to Use last Used profile? return new List { GetArtistInfo(searchGuid.ToString(), metadataProfile) }; } diff --git a/src/NzbDrone.Core/Music/Model/Album.cs b/src/NzbDrone.Core/Music/Model/Album.cs index 43b50aea7..582ffe241 100644 --- a/src/NzbDrone.Core/Music/Model/Album.cs +++ b/src/NzbDrone.Core/Music/Model/Album.cs @@ -57,7 +57,7 @@ namespace NzbDrone.Core.Music [MemberwiseEqualityIgnore] public LazyLoaded Artist { get; set; } - //compatibility properties with old version of Album + // compatibility properties with old version of Album [MemberwiseEqualityIgnore] [JsonIgnore] public int ArtistId diff --git a/src/NzbDrone.Core/Music/Model/Artist.cs b/src/NzbDrone.Core/Music/Model/Artist.cs index a1f840e46..eb60e2140 100644 --- a/src/NzbDrone.Core/Music/Model/Artist.cs +++ b/src/NzbDrone.Core/Music/Model/Artist.cs @@ -42,7 +42,7 @@ namespace NzbDrone.Core.Music [MemberwiseEqualityIgnore] public LazyLoaded> Albums { get; set; } - //compatibility properties + // compatibility properties [MemberwiseEqualityIgnore] public string Name { diff --git a/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs b/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs index 4dbb18f22..1678ea083 100644 --- a/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs +++ b/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs @@ -90,7 +90,7 @@ namespace NzbDrone.Core.Music return Query(s => s.ForeignAlbumId == foreignAlbumId).SingleOrDefault(); } - //x.Id == null is converted to SQL, so warning incorrect + // x.Id == null is converted to SQL, so warning incorrect #pragma warning disable CS0472 private SqlBuilder AlbumsWithoutFilesBuilder(DateTime currentTime) { diff --git a/src/NzbDrone.Core/Music/Repositories/TrackRepository.cs b/src/NzbDrone.Core/Music/Repositories/TrackRepository.cs index 75c8b7a1b..83e24a8bf 100644 --- a/src/NzbDrone.Core/Music/Repositories/TrackRepository.cs +++ b/src/NzbDrone.Core/Music/Repositories/TrackRepository.cs @@ -92,7 +92,7 @@ namespace NzbDrone.Core.Music public List TracksWithoutFiles(int albumId) { - //x.Id == null is converted to SQL, so warning incorrect + // x.Id == null is converted to SQL, so warning incorrect #pragma warning disable CS0472 return Query(Builder() .Join((t, r) => t.AlbumReleaseId == r.Id) diff --git a/src/NzbDrone.Core/Music/Services/AlbumCutoffService.cs b/src/NzbDrone.Core/Music/Services/AlbumCutoffService.cs index fc6b325bd..187be1f0c 100644 --- a/src/NzbDrone.Core/Music/Services/AlbumCutoffService.cs +++ b/src/NzbDrone.Core/Music/Services/AlbumCutoffService.cs @@ -27,7 +27,7 @@ namespace NzbDrone.Core.Music var qualitiesBelowCutoff = new List(); var profiles = _profileService.All(); - //Get all items less than the cutoff + // Get all items less than the cutoff foreach (var profile in profiles) { var cutoffIndex = profile.GetIndex(profile.Cutoff); diff --git a/src/NzbDrone.Core/Music/Services/AlbumService.cs b/src/NzbDrone.Core/Music/Services/AlbumService.cs index abce226ce..fed437aef 100644 --- a/src/NzbDrone.Core/Music/Services/AlbumService.cs +++ b/src/NzbDrone.Core/Music/Services/AlbumService.cs @@ -291,7 +291,7 @@ namespace NzbDrone.Core.Music public void Handle(ArtistsDeletedEvent message) { - //TODO Do this in one call instead of one for each artist? + // TODO Do this in one call instead of one for each artist? var albums = message.Artists.SelectMany(x => GetAlbumsByArtistMetadataId(x.ArtistMetadataId)).ToList(); DeleteMany(albums); } diff --git a/src/NzbDrone.Core/Notifications/Discord/Discord.cs b/src/NzbDrone.Core/Notifications/Discord/Discord.cs index dafaa288f..a0ccbeb12 100644 --- a/src/NzbDrone.Core/Notifications/Discord/Discord.cs +++ b/src/NzbDrone.Core/Notifications/Discord/Discord.cs @@ -396,7 +396,7 @@ namespace NzbDrone.Core.Notifications.Discord private string BytesToString(long byteCount) { - string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; //Longs run out around EB + string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; // Longs run out around EB if (byteCount == 0) { return "0 " + suf[0]; diff --git a/src/NzbDrone.Core/Notifications/Discord/DiscordSettings.cs b/src/NzbDrone.Core/Notifications/Discord/DiscordSettings.cs index a79b8d073..8bc7878d4 100644 --- a/src/NzbDrone.Core/Notifications/Discord/DiscordSettings.cs +++ b/src/NzbDrone.Core/Notifications/Discord/DiscordSettings.cs @@ -18,7 +18,7 @@ namespace NzbDrone.Core.Notifications.Discord { public DiscordSettings() { - //Set Default Fields + // Set Default Fields GrabFields = new int[] { 0, 1, 2, 3, 5, 6, 7, 8, 9 }; ImportFields = new int[] { 0, 1, 2, 3, 5, 6, 7, 8, 9 }; } diff --git a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs index f774140f9..549ac63a9 100644 --- a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs +++ b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs @@ -99,7 +99,7 @@ namespace NzbDrone.Core.Notifications.Emby { _logger.Debug("Looking for error in response: {0}", response); - //TODO: actually check for the error + // TODO: actually check for the error } public List GetArtist(MediaBrowserSettings settings) diff --git a/src/NzbDrone.Core/Notifications/NotificationService.cs b/src/NzbDrone.Core/Notifications/NotificationService.cs index 0a13400be..37f5afeeb 100644 --- a/src/NzbDrone.Core/Notifications/NotificationService.cs +++ b/src/NzbDrone.Core/Notifications/NotificationService.cs @@ -94,7 +94,7 @@ namespace NzbDrone.Core.Notifications return true; } - //TODO: this message could be more clear + // TODO: this message could be more clear _logger.Debug("{0} does not have any intersecting tags with {1}. Notification will not be sent.", definition.Name, artist.Name); return false; } diff --git a/src/NzbDrone.Core/Notifications/Pushover/PushoverSettings.cs b/src/NzbDrone.Core/Notifications/Pushover/PushoverSettings.cs index 53fa09a67..02ba85c28 100644 --- a/src/NzbDrone.Core/Notifications/Pushover/PushoverSettings.cs +++ b/src/NzbDrone.Core/Notifications/Pushover/PushoverSettings.cs @@ -26,7 +26,7 @@ namespace NzbDrone.Core.Notifications.Pushover Devices = new string[] { }; } - //TODO: Get Pushover to change our app name (or create a new app) when we have a new logo + // TODO: Get Pushover to change our app name (or create a new app) when we have a new logo [FieldDefinition(0, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpLink = "https://pushover.net/apps/clone/lidarr")] public string ApiKey { get; set; } diff --git a/src/NzbDrone.Core/Notifications/Telegram/TelegramService.cs b/src/NzbDrone.Core/Notifications/Telegram/TelegramService.cs index be0ab5da6..cc09b80ce 100644 --- a/src/NzbDrone.Core/Notifications/Telegram/TelegramService.cs +++ b/src/NzbDrone.Core/Notifications/Telegram/TelegramService.cs @@ -29,7 +29,7 @@ namespace NzbDrone.Core.Notifications.Telegram public void SendNotification(string title, string message, TelegramSettings settings) { - //Format text to add the title before and bold using markdown + // Format text to add the title before and bold using markdown var text = $"{HttpUtility.HtmlEncode(title)}\n{HttpUtility.HtmlEncode(message)}"; var requestBuilder = new HttpRequestBuilder(URL).Resource("bot{token}/sendmessage").Post(); diff --git a/src/NzbDrone.Core/Notifications/Twitter/TwitterSettings.cs b/src/NzbDrone.Core/Notifications/Twitter/TwitterSettings.cs index b0702b010..be052226e 100644 --- a/src/NzbDrone.Core/Notifications/Twitter/TwitterSettings.cs +++ b/src/NzbDrone.Core/Notifications/Twitter/TwitterSettings.cs @@ -15,7 +15,7 @@ namespace NzbDrone.Core.Notifications.Twitter RuleFor(c => c.AccessToken).NotEmpty(); RuleFor(c => c.AccessTokenSecret).NotEmpty(); - //TODO: Validate that it is a valid username (numbers, letters and underscores - I think) + // TODO: Validate that it is a valid username (numbers, letters and underscores - I think) RuleFor(c => c.Mention).NotEmpty().When(c => c.DirectMessage); RuleFor(c => c.DirectMessage).Equal(true) diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index b19210070..a6646bdac 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -61,7 +61,7 @@ namespace NzbDrone.Core.Organizer private static readonly Regex ScenifyRemoveChars = new Regex(@"(?<=\s)(,|<|>|\/|\\|;|:|'|""|\||`|~|!|\?|@|$|%|^|\*|-|_|=){1}(?=\s)|('|:|\?|,)(?=(?:(?:s|m)\s)|\s|$)|(\(|\)|\[|\]|\{|\})", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex ScenifyReplaceChars = new Regex(@"[\/]", RegexOptions.Compiled | RegexOptions.IgnoreCase); - //TODO: Support Written numbers (One, Two, etc) and Roman Numerals (I, II, III etc) + // TODO: Support Written numbers (One, Two, etc) and Roman Numerals (I, II, III etc) private static readonly Regex MultiPartCleanupRegex = new Regex(@"(?:\(\d+\)|(Part|Pt\.?)\s?\d+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly char[] TrackTitleTrimCharacters = new[] { ' ', '.', '?' }; @@ -330,12 +330,12 @@ namespace NzbDrone.Core.Organizer var qualityTitle = _qualityDefinitionService.Get(trackFile.Quality.Quality).Title; var qualityProper = GetQualityProper(trackFile.Quality); - //var qualityReal = GetQualityReal(artist, trackFile.Quality); + // var qualityReal = GetQualityReal(artist, trackFile.Quality); tokenHandlers["{Quality Full}"] = m => string.Format("{0}", qualityTitle); tokenHandlers["{Quality Title}"] = m => qualityTitle; tokenHandlers["{Quality Proper}"] = m => qualityProper; - //tokenHandlers["{Quality Real}"] = m => qualityReal; + // tokenHandlers["{Quality Real}"] = m => qualityReal; } private void AddMediaInfoTokens(Dictionary> tokenHandlers, TrackFile trackFile) @@ -496,7 +496,7 @@ namespace NzbDrone.Core.Organizer private string CleanupTrackTitle(string title) { - //this will remove (1),(2) from the end of multi part episodes. + // this will remove (1),(2) from the end of multi part episodes. return MultiPartCleanupRegex.Replace(title, string.Empty).Trim(); } @@ -515,15 +515,15 @@ namespace NzbDrone.Core.Organizer return string.Empty; } - //private string GetQualityReal(Series series, QualityModel quality) - //{ + // private string GetQualityReal(Series series, QualityModel quality) + // { // if (quality.Revision.Real > 0) // { // return "REAL"; // } - // return string.Empty; - //} + // return string.Empty; + // } private string GetOriginalTitle(TrackFile trackFile) { if (trackFile.SceneName.IsNullOrWhiteSpace()) diff --git a/src/NzbDrone.Core/Organizer/FileNameValidation.cs b/src/NzbDrone.Core/Organizer/FileNameValidation.cs index 6159fb8eb..fb59d38de 100644 --- a/src/NzbDrone.Core/Organizer/FileNameValidation.cs +++ b/src/NzbDrone.Core/Organizer/FileNameValidation.cs @@ -35,7 +35,7 @@ namespace NzbDrone.Core.Organizer ruleBuilder.SetValidator(new IllegalCharactersValidator()); return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.AlbumTitleRegex)).WithMessage("Must contain Album title"); - //.SetValidator(new RegularExpressionValidator(FileNameBuilder.ReleaseDateRegex)).WithMessage("Must contain Release year"); + // .SetValidator(new RegularExpressionValidator(FileNameBuilder.ReleaseDateRegex)).WithMessage("Must contain Release year"); } } diff --git a/src/NzbDrone.Core/Organizer/FileNameValidationService.cs b/src/NzbDrone.Core/Organizer/FileNameValidationService.cs index 606b445aa..f705d8443 100644 --- a/src/NzbDrone.Core/Organizer/FileNameValidationService.cs +++ b/src/NzbDrone.Core/Organizer/FileNameValidationService.cs @@ -15,18 +15,18 @@ namespace NzbDrone.Core.Organizer { var validationFailure = new ValidationFailure("StandardTrackFormat", ERROR_MESSAGE); - //TODO Add Validation for TrackFilename - //var parsedEpisodeInfo = Parser.Parser.ParseTitle(sampleResult.FileName); + // TODO Add Validation for TrackFilename + // var parsedEpisodeInfo = Parser.Parser.ParseTitle(sampleResult.FileName); - //if (parsedEpisodeInfo == null) - //{ + // if (parsedEpisodeInfo == null) + // { // return validationFailure; - //} + // } - //if (!ValidateSeasonAndEpisodeNumbers(sampleResult.Episodes, parsedEpisodeInfo)) - //{ + // if (!ValidateSeasonAndEpisodeNumbers(sampleResult.Episodes, parsedEpisodeInfo)) + // { // return validationFailure; - //} + // } return null; } } diff --git a/src/NzbDrone.Core/Parser/IsoLanguages.cs b/src/NzbDrone.Core/Parser/IsoLanguages.cs index b0493dd7b..b10dc7e40 100644 --- a/src/NzbDrone.Core/Parser/IsoLanguages.cs +++ b/src/NzbDrone.Core/Parser/IsoLanguages.cs @@ -48,7 +48,7 @@ namespace NzbDrone.Core.Parser if (langCode.Length == 2) { - //Lookup ISO639-1 code + // Lookup ISO639-1 code var isoLanguages = All.Where(l => l.TwoLetterCode == langCode).ToList(); if (isoArray.Length > 1) @@ -61,7 +61,7 @@ namespace NzbDrone.Core.Parser } else if (langCode.Length == 3) { - //Lookup ISO639-2T code + // Lookup ISO639-2T code return All.FirstOrDefault(l => l.ThreeLetterCode == langCode); } diff --git a/src/NzbDrone.Core/Parser/Model/ParsedTrackInfo.cs b/src/NzbDrone.Core/Parser/Model/ParsedTrackInfo.cs index 567564695..2e454a6bf 100644 --- a/src/NzbDrone.Core/Parser/Model/ParsedTrackInfo.cs +++ b/src/NzbDrone.Core/Parser/Model/ParsedTrackInfo.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.Parser.Model { public class ParsedTrackInfo { - //public int TrackNumber { get; set; } + // public int TrackNumber { get; set; } public string Title { get; set; } public string CleanTitle { get; set; } public string ArtistTitle { get; set; } diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index f2a9c9192..cf1b81bfa 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -40,89 +40,89 @@ namespace NzbDrone.Core.Parser private static readonly Regex[] ReportAlbumTitleRegex = new[] { - //ruTracker - (Genre) [Source]? Artist - Discography + // ruTracker - (Genre) [Source]? Artist - Discography new Regex(@"^(?:\(.+?\))(?:\W*(?:\[(?.+?)\]))?\W*(?.+?)(?: - )(?Discography|Discografia).+?(?\d{4}).+?(?\d{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist - Discography with two years + // Artist - Discography with two years new Regex(@"^(?.+?)(?: - )(?:.+?)?(?Discography|Discografia).+?(?\d{4}).+?(?\d{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist - Discography with end year + // Artist - Discography with end year new Regex(@"^(?.+?)(?: - )(?:.+?)?(?Discography|Discografia).+?(?\d{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist Discography with two years + // Artist Discography with two years new Regex(@"^(?.+?)\W*(?Discography|Discografia).+?(?\d{4}).+?(?\d{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist Discography with end year + // Artist Discography with end year new Regex(@"^(?.+?)\W*(?Discography|Discografia).+?(?\d{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist Discography + // Artist Discography new Regex(@"^(?.+?)\W*(?Discography|Discografia)", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //ruTracker - (Genre) [Source]? Artist - Album - Year + // ruTracker - (Genre) [Source]? Artist - Album - Year new Regex(@"^(?:\(.+?\))(?:\W*(?:\[(?.+?)\]))?\W*(?.+?)(?: - )(?.+?)(?: - )(?\d{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist-Album-Version-Source-Year - //ex. Imagine Dragons-Smoke And Mirrors-Deluxe Edition-2CD-FLAC-2015-JLM + // Artist-Album-Version-Source-Year + // ex. Imagine Dragons-Smoke And Mirrors-Deluxe Edition-2CD-FLAC-2015-JLM new Regex(@"^(?.+?)[-](?.+?)[-](?:[\(|\[]?)(?.+?(?:Edition)?)(?:[\)|\]]?)[-](?\d?CD|WEB).+?(?\d{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist-Album-Source-Year - //ex. Dani_Sbert-Togheter-WEB-2017-FURY + // Artist-Album-Source-Year + // ex. Dani_Sbert-Togheter-WEB-2017-FURY new Regex(@"^(?.+?)[-](?.+?)[-](?\d?CD|WEB).+?(?\d{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist - Album (Year) Strict + // Artist - Album (Year) Strict new Regex(@"^(?:(?.+?)(?: - )+)(?.+?)\W*(?:\(|\[).+?(?\d{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist - Album (Year) + // Artist - Album (Year) new Regex(@"^(?:(?.+?)(?: - )+)(?.+?)\W*(?:\(|\[)(?\d{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist - Album - Year [something] + // Artist - Album - Year [something] new Regex(@"^(?:(?.+?)(?: - )+)(?.+?)\W*(?: - )(?\d{4})\W*(?:\(|\[)", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist - Album [something] or Artist - Album (something) + // Artist - Album [something] or Artist - Album (something) new Regex(@"^(?:(?.+?)(?: - )+)(?.+?)\W*(?:\(|\[)", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist - Album Year + // Artist - Album Year new Regex(@"^(?:(?.+?)(?: - )+)(?.+?)\W*(?\d{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist-Album (Year) Strict - //Hyphen no space between artist and album + // Artist-Album (Year) Strict + // Hyphen no space between artist and album new Regex(@"^(?:(?.+?)(?:-)+)(?.+?)\W*(?:\(|\[).+?(?\d{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist-Album (Year) - //Hyphen no space between artist and album + // Artist-Album (Year) + // Hyphen no space between artist and album new Regex(@"^(?:(?.+?)(?:-)+)(?.+?)\W*(?:\(|\[)(?\d{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist-Album [something] or Artist-Album (something) - //Hyphen no space between artist and album + // Artist-Album [something] or Artist-Album (something) + // Hyphen no space between artist and album new Regex(@"^(?:(?.+?)(?:-)+)(?.+?)\W*(?:\(|\[)", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist-Album-something-Year + // Artist-Album-something-Year new Regex(@"^(?:(?.+?)(?:-)+)(?.+?)(?:-.+?)(?\d{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist-Album Year - //Hyphen no space between artist and album + // Artist-Album Year + // Hyphen no space between artist and album new Regex(@"^(?:(?.+?)(?:-)+)(?:(?.+?)(?:-)+)(?\d{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled), - //Artist - Year - Album + // Artist - Year - Album // Hyphen with no or more spaces between artist/album/year new Regex(@"^(?:(?.+?)(?:-))(?\d{4})(?:-)(?[^-]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled), @@ -141,16 +141,16 @@ namespace NzbDrone.Core.Parser new Regex(@"^[A-Z]{11}\d{3}$", RegexOptions.Compiled), new Regex(@"^[a-z]{12}\d{3}$", RegexOptions.Compiled), - //Backup filename (Unknown origins) + // Backup filename (Unknown origins) new Regex(@"^Backup_\d{5,}S\d{2}-\d{2}$", RegexOptions.Compiled), - //123 - Started appearing December 2014 + // 123 - Started appearing December 2014 new Regex(@"^123$", RegexOptions.Compiled), - //abc - Started appearing January 2015 + // abc - Started appearing January 2015 new Regex(@"^abc$", RegexOptions.Compiled | RegexOptions.IgnoreCase), - //b00bs - Started appearing January 2015 + // b00bs - Started appearing January 2015 new Regex(@"^b00bs$", RegexOptions.Compiled | RegexOptions.IgnoreCase) }; @@ -163,7 +163,7 @@ namespace NzbDrone.Core.Parser private static readonly Regex FileExtensionRegex = new Regex(@"\.[a-z0-9]{2,4}$", RegexOptions.IgnoreCase | RegexOptions.Compiled); - //TODO Rework this Regex for Music + // TODO Rework this Regex for Music private static readonly RegexReplace SimpleTitleRegex = new RegexReplace(@"(?:(480|720|1080|2160|320)[ip]|[xh][\W_]?26[45]|DD\W?5\W1|[<>*:|]|848x480|1280x720|1920x1080|3840x2160|4096x2160|(8|10)b(it)?)\s*", string.Empty, RegexOptions.IgnoreCase | RegexOptions.Compiled); diff --git a/src/NzbDrone.Core/Parser/QualityParser.cs b/src/NzbDrone.Core/Parser/QualityParser.cs index e6c5174c3..f95d6fee4 100644 --- a/src/NzbDrone.Core/Parser/QualityParser.cs +++ b/src/NzbDrone.Core/Parser/QualityParser.cs @@ -238,7 +238,7 @@ namespace NzbDrone.Core.Parser break; } - //Based on extension + // Based on extension if (result.Quality == Quality.Unknown && !name.ContainsInvalidPathChars()) { try @@ -248,8 +248,8 @@ namespace NzbDrone.Core.Parser } catch (ArgumentException) { - //Swallow exception for cases where string contains illegal - //path characters. + // Swallow exception for cases where string contains illegal + // path characters. } } @@ -340,7 +340,7 @@ namespace NzbDrone.Core.Parser private static BitRate ParseBitRate(string name) { - //var nameWithNoSpaces = Regex.Replace(name, @"\s+", ""); + // var nameWithNoSpaces = Regex.Replace(name, @"\s+", ""); var match = BitRateRegex.Match(name); if (!match.Success) @@ -644,7 +644,7 @@ namespace NzbDrone.Core.Parser result.Revision.Version = Convert.ToInt32(versionRegexResult.Groups["version"].Value); } - //TODO: re-enable this when we have a reliable way to determine real + // TODO: re-enable this when we have a reliable way to determine real MatchCollection realRegexResult = RealRegex.Matches(name); if (realRegexResult.Count > 0) diff --git a/src/NzbDrone.Core/Parser/SceneChecker.cs b/src/NzbDrone.Core/Parser/SceneChecker.cs index 24220d1bd..10b092bd7 100644 --- a/src/NzbDrone.Core/Parser/SceneChecker.cs +++ b/src/NzbDrone.Core/Parser/SceneChecker.cs @@ -2,8 +2,8 @@ namespace NzbDrone.Core.Parser { public static class SceneChecker { - //This method should prefer false negatives over false positives. - //It's better not to use a title that might be scene than to use one that isn't scene + // This method should prefer false negatives over false positives. + // It's better not to use a title that might be scene than to use one that isn't scene public static bool IsSceneTitle(string title) { if (!title.Contains(".")) diff --git a/src/NzbDrone.Core/RootFolders/RootFolderService.cs b/src/NzbDrone.Core/RootFolders/RootFolderService.cs index cb85af184..760467166 100644 --- a/src/NzbDrone.Core/RootFolders/RootFolderService.cs +++ b/src/NzbDrone.Core/RootFolders/RootFolderService.cs @@ -65,7 +65,7 @@ namespace NzbDrone.Core.RootFolders } } - //We don't want an exception to prevent the root folders from loading in the UI, so they can still be deleted + // We don't want an exception to prevent the root folders from loading in the UI, so they can still be deleted catch (Exception ex) { _logger.Error(ex, "Unable to get free space and unmapped folders for root folder {0}", folder.Path); diff --git a/src/NzbDrone.Core/ThingiProvider/IProviderRepository.cs b/src/NzbDrone.Core/ThingiProvider/IProviderRepository.cs index 421cc6899..7686eece1 100644 --- a/src/NzbDrone.Core/ThingiProvider/IProviderRepository.cs +++ b/src/NzbDrone.Core/ThingiProvider/IProviderRepository.cs @@ -5,6 +5,6 @@ namespace NzbDrone.Core.ThingiProvider public interface IProviderRepository : IBasicRepository where TProvider : ModelBase, new() { - // void DeleteImplementations(string implementation); + // void DeleteImplementations(string implementation); } } diff --git a/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs b/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs index dd51a9d7a..5f1b25e19 100644 --- a/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs +++ b/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs @@ -169,7 +169,7 @@ namespace NzbDrone.Core.ThingiProvider definition.Message = provider.Message; } - //TODO: Remove providers even if the ConfigContract can't be deserialized (this will fail to remove providers if the settings can't be deserialized). + // TODO: Remove providers even if the ConfigContract can't be deserialized (this will fail to remove providers if the settings can't be deserialized). private void RemoveMissingImplementations() { var storedProvider = _providerRepository.All(); diff --git a/src/NzbDrone.Host/AccessControl/FirewallAdapter.cs b/src/NzbDrone.Host/AccessControl/FirewallAdapter.cs index b8c0da4df..2cc9773e8 100644 --- a/src/NzbDrone.Host/AccessControl/FirewallAdapter.cs +++ b/src/NzbDrone.Host/AccessControl/FirewallAdapter.cs @@ -77,7 +77,7 @@ namespace NzbDrone.Host.AccessControl var netFwMgrType = Type.GetTypeFromProgID("HNetCfg.FwMgr", false); var mgr = (INetFwMgr)Activator.CreateInstance(netFwMgrType); - //Open the port for the standard profile, should help when the user has multiple network adapters + // Open the port for the standard profile, should help when the user has multiple network adapters mgr.LocalPolicy.GetProfileByType(FIREWALL_PROFILE).GloballyOpenPorts.Add(port); } catch (Exception ex) diff --git a/src/NzbDrone.Integration.Test/ApiTests/ReleaseFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/ReleaseFixture.cs index 10784f85a..c4ab09f9b 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/ReleaseFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/ReleaseFixture.cs @@ -36,8 +36,8 @@ namespace NzbDrone.Integration.Test.ApiTests // InternalServerError is caused by the Release being invalid for download (no Series). // But if it didn't accept it, it would return NotFound. // TODO: Maybe we should create a full mock Newznab server endpoint. - //var result = Releases.Post(new ReleaseResource { Guid = releases.First().Guid }); - //result.Guid.Should().Be(releases.First().Guid); + // var result = Releases.Post(new ReleaseResource { Guid = releases.First().Guid }); + // result.Guid.Should().Be(releases.First().Guid); var result = Releases.Post(new ReleaseResource { Guid = releases.First().Guid }, HttpStatusCode.InternalServerError); } @@ -49,9 +49,9 @@ namespace NzbDrone.Integration.Test.ApiTests releaseResource.DownloadUrl.Should().NotBeNullOrWhiteSpace(); releaseResource.ArtistName.Should().NotBeNullOrWhiteSpace(); - //TODO: uncomment these after moving to restsharp for rss - //releaseResource.NzbInfoUrl.Should().NotBeNullOrWhiteSpace(); - //releaseResource.Size.Should().BeGreaterThan(0); + // TODO: uncomment these after moving to restsharp for rss + // releaseResource.NzbInfoUrl.Should().NotBeNullOrWhiteSpace(); + // releaseResource.Size.Should().BeGreaterThan(0); return true; } } diff --git a/src/NzbDrone.Test.Common/LoggingTest.cs b/src/NzbDrone.Test.Common/LoggingTest.cs index 2bb704971..0c2cbe728 100644 --- a/src/NzbDrone.Test.Common/LoggingTest.cs +++ b/src/NzbDrone.Test.Common/LoggingTest.cs @@ -95,8 +95,8 @@ namespace NzbDrone.Test.Common [TearDown] public void LoggingDownBase() { - //can't use because of a bug in mono with 2.6.2, - //https://bugs.launchpad.net/nunitv2/+bug/1076932 + // can't use because of a bug in mono with 2.6.2, + // https://bugs.launchpad.net/nunitv2/+bug/1076932 if (BuildInfo.IsDebug && TestContext.CurrentContext.Result.Outcome == ResultState.Success) { ExceptionVerification.AssertNoUnexpectedLogs(); From b445d0717d8f74641b5403ccbad114ace4f6f539 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 4 Nov 2022 14:16:11 -0700 Subject: [PATCH 0074/1478] Added react-hooks lint rules (cherry picked from commit 381d64259396582de8d63ada99333e42cf5e3189) --- frontend/.eslintrc.js | 5 ++++- frontend/src/App/ApplyTheme.js | 9 +++++---- frontend/src/Components/Modal/ConfirmModal.js | 2 +- frontend/src/Settings/PendingChangesModal.js | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index b087037f4..06695aac2 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -39,6 +39,7 @@ module.exports = { plugins: [ 'filenames', 'react', + 'react-hooks', 'simple-import-sort', 'import' ], @@ -308,7 +309,9 @@ module.exports = { 'react/react-in-jsx-scope': 2, 'react/self-closing-comp': 2, 'react/sort-comp': 2, - 'react/jsx-wrap-multilines': 2 + 'react/jsx-wrap-multilines': 2, + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'error' }, overrides: [ { diff --git a/frontend/src/App/ApplyTheme.js b/frontend/src/App/ApplyTheme.js index 170b51596..7e937e586 100644 --- a/frontend/src/App/ApplyTheme.js +++ b/frontend/src/App/ApplyTheme.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import React, { Fragment, useEffect } from 'react'; +import React, { Fragment, useCallback, useEffect } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import themes from 'Styles/Themes'; @@ -19,7 +19,8 @@ function createMapStateToProps() { function ApplyTheme({ theme, children }) { // Update the CSS Variables - function updateCSSVariables() { + + const updateCSSVariables = useCallback(() => { const arrayOfVariableKeys = Object.keys(themes[theme]); const arrayOfVariableValues = Object.values(themes[theme]); @@ -31,12 +32,12 @@ function ApplyTheme({ theme, children }) { arrayOfVariableValues[index] ); }); - } + }, [theme]); // On Component Mount and Component Update useEffect(() => { updateCSSVariables(theme); - }, [theme]); + }, [updateCSSVariables, theme]); return {children}; } diff --git a/frontend/src/Components/Modal/ConfirmModal.js b/frontend/src/Components/Modal/ConfirmModal.js index a6eaf6bd7..c129f29b3 100644 --- a/frontend/src/Components/Modal/ConfirmModal.js +++ b/frontend/src/Components/Modal/ConfirmModal.js @@ -33,7 +33,7 @@ function ConfirmModal(props) { return () => unbindShortcut('enter', onConfirm); } - }, [isOpen, onConfirm]); + }, [bindShortcut, unbindShortcut, isOpen, onConfirm]); return ( { bindShortcut('enter', onConfirm); - }, [onConfirm]); + }, [bindShortcut, onConfirm]); return ( Date: Fri, 18 Nov 2022 22:47:27 -0800 Subject: [PATCH 0075/1478] Add missing eslint-plugin-react-hooks package (cherry picked from commit c43d06628d7f29ab9803055a34b571f5c0984a77) --- package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index 4fc0c1c73..d1ebbe695 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "eslint-plugin-import": "2.25.4", "eslint-plugin-json": "3.1.0", "eslint-plugin-react": "7.29.4", + "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-simple-import-sort": "7.0.0", "esprint": "3.3.0", "file-loader": "6.2.0", diff --git a/yarn.lock b/yarn.lock index 2f7a124b5..657b3aca5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2915,6 +2915,11 @@ eslint-plugin-json@3.1.0: lodash "^4.17.21" vscode-json-languageservice "^4.1.6" +eslint-plugin-react-hooks@4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" + integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== + eslint-plugin-react@7.29.4: version "7.29.4" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz#4717de5227f55f3801a5fd51a16a4fa22b5914d2" From 4585e5d274d004f82e147d658c8795686476b561 Mon Sep 17 00:00:00 2001 From: Weblate Date: Wed, 23 Nov 2022 19:09:48 +0000 Subject: [PATCH 0076/1478] Translated using Weblate (German) [skip ci] Currently translated at 95.6% (841 of 879 strings) Translated using Weblate (Czech) [skip ci] Currently translated at 66.0% (581 of 879 strings) Translated using Weblate (Hungarian) [skip ci] Currently translated at 100.0% (879 of 879 strings) Translated using Weblate (Portuguese (Brazil)) [skip ci] Currently translated at 99.8% (878 of 879 strings) Translated using Weblate (Portuguese (Brazil)) [skip ci] Currently translated at 99.8% (877 of 878 strings) Co-authored-by: Csaba Co-authored-by: Havok Dan Co-authored-by: Weblate Co-authored-by: Zalhera Co-authored-by: marapavelka Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/cs/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/de/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hu/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/ Translation: Servarr/Lidarr --- src/NzbDrone.Core/Localization/Core/cs.json | 2 +- src/NzbDrone.Core/Localization/Core/de.json | 29 ++++++++++++++++--- src/NzbDrone.Core/Localization/Core/hu.json | 3 +- .../Localization/Core/pt_BR.json | 9 +++--- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/cs.json b/src/NzbDrone.Core/Localization/Core/cs.json index f774e83d0..22c180457 100644 --- a/src/NzbDrone.Core/Localization/Core/cs.json +++ b/src/NzbDrone.Core/Localization/Core/cs.json @@ -22,7 +22,7 @@ "Cancel": "zrušení", "CancelMessageText": "Opravdu chcete zrušit tento nevyřízený úkol?", "CertificateValidation": "Ověření certifikátu", - "CertificateValidationHelpText": "Změňte, jak přísné je ověření certifikace HTTPS", + "CertificateValidationHelpText": "Změňte přísnost ověřování certifikátů HTTPS. Neměňte, pokud nerozumíte rizikům.", "ChangeFileDate": "Změnit datum souboru", "ChangeHasNotBeenSavedYet": "Změna ještě nebyla uložena", "20MinutesTwenty": "120 minut: {0}", diff --git a/src/NzbDrone.Core/Localization/Core/de.json b/src/NzbDrone.Core/Localization/Core/de.json index be4e22f68..0da485ba3 100644 --- a/src/NzbDrone.Core/Localization/Core/de.json +++ b/src/NzbDrone.Core/Localization/Core/de.json @@ -12,7 +12,7 @@ "AutoRedownloadFailedHelpText": "Automatisch nach einem anderen Release suchen", "BypassProxyForLocalAddresses": "Proxy für lokale Adressen umgehen", "Cancel": "Abbrechen", - "CertificateValidationHelpText": "Ändere wie streng die Validierung der HTTPS-Zertifizierung ist", + "CertificateValidationHelpText": "Ändere wie streng die Validierung der HTTPS-Zertifizierung ist. Ändern Sie nicht wenn Ihnen die Risiken nicht bewusst sind.", "ChangeHasNotBeenSavedYet": "Änderung wurde noch nicht gespeichert", "ChmodFolderHelpText": "Oktal, wird beim Importieren/Umbenennen von Medienordnern und Dateien angewendet (ohne Ausführungsbits)", "ChmodFolderHelpTextWarning": "Dies funktioniert nur, wenn der Benutzer, der Lidarr ausführt, der Eigentümer der Datei ist. Es ist besser, sicherzustellen, dass der Download-Client die Berechtigungen richtig setzt.", @@ -191,7 +191,7 @@ "RemoveFromDownloadClient": "Aus dem Downloader entfernen", "RemoveFromQueue": "Aus der Warteschlage entfernen", "RemoveHelpTextWarning": "Dies wird den Download und alle bereits heruntergeladenen Dateien aus dem Downloader entfernen.", - "RemoveSelectedMessageText": "Bist du icher, dass du die ausgewählten Einträge aus der Sperrliste entfernen willst?", + "RemoveSelectedMessageText": "Sind sie sicher, dass die ausgewählten Einträge aus der Sperrliste entfernt werden sollen?", "RemoveTagRemovingTag": "Tag entfernen", "RenameTracksHelpText": "Wenn das umbennen deaktiviert ist, wird der vorhandene Dateiname benutzt", "Reorder": "Neu sortieren", @@ -570,7 +570,7 @@ "ContinuingMoreAlbumsAreExpected": "Mehr Alben werden erwartet", "DefaultLidarrTags": "Standard Lidarr Tags", "DefaultMetadataProfileIdHelpText": "Standard Metadaten Profil für in diesem Ordner erkannte Künstler", - "DefaultMonitorOptionHelpText": "Standard Überwachungsoptionen für Alben von Künstlern, die in diesem Ordner gefunden werden", + "DefaultMonitorOptionHelpText": "Welche Alben sollen beim ersten Hinzufügen von Künstlern in diesem Ordner überwacht werden", "DefaultQualityProfileIdHelpText": "Standard Qualitätsprofil für Künstler, die in diesem Ordner gefunden werden", "DefaultTagsHelpText": "Standard Lidarr Tags für Künstler, die in diesem Ordner gefunden werden", "DeleteMetadataProfile": "Metadaten Profil löschen", @@ -820,5 +820,26 @@ "RestoreBackupAdditionalInfo": "Hinweis: Während der wiederherstellung wird Radarr automatisch neugestartet und die Oberfläche neugelade.", "SelectFolder": "Ordner auswählen", "AddConnection": "Sammlung bearbeiten", - "EditMetadataProfile": "Metadaten Profil" + "EditMetadataProfile": "Metadaten Profil", + "Database": "Datenbank", + "EditReleaseProfile": "Release Profile bearbeiten", + "ShouldSearchHelpText": "Dursuche Indexer nach neu hinzugefügten Einträgen. Vorsichtig benutzen bei großen Listen.", + "TrackTitle": "Track Titel", + "WriteMetadataTags": "Schreibe Metadaten Tags", + "AddReleaseProfile": "Release Profil hinzufügen", + "AlbumRelease": "Album Release", + "AlbumReleaseDate": "Album Erscheinungsdatum", + "AlbumStatus": "Album Status", + "AlbumTitle": "Album Titel", + "AlbumType": "Albumtyp", + "AreYouSure": "Sind sie sicher?", + "ArtistName": "Künstlername", + "ArtistType": "Künstlertyp", + "CombineWithExistingFiles": "Kombiniere mit existierenden Dateien", + "ContinuingOnly": "Nur fortlaufend", + "DateAdded": "Hinzugefügt Datum", + "DeleteArtist": "Lösche ausgewählten Künstler", + "Discography": "Diskographie", + "DownloadImported": "Importiere herunterladen", + "EditMetadata": "Metadaten bearbeiten" } diff --git a/src/NzbDrone.Core/Localization/Core/hu.json b/src/NzbDrone.Core/Localization/Core/hu.json index 8c82ad4d6..942909555 100644 --- a/src/NzbDrone.Core/Localization/Core/hu.json +++ b/src/NzbDrone.Core/Localization/Core/hu.json @@ -876,5 +876,6 @@ "TrackCount": "Darabszám", "TrackImported": "Szám importálva", "TrackProgress": "Szám folyamata", - "SelectAlbumRelease": "Album kiadásának kiválasztása" + "SelectAlbumRelease": "Album kiadásának kiválasztása", + "Database": "Adatbázis" } diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index dfb5b7071..f83f29d76 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -401,7 +401,7 @@ "PreferredHelpTexts3": "Uma pontuação negativa será preterida", "PreviewRename": "Visualizar renomeação", "PreviewRetag": "Visualizar adição de nova tag", - "PriorityHelpText": "Prioridade do indexador de 1 (mais alta) a 50 (mais baixa). Padrão: 25.", + "PriorityHelpText": "Prioridade do indexador de 1 (mais alta) a 50 (mais baixa). Padrão: 25. Usado ao obter lançamentos como um desempate para lançamentos iguais, o Lidarr ainda usará todos os indexadores habilitados para sincronização e pesquisa de RSS.", "NETCore": ".NET", "PropersAndRepacks": "Propers e repacks", "Protocol": "Protocolo", @@ -612,7 +612,7 @@ "TrackTitle": "Título da faixa", "Type": "Tipo", "Ungroup": "Desagrupar", - "DefaultMonitorOptionHelpText": "Opções de Monitoramento Padrão para álbuns de artistas detectados nesta pasta", + "DefaultMonitorOptionHelpText": "Quais álbuns devem ser monitorados na adição inicial para artistas detectados nesta pasta", "CollapseMultipleAlbums": "Agrupar Múltiplos Álbuns", "ContinuingMoreAlbumsAreExpected": "Mais álbuns são esperados", "RemoveCompleted": "Remover Completos", @@ -636,7 +636,7 @@ "HasMonitoredAlbumsNoMonitoredAlbumsForThisArtist": "Não monitorar álbuns para este artista", "HideAlbums": "Ocultar álbuns", "HideTracks": "Ocultar faixas", - "ImportListSettings": "Configurações da Lista de Importação", + "ImportListSettings": "Configurações Gerais da Lista de Importação", "IsExpandedHideAlbums": "Ocultar álbuns", "IsExpandedHideTracks": "Ocultar faixas", "IsExpandedShowAlbums": "Mostrar álbuns", @@ -875,5 +875,6 @@ "TrackImported": "Faixa Importada", "TrackProgress": "Progresso da Faixa", "EndedOnly": "Concluídos Somente", - "MediaCount": "Número de Mídias" + "MediaCount": "Número de Mídias", + "Database": "Banco de dados" } From 933fca7a0bc5bbb32039c5ef1764b9645bfdd10e Mon Sep 17 00:00:00 2001 From: Robin Dadswell <19610103+RobinDadswell@users.noreply.github.com> Date: Mon, 28 Nov 2022 14:38:03 +0000 Subject: [PATCH 0077/1478] Fix: Restore missing groupbys in AlbumRepo --- src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs b/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs index 1678ea083..3eb594175 100644 --- a/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs +++ b/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs @@ -101,7 +101,8 @@ namespace NzbDrone.Core.Music .LeftJoin((t, f) => t.TrackFileId == f.Id) .Where(f => f.Id == null) .Where(r => r.Monitored == true) - .Where(a => a.ReleaseDate <= currentTime); + .Where(a => a.ReleaseDate <= currentTime) + .GroupBy(x => x.Id); } #pragma warning restore CS0472 @@ -123,7 +124,8 @@ namespace NzbDrone.Core.Music .Join((r, t) => r.Id == t.AlbumReleaseId) .LeftJoin((t, f) => t.TrackFileId == f.Id) .Where(r => r.Monitored == true) - .Where(BuildQualityCutoffWhereClause(qualitiesBelowCutoff)); + .Where(BuildQualityCutoffWhereClause(qualitiesBelowCutoff)) + .GroupBy(x => x.Id); } private string BuildQualityCutoffWhereClause(List qualitiesBelowCutoff) From e9996f17835b312236e23ce224ca8bf5435b0768 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 28 Nov 2022 19:04:14 -0800 Subject: [PATCH 0078/1478] Fixed: Handle Flood reporting errors for completed and stopped downloads (cherry picked from commit f2b2eb69a3e8b271535bd92dc2f5cbfd45664daf) --- .../Download/Clients/Flood/Flood.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs b/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs index a6b47eb32..15e2d1369 100644 --- a/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs +++ b/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs @@ -124,22 +124,22 @@ namespace NzbDrone.Core.Download.Clients.Flood item.RemainingTime = TimeSpan.FromSeconds(properties.Eta); } - if (properties.Status.Contains("error")) - { - item.Status = DownloadItemStatus.Warning; - } - else if (properties.Status.Contains("seeding") || properties.Status.Contains("complete")) + if (properties.Status.Contains("seeding") || properties.Status.Contains("complete")) { item.Status = DownloadItemStatus.Completed; } - else if (properties.Status.Contains("downloading")) - { - item.Status = DownloadItemStatus.Downloading; - } else if (properties.Status.Contains("stopped")) { item.Status = DownloadItemStatus.Paused; } + else if (properties.Status.Contains("error")) + { + item.Status = DownloadItemStatus.Warning; + } + else if (properties.Status.Contains("downloading")) + { + item.Status = DownloadItemStatus.Downloading; + } if (item.Status == DownloadItemStatus.Completed) { From 980f8f0408ad189f85ca2b2081c7deec4552a0eb Mon Sep 17 00:00:00 2001 From: Qstick Date: Wed, 7 Dec 2022 21:16:40 -0600 Subject: [PATCH 0079/1478] New: Improve IPAddress.IsLocal method Co-Authored-By: ta264 (cherry picked from commit fd98a179ab6fed8037c99344b34593aac24a0ac0) --- .../Extensions/IpAddressExtensions.cs | 48 ++++++++++++------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/NzbDrone.Common/Extensions/IpAddressExtensions.cs b/src/NzbDrone.Common/Extensions/IpAddressExtensions.cs index 2b154ca52..7feb431c4 100644 --- a/src/NzbDrone.Common/Extensions/IpAddressExtensions.cs +++ b/src/NzbDrone.Common/Extensions/IpAddressExtensions.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; using System.Net.Sockets; namespace NzbDrone.Common.Extensions @@ -7,34 +7,50 @@ namespace NzbDrone.Common.Extensions { public static bool IsLocalAddress(this IPAddress ipAddress) { - if (ipAddress.IsIPv6LinkLocal) + // Map back to IPv4 if mapped to IPv6, for example "::ffff:1.2.3.4" to "1.2.3.4". + if (ipAddress.IsIPv4MappedToIPv6) { - return true; + ipAddress = ipAddress.MapToIPv4(); } + // Checks loopback ranges for both IPv4 and IPv6. if (IPAddress.IsLoopback(ipAddress)) { return true; } + // IPv4 if (ipAddress.AddressFamily == AddressFamily.InterNetwork) { - byte[] bytes = ipAddress.GetAddressBytes(); - switch (bytes[0]) - { - case 10: - case 127: - return true; - case 172: - return bytes[1] < 32 && bytes[1] >= 16; - case 192: - return bytes[1] == 168; - default: - return false; - } + return IsLocalIPv4(ipAddress.GetAddressBytes()); + } + + // IPv6 + if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) + { + return ipAddress.IsIPv6LinkLocal || + ipAddress.IsIPv6UniqueLocal || + ipAddress.IsIPv6SiteLocal; } return false; } + + private static bool IsLocalIPv4(byte[] ipv4Bytes) + { + // Link local (no IP assigned by DHCP): 169.254.0.0 to 169.254.255.255 (169.254.0.0/16) + bool IsLinkLocal() => ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254; + + // Class A private range: 10.0.0.0 – 10.255.255.255 (10.0.0.0/8) + bool IsClassA() => ipv4Bytes[0] == 10; + + // Class B private range: 172.16.0.0 – 172.31.255.255 (172.16.0.0/12) + bool IsClassB() => ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31; + + // Class C private range: 192.168.0.0 – 192.168.255.255 (192.168.0.0/16) + bool IsClassC() => ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168; + + return IsLinkLocal() || IsClassA() || IsClassC() || IsClassB(); + } } } From 73a3e3f4e32c80901f285d182fd1a6d00daa5d0c Mon Sep 17 00:00:00 2001 From: Qstick Date: Wed, 7 Dec 2022 21:18:17 -0600 Subject: [PATCH 0080/1478] Simplify X-Forwarded-For handling This happens in asp.net middleware now Co-Authored-By: ta264 (cherry picked from commit 16e2d130e6a2e7239bcfe92187a7f990f93eff00) --- .../Extensions/RequestExtensions.cs | 34 +------------------ 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/src/Lidarr.Http/Extensions/RequestExtensions.cs b/src/Lidarr.Http/Extensions/RequestExtensions.cs index 7fe891dec..146cf053f 100644 --- a/src/Lidarr.Http/Extensions/RequestExtensions.cs +++ b/src/Lidarr.Http/Extensions/RequestExtensions.cs @@ -162,39 +162,7 @@ namespace Lidarr.Http.Extensions remoteIP = remoteIP.MapToIPv4(); } - var remoteAddress = remoteIP.ToString(); - - // Only check if forwarded by a local network reverse proxy - if (remoteIP.IsLocalAddress()) - { - var realIPHeader = request.Headers["X-Real-IP"]; - if (realIPHeader.Any()) - { - return realIPHeader.First().ToString(); - } - - var forwardedForHeader = request.Headers["X-Forwarded-For"]; - if (forwardedForHeader.Any()) - { - // Get the first address that was forwarded by a local IP to prevent remote clients faking another proxy - foreach (var forwardedForAddress in forwardedForHeader.SelectMany(v => v.Split(',')).Select(v => v.Trim()).Reverse()) - { - if (!IPAddress.TryParse(forwardedForAddress, out remoteIP)) - { - return remoteAddress; - } - - if (!remoteIP.IsLocalAddress()) - { - return forwardedForAddress; - } - - remoteAddress = forwardedForAddress; - } - } - } - - return remoteAddress; + return remoteIP.ToString(); } public static void DisableCache(this IHeaderDictionary headers) From 92d885dd78eb025f4f95d4a7bd236cd7c3ea630f Mon Sep 17 00:00:00 2001 From: Zak Saunders Date: Mon, 5 Dec 2022 07:58:24 +0000 Subject: [PATCH 0081/1478] Fixup Theme CSS --- frontend/src/Album/Details/AlbumDetailsMedium.css | 4 ++-- frontend/src/AlbumStudio/AlbumStudioAlbum.css | 4 ++-- .../Artist/Index/Banners/ArtistIndexBannerInfo.css | 2 +- .../src/Artist/Index/Posters/ArtistIndexPoster.css | 4 ++-- .../Artist/Index/Posters/ArtistIndexPosterInfo.css | 2 +- .../src/Search/Album/AddNewAlbumSearchResult.css | 2 +- .../src/Search/Artist/AddNewArtistSearchResult.css | 6 ++++-- frontend/src/Styles/Themes/dark.js | 12 ++++++------ frontend/src/Styles/Themes/light.js | 12 ++++++------ frontend/src/TrackFile/ExpandingFileDetails.css | 2 +- 10 files changed, 26 insertions(+), 24 deletions(-) diff --git a/frontend/src/Album/Details/AlbumDetailsMedium.css b/frontend/src/Album/Details/AlbumDetailsMedium.css index 865d84a0a..c488fa4a5 100644 --- a/frontend/src/Album/Details/AlbumDetailsMedium.css +++ b/frontend/src/Album/Details/AlbumDetailsMedium.css @@ -2,7 +2,7 @@ margin-bottom: 20px; border: 1px solid var(--borderColor); border-radius: 4px; - background-color: var(--white); + background-color: var(--modalBackgroundColor); &:last-of-type { margin-bottom: 0; @@ -81,7 +81,7 @@ border-top: 1px solid var(--borderColor); border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; - background-color: #fafafa; + background-color: var(--cardBackgroundColor); text-align: center; } diff --git a/frontend/src/AlbumStudio/AlbumStudioAlbum.css b/frontend/src/AlbumStudio/AlbumStudioAlbum.css index d412e1d49..791bb9f9b 100644 --- a/frontend/src/AlbumStudio/AlbumStudioAlbum.css +++ b/frontend/src/AlbumStudio/AlbumStudioAlbum.css @@ -5,7 +5,7 @@ margin: 2px 4px; border: 1px solid var(--borderColor); border-radius: 4px; - background-color: var(--seasonBackgroundColor); + background-color: var(--albumBackgroundColor); cursor: default; } @@ -24,7 +24,7 @@ .tracks { padding: 0 4px; - background-color: var(--episodesBackgroundColor); + background-color: var(--trackBackgroundColor); color: var(--defaultColor); } diff --git a/frontend/src/Artist/Index/Banners/ArtistIndexBannerInfo.css b/frontend/src/Artist/Index/Banners/ArtistIndexBannerInfo.css index 17741773d..22ed2d528 100644 --- a/frontend/src/Artist/Index/Banners/ArtistIndexBannerInfo.css +++ b/frontend/src/Artist/Index/Banners/ArtistIndexBannerInfo.css @@ -1,5 +1,5 @@ .info { - background-color: var(--seriesBackgroundColor); + background-color: var(--artistBackgroundColor); text-align: center; font-size: $smallFontSize; } diff --git a/frontend/src/Artist/Index/Posters/ArtistIndexPoster.css b/frontend/src/Artist/Index/Posters/ArtistIndexPoster.css index 8e691c74b..33f81db64 100644 --- a/frontend/src/Artist/Index/Posters/ArtistIndexPoster.css +++ b/frontend/src/Artist/Index/Posters/ArtistIndexPoster.css @@ -44,7 +44,7 @@ $hoverScale: 1.05; } .nextAiring { - background-color: var(--seriesBackgroundColor); + background-color: var(--artistBackgroundColor); text-align: center; font-size: $smallFontSize; } @@ -52,7 +52,7 @@ $hoverScale: 1.05; .title { @add-mixin truncate; - background-color: var(--seriesBackgroundColor); + background-color: var(--artistBackgroundColor); text-align: center; font-size: $smallFontSize; } diff --git a/frontend/src/Artist/Index/Posters/ArtistIndexPosterInfo.css b/frontend/src/Artist/Index/Posters/ArtistIndexPosterInfo.css index aab27d827..22ed2d528 100644 --- a/frontend/src/Artist/Index/Posters/ArtistIndexPosterInfo.css +++ b/frontend/src/Artist/Index/Posters/ArtistIndexPosterInfo.css @@ -1,5 +1,5 @@ .info { - background-color: #fafbfc; + background-color: var(--artistBackgroundColor); text-align: center; font-size: $smallFontSize; } diff --git a/frontend/src/Search/Album/AddNewAlbumSearchResult.css b/frontend/src/Search/Album/AddNewAlbumSearchResult.css index 28ad1f2d1..5af11bac0 100644 --- a/frontend/src/Search/Album/AddNewAlbumSearchResult.css +++ b/frontend/src/Search/Album/AddNewAlbumSearchResult.css @@ -9,7 +9,7 @@ .underlay { @add-mixin cover; - background-color: var(--addSeriesBackgroundColor); + background-color: var(--addArtistBackgroundColor); transition: background 500ms; &:hover { diff --git a/frontend/src/Search/Artist/AddNewArtistSearchResult.css b/frontend/src/Search/Artist/AddNewArtistSearchResult.css index 3ff77ddc7..b3be02d45 100644 --- a/frontend/src/Search/Artist/AddNewArtistSearchResult.css +++ b/frontend/src/Search/Artist/AddNewArtistSearchResult.css @@ -9,13 +9,15 @@ .underlay { @add-mixin cover; - background-color: var(--white); + background-color: var(--addArtistBackgroundColor); transition: background 500ms; &:hover { - background-color: #eaf2ff; + background-color: var(--pageBackground); + box-shadow: 0 0 12px var(--black); color: inherit; text-decoration: none; + transition: all 200ms ease-in; } } diff --git a/frontend/src/Styles/Themes/dark.js b/frontend/src/Styles/Themes/dark.js index f5f380690..ae7bcf56d 100644 --- a/frontend/src/Styles/Themes/dark.js +++ b/frontend/src/Styles/Themes/dark.js @@ -218,18 +218,18 @@ module.exports = { tableRowHoverBackgroundColor: 'rgba(255, 255, 255, 0.08)', // - // Series + // Srtist - addSeriesBackgroundColor: '#2a2a2a', - seriesBackgroundColor: '#2a2a2a', + addArtistBackgroundColor: '#2a2a2a', + artistBackgroundColor: '#2a2a2a', searchIconContainerBackgroundColor: '#2b2b2b', collapseButtonBackgroundColor: '#2a2a2a', // - // Season + // Album - seasonBackgroundColor: '#424242', - episodesBackgroundColor: '#2a2a2a', + albumBackgroundColor: '#424242', + trackBackgroundColor: '#2a2a2a', // // misc diff --git a/frontend/src/Styles/Themes/light.js b/frontend/src/Styles/Themes/light.js index 3f8dc8b44..4c8bcc5bd 100644 --- a/frontend/src/Styles/Themes/light.js +++ b/frontend/src/Styles/Themes/light.js @@ -212,18 +212,18 @@ module.exports = { tableRowHoverBackgroundColor: '#fafbfc', // - // Series + // Atist - addSeriesBackgroundColor: '#ededed', - seriesBackgroundColor: '#ededed', + addArtistBackgroundColor: '#ededed', + artistBackgroundColor: '#ededed', searchIconContainerBackgroundColor: offWhite, collapseButtonBackgroundColor: offWhite, // - // Season + // Album - seasonBackgroundColor: white, - episodesBackgroundColor: offWhite, + albumBackgroundColor: white, + trackBackgroundColor: offWhite, // // misc diff --git a/frontend/src/TrackFile/ExpandingFileDetails.css b/frontend/src/TrackFile/ExpandingFileDetails.css index aa2b038a9..23af8caf7 100644 --- a/frontend/src/TrackFile/ExpandingFileDetails.css +++ b/frontend/src/TrackFile/ExpandingFileDetails.css @@ -2,7 +2,7 @@ margin-bottom: 20px; border: 1px solid var(--borderColor); border-radius: 4px; - background-color: var(--white); + background-color: var(--cardBackgroundColor); &:last-of-type { margin-bottom: 0; From e0cf98d39e2e44c15ff360754a1d2a5c2af7c6d9 Mon Sep 17 00:00:00 2001 From: Qstick Date: Wed, 30 Nov 2022 22:09:18 -0600 Subject: [PATCH 0082/1478] Fixed: Correct Attribute compare for Id validation (cherry picked from commit 7e48ea0231272ae56c30f5f43339f0dca7a27fb3) --- src/Lidarr.Http/REST/RestController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Lidarr.Http/REST/RestController.cs b/src/Lidarr.Http/REST/RestController.cs index b75b13820..187084077 100644 --- a/src/Lidarr.Http/REST/RestController.cs +++ b/src/Lidarr.Http/REST/RestController.cs @@ -62,7 +62,7 @@ namespace Lidarr.Http.REST } var attributes = descriptor.MethodInfo.CustomAttributes; - if (attributes.Any(x => VALIDATE_ID_ATTRIBUTES.Contains(x.GetType())) && !skipValidate) + if (attributes.Any(x => VALIDATE_ID_ATTRIBUTES.Contains(x.AttributeType)) && !skipValidate) { if (context.ActionArguments.TryGetValue("id", out var idObj)) { From 39ba97f6a5cc1a342e626bee07988f29d2b81caf Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 2 Dec 2022 17:27:54 -0800 Subject: [PATCH 0083/1478] Fixed: Grab/remove queue actions not showing spinner (cherry picked from commit 51b1ba13c1b9dc9e26469c728e3871d4b7da0788) --- frontend/src/Activity/Queue/Queue.js | 32 ++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/frontend/src/Activity/Queue/Queue.js b/frontend/src/Activity/Queue/Queue.js index ea364367b..b148645d3 100644 --- a/frontend/src/Activity/Queue/Queue.js +++ b/frontend/src/Activity/Queue/Queue.js @@ -75,13 +75,23 @@ class Queue extends Component { return; } + const nextState = {}; + + if (prevProps.items !== items) { + nextState.items = items; + } + const selectedIds = this.getSelectedIds(); const isPendingSelected = _.some(this.props.items, (item) => { return selectedIds.indexOf(item.id) > -1 && item.status === 'delay'; }); if (isPendingSelected !== this.state.isPendingSelected) { - this.setState({ isPendingSelected }); + nextState.isPendingSelected = isPendingSelected; + } + + if (!_.isEmpty(nextState)) { + this.setState(nextState); } } @@ -216,26 +226,29 @@ class Queue extends Component { { - isRefreshing && !isAllPopulated && - + isRefreshing && !isAllPopulated ? + : + null } { - !isRefreshing && hasError && + !isRefreshing && hasError ?
Failed to load Queue -
+ : + null } { - isAllPopulated && !hasError && !items.length && + isAllPopulated && !hasError && !items.length ?
Queue is empty -
+ : + null } { - isAllPopulated && !hasError && !!items.length && + isAllPopulated && !hasError && !!items.length ?
- + : + null } From 39abf00d66c398c3b79e5ead9a4e3c285395d7df Mon Sep 17 00:00:00 2001 From: bakerboy448 <55419169+bakerboy448@users.noreply.github.com> Date: Mon, 3 Oct 2022 12:17:24 -0500 Subject: [PATCH 0084/1478] Fixed: Improve RarBG Error Handling (cherry picked from commit 7cd38bba841659a595fe4a0e14c478fc4e4047b1) --- src/NzbDrone.Core/Indexers/Rarbg/RarbgParser.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/Indexers/Rarbg/RarbgParser.cs b/src/NzbDrone.Core/Indexers/Rarbg/RarbgParser.cs index 482d0c706..40677fba4 100644 --- a/src/NzbDrone.Core/Indexers/Rarbg/RarbgParser.cs +++ b/src/NzbDrone.Core/Indexers/Rarbg/RarbgParser.cs @@ -30,9 +30,12 @@ namespace NzbDrone.Core.Indexers.Rarbg if (jsonResponse.Resource.error_code.HasValue) { - if (jsonResponse.Resource.error_code == 20 || jsonResponse.Resource.error_code == 8) + if (jsonResponse.Resource.error_code == 5 || jsonResponse.Resource.error_code == 8 + || jsonResponse.Resource.error_code == 9 || jsonResponse.Resource.error_code == 10 + || jsonResponse.Resource.error_code == 13 || jsonResponse.Resource.error_code == 14 + || jsonResponse.Resource.error_code == 20) { - // No results found + // No results, rate limit, tvdbid not found/invalid, or imdbid not found/invalid return results; } From b8fc07e343865c64818df4b18121599c48e8cb78 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 11 Dec 2022 18:42:13 -0600 Subject: [PATCH 0085/1478] Bump Newtonsoft to 13.0.2 --- src/NzbDrone.Common/Lidarr.Common.csproj | 2 +- src/NzbDrone.Core/Lidarr.Core.csproj | 2 +- src/NzbDrone.Test.Common/Lidarr.Test.Common.csproj | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Common/Lidarr.Common.csproj b/src/NzbDrone.Common/Lidarr.Common.csproj index 9b6be37d9..3a5cf4cdc 100644 --- a/src/NzbDrone.Common/Lidarr.Common.csproj +++ b/src/NzbDrone.Common/Lidarr.Common.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/NzbDrone.Core/Lidarr.Core.csproj b/src/NzbDrone.Core/Lidarr.Core.csproj index 203dc1393..aaf703849 100644 --- a/src/NzbDrone.Core/Lidarr.Core.csproj +++ b/src/NzbDrone.Core/Lidarr.Core.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/NzbDrone.Test.Common/Lidarr.Test.Common.csproj b/src/NzbDrone.Test.Common/Lidarr.Test.Common.csproj index 506cd84fc..68749c7b0 100644 --- a/src/NzbDrone.Test.Common/Lidarr.Test.Common.csproj +++ b/src/NzbDrone.Test.Common/Lidarr.Test.Common.csproj @@ -6,7 +6,6 @@ - From acf3ef7fb2c6bdaaea04ec75c7994d121f8a02a0 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 4 Dec 2022 22:08:56 -0800 Subject: [PATCH 0086/1478] Fixed: Improve Bind Address validation and help text Closes #3167 (cherry picked from commit 6bdeafcf8c78e145595f52e885356be1210abe91) --- .../Config/HostConfigController.cs | 4 +-- .../IsValidIPAddressFixture.cs | 25 +++++++++++++++++++ .../Extensions/StringExtensions.cs | 22 ++++++++++++++++ src/NzbDrone.Core/Localization/Core/en.json | 2 +- src/NzbDrone.Core/Validation/IpValidation.cs | 22 +++------------- 5 files changed, 53 insertions(+), 22 deletions(-) create mode 100644 src/NzbDrone.Common.Test/ExtensionTests/StringExtensionTests/IsValidIPAddressFixture.cs diff --git a/src/Lidarr.Api.V1/Config/HostConfigController.cs b/src/Lidarr.Api.V1/Config/HostConfigController.cs index 1eae320b6..c34fc3132 100644 --- a/src/Lidarr.Api.V1/Config/HostConfigController.cs +++ b/src/Lidarr.Api.V1/Config/HostConfigController.cs @@ -33,9 +33,9 @@ namespace Lidarr.Api.V1.Config _userService = userService; SharedValidator.RuleFor(c => c.BindAddress) - .ValidIp4Address() + .ValidIpAddress() .NotListenAllIp4Address() - .When(c => c.BindAddress != "*"); + .When(c => c.BindAddress != "*" && c.BindAddress != "localhost"); SharedValidator.RuleFor(c => c.Port).ValidPort(); diff --git a/src/NzbDrone.Common.Test/ExtensionTests/StringExtensionTests/IsValidIPAddressFixture.cs b/src/NzbDrone.Common.Test/ExtensionTests/StringExtensionTests/IsValidIPAddressFixture.cs new file mode 100644 index 000000000..0e2ac3d63 --- /dev/null +++ b/src/NzbDrone.Common.Test/ExtensionTests/StringExtensionTests/IsValidIPAddressFixture.cs @@ -0,0 +1,25 @@ +using System.Globalization; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Common.Extensions; + +namespace NzbDrone.Common.Test.ExtensionTests.StringExtensionTests +{ + [TestFixture] + public class IsValidIPAddressFixture + { + [TestCase("192.168.0.1")] + [TestCase("::1")] + [TestCase("2001:db8:4006:812::200e")] + public void should_validate_ip_address(string input) + { + input.IsValidIpAddress().Should().BeTrue(); + } + + [TestCase("sonarr.tv")] + public void should_not_parse_non_ip_address(string input) + { + input.IsValidIpAddress().Should().BeFalse(); + } + } +} diff --git a/src/NzbDrone.Common/Extensions/StringExtensions.cs b/src/NzbDrone.Common/Extensions/StringExtensions.cs index 686167d64..a3fa20bbf 100644 --- a/src/NzbDrone.Common/Extensions/StringExtensions.cs +++ b/src/NzbDrone.Common/Extensions/StringExtensions.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Net; +using System.Net.Sockets; using System.Text; using System.Text.RegularExpressions; @@ -231,5 +233,25 @@ namespace NzbDrone.Common.Extensions .Replace("'", "%27") .Replace("%7E", "~"); } + + public static bool IsValidIpAddress(this string value) + { + if (!IPAddress.TryParse(value, out var parsedAddress)) + { + return false; + } + + if (parsedAddress.Equals(IPAddress.Parse("255.255.255.255"))) + { + return false; + } + + if (parsedAddress.IsIPv6Multicast) + { + return false; + } + + return parsedAddress.AddressFamily == AddressFamily.InterNetwork || parsedAddress.AddressFamily == AddressFamily.InterNetworkV6; + } } } diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 1a11a83a1..37cbbc441 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -100,7 +100,7 @@ "Backups": "Backups", "BeforeUpdate": "Before Update", "BindAddress": "Bind Address", - "BindAddressHelpText": "Valid IPv4 address or '*' for all interfaces", + "BindAddressHelpText": "Valid IP address, localhost or '*' for all interfaces", "BindAddressHelpTextWarning": "Requires restart to take effect", "Blocklist": "Blocklist", "BlocklistHelpText": "Prevents Lidarr from automatically grabbing these files again", diff --git a/src/NzbDrone.Core/Validation/IpValidation.cs b/src/NzbDrone.Core/Validation/IpValidation.cs index 7037ae1ea..eb5863caa 100644 --- a/src/NzbDrone.Core/Validation/IpValidation.cs +++ b/src/NzbDrone.Core/Validation/IpValidation.cs @@ -1,30 +1,14 @@ -using System.Net; -using System.Net.Sockets; using FluentValidation; using FluentValidation.Validators; +using NzbDrone.Common.Extensions; namespace NzbDrone.Core.Validation { public static class IpValidation { - public static IRuleBuilderOptions ValidIp4Address(this IRuleBuilder ruleBuilder) + public static IRuleBuilderOptions ValidIpAddress(this IRuleBuilder ruleBuilder) { - return ruleBuilder.Must(x => - { - IPAddress parsedAddress; - - if (!IPAddress.TryParse(x, out parsedAddress)) - { - return false; - } - - if (parsedAddress.Equals(IPAddress.Parse("255.255.255.255"))) - { - return false; - } - - return parsedAddress.AddressFamily == AddressFamily.InterNetwork; - }).WithMessage("Must contain wildcard (*) or a valid IPv4 Address"); + return ruleBuilder.Must(x => x.IsValidIpAddress()).WithMessage("Must contain wildcard (*) or a valid IP Address"); } public static IRuleBuilderOptions NotListenAllIp4Address(this IRuleBuilder ruleBuilder) From fd463ad283896ade6c6f518eb9779065a2805d31 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 5 Dec 2022 00:57:21 -0800 Subject: [PATCH 0087/1478] New: IPv6 support for connections/indexers/download clients Closes #3168 (cherry picked from commit 1b90fbcf7df2c1086da4791c6491771924b1b7aa) --- src/NzbDrone.Common.Test/Http/HttpUriFixture.cs | 1 + .../Extensions/StringExtensions.cs | 5 +++++ src/NzbDrone.Common/Http/HttpUri.cs | 6 ++++-- .../MediaBrowser/MediaBrowserSettings.cs | 3 ++- .../Plex/Server/PlexServerProxy.cs | 17 +++++++++-------- .../Notifications/Xbmc/XbmcSettings.cs | 3 ++- .../Validation/RuleBuilderExtensions.cs | 10 ++++++++-- 7 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/NzbDrone.Common.Test/Http/HttpUriFixture.cs b/src/NzbDrone.Common.Test/Http/HttpUriFixture.cs index f47c59881..d25d07bfe 100644 --- a/src/NzbDrone.Common.Test/Http/HttpUriFixture.cs +++ b/src/NzbDrone.Common.Test/Http/HttpUriFixture.cs @@ -10,6 +10,7 @@ namespace NzbDrone.Common.Test.Http [TestCase("abc://my_host.com:8080/root/api/")] [TestCase("abc://my_host.com:8080//root/api/")] [TestCase("abc://my_host.com:8080/root//api/")] + [TestCase("abc://[::1]:8080/root//api/")] public void should_parse(string uri) { var newUri = new HttpUri(uri); diff --git a/src/NzbDrone.Common/Extensions/StringExtensions.cs b/src/NzbDrone.Common/Extensions/StringExtensions.cs index a3fa20bbf..e71a14fcb 100644 --- a/src/NzbDrone.Common/Extensions/StringExtensions.cs +++ b/src/NzbDrone.Common/Extensions/StringExtensions.cs @@ -253,5 +253,10 @@ namespace NzbDrone.Common.Extensions return parsedAddress.AddressFamily == AddressFamily.InterNetwork || parsedAddress.AddressFamily == AddressFamily.InterNetworkV6; } + + public static string ToUrlHost(this string input) + { + return input.Contains(":") ? $"[{input}]" : input; + } } } diff --git a/src/NzbDrone.Common/Http/HttpUri.cs b/src/NzbDrone.Common/Http/HttpUri.cs index f90e564f5..647da04ee 100644 --- a/src/NzbDrone.Common/Http/HttpUri.cs +++ b/src/NzbDrone.Common/Http/HttpUri.cs @@ -8,7 +8,7 @@ namespace NzbDrone.Common.Http { public class HttpUri : IEquatable { - private static readonly Regex RegexUri = new Regex(@"^(?:(?[a-z]+):)?(?://(?[-_A-Z0-9.]+)(?::(?[0-9]{1,5}))?)?(?(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?[^#\r\n]*))?(?:\#(?.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex RegexUri = new Regex(@"^(?:(?[a-z]+):)?(?://(?[-_A-Z0-9.]+|\[[[A-F0-9:]+\])(?::(?[0-9]{1,5}))?)?(?(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?[^#\r\n]*))?(?:\#(?.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); private readonly string _uri; public string FullUri => _uri; @@ -70,6 +70,8 @@ namespace NzbDrone.Common.Http private void Parse() { + var parseSuccess = Uri.TryCreate(_uri, UriKind.RelativeOrAbsolute, out var uri); + var match = RegexUri.Match(_uri); var scheme = match.Groups["scheme"]; @@ -79,7 +81,7 @@ namespace NzbDrone.Common.Http var query = match.Groups["query"]; var fragment = match.Groups["fragment"]; - if (!match.Success || (scheme.Success && !host.Success && path.Success)) + if (!parseSuccess || (scheme.Success && !host.Success && path.Success)) { throw new ArgumentException("Uri didn't match expected pattern: " + _uri); } diff --git a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserSettings.cs b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserSettings.cs index 9dd64d0cb..c3ec11044 100644 --- a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserSettings.cs +++ b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserSettings.cs @@ -1,5 +1,6 @@ using FluentValidation; using Newtonsoft.Json; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Annotations; using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Validation; @@ -43,7 +44,7 @@ namespace NzbDrone.Core.Notifications.Emby public bool UpdateLibrary { get; set; } [JsonIgnore] - public string Address => $"{Host}:{Port}"; + public string Address => $"{Host.ToUrlHost()}:{Port}"; public bool IsValid => !string.IsNullOrWhiteSpace(Host) && Port > 0; diff --git a/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerProxy.cs b/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerProxy.cs index effa10244..eaa5cf968 100644 --- a/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerProxy.cs +++ b/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerProxy.cs @@ -151,14 +151,15 @@ namespace NzbDrone.Core.Notifications.Plex.Server private HttpRequestBuilder BuildRequest(string resource, HttpMethod method, PlexServerSettings settings) { var scheme = settings.UseSsl ? "https" : "http"; - var requestBuilder = new HttpRequestBuilder($"{scheme}://{settings.Host}:{settings.Port}") - .Accept(HttpAccept.Json) - .AddQueryParam("X-Plex-Client-Identifier", _configService.PlexClientIdentifier) - .AddQueryParam("X-Plex-Product", BuildInfo.AppName) - .AddQueryParam("X-Plex-Platform", "Windows") - .AddQueryParam("X-Plex-Platform-Version", "7") - .AddQueryParam("X-Plex-Device-Name", BuildInfo.AppName) - .AddQueryParam("X-Plex-Version", BuildInfo.Version.ToString()); + + var requestBuilder = new HttpRequestBuilder($"{scheme}://{settings.Host.ToUrlHost()}:{settings.Port}") + .Accept(HttpAccept.Json) + .AddQueryParam("X-Plex-Client-Identifier", _configService.PlexClientIdentifier) + .AddQueryParam("X-Plex-Product", BuildInfo.AppName) + .AddQueryParam("X-Plex-Platform", "Windows") + .AddQueryParam("X-Plex-Platform-Version", "7") + .AddQueryParam("X-Plex-Device-Name", BuildInfo.AppName) + .AddQueryParam("X-Plex-Version", BuildInfo.Version.ToString()); if (settings.AuthToken.IsNotNullOrWhiteSpace()) { diff --git a/src/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs b/src/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs index 99e4c6004..8f2e00870 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using FluentValidation; using Newtonsoft.Json; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Annotations; using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Validation; @@ -58,7 +59,7 @@ namespace NzbDrone.Core.Notifications.Xbmc public bool AlwaysUpdate { get; set; } [JsonIgnore] - public string Address => string.Format("{0}:{1}", Host, Port); + public string Address => $"{Host.ToUrlHost()}:{Port}"; public NzbDroneValidationResult Validate() { diff --git a/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs b/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs index 012c74a53..c9629b5e1 100644 --- a/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs +++ b/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs @@ -1,11 +1,15 @@ +using System; using System.Text.RegularExpressions; using FluentValidation; using FluentValidation.Validators; +using NzbDrone.Common.Extensions; namespace NzbDrone.Core.Validation { public static class RuleBuilderExtensions { + private static readonly Regex HostRegex = new Regex("^[-_a-z0-9.]+$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + public static IRuleBuilderOptions ValidId(this IRuleBuilder ruleBuilder) { return ruleBuilder.SetValidator(new GreaterThanValidator(0)); @@ -24,13 +28,15 @@ namespace NzbDrone.Core.Validation public static IRuleBuilderOptions ValidHost(this IRuleBuilder ruleBuilder) { ruleBuilder.SetValidator(new NotEmptyValidator(null)); - return ruleBuilder.SetValidator(new RegularExpressionValidator("^[-_a-z0-9.]+$", RegexOptions.IgnoreCase)).WithMessage("must be valid Host without http://"); + + return ruleBuilder.Must(x => HostRegex.IsMatch(x) || x.IsValidIpAddress()).WithMessage("must be valid Host without http://"); } public static IRuleBuilderOptions ValidRootUrl(this IRuleBuilder ruleBuilder) { ruleBuilder.SetValidator(new NotEmptyValidator(null)); - return ruleBuilder.SetValidator(new RegularExpressionValidator("^https?://[-_a-z0-9.]+", RegexOptions.IgnoreCase)).WithMessage("must be valid URL that starts with http(s)://"); + + return ruleBuilder.Must(x => x.IsValidUrl() && x.StartsWith("http", StringComparison.InvariantCultureIgnoreCase)).WithMessage("must be valid URL that starts with http(s)://"); } public static IRuleBuilderOptions ValidUrlBase(this IRuleBuilder ruleBuilder, string example = "/lidarr") From 4fd389021ae7c44fb9683db5adc5718fabe4dc03 Mon Sep 17 00:00:00 2001 From: Zak Saunders Date: Tue, 6 Dec 2022 07:22:52 +0000 Subject: [PATCH 0088/1478] New: Auto theme option to match OS theme Closes #3165 Co-authored-by: Qstick (cherry picked from commit 4ca5a213fa0fc29ed93e7e31b080728d6fa7f1f3) --- frontend/src/Styles/Themes/index.js | 4 ++++ src/NzbDrone.Core/Configuration/ConfigFileProvider.cs | 2 +- src/NzbDrone.Core/Localization/Core/en.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/Styles/Themes/index.js b/frontend/src/Styles/Themes/index.js index 02068cc4c..d93c5dd8c 100644 --- a/frontend/src/Styles/Themes/index.js +++ b/frontend/src/Styles/Themes/index.js @@ -1,7 +1,11 @@ import * as dark from './dark'; import * as light from './light'; +const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches; +const auto = defaultDark ? { ...dark } : { ...light }; + export default { + auto, light, dark }; diff --git a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs index 2156381a6..72c424318 100644 --- a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs +++ b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs @@ -196,7 +196,7 @@ namespace NzbDrone.Core.Configuration public string LogLevel => GetValue("LogLevel", "info"); public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false); - public string Theme => GetValue("Theme", "light", persist: false); + public string Theme => GetValue("Theme", "auto", persist: false); public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false); public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false); public string PostgresPassword => _postgresOptions?.Password ?? GetValue("PostgresPassword", string.Empty, persist: false); diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 37cbbc441..21d2ed75f 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -769,7 +769,7 @@ "TheAlbumsFilesWillBeDeleted": "The album's files will be deleted.", "TheArtistFolderStrongpathstrongAndAllOfItsContentWillBeDeleted": "The artist folder {path} and all of its content will be deleted.", "Theme": "Theme", - "ThemeHelpText": "Change Application UI Theme", + "ThemeHelpText": "Change Application UI Theme, 'Auto' Theme will use your OS Theme to set Light or Dark mode. Inspired by Theme.Park", "ThisCannotBeCancelled": "This cannot be cancelled once started without disabling all of your indexers.", "ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "This will apply to all indexers, please follow the rules set forth by them", "Time": "Time", From 2cc835d4566f559b739ff54a9008f612a351429e Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 11 Dec 2022 18:59:50 -0600 Subject: [PATCH 0089/1478] Fixed: Use route Id for PUT requests if not passed in body Closes #3133 --- src/Lidarr.Http/REST/RestController.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Lidarr.Http/REST/RestController.cs b/src/Lidarr.Http/REST/RestController.cs index 187084077..4a9f5d607 100644 --- a/src/Lidarr.Http/REST/RestController.cs +++ b/src/Lidarr.Http/REST/RestController.cs @@ -57,6 +57,12 @@ namespace Lidarr.Http.REST foreach (var resource in resourceArgs) { + // Map route Id to body resource if not set in request + if (Request.Method == "PUT" && resource.Id == 0 && context.RouteData.Values.TryGetValue("id", out var routeId)) + { + resource.Id = Convert.ToInt32(routeId); + } + ValidateResource(resource, skipValidate, skipShared); } } From 37bec3ade0592f879ce7370d1cde026ea331dba7 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 11 Dec 2022 00:09:53 +0000 Subject: [PATCH 0090/1478] Translated using Weblate (Italian) [skip ci] Currently translated at 77.4% (681 of 879 strings) Translated using Weblate (Finnish) [skip ci] Currently translated at 77.3% (680 of 879 strings) Translated using Weblate (German) [skip ci] Currently translated at 96.0% (844 of 879 strings) Translated using Weblate (Italian) [skip ci] Currently translated at 77.3% (680 of 879 strings) Translated using Weblate (Italian) [skip ci] Currently translated at 73.9% (650 of 879 strings) Translated using Weblate (Dutch) [skip ci] Currently translated at 68.0% (598 of 879 strings) Translated using Weblate (Hungarian) [skip ci] Currently translated at 100.0% (879 of 879 strings) Translated using Weblate (Portuguese (Brazil)) [skip ci] Currently translated at 99.8% (878 of 879 strings) Co-authored-by: Csaba Co-authored-by: Havok Dan Co-authored-by: Mipiaceanutella Co-authored-by: Oskari Lavinto Co-authored-by: Robin Flikkema Co-authored-by: Weblate Co-authored-by: deepserket Co-authored-by: hidaba Co-authored-by: reloxx Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/de/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fi/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hu/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/it/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/nl/ Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/ Translation: Servarr/Lidarr --- src/NzbDrone.Core/Localization/Core/de.json | 6 ++- src/NzbDrone.Core/Localization/Core/fi.json | 18 ++++--- src/NzbDrone.Core/Localization/Core/hu.json | 2 +- src/NzbDrone.Core/Localization/Core/it.json | 54 +++++++++++++++++-- src/NzbDrone.Core/Localization/Core/nl.json | 6 +-- .../Localization/Core/pt_BR.json | 4 +- 6 files changed, 70 insertions(+), 20 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/de.json b/src/NzbDrone.Core/Localization/Core/de.json index 0da485ba3..28ebb0ccc 100644 --- a/src/NzbDrone.Core/Localization/Core/de.json +++ b/src/NzbDrone.Core/Localization/Core/de.json @@ -597,7 +597,7 @@ "IfYouDontAddAnImportListExclusionAndTheArtistHasAMetadataProfileOtherThanNoneThenThisAlbumMayBeReaddedDuringTheNextArtistRefresh": "Wenn kein Ausschluss der Import Liste hinzugefügt wird und der Künstler ein anderes Metadaten Profil als 'Keine' hat, kann es passieren, dass dieses Album bei der nächsten Aktualisierung des Künstlers erneut hinzugefügt wird.", "ImportFailures": "Importfehler", "ImportLists": "Importlisten", - "ImportListSettings": "Importliste Einstellungen", + "ImportListSettings": "Allgemeine Importlisten-Einstellungen", "IncludePreferredWhenRenaming": "Bevorzugungen bei Umbennenung beachten", "IndexerIdHelpText": "Angabe, welcher Indexer für das Profil gilt", "IndexerIdHelpTextWarning": "Die Verwendung eines bestimmten Indexers mit bevorzugten Wörtern kann dazu führen, dass Veröffentlichungen mehrfach erfasst werden", @@ -841,5 +841,7 @@ "DeleteArtist": "Lösche ausgewählten Künstler", "Discography": "Diskographie", "DownloadImported": "Importiere herunterladen", - "EditMetadata": "Metadaten bearbeiten" + "EditMetadata": "Metadaten bearbeiten", + "ForNewImportsOnly": "Nur für neue Imports", + "ImportFailed": "Import fehlgeschlagen" } diff --git a/src/NzbDrone.Core/Localization/Core/fi.json b/src/NzbDrone.Core/Localization/Core/fi.json index 36083d4d2..680718d5f 100644 --- a/src/NzbDrone.Core/Localization/Core/fi.json +++ b/src/NzbDrone.Core/Localization/Core/fi.json @@ -65,7 +65,7 @@ "DeleteImportListMessageText": "Haluatko varmasti poistaa tuontilistan '{0}'?", "DeleteIndexerMessageText": "Haluatko varmasti poistaa tietolähteen '{0}'?", "ApplyTagsHelpTexts2": "– 'Lisää' syötetyt tunnisteet aiempiin tunnisteisiin", - "ApplyTagsHelpTexts4": "– 'Korvaa' kaikki aiemmat tunnisteet tai poista kaikki tunnisteet jättämällä tyhjäksi", + "ApplyTagsHelpTexts4": "- \"Korvaa\" nykyiset tunnisteet syötetyillä tai tyhjennä kaikki tunnisteet jättämällä tyhjäksi.", "DeleteTagMessageText": "Haluatko varmasti poistaa tunnisteen '{0}'?", "TagIsNotUsedAndCanBeDeleted": "Tunnistetta ei ole määritetty millekään kohteelle, joten sen voi poistaa.", "Security": "Suojaus", @@ -74,7 +74,7 @@ "AddListExclusion": "Lisää tuontilistojen poikkeussääntö", "ApiKeyHelpTextWarning": "Käyttöönotto vaatii uudelleenkäynnistyksen.", "APIKey": "API-avain", - "ApplyTagsHelpTexts3": "– 'Poista' ainoastaan syötetyt tunnisteet", + "ApplyTagsHelpTexts3": "- \"Poista\" tyhjentää syötetyt tunnisteet.", "AppDataDirectory": "AppData-kansio", "Authentication": "Todennus", "AuthenticationMethodHelpText": "Vaadi käyttäjätunnus ja salasana.", @@ -491,7 +491,7 @@ "UnableToAddANewImportListExclusionPleaseTryAgain": "Uuden luettelon poissulkemisen lisääminen epäonnistui, yritä uudelleen.", "UnableToAddANewMetadataProfilePleaseTryAgain": "Uutta laatuprofiilia ei voi lisätä, yritä uudelleen.", "UnableToAddANewRootFolderPleaseTryAgain": "Uutta mukautettua muotoa ei voi lisätä, yritä uudelleen.", - "UnableToLoadMetadataProfiles": "Metatietoprofiilien lataus epäonnistui.", + "UnableToLoadMetadataProfiles": "Metatietoprofiileja ei voida ladata.", "Updates": "Päivitykset", "UsenetDelay": "Usenet-viive", "NETCore": ".NET", @@ -546,17 +546,17 @@ "Other": "Muu", "Tracks": "Jäljitys", "WatchLibraryForChangesHelpText": "Suorita automaattinen uudelleentutkinta, kun juurikansiossa havaitaan tiedostomuutoksia.", - "AddedArtistSettings": "Uuden kirjailijan oletusasetukset", + "AddedArtistSettings": "Lisätyn esittäjän asetukset", "MonitorAlbumExistingOnlyWarning": "Tämä on albumikohtaisen valvonnan kertaluontoinen määritys. Määritä Esittäjä/Muokkaa-valinnalla mitä uusille albumilisäyksille tehdään.", "MonitoringOptionsHelpText": "Mitkä albumit asetetaan valvottaviksi esittäjän lisäyksen yhteydessä (kertaluontoinen määritys).", "MonitorNewItemsHelpText": "Uusien albumien valvontatapa.", "MonoVersion": "Mono-versio", "AddDelayProfile": "Lisää viiveprofiili", "Added": "Lisätty", - "AddImportListExclusion": "Tuotilistojen poikkeukset", + "AddImportListExclusion": "Lisää tuontilistapoikkeus", "AddIndexer": "Lisää tietolähde", "AddList": "Lisää tuontilista", - "AddMetadataProfile": "Metatietoprofiili", + "AddMetadataProfile": "Lisää metatietoprofiili", "AddNew": "Lisää uusi", "AddQualityProfile": "Lisää laatuprofiili", "AddRootFolder": "Lisää juurikansio", @@ -578,7 +578,7 @@ "Custom": "Mukautettu", "CustomFilters": "Mukautetut suodattimet", "Date": "Päiväys", - "DefaultDelayProfileHelpText": "Tämä on oletusprofiili, jota sovelletaan kaikkiin elokuviin, joille ei ole määritetty erillistä profiilia.", + "DefaultDelayProfileHelpText": "Tämä on oletusprofiili, jota sovelletaan kaikkiin esittäjiin, joille ei ole määritetty erillistä profiilia.", "Deleted": "Poistettu", "Details": "Tiedot", "Donations": "Lahjoitukset", @@ -676,5 +676,7 @@ "EditDelayProfile": "Muokkaa viiveprofiilia", "Add": "Lisää", "AddConnection": "Lisää yhteys", - "EditMetadataProfile": "Metatietoprofiili" + "EditMetadataProfile": "Metatietoprofiili", + "Database": "Tietokanta", + "AllowFingerprinting": "Salli piiloleimaus" } diff --git a/src/NzbDrone.Core/Localization/Core/hu.json b/src/NzbDrone.Core/Localization/Core/hu.json index 942909555..c04498f45 100644 --- a/src/NzbDrone.Core/Localization/Core/hu.json +++ b/src/NzbDrone.Core/Localization/Core/hu.json @@ -719,7 +719,7 @@ "AllMonitoringOptionHelpText": "Az importálási listán lévő szerzők összes könyvének monitorozása", "AllResultsFiltered": "Az alkalmazott szűrők miatt, az összes keresési eredmény rejtve marad", "ApplicationURL": "Alkalmazás URL-je", - "ApplicationUrlHelpText": "Az alkalmazás külső URL-címe, beleértve a \"http(s)://\"-t, a \"Portot\" és az \"URL-alapot\" is", + "ApplicationUrlHelpText": "Az alkalmazás külső URL-címe, beleértve a http(s)://-t, a portot és az URL-alapot", "Apply": "Alkalmazás", "AudioInfo": "Hang Infó", "BeforeUpdate": "Alkalmazásfrissítés előtt", diff --git a/src/NzbDrone.Core/Localization/Core/it.json b/src/NzbDrone.Core/Localization/Core/it.json index a9d9b4005..593f00da7 100644 --- a/src/NzbDrone.Core/Localization/Core/it.json +++ b/src/NzbDrone.Core/Localization/Core/it.json @@ -19,7 +19,7 @@ "AlbumIsDownloading": "Album in scaricamento", "AlbumIsDownloadingInterp": "Album in scaricamento - {0}% {1}", "AlbumIsNotMonitored": "Album non monitorato", - "AlbumStudio": "Album Studio", + "AlbumStudio": "Studio Album", "AllAlbums": "Tutti gli album", "Actions": "Azioni", "AllAlbumsData": "Monitora tutti gli album tranne quelli speciali", @@ -53,7 +53,7 @@ "BackupRetentionHelpText": "I backup automatici più vecchi del periodo di conservazione verranno eliminati automaticamente", "Backups": "I Backup", "BindAddress": "Indirizzo per il collegamento", - "BindAddressHelpText": "Indirizzo IPV4 valido o '*' per tutte le interfacce", + "BindAddressHelpText": "Indirizzo IPV4 valido o '*' per tutte le interfacce di rete", "BindAddressHelpTextWarning": "Richiede il riavvio per avere effetti", "Blocklist": "Lista Nera", "BlocklistHelpText": "Impedisci a Lidarr di acquisire automaticamente questo versione", @@ -62,7 +62,7 @@ "BypassProxyForLocalAddresses": "Evita il Proxy per gli indirizzi locali", "CancelMessageText": "Sei sicuro di voler cancellare questa operazione in sospeso?", "CertificateValidation": "Convalida del certificato", - "CertificateValidationHelpText": "Cambiare il \"grado di severità\" della convalida del certificato HTTPS", + "CertificateValidationHelpText": "Cambia quanto rigorosamente vengono validati i certificati HTTPS. Non cambiare senza conoscerne i rischi.", "ChangeFileDate": "Cambiare la data del file", "ChangeHasNotBeenSavedYet": "Il cambiamento non è stato ancora salvato", "ChmodFolder": "Permessi Cartella", @@ -636,5 +636,51 @@ "Presets": "Preset", "AddImportListExclusion": "Cancellare la lista delle esclusioni", "EditMetadataProfile": "profilo metadati", - "ImportListExclusions": "Cancellare la lista delle esclusioni" + "ImportListExclusions": "Cancellare la lista delle esclusioni", + "ArtistEditor": "Direttore Artistico", + "CatalogNumber": "Numero Catalogo", + "AddConnection": "Aggiungi Connessione", + "AddReleaseProfile": "Aggiungi Profilo di Pubblicazione", + "AlbumRelease": "Pubblicazione Album", + "AlbumReleaseDate": "Data di Pubblicazione dell'Album", + "AlbumStatus": "Stato Album", + "AlbumTitle": "Titolo Album", + "AlbumType": "Tipo Album", + "AllMonitoringOptionHelpText": "Monitora gli artisti e tutti gli album di ogni artista incluso della lista importata", + "ApplicationURL": "URL Applicazione", + "ApplicationUrlHelpText": "L'URL esterno di questa applicazione, incluso http(s)://, porta e URL base", + "AreYouSure": "Sei sicuro?", + "ArtistName": "Nome Artista", + "ArtistType": "Tipo Artista", + "CombineWithExistingFiles": "Combina Con i Documenti Esistenti", + "DeleteArtist": "Cancella gli Artisti Selezionati", + "DeleteTrackFile": "Cancella la traccia", + "Disambiguation": "Disambiguazione", + "Discography": "Discografia", + "DownloadImported": "Scaricamento Importato", + "EditArtist": "Modifica Artista", + "EditConnection": "Modifica Connessione", + "EditList": "Modifica Lista", + "EditMetadata": "Modifica Metadati", + "EnableProfile": "Abilita Profilo", + "DiscNumber": "Numero Disco", + "Database": "Database", + "EnableAutomaticAddHelpText": "Aggiungi Artista/Album a Lidarr quando le sincronizzazione sono completare attraverso l'interfaccia o da Lidarr", + "ChownGroup": "chown Group", + "Continuing": "Continuando", + "ContinuingAllTracksDownloaded": "Continuando (Tutte le tracce scaricate)", + "ContinuingMoreAlbumsAreExpected": "Sono richiesti più Albums", + "ContinuingNoAdditionalAlbumsAreExpected": "Non sono richiesti più Albums", + "ContinuingOnly": "Solo Continuare", + "Country": "Nazione", + "DateAdded": "Aggiunto in Data", + "DeleteFilesHelpText": "Cancella le tracce e le cartelle degli artisti", + "DeleteImportList": "Cancella la lista di importazione", + "DeleteRootFolder": "Cancella la cartella principale", + "EndedAllTracksDownloaded": "Finito (tutte le tracce scaricate)", + "EndedOnly": "Solo Finito", + "Episode": "Episodio", + "ExistingAlbums": "Albums esistente", + "AnchorTooltip": "Questo file è già nella tua raccolta per una versione che stai attualmente importando", + "Started": "Iniziato" } diff --git a/src/NzbDrone.Core/Localization/Core/nl.json b/src/NzbDrone.Core/Localization/Core/nl.json index 50b1105fc..0dc534e08 100644 --- a/src/NzbDrone.Core/Localization/Core/nl.json +++ b/src/NzbDrone.Core/Localization/Core/nl.json @@ -14,7 +14,7 @@ "BackupNow": "Veiligheidskopie Maken", "BackupRetentionHelpText": "Automatische veiligheidskopieën ouder dan de retentie periode zullen worden opgeruimd", "Backups": "Veiligheidskopieën", - "BindAddressHelpText": "Geldig IPv4 adres of '*' voor alle interfaces", + "BindAddressHelpText": "Geldig IPv4-adres of '*' voor alle interfaces", "BindAddressHelpTextWarning": "Herstarten vereist om in werking te treden", "BlocklistHelpText": "Voorkom dat Lidarr deze release nogmaals automatisch ophaalt", "Branch": "Branch", @@ -24,7 +24,7 @@ "Cancel": "Annuleer", "CancelMessageText": "Bent u zeker dat u deze taak in afwachting wilt annuleren?", "CertificateValidation": "Certificaat Validatie", - "CertificateValidationHelpText": "Wijzig hoe strikt HTTPS certificaat validatie is", + "CertificateValidationHelpText": "Wijzig hoe strict HTTPS certificaat validatie is. Wijzig dit niet behalve als je de risico's begrijpt.", "ChangeFileDate": "Wijzig Bestandsdatum", "ChangeHasNotBeenSavedYet": "Wijziging is nog niet opgeslagen", "ChmodFolder": "chmod Map", @@ -524,7 +524,7 @@ "ErrorRestoringBackup": "Fout bij het herstellen van de back-up", "Events": "Gebeurtenissen", "EventType": "Gebeurtenis Type", - "Filters": "Filter", + "Filters": "Filters", "FreeSpace": "Vrije Ruimte", "General": "Algemeen", "Genres": "Genres", diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index f83f29d76..3fcf30c42 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -717,8 +717,8 @@ "AllFiles": "Todos os arquivos", "AllMonitoringOptionHelpText": "Monitorar autores e todos os livros para cada autor incluído na lista de importação", "AllResultsFiltered": "Todos os resultados estão ocultos pelo filtro aplicado", - "ApplicationURL": "URL da Aplicação", - "ApplicationUrlHelpText": "O URL externa deste aplicativo, incluindo http(s)://, porta e base de URL", + "ApplicationURL": "URL do Aplicativo", + "ApplicationUrlHelpText": "A URL externa deste aplicativo, incluindo http(s)://, porta e base da URL", "Apply": "Aplicar", "AudioInfo": "Informações do áudio", "Backup": "Backup", From efb9013bad545a8b86109a09a9662f08e2c77567 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 11 Dec 2022 19:23:22 -0600 Subject: [PATCH 0091/1478] Sliding expiration for auth cookie and a little clean up Closes #2920 Co-Authored-By: Mark McDowall --- .../Authentication/AuthenticationBuilderExtensions.cs | 1 + src/Lidarr.Http/Authentication/AuthenticationService.cs | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Lidarr.Http/Authentication/AuthenticationBuilderExtensions.cs b/src/Lidarr.Http/Authentication/AuthenticationBuilderExtensions.cs index 8383ef685..30f8a97d9 100644 --- a/src/Lidarr.Http/Authentication/AuthenticationBuilderExtensions.cs +++ b/src/Lidarr.Http/Authentication/AuthenticationBuilderExtensions.cs @@ -33,6 +33,7 @@ namespace Lidarr.Http.Authentication options.AccessDeniedPath = "/login?loginFailed=true"; options.LoginPath = "/login"; options.ExpireTimeSpan = TimeSpan.FromDays(7); + options.SlidingExpiration = true; }) .AddApiKey("API", options => { diff --git a/src/Lidarr.Http/Authentication/AuthenticationService.cs b/src/Lidarr.Http/Authentication/AuthenticationService.cs index f1ff69f5b..64dd0f323 100644 --- a/src/Lidarr.Http/Authentication/AuthenticationService.cs +++ b/src/Lidarr.Http/Authentication/AuthenticationService.cs @@ -15,17 +15,14 @@ namespace Lidarr.Http.Authentication public class AuthenticationService : IAuthenticationService { - private const string AnonymousUser = "Anonymous"; private static readonly Logger _authLogger = LogManager.GetLogger("Auth"); private readonly IUserService _userService; - private static string API_KEY; private static AuthenticationType AUTH_METHOD; public AuthenticationService(IConfigFileProvider configFileProvider, IUserService userService) { _userService = userService; - API_KEY = configFileProvider.ApiKey; AUTH_METHOD = configFileProvider.AuthenticationMethod; } From 10c2f014bd3185ef2724c2302b433dcf473d6b6d Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 11 Dec 2022 21:45:16 -0600 Subject: [PATCH 0092/1478] New: Artist genres on artist details page Closes #2507 Co-Authored-By: Mark McDowall --- frontend/src/Artist/Details/ArtistDetails.js | 5 ++ frontend/src/Artist/Details/ArtistGenres.css | 3 ++ frontend/src/Artist/Details/ArtistGenres.js | 53 ++++++++++++++++++++ frontend/src/Components/HeartRating.css | 4 ++ frontend/src/Components/HeartRating.js | 2 +- 5 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 frontend/src/Artist/Details/ArtistGenres.css create mode 100644 frontend/src/Artist/Details/ArtistGenres.js diff --git a/frontend/src/Artist/Details/ArtistDetails.js b/frontend/src/Artist/Details/ArtistDetails.js index ab40a8f92..ecc901330 100644 --- a/frontend/src/Artist/Details/ArtistDetails.js +++ b/frontend/src/Artist/Details/ArtistDetails.js @@ -36,6 +36,7 @@ import InteractiveImportModal from '../../InteractiveImport/InteractiveImportMod import ArtistAlternateTitles from './ArtistAlternateTitles'; import ArtistDetailsLinks from './ArtistDetailsLinks'; import ArtistDetailsSeasonConnector from './ArtistDetailsSeasonConnector'; +import ArtistGenres from './ArtistGenres'; import ArtistTagsConnector from './ArtistTagsConnector'; import styles from './ArtistDetails.css'; @@ -187,6 +188,7 @@ class ArtistDetails extends Component { statistics, qualityProfileId, monitored, + genres, albumTypes, status, overview, @@ -431,6 +433,8 @@ class ArtistDetails extends Component { rating={ratings.value} iconSize={20} /> + + @@ -695,6 +699,7 @@ ArtistDetails.propTypes = { monitored: PropTypes.bool.isRequired, artistType: PropTypes.string, albumTypes: PropTypes.arrayOf(PropTypes.string), + genres: PropTypes.arrayOf(PropTypes.string), status: PropTypes.string.isRequired, overview: PropTypes.string.isRequired, links: PropTypes.arrayOf(PropTypes.object).isRequired, diff --git a/frontend/src/Artist/Details/ArtistGenres.css b/frontend/src/Artist/Details/ArtistGenres.css new file mode 100644 index 000000000..93a028748 --- /dev/null +++ b/frontend/src/Artist/Details/ArtistGenres.css @@ -0,0 +1,3 @@ +.genres { + margin-right: 15px; +} diff --git a/frontend/src/Artist/Details/ArtistGenres.js b/frontend/src/Artist/Details/ArtistGenres.js new file mode 100644 index 000000000..15f3505d8 --- /dev/null +++ b/frontend/src/Artist/Details/ArtistGenres.js @@ -0,0 +1,53 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import Label from 'Components/Label'; +import Tooltip from 'Components/Tooltip/Tooltip'; +import { kinds, sizes, tooltipPositions } from 'Helpers/Props'; +import styles from './ArtistGenres.css'; + +function ArtistGenres({ genres }) { + const [firstGenre, ...otherGenres] = genres; + + if (otherGenres.length) { + return ( + + {firstGenre} + + } + tooltip={ +
+ { + otherGenres.map((tag) => { + return ( + + ); + }) + } +
+ } + kind={kinds.INVERSE} + position={tooltipPositions.TOP} + /> + ); + } + + return ( + + {firstGenre} + + ); +} + +ArtistGenres.propTypes = { + genres: PropTypes.arrayOf(PropTypes.string).isRequired +}; + +export default ArtistGenres; diff --git a/frontend/src/Components/HeartRating.css b/frontend/src/Components/HeartRating.css index 030605dbd..14a20019d 100644 --- a/frontend/src/Components/HeartRating.css +++ b/frontend/src/Components/HeartRating.css @@ -2,3 +2,7 @@ margin-right: 5px; color: var(--themeRed); } + +.rating { + margin-right: 15px; +} diff --git a/frontend/src/Components/HeartRating.js b/frontend/src/Components/HeartRating.js index c1a4f14ff..fe53a4e5f 100644 --- a/frontend/src/Components/HeartRating.js +++ b/frontend/src/Components/HeartRating.js @@ -6,7 +6,7 @@ import styles from './HeartRating.css'; function HeartRating({ rating, iconSize }) { return ( - + Date: Sun, 11 Dec 2022 21:48:34 -0600 Subject: [PATCH 0093/1478] New: Album genres on album details page --- frontend/src/Album/Details/AlbumDetails.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/src/Album/Details/AlbumDetails.js b/frontend/src/Album/Details/AlbumDetails.js index a42055974..5c95e565b 100644 --- a/frontend/src/Album/Details/AlbumDetails.js +++ b/frontend/src/Album/Details/AlbumDetails.js @@ -7,6 +7,7 @@ import AlbumCover from 'Album/AlbumCover'; import DeleteAlbumModal from 'Album/Delete/DeleteAlbumModal'; import EditAlbumModalConnector from 'Album/Edit/EditAlbumModalConnector'; import AlbumInteractiveSearchModalConnector from 'Album/Search/AlbumInteractiveSearchModalConnector'; +import ArtistGenres from 'Artist/Details/ArtistGenres'; import ArtistHistoryModal from 'Artist/History/ArtistHistoryModal'; import HeartRating from 'Components/HeartRating'; import Icon from 'Components/Icon'; @@ -199,6 +200,7 @@ class AlbumDetails extends Component { releaseDate, ratings, images, + genres, links, media, isSaving, @@ -385,6 +387,8 @@ class AlbumDetails extends Component { rating={ratings.value} iconSize={20} /> + + @@ -602,6 +606,7 @@ AlbumDetails.propTypes = { releaseDate: PropTypes.string.isRequired, ratings: PropTypes.object.isRequired, images: PropTypes.arrayOf(PropTypes.object).isRequired, + genres: PropTypes.arrayOf(PropTypes.string).isRequired, links: PropTypes.arrayOf(PropTypes.object).isRequired, media: PropTypes.arrayOf(PropTypes.object).isRequired, monitored: PropTypes.bool.isRequired, From 21053885daae1d3ed03386ba61ec6e3ad1cde13a Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 11 Dec 2022 22:02:46 -0600 Subject: [PATCH 0094/1478] New: Show number of files as tooltip over size on disk Closes #1966 Co-Authored-By: Robin Dadswell <19610103+RobinDadswell@users.noreply.github.com> --- frontend/src/Album/Details/AlbumDetails.js | 52 ++++++++++++++------ frontend/src/Artist/Details/ArtistDetails.js | 40 +++++++++------ 2 files changed, 63 insertions(+), 29 deletions(-) diff --git a/frontend/src/Album/Details/AlbumDetails.js b/frontend/src/Album/Details/AlbumDetails.js index 5c95e565b..c0c3b57ec 100644 --- a/frontend/src/Album/Details/AlbumDetails.js +++ b/frontend/src/Album/Details/AlbumDetails.js @@ -218,6 +218,11 @@ class AlbumDetails extends Component { onSearchPress } = this.props; + const { + trackFileCount, + sizeOnDisk + } = statistics; + const { isOrganizeModalOpen, isRetagModalOpen, @@ -233,6 +238,14 @@ class AlbumDetails extends Component { let expandIcon = icons.EXPAND_INDETERMINATE; + let trackFilesCountMessage = translate('TrackFilesCountMessage'); + + if (trackFileCount === 1) { + trackFilesCountMessage = '1 track file'; + } else if (trackFileCount > 1) { + trackFilesCountMessage = `${trackFileCount} track files`; + } + if (allExpanded) { expandIcon = icons.COLLAPSE; } else if (allCollapsed) { @@ -410,21 +423,32 @@ class AlbumDetails extends Component { - + + { + formatBytes(sizeOnDisk || 0) + } + + + } + tooltip={ + + {trackFilesCountMessage} + + } + kind={kinds.INVERSE} + position={tooltipPositions.BOTTOM} + /> - + + { + formatBytes(sizeOnDisk || 0) + } + + + } + tooltip={ + + {trackFilesCountMessage} + + } + kind={kinds.INVERSE} + position={tooltipPositions.BOTTOM} + />