mirror of
https://github.com/Readarr/Readarr.git
synced 2026-03-22 17:04:16 -04:00
Compare commits
1 Commits
v0.4.17.28
...
sonarr-pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17f28a10d5 |
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '0.4.17'
|
||||
majorVersion: '0.4.4'
|
||||
minorVersion: $[counter('minorVersion', 1)]
|
||||
readarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
||||
@@ -19,7 +19,7 @@ variables:
|
||||
nodeVersion: '20.X'
|
||||
innoVersion: '6.2.0'
|
||||
windowsImage: 'windows-2022'
|
||||
linuxImage: 'ubuntu-22.04'
|
||||
linuxImage: 'ubuntu-20.04'
|
||||
macImage: 'macOS-13'
|
||||
|
||||
trigger:
|
||||
@@ -1102,19 +1102,19 @@ stages:
|
||||
vmImage: ${{ variables.windowsImage }}
|
||||
steps:
|
||||
- checkout: self # Need history for Sonar analysis
|
||||
- task: SonarCloudPrepare@3
|
||||
- task: SonarCloudPrepare@2
|
||||
env:
|
||||
SONAR_SCANNER_OPTS: ''
|
||||
inputs:
|
||||
SonarCloud: 'SonarCloud'
|
||||
organization: 'readarr'
|
||||
scannerMode: 'cli'
|
||||
scannerMode: 'CLI'
|
||||
configMode: 'manual'
|
||||
cliProjectKey: 'readarrui'
|
||||
cliProjectName: 'ReadarrUI'
|
||||
cliProjectVersion: '$(readarrVersion)'
|
||||
cliSources: './frontend'
|
||||
- task: SonarCloudAnalyze@3
|
||||
- task: SonarCloudAnalyze@2
|
||||
|
||||
- job: Api_Docs
|
||||
displayName: API Docs
|
||||
@@ -1190,12 +1190,12 @@ stages:
|
||||
submodules: true
|
||||
- powershell: Set-Service SCardSvr -StartupType Manual
|
||||
displayName: Enable Windows Test Service
|
||||
- task: SonarCloudPrepare@3
|
||||
- task: SonarCloudPrepare@2
|
||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||
inputs:
|
||||
SonarCloud: 'SonarCloud'
|
||||
organization: 'readarr'
|
||||
scannerMode: 'dotnet'
|
||||
scannerMode: 'MSBuild'
|
||||
projectKey: 'Readarr_Readarr'
|
||||
projectName: 'Readarr'
|
||||
projectVersion: '$(readarrVersion)'
|
||||
@@ -1208,10 +1208,10 @@ stages:
|
||||
./build.sh --backend -f net6.0 -r win-x64
|
||||
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
|
||||
displayName: Coverage Unit Tests
|
||||
- task: SonarCloudAnalyze@3
|
||||
- task: SonarCloudAnalyze@2
|
||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||
displayName: Publish SonarCloud Results
|
||||
- task: reportgenerator@5.3.11
|
||||
- task: reportgenerator@5
|
||||
displayName: Generate Coverage Report
|
||||
inputs:
|
||||
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml'
|
||||
|
||||
@@ -26,7 +26,6 @@ module.exports = (env) => {
|
||||
const config = {
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
devtool: isProduction ? 'source-map' : 'eval-source-map',
|
||||
target: 'web',
|
||||
|
||||
stats: {
|
||||
children: false
|
||||
@@ -182,7 +181,7 @@ module.exports = (env) => {
|
||||
loose: true,
|
||||
debug: false,
|
||||
useBuiltIns: 'entry',
|
||||
corejs: '3.39'
|
||||
corejs: 3
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -165,8 +165,7 @@ function HistoryDetails(props) {
|
||||
|
||||
if (eventType === 'downloadFailed') {
|
||||
const {
|
||||
message,
|
||||
indexer
|
||||
message
|
||||
} = data;
|
||||
|
||||
return (
|
||||
@@ -178,21 +177,11 @@ function HistoryDetails(props) {
|
||||
/>
|
||||
|
||||
{
|
||||
indexer ?
|
||||
<DescriptionListItem
|
||||
title={translate('Indexer')}
|
||||
data={indexer}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
message ?
|
||||
!!message &&
|
||||
<DescriptionListItem
|
||||
title={translate('Message')}
|
||||
data={message}
|
||||
/> :
|
||||
null
|
||||
/>
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import ModelBase from 'App/ModelBase';
|
||||
|
||||
export type AuthorStatus = 'continuing' | 'ended';
|
||||
|
||||
interface Author extends ModelBase {
|
||||
added: string;
|
||||
genres: string[];
|
||||
@@ -12,7 +10,6 @@ interface Author extends ModelBase {
|
||||
metadataProfileId: number;
|
||||
rootFolderPath: string;
|
||||
sortName: string;
|
||||
status: AuthorStatus;
|
||||
tags: number[];
|
||||
authorName: string;
|
||||
isSaving?: boolean;
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { AuthorStatus } from 'Author/Author';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
export function getAuthorStatusDetails(status: AuthorStatus) {
|
||||
let statusDetails = {
|
||||
icon: icons.AUTHOR_CONTINUING,
|
||||
title: translate('StatusEndedContinuing'),
|
||||
message: translate('ContinuingMoreBooksAreExpected'),
|
||||
};
|
||||
|
||||
if (status === 'ended') {
|
||||
statusDetails = {
|
||||
icon: icons.AUTHOR_ENDED,
|
||||
title: translate('StatusEndedEnded'),
|
||||
message: translate('ContinuingNoAdditionalBooksAreExpected'),
|
||||
};
|
||||
}
|
||||
|
||||
return statusDetails;
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import AuthorHistoryTable from 'Author/History/AuthorHistoryTable';
|
||||
import MonitoringOptionsModal from 'Author/MonitoringOptions/MonitoringOptionsModal';
|
||||
import BookEditorFooter from 'Book/Editor/BookEditorFooter';
|
||||
import BookFileEditorTable from 'BookFile/Editor/BookFileEditorTable';
|
||||
import Alert from 'Components/Alert';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Link from 'Components/Link/Link';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
@@ -18,7 +17,7 @@ import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import SwipeHeaderConnector from 'Components/Swipe/SwipeHeaderConnector';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector';
|
||||
import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable';
|
||||
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
||||
@@ -413,25 +412,22 @@ class AuthorDetails extends Component {
|
||||
|
||||
<div className={styles.contentContainer}>
|
||||
{
|
||||
!isPopulated && !booksError && !bookFilesError ?
|
||||
<LoadingIndicator /> :
|
||||
null
|
||||
!isPopulated && !booksError && !bookFilesError &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && booksError ?
|
||||
<Alert kind={kinds.DANGER}>
|
||||
!isFetching && booksError &&
|
||||
<div>
|
||||
{translate('LoadingBooksFailed')}
|
||||
</Alert> :
|
||||
null
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && bookFilesError ?
|
||||
<Alert kind={kinds.DANGER}>
|
||||
!isFetching && bookFilesError &&
|
||||
<div>
|
||||
{translate('LoadingBookFilesFailed')}
|
||||
</Alert> :
|
||||
null
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import TextTruncate from 'react-text-truncate';
|
||||
import AuthorPoster from 'Author/AuthorPoster';
|
||||
import { getAuthorStatusDetails } from 'Author/AuthorStatus';
|
||||
import HeartRating from 'Components/HeartRating';
|
||||
import Icon from 'Components/Icon';
|
||||
import Label from 'Components/Label';
|
||||
@@ -12,7 +11,7 @@ import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
||||
import QualityProfileName from 'Settings/Profiles/Quality/QualityProfileName';
|
||||
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
|
||||
import fonts from 'Styles/Variables/fonts';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import stripHtml from 'Utilities/String/stripHtml';
|
||||
@@ -88,11 +87,11 @@ class AuthorDetailsHeader extends Component {
|
||||
titleWidth
|
||||
} = this.state;
|
||||
|
||||
const statusDetails = getAuthorStatusDetails(status);
|
||||
|
||||
const fanartUrl = getFanartUrl(images);
|
||||
const marqueeWidth = titleWidth - (isSmallScreen ? 85 : 160);
|
||||
|
||||
const continuing = status === 'continuing';
|
||||
|
||||
let bookFilesCountMessage = translate('BookFilesCountMessage');
|
||||
|
||||
if (bookFileCount === 1) {
|
||||
@@ -214,7 +213,7 @@ class AuthorDetailsHeader extends Component {
|
||||
|
||||
<span className={styles.qualityProfileName}>
|
||||
{
|
||||
<QualityProfileName
|
||||
<QualityProfileNameConnector
|
||||
qualityProfileId={qualityProfileId}
|
||||
/>
|
||||
}
|
||||
@@ -237,16 +236,16 @@ class AuthorDetailsHeader extends Component {
|
||||
|
||||
<Label
|
||||
className={styles.detailsLabel}
|
||||
title={statusDetails.message}
|
||||
title={continuing ? translate('ContinuingMoreBooksAreExpected') : translate('ContinuingNoAdditionalBooksAreExpected')}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
<Icon
|
||||
name={statusDetails.icon}
|
||||
name={continuing ? icons.AUTHOR_CONTINUING : icons.AUTHOR_ENDED}
|
||||
size={17}
|
||||
/>
|
||||
|
||||
<span className={styles.qualityProfileName}>
|
||||
{statusDetails.title}
|
||||
{continuing ? 'Continuing' : 'Deceased'}
|
||||
</span>
|
||||
</Label>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { getAuthorStatusDetails } from 'Author/AuthorStatus';
|
||||
import Icon from 'Components/Icon';
|
||||
import VirtualTableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import { icons } from 'Helpers/Props';
|
||||
@@ -16,8 +15,6 @@ function AuthorStatusCell(props) {
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const statusDetails = getAuthorStatusDetails(status);
|
||||
|
||||
return (
|
||||
<Component
|
||||
className={className}
|
||||
@@ -31,8 +28,8 @@ function AuthorStatusCell(props) {
|
||||
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={statusDetails.icon}
|
||||
title={`${statusDetails.title}: ${statusDetails.message}`}
|
||||
name={status === 'ended' ? icons.AUTHOR_ENDED : icons.AUTHOR_CONTINUING}
|
||||
title={status === 'ended' ? translate('StatusEndedDeceased') : translate('StatusEndedContinuing')}
|
||||
/>
|
||||
</Component>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Button from 'Components/Link/Button';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './NoAuthor.css';
|
||||
|
||||
function NoAuthor(props) {
|
||||
@@ -32,7 +31,7 @@ function NoAuthor(props) {
|
||||
to="/settings/mediamanagement"
|
||||
kind={kinds.PRIMARY}
|
||||
>
|
||||
{translate('AddRootFolder')}
|
||||
Add Root Folder
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -41,7 +40,7 @@ function NoAuthor(props) {
|
||||
to="/add/search"
|
||||
kind={kinds.PRIMARY}
|
||||
>
|
||||
{translate('AddNewAuthor')}
|
||||
Add New Author
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import AuthorNameLink from 'Author/AuthorNameLink';
|
||||
import { getAuthorStatusDetails } from 'Author/AuthorStatus';
|
||||
import Icon from 'Components/Icon';
|
||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import BookshelfBook from './BookshelfBook';
|
||||
import styles from './BookshelfRow.css';
|
||||
|
||||
@@ -29,8 +30,6 @@ class BookshelfRow extends Component {
|
||||
onBookMonitoredPress
|
||||
} = this.props;
|
||||
|
||||
const statusDetails = getAuthorStatusDetails(status);
|
||||
|
||||
return (
|
||||
<>
|
||||
<VirtualTableSelectCell
|
||||
@@ -53,8 +52,8 @@ class BookshelfRow extends Component {
|
||||
<VirtualTableRowCell className={styles.status}>
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={statusDetails.icon}
|
||||
title={statusDetails.title}
|
||||
name={status === 'ended' ? icons.AUTHOR_ENDED : icons.AUTHOR_CONTINUING}
|
||||
title={status === 'ended' ? translate('StatusEndedEnded') : translate('StatusEndedContinuing')}
|
||||
/>
|
||||
</VirtualTableRowCell>
|
||||
|
||||
|
||||
@@ -20,8 +20,6 @@ import HintedSelectInputSelectedValue from './HintedSelectInputSelectedValue';
|
||||
import TextInput from './TextInput';
|
||||
import styles from './EnhancedSelectInput.css';
|
||||
|
||||
const MINIMUM_DISTANCE_FROM_EDGE = 10;
|
||||
|
||||
function isArrowKey(keyCode) {
|
||||
return keyCode === keyCodes.UP_ARROW || keyCode === keyCodes.DOWN_ARROW;
|
||||
}
|
||||
@@ -139,9 +137,18 @@ class EnhancedSelectInput extends Component {
|
||||
// Listeners
|
||||
|
||||
onComputeMaxHeight = (data) => {
|
||||
const {
|
||||
top,
|
||||
bottom
|
||||
} = data.offsets.reference;
|
||||
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
data.styles.maxHeight = windowHeight - MINIMUM_DISTANCE_FROM_EDGE;
|
||||
if ((/^botton/).test(data.placement)) {
|
||||
data.styles.maxHeight = windowHeight - bottom;
|
||||
} else {
|
||||
data.styles.maxHeight = top;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
@@ -450,10 +457,6 @@ class EnhancedSelectInput extends Component {
|
||||
order: 851,
|
||||
enabled: true,
|
||||
fn: this.onComputeMaxHeight
|
||||
},
|
||||
preventOverflow: {
|
||||
enabled: true,
|
||||
boundariesElement: 'viewport'
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -28,7 +28,8 @@ function createMapStateToProps() {
|
||||
if (includeNoChange) {
|
||||
values.unshift({
|
||||
key: 'noChange',
|
||||
value: translate('NoChange'),
|
||||
value: '',
|
||||
name: translate('NoChange'),
|
||||
isDisabled: includeNoChangeDisabled,
|
||||
isMissing: false
|
||||
});
|
||||
@@ -38,6 +39,7 @@ function createMapStateToProps() {
|
||||
values.push({
|
||||
key: '',
|
||||
value: '',
|
||||
name: '',
|
||||
isDisabled: true,
|
||||
isHidden: true
|
||||
});
|
||||
@@ -54,7 +56,8 @@ function createMapStateToProps() {
|
||||
|
||||
values.push({
|
||||
key: ADD_NEW_KEY,
|
||||
value: 'Add a new path'
|
||||
value: '',
|
||||
name: 'Add a new path'
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -102,27 +105,6 @@ class RootFolderSelectInputConnector extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
name,
|
||||
value,
|
||||
values,
|
||||
onChange
|
||||
} = this.props;
|
||||
|
||||
if (prevProps.values === values) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value && values.length && values.some((v) => !!v.key && v.key !== ADD_NEW_KEY)) {
|
||||
const defaultValue = values[0];
|
||||
|
||||
if (defaultValue.key !== ADD_NEW_KEY) {
|
||||
onChange({ name, value: defaultValue.key });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
|
||||
@@ -13,15 +13,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.value {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.authorFolder {
|
||||
flex: 0 0 auto;
|
||||
color: var(--disabledColor);
|
||||
}
|
||||
|
||||
.freeSpace {
|
||||
margin-left: 15px;
|
||||
color: var(--darkGray);
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'authorFolder': string;
|
||||
'freeSpace': string;
|
||||
'isMissing': string;
|
||||
'isMobile': string;
|
||||
'optionText': string;
|
||||
'value': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
|
||||
@@ -7,24 +7,18 @@ import styles from './RootFolderSelectInputOption.css';
|
||||
|
||||
function RootFolderSelectInputOption(props) {
|
||||
const {
|
||||
id,
|
||||
value,
|
||||
name,
|
||||
freeSpace,
|
||||
authorFolder,
|
||||
isMissing,
|
||||
isMobile,
|
||||
isWindows,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const slashCharacter = isWindows ? '\\' : '/';
|
||||
|
||||
const text = name === '' ? value : `[${name}] ${value}`;
|
||||
const text = value === '' ? name : `${name} [${value}]`;
|
||||
|
||||
return (
|
||||
<EnhancedSelectInputOption
|
||||
id={id}
|
||||
isMobile={isMobile}
|
||||
{...otherProps}
|
||||
>
|
||||
@@ -33,18 +27,7 @@ function RootFolderSelectInputOption(props) {
|
||||
isMobile && styles.isMobile
|
||||
)}
|
||||
>
|
||||
<div className={styles.value}>
|
||||
{text}
|
||||
|
||||
{
|
||||
authorFolder && id !== 'addNew' ?
|
||||
<div className={styles.authorFolder}>
|
||||
{slashCharacter}
|
||||
{authorFolder}
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
<div>{text}</div>
|
||||
|
||||
{
|
||||
freeSpace == null ?
|
||||
@@ -67,18 +50,11 @@ function RootFolderSelectInputOption(props) {
|
||||
}
|
||||
|
||||
RootFolderSelectInputOption.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
freeSpace: PropTypes.number,
|
||||
authorFolder: PropTypes.string,
|
||||
isMissing: PropTypes.bool,
|
||||
isMobile: PropTypes.bool.isRequired,
|
||||
isWindows: PropTypes.bool
|
||||
};
|
||||
|
||||
RootFolderSelectInputOption.defaultProps = {
|
||||
name: ''
|
||||
isMobile: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default RootFolderSelectInputOption;
|
||||
|
||||
@@ -7,20 +7,10 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pathContainer {
|
||||
@add-mixin truncate;
|
||||
display: flex;
|
||||
flex: 1 0 0;
|
||||
}
|
||||
|
||||
.path {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.authorFolder {
|
||||
@add-mixin truncate;
|
||||
flex: 0 1 auto;
|
||||
color: var(--disabledColor);
|
||||
|
||||
flex: 1 0 0;
|
||||
}
|
||||
|
||||
.freeSpace {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'authorFolder': string;
|
||||
'freeSpace': string;
|
||||
'path': string;
|
||||
'pathContainer': string;
|
||||
'selectedValue': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
|
||||
@@ -9,34 +9,19 @@ function RootFolderSelectInputSelectedValue(props) {
|
||||
name,
|
||||
value,
|
||||
freeSpace,
|
||||
authorFolder,
|
||||
includeFreeSpace,
|
||||
isWindows,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const slashCharacter = isWindows ? '\\' : '/';
|
||||
|
||||
const text = name === '' ? value : `[${name}] ${value}`;
|
||||
const text = value === '' ? name : `${name} [${value}]`;
|
||||
|
||||
return (
|
||||
<EnhancedSelectInputSelectedValue
|
||||
className={styles.selectedValue}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.pathContainer}>
|
||||
<div className={styles.path}>
|
||||
{text}
|
||||
</div>
|
||||
|
||||
{
|
||||
authorFolder ?
|
||||
<div className={styles.authorFolder}>
|
||||
{slashCharacter}
|
||||
{authorFolder}
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
<div className={styles.path}>
|
||||
{text}
|
||||
</div>
|
||||
|
||||
{
|
||||
@@ -53,13 +38,10 @@ RootFolderSelectInputSelectedValue.propTypes = {
|
||||
name: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
freeSpace: PropTypes.number,
|
||||
authorFolder: PropTypes.string,
|
||||
isWindows: PropTypes.bool,
|
||||
includeFreeSpace: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
RootFolderSelectInputSelectedValue.defaultProps = {
|
||||
name: '',
|
||||
includeFreeSpace: true
|
||||
};
|
||||
|
||||
|
||||
@@ -52,7 +52,6 @@ class SelectInput extends Component {
|
||||
const {
|
||||
key,
|
||||
value: optionValue,
|
||||
isDisabled: optionIsDisabled = false,
|
||||
...otherOptionProps
|
||||
} = option;
|
||||
|
||||
@@ -60,7 +59,6 @@ class SelectInput extends Component {
|
||||
<option
|
||||
key={key}
|
||||
value={key}
|
||||
disabled={optionIsDisabled}
|
||||
{...otherOptionProps}
|
||||
>
|
||||
{typeof optionValue === 'function' ? optionValue() : optionValue}
|
||||
|
||||
@@ -83,6 +83,13 @@
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointMedium) {
|
||||
.modal.small,
|
||||
.modal.medium {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.modalContainer {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
line-height: 1.52857143;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointMedium) {
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.cell {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointMedium) {
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.cell {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointMedium) {
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.tableContainer {
|
||||
min-width: 100%;
|
||||
width: fit-content;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointMedium) {
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.headerCell {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointMedium) {
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.pager {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointMedium) {
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.headerCell {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -35,12 +35,11 @@
|
||||
.message {
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
font-size: $largeFontSize;
|
||||
}
|
||||
|
||||
.helpText {
|
||||
margin-bottom: 10px;
|
||||
font-weight: 300;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,8 +82,7 @@ class AddNewItem extends Component {
|
||||
render() {
|
||||
const {
|
||||
error,
|
||||
items,
|
||||
hasExistingAuthors
|
||||
items
|
||||
} = this.props;
|
||||
|
||||
const term = this.state.term;
|
||||
@@ -187,8 +186,7 @@ class AddNewItem extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
term ?
|
||||
null :
|
||||
!term &&
|
||||
<div className={styles.message}>
|
||||
<div className={styles.helpText}>
|
||||
{translate('ItsEasyToAddANewAuthorOrBookJustStartTypingTheNameOfTheItemYouWantToAdd')}
|
||||
@@ -201,24 +199,6 @@ class AddNewItem extends Component {
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!term && !hasExistingAuthors ?
|
||||
<div className={styles.message}>
|
||||
<div className={styles.noAuthorsText}>
|
||||
You haven't added any authors yet, do you want to add an existing library location (Root Folder) and update?
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
to="/settings/mediamanagement"
|
||||
kind={kinds.PRIMARY}
|
||||
>
|
||||
{translate('AddRootFolder')}
|
||||
</Button>
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
<div />
|
||||
</PageContentBody>
|
||||
</PageContent>
|
||||
@@ -233,7 +213,6 @@ AddNewItem.propTypes = {
|
||||
isAdding: PropTypes.bool.isRequired,
|
||||
addError: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
hasExistingAuthors: PropTypes.bool.isRequired,
|
||||
onSearchChange: PropTypes.func.isRequired,
|
||||
onClearSearch: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -10,15 +10,13 @@ import AddNewItem from './AddNewItem';
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.search,
|
||||
(state) => state.authors.items.length,
|
||||
(state) => state.router.location,
|
||||
(search, existingAuthorsCount, location) => {
|
||||
(search, location) => {
|
||||
const { params } = parseUrl(location.search);
|
||||
|
||||
return {
|
||||
...search,
|
||||
term: params.term,
|
||||
hasExistingAuthors: existingAuthorsCount > 0
|
||||
...search
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -9,7 +9,6 @@ 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';
|
||||
import AddAuthorOptionsForm from '../Common/AddAuthorOptionsForm.js';
|
||||
import styles from './AddNewAuthorModalContent.css';
|
||||
|
||||
@@ -55,7 +54,7 @@ class AddNewAuthorModalContent extends Component {
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('AddNewAuthor')}
|
||||
Add new Author
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
@@ -134,7 +133,7 @@ class AddNewAuthorModalContent extends Component {
|
||||
|
||||
AddNewAuthorModalContent.propTypes = {
|
||||
authorName: PropTypes.string.isRequired,
|
||||
disambiguation: PropTypes.string,
|
||||
disambiguation: PropTypes.string.isRequired,
|
||||
overview: PropTypes.string,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isAdding: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -4,7 +4,6 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { addAuthor, setAuthorAddDefault } from 'Store/Actions/searchActions';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||
import selectSettings from 'Store/Selectors/selectSettings';
|
||||
import AddNewAuthorModalContent from './AddNewAuthorModalContent';
|
||||
|
||||
@@ -13,8 +12,7 @@ function createMapStateToProps() {
|
||||
(state) => state.search,
|
||||
(state) => state.settings.metadataProfiles,
|
||||
createDimensionsSelector(),
|
||||
createSystemStatusSelector(),
|
||||
(searchState, metadataProfiles, dimensions, systemStatus) => {
|
||||
(searchState, metadataProfiles, dimensions) => {
|
||||
const {
|
||||
isAdding,
|
||||
addError,
|
||||
@@ -34,7 +32,6 @@ function createMapStateToProps() {
|
||||
isSmallScreen: dimensions.isSmallScreen,
|
||||
validationErrors,
|
||||
validationWarnings,
|
||||
isWindows: systemStatus.isWindows,
|
||||
...settings
|
||||
};
|
||||
}
|
||||
|
||||
@@ -78,7 +78,6 @@ class AddNewAuthorSearchResult extends Component {
|
||||
status,
|
||||
overview,
|
||||
ratings,
|
||||
folder,
|
||||
images,
|
||||
isExistingAuthor,
|
||||
isSmallScreen
|
||||
@@ -206,7 +205,6 @@ class AddNewAuthorSearchResult extends Component {
|
||||
disambiguation={disambiguation}
|
||||
year={year}
|
||||
overview={overview}
|
||||
folder={folder}
|
||||
images={images}
|
||||
onModalClose={this.onAddAuthorModalClose}
|
||||
/>
|
||||
@@ -224,7 +222,6 @@ AddNewAuthorSearchResult.propTypes = {
|
||||
status: PropTypes.string.isRequired,
|
||||
overview: PropTypes.string,
|
||||
ratings: PropTypes.object.isRequired,
|
||||
folder: PropTypes.string.isRequired,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isExistingAuthor: PropTypes.bool.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired
|
||||
|
||||
@@ -10,7 +10,6 @@ import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import stripHtml from 'Utilities/String/stripHtml';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AddAuthorOptionsForm from '../Common/AddAuthorOptionsForm.js';
|
||||
import styles from './AddNewBookModalContent.css';
|
||||
|
||||
@@ -59,7 +58,7 @@ class AddNewBookModalContent extends Component {
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('AddNewBook')}
|
||||
Add new Book
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { addBook, setBookAddDefault } from 'Store/Actions/searchActions';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||
import selectSettings from 'Store/Selectors/selectSettings';
|
||||
import AddNewBookModalContent from './AddNewBookModalContent';
|
||||
|
||||
@@ -14,8 +13,7 @@ function createMapStateToProps() {
|
||||
(state) => state.search,
|
||||
(state) => state.settings.metadataProfiles,
|
||||
createDimensionsSelector(),
|
||||
createSystemStatusSelector(),
|
||||
(isExistingAuthor, searchState, metadataProfiles, dimensions, systemStatus) => {
|
||||
(isExistingAuthor, searchState, metadataProfiles, dimensions) => {
|
||||
const {
|
||||
isAdding,
|
||||
addError,
|
||||
@@ -35,7 +33,6 @@ function createMapStateToProps() {
|
||||
isSmallScreen: dimensions.isSmallScreen,
|
||||
validationErrors,
|
||||
validationWarnings,
|
||||
isWindows: systemStatus.isWindows,
|
||||
...settings
|
||||
};
|
||||
}
|
||||
|
||||
@@ -203,7 +203,6 @@ class AddNewBookSearchResult extends Component {
|
||||
disambiguation={disambiguation}
|
||||
authorName={author.authorName}
|
||||
overview={overview}
|
||||
folder={author.folder}
|
||||
images={images}
|
||||
onModalClose={this.onAddBookModalClose}
|
||||
/>
|
||||
|
||||
@@ -39,9 +39,7 @@ class AddAuthorOptionsForm extends Component {
|
||||
includeNoneMetadataProfile,
|
||||
includeSpecificBookMonitor,
|
||||
showMetadataProfile,
|
||||
folder,
|
||||
tags,
|
||||
isWindows,
|
||||
onInputChange,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
@@ -56,15 +54,6 @@ class AddAuthorOptionsForm extends Component {
|
||||
<FormInputGroup
|
||||
type={inputTypes.ROOT_FOLDER_SELECT}
|
||||
name="rootFolderPath"
|
||||
valueOptions={{
|
||||
authorFolder: folder,
|
||||
isWindows
|
||||
}}
|
||||
selectedValueOptions={{
|
||||
authorFolder: folder,
|
||||
isWindows
|
||||
}}
|
||||
helpText={translate('AddNewAuthorRootFolderHelpText', { folder })}
|
||||
onChange={onInputChange}
|
||||
{...rootFolderPath}
|
||||
/>
|
||||
@@ -190,14 +179,8 @@ AddAuthorOptionsForm.propTypes = {
|
||||
showMetadataProfile: PropTypes.bool.isRequired,
|
||||
includeNoneMetadataProfile: PropTypes.bool.isRequired,
|
||||
includeSpecificBookMonitor: PropTypes.bool.isRequired,
|
||||
folder: PropTypes.string.isRequired,
|
||||
tags: PropTypes.object.isRequired,
|
||||
isWindows: PropTypes.bool.isRequired,
|
||||
onInputChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
AddAuthorOptionsForm.defaultProps = {
|
||||
includeSpecificBookMonitor: false
|
||||
};
|
||||
|
||||
export default AddAuthorOptionsForm;
|
||||
|
||||
@@ -10,11 +10,11 @@ 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 Column from 'Components/Table/Column';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import useSelectState from 'Helpers/Hooks/useSelectState';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import SortDirection from 'Helpers/Props/SortDirection';
|
||||
import {
|
||||
bulkDeleteDownloadClients,
|
||||
bulkEditDownloadClients,
|
||||
@@ -35,7 +35,7 @@ type OnSelectedChangeCallback = React.ComponentProps<
|
||||
typeof ManageDownloadClientsModalRow
|
||||
>['onSelectedChange'];
|
||||
|
||||
const COLUMNS: Column[] = [
|
||||
const COLUMNS = [
|
||||
{
|
||||
name: 'name',
|
||||
label: () => translate('Name'),
|
||||
@@ -82,6 +82,8 @@ const COLUMNS: Column[] = [
|
||||
|
||||
interface ManageDownloadClientsModalContentProps {
|
||||
onModalClose(): void;
|
||||
sortKey?: string;
|
||||
sortDirection?: SortDirection;
|
||||
}
|
||||
|
||||
function ManageDownloadClientsModalContent(
|
||||
|
||||
@@ -76,7 +76,7 @@ function EditImportListExclusionModalContent(props) {
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
{translate('ForeignId')}
|
||||
{translate('MusicbrainzId')}
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
|
||||
@@ -10,11 +10,11 @@ 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 Column from 'Components/Table/Column';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import useSelectState from 'Helpers/Hooks/useSelectState';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import SortDirection from 'Helpers/Props/SortDirection';
|
||||
import {
|
||||
bulkDeleteIndexers,
|
||||
bulkEditIndexers,
|
||||
@@ -35,7 +35,7 @@ type OnSelectedChangeCallback = React.ComponentProps<
|
||||
typeof ManageIndexersModalRow
|
||||
>['onSelectedChange'];
|
||||
|
||||
const COLUMNS: Column[] = [
|
||||
const COLUMNS = [
|
||||
{
|
||||
name: 'name',
|
||||
label: () => translate('Name'),
|
||||
@@ -82,6 +82,8 @@ const COLUMNS: Column[] = [
|
||||
|
||||
interface ManageIndexersModalContentProps {
|
||||
onModalClose(): void;
|
||||
sortKey?: string;
|
||||
sortDirection?: SortDirection;
|
||||
}
|
||||
|
||||
function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
interface QualityProfileNameProps {
|
||||
qualityProfileId: number;
|
||||
}
|
||||
|
||||
function QualityProfileName({ qualityProfileId }: QualityProfileNameProps) {
|
||||
const qualityProfile = useSelector(
|
||||
createQualityProfileSelectorForHook(qualityProfileId)
|
||||
);
|
||||
|
||||
return <span>{qualityProfile?.name ?? translate('Unknown')}</span>;
|
||||
}
|
||||
|
||||
export default QualityProfileName;
|
||||
@@ -0,0 +1,31 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createQualityProfileSelector from 'Store/Selectors/createQualityProfileSelector';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createQualityProfileSelector(),
|
||||
(qualityProfile) => {
|
||||
return {
|
||||
name: qualityProfile.name
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function QualityProfileNameConnector({ name, ...otherProps }) {
|
||||
return (
|
||||
<span>
|
||||
{name}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
QualityProfileNameConnector.propTypes = {
|
||||
qualityProfileId: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps)(QualityProfileNameConnector);
|
||||
@@ -45,12 +45,6 @@ export const defaultState = {
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'books.lastSearchTime',
|
||||
label: 'Last Searched',
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
columnLabel: 'Actions',
|
||||
@@ -114,12 +108,6 @@ export const defaultState = {
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'books.lastSearchTime',
|
||||
label: 'Last Searched',
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
columnLabel: 'Actions',
|
||||
|
||||
@@ -27,12 +27,6 @@ export default function translate(
|
||||
key: string,
|
||||
tokens: Record<string, string | number | boolean> = {}
|
||||
) {
|
||||
const { isProduction = true } = window.Readarr;
|
||||
|
||||
if (!isProduction && !(key in translations)) {
|
||||
console.warn(`Missing translation for key: ${key}`);
|
||||
}
|
||||
|
||||
const translation = translations[key] || key;
|
||||
|
||||
tokens.appName = 'Readarr';
|
||||
|
||||
@@ -131,15 +131,13 @@ class CutoffUnmetConnector extends Component {
|
||||
onSearchSelectedPress = (selected) => {
|
||||
this.props.executeCommand({
|
||||
name: commandNames.BOOK_SEARCH,
|
||||
bookIds: selected,
|
||||
commandFinished: this.repopulate
|
||||
bookIds: selected
|
||||
});
|
||||
};
|
||||
|
||||
onSearchAllCutoffUnmetPress = () => {
|
||||
this.props.executeCommand({
|
||||
name: commandNames.CUTOFF_UNMET_BOOK_SEARCH,
|
||||
commandFinished: this.repopulate
|
||||
name: commandNames.CUTOFF_UNMET_BOOK_SEARCH
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ function CutoffUnmetRow(props) {
|
||||
releaseDate,
|
||||
titleSlug,
|
||||
title,
|
||||
lastSearchTime,
|
||||
disambiguation,
|
||||
isSelected,
|
||||
columns,
|
||||
@@ -69,15 +68,6 @@ function CutoffUnmetRow(props) {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'books.lastSearchTime') {
|
||||
return (
|
||||
<RelativeDateCellConnector
|
||||
key={name}
|
||||
date={lastSearchTime}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'releaseDate') {
|
||||
return (
|
||||
<RelativeDateCellConnector
|
||||
@@ -115,7 +105,6 @@ CutoffUnmetRow.propTypes = {
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
lastSearchTime: PropTypes.string,
|
||||
disambiguation: PropTypes.string,
|
||||
isSelected: PropTypes.bool,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
||||
@@ -121,15 +121,13 @@ class MissingConnector extends Component {
|
||||
onSearchSelectedPress = (selected) => {
|
||||
this.props.executeCommand({
|
||||
name: commandNames.BOOK_SEARCH,
|
||||
bookIds: selected,
|
||||
commandFinished: this.repopulate
|
||||
bookIds: selected
|
||||
});
|
||||
};
|
||||
|
||||
onSearchAllMissingPress = () => {
|
||||
this.props.executeCommand({
|
||||
name: commandNames.MISSING_BOOK_SEARCH,
|
||||
commandFinished: this.repopulate
|
||||
name: commandNames.MISSING_BOOK_SEARCH
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ function MissingRow(props) {
|
||||
releaseDate,
|
||||
titleSlug,
|
||||
title,
|
||||
lastSearchTime,
|
||||
disambiguation,
|
||||
isSelected,
|
||||
columns,
|
||||
@@ -78,15 +77,6 @@ function MissingRow(props) {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'books.lastSearchTime') {
|
||||
return (
|
||||
<RelativeDateCellConnector
|
||||
key={name}
|
||||
date={lastSearchTime}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'actions') {
|
||||
return (
|
||||
<BookSearchCellConnector
|
||||
@@ -114,7 +104,6 @@ MissingRow.propTypes = {
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
lastSearchTime: PropTypes.string,
|
||||
disambiguation: PropTypes.string,
|
||||
isSelected: PropTypes.bool,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
||||
1
frontend/typings/Globals.d.ts
vendored
1
frontend/typings/Globals.d.ts
vendored
@@ -7,6 +7,5 @@ interface Window {
|
||||
theme: string;
|
||||
urlBase: string;
|
||||
version: string;
|
||||
isProduction: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
24
package.json
24
package.json
@@ -25,10 +25,10 @@
|
||||
"defaults"
|
||||
],
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "6.7.1",
|
||||
"@fortawesome/fontawesome-svg-core": "6.7.1",
|
||||
"@fortawesome/free-regular-svg-icons": "6.7.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.7.1",
|
||||
"@fortawesome/fontawesome-free": "6.6.0",
|
||||
"@fortawesome/fontawesome-svg-core": "6.6.0",
|
||||
"@fortawesome/free-regular-svg-icons": "6.6.0",
|
||||
"@fortawesome/free-solid-svg-icons": "6.6.0",
|
||||
"@fortawesome/react-fontawesome": "0.2.2",
|
||||
"@microsoft/signalr": "6.0.25",
|
||||
"@sentry/browser": "7.119.1",
|
||||
@@ -86,13 +86,13 @@
|
||||
"typescript": "5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.26.0",
|
||||
"@babel/eslint-parser": "7.25.9",
|
||||
"@babel/plugin-proposal-export-default-from": "7.25.9",
|
||||
"@babel/core": "7.25.8",
|
||||
"@babel/eslint-parser": "7.25.8",
|
||||
"@babel/plugin-proposal-export-default-from": "7.25.8",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
"@babel/preset-env": "7.26.0",
|
||||
"@babel/preset-react": "7.26.3",
|
||||
"@babel/preset-typescript": "7.26.0",
|
||||
"@babel/preset-env": "7.25.8",
|
||||
"@babel/preset-react": "7.25.7",
|
||||
"@babel/preset-typescript": "7.25.7",
|
||||
"@types/lodash": "4.14.195",
|
||||
"@types/react-lazyload": "3.2.3",
|
||||
"@types/redux-actions": "2.6.5",
|
||||
@@ -102,7 +102,7 @@
|
||||
"babel-loader": "9.2.1",
|
||||
"babel-plugin-inline-classnames": "2.0.1",
|
||||
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
||||
"core-js": "3.39.0",
|
||||
"core-js": "3.38.1",
|
||||
"css-loader": "6.8.1",
|
||||
"css-modules-typescript-loader": "4.0.1",
|
||||
"eslint": "8.57.1",
|
||||
@@ -142,7 +142,7 @@
|
||||
"worker-loader": "3.0.8"
|
||||
},
|
||||
"volta": {
|
||||
"node": "20.11.1",
|
||||
"node": "16.17.0",
|
||||
"yarn": "1.22.19"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,35 +99,6 @@
|
||||
<RootNamespace Condition="'$(ReadarrProject)'=='true'">$(MSBuildProjectName.Replace('Readarr','NzbDrone'))</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TestProject)'!='true'">
|
||||
<!-- Annotates .NET assemblies with repository information including SHA -->
|
||||
<!-- Sentry uses this to link directly to GitHub at the exact version/file/line -->
|
||||
<!-- This is built-in on .NET 8 and can be removed once the project is updated -->
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Sentry specific configuration: Only in Release mode -->
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<!-- https://docs.sentry.io/platforms/dotnet/configuration/msbuild/ -->
|
||||
<!-- OrgSlug, ProjectSlug and AuthToken are required.
|
||||
They can be set below, via argument to 'msbuild -p:' or environment variable -->
|
||||
<SentryOrg></SentryOrg>
|
||||
<SentryProject></SentryProject>
|
||||
<SentryUrl></SentryUrl> <!-- If empty, assumed to be sentry.io -->
|
||||
<SentryAuthToken></SentryAuthToken> <!-- Use env var instead: SENTRY_AUTH_TOKEN -->
|
||||
|
||||
<!-- Upload PDBs to Sentry, enabling stack traces with line numbers and file paths
|
||||
without the need to deploy the application with PDBs -->
|
||||
<SentryUploadSymbols>true</SentryUploadSymbols>
|
||||
|
||||
<!-- Source Link settings -->
|
||||
<!-- https://github.com/dotnet/sourcelink/blob/main/docs/README.md#publishrepositoryurl -->
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<!-- Embeds all source code in the respective PDB. This can make it a bit bigger but since it'll be uploaded
|
||||
to Sentry and not distributed to run on the server, it helps debug crashes while making releases smaller -->
|
||||
<EmbedAllSources>true</EmbedAllSources>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Standard testing packages -->
|
||||
<ItemGroup Condition="'$(TestProject)'=='true'">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
|
||||
<PackageVersion Include="Equ" Version="2.3.0" />
|
||||
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
|
||||
<PackageVersion Include="IPAddressRange" Version="6.1.0" />
|
||||
<PackageVersion Include="Polly" Version="8.5.2" />
|
||||
<PackageVersion Include="Polly" Version="8.4.2" />
|
||||
<PackageVersion Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
|
||||
<PackageVersion Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
|
||||
<PackageVersion Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
|
||||
@@ -18,16 +17,14 @@
|
||||
<PackageVersion Include="Ical.Net" Version="4.3.1" />
|
||||
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
|
||||
<PackageVersion Include="LazyCache" Version="2.4.0" />
|
||||
<PackageVersion Include="Mailkit" Version="4.8.0" />
|
||||
<PackageVersion Include="Mailkit" Version="3.6.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.35" />
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient" Version="2.1.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageVersion Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr22" />
|
||||
<PackageVersion Include="Moq" Version="4.17.2" />
|
||||
@@ -37,28 +34,28 @@
|
||||
<PackageVersion Include="NLog.Extensions.Logging" Version="5.2.3" />
|
||||
<PackageVersion Include="NLog" Version="5.1.4" />
|
||||
<PackageVersion Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
<PackageVersion Include="Npgsql" Version="7.0.10" />
|
||||
<PackageVersion Include="Npgsql" Version="7.0.8" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageVersion Include="NUnit" Version="3.14.0" />
|
||||
<PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||
<PackageVersion Include="PdfSharpCore" Version="1.3.65" />
|
||||
<PackageVersion Include="PdfSharpCore" Version="1.3.32" />
|
||||
<PackageVersion Include="RestSharp.Serializers.SystemTextJson" Version="106.15.0" />
|
||||
<PackageVersion Include="RestSharp" Version="106.15.0" />
|
||||
<PackageVersion Include="Selenium.Support" Version="3.141.0" />
|
||||
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="134.0.6998.16500" />
|
||||
<PackageVersion Include="Sentry" Version="4.0.2" />
|
||||
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
|
||||
<PackageVersion Include="Sentry" Version="3.31.0" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="6.6.2" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.6.2" />
|
||||
<PackageVersion Include="System.Buffers" Version="4.6.0" />
|
||||
<PackageVersion Include="System.Buffers" Version="4.5.1" />
|
||||
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="6.0.1" />
|
||||
<PackageVersion Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="17.0.24" />
|
||||
<PackageVersion Include="System.IO.Abstractions" Version="17.0.24" />
|
||||
<PackageVersion Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
||||
<PackageVersion Include="System.Memory" Version="4.6.2" />
|
||||
<PackageVersion Include="System.Memory" Version="4.5.5" />
|
||||
<PackageVersion Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
||||
<PackageVersion Include="System.Resources.Extensions" Version="6.0.0" />
|
||||
<PackageVersion Include="System.Runtime.Loader" Version="4.3.0" />
|
||||
@@ -66,7 +63,7 @@
|
||||
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.1" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||
<PackageVersion Include="System.Text.Json" Version="6.0.10" />
|
||||
<PackageVersion Include="System.ValueTuple" Version="4.6.1" />
|
||||
<PackageVersion Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageVersion Include="TagLibSharp-Lidarr" Version="2.2.0.19" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -21,28 +21,9 @@ namespace NzbDrone.Common.Test.ExtensionTests
|
||||
[TestCase("1.2.3.4")]
|
||||
[TestCase("172.55.0.1")]
|
||||
[TestCase("192.55.0.1")]
|
||||
[TestCase("100.64.0.1")]
|
||||
[TestCase("100.127.255.254")]
|
||||
public void should_return_false_for_public_ip_address(string ipAddress)
|
||||
{
|
||||
IPAddress.Parse(ipAddress).IsLocalAddress().Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestCase("100.64.0.1")]
|
||||
[TestCase("100.127.255.254")]
|
||||
[TestCase("100.100.100.100")]
|
||||
public void should_return_true_for_cgnat_ip_address(string ipAddress)
|
||||
{
|
||||
IPAddress.Parse(ipAddress).IsCgnatIpAddress().Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("1.2.3.4")]
|
||||
[TestCase("192.168.5.1")]
|
||||
[TestCase("100.63.255.255")]
|
||||
[TestCase("100.128.0.0")]
|
||||
public void should_return_false_for_non_cgnat_ip_address(string ipAddress)
|
||||
{
|
||||
IPAddress.Parse(ipAddress).IsCgnatIpAddress().Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Linq;
|
||||
using FluentAssertions;
|
||||
using NLog;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Instrumentation.Sentry;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
@@ -28,7 +27,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_subject = new SentryTarget("https://aaaaaaaaaaaaaaaaaaaaaaaaaa@sentry.io/111111", Mocker.GetMock<IAppFolderInfo>().Object);
|
||||
_subject = new SentryTarget("https://aaaaaaaaaaaaaaaaaaaaaaaaaa@sentry.io/111111");
|
||||
}
|
||||
|
||||
private LogEventInfo GivenLogEvent(LogLevel level, Exception ex, string message)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -316,5 +317,14 @@ namespace NzbDrone.Common.Test
|
||||
result[2].Should().Be(@"Music");
|
||||
result[3].Should().Be(@"Author Title");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_equal_with_different_unicode_representations()
|
||||
{
|
||||
var path1 = @"C:\Test\file.mkv".AsOsAgnostic().Normalize(NormalizationForm.FormC);
|
||||
var path2 = @"C:\Test\file.mkv".AsOsAgnostic().Normalize(NormalizationForm.FormD);
|
||||
|
||||
path1.PathEquals(path2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,18 +42,17 @@ namespace NzbDrone.Common
|
||||
|
||||
public void CreateZip(string path, IEnumerable<string> files)
|
||||
{
|
||||
_logger.Debug("Creating archive {0}", path);
|
||||
|
||||
using var zipFile = ZipFile.Create(path);
|
||||
|
||||
zipFile.BeginUpdate();
|
||||
|
||||
foreach (var file in files)
|
||||
using (var zipFile = ZipFile.Create(path))
|
||||
{
|
||||
zipFile.Add(file, Path.GetFileName(file));
|
||||
}
|
||||
zipFile.BeginUpdate();
|
||||
|
||||
zipFile.CommitUpdate();
|
||||
foreach (var file in files)
|
||||
{
|
||||
zipFile.Add(file, Path.GetFileName(file));
|
||||
}
|
||||
|
||||
zipFile.CommitUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private void ExtractZip(string compressedFile, string destination)
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
@@ -307,26 +306,9 @@ namespace NzbDrone.Common.Disk
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
var files = GetFiles(path, recursive).ToList();
|
||||
var files = GetFiles(path, recursive);
|
||||
|
||||
files.ForEach(RemoveReadOnly);
|
||||
|
||||
var attempts = 0;
|
||||
|
||||
while (attempts < 3 && files.Any())
|
||||
{
|
||||
EmptyFolder(path);
|
||||
|
||||
if (GetFiles(path, recursive).Any())
|
||||
{
|
||||
// Wait for IO operations to complete after emptying the folder since they aren't always
|
||||
// instantly removed and it can lead to false positives that files are still present.
|
||||
Thread.Sleep(3000);
|
||||
}
|
||||
|
||||
attempts++;
|
||||
files = GetFiles(path, recursive).ToList();
|
||||
}
|
||||
files.ToList().ForEach(RemoveReadOnly);
|
||||
|
||||
_fileSystem.Directory.Delete(path, recursive);
|
||||
}
|
||||
|
||||
@@ -342,11 +342,10 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
var isCifs = targetDriveFormat == "cifs";
|
||||
var isBtrfs = sourceDriveFormat == "btrfs" && targetDriveFormat == "btrfs";
|
||||
var isZfs = sourceDriveFormat == "zfs" && targetDriveFormat == "zfs";
|
||||
|
||||
if (mode.HasFlag(TransferMode.Copy))
|
||||
{
|
||||
if (isBtrfs || isZfs)
|
||||
if (isBtrfs)
|
||||
{
|
||||
if (_diskProvider.TryCreateRefLink(sourcePath, targetPath))
|
||||
{
|
||||
@@ -360,7 +359,7 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
if (mode.HasFlag(TransferMode.Move))
|
||||
{
|
||||
if (isBtrfs || isZfs)
|
||||
if (isBtrfs)
|
||||
{
|
||||
if (isSameMount && _diskProvider.TryRenameFile(sourcePath, targetPath))
|
||||
{
|
||||
|
||||
@@ -39,24 +39,18 @@ namespace NzbDrone.Common.Extensions
|
||||
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)
|
||||
var isLinkLocal = ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;
|
||||
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)
|
||||
var isClassA = ipv4Bytes[0] == 10;
|
||||
bool IsClassA() => ipv4Bytes[0] == 10;
|
||||
|
||||
// Class B private range: 172.16.0.0 – 172.31.255.255 (172.16.0.0/12)
|
||||
var isClassB = ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;
|
||||
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)
|
||||
var isClassC = ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;
|
||||
bool IsClassC() => ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;
|
||||
|
||||
return isLinkLocal || isClassA || isClassC || isClassB;
|
||||
}
|
||||
|
||||
public static bool IsCgnatIpAddress(this IPAddress ipAddress)
|
||||
{
|
||||
var bytes = ipAddress.GetAddressBytes();
|
||||
return bytes.Length == 4 && bytes[0] == 100 && bytes[1] >= 64 && bytes[1] <= 127;
|
||||
return IsLinkLocal() || IsClassA() || IsClassC() || IsClassB();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,10 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
public static bool PathEquals(this string firstPath, string secondPath, StringComparison? comparison = null)
|
||||
{
|
||||
// Normalize paths to ensure unicode characters are represented the same way
|
||||
firstPath = firstPath.Normalize();
|
||||
secondPath = secondPath?.Normalize();
|
||||
|
||||
if (!comparison.HasValue)
|
||||
{
|
||||
comparison = DiskProviderBase.PathStringComparison;
|
||||
@@ -108,15 +112,6 @@ namespace NzbDrone.Common.Extensions
|
||||
return Directory.GetParent(cleanPath)?.FullName;
|
||||
}
|
||||
|
||||
public static string GetCleanPath(this string path)
|
||||
{
|
||||
var cleanPath = OsInfo.IsWindows
|
||||
? PARENT_PATH_END_SLASH_REGEX.Replace(path, "")
|
||||
: path.TrimEnd(Path.DirectorySeparatorChar);
|
||||
|
||||
return cleanPath;
|
||||
}
|
||||
|
||||
public static bool IsParentPath(this string parentPath, string childPath)
|
||||
{
|
||||
if (parentPath != "/" && !parentPath.EndsWith(":\\"))
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Http.Proxy
|
||||
@@ -30,8 +29,7 @@ namespace NzbDrone.Common.Http.Proxy
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(BypassFilter))
|
||||
{
|
||||
var hostlist = BypassFilter.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
var hostlist = BypassFilter.Split(',');
|
||||
for (var i = 0; i < hostlist.Length; i++)
|
||||
{
|
||||
if (hostlist[i].StartsWith("*"))
|
||||
@@ -43,7 +41,7 @@ namespace NzbDrone.Common.Http.Proxy
|
||||
return hostlist;
|
||||
}
|
||||
|
||||
return Array.Empty<string>();
|
||||
return new string[] { };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,27 +4,27 @@ namespace NzbDrone.Common.Instrumentation.Extensions
|
||||
{
|
||||
public static class LoggerExtensions
|
||||
{
|
||||
[MessageTemplateFormatMethod("message")]
|
||||
public static void ProgressInfo(this Logger logger, string message, params object[] args)
|
||||
{
|
||||
LogProgressMessage(logger, LogLevel.Info, message, args);
|
||||
var formattedMessage = string.Format(message, args);
|
||||
LogProgressMessage(logger, LogLevel.Info, formattedMessage);
|
||||
}
|
||||
|
||||
[MessageTemplateFormatMethod("message")]
|
||||
public static void ProgressDebug(this Logger logger, string message, params object[] args)
|
||||
{
|
||||
LogProgressMessage(logger, LogLevel.Debug, message, args);
|
||||
var formattedMessage = string.Format(message, args);
|
||||
LogProgressMessage(logger, LogLevel.Debug, formattedMessage);
|
||||
}
|
||||
|
||||
[MessageTemplateFormatMethod("message")]
|
||||
public static void ProgressTrace(this Logger logger, string message, params object[] args)
|
||||
{
|
||||
LogProgressMessage(logger, LogLevel.Trace, message, args);
|
||||
var formattedMessage = string.Format(message, args);
|
||||
LogProgressMessage(logger, LogLevel.Trace, formattedMessage);
|
||||
}
|
||||
|
||||
private static void LogProgressMessage(Logger logger, LogLevel level, string message, object[] parameters)
|
||||
private static void LogProgressMessage(Logger logger, LogLevel level, string message)
|
||||
{
|
||||
var logEvent = new LogEventInfo(level, logger.Name, null, message, parameters);
|
||||
var logEvent = new LogEventInfo(level, logger.Name, message);
|
||||
logEvent.Properties.Add("Status", "");
|
||||
|
||||
logger.Log(logEvent);
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
RegisterDebugger();
|
||||
}
|
||||
|
||||
RegisterSentry(updateApp, appFolderInfo);
|
||||
RegisterSentry(updateApp);
|
||||
|
||||
if (updateApp)
|
||||
{
|
||||
@@ -62,7 +62,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
LogManager.ReconfigExistingLoggers();
|
||||
}
|
||||
|
||||
private static void RegisterSentry(bool updateClient, IAppFolderInfo appFolderInfo)
|
||||
private static void RegisterSentry(bool updateClient)
|
||||
{
|
||||
string dsn;
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
: "https://31e00a6c63ea42c8b5fe70358526a30d@sentry.servarr.com/4";
|
||||
}
|
||||
|
||||
var target = new SentryTarget(dsn, appFolderInfo)
|
||||
var target = new SentryTarget(dsn)
|
||||
{
|
||||
Name = "sentryTarget",
|
||||
Layout = "${message}"
|
||||
|
||||
@@ -9,7 +9,6 @@ using NLog;
|
||||
using NLog.Common;
|
||||
using NLog.Targets;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using Sentry;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
@@ -100,7 +99,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
public bool FilterEvents { get; set; }
|
||||
public bool SentryEnabled { get; set; }
|
||||
|
||||
public SentryTarget(string dsn, IAppFolderInfo appFolderInfo)
|
||||
public SentryTarget(string dsn)
|
||||
{
|
||||
_sdk = SentrySdk.Init(o =>
|
||||
{
|
||||
@@ -108,33 +107,9 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
o.AttachStacktrace = true;
|
||||
o.MaxBreadcrumbs = 200;
|
||||
o.Release = $"{BuildInfo.AppName}@{BuildInfo.Release}";
|
||||
o.SetBeforeSend(x => SentryCleanser.CleanseEvent(x));
|
||||
o.SetBeforeBreadcrumb(x => SentryCleanser.CleanseBreadcrumb(x));
|
||||
o.BeforeSend = x => SentryCleanser.CleanseEvent(x);
|
||||
o.BeforeBreadcrumb = x => SentryCleanser.CleanseBreadcrumb(x);
|
||||
o.Environment = BuildInfo.Branch;
|
||||
|
||||
// Crash free run statistics (sends a ping for healthy and for crashes sessions)
|
||||
o.AutoSessionTracking = false;
|
||||
|
||||
// Caches files in the event device is offline
|
||||
// Sentry creates a 'sentry' sub directory, no need to concat here
|
||||
o.CacheDirectoryPath = appFolderInfo.GetAppDataPath();
|
||||
|
||||
// default environment is production
|
||||
if (!RuntimeInfo.IsProduction)
|
||||
{
|
||||
if (RuntimeInfo.IsDevelopment)
|
||||
{
|
||||
o.Environment = "development";
|
||||
}
|
||||
else if (RuntimeInfo.IsTesting)
|
||||
{
|
||||
o.Environment = "testing";
|
||||
}
|
||||
else
|
||||
{
|
||||
o.Environment = "other";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
InitializeScope();
|
||||
@@ -152,7 +127,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
{
|
||||
SentrySdk.ConfigureScope(scope =>
|
||||
{
|
||||
scope.User = new SentryUser
|
||||
scope.User = new User
|
||||
{
|
||||
Id = HashUtil.AnonymousToken()
|
||||
};
|
||||
@@ -194,7 +169,9 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
|
||||
private void OnError(Exception ex)
|
||||
{
|
||||
if (ex is WebException webException)
|
||||
var webException = ex as WebException;
|
||||
|
||||
if (webException != null)
|
||||
{
|
||||
var response = webException.Response as HttpWebResponse;
|
||||
var statusCode = response?.StatusCode;
|
||||
@@ -313,21 +290,13 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
}
|
||||
}
|
||||
|
||||
var level = LoggingLevelMap[logEvent.Level];
|
||||
var sentryEvent = new SentryEvent(logEvent.Exception)
|
||||
{
|
||||
Level = level,
|
||||
Level = LoggingLevelMap[logEvent.Level],
|
||||
Logger = logEvent.LoggerName,
|
||||
Message = logEvent.FormattedMessage
|
||||
};
|
||||
|
||||
if (level is SentryLevel.Fatal && logEvent.Exception is not null)
|
||||
{
|
||||
// Usages of 'fatal' here indicates the process will crash. In Sentry this is represented with
|
||||
// the 'unhandled' exception flag
|
||||
logEvent.Exception.SetSentryMechanism("Logger.Fatal", "Logger.Fatal was called", false);
|
||||
}
|
||||
|
||||
sentryEvent.SetExtras(extras);
|
||||
sentryEvent.SetFingerprint(fingerPrint);
|
||||
|
||||
|
||||
@@ -6,5 +6,4 @@ public class AuthOptions
|
||||
public bool? Enabled { get; set; }
|
||||
public string Method { get; set; }
|
||||
public string Required { get; set; }
|
||||
public bool? TrustCgnatIpAddresses { get; set; }
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ namespace NzbDrone.Common
|
||||
{
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
return obj.CleanFilePath().ToLower().GetHashCode();
|
||||
return obj.CleanFilePath().Normalize().ToLower().GetHashCode();
|
||||
}
|
||||
|
||||
return obj.CleanFilePath().GetHashCode();
|
||||
return obj.CleanFilePath().Normalize().GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Model;
|
||||
@@ -118,9 +117,7 @@ namespace NzbDrone.Common.Processes
|
||||
UseShellExecute = false,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardInput = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
RedirectStandardInput = true
|
||||
};
|
||||
|
||||
if (environmentVariables != null)
|
||||
@@ -316,7 +313,7 @@ namespace NzbDrone.Common.Processes
|
||||
processInfo = new ProcessInfo();
|
||||
processInfo.Id = process.Id;
|
||||
processInfo.Name = process.ProcessName;
|
||||
processInfo.StartPath = process.MainModule?.FileName;
|
||||
processInfo.StartPath = process.MainModule.FileName;
|
||||
|
||||
if (process.Id != GetCurrentProcessId() && process.HasExited)
|
||||
{
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DryIoc.dll" />
|
||||
<PackageReference Include="IPAddressRange" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" />
|
||||
|
||||
@@ -178,9 +178,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
VerifyWarning(item);
|
||||
}
|
||||
|
||||
[TestCase("pausedDL")]
|
||||
[TestCase("stoppedDL")]
|
||||
public void paused_item_should_have_required_properties(string state)
|
||||
[Test]
|
||||
public void paused_item_should_have_required_properties()
|
||||
{
|
||||
var torrent = new QBittorrentTorrent
|
||||
{
|
||||
@@ -189,7 +188,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
Size = 1000,
|
||||
Progress = 0.7,
|
||||
Eta = 8640000,
|
||||
State = state,
|
||||
State = "pausedDL",
|
||||
Label = "",
|
||||
SavePath = ""
|
||||
};
|
||||
@@ -201,7 +200,6 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
[TestCase("queuedUP")]
|
||||
[TestCase("uploading")]
|
||||
[TestCase("stalledUP")]
|
||||
@@ -399,9 +397,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
result.OutputPath.FullPath.Should().Be(Path.Combine(torrent.SavePath, "Droned.S01.12"));
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void api_261_should_use_content_path(string state)
|
||||
[Test]
|
||||
public void api_261_should_use_content_path()
|
||||
{
|
||||
var torrent = new QBittorrentTorrent
|
||||
{
|
||||
@@ -410,7 +407,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
Size = 1000,
|
||||
Progress = 0.7,
|
||||
Eta = 8640000,
|
||||
State = state,
|
||||
State = "pausedUP",
|
||||
Label = "",
|
||||
SavePath = @"C:\Torrents".AsOsAgnostic(),
|
||||
ContentPath = @"C:\Torrents\Droned.S01.12".AsOsAgnostic()
|
||||
@@ -687,96 +684,44 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
item.CanMoveFiles.Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio_is_not_set(string state)
|
||||
[Test]
|
||||
public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio_is_not_set()
|
||||
{
|
||||
GivenGlobalSeedLimits(-1);
|
||||
GivenCompletedTorrent(state, ratio: 1.0f);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 1.0f);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeFalse();
|
||||
item.CanMoveFiles.Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_ratio_reached_and_paused(string state)
|
||||
[Test]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_ratio_reached_and_paused()
|
||||
{
|
||||
GivenGlobalSeedLimits(1.0f);
|
||||
GivenCompletedTorrent(state, ratio: 1.0f);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 1.0f);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_ratio_reached_after_rounding_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(1.0f);
|
||||
GivenCompletedTorrent(state, ratio: 1.1006066990976857f);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_just_under_max_ratio_reached_after_rounding_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(1.0f);
|
||||
GivenCompletedTorrent(state, ratio: 0.9999f);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_and_paused(string state)
|
||||
[Test]
|
||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_and_paused()
|
||||
{
|
||||
GivenGlobalSeedLimits(2.0f);
|
||||
GivenCompletedTorrent(state, ratio: 1.0f, ratioLimit: 0.8f);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 1.0f, ratioLimit: 0.8f);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_after_rounding_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(2.0f);
|
||||
GivenCompletedTorrent(state, ratio: 1.1006066990976857f, ratioLimit: 1.1f);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_just_under_overridden_max_ratio_reached_after_rounding_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(2.0f);
|
||||
GivenCompletedTorrent(state, ratio: 0.9999f, ratioLimit: 1.0f);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_not_be_removable_if_overridden_max_ratio_not_reached_and_paused(string state)
|
||||
[Test]
|
||||
public void should_not_be_removable_if_overridden_max_ratio_not_reached_and_paused()
|
||||
{
|
||||
GivenGlobalSeedLimits(0.2f);
|
||||
GivenCompletedTorrent(state, ratio: 0.5f, ratioLimit: 0.8f);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 0.5f, ratioLimit: 0.8f);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeFalse();
|
||||
@@ -794,36 +739,33 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
item.CanMoveFiles.Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_and_paused(string state)
|
||||
[Test]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_and_paused()
|
||||
{
|
||||
GivenGlobalSeedLimits(-1, 20);
|
||||
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_seedingtime_reached_and_paused(string state)
|
||||
[Test]
|
||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_seedingtime_reached_and_paused()
|
||||
{
|
||||
GivenGlobalSeedLimits(-1, 40);
|
||||
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20, seedingTimeLimit: 10);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20, seedingTimeLimit: 10);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_not_be_removable_if_overridden_max_seedingtime_not_reached_and_paused(string state)
|
||||
[Test]
|
||||
public void should_not_be_removable_if_overridden_max_seedingtime_not_reached_and_paused()
|
||||
{
|
||||
GivenGlobalSeedLimits(-1, 20);
|
||||
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 30, seedingTimeLimit: 40);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 30, seedingTimeLimit: 40);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeFalse();
|
||||
@@ -841,72 +783,66 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
item.CanMoveFiles.Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_and_paused(string state)
|
||||
[Test]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_and_paused()
|
||||
{
|
||||
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
|
||||
GivenCompletedTorrent(state, ratio: 2.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
|
||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_inactive_seedingtime_reached_and_paused(string state)
|
||||
[Test]
|
||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_inactive_seedingtime_reached_and_paused()
|
||||
{
|
||||
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 40);
|
||||
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20, inactiveSeedingTimeLimit: 10, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(15)).ToUnixTimeSeconds());
|
||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20, inactiveSeedingTimeLimit: 10, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(15)).ToUnixTimeSeconds());
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_not_be_removable_if_overridden_max_inactive_seedingtime_not_reached_and_paused(string state)
|
||||
[Test]
|
||||
public void should_not_be_removable_if_overridden_max_inactive_seedingtime_not_reached_and_paused()
|
||||
{
|
||||
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
|
||||
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 30, inactiveSeedingTimeLimit: 40, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(30)).ToUnixTimeSeconds());
|
||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 30, inactiveSeedingTimeLimit: 40, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(30)).ToUnixTimeSeconds());
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeFalse();
|
||||
item.CanMoveFiles.Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_but_ratio_not_and_paused(string state)
|
||||
[Test]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_but_ratio_not_and_paused()
|
||||
{
|
||||
GivenGlobalSeedLimits(2.0f, 20);
|
||||
GivenCompletedTorrent(state, ratio: 1.0f, seedingTime: 30);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 1.0f, seedingTime: 30);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_but_ratio_not_and_paused(string state)
|
||||
[Test]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_but_ratio_not_and_paused()
|
||||
{
|
||||
GivenGlobalSeedLimits(2.0f, maxInactiveSeedingTime: 20);
|
||||
GivenCompletedTorrent(state, ratio: 1.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
|
||||
GivenCompletedTorrent("pausedUP", ratio: 1.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_not_fetch_details_twice(string state)
|
||||
[Test]
|
||||
public void should_not_fetch_details_twice()
|
||||
{
|
||||
GivenGlobalSeedLimits(-1, 30);
|
||||
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20);
|
||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeFalse();
|
||||
@@ -918,9 +854,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
.Verify(p => p.GetTorrentProperties(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_get_category_from_the_category_if_set(string state)
|
||||
[Test]
|
||||
public void should_get_category_from_the_category_if_set()
|
||||
{
|
||||
const string category = "music-readarr";
|
||||
GivenGlobalSeedLimits(1.0f);
|
||||
@@ -932,7 +867,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
Size = 1000,
|
||||
Progress = 1.0,
|
||||
Eta = 8640000,
|
||||
State = state,
|
||||
State = "pausedUP",
|
||||
Category = category,
|
||||
SavePath = "",
|
||||
Ratio = 1.0f
|
||||
@@ -944,9 +879,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
item.Category.Should().Be(category);
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_get_category_from_the_label_if_the_category_is_not_available(string state)
|
||||
[Test]
|
||||
public void should_get_category_from_the_label_if_the_category_is_not_available()
|
||||
{
|
||||
const string category = "music-readarr";
|
||||
GivenGlobalSeedLimits(1.0f);
|
||||
@@ -958,7 +892,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
Size = 1000,
|
||||
Progress = 1.0,
|
||||
Eta = 8640000,
|
||||
State = state,
|
||||
State = "pausedUP",
|
||||
Label = category,
|
||||
SavePath = "",
|
||||
Ratio = 1.0f
|
||||
|
||||
@@ -478,37 +478,6 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
downloadClientInfo.RemovesCompletedDownloads.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("all", 0)]
|
||||
[TestCase("days-archive", 15)]
|
||||
[TestCase("days-delete", 15)]
|
||||
public void should_set_history_removes_completed_downloads_false_for_separate_properties(string option, int number)
|
||||
{
|
||||
_config.Misc.history_retention_option = option;
|
||||
_config.Misc.history_retention_number = number;
|
||||
|
||||
var downloadClientInfo = Subject.GetStatus();
|
||||
|
||||
downloadClientInfo.RemovesCompletedDownloads.Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestCase("number-archive", 10)]
|
||||
[TestCase("number-delete", 10)]
|
||||
[TestCase("number-archive", 0)]
|
||||
[TestCase("number-delete", 0)]
|
||||
[TestCase("days-archive", 3)]
|
||||
[TestCase("days-delete", 3)]
|
||||
[TestCase("all-archive", 0)]
|
||||
[TestCase("all-delete", 0)]
|
||||
public void should_set_history_removes_completed_downloads_true_for_separate_properties(string option, int number)
|
||||
{
|
||||
_config.Misc.history_retention_option = option;
|
||||
_config.Misc.history_retention_number = number;
|
||||
|
||||
var downloadClientInfo = Subject.GetStatus();
|
||||
|
||||
downloadClientInfo.RemovesCompletedDownloads.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase(@"Y:\nzbget\root", @"completed\downloads", @"vv", @"Y:\nzbget\root\completed\downloads", @"Y:\nzbget\root\completed\downloads\vv")]
|
||||
[TestCase(@"Y:\nzbget\root", @"completed", @"vv", @"Y:\nzbget\root\completed", @"Y:\nzbget\root\completed\vv")]
|
||||
[TestCase(@"/nzbget/root", @"completed/downloads", @"vv", @"/nzbget/root/completed/downloads", @"/nzbget/root/completed/downloads/vv")]
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace NzbDrone.Core.Test.Http
|
||||
{
|
||||
private HttpProxySettings GetProxySettings()
|
||||
{
|
||||
return new HttpProxySettings(ProxyType.Socks5, "localhost", 8080, "*.httpbin.org,google.com,172.16.0.0/12", true, null, null);
|
||||
return new HttpProxySettings(ProxyType.Socks5, "localhost", 8080, "*.httpbin.org,google.com", true, null, null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -23,7 +23,6 @@ namespace NzbDrone.Core.Test.Http
|
||||
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://eu.httpbin.org/get")).Should().BeTrue();
|
||||
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://google.com/get")).Should().BeTrue();
|
||||
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://localhost:8654/get")).Should().BeTrue();
|
||||
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://172.21.0.1:8989/api/v3/indexer/schema")).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -32,7 +31,6 @@ namespace NzbDrone.Core.Test.Http
|
||||
var settings = GetProxySettings();
|
||||
|
||||
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://bing.com/get")).Should().BeFalse();
|
||||
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://172.3.0.1:8989/api/v3/indexer/schema")).Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ using NzbDrone.Core.Test.Framework;
|
||||
namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
||||
{
|
||||
[TestFixture]
|
||||
[Ignore("Waiting for metadata to be back again", Until = "2026-01-15 00:00:00Z")]
|
||||
[Ignore("Waiting for metadata to be back again", Until = "2024-12-15 00:00:00Z")]
|
||||
public class BookInfoProxyFixture : CoreTest<BookInfoProxy>
|
||||
{
|
||||
private MetadataProfile _metadataProfile;
|
||||
|
||||
@@ -15,7 +15,7 @@ using NzbDrone.Test.Common;
|
||||
namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
||||
{
|
||||
[TestFixture]
|
||||
[Ignore("Waiting for metadata to be back again", Until = "2026-01-15 00:00:00Z")]
|
||||
[Ignore("Waiting for metadata to be back again", Until = "2024-12-15 00:00:00Z")]
|
||||
public class BookInfoProxySearchFixture : CoreTest<BookInfoProxy>
|
||||
{
|
||||
[SetUp]
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
||||
|
||||
[TestCase("Harry Potter and the sorcerer's stone a detailed summary", 72245296)]
|
||||
[TestCase("B0192CTMYG", 61209488)]
|
||||
[TestCase("9780439554930", 3)]
|
||||
[TestCase("9780439554930", 48517161)]
|
||||
public void successful_book_search(string title, int expected)
|
||||
{
|
||||
var result = Subject.Search(title);
|
||||
|
||||
@@ -21,14 +21,14 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||
public void no_update_when_version_higher()
|
||||
{
|
||||
UseRealHttp();
|
||||
Subject.GetLatestUpdate("develop", new Version(10, 0)).Should().BeNull();
|
||||
Subject.GetLatestUpdate("nightly", new Version(10, 0)).Should().BeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void finds_update_when_version_lower()
|
||||
{
|
||||
UseRealHttp();
|
||||
Subject.GetLatestUpdate("develop", new Version(0, 1)).Should().NotBeNull();
|
||||
Subject.GetLatestUpdate("nightly", new Version(0, 1)).Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -42,9 +42,10 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||
[Test]
|
||||
public void should_get_recent_updates()
|
||||
{
|
||||
const string branch = "develop";
|
||||
const string branch = "nightly";
|
||||
UseRealHttp();
|
||||
var recent = Subject.GetRecentUpdates(branch, new Version(0, 1), null);
|
||||
var recentWithChanges = recent.Where(c => c.Changes != null);
|
||||
|
||||
recent.Should().NotBeEmpty();
|
||||
recent.Should().OnlyContain(c => c.Hash.IsNotNullOrWhiteSpace());
|
||||
|
||||
@@ -66,19 +66,12 @@ namespace NzbDrone.Core.Backup
|
||||
{
|
||||
_logger.ProgressInfo("Starting Backup");
|
||||
|
||||
var backupFolder = GetBackupFolder(backupType);
|
||||
|
||||
_diskProvider.EnsureFolder(_backupTempFolder);
|
||||
_diskProvider.EnsureFolder(backupFolder);
|
||||
|
||||
if (!_diskProvider.FolderWritable(backupFolder))
|
||||
{
|
||||
throw new UnauthorizedAccessException($"Backup folder {backupFolder} is not writable");
|
||||
}
|
||||
_diskProvider.EnsureFolder(GetBackupFolder(backupType));
|
||||
|
||||
var dateNow = DateTime.Now;
|
||||
var backupFilename = $"readarr_backup_v{BuildInfo.Version}_{dateNow:yyyy.MM.dd_HH.mm.ss}.zip";
|
||||
var backupPath = Path.Combine(backupFolder, backupFilename);
|
||||
var backupPath = Path.Combine(GetBackupFolder(backupType), backupFilename);
|
||||
|
||||
Cleanup();
|
||||
|
||||
|
||||
@@ -102,9 +102,9 @@ namespace NzbDrone.Core.Books
|
||||
_logger.Error("ReadarrId {0} was not found, it may have been removed from Goodreads.", newAuthor.Metadata.Value.ForeignAuthorId);
|
||||
|
||||
throw new ValidationException(new List<ValidationFailure>
|
||||
{
|
||||
new ("ForeignAuthorId", "An author with this ID was not found", newAuthor.Metadata.Value.ForeignAuthorId)
|
||||
});
|
||||
{
|
||||
new ValidationFailure("MusicbrainzId", "An author with this ID was not found", newAuthor.Metadata.Value.ForeignAuthorId)
|
||||
});
|
||||
}
|
||||
|
||||
author.ApplyChanges(newAuthor);
|
||||
|
||||
@@ -53,7 +53,6 @@ namespace NzbDrone.Core.Configuration
|
||||
string SyslogServer { get; }
|
||||
int SyslogPort { get; }
|
||||
string SyslogLevel { get; }
|
||||
string Theme { get; }
|
||||
string PostgresHost { get; }
|
||||
int PostgresPort { get; }
|
||||
string PostgresUser { get; }
|
||||
@@ -61,7 +60,7 @@ namespace NzbDrone.Core.Configuration
|
||||
string PostgresMainDb { get; }
|
||||
string PostgresLogDb { get; }
|
||||
string PostgresCacheDb { get; }
|
||||
bool TrustCgnatIpAddresses { get; }
|
||||
string Theme { get; }
|
||||
}
|
||||
|
||||
public class ConfigFileProvider : IConfigFileProvider
|
||||
@@ -254,21 +253,7 @@ namespace NzbDrone.Core.Configuration
|
||||
}
|
||||
|
||||
public string UiFolder => BuildInfo.IsDebug ? Path.Combine("..", "UI") : "UI";
|
||||
|
||||
public string InstanceName
|
||||
{
|
||||
get
|
||||
{
|
||||
var instanceName = _appOptions.InstanceName ?? GetValue("InstanceName", BuildInfo.AppName);
|
||||
|
||||
if (instanceName.Contains(BuildInfo.AppName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return instanceName;
|
||||
}
|
||||
|
||||
return BuildInfo.AppName;
|
||||
}
|
||||
}
|
||||
public string InstanceName => _appOptions.InstanceName ?? GetValue("InstanceName", BuildInfo.AppName);
|
||||
|
||||
public bool UpdateAutomatically => _updateOptions.Automatically ?? GetValueBoolean("UpdateAutomatically", OsInfo.IsWindows, false);
|
||||
|
||||
@@ -477,7 +462,5 @@ namespace NzbDrone.Core.Configuration
|
||||
{
|
||||
SetValue("ApiKey", GenerateApiKey());
|
||||
}
|
||||
|
||||
public bool TrustCgnatIpAddresses => _authOptions.TrustCgnatIpAddresses ?? GetValueBoolean("TrustCgnatIpAddresses", false, persist: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,12 +404,6 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
public string ApplicationUrl => GetValue("ApplicationUrl", string.Empty);
|
||||
|
||||
public bool TrustCgnatIpAddresses
|
||||
{
|
||||
get { return GetValueBoolean("TrustCgnatIpAddresses", false); }
|
||||
set { SetValue("TrustCgnatIpAddresses", value); }
|
||||
}
|
||||
|
||||
private string GetValue(string key)
|
||||
{
|
||||
return GetValue(key, string.Empty);
|
||||
|
||||
@@ -264,7 +264,7 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
protected void Delete(SqlBuilder builder)
|
||||
{
|
||||
var sql = builder.AddDeleteTemplate(typeof(TModel));
|
||||
var sql = builder.AddDeleteTemplate(typeof(TModel)).LogQuery();
|
||||
|
||||
using (var conn = _database.OpenConnection())
|
||||
{
|
||||
|
||||
@@ -239,7 +239,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
// Avoid removing torrents that haven't reached the global max ratio.
|
||||
// Removal also requires the torrent to be paused, in case a higher max ratio was set on the torrent itself (which is not exposed by the api).
|
||||
item.CanMoveFiles = item.CanBeRemoved = torrent.State is "pausedUP" or "stoppedUP" && HasReachedSeedLimit(torrent, config);
|
||||
item.CanMoveFiles = item.CanBeRemoved = torrent.State == "pausedUP" && HasReachedSeedLimit(torrent, config);
|
||||
|
||||
switch (torrent.State)
|
||||
{
|
||||
@@ -248,8 +248,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
item.Message = "qBittorrent is reporting an error";
|
||||
break;
|
||||
|
||||
case "stoppedDL": // torrent is stopped and has NOT finished downloading
|
||||
case "pausedDL": // torrent is paused and has NOT finished downloading (qBittorrent < 5)
|
||||
case "pausedDL": // torrent is paused and has NOT finished downloading
|
||||
item.Status = DownloadItemStatus.Paused;
|
||||
break;
|
||||
|
||||
@@ -260,8 +259,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
item.Status = DownloadItemStatus.Queued;
|
||||
break;
|
||||
|
||||
case "pausedUP": // torrent is paused and has finished downloading (qBittorent < 5)
|
||||
case "stoppedUP": // torrent is stopped and has finished downloading
|
||||
case "pausedUP": // torrent is paused and has finished downloading
|
||||
case "uploading": // torrent is being seeded and data is being transferred
|
||||
case "stalledUP": // torrent is being seeded, but no connection were made
|
||||
case "queuedUP": // queuing is enabled and torrent is queued for upload
|
||||
@@ -620,14 +618,14 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
if (torrent.RatioLimit >= 0)
|
||||
{
|
||||
if (torrent.RatioLimit - torrent.Ratio <= 0.001f)
|
||||
if (torrent.Ratio >= torrent.RatioLimit)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (torrent.RatioLimit == -2 && config.MaxRatioEnabled)
|
||||
{
|
||||
if (config.MaxRatio - torrent.Ratio <= 0.001f)
|
||||
if (Math.Round(torrent.Ratio, 2) >= config.MaxRatio)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
Dictionary<string, QBittorrentLabel> GetLabels(QBittorrentSettings settings);
|
||||
void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings);
|
||||
void MoveTorrentToTopInQueue(string hash, QBittorrentSettings settings);
|
||||
void PauseTorrent(string hash, QBittorrentSettings settings);
|
||||
void ResumeTorrent(string hash, QBittorrentSettings settings);
|
||||
void SetForceStart(string hash, bool enabled, QBittorrentSettings settings);
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
request.AddFormParameter("paused", false);
|
||||
}
|
||||
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Stop)
|
||||
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
|
||||
{
|
||||
request.AddFormParameter("paused", true);
|
||||
}
|
||||
@@ -178,7 +178,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
request.AddFormParameter("paused", false);
|
||||
}
|
||||
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Stop)
|
||||
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
|
||||
{
|
||||
request.AddFormParameter("paused", true);
|
||||
}
|
||||
@@ -214,7 +214,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
// if setCategory fails due to method not being found, then try older setLabel command for qBittorrent < v.3.3.5
|
||||
if (ex.InnerException is HttpException httpException && httpException.Response.StatusCode == HttpStatusCode.NotFound)
|
||||
if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
var setLabelRequest = BuildRequest(settings).Resource("/command/setLabel")
|
||||
.Post()
|
||||
@@ -257,7 +257,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
// qBittorrent rejects all Prio commands with 403: Forbidden if Options -> BitTorrent -> Torrent Queueing is not enabled
|
||||
if (ex.InnerException is HttpException httpException && httpException.Response.StatusCode == HttpStatusCode.Forbidden)
|
||||
if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -266,6 +266,22 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
}
|
||||
|
||||
public void PauseTorrent(string hash, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/command/pause")
|
||||
.Post()
|
||||
.AddFormParameter("hash", hash);
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
public void ResumeTorrent(string hash, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/command/resume")
|
||||
.Post()
|
||||
.AddFormParameter("hash", hash);
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
public void SetForceStart(string hash, bool enabled, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/command/setForceStart")
|
||||
|
||||
@@ -246,20 +246,14 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
request.AddFormParameter("category", settings.MusicCategory);
|
||||
}
|
||||
|
||||
// Avoid extraneous API version check if initial state is ForceStart
|
||||
if ((QBittorrentState)settings.InitialState is QBittorrentState.Start or QBittorrentState.Stop)
|
||||
// Note: ForceStart is handled by separate api call
|
||||
if ((QBittorrentState)settings.InitialState == QBittorrentState.Start)
|
||||
{
|
||||
var stoppedParameterName = GetApiVersion(settings) >= new Version(2, 11, 0) ? "stopped" : "paused";
|
||||
|
||||
// Note: ForceStart is handled by separate api call
|
||||
if ((QBittorrentState)settings.InitialState == QBittorrentState.Start)
|
||||
{
|
||||
request.AddFormParameter(stoppedParameterName, false);
|
||||
}
|
||||
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Stop)
|
||||
{
|
||||
request.AddFormParameter(stoppedParameterName, true);
|
||||
}
|
||||
request.AddFormParameter("paused", false);
|
||||
}
|
||||
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
|
||||
{
|
||||
request.AddFormParameter("paused", true);
|
||||
}
|
||||
|
||||
if (settings.SequentialOrder)
|
||||
@@ -297,7 +291,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
// setShareLimits was added in api v2.0.1 so catch it case of the unlikely event that someone has api v2.0
|
||||
if (ex.InnerException is HttpException httpException && httpException.Response.StatusCode == HttpStatusCode.NotFound)
|
||||
if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -319,7 +313,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
// qBittorrent rejects all Prio commands with 409: Conflict if Options -> BitTorrent -> Torrent Queueing is not enabled
|
||||
if (ex.InnerException is HttpException httpException && httpException.Response.StatusCode == HttpStatusCode.Conflict)
|
||||
if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.Conflict)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -328,6 +322,22 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
}
|
||||
|
||||
public void PauseTorrent(string hash, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/pause")
|
||||
.Post()
|
||||
.AddFormParameter("hashes", hash);
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
public void ResumeTorrent(string hash, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/resume")
|
||||
.Post()
|
||||
.AddFormParameter("hashes", hash);
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
public void SetForceStart(string hash, bool enabled, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/setForceStart")
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
using NzbDrone.Core.Annotations;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
public enum QBittorrentState
|
||||
{
|
||||
[FieldOption(Label = "Started")]
|
||||
Start = 0,
|
||||
|
||||
[FieldOption(Label = "Force Started")]
|
||||
ForceStart = 1,
|
||||
|
||||
[FieldOption(Label = "Stopped")]
|
||||
Stop = 2
|
||||
Pause = 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,7 +263,20 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
status.OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.FullPath) };
|
||||
}
|
||||
|
||||
status.RemovesCompletedDownloads = RemovesCompletedDownloads(config);
|
||||
if (config.Misc.history_retention.IsNullOrWhiteSpace())
|
||||
{
|
||||
status.RemovesCompletedDownloads = false;
|
||||
}
|
||||
else if (config.Misc.history_retention.EndsWith("d"))
|
||||
{
|
||||
int.TryParse(config.Misc.history_retention.AsSpan(0, config.Misc.history_retention.Length - 1),
|
||||
out var daysRetention);
|
||||
status.RemovesCompletedDownloads = daysRetention < 14;
|
||||
}
|
||||
else
|
||||
{
|
||||
status.RemovesCompletedDownloads = config.Misc.history_retention != "0";
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
@@ -505,43 +518,6 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
return categories.Contains(category);
|
||||
}
|
||||
|
||||
private bool RemovesCompletedDownloads(SabnzbdConfig config)
|
||||
{
|
||||
var retention = config.Misc.history_retention;
|
||||
var option = config.Misc.history_retention_option;
|
||||
var number = config.Misc.history_retention_number;
|
||||
|
||||
switch (option)
|
||||
{
|
||||
case "all":
|
||||
return false;
|
||||
case "number-archive":
|
||||
case "number-delete":
|
||||
return true;
|
||||
case "days-archive":
|
||||
case "days-delete":
|
||||
return number < 14;
|
||||
case "all-archive":
|
||||
case "all-delete":
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Remove these checks once support for SABnzbd < 4.3 is removed
|
||||
if (retention.IsNullOrWhiteSpace())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (retention.EndsWith("d"))
|
||||
{
|
||||
int.TryParse(config.Misc.history_retention.AsSpan(0, config.Misc.history_retention.Length - 1),
|
||||
out var daysRetention);
|
||||
return daysRetention < 14;
|
||||
}
|
||||
|
||||
return retention != "0";
|
||||
}
|
||||
|
||||
private bool ValidatePath(DownloadClientItem downloadClientItem)
|
||||
{
|
||||
var downloadItemOutputPath = downloadClientItem.OutputPath;
|
||||
|
||||
@@ -30,8 +30,6 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
public bool enable_date_sorting { get; set; }
|
||||
public bool pre_check { get; set; }
|
||||
public string history_retention { get; set; }
|
||||
public string history_retention_option { get; set; }
|
||||
public int history_retention_number { get; set; }
|
||||
}
|
||||
|
||||
public class SabnzbdCategory
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Net;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
@@ -209,7 +208,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
|
||||
private void AuthenticateClient(HttpRequestBuilder requestBuilder, TransmissionSettings settings, bool reauthenticate = false)
|
||||
{
|
||||
var authKey = $"{requestBuilder.BaseUrl}:{settings.Password}";
|
||||
var authKey = string.Format("{0}:{1}", requestBuilder.BaseUrl, settings.Password);
|
||||
|
||||
var sessionId = _authSessionIDCache.Find(authKey);
|
||||
|
||||
@@ -221,26 +220,24 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
authLoginRequest.SuppressHttpError = true;
|
||||
|
||||
var response = _httpClient.Execute(authLoginRequest);
|
||||
|
||||
switch (response.StatusCode)
|
||||
if (response.StatusCode == HttpStatusCode.MovedPermanently)
|
||||
{
|
||||
case HttpStatusCode.MovedPermanently:
|
||||
var url = response.Headers.GetSingleValue("Location");
|
||||
var url = response.Headers.GetSingleValue("Location");
|
||||
|
||||
throw new DownloadClientException("Remote site redirected to " + url);
|
||||
case HttpStatusCode.Forbidden:
|
||||
throw new DownloadClientException($"Failed to authenticate with Transmission. It may be necessary to add {BuildInfo.AppName}'s IP address to RPC whitelist.");
|
||||
case HttpStatusCode.Conflict:
|
||||
sessionId = response.Headers.GetSingleValue("X-Transmission-Session-Id");
|
||||
throw new DownloadClientException("Remote site redirected to " + url);
|
||||
}
|
||||
else if (response.StatusCode == HttpStatusCode.Conflict)
|
||||
{
|
||||
sessionId = response.Headers.GetSingleValue("X-Transmission-Session-Id");
|
||||
|
||||
if (sessionId == null)
|
||||
{
|
||||
throw new DownloadClientException("Remote host did not return a Session Id.");
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new DownloadClientAuthenticationException("Failed to authenticate with Transmission.");
|
||||
if (sessionId == null)
|
||||
{
|
||||
throw new DownloadClientException("Remote host did not return a Session Id.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DownloadClientAuthenticationException("Failed to authenticate with Transmission.");
|
||||
}
|
||||
|
||||
_logger.Debug("Transmission authentication succeeded.");
|
||||
|
||||
@@ -4,24 +4,24 @@ namespace NzbDrone.Core.Exceptions
|
||||
{
|
||||
public class AuthorNotFoundException : NzbDroneException
|
||||
{
|
||||
public string ForeignAuthorId { get; set; }
|
||||
public string MusicBrainzId { get; set; }
|
||||
|
||||
public AuthorNotFoundException(string foreignAuthorId)
|
||||
: base($"Author with id {foreignAuthorId} was not found, it may have been removed from the metadata server.")
|
||||
public AuthorNotFoundException(string musicbrainzId)
|
||||
: base(string.Format("Author with id {0} was not found, it may have been removed from the metadata server.", musicbrainzId))
|
||||
{
|
||||
ForeignAuthorId = foreignAuthorId;
|
||||
MusicBrainzId = musicbrainzId;
|
||||
}
|
||||
|
||||
public AuthorNotFoundException(string foreignAuthorId, string message, params object[] args)
|
||||
public AuthorNotFoundException(string musicbrainzId, string message, params object[] args)
|
||||
: base(message, args)
|
||||
{
|
||||
ForeignAuthorId = foreignAuthorId;
|
||||
MusicBrainzId = musicbrainzId;
|
||||
}
|
||||
|
||||
public AuthorNotFoundException(string foreignAuthorId, string message)
|
||||
public AuthorNotFoundException(string musicbrainzId, string message)
|
||||
: base(message)
|
||||
{
|
||||
ForeignAuthorId = foreignAuthorId;
|
||||
MusicBrainzId = musicbrainzId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,24 +4,24 @@ namespace NzbDrone.Core.Exceptions
|
||||
{
|
||||
public class BookNotFoundException : NzbDroneException
|
||||
{
|
||||
public string ForeignBookId { get; set; }
|
||||
public string MusicBrainzId { get; set; }
|
||||
|
||||
public BookNotFoundException(string foreignBookId)
|
||||
: base($"Book with id {foreignBookId} was not found, it may have been removed from metadata server.")
|
||||
public BookNotFoundException(string musicbrainzId)
|
||||
: base(string.Format("Book with id {0} was not found, it may have been removed from metadata server.", musicbrainzId))
|
||||
{
|
||||
ForeignBookId = foreignBookId;
|
||||
MusicBrainzId = musicbrainzId;
|
||||
}
|
||||
|
||||
public BookNotFoundException(string foreignBookId, string message, params object[] args)
|
||||
public BookNotFoundException(string musicbrainzId, string message, params object[] args)
|
||||
: base(message, args)
|
||||
{
|
||||
ForeignBookId = foreignBookId;
|
||||
MusicBrainzId = musicbrainzId;
|
||||
}
|
||||
|
||||
public BookNotFoundException(string foreignBookId, string message)
|
||||
public BookNotFoundException(string musicbrainzId, string message)
|
||||
: base(message)
|
||||
{
|
||||
ForeignBookId = foreignBookId;
|
||||
MusicBrainzId = musicbrainzId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,24 +4,24 @@ namespace NzbDrone.Core.Exceptions
|
||||
{
|
||||
public class EditionNotFoundException : NzbDroneException
|
||||
{
|
||||
public string ForeignEditionId { get; set; }
|
||||
public string MusicBrainzId { get; set; }
|
||||
|
||||
public EditionNotFoundException(string foreignEditionId)
|
||||
: base($"Edition with id {foreignEditionId} was not found, it may have been removed from metadata server.")
|
||||
public EditionNotFoundException(string musicbrainzId)
|
||||
: base(string.Format("Edition with id {0} was not found, it may have been removed from metadata server.", musicbrainzId))
|
||||
{
|
||||
ForeignEditionId = foreignEditionId;
|
||||
MusicBrainzId = musicbrainzId;
|
||||
}
|
||||
|
||||
public EditionNotFoundException(string foreignEditionId, string message, params object[] args)
|
||||
public EditionNotFoundException(string musicbrainzId, string message, params object[] args)
|
||||
: base(message, args)
|
||||
{
|
||||
ForeignEditionId = foreignEditionId;
|
||||
MusicBrainzId = musicbrainzId;
|
||||
}
|
||||
|
||||
public EditionNotFoundException(string foreignEditionId, string message)
|
||||
public EditionNotFoundException(string musicbrainzId, string message)
|
||||
: base(message)
|
||||
{
|
||||
ForeignEditionId = foreignEditionId;
|
||||
MusicBrainzId = musicbrainzId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,6 @@ namespace NzbDrone.Core.History
|
||||
{
|
||||
public const string DOWNLOAD_CLIENT = "downloadClient";
|
||||
public const string RELEASE_SOURCE = "releaseSource";
|
||||
public const string RELEASE_GROUP = "releaseGroup";
|
||||
public const string SIZE = "size";
|
||||
public const string INDEXER = "indexer";
|
||||
|
||||
public EntityHistory()
|
||||
{
|
||||
|
||||
@@ -116,7 +116,6 @@ namespace NzbDrone.Core.History
|
||||
{
|
||||
var builder = Builder()
|
||||
.Join<EntityHistory, Author>((h, a) => h.AuthorId == a.Id)
|
||||
.LeftJoin<EntityHistory, Book>((h, b) => h.BookId == b.Id)
|
||||
.Where<EntityHistory>(x => x.Date >= date);
|
||||
|
||||
if (eventType.HasValue)
|
||||
@@ -124,10 +123,9 @@ namespace NzbDrone.Core.History
|
||||
builder.Where<EntityHistory>(h => h.EventType == eventType);
|
||||
}
|
||||
|
||||
return _database.QueryJoined<EntityHistory, Author, Book>(builder, (history, author, book) =>
|
||||
return _database.QueryJoined<EntityHistory, Author>(builder, (history, author) =>
|
||||
{
|
||||
history.Author = author;
|
||||
history.Book = book;
|
||||
return history;
|
||||
}).OrderBy(h => h.Date).ToList();
|
||||
}
|
||||
|
||||
@@ -263,9 +263,7 @@ namespace NzbDrone.Core.History
|
||||
history.Data.Add("DownloadClient", message.DownloadClient);
|
||||
history.Data.Add("DownloadClientName", message.TrackedDownload?.DownloadItem.DownloadClientInfo.Name);
|
||||
history.Data.Add("Message", message.Message);
|
||||
history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteBook?.ParsedBookInfo?.ReleaseGroup ?? message.Data.GetValueOrDefault(EntityHistory.RELEASE_GROUP));
|
||||
history.Data.Add("Size", message.TrackedDownload?.DownloadItem.TotalSize.ToString() ?? message.Data.GetValueOrDefault(EntityHistory.SIZE));
|
||||
history.Data.Add("Indexer", message.TrackedDownload?.RemoteBook?.Release?.Indexer ?? message.Data.GetValueOrDefault(EntityHistory.INDEXER));
|
||||
history.Data.Add("Size", message.TrackedDownload?.DownloadItem.TotalSize.ToString());
|
||||
|
||||
_historyRepository.Insert(history);
|
||||
}
|
||||
@@ -375,7 +373,6 @@ namespace NzbDrone.Core.History
|
||||
history.Data.Add("Message", message.Message);
|
||||
history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteBook?.ParsedBookInfo?.ReleaseGroup);
|
||||
history.Data.Add("Size", message.TrackedDownload?.DownloadItem.TotalSize.ToString());
|
||||
history.Data.Add("Indexer", message.TrackedDownload?.RemoteBook?.Release?.Indexer);
|
||||
|
||||
historyToAdd.Add(history);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using NetTools;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Configuration;
|
||||
@@ -54,15 +52,7 @@ namespace NzbDrone.Core.Http
|
||||
//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) || IsBypassedByIpAddressRange(proxySettings.BypassListAsArray, url.Host);
|
||||
}
|
||||
|
||||
private static bool IsBypassedByIpAddressRange(string[] bypassList, string host)
|
||||
{
|
||||
return bypassList.Any(bypass =>
|
||||
IPAddressRange.TryParse(bypass, out var ipAddressRange) &&
|
||||
IPAddress.TryParse(host, out var ipAddress) &&
|
||||
ipAddressRange.Contains(ipAddress));
|
||||
return proxy.IsBypassed((Uri)url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ namespace NzbDrone.Core.ImportLists
|
||||
report.BookGoodreadsId = remoteBook.ForeignBookId;
|
||||
report.Book = remoteBook.Title;
|
||||
report.Author ??= remoteBook.AuthorMetadata.Value.Name;
|
||||
report.AuthorGoodreadsId ??= remoteBook.AuthorMetadata.Value.ForeignAuthorId;
|
||||
report.AuthorGoodreadsId ??= remoteBook.AuthorMetadata.Value.Name;
|
||||
}
|
||||
catch (BookNotFoundException)
|
||||
{
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
var url = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("download.php")
|
||||
.AddQueryParam("id", torrentId)
|
||||
.AddQueryParam("passkey", _settings.Passkey.Trim());
|
||||
.AddQueryParam("passkey", _settings.Passkey);
|
||||
|
||||
return url.FullUri;
|
||||
}
|
||||
|
||||
@@ -653,7 +653,5 @@
|
||||
"UnmappedFiles": "المجلدات غير المعينة",
|
||||
"UpdateAppDirectlyLoadError": "تعذر تحديث {appName} مباشرة ،",
|
||||
"Clone": "قريب",
|
||||
"BuiltIn": "مدمج",
|
||||
"AddNewAuthorRootFolderHelpText": "سيتم إنشاء المجلد الفرعي \"{folder}\" تلقائيًا",
|
||||
"AddRootFolder": "إضافة مجلد جذر"
|
||||
"BuiltIn": "مدمج"
|
||||
}
|
||||
|
||||
@@ -653,49 +653,5 @@
|
||||
"Clone": "Близо",
|
||||
"DockerUpdater": "актуализирайте контейнера на докера, за да получите актуализацията",
|
||||
"InstallLatest": "Инсталирайте най-новите",
|
||||
"OnLatestVersion": "Вече е инсталирана най-новата версия на {appName}",
|
||||
"BlocklistAndSearch": "Списък за блокиране и търсене",
|
||||
"BlocklistMultipleOnlyHint": "Списък за блокиране без търсене на заместители",
|
||||
"BlocklistAndSearchHint": "Започнете търсене на заместител след блокиране",
|
||||
"BlocklistAndSearchMultipleHint": "Започнете търсене на заместители след блокиране",
|
||||
"DoNotBlocklistHint": "Премахване без блокиране",
|
||||
"Database": "База данни",
|
||||
"DoNotBlocklist": "Не блокирайте",
|
||||
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Автоматично търсене и опит за изтегляне на различна версия, когато неуспешната версия е била взета от интерактивно търсене",
|
||||
"DownloadClientDelugeSettingsDirectoryHelpText": "Незадължителна локация за изтеглянията, оставете празно, за да използвате мястото по подразбиране на Deluge",
|
||||
"CustomFormatsSettingsTriggerInfo": "Персонализиран формат ще бъде приложен към издание или файл, когато съвпада с поне един от всеки от избраните различни типове условия.",
|
||||
"AutomaticAdd": "Автоматично добавяне",
|
||||
"BlocklistOnly": "Само списък за блокиране",
|
||||
"BlocklistOnlyHint": "Списък за блокиране без търсене на заместител",
|
||||
"DownloadClientDelugeSettingsDirectoryCompleted": "Директория за вече завършените изтегляния",
|
||||
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Незадължителна локация за преместване на вече завършените изтегляния, оставете празно, за да използвате мястото по подразбиране на Deluge",
|
||||
"Library": "Библиотека",
|
||||
"ApplicationURL": "URL адрес на приложението",
|
||||
"ApplicationUrlHelpText": "Външният URL на това приложение, включително http(s)://, порт и базов URL",
|
||||
"CustomFormatsSpecificationFlag": "Флаг",
|
||||
"BypassIfAboveCustomFormatScore": "Пропусни, ако е над рейтинга на персонализирания формат",
|
||||
"AppUpdated": "{appName} Актуализиран",
|
||||
"AppUpdatedVersion": "{appName} е актуализиранa до версия `{version}`, за да получите най-новите промени, ще трябва да презаредите {appName}",
|
||||
"CatalogNumber": "каталожен номер",
|
||||
"AutoAdd": "Автоматично добавяне",
|
||||
"CustomFormatsSpecificationRegularExpression": "Регулярни изрази",
|
||||
"CustomFormatsSpecificationRegularExpressionHelpText": "Персонализираният формат RegEx не е чувствителен към главни и малки букви",
|
||||
"Label": "Етикет",
|
||||
"AutomaticUpdatesDisabledDocker": "Автоматичните актуализации не се поддържат директно при използване на механизма за актуализация на Docker. Ще трябва да актуализирате Image-a на контейнера извън {appName} или да използвате скрипт",
|
||||
"NoCutoffUnmetItems": "Няма неизпълнени елементи за прекъсване",
|
||||
"Publisher": "Издател",
|
||||
"Series": "Сериали",
|
||||
"Theme": "Тема",
|
||||
"BypassIfAboveCustomFormatScoreHelpText": "Активиране на пропускане, когато изданието има резултат, по-висок от конфигурирания минимален резултат за потребителски формат",
|
||||
"MinimumCustomFormatScoreHelpText": "Минимална резултат на персонализирания формат, необходима за пропускане на забавянето за предпочитания протокол",
|
||||
"DownloadClientDelugeSettingsDirectory": "Директория за изтегляне",
|
||||
"AuthenticationMethodHelpTextWarning": "Моля, изберете валиден метод за удостоверяване",
|
||||
"AuthenticationMethod": "Метод за удостоверяване",
|
||||
"AuthenticationRequiredHelpText": "Променете за кои заявки се изисква удостоверяване. Не променяйте, освен ако не разбирате рисковете.",
|
||||
"AuthenticationRequired": "Изисква се удостоверяване",
|
||||
"AuthenticationRequiredPasswordHelpTextWarning": "Въведете нова парола",
|
||||
"ApplyChanges": "Прилагане на промените",
|
||||
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Потвърдете новата парола",
|
||||
"AddNewAuthorRootFolderHelpText": "Подпапката „{0}“ ще бъде създадена автоматично",
|
||||
"AddRootFolder": "Добавяне на коренна папка"
|
||||
"OnLatestVersion": "Вече е инсталирана най-новата версия на {appName}"
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@
|
||||
"PackageVersion": "Versió del paquet",
|
||||
"PageSize": "Mida de la pàgina",
|
||||
"PageSizeHelpText": "Nombre d'elements per mostrar a cada pàgina",
|
||||
"Proper": "Correcte",
|
||||
"Proper": "Proper",
|
||||
"ProxyBypassFilterHelpText": "Utilitzeu ',' com a separador i '*.' com a comodí per als subdominis",
|
||||
"ProxyCheckBadRequestMessage": "No s'ha pogut provar el servidor intermediari. Codi d'estat: {0}",
|
||||
"ProxyCheckFailedToTestMessage": "No s'ha pogut provar el servidor intermediari: {0}",
|
||||
@@ -316,7 +316,7 @@
|
||||
"URLBase": "Base URL",
|
||||
"Backups": "Còpies de seguretat",
|
||||
"Connections": "Connexions",
|
||||
"CopyUsingHardlinksHelpText": "Els enllaços durs permeten a Readarr importar torrents de sembra a la carpeta de la sèrie sense prendre espai de disc extra o copiar tot el contingut del fitxer. Els enllaços durs només funcionaran si l'origen i la destinació estan en el mateix volum",
|
||||
"CopyUsingHardlinksHelpText": "Utilitzeu els enllaços durs quan intenteu copiar fitxers de torrents que encara s'estan sembrant",
|
||||
"DeleteBackup": "Suprimeix la còpia de seguretat",
|
||||
"DeleteBackupMessageText": "Esteu segur que voleu suprimir la còpia de seguretat '{name}'?",
|
||||
"DeleteDownloadClient": "Suprimeix el client de descàrrega",
|
||||
@@ -336,7 +336,7 @@
|
||||
"IgnoredAddresses": "Adreces ignorades",
|
||||
"IgnoredPlaceHolder": "Afegeix una nova restricció",
|
||||
"ImportExtraFiles": "Importa fitxers addicionals",
|
||||
"ImportFailedInterp": "Importació fallida: {0}",
|
||||
"ImportFailedInterp": "ImportFailedInterp",
|
||||
"IncludeHealthWarningsHelpText": "Inclou advertències de salut",
|
||||
"NotificationTriggers": "Activadors de notificacions",
|
||||
"NoUpdatesAreAvailable": "No hi ha actualitzacions disponibles",
|
||||
@@ -428,52 +428,52 @@
|
||||
"BlocklistRelease": "Publicació de la llista de bloqueig",
|
||||
"HasPendingChangesNoChanges": "Sense Canvis",
|
||||
"ManualImportSelectEdition": "Importació manual - Seleccioneu la pel·lícula",
|
||||
"MissingFromDisk": "Readarr no ha pogut trobar el fitxer al disc, de manera que el fitxer es desenllaçarà de la pel·lícula a la base de dades",
|
||||
"MissingFromDisk": "{appName} no ha pogut trobar el fitxer al disc, de manera que el fitxer es desenllaçarà de la pel·lícula a la base de dades",
|
||||
"SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS no és compatible amb aquest indexador",
|
||||
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "S'utilitzarà quan es realitzin cerques automàtiques mitjançant la interfície d'usuari o per Readarr",
|
||||
"CutoffHelpText": "Un cop s'assoleixi aquesta qualitat, Readarr ja no baixarà pel·lícules",
|
||||
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "S'utilitzarà quan es realitzin cerques automàtiques mitjançant la interfície d'usuari o per {appName}",
|
||||
"CutoffHelpText": "Un cop s'assoleixi aquesta qualitat, {appName} ja no baixarà pel·lícules",
|
||||
"ResetAPIKeyMessageText": "Esteu segur que voleu restablir la clau API?",
|
||||
"PropersAndRepacks": "Propietats i Repacks",
|
||||
"RemotePathMappingCheckFolderPermissions": "Readarr pot veure però no accedir al directori de descàrregues {1}. Error de permisos probable.",
|
||||
"RemotePathMappingCheckFolderPermissions": "{appName} pot veure però no accedir al directori de descàrregues {0}. Error de permisos probable.",
|
||||
"RescanAuthorFolderAfterRefresh": "Torna a escanejar la carpeta de pel·lícules després de l'actualització",
|
||||
"RescanAfterRefreshHelpText": "Torneu a escanejar la carpeta de la pel·lícula després d'actualitzar la pel·lícula",
|
||||
"RestartReadarr": "Reinicia Readarr",
|
||||
"RestartReadarr": "Reinicia {appName}",
|
||||
"ShowRelativeDatesHelpText": "Mostra dates relatives (avui/ahir/etc) o absolutes",
|
||||
"ShowSearchActionHelpText": "Mostra el botó de cerca al passar el cursor",
|
||||
"TheAuthorFolderAndAllOfItsContentWillBeDeleted": "La carpeta de pel·lícules '{0}' i tot el seu contingut es suprimiran.",
|
||||
"UrlBaseHelpTextWarning": "Cal reiniciar perquè tingui efecte",
|
||||
"ApplicationURL": "URL de l'aplicació",
|
||||
"ApplicationUrlHelpText": "URL extern de l'aplicació, inclòs http(s)://, port i URL base",
|
||||
"BackupFolderHelpText": "Els camins relatius estaran sota el directori AppData de Readarr",
|
||||
"BackupFolderHelpText": "Els camins relatius estaran sota el directori AppData del {appName}",
|
||||
"CancelPendingTask": "Esteu segur que voleu cancel·lar aquesta tasca pendent?",
|
||||
"ChownGroupHelpTextWarning": "Això només funciona si l'usuari que executa Readarr és el propietari del fitxer. És millor assegurar-se que el client de descàrrega utilitza el mateix grup que Readarr.",
|
||||
"ChownGroupHelpTextWarning": "Això només funciona si l'usuari que executa {appName} és el propietari del fitxer. És millor assegurar-se que el client de descàrrega utilitza el mateix grup que {appName}.",
|
||||
"ConnectSettingsSummary": "Notificacions, connexions a servidors/reproductors multimèdia i scripts personalitzats",
|
||||
"DeleteEmptyFoldersHelpText": "Suprimeix les carpetes de pel·lícules buides durant l'exploració del disc i quan s'esborren els fitxers de pel·lícules",
|
||||
"DeleteImportListMessageText": "Esteu segur que voleu suprimir la llista '{name}'?",
|
||||
"DeleteMetadataProfileMessageText": "Esteu segur que voleu suprimir el perfil de metadades ‘{name}’?",
|
||||
"DeleteMetadataProfileMessageText": "Esteu segur que voleu suprimir el perfil de qualitat {0}",
|
||||
"ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Per obtenir més informació sobre els clients de baixada individuals, feu clic als botons de més informació.",
|
||||
"ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "Per obtenir més informació sobre els indexadors individuals, feu clic als botons d'informació.",
|
||||
"ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "Per obtenir més informació sobre les llistes d'importació individuals, feu clic als botons d'informació.",
|
||||
"IndexerPriorityHelpText": "Prioritat de l'indexador d'1 (la més alta) a 50 (la més baixa). Per defecte: 25. Usada per a desempatar llançaments iguals quan es capturen, Readarr continuarà usant tots els indexadores habilitats per a Sincronització d'RSS i Cerca.",
|
||||
"IndexerRssHealthCheckNoIndexers": "No hi ha indexadors disponibles amb la sincronització RSS activada, Readarr no capturarà les noves versions automàticament",
|
||||
"IndexerSearchCheckNoAutomaticMessage": "No hi ha indexadors disponibles amb la cerca automàtica activada, Readarr no proporcionarà cap resultat de cerca automàtica",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "No hi ha indexadors amb la cerca interactiva activada, Readarr no obtindrà cap resultat de cerca",
|
||||
"IndexerPriorityHelpText": "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, {appName} encara utilitzarà tots els indexadors habilitats per a la sincronització i la cerca RSS",
|
||||
"IndexerRssHealthCheckNoIndexers": "No hi ha indexadors disponibles amb la sincronització RSS activada, {appName} no capturarà les noves versions automàticament",
|
||||
"IndexerSearchCheckNoAutomaticMessage": "No hi ha indexadors disponibles amb la cerca automàtica activada, {appName} no proporcionarà cap resultat de cerca automàtica",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "No hi ha indexadors amb la cerca interactiva activada, {appName} no obtindrà cap resultat de cerca",
|
||||
"IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Actualitzeu fins que s'assoleixi o superi aquesta qualitat",
|
||||
"IsTagUsedCannotBeDeletedWhileInUse": "No es pot suprimir mentre està en ús",
|
||||
"LaunchBrowserHelpText": " Obriu un navegador web i navegueu a la pàgina d'inici de Readarr a l'inici de l'aplicació.",
|
||||
"LaunchBrowserHelpText": " Obriu un navegador web i navegueu a la pàgina d'inici de {appName} a l'inici de l'aplicació.",
|
||||
"LoadingBookFilesFailed": "No s'han pogut carregar els fitxers de pel·lícules",
|
||||
"NoHistory": "Sense història.",
|
||||
"NoHistory": "Sense història",
|
||||
"OnBookFileDeleteForUpgradeHelpText": "Al suprimir el fitxer de pel·lícula per a l'actualització",
|
||||
"OnBookFileDeleteHelpText": "Al suprimir fitxer de pel·lícula",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "La branca {0} no és una branca de llançament de Readarr vàlida, no rebreu actualitzacions",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "La branca {0} no és una branca de llançament de {appName} vàlida, no rebreu actualitzacions",
|
||||
"ReleaseDate": "Dates de llançament",
|
||||
"RemotePathMappingCheckDownloadPermissions": "Readarr pot veure però no accedir a la pel·lícula baixada {0}. Error de permisos probable.",
|
||||
"RemotePathMappingCheckFilesGenericPermissions": "El client de baixada {0} ha informat de fitxers a {1} però Readarr no pot veure aquest directori. És possible que hàgiu d'ajustar els permisos de la carpeta.",
|
||||
"RemotePathMappingCheckGenericPermissions": "El client de baixada {0} col·loca les baixades a {1} però Readarr no pot veure aquest directori. És possible que hàgiu d'ajustar els permisos de la carpeta.",
|
||||
"ReplaceIllegalCharactersHelpText": "Substitueix caràcters il·legals. Si no es marca, Readarr els eliminarà",
|
||||
"RemotePathMappingCheckDownloadPermissions": "{appName} pot veure però no accedir a la pel·lícula baixada {0}. Error de permisos probable.",
|
||||
"RemotePathMappingCheckFilesGenericPermissions": "El client de baixada {0} ha informat de fitxers a {1} però {appName} no pot veure aquest directori. És possible que hàgiu d'ajustar els permisos de la carpeta.",
|
||||
"RemotePathMappingCheckGenericPermissions": "El client de baixada {0} col·loca les baixades a {1} però {appName} no pot veure aquest directori. És possible que hàgiu d'ajustar els permisos de la carpeta.",
|
||||
"ReplaceIllegalCharactersHelpText": "Substitueix caràcters il·legals. Si no es marca, {appName} els eliminarà",
|
||||
"RssSyncIntervalHelpText": "Interval en minuts. Establiu a zero per desactivar (això aturarà tota captura automàtica de llançaments)",
|
||||
"SelectedCountBooksSelectedInterp": "S'han seleccionat {0} pel·lícules",
|
||||
"SettingsRemotePathMappingLocalPathHelpText": "Camí que Readarr hauria d'utilitzar per accedir al camí remot localment",
|
||||
"SettingsRemotePathMappingLocalPathHelpText": "Camí que {appName} hauria d'utilitzar per accedir al camí remot localment",
|
||||
"ShortDateFormat": "Format de data curta",
|
||||
"ShowBookTitleHelpText": "Mostra el títol de la pel·lícula sota el cartell",
|
||||
"ShowRelativeDates": "Mostra les dates relatives",
|
||||
@@ -491,34 +491,34 @@
|
||||
"UserAgentProvidedByTheAppThatCalledTheAPI": "Agent d'usuari proporcionat per l'aplicació per fer peticions a l'API",
|
||||
"BranchUpdateMechanism": "Branca utilitzada pel mecanisme d'actualització extern",
|
||||
"WriteTagsNo": "Mai",
|
||||
"RestartReloadNote": "Nota: Readarr es reiniciarà i tornarà a carregar automàticament la interfície d'usuari durant el procés de restauració.",
|
||||
"RestartReloadNote": "Nota: {appName} es reiniciarà i tornarà a carregar automàticament la interfície d'usuari durant el procés de restauració.",
|
||||
"Series": "Sèries",
|
||||
"ShownAboveEachColumnWhenWeekIsTheActiveView": "Es mostra a sobre de cada columna quan la setmana és la visualització activa",
|
||||
"SorryThatAuthorCannotBeFound": "Ho sentim, aquesta pel·lícula no s'ha trobat.",
|
||||
"SorryThatBookCannotBeFound": "Ho sentim, aquesta pel·lícula no s'ha trobat.",
|
||||
"SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "S'utilitzarà quan s'utilitzi la cerca interactiva",
|
||||
"ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "Això s'aplicarà a tots els indexadors, si us plau, seguiu les regles establertes per ells",
|
||||
"UnableToLoadHistory": "No es pot carregar l'historial.",
|
||||
"UnableToLoadHistory": "No es pot carregar l'historial",
|
||||
"IconTooltip": "Programat",
|
||||
"ReadarrTags": "Etiquetes de Readarr",
|
||||
"ReadarrTags": "Etiquetes de {appName}",
|
||||
"DownloadPropersAndRepacksHelpTexts1": "Si s'ha d'actualitzar automàticament o no a Propers/Repacks",
|
||||
"GrabReleaseMessageText": "Readarr no ha pogut determinar per a quina pel·lícula era aquest llançament. És possible que Readarr no pugui importar automàticament aquesta versió. Voleu capturar '{0}'?",
|
||||
"GrabReleaseMessageText": "{appName} no ha pogut determinar per a quina pel·lícula era aquest llançament. És possible que {appName} no pugui importar automàticament aquesta versió. Voleu capturar \"{0}\"?",
|
||||
"IsCutoffCutoff": "Requisit",
|
||||
"MountCheckMessage": "El muntatge que conté una ruta de pel·lícula es munta com a només de lectura: ",
|
||||
"RescanAfterRefreshHelpTextWarning": "Readarr no detectarà automàticament els canvis als fitxers quan no estigui configurat com a \"Sempre\"",
|
||||
"RescanAfterRefreshHelpTextWarning": "{appName} no detectarà automàticament els canvis als fitxers quan no estigui configurat com a \"Sempre\"",
|
||||
"ShowUnknownAuthorItems": "Mostra elements de pel·lícula desconeguda",
|
||||
"Size": " Mida",
|
||||
"SkipFreeSpaceCheckWhenImportingHelpText": "Utilitzeu-lo quan Readarr no pugui detectar espai lliure a la carpeta arrel de la pel·lícula",
|
||||
"SkipFreeSpaceCheckWhenImportingHelpText": "Utilitzeu-lo quan {appName} no pugui detectar espai lliure a la carpeta arrel de la pel·lícula",
|
||||
"StandardBookFormat": "Format de pel·lícula estàndard",
|
||||
"UnableToAddANewImportListExclusionPleaseTryAgain": "No es pot afegir una nova llista d'exclusió, torneu-ho a provar.",
|
||||
"UnableToLoadReleaseProfiles": "No es poden carregar els perfils de retard",
|
||||
"UnmonitoredHelpText": "Inclou pel·lícules no monitorades al canal iCal",
|
||||
"UpdateAll": "Actualitzar-ho tot",
|
||||
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Les pel·lícules suprimides del disc no es descarten automàticament al Readarr",
|
||||
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Les pel·lícules suprimides del disc no es descarten automàticament al {appName}",
|
||||
"ChownGroupHelpText": "Nom del grup o gid. Utilitzeu gid per a sistemes de fitxers remots.",
|
||||
"AuthorClickToChangeBook": "Feu clic per canviar la pel·lícula",
|
||||
"ChmodFolderHelpTextWarning": "Això només funciona si l'usuari que executa Readarr és el propietari del fitxer. És millor assegurar-se que el client de descàrrega estableixi correctament els permisos.",
|
||||
"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 Readarr com a solució.",
|
||||
"ChmodFolderHelpTextWarning": "Això només funciona si l'usuari que executa {appName} és el propietari del fitxer. És millor assegurar-se que el client de descàrrega estableixi correctament els permisos.",
|
||||
"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 {appName} com a solució.",
|
||||
"CouldntFindAnyResultsForTerm": "No s'ha pogut trobar cap resultat per a '{0}'",
|
||||
"CreateEmptyAuthorFolders": "Creeu carpetes buides per a les pel·lícules",
|
||||
"CreateEmptyAuthorFoldersHelpText": "Creeu carpetes de pel·lícules que falten durant l'exploració del disc",
|
||||
@@ -531,32 +531,32 @@
|
||||
"ImportExtraFilesHelpText": "Importeu fitxers addicionals coincidents (subtítols, nfo, etc.) després d'importar un fitxer de pel·lícula",
|
||||
"ImportListExclusions": "Suprimeix l'exclusió de la llista d'importació",
|
||||
"LongDateFormat": "Format de data llarga",
|
||||
"MaximumSizeHelpText": "Mida màxima per a una versió que es pot capturar en MB. Establiu a zero per establir-lo en il·limitat,",
|
||||
"MaximumSizeHelpText": "Mida màxima per a una versió que es pot capturar en MB. Establiu a zero per establir-lo en il·limitat",
|
||||
"MetadataProfile": "perfil de metadades",
|
||||
"MetadataProfiles": "perfil de metadades",
|
||||
"OnBookFileDelete": "Al suprimir fitxer de pel·lícula",
|
||||
"OnBookFileDeleteForUpgrade": "Al suprimir el fitxer de pel·lícula per a l'actualització",
|
||||
"ReadarrSupportsAnyDownloadClient": "Readarr admet molts clients de baixada de torrent i usenet populars.",
|
||||
"ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Readarr admet qualsevol indexador que utilitzi l'estàndard Newznab, així com altres indexadors que s'enumeren a continuació.",
|
||||
"ReadarrSupportsAnyDownloadClient": "{appName} admet molts clients de baixada de torrent i usenet populars.",
|
||||
"ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "{appName} admet qualsevol indexador que utilitzi l'estàndard Newznab, així com altres indexadors que s'enumeren a continuació.",
|
||||
"RecycleBinHelpText": "Els fitxers de pel·lícula aniran aquí quan se suprimeixin en lloc de suprimir-se permanentment",
|
||||
"RenameBooksHelpText": "Readarr utilitzarà el nom del fitxer existent si el reanomenat està desactivat",
|
||||
"RequiredHelpText": "Aquesta condició {0} ha de coincidir amb el format personalitzat a aplicar. En cas contrari, una única coincidència {0} és suficient.",
|
||||
"UILanguageHelpText": "Idioma que utilitzarà Readarr per a la interfície d'usuari",
|
||||
"RenameBooksHelpText": "{appName} utilitzarà el nom del fitxer existent si el reanomenat està desactivat",
|
||||
"RequiredHelpText": "El llançament ha de contenir almenys un d'aquests termes (no distingeix entre majúscules i minúscules)",
|
||||
"UILanguageHelpText": "Idioma que utilitzarà {appName} per a la interfície d'usuari",
|
||||
"UnableToAddANewRootFolderPleaseTryAgain": "No es pot afegir un indexador nou, torneu-ho a provar.",
|
||||
"UnableToLoadMetadataProfiles": "No es poden carregar els perfils de qualitat",
|
||||
"UpdateMechanismHelpText": "Utilitzeu l'actualitzador integrat de Readarr o un script",
|
||||
"UpdateMechanismHelpText": "Utilitzeu l'actualitzador integrat de {appName} o un script",
|
||||
"UpdateSelected": "Actualització seleccionada",
|
||||
"Database": "Base de dades",
|
||||
"DeleteQualityProfileMessageText": "Esteu segur que voleu suprimir el perfil de qualitat '{name}'?",
|
||||
"DeleteReleaseProfile": "Suprimeix el perfil de llançament",
|
||||
"DeleteReleaseProfileMessageText": "Esteu segur que voleu suprimir aquest perfil de retard?",
|
||||
"DeleteRootFolderMessageText": "Esteu segur que voleu suprimir la carpeta arrel '{name}'?",
|
||||
"DeleteRootFolderMessageText": "Esteu segur que voleu suprimir l'indexador '{0}'?",
|
||||
"DeleteSelectedBookFiles": "Suprimeix els fitxers de pel·lícules seleccionats",
|
||||
"DeleteSelectedBookFilesMessageText": "Esteu segur que voleu suprimir els fitxers de pel·lícules seleccionats?",
|
||||
"IncludeUnknownAuthorItemsHelpText": "Mostra elements sense pel·lícula a la cua. Això podria incloure pel·lícules eliminades o qualsevol altra cosa de la categoria de Readarr",
|
||||
"IncludeUnknownAuthorItemsHelpText": "Mostra elements sense pel·lícula a la cua. Això podria incloure pel·lícules eliminades o qualsevol altra cosa de la categoria de {appName}",
|
||||
"LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "El registre de traça només s'hauria d'habilitar temporalment",
|
||||
"PortHelpTextWarning": "Cal reiniciar perquè tingui efecte",
|
||||
"RemotePathMappingCheckImportFailed": "Readarr no ha pogut importar una pel·lícula. Comproveu els vostres registres per obtenir més informació.",
|
||||
"RemotePathMappingCheckImportFailed": "{appName} no ha pogut importar una pel·lícula. Comproveu els vostres registres per obtenir més informació.",
|
||||
"RemoveTagExistingTag": "Etiqueta existent",
|
||||
"RemoveTagRemovingTag": "S'està eliminant l'etiqueta",
|
||||
"SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "La cerca no és compatible amb aquest indexador",
|
||||
@@ -564,9 +564,9 @@
|
||||
"RequiredPlaceHolder": "Afegeix una nova restricció",
|
||||
"20MinutesTwenty": "20 minuts: {0}",
|
||||
"AlternateTitles": "Títols alternatius",
|
||||
"AnalyticsEnabledHelpText": "Envieu informació anònima d'ús i errors als servidors de Readarr. Això inclou informació sobre el vostre navegador, quines pàgines de l'interfície de Readarr 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.",
|
||||
"AnalyticsEnabledHelpText": "Envieu informació anònima d'ús i errors als servidors de {appName}. Això inclou informació sobre el vostre navegador, quines pàgines {appName} 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",
|
||||
"AuthenticationMethodHelpText": "Requereix el nom d'usuari i la contrasenya per accedir a {appName}",
|
||||
"AuthenticationMethodHelpText": "Requereix nom d'usuari i contrasenya per accedir al radar",
|
||||
"CalendarWeekColumnHeaderHelpText": "Es mostra a sobre de cada columna quan la setmana és la visualització activa",
|
||||
"45MinutesFourtyFive": "45 minuts: {0}",
|
||||
"60MinutesSixty": "60 minuts: {0}",
|
||||
@@ -584,13 +584,13 @@
|
||||
"BypassIfHighestQuality": "Bypass si és de màxima qualitat",
|
||||
"MinimumCustomFormatScore": "Puntuació mínima de format personalitzat",
|
||||
"CustomFormatScore": "Puntuació de format personalitzat",
|
||||
"EnableRssHelpText": "S'utilitzarà quan Readarr cerqui publicacions periòdicament mitjançant RSS Sync",
|
||||
"EnableRssHelpText": "S'utilitzarà quan {appName} cerqui publicacions periòdicament mitjançant RSS Sync",
|
||||
"ImportListMultipleMissingRoots": "Falten diverses carpetes arrel per a les llistes d'importació: {0}",
|
||||
"IndexerDownloadClientHelpText": "Especifiqueu quin client de baixada s'utilitza per a capturar des d'aquest indexador",
|
||||
"ThemeHelpText": "Canvieu el tema de la interfície d'usuari de l'aplicació, el tema \"Automàtic\" utilitzarà el tema del vostre sistema operatiu per configurar el mode clar o fosc. Inspirat en Theme.Park",
|
||||
"UnableToLoadCustomFormats": "No es poden carregar formats personalitzats",
|
||||
"DeleteCustomFormat": "Suprimeix el format personalitzat",
|
||||
"DeleteFormatMessageText": "Esteu segur que voleu suprimir l'etiqueta de format '{0}'?",
|
||||
"DeleteFormatMessageText": "Esteu segur que voleu suprimir l'etiqueta de format {0} ?",
|
||||
"ExportCustomFormat": "Exporta el format personalitzat",
|
||||
"Formats": "Formats",
|
||||
"IncludeCustomFormatWhenRenamingHelpText": "Inclou en {Custom Formats} el format de canvi de nom",
|
||||
@@ -607,8 +607,8 @@
|
||||
"CustomFormat": "Format personalitzat",
|
||||
"CustomFormatSettings": "Configuració de formats personalitzats",
|
||||
"CustomFormats": "Formats personalitzats",
|
||||
"CutoffFormatScoreHelpText": "Un cop s'arribi a aquesta puntuació de format personalitzat, Readarr ja no baixarà pel·lícules",
|
||||
"DeleteCustomFormatMessageText": "Esteu segur que voleu suprimir el format personalitzat ‘{name}’?",
|
||||
"CutoffFormatScoreHelpText": "Un cop s'arribi a aquesta puntuació de format personalitzat, {appName} ja no baixarà pel·lícules",
|
||||
"DeleteCustomFormatMessageText": "Esteu segur que voleu suprimir l'indexador '{0}'?",
|
||||
"ImportListMissingRoot": "Falta la carpeta arrel per a les llistes d'importació: {0}",
|
||||
"IndexerTagsHelpText": "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.",
|
||||
"ColonReplacement": "Substitució de dos punts",
|
||||
@@ -711,7 +711,7 @@
|
||||
"DownloadClientDelugeSettingsDirectoryCompleted": "Directori al qual es mou quan s'hagi completat",
|
||||
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge",
|
||||
"DownloadClientDelugeSettingsDirectoryHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge",
|
||||
"WhatsNew": "Què hi ha de nou?",
|
||||
"WhatsNew": "Novetats",
|
||||
"SelectDropdown": "Seleccioneu...",
|
||||
"NoCutoffUnmetItems": "No hi ha elements de tall no assolits",
|
||||
"ApplyTagsHelpTextHowToApplyAuthors": "Com aplicar etiquetes a les pel·lícules seleccionades",
|
||||
@@ -723,13 +723,13 @@
|
||||
"ResetQualityDefinitions": "Restableix les definicions de qualitat",
|
||||
"Small": "Petita",
|
||||
"TotalSpace": "Espai total",
|
||||
"BlocklistReleaseHelpText": "Impedeix que Readarr torni a capturar aquesta versió automàticament",
|
||||
"BlocklistReleaseHelpText": "Impedeix que {appName} torni a capturar aquesta versió automàticament",
|
||||
"CatalogNumber": "número de catàleg",
|
||||
"LastWriteTime": "La darrera hora d'escriptura",
|
||||
"NextExecution": "Propera execució",
|
||||
"RemoveCompleted": "S'ha eliminat",
|
||||
"SelectReleaseGroup": "Seleccioneu grup de llançament",
|
||||
"CountDownloadClientsSelected": "{selectedCount} client(s) de baixada seleccionat(s)",
|
||||
"CountDownloadClientsSelected": "{count} client(s) de baixada seleccionat(s)",
|
||||
"Authors": "Autor",
|
||||
"FreeSpace": "Espai lliure",
|
||||
"ExtraFileExtensionsHelpText": "Llista separada per comes de fitxers addicionals per importar (.nfo s'importarà com a .nfo-orig)",
|
||||
@@ -740,7 +740,7 @@
|
||||
"RemoveFailed": "Ha fallat l'eliminació",
|
||||
"ImportLists": "llista d'importació",
|
||||
"RemovingTag": "S'està eliminant l'etiqueta",
|
||||
"ApiKeyValidationHealthCheckMessage": "Actualitzeu la vostra clau de l'API perquè tingui almenys {0} caràcters. Podeu fer-ho mitjançant la configuració o el fitxer de configuració",
|
||||
"ApiKeyValidationHealthCheckMessage": "Actualitzeu la vostra clau de l'API perquè tingui almenys {length} caràcters. Podeu fer-ho mitjançant la configuració o el fitxer de configuració",
|
||||
"ExtraFileExtensionsHelpTextsExamples": "Exemples: '.sub, .nfo' o 'sub,nfo'",
|
||||
"SourceTitle": "Títol de la font",
|
||||
"NoEventsFound": "No s'han trobat esdeveniments",
|
||||
@@ -750,9 +750,9 @@
|
||||
"RecentChanges": "Canvis recents",
|
||||
"Rejections": "Rebutjats",
|
||||
"StatusEndedContinuing": "Continua",
|
||||
"DeleteBookFileMessageText": "Esteu segur que voleu suprimir '{0}'?",
|
||||
"DeleteBookFileMessageText": "Esteu segur que voleu suprimir '{path}'?",
|
||||
"DownloadClientTagHelpText": "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.",
|
||||
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "El client de baixada {0} està configurat per eliminar les baixades completades. Això pot provocar que les baixades s'eliminin del vostre client abans que {1} pugui importar-les.",
|
||||
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "El client de baixada {downloadClientName} està configurat per eliminar les baixades completades. Això pot provocar que les baixades s'eliminin del vostre client abans que {1} pugui importar-les.",
|
||||
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Cerqueu i intenteu baixar automàticament una versió diferent quan es trobi una versió fallida a la cerca interactiva",
|
||||
"FailedLoadingSearchResults": "No s'han pogut carregar els resultats de la cerca, torneu-ho a provar.",
|
||||
"IndexerFlags": "Indicadors de l'indexador",
|
||||
@@ -784,343 +784,5 @@
|
||||
"DisabledForLocalAddresses": "Desactivat per a adreces locals",
|
||||
"Enabled": "Habilitat",
|
||||
"External": "Extern",
|
||||
"ApiKey": "Clau API",
|
||||
"FailedToFetchUpdates": "No s'han pogut obtenir les actualitzacions",
|
||||
"AptUpdater": "Utilitzeu apt per a instal·lar l'actualització",
|
||||
"BuiltIn": "Integrat",
|
||||
"CurrentlyInstalled": "Instal·lat actualment",
|
||||
"DockerUpdater": "actualitzeu el contenidor Docker per a rebre l'actualització",
|
||||
"ExternalUpdater": "{appName} està configurat per a utilitzar un mecanisme d'actualització extern",
|
||||
"InstallLatest": "Instal·la l'últim",
|
||||
"OnLatestVersion": "La darrera versió de {appName} ja està instal·lada",
|
||||
"Script": "Script",
|
||||
"UnmappedFiles": "Carpetes sense mapejar",
|
||||
"UpdateAppDirectlyLoadError": "No es pot actualitzar {appName} directament,",
|
||||
"AddMissing": "Afegeix faltants",
|
||||
"Install": "Instal·la",
|
||||
"PasswordConfirmation": "Confirmeu la contrasenya",
|
||||
"PreviouslyInstalled": "Instal·lat anteriorment",
|
||||
"AddNewItem": "Afegeix un nou element",
|
||||
"ErrorLoadingContent": "S'ha produït un error en carregar aquest contingut",
|
||||
"DashOrSpaceDashDependingOnName": "Traç o guió d'espai depenent del nom",
|
||||
"EnabledHelpText": "Marqueu-ho per a habilitar el perfil de la versió",
|
||||
"IgnoreDownload": "Ignora la baixada",
|
||||
"IgnoreDownloadHint": "Atura {appName} de processar aquesta baixada més",
|
||||
"IgnoreDownloads": "Ignora les baixades",
|
||||
"IgnoreDownloadsHint": "Atura {appName} de processar aquestes baixades més",
|
||||
"IndexerIdHelpText": "Especifiqueu a quin indexador s'aplica el perfil",
|
||||
"LabelIsRequired": "L'etiqueta és necessària",
|
||||
"MediaManagementSettingsSummary": "Nomenat, configuració de la gestió de fitxers i carpetes arrel",
|
||||
"MetadataSource": "Font de les metadades",
|
||||
"NotificationsPlexSettingsAuthenticateWithPlexTv": "Autentica amb Plex.tv",
|
||||
"RemoveCompletedDownloads": "Elimina les baixades completes",
|
||||
"RemoveFailedDownloads": "Elimina les baixades fallides",
|
||||
"RemoveFromDownloadClientHint": "Elimina la baixada i el(s) fitxer(s) del client de baixada",
|
||||
"RemoveMultipleFromDownloadClientHint": "Elimina les baixades i els fitxers del client de baixada",
|
||||
"IndexerSettingsSeedTime": "Temps de la llavor",
|
||||
"IndexerSettingsSeedTimeHelpText": "El temps en què s'ha de sembrar un torrent abans d'aturar-lo, el buit utilitza el valor per defecte del client de baixada",
|
||||
"IndexerSettingsSeedRatio": "Ràtio de la llavor",
|
||||
"ResetQualityDefinitionsMessageText": "Esteu segur que voleu restablir les definicions de qualitat?",
|
||||
"SelectIndexerFlags": "Selecciona les banderes de l'indexador",
|
||||
"ShowBanners": "Mostra els bàners",
|
||||
"SkipRedownload": "Omet que es torni a descarregar",
|
||||
"SmartReplace": "Reemplaçament intel·ligent",
|
||||
"UseSSL": "Usa SSL",
|
||||
"WhySearchesCouldBeFailing": "Feu clic aquí per saber per què les cerques podrien estar fallant",
|
||||
"ThereWasAnErrorLoadingThisItem": "S'ha produït un error en carregar aquest element",
|
||||
"SearchMonitored": "Cerca monitorats",
|
||||
"DeleteSelected": "Suprimeix els seleccionats",
|
||||
"AllExpandedExpandAll": "Expandeix-ho tot",
|
||||
"CustomFormatsSettingsTriggerInfo": "Un format personalitzat s'aplicarà a un llançament o fitxer quan coincideixi almenys amb un de cada un dels diferents tipus de condició escollits.",
|
||||
"InstallMajorVersionUpdate": "Instal·la l'actualització",
|
||||
"InstallMajorVersionUpdateMessage": "Aquesta actualització instal·larà una nova versió principal i pot no ser compatible amb el vostre sistema. Esteu segur que voleu instal·lar aquesta actualització?",
|
||||
"NotificationsPlexSettingsAuthToken": "Testimoni d'autenticació",
|
||||
"Unknown": "Desconegut",
|
||||
"IsShowingMonitoredMonitorSelected": "Monitor seleccionat",
|
||||
"IsShowingMonitoredUnmonitorSelected": "Unmonitor seleccionat",
|
||||
"NotificationsSettingsUpdateMapPathsFromHelpText": "Camí de {appName}, utilitzat per modificar els camins de sèries quan {serviceName} veu la ubicació del camí de la biblioteca diferent de {appName} (requereix 'Biblioteca d'actualització')",
|
||||
"NotificationsSettingsUpdateMapPathsToHelpText": "Camí de {serviceName}, utilitzat per modificar els camins de sèries quan {serviceName} veu la ubicació del camí de la biblioteca diferent de {appName} (requereix 'Biblioteca d'actualització')",
|
||||
"NoMissingItems": "No falten elements",
|
||||
"InstallMajorVersionUpdateMessageLink": "Si us plau, comproveu [{domain}]({url}) per a més informació.",
|
||||
"RemoveQueueItemsRemovalMethodHelpTextWarning": "'Elimina del client de baixada' eliminarà les baixades i els fitxers del client de baixada.",
|
||||
"Monitoring": "Monitorant",
|
||||
"RemoveSelectedItemBlocklistMessageText": "Esteu segur que voleu eliminar els elements seleccionats de la llista de bloqueigs?",
|
||||
"NotificationsSettingsUseSslHelpText": "Connecta a {serviceName} a través d'HTTPS en lloc d'HTTP",
|
||||
"RemoveQueueItemRemovalMethod": "Mètode d'eliminació",
|
||||
"RemoveQueueItemRemovalMethodHelpTextWarning": "'Elimina des del client de baixada' eliminarà la baixada i el(s) fitxer(s) del client de baixada.",
|
||||
"LastSearched": "Darrera cerca",
|
||||
"NotificationsSettingsUpdateMapPathsFrom": "Mapear els camins des de",
|
||||
"NotificationsSettingsUpdateMapPathsTo": "Mapear els camins a",
|
||||
"RemoveQueueItem": "Elimina - {sourceTitle}",
|
||||
"SetIndexerFlags": "Estableix els indicadors de l'indexador",
|
||||
"NotificationsSettingsUpdateLibrary": "Actualitza la biblioteca",
|
||||
"IndexerSettingsSeedRatioHelpText": "Ràtio a la qual ha d'arribar un torrent abans d'aturar-se, buit utilitza el valor per defecte del client de baixada. La relació ha de ser com a mínim 1.0 i seguir les regles dels indexadors",
|
||||
"FailedToFetchSettings": "No s'ha pogut recuperar la configuració",
|
||||
"MonitoringOptions": "Opcions de monitoratge",
|
||||
"RootFolderPathHelpText": "Els elements de la llista del directori arrel s'afegiran a",
|
||||
"ThereWasAnErrorLoadingThisPage": "S'ha produït un error en carregar aquesta pàgina",
|
||||
"BookEditor": "Editor de llibres",
|
||||
"BookProgressBarText": "{bookCount} / {totalBookCount} (Files: {bookFileCount})",
|
||||
"ConsoleLogLevel": "Nivell de registre de la consola",
|
||||
"Country": "País",
|
||||
"DataFutureBooks": "Controla els llibres que encara no s'han publicat",
|
||||
"EmbedMetadataInBookFiles": "Incrusta les metadades als fitxers de llibre",
|
||||
"ForeignId": "ID estranger",
|
||||
"GoToAuthorListing": "Ves a la llista d'autors",
|
||||
"HasMonitoredBooksNoMonitoredBooksForThisAuthor": "No hi ha llibres supervisats per a aquest autor",
|
||||
"HideBooks": "Oculta els llibres",
|
||||
"ISBN": "ISBN",
|
||||
"IgnoreDeletedBooks": "Ignora els llibres eliminats",
|
||||
"IfYouDontAddAnImportListExclusionAndTheAuthorHasAMetadataProfileOtherThanNoneThenThisBookMayBeReaddedDuringTheNextAuthorRefresh": "Si no afegeixes una exclusió de llista d'importació i l'autor té un perfil de metadades distint de 'Cap', llavors aquest llibre pot ser de nou afegit durant el següent refresc d'autor.",
|
||||
"IgnoredMetaHelpText": "Els llibres s'ignoraran si contenen un o més termes (insensible a majúscules i minúscules)",
|
||||
"MassBookSearchWarning": "Esteu segur que voleu realitzar una cerca massiva de llibres per {0} llibres?",
|
||||
"InteractiveSearchModalHeaderBookAuthor": "Cerca interactiva - {bookTitle} per {authorName}",
|
||||
"IsCalibreLibraryHelpText": "Usa el servidor de contingut del Calibre per manipular la biblioteca",
|
||||
"IsExpandedHideBooks": "Oculta els llibres",
|
||||
"IsExpandedShowBooks": "Mostra els llibres",
|
||||
"IsInUseCantDeleteAQualityProfileThatIsAttachedToAnAuthorOrImportList": "No es pot suprimir un perfil de qualitat que està adjuntat a un autor o llista d'importació",
|
||||
"Iso639-3": "Codis de llengua ISO 639-3, o 'null', separats per comes",
|
||||
"LibraryHelpText": "Nom de la biblioteca del servidor de contingut del Calibre. Deixeu-ho en blanc per defecte.",
|
||||
"LogRotation": "Rotació del registre",
|
||||
"LogSqlHelpText": "Registra totes les consultes SQL des de Readarr",
|
||||
"MassBookSearch": "Cerca massiva de llibres",
|
||||
"MetadataConsumers": "Consumidors de metadades",
|
||||
"MetadataSourceHelpText": "Font alternativa de metadades (Deixa en blanc per defecte)",
|
||||
"BookNaming": "Nom del llibre",
|
||||
"CalibreLibrary": "Biblioteca del Calibre",
|
||||
"OnImportFailure": "En importar fallada",
|
||||
"OnAuthorAdded": "En afegir l'autor",
|
||||
"SendMetadataToCalibre": "Envia les metadades al Calibre",
|
||||
"SeriesNumber": "Número de sèrie",
|
||||
"SeriesTotal": "Sèries ({0})",
|
||||
"AllowFingerprintingHelpTextWarning": "Això requereix que el Readarr llegeixi parts del fitxer que alentiran els escanejos i poden causar una alta activitat de disc o de xarxa.",
|
||||
"AuthorProgressBarText": "{availableBookCount} / {bookCount} (Total: {totalBookCount}, Fitxers: {bookFileCount})",
|
||||
"AutomaticallySwitchEdition": "Canvia l'edició automàticament",
|
||||
"BackupIntervalHelpText": "Interval per a fer una còpia de seguretat de la base de dades de Readarr i de la configuració",
|
||||
"CalibreContentServer": "Servidor de contingut del Calibre",
|
||||
"CalibreContentServerText": "L'ús d'un Servidor de Contingut Calibre (no Calibre Web) permet a Readarr afegir llibres a la vostra biblioteca Calibre i activar les conversions entre formats",
|
||||
"CalibrePassword": "Contrasenya del Calibre",
|
||||
"CalibreUsername": "Nom d'usuari del Calibre",
|
||||
"CountIndexersSelected": "{selectedCount} indexador(s) seleccionat",
|
||||
"DataListMonitorAll": "Controla els autors i tots els llibres de cada autor inclosos a la llista d'importació",
|
||||
"DataMissingBooks": "Monitoritza els llibres que encara no tenen fitxers o que encara no s'han publicat",
|
||||
"DefaultMonitorOptionHelpText": "Quins llibres s'han de controlar en afegir-los inicialment per als autors detectats en aquesta carpeta",
|
||||
"DefaultQualityProfileIdHelpText": "Perfil de qualitat per defecte per als autors detectats en aquesta carpeta",
|
||||
"EditList": "Edita la llista",
|
||||
"EmbedMetadataHelpText": "Digues al Calibre que escrigui les metadades al fitxer de llibre real",
|
||||
"ExistingBooks": "Llibres existents",
|
||||
"ForeignIdHelpText": "L'identificador estranger de l'autor/llibre a excloure",
|
||||
"FutureDaysHelpText": "Dies per a l'alimentació iCal per mirar al futur",
|
||||
"ImportFailures": "Importa fallades",
|
||||
"ImportListSpecificSettings": "Importa la configuració específica de la llista",
|
||||
"ItsEasyToAddANewAuthorOrBookJustStartTypingTheNameOfTheItemYouWantToAdd": "És fàcil afegir un Autor nou o un Llibre simplement començar a escriure el nom de l'element que voleu afegir",
|
||||
"LatestBook": "Últim llibre",
|
||||
"MinPagesHelpText": "Ignora els llibres amb menys pàgines que això",
|
||||
"MinPopularityHelpText": "La popularitat és la qualificació mitjana * nombre de vots",
|
||||
"MinimumPopularity": "Popularitat mínima",
|
||||
"MissingBooks": "Llibres que falten",
|
||||
"MonitorAuthor": "Autor del monitor",
|
||||
"MonitorBook": "Llibre del monitor",
|
||||
"MonitorBookExistingOnlyWarning": "Aquest és un ajust ajustat de la configuració supervisada per a cada llibre. Utilitzeu l'opció d'Autor/Edita per controlar què passa amb els llibres acabats d'afegir",
|
||||
"MonitorNewBooks": "Monitora els llibres nous",
|
||||
"MonitorNewItemsHelpText": "Quins llibres nous s'han de controlar",
|
||||
"MonitoredAuthorIsMonitored": "L'autor està monitoritzat",
|
||||
"MonitoredAuthorIsUnmonitored": "L'autor no està monitoritzat",
|
||||
"MonitoringOptionsHelpText": "Quins llibres s'han de controlar després d'afegir l'autor (ajust d'un sol cop)",
|
||||
"MusicBrainzBookID": "ID del llibre MusicBrainz",
|
||||
"MusicBrainzReleaseID": "ID de llançament del MusicBrainz",
|
||||
"MusicBrainzTrackID": "ID de la pista MusicBrainz",
|
||||
"NameLastFirst": "Cognom Nom",
|
||||
"NewBooks": "Llibres nous",
|
||||
"NoName": "No mostris el nom",
|
||||
"OnAuthorAddedHelpText": "En afegir l'autor",
|
||||
"OnAuthorDelete": "En suprimir l'autor",
|
||||
"OnAuthorDeleteHelpText": "En suprimir l'autor",
|
||||
"OnBookDeleteHelpText": "En suprimir el llibre",
|
||||
"OnBookRetagHelpText": "En reetiquetar el llibre",
|
||||
"OnDownloadFailure": "A la fallada de baixada",
|
||||
"OnDownloadFailureHelpText": "A la fallada de baixada",
|
||||
"OnImportFailureHelpText": "En importar fallada",
|
||||
"OnReleaseImport": "En publicar la importació",
|
||||
"OnReleaseImportHelpText": "En publicar la importació",
|
||||
"OutputFormatHelpText": "Opcionalment, demaneu al Calibre que es converteixi a altres formats en importar. Llista separada per comes.",
|
||||
"PathHelpText": "Carpeta arrel que conté la biblioteca de llibres",
|
||||
"PathHelpTextWarning": "Això ha de ser diferent del directori on el vostre client de baixada posa fitxers",
|
||||
"PortHelpText": "Port del servidor de contingut del Calibre",
|
||||
"PreviewRetag": "Reetiqueta de la vista prèvia",
|
||||
"QualityProfileIdHelpText": "Els elements de la llista de perfils de qualitat s'han d'afegir amb",
|
||||
"ReadarrSupportsMultipleListsForImportingBooksAndAuthorsIntoTheDatabase": "Readarr admet múltiples llistes per importar llibres i autors a la base de dades.",
|
||||
"RecycleBinUnableToWriteHealthCheck": "No s'ha pogut escriure a la carpeta de contenidors de reciclatge configurada: {0}. Assegureu-vos que aquest camí existeix i que l'usuari que executa el Readarr pot escriure",
|
||||
"RefreshAuthor": "Actualitza l'autor",
|
||||
"RefreshBook": "Actualitza el llibre",
|
||||
"RefreshInformation": "Actualitza la informació",
|
||||
"RemotePathMappingsInfo": "Remote Path Mappings són molt rarament necessaris, si {app} i el vostre client de descàrrega estan en el mateix sistema, és millor que coincideixi amb els vostres camins. Per a més informació, vegeu el [wiki]({wikiLink}).",
|
||||
"SearchForMonitoredBooks": "Cerca llibres monitoritzats",
|
||||
"SearchForNewItems": "Cerca elements nous",
|
||||
"SelectBook": "Selecciona el llibre",
|
||||
"SelectEdition": "Selecciona l'edició",
|
||||
"ShouldMonitorExisting": "Controla els llibres existents",
|
||||
"ShouldSearchHelpText": "Cerca indexadors per als elements nous afegits. Utilitza amb precaució per a llistes grans.",
|
||||
"ShowBookCount": "Mostra el recompte de llibres",
|
||||
"ShowTitleHelpText": "Mostra el nom de l'autor sota el cartell",
|
||||
"StatusEndedDeceased": "Defunció",
|
||||
"TooManyBooks": "Falten o hi ha massa llibres? Modifica o crea un nou",
|
||||
"UnableToLoadMetadataProviderSettings": "No s'ha pogut carregar la configuració del proveïdor de metadades",
|
||||
"UrlBaseHelpText": "Afegeix un prefix a l'URL del Calibre, p. ex. http://[host]:[port]/[urlBase]",
|
||||
"ShouldMonitorExistingHelpText": "Controla automàticament els llibres d'aquesta llista que ja són a Readarr",
|
||||
"ShowBannersHelpText": "Mostra els bàners en lloc dels noms",
|
||||
"ShowLastBook": "Mostra l'últim llibre",
|
||||
"ShowName": "Mostra el nom",
|
||||
"SkipBooksWithNoISBNOrASIN": "Omet els llibres sense ISBN ni ASIN",
|
||||
"SkipPartBooksAndSets": "Omet els llibres de parts i els conjunts",
|
||||
"SkipSecondarySeriesBooks": "Omet els llibres de les sèries secundàries",
|
||||
"SpecificBook": "Llibre específic",
|
||||
"FileDetails": "Detalls del fitxer",
|
||||
"FilesTotal": "Fitxers ({0})",
|
||||
"FilterAnalyticsEvents": "Filtra els esdeveniments d'anàlisi",
|
||||
"FilterAuthor": "Filtra l'autor",
|
||||
"FilterSentryEventsHelpText": "Filtra els esdeveniments d'error d'usuari coneguts perquè s'enviïn com a Analytics",
|
||||
"IsExpandedShowFileInfo": "Mostra la informació del fitxer",
|
||||
"IsInUseCantDeleteAMetadataProfileThatIsAttachedToAnAuthorOrImportList": "No es pot suprimir un perfil de metadades que està adjuntat a un autor o a una llista d'importació",
|
||||
"PasswordHelpText": "Contrasenya del servidor de contingut del Calibre",
|
||||
"PastDays": "Dies passats",
|
||||
"WriteTagsAll": "Tots els fitxers; només importació inicial",
|
||||
"TagsHelpText": "Aplica als autors amb almenys una etiqueta coincident. Deixa en blanc per aplicar a tots els autors",
|
||||
"TotalBookCountBooksTotalBookFileCountBooksWithFilesInterp": "{0} llibres en total. {1} llibres amb fitxers.",
|
||||
"TrackNumber": "Número de pista",
|
||||
"TrackTitle": "Títol de la pista",
|
||||
"WatchLibraryForChangesHelpText": "Torna a explorar automàticament quan els fitxers canviïn en una carpeta arrel",
|
||||
"WatchRootFoldersForFileChanges": "Vigila les carpetes arrel per als canvis de fitxer",
|
||||
"WriteAudioTagsScrub": "Neteja les etiquetes existents",
|
||||
"WriteAudioTagsScrubHelp": "Elimina les etiquetes existents dels fitxers, deixant només les afegides pel Readarr.",
|
||||
"LogSQL": "Log SQL",
|
||||
"AnyEditionOkHelpText": "Readarr canviarà automàticament a l'edició que coincideixi millor amb els fitxers baixats",
|
||||
"AudioFileMetadata": "Escriu les metadades als fitxers d'àudio",
|
||||
"AuthorEditor": "Editor de l'autor",
|
||||
"AuthorFolderFormat": "Format de carpeta d'autor",
|
||||
"EntityName": "Nom de l'entitat",
|
||||
"FilterPlaceHolder": "Filtra el llibre",
|
||||
"UsernameHelpText": "Nom d'usuari del servidor de contingut del Calibre",
|
||||
"OnBookDelete": "En suprimir el llibre",
|
||||
"BypassIfHighestQualityHelpText": "Evita el retard quan el llançament tingui la qualitat més alta habilitada en el perfil de qualitat",
|
||||
"CountImportListsSelected": "{selectedCount} llista(es) d'importació seleccionada",
|
||||
"DataNone": "No es controlarà cap llibre",
|
||||
"IsExpandedHideFileInfo": "Amaga la informació del fitxer",
|
||||
"NoTagsHaveBeenAddedYet": "No s'han afegit etiquetes encara. Afegeix etiquetes per enllaçar autors amb perfils de retard, restriccions o notificacions. Feu clic a {0} per obtenir més informació sobre les etiquetes a Readarr.",
|
||||
"DeleteMetadataProfile": "Suprimeix el perfil de metadades",
|
||||
"FutureBooks": "Llibres futurs",
|
||||
"FutureDays": "Dies de futur",
|
||||
"MetadataProfileIdHelpText": "Els elements de la llista de perfils de metadades s'han d'afegir amb",
|
||||
"NameFirstLast": "Nom Cognom",
|
||||
"DownloadPropersAndRepacksHelpTexts2": "Usa 'No prefereixis' per ordenar per puntuació de paraules preferida sobre propers/repacks",
|
||||
"IndexerIdHelpTextWarning": "L'ús d'un indexador específic amb paraules preferides pot conduir a versions duplicades",
|
||||
"LogRotateHelpText": "Nombre màxim de fitxers de registre a mantenir desats a la carpeta de registres",
|
||||
"MetadataProviderSource": "Font del proveïdor de metadades",
|
||||
"MissingBooksAuthorMonitored": "Llibres que falten (controlat per l'autor)",
|
||||
"MissingBooksAuthorNotMonitored": "Llibres que falten (Autor no supervisat)",
|
||||
"MonitoredHelpText": "Readarr cercarà i descarregarà un llibre",
|
||||
"UseCalibreContentServer": "Usa el servidor de contingut del Calibre",
|
||||
"MonitorNewItems": "Monitora els llibres nous",
|
||||
"SearchForAllCutoffUnmetBooks": "Cerca tots els llibres retallats i no satisfets",
|
||||
"SearchForAllMissingBooks": "Cerca tots els llibres que falten",
|
||||
"TheFollowingFilesWillBeDeleted": "S'eliminaran els següents fitxers:",
|
||||
"TagsSettingsSummary": "Gestiona les etiquetes d'autor, perfil, restricció i notificació",
|
||||
"WriteAudioTags": "Etiqueta els fitxers d'àudio amb metadades",
|
||||
"AddImportListExclusionHelpText": "Evita que el llibre s'afegeixi al Readarr mitjançant la importació de llistes o l'actualització de l'autor",
|
||||
"BookStudio": "Book Studio",
|
||||
"BookTitle": "Títol del llibre",
|
||||
"Books": "Llibres",
|
||||
"DataListMonitorSpecificBook": "Autors de monitors, però només monitoritza els llibres inclosos explícitament a la llista",
|
||||
"OnBookTagUpdate": "En actualitzar l'etiqueta del llibre",
|
||||
"CalibreNotCalibreWeb": "Readarr pot interactuar amb el servidor de continguts de Calibre. No pot utilitzar Calibre-Web, que és programari no relacionat.",
|
||||
"ShouldMonitorHelpText": "Monitora els autors i llibres nous afegits d'aquesta llista",
|
||||
"MusicBrainzRecordingID": "ID d'enregistrament del MusicBrainz",
|
||||
"AllAuthorBooks": "Tots els autors",
|
||||
"EditionsHelpText": "Canvia l'edició d'aquest llibre",
|
||||
"BooksTotal": "Llibres ({0})",
|
||||
"CountAuthorsSelected": "{selectedCount} autors seleccionats",
|
||||
"DataNewAllBooks": "Monitora tots els llibres nous",
|
||||
"LoadingEditionsFailed": "Ha fallat la càrrega d'edicions",
|
||||
"MetadataSettingsSummary": "Crea fitxers de metadades quan s'importin llibres o s'actualitzi l'autor",
|
||||
"SearchBoxPlaceHolder": "P. ex. Guerra i pau, goodreads:656, isbn:067003469X, asin:B00JCDK5ME",
|
||||
"SkipRedownloadHelpText": "Evita que el Readarr intenti baixar versions alternatives per als elements eliminats",
|
||||
"CalibreMetadata": "Metadades del Calibre",
|
||||
"Bookshelf": "Prestatge",
|
||||
"DataListMonitorNone": "No vigilar autors ni llibres",
|
||||
"ImportListSettings": "Configuració general de la llista d'importació",
|
||||
"AuthorIndex": "Índex de l'autor",
|
||||
"AuthorNameHelpText": "El nom de l'autor/llibre a excloure (pot ser qualsevol cosa significativa)",
|
||||
"BookIndex": "Índex del llibre",
|
||||
"BookList": "Llista de llibres",
|
||||
"CalibreHost": "Amfitrió del Calibre",
|
||||
"CalibrePort": "Port del Calibre",
|
||||
"CalibreSettings": "Paràmetres del Calibre",
|
||||
"CalibreUrlBase": "Base d'url del Calibre",
|
||||
"UpdateCovers": "Actualitza les converses",
|
||||
"UpdateCoversHelpText": "Estableix les portades del llibre al Calibre perquè coincideixin amb les del Readarr",
|
||||
"UseSslHelpText": "Usa SSL per a connectar al servidor de contingut Calibre",
|
||||
"WriteBookTagsHelpTextWarning": "En seleccionar ‘Tots els fitxers’ s'alteraran els fitxers existents quan s'importin.",
|
||||
"WriteTagsNew": "Només per a baixades noves",
|
||||
"AddedAuthorSettings": "Configuració de l'autor afegit",
|
||||
"DefaultMetadataProfileIdHelpText": "Perfil predeterminat de metadades per als autors detectats en aquesta carpeta",
|
||||
"ContinuingMoreBooksAreExpected": "S'esperen més llibres",
|
||||
"DataNewBooks": "Supervisa els llibres nous publicats després del llibre més nou existent",
|
||||
"AllowAuthorChangeClickToChangeAuthor": "Feu clic per canviar l'autor",
|
||||
"AllowedLanguages": "Llengües permeses",
|
||||
"ContinuingNoAdditionalBooksAreExpected": "No s'espera cap llibre addicional",
|
||||
"DeleteBookFile": "Suprimeix el fitxer de llibre",
|
||||
"ConvertToFormat": "Converteix a format",
|
||||
"DataAllBooks": "Controla tots els llibres",
|
||||
"DataExistingBooks": "Controla els llibres que tenen fitxers o que encara no s'han publicat",
|
||||
"DeleteFormat": "Suprimeix el format",
|
||||
"DeleteFilesHelpText": "Suprimeix els fitxers del llibre i la carpeta de l'autor",
|
||||
"DiscNumber": "Número de disc",
|
||||
"EndedAllBooksDownloaded": "Ended (Tots els llibres baixats)",
|
||||
"FirstBook": "Primer llibre",
|
||||
"HostHelpText": "Servidor de contingut del Calibre",
|
||||
"MonitorExistingBooks": "Monitora els llibres existents",
|
||||
"MusicBrainzAuthorID": "ID de l'autor del MusicBrainz",
|
||||
"NameStyle": "Estil del nom de l'autor",
|
||||
"PastDaysHelpText": "Dies per a l'alimentació iCal per a mirar el passat",
|
||||
"DataFirstBook": "Fes el seguiment del primer llibre. Tots els altres llibres seran ignorats",
|
||||
"DataLatestBook": "Controla els últims llibres i futurs",
|
||||
"DataNewNone": "No vigila cap llibre nou",
|
||||
"DefaultReadarrTags": "Etiquetes del lector per defecte",
|
||||
"DefaultTagsHelpText": "Etiquetes de Readarr per defecte per als autors detectats en aquesta carpeta",
|
||||
"DiscCount": "Comptador de discs",
|
||||
"EditAuthor": "Edita l'autor",
|
||||
"EditBook": "Edita el llibre",
|
||||
"ExistingItems": "Elements existents",
|
||||
"ManualDownload": "Baixada manual",
|
||||
"MinimumPages": "Pàgines mínimes",
|
||||
"CollapseMultipleBooks": "Redueix diversos llibres",
|
||||
"CollapseMultipleBooksHelpText": "Redueix diversos llibres que es publiquen el mateix dia",
|
||||
"ContinuingAllBooksDownloaded": "Continuant (tots els llibres baixats)",
|
||||
"Development": "Desenvolupament",
|
||||
"EnableAutomaticAddHelpText": "Afegeix autor/llibres al Readarr quan es realitzin sincronitzacions a través de la IU o per Readarr",
|
||||
"RenameBooks": "Canvia el nom dels llibres",
|
||||
"SearchBook": "Cerca al llibre",
|
||||
"SelectedCountAuthorsSelectedInterp": "{0} Autor/s seleccionat/s",
|
||||
"SetReadarrTags": "Estableix les etiquetes del Readarr",
|
||||
"SkipBooksWithMissingReleaseDate": "Omet els llibres amb la data de publicació que manca",
|
||||
"TheBooksFilesWillBeDeleted": "S'eliminaran els fitxers del llibre.",
|
||||
"UpdatingIsDisabledInsideADockerContainerUpdateTheContainerImageInstead": "L'actualització està desactivada dins d'un contenidor d'acobladors. Actualitza la imatge del contenidor.",
|
||||
"WriteTagsSync": "Tots els fitxers; mantén la sincronització amb Goodreads",
|
||||
"ASIN": "ASIN",
|
||||
"BookAvailableButMissing": "Llibre disponible, però desaparegut",
|
||||
"CalibreOutputFormat": "Format de sortida del Calibre",
|
||||
"CalibreOutputProfile": "Perfil de sortida del Calibre",
|
||||
"BookFilesCountMessage": "No hi ha fitxers de llibre",
|
||||
"BookMonitoring": "Seguiment del llibre",
|
||||
"AllBooks": "Tots els llibres",
|
||||
"AllowFingerprinting": "Permet la impressió digital",
|
||||
"AllowFingerprintingHelpText": "Utilitza l'empremta digital per millorar la precisió de la coincidència de llibres",
|
||||
"ExistingTagsScrubbed": "Etiquetes existents rastrejades",
|
||||
"NETCore": ".NET Core",
|
||||
"WriteMetadataTags": "Escriu les etiquetes de les metadades",
|
||||
"AddNewAuthorRootFolderHelpText": "La subcarpeta '{folder}' es crearà automàticament",
|
||||
"AddRootFolder": "Afegeix una carpeta arrel",
|
||||
"Book": "Llibre",
|
||||
"AddNewBook": "Afegeix nou llibre",
|
||||
"AddNewAuthor": "Afegeix nou autor"
|
||||
"ApiKey": "Clau API"
|
||||
}
|
||||
|
||||
@@ -13,26 +13,26 @@
|
||||
"60MinutesSixty": "60 minut: {0}",
|
||||
"About": "O aplikaci",
|
||||
"AddListExclusion": "Přidat vyloučení seznamu",
|
||||
"AddingTag": "Přidávání štítku",
|
||||
"AddingTag": "Přidání značky",
|
||||
"AgeWhenGrabbed": "Stáří (kdy bylo získáno)",
|
||||
"AlreadyInYourLibrary": "Již máte ve své knihovně",
|
||||
"AlternateTitles": "Alternativní název",
|
||||
"Analytics": "Analýzy",
|
||||
"AnalyticsEnabledHelpText": "Odesílejte anonymní informace o použití a chybách na servery {appName}u. To zahrnuje informace o vašem prohlížeči, které stránky {appName} 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.",
|
||||
"AppDataDirectory": "Adresář AppData",
|
||||
"ApplyTags": "Použít štítky",
|
||||
"ApplyTags": "Použít značky",
|
||||
"Authentication": "Ověřování",
|
||||
"AuthenticationMethodHelpText": "Vyžadovat uživatelské jméno a heslo pro přístup k {appName}u",
|
||||
"AuthenticationMethodHelpText": "Vyžadovat uživatelské jméno a heslo pro přístup k {appName}",
|
||||
"AuthorClickToChangeBook": "Kliknutím změníte film",
|
||||
"AutoRedownloadFailedHelpText": "Automatické vyhledání a pokus o stažení jiného vydání",
|
||||
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Filmy odstraněné z disku jsou automaticky sledovány v {appName}u",
|
||||
"Automatic": "Automatický",
|
||||
"BackupFolderHelpText": "Relativní cesty budou v adresáři AppData společnosti {appName}",
|
||||
"BackupNow": "Zálohovat nyní",
|
||||
"BackupNow": "Ihned zálohovat",
|
||||
"BackupRetentionHelpText": "Automatické zálohy starší než doba uchovávání budou automaticky vyčištěny",
|
||||
"Backups": "Zálohy",
|
||||
"BindAddress": "Vázat adresu",
|
||||
"BindAddressHelpText": "Platná IP adresa, localhost nebo ‚*‘ pro všechna rozhraní",
|
||||
"BindAddressHelpText": "Platná IP adresa, localhost nebo '*' pro všechna rozhraní",
|
||||
"BindAddressHelpTextWarning": "Vyžaduje restart, aby se projevilo",
|
||||
"BookIsDownloading": "Film se stahuje",
|
||||
"BookIsDownloadingInterp": "Film se stahuje - {0}% {1}",
|
||||
@@ -41,8 +41,8 @@
|
||||
"Calendar": "Kalendář",
|
||||
"CalendarWeekColumnHeaderHelpText": "Zobrazuje se nad každým sloupcem, když je aktivní zobrazení týden",
|
||||
"Cancel": "Zrušit",
|
||||
"CancelPendingTask": "Opravdu chcete zrušit tento úkol čekající na vyřízení?",
|
||||
"CertificateValidation": "Ověřování certifikátu",
|
||||
"CancelPendingTask": "Opravdu chcete zrušit tento nevyřízený úkol?",
|
||||
"CertificateValidation": "Ověření certifikátu",
|
||||
"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",
|
||||
@@ -51,7 +51,7 @@
|
||||
"ChmodFolderHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru {appName} vlastníkem souboru. Je lepší zajistit, aby stahovací klient správně nastavil oprávnění.",
|
||||
"ChownGroupHelpText": "Název skupiny nebo gid. Použijte gid pro vzdálené systémy souborů.",
|
||||
"ChownGroupHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru {appName} vlastníkem souboru. Je lepší zajistit, aby stahovací klient používal stejnou skupinu jako {appName}.",
|
||||
"Clear": "Vymazat",
|
||||
"Clear": "Vyčistit",
|
||||
"ClickToChangeQuality": "Kliknutím změníte kvalitu",
|
||||
"ClientPriority": "Priorita klienta",
|
||||
"CloneIndexer": "Klonovat indexátor",
|
||||
@@ -68,7 +68,7 @@
|
||||
"CutoffHelpText": "Jakmile je této kvality dosaženo, {appName} již nebude stahovat filmy",
|
||||
"CutoffUnmet": "Mezní hodnota nesplněna",
|
||||
"DatabaseMigration": "Migrace databáze",
|
||||
"Dates": "Data",
|
||||
"Dates": "Termíny",
|
||||
"DelayProfile": "Profil zpoždění",
|
||||
"DelayProfiles": "Profily zpoždění",
|
||||
"DelayingDownloadUntilInterp": "Zpoždění stahování do {0} o {1}",
|
||||
@@ -429,8 +429,8 @@
|
||||
"UsenetDelay": "Usenet Zpoždění",
|
||||
"UsenetDelayHelpText": "Zpoždění v minutách čekání před uvolněním z Usenetu",
|
||||
"Username": "Uživatelské jméno",
|
||||
"BranchUpdate": "Větev použitá k aktualizaci {appName}u",
|
||||
"BranchUpdateMechanism": "Větev použitá externím aktualizačním mechanismem",
|
||||
"BranchUpdate": "Pobočka, která se má použít k aktualizaci {appName}",
|
||||
"BranchUpdateMechanism": "Pobočka používaná mechanismem externí aktualizace",
|
||||
"Version": "Verze",
|
||||
"WeekColumnHeader": "Záhlaví sloupce týdne",
|
||||
"Year": "Rok",
|
||||
@@ -593,12 +593,12 @@
|
||||
"NoChange": "Žádná změna",
|
||||
"RemovingTag": "Odebírání značky",
|
||||
"SetTags": "Nastavit značky",
|
||||
"ApplyTagsHelpTextAdd": "Přidat: Přidat štítky do existujícího seznamu štítků",
|
||||
"ApplyTagsHelpTextAdd": "Přidat: Přidá značky k již existujícímu seznamu",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Jak použít značky na vybrané klienty pro stahování",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Jak použít značky na vybrané seznamy k importu",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Jak použít štítky na vybrané indexery",
|
||||
"ApplyTagsHelpTextRemove": "Odebrat: Odebrat zadané štítky",
|
||||
"ApplyTagsHelpTextReplace": "Nahradit: Nahradit štítky zadanými štítky (prázdné pole vymaže všechny štítky)",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Jak použít značky na vybrané indexery",
|
||||
"ApplyTagsHelpTextRemove": "Odebrat: Odebrat zadané značky",
|
||||
"ApplyTagsHelpTextReplace": "Nahradit: Nahradit značky zadanými značkami (prázdné pole vymaže všechny značky)",
|
||||
"DeleteSelectedDownloadClients": "Odstranit klienta pro stahování",
|
||||
"DeleteSelectedIndexersMessageText": "Opravdu chcete smazat {count} vybraný(ch) indexer(ů)?",
|
||||
"Yes": "Ano",
|
||||
@@ -618,9 +618,9 @@
|
||||
"FreeSpace": "Volný prostor",
|
||||
"System": "Systém",
|
||||
"TotalSpace": "Celkový prostor",
|
||||
"ConnectionLost": "Ztráta spojení",
|
||||
"ConnectionLost": "Spojení ztraceno",
|
||||
"ConnectionLostReconnect": "{appName} se pokusí připojit automaticky, nebo můžete kliknout na tlačítko znovunačtení níže.",
|
||||
"ConnectionLostToBackend": "{appName} ztratil spojení s backendem a pro obnovení funkčnosti bude potřeba ho znovu načíst.",
|
||||
"ConnectionLostToBackend": "{appName} ztratil spojení s backendem a pro obnovení funkčnosti bude třebaho znovu načíst.",
|
||||
"Large": "Velký",
|
||||
"LastDuration": "lastDuration",
|
||||
"Ui": "UI",
|
||||
@@ -630,7 +630,7 @@
|
||||
"NextExecution": "Další spuštění",
|
||||
"ClickToChangeReleaseGroup": "Kliknutím změníte skupinu vydání",
|
||||
"ApplicationURL": "URL aplikace",
|
||||
"ApplicationUrlHelpText": "Externí adresa URL této aplikace včetně http(s)://, portu a základu URL",
|
||||
"ApplicationUrlHelpText": "Externí adresa URL této aplikace včetně http(s)://, portu a základní adresy URL",
|
||||
"Continuing": "Pokračující",
|
||||
"AutomaticUpdatesDisabledDocker": "Automatické aktualizace nejsou při použití aktualizačního mechanismu Docker přímo podporovány. Obraz kontejneru je nutné aktualizovat mimo {appName} nebo použít skript",
|
||||
"AppUpdated": "{appName} aktualizován",
|
||||
@@ -688,7 +688,7 @@
|
||||
"AutoRedownloadFailedFromInteractiveSearch": "Opětovné stažení z interaktivního vyhledávání selhalo",
|
||||
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Automaticky vyhledat a pokusit se o stažení jiného vydání, pokud bylo neúspěšné vydání zachyceno z interaktivního vyhledávání",
|
||||
"SelectDropdown": "'Vybrat...",
|
||||
"CustomFilter": "Vlastní filtr",
|
||||
"CustomFilter": "Vlastní filtry",
|
||||
"SelectQuality": "Vyberte kvalitu",
|
||||
"IndexerFlags": "Příznaky indexeru",
|
||||
"InteractiveSearchModalHeader": "Interaktivní vyhledávání",
|
||||
@@ -702,13 +702,13 @@
|
||||
"ConnectionSettingsUrlBaseHelpText": "Přidá předponu do {connectionName} url, jako např. {url}",
|
||||
"AuthBasic": "Základní (vyskakovací okno prohlížeče)",
|
||||
"AuthenticationMethod": "Metoda ověřování",
|
||||
"AuthenticationMethodHelpTextWarning": "Vyberte platnou metodu ověřování",
|
||||
"AuthenticationRequired": "Vyžadováno ověření",
|
||||
"AuthenticationRequiredHelpText": "Změnit, pro které požadavky je vyžadováno ověření. Neměňte, pokud nerozumíte rizikům.",
|
||||
"AuthenticationMethodHelpTextWarning": "Prosím vyberte platnou metodu ověřování",
|
||||
"AuthenticationRequired": "Vyžadované ověření",
|
||||
"AuthenticationRequiredHelpText": "Změnit, pro které požadavky je vyžadováno ověření. Pokud nerozumíte rizikům, neměňte je.",
|
||||
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Potvrďte nové heslo",
|
||||
"AuthenticationRequiredPasswordHelpTextWarning": "Zadejte nové heslo",
|
||||
"AuthenticationRequiredUsernameHelpTextWarning": "Zadejte nové uživatelské jméno",
|
||||
"AuthenticationRequiredWarning": "Aby se zabránilo vzdálenému přístupu bez ověření, vyžaduje nyní {appName}, aby bylo povoleno ověřování. Volitelně můžete zakázat ověřování z místních adres.",
|
||||
"AuthenticationRequiredPasswordHelpTextWarning": "Vložte nové heslo",
|
||||
"AuthenticationRequiredUsernameHelpTextWarning": "Vložte nové uživatelské jméno",
|
||||
"AuthenticationRequiredWarning": "Aby se zabránilo vzdálenému přístupu bez ověření, vyžaduje nyní {appName} povolení ověření. Ověřování z místních adres můžete volitelně zakázat.",
|
||||
"BlocklistOnlyHint": "Blokovat a nehledat náhradu",
|
||||
"Enabled": "Povoleno",
|
||||
"ApiKey": "Klíč API",
|
||||
@@ -724,41 +724,5 @@
|
||||
"InstallLatest": "Nainstalujte nejnovější",
|
||||
"CurrentlyInstalled": "Aktuálně nainstalováno",
|
||||
"UnmappedFiles": "Nezmapované složky",
|
||||
"AptUpdater": "K instalaci aktualizace používat apt",
|
||||
"Author": "Autor",
|
||||
"Book": "Kniha",
|
||||
"AllowFingerprinting": "Povol Digitální Otisk (Fingerprinting)",
|
||||
"AllowedLanguages": "Povolené Jazyky",
|
||||
"ASIN": "ASIN",
|
||||
"AllAuthorBooks": "Všechny Knihy Autora",
|
||||
"AllBooks": "Všechny Knihy",
|
||||
"AllExpandedCollapseAll": "Sbalit Všechny",
|
||||
"AllExpandedExpandAll": "Rozbal Všechny",
|
||||
"AnyEditionOkHelpText": "Readarr automaticky přepne edici, která nejlépe odpovídá staženým souborům",
|
||||
"AllowFingerprintingHelpText": "Použít digitální otisk (fingerprinting) ke zlepšení přesnosti párování knih",
|
||||
"AllowFingerprintingHelpTextWarning": "To vyžaduje, aby Readarr četl části souboru, což zpomaluje skenování a může způsobit vysokou aktivitu disku nebo sítě.",
|
||||
"AddImportListExclusionHelpText": "Zabránit přidání knihy do Readarr pomocí Importovaných Seznamů nebo Obnovení Autora",
|
||||
"AllowAuthorChangeClickToChangeAuthor": "Klikni pro změnu autora",
|
||||
"BlocklistOnly": "Pouze seznam blokování",
|
||||
"DoNotBlocklistHint": "Odstraň bez přidání do seznamu blokování",
|
||||
"External": "Externí",
|
||||
"Implementation": "Implementace",
|
||||
"DoNotBlocklist": "Nepřidávat do Seznamu blokování",
|
||||
"DownloadClientDelugeSettingsDirectory": "Adresář stahování",
|
||||
"BlocklistAndSearchHint": "Začne hledat náhradu po blokaci",
|
||||
"BlocklistAndSearchMultipleHint": "Začne vyhledávat náhrady po blokaci",
|
||||
"ChangeCategoryHint": "Změní stahování do kategorie „Post-Import“ z aplikace Download Client",
|
||||
"DeleteSelected": "Smazat vybrané",
|
||||
"ClickToChangeIndexerFlags": "Kliknutím změníte značky indexeru",
|
||||
"DownloadClientQbittorrentSettingsContentLayoutHelpText": "Zda použít rozvržení obsahu nakonfigurované v qBittorrentu, původní rozvržení z torrentu nebo vždy vytvořit podsložku (qBittorrent 4.3.2+)",
|
||||
"DownloadClientQbittorrentSettingsContentLayout": "Rozvržení obsahu",
|
||||
"CustomFormatsSpecificationRegularExpression": "Běžný výraz",
|
||||
"ChangeCategoryMultipleHint": "Změní stahování do kategorie „Post-Import“ z aplikace Download Client",
|
||||
"FailedToFetchSettings": "Nepodařilo se načíst nastavení",
|
||||
"NoCutoffUnmetItems": "Žádné neodpovídající nesplněné položky",
|
||||
"DownloadClientDelugeSettingsDirectoryCompleted": "Adresář kam přesunout po dokončení",
|
||||
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Nepovinné - umístění kam přesunout dokončená stahování, pokud ponecháte prázné, použije se výchozí umístění Deluge",
|
||||
"DownloadClientDelugeSettingsDirectoryHelpText": "Nepovinné - umístění stahovaných souborů, pokud ponecháte prázné, použije se výchozí umístění Deluge",
|
||||
"AddNewAuthorRootFolderHelpText": "Podsložka '{folder}' bude vytvořena automaticky",
|
||||
"AddRootFolder": "Přidat kořenový adresář"
|
||||
"AptUpdater": "K instalaci aktualizace použijte apt"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user