mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-06 13:31:28 -05:00
Compare commits
70 Commits
zeus-oidc
...
v4.4.1.692
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df681d82be | ||
|
|
daf81c5b26 | ||
|
|
78f929c60b | ||
|
|
87d59d12a4 | ||
|
|
ce031124c7 | ||
|
|
d4ce08a044 | ||
|
|
871e78b314 | ||
|
|
eeee682f6c | ||
|
|
9c594c3e53 | ||
|
|
0b1b19a165 | ||
|
|
f1ff7b3b61 | ||
|
|
165c588557 | ||
|
|
327e18bc7a | ||
|
|
f61f2c89dc | ||
|
|
2327b72558 | ||
|
|
66ddd08684 | ||
|
|
4d2143e9b2 | ||
|
|
7906ea2a0c | ||
|
|
9d1956794e | ||
|
|
4956ff7914 | ||
|
|
f22a589cb8 | ||
|
|
04185d6839 | ||
|
|
fb25e5d577 | ||
|
|
6845eaa9b2 | ||
|
|
c1e65874bc | ||
|
|
226a5da0c9 | ||
|
|
685a24e476 | ||
|
|
cae4faae61 | ||
|
|
5dac6badf2 | ||
|
|
5948f56482 | ||
|
|
98ddd0386b | ||
|
|
2947b244e4 | ||
|
|
72552b8084 | ||
|
|
09642444d7 | ||
|
|
d1080b825c | ||
|
|
001421de10 | ||
|
|
bab9b8b36a | ||
|
|
0fb738aa2e | ||
|
|
4963920a46 | ||
|
|
f0d10fe1cd | ||
|
|
386b33b624 | ||
|
|
98201508f2 | ||
|
|
9723c569a1 | ||
|
|
0584d7676c | ||
|
|
09c42530ec | ||
|
|
0697d694e0 | ||
|
|
e085f6af8a | ||
|
|
7feda1c446 | ||
|
|
e1f83c205d | ||
|
|
db00edd266 | ||
|
|
d699f61f5d | ||
|
|
dc1b478f2c | ||
|
|
0ca665c903 | ||
|
|
111c6a743f | ||
|
|
d3517532a4 | ||
|
|
5790ebc558 | ||
|
|
c11f72c098 | ||
|
|
3617bef54b | ||
|
|
a5fb01f1e6 | ||
|
|
fa6acb7497 | ||
|
|
904259df92 | ||
|
|
65c316bd6d | ||
|
|
3b46a08606 | ||
|
|
6ad49373d4 | ||
|
|
2a1f57c085 | ||
|
|
9d9065fbcd | ||
|
|
694940452c | ||
|
|
f5d6a79998 | ||
|
|
4cc98a10a0 | ||
|
|
1751bd1a58 |
23
.gitignore
vendored
23
.gitignore
vendored
@@ -166,27 +166,8 @@ packages.config.md5sum
|
||||
|
||||
# Common IntelliJ Platform excludes
|
||||
|
||||
# User specific
|
||||
**/.idea/**/workspace.xml
|
||||
**/.idea/**/tasks.xml
|
||||
**/.idea/shelf/*
|
||||
**/.idea/dictionaries
|
||||
**/.idea/.idea.Radarr.Posix
|
||||
**/.idea/.idea.Radarr.Windows
|
||||
|
||||
# Sensitive or high-churn files
|
||||
**/.idea/**/dataSources/
|
||||
**/.idea/**/dataSources.ids
|
||||
**/.idea/**/dataSources.xml
|
||||
**/.idea/**/dataSources.local.xml
|
||||
**/.idea/**/sqlDataSources.xml
|
||||
**/.idea/**/dynamic.xml
|
||||
|
||||
# Rider
|
||||
# Rider auto-generates .iml files, and contentModel.xml
|
||||
**/.idea/**/*.iml
|
||||
**/.idea/**/contentModel.xml
|
||||
**/.idea/**/modules.xml
|
||||
# Ignore Rider projects completely for now
|
||||
.idea/
|
||||
|
||||
# ignore node_modules symlink
|
||||
node_modules
|
||||
|
||||
@@ -76,6 +76,15 @@ Thank you to [<img src="/Logo/jetbrains.svg" alt="JetBrains" width="32"> JetBrai
|
||||
* [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
|
||||
* [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
|
||||
|
||||
## DigitalOcean
|
||||
|
||||
This project is also supported by DigitalOcean
|
||||
<p>
|
||||
<a href="https://www.digitalocean.com/">
|
||||
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
### License
|
||||
|
||||
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
||||
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '4.3.1'
|
||||
majorVersion: '4.4.1'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
|
||||
@@ -75,13 +75,23 @@ class Queue extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextState = {};
|
||||
|
||||
if (prevProps.items !== items) {
|
||||
nextState.items = items;
|
||||
}
|
||||
|
||||
const selectedIds = this.getSelectedIds();
|
||||
const isPendingSelected = _.some(this.props.items, (item) => {
|
||||
return selectedIds.indexOf(item.id) > -1 && item.status === 'delay';
|
||||
});
|
||||
|
||||
if (isPendingSelected !== this.state.isPendingSelected) {
|
||||
this.setState({ isPendingSelected });
|
||||
nextState.isPendingSelected = isPendingSelected;
|
||||
}
|
||||
|
||||
if (!_.isEmpty(nextState)) {
|
||||
this.setState(nextState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,26 +224,29 @@ class Queue extends Component {
|
||||
|
||||
<PageContentBody>
|
||||
{
|
||||
isRefreshing && !isAllPopulated &&
|
||||
<LoadingIndicator />
|
||||
isRefreshing && !isAllPopulated ?
|
||||
<LoadingIndicator /> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!isRefreshing && hasError &&
|
||||
!isRefreshing && hasError ?
|
||||
<div>
|
||||
{translate('FailedToLoadQueue')}
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
isAllPopulated && !hasError && !items.length &&
|
||||
isAllPopulated && !hasError && !items.length ?
|
||||
<div>
|
||||
{translate('QueueIsEmpty')}
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
isAllPopulated && !hasError && !!items.length &&
|
||||
isAllPopulated && !hasError && !!items.length ?
|
||||
<div>
|
||||
<Table
|
||||
columns={columns}
|
||||
@@ -268,7 +281,8 @@ class Queue extends Component {
|
||||
isFetching={isRefreshing}
|
||||
{...otherProps}
|
||||
/>
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
</PageContentBody>
|
||||
|
||||
|
||||
@@ -225,13 +225,19 @@ class ImportMovieFooter extends Component {
|
||||
body={
|
||||
<ul>
|
||||
{
|
||||
importError.responseJSON.map((error, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
{error.errorMessage}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
Array.isArray(importError.responseJSON) ?
|
||||
importError.responseJSON.map((error, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
{error.errorMessage}
|
||||
</li>
|
||||
);
|
||||
}) :
|
||||
<li>
|
||||
{
|
||||
JSON.stringify(importError.responseJSON)
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@@ -152,13 +152,19 @@ class ImportMovieSelectFolder extends Component {
|
||||
|
||||
<ul>
|
||||
{
|
||||
saveError.responseJSON.map((e, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
{e.errorMessage}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
Array.isArray(saveError.responseJSON) ?
|
||||
saveError.responseJSON.map((e, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
{e.errorMessage}
|
||||
</li>
|
||||
);
|
||||
}) :
|
||||
<li>
|
||||
{
|
||||
JSON.stringify(saveError.responseJSON)
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</Alert> :
|
||||
|
||||
@@ -113,10 +113,12 @@ class EnhancedSelectInput extends Component {
|
||||
this._scheduleUpdate();
|
||||
}
|
||||
|
||||
if (!Array.isArray(this.props.value) && prevProps.value !== this.props.value) {
|
||||
this.setState({
|
||||
selectedIndex: getSelectedIndex(this.props)
|
||||
});
|
||||
if (!Array.isArray(this.props.value)) {
|
||||
if (prevProps.value !== this.props.value || prevProps.values !== this.props.values) {
|
||||
this.setState({
|
||||
selectedIndex: getSelectedIndex(this.props)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,6 +334,11 @@ class EnhancedSelectInput extends Component {
|
||||
|
||||
const isMultiSelect = Array.isArray(value);
|
||||
const selectedOption = getSelectedOption(selectedIndex, values);
|
||||
let selectedValue = value;
|
||||
|
||||
if (!values.length) {
|
||||
selectedValue = isMultiSelect ? [] : '';
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -372,15 +379,17 @@ class EnhancedSelectInput extends Component {
|
||||
onPress={this.onPress}
|
||||
>
|
||||
{
|
||||
isFetching &&
|
||||
isFetching ?
|
||||
<LoadingIndicator
|
||||
className={styles.loading}
|
||||
size={20}
|
||||
/>
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching &&
|
||||
isFetching ?
|
||||
null :
|
||||
<Icon
|
||||
name={icons.CARET_DOWN}
|
||||
/>
|
||||
@@ -400,7 +409,7 @@ class EnhancedSelectInput extends Component {
|
||||
onPress={this.onPress}
|
||||
>
|
||||
<SelectedValueComponent
|
||||
value={value}
|
||||
value={selectedValue}
|
||||
values={values}
|
||||
{...selectedValueOptions}
|
||||
{...selectedOption}
|
||||
@@ -418,15 +427,17 @@ class EnhancedSelectInput extends Component {
|
||||
>
|
||||
|
||||
{
|
||||
isFetching &&
|
||||
isFetching ?
|
||||
<LoadingIndicator
|
||||
className={styles.loading}
|
||||
size={20}
|
||||
/>
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching &&
|
||||
isFetching ?
|
||||
null :
|
||||
<Icon
|
||||
name={icons.CARET_DOWN}
|
||||
/>
|
||||
@@ -506,7 +517,7 @@ class EnhancedSelectInput extends Component {
|
||||
</Manager>
|
||||
|
||||
{
|
||||
isMobile &&
|
||||
isMobile ?
|
||||
<Modal
|
||||
className={styles.optionsModal}
|
||||
size={sizes.EXTRA_SMALL}
|
||||
@@ -557,7 +568,8 @@ class EnhancedSelectInput extends Component {
|
||||
}
|
||||
</Scroller>
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
</Modal> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ function HintedSelectInputSelectedValue(props) {
|
||||
>
|
||||
<div className={styles.valueText}>
|
||||
{
|
||||
isMultiSelect &&
|
||||
isMultiSelect ?
|
||||
value.map((key, index) => {
|
||||
const v = valuesMap[key];
|
||||
return (
|
||||
@@ -32,26 +32,28 @@ function HintedSelectInputSelectedValue(props) {
|
||||
{v ? v.value : key}
|
||||
</Label>
|
||||
);
|
||||
})
|
||||
}) :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!isMultiSelect && value
|
||||
isMultiSelect ? null : value
|
||||
}
|
||||
</div>
|
||||
|
||||
{
|
||||
hint != null && includeHint &&
|
||||
hint != null && includeHint ?
|
||||
<div className={styles.hintText}>
|
||||
{hint}
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
</EnhancedSelectInputSelectedValue>
|
||||
);
|
||||
}
|
||||
|
||||
HintedSelectInputSelectedValue.propTypes = {
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired,
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
hint: PropTypes.string,
|
||||
isMultiSelect: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -68,7 +68,7 @@ RootFolderSelectInputOption.propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
freeSpace: PropTypes.number,
|
||||
movieFolder: PropTypes.string,
|
||||
isMissing: PropTypes.boolean,
|
||||
isMissing: PropTypes.bool,
|
||||
isMobile: PropTypes.bool.isRequired,
|
||||
isWindows: PropTypes.bool
|
||||
};
|
||||
|
||||
@@ -60,7 +60,7 @@ class FilterMenu extends Component {
|
||||
iconName={icons.FILTER}
|
||||
text={translate('Filter')}
|
||||
isDisabled={isDisabled}
|
||||
indicator={selectedFilterKey !== 'all'}
|
||||
showIndicator={selectedFilterKey !== 'all'}
|
||||
/>
|
||||
|
||||
<FilterMenuContent
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
.menuButton {
|
||||
composes: menuButton from '~./MenuButton.css';
|
||||
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.indicatorContainer {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import MenuButton from 'Components/Menu/MenuButton';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import styles from './PageMenuButton.css';
|
||||
|
||||
function PageMenuButton(props) {
|
||||
const {
|
||||
iconName,
|
||||
indicator,
|
||||
showIndicator,
|
||||
text,
|
||||
...otherProps
|
||||
} = props;
|
||||
@@ -22,6 +24,22 @@ function PageMenuButton(props) {
|
||||
size={18}
|
||||
/>
|
||||
|
||||
{
|
||||
showIndicator ?
|
||||
<span
|
||||
className={classNames(
|
||||
styles.indicatorContainer,
|
||||
'fa-layers fa-fw'
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
name={icons.CIRCLE}
|
||||
size={9}
|
||||
/>
|
||||
</span> :
|
||||
null
|
||||
}
|
||||
|
||||
<div className={styles.label}>
|
||||
{text}
|
||||
</div>
|
||||
@@ -32,11 +50,11 @@ function PageMenuButton(props) {
|
||||
PageMenuButton.propTypes = {
|
||||
iconName: PropTypes.object.isRequired,
|
||||
text: PropTypes.string,
|
||||
indicator: PropTypes.bool.isRequired
|
||||
showIndicator: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
PageMenuButton.defaultProps = {
|
||||
indicator: false
|
||||
showIndicator: false
|
||||
};
|
||||
|
||||
export default PageMenuButton;
|
||||
|
||||
@@ -9,7 +9,7 @@ import styles from './ToolbarMenuButton.css';
|
||||
function ToolbarMenuButton(props) {
|
||||
const {
|
||||
iconName,
|
||||
indicator,
|
||||
showIndicator,
|
||||
text,
|
||||
...otherProps
|
||||
} = props;
|
||||
@@ -26,7 +26,7 @@ function ToolbarMenuButton(props) {
|
||||
/>
|
||||
|
||||
{
|
||||
indicator &&
|
||||
showIndicator &&
|
||||
<span
|
||||
className={classNames(
|
||||
styles.indicatorContainer,
|
||||
@@ -53,11 +53,11 @@ function ToolbarMenuButton(props) {
|
||||
ToolbarMenuButton.propTypes = {
|
||||
iconName: PropTypes.object.isRequired,
|
||||
text: PropTypes.string,
|
||||
indicator: PropTypes.bool.isRequired
|
||||
showIndicator: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
ToolbarMenuButton.defaultProps = {
|
||||
indicator: false
|
||||
showIndicator: false
|
||||
};
|
||||
|
||||
export default ToolbarMenuButton;
|
||||
|
||||
@@ -20,7 +20,11 @@
|
||||
|
||||
.frontTextContainer {
|
||||
z-index: 1;
|
||||
color: var(--white);
|
||||
color: var(--progressBarFrontTextColor);
|
||||
}
|
||||
|
||||
.backTextContainer {
|
||||
color: var(--progressBarBackTextColor);
|
||||
}
|
||||
|
||||
.backTextContainer,
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.cloneButton {
|
||||
composes: button from '~Components/Link/IconButton.css';
|
||||
|
||||
@@ -36,3 +40,10 @@
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
@add-mixin truncate;
|
||||
composes: label from '~Components/Label.css';
|
||||
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ class CustomFormat extends Component {
|
||||
{name}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className={styles.buttons}>
|
||||
<IconButton
|
||||
className={styles.cloneButton}
|
||||
title={translate('CloneCustomFormat')}
|
||||
@@ -124,6 +124,7 @@ class CustomFormat extends Component {
|
||||
|
||||
return (
|
||||
<Label
|
||||
className={styles.label}
|
||||
key={index}
|
||||
kind={kind}
|
||||
>
|
||||
|
||||
@@ -89,6 +89,7 @@ function EditIndexerModalContent(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="enableRss"
|
||||
helpText={supportsRss.value ? translate('RSSHelpText') : undefined}
|
||||
helpTextWarning={supportsRss.value ? undefined : translate('RSSIsNotSupportedWithThisIndexer')}
|
||||
isDisabled={!supportsRss.value}
|
||||
{...enableRss}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
code {
|
||||
padding: 0 1px;
|
||||
border: 1px solid var(--borderColor);
|
||||
background-color: #f7f7f7;
|
||||
background-color: var(--modalCloseButtonHoverColor);
|
||||
color: var(--movieBackgroundColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
|
||||
&:hover {
|
||||
.token {
|
||||
background-color: #ddd;
|
||||
background-color: var(--popoverTitleBackgroundInverseColor);
|
||||
}
|
||||
|
||||
.example {
|
||||
background-color: #ccc;
|
||||
background-color: var(--popoverTitleBorderInverseColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
.token {
|
||||
flex: 0 0 50%;
|
||||
padding: 6px 16px;
|
||||
background-color: #eee;
|
||||
background-color: var(--popoverTitleBorderColor);
|
||||
font-family: $monoSpaceFontFamily;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
justify-content: space-between;
|
||||
flex: 0 0 50%;
|
||||
padding: 6px 16px;
|
||||
background-color: #ddd;
|
||||
background-color: var(--popoverTitleBackgroundColor);
|
||||
|
||||
.footNote {
|
||||
padding: 2px;
|
||||
|
||||
@@ -201,6 +201,11 @@ export const defaultState = {
|
||||
return genreList.sort(sortByName);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'customFormatScore',
|
||||
label: translate('CustomFormatScore'),
|
||||
type: filterBuilderTypes.NUMBER
|
||||
},
|
||||
{
|
||||
name: 'rejectionCount',
|
||||
label: translate('RejectionCount'),
|
||||
|
||||
@@ -226,6 +226,8 @@ module.exports = {
|
||||
//
|
||||
// Misc
|
||||
|
||||
progressBarFrontTextColor: white,
|
||||
progressBarBackTextColor: white,
|
||||
progressBarBackgroundColor: '#727070',
|
||||
logEventsBackgroundColor: '#2a2a2a'
|
||||
};
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import * as dark from './dark';
|
||||
import * as light from './light';
|
||||
|
||||
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const auto = defaultDark ? { ...dark } : { ...light };
|
||||
|
||||
export default {
|
||||
auto,
|
||||
light,
|
||||
dark
|
||||
};
|
||||
|
||||
@@ -198,8 +198,8 @@ module.exports = {
|
||||
popoverShadowColor: 'rgba(0, 0, 0, 0.2)',
|
||||
popoverArrowBorderColor: '#fff',
|
||||
|
||||
popoverTitleBackgroundInverseColor: '#595959',
|
||||
popoverTitleBorderInverseColor: '#707070',
|
||||
popoverTitleBackgroundInverseColor: '#9b9b9b',
|
||||
popoverTitleBorderInverseColor: '#bfbfbf',
|
||||
popoverShadowInverseColor: 'rgba(0, 0, 0, 0.2)',
|
||||
popoverArrowBorderInverseColor: 'rgba(58, 63, 81, 0.75)',
|
||||
|
||||
@@ -227,6 +227,8 @@ module.exports = {
|
||||
//
|
||||
// Misc
|
||||
|
||||
progressBarBackgroundColor: '#fff',
|
||||
logEventsBackgroundColor: '#fff'
|
||||
progressBarFrontTextColor: white,
|
||||
progressBarBackTextColor: darkGray,
|
||||
progressBarBackgroundColor: white,
|
||||
logEventsBackgroundColor: white
|
||||
};
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
"file-loader": "6.2.0",
|
||||
"filemanager-webpack-plugin": "5.0.0",
|
||||
"html-webpack-plugin": "5.3.1",
|
||||
"loader-utils": "^2.0.0",
|
||||
"loader-utils": "^3.2.1",
|
||||
"mini-css-extract-plugin": "1.5.0",
|
||||
"postcss": "8.2.12",
|
||||
"postcss-color-function": "4.1.0",
|
||||
@@ -134,5 +134,9 @@
|
||||
"webpack-cli": "4.9.1",
|
||||
"webpack-livereload-plugin": "3.0.2",
|
||||
"worker-loader": "3.0.8"
|
||||
},
|
||||
"volta": {
|
||||
"node": "16.17.0",
|
||||
"yarn": "1.22.19"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Globalization;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Test.ExtensionTests.StringExtensionTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class IsValidIPAddressFixture
|
||||
{
|
||||
[TestCase("192.168.0.1")]
|
||||
[TestCase("::1")]
|
||||
[TestCase("2001:db8:4006:812::200e")]
|
||||
public void should_validate_ip_address(string input)
|
||||
{
|
||||
input.IsValidIpAddress().Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("sonarr.tv")]
|
||||
public void should_not_parse_non_ip_address(string input)
|
||||
{
|
||||
input.IsValidIpAddress().Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using FluentAssertions;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Test.Common;
|
||||
@@ -10,6 +10,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[TestCase("abc://my_host.com:8080/root/api/")]
|
||||
[TestCase("abc://my_host.com:8080//root/api/")]
|
||||
[TestCase("abc://my_host.com:8080/root//api/")]
|
||||
[TestCase("abc://[::1]:8080/root//api/")]
|
||||
public void should_parse(string uri)
|
||||
{
|
||||
var newUri = new HttpUri(uri);
|
||||
|
||||
@@ -7,34 +7,50 @@ namespace NzbDrone.Common.Extensions
|
||||
{
|
||||
public static bool IsLocalAddress(this IPAddress ipAddress)
|
||||
{
|
||||
if (ipAddress.IsIPv6LinkLocal)
|
||||
// Map back to IPv4 if mapped to IPv6, for example "::ffff:1.2.3.4" to "1.2.3.4".
|
||||
if (ipAddress.IsIPv4MappedToIPv6)
|
||||
{
|
||||
return true;
|
||||
ipAddress = ipAddress.MapToIPv4();
|
||||
}
|
||||
|
||||
// Checks loopback ranges for both IPv4 and IPv6.
|
||||
if (IPAddress.IsLoopback(ipAddress))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// IPv4
|
||||
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
byte[] bytes = ipAddress.GetAddressBytes();
|
||||
switch (bytes[0])
|
||||
{
|
||||
case 10:
|
||||
case 127:
|
||||
return true;
|
||||
case 172:
|
||||
return bytes[1] < 32 && bytes[1] >= 16;
|
||||
case 192:
|
||||
return bytes[1] == 168;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return IsLocalIPv4(ipAddress.GetAddressBytes());
|
||||
}
|
||||
|
||||
// IPv6
|
||||
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
return ipAddress.IsIPv6LinkLocal ||
|
||||
ipAddress.IsIPv6UniqueLocal ||
|
||||
ipAddress.IsIPv6SiteLocal;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsLocalIPv4(byte[] ipv4Bytes)
|
||||
{
|
||||
// Link local (no IP assigned by DHCP): 169.254.0.0 to 169.254.255.255 (169.254.0.0/16)
|
||||
bool IsLinkLocal() => ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;
|
||||
|
||||
// Class A private range: 10.0.0.0 – 10.255.255.255 (10.0.0.0/8)
|
||||
bool IsClassA() => ipv4Bytes[0] == 10;
|
||||
|
||||
// Class B private range: 172.16.0.0 – 172.31.255.255 (172.16.0.0/12)
|
||||
bool IsClassB() => ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;
|
||||
|
||||
// Class C private range: 192.168.0.0 – 192.168.255.255 (192.168.0.0/16)
|
||||
bool IsClassC() => ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;
|
||||
|
||||
return IsLinkLocal() || IsClassA() || IsClassC() || IsClassB();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
@@ -192,5 +194,30 @@ namespace NzbDrone.Common.Extensions
|
||||
.Replace("'", "%27")
|
||||
.Replace("%7E", "~");
|
||||
}
|
||||
|
||||
public static bool IsValidIpAddress(this string value)
|
||||
{
|
||||
if (!IPAddress.TryParse(value, out var parsedAddress))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsedAddress.Equals(IPAddress.Parse("255.255.255.255")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsedAddress.IsIPv6Multicast)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return parsedAddress.AddressFamily == AddressFamily.InterNetwork || parsedAddress.AddressFamily == AddressFamily.InterNetworkV6;
|
||||
}
|
||||
|
||||
public static string ToUrlHost(this string input)
|
||||
{
|
||||
return input.Contains(":") ? $"[{input}]" : input;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Common.Http
|
||||
{
|
||||
public class HttpUri : IEquatable<HttpUri>
|
||||
{
|
||||
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+)(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+|\[[[A-F0-9:]+\])(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private readonly string _uri;
|
||||
public string FullUri => _uri;
|
||||
@@ -70,6 +70,8 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
private void Parse()
|
||||
{
|
||||
var parseSuccess = Uri.TryCreate(_uri, UriKind.RelativeOrAbsolute, out var uri);
|
||||
|
||||
var match = RegexUri.Match(_uri);
|
||||
|
||||
var scheme = match.Groups["scheme"];
|
||||
@@ -79,7 +81,7 @@ namespace NzbDrone.Common.Http
|
||||
var query = match.Groups["query"];
|
||||
var fragment = match.Groups["fragment"];
|
||||
|
||||
if (!match.Success || (scheme.Success && !host.Success && path.Success))
|
||||
if (!parseSuccess || (scheme.Success && !host.Success && path.Success))
|
||||
{
|
||||
throw new ArgumentException("Uri didn't match expected pattern: " + _uri);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Threading;
|
||||
using NLog;
|
||||
using NLog.Common;
|
||||
using NLog.Targets;
|
||||
using Npgsql;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using Sentry;
|
||||
@@ -34,6 +35,14 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
SQLiteErrorCode.Auth
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> FilteredPostgresErrorCodes = new HashSet<string>
|
||||
{
|
||||
PostgresErrorCodes.OutOfMemory,
|
||||
PostgresErrorCodes.TooManyConnections,
|
||||
PostgresErrorCodes.DiskFull,
|
||||
PostgresErrorCodes.ProgramLimitExceeded
|
||||
};
|
||||
|
||||
// use string and not Type so we don't need a reference to the project
|
||||
// where these are defined
|
||||
private static readonly HashSet<string> FilteredExceptionTypeNames = new HashSet<string>
|
||||
@@ -250,6 +259,19 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
isSentry = false;
|
||||
}
|
||||
|
||||
var pgEx = logEvent.Exception as PostgresException;
|
||||
if (pgEx != null && FilteredPostgresErrorCodes.Contains(pgEx.SqlState))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// We don't care about transient network and timeout errors
|
||||
var npgEx = logEvent.Exception as NpgsqlException;
|
||||
if (npgEx != null && npgEx.IsTransient)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FilteredExceptionTypeNames.Contains(ex.GetType().Name))
|
||||
{
|
||||
isSentry = false;
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
<PackageReference Include="DryIoc.dll" Version="5.3.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="5.0.11" />
|
||||
<PackageReference Include="Sentry" Version="3.23.1" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.CustomFormats;
|
||||
using NzbDrone.Core.DecisionEngine.Specifications;
|
||||
using NzbDrone.Core.Profiles;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class UpgradeAllowedSpecificationFixture : CoreTest<UpgradableSpecification>
|
||||
{
|
||||
private CustomFormat _customFormatOne;
|
||||
private CustomFormat _customFormatTwo;
|
||||
private Profile _qualityProfile;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_customFormatOne = new CustomFormat
|
||||
{
|
||||
Id = 1,
|
||||
Name = "One"
|
||||
};
|
||||
_customFormatTwo = new CustomFormat
|
||||
{
|
||||
Id = 2,
|
||||
Name = "Two"
|
||||
};
|
||||
|
||||
_qualityProfile = new Profile
|
||||
{
|
||||
Cutoff = Quality.Bluray1080p.Id,
|
||||
Items = Qualities.QualityFixture.GetDefaultQualities(),
|
||||
UpgradeAllowed = false,
|
||||
CutoffFormatScore = 100,
|
||||
FormatItems = new List<ProfileFormatItem>
|
||||
{
|
||||
new ProfileFormatItem
|
||||
{
|
||||
Format = _customFormatOne,
|
||||
Score = 50
|
||||
},
|
||||
new ProfileFormatItem
|
||||
{
|
||||
Format = _customFormatTwo,
|
||||
Score = 100
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_false_when_quality_is_better_custom_formats_are_the_same_and_upgrading_is_not_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = false;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat>(),
|
||||
new QualityModel(Quality.Bluray1080p),
|
||||
new List<CustomFormat>())
|
||||
.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_false_when_quality_is_same_and_custom_format_is_upgrade_and_upgrading_is_not_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = false;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatOne },
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatTwo })
|
||||
.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_custom_format_upgrade_when_upgrading_is_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = true;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatOne },
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatTwo })
|
||||
.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_same_custom_format_score_when_upgrading_is_not_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = false;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatOne },
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatOne })
|
||||
.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_lower_custom_format_score_when_upgrading_is_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = true;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatTwo },
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatOne })
|
||||
.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_lower_language_when_upgrading_is_not_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = false;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatTwo },
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat> { _customFormatOne })
|
||||
.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_quality_upgrade_when_upgrading_is_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = true;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat>(),
|
||||
new QualityModel(Quality.Bluray1080p),
|
||||
new List<CustomFormat>())
|
||||
.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_same_quality_when_upgrading_is_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = true;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat>(),
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat>())
|
||||
.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_same_quality_when_upgrading_is_not_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = false;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat>(),
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat>())
|
||||
.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_lower_quality_when_upgrading_is_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = true;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat>(),
|
||||
new QualityModel(Quality.SDTV),
|
||||
new List<CustomFormat>())
|
||||
.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_for_lower_quality_when_upgrading_is_not_allowed()
|
||||
{
|
||||
_qualityProfile.UpgradeAllowed = false;
|
||||
|
||||
Subject.IsUpgradeAllowed(
|
||||
_qualityProfile,
|
||||
new QualityModel(Quality.DVD),
|
||||
new List<CustomFormat>(),
|
||||
new QualityModel(Quality.SDTV),
|
||||
new List<CustomFormat>())
|
||||
.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.Clients;
|
||||
using NzbDrone.Core.Download.Clients.FreeboxDownload;
|
||||
using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Test.Download.DownloadClientTests.FreeboxDownloadTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TorrentFreeboxDownloadFixture : DownloadClientFixtureBase<TorrentFreeboxDownload>
|
||||
{
|
||||
protected FreeboxDownloadSettings _settings;
|
||||
|
||||
protected FreeboxDownloadConfiguration _downloadConfiguration;
|
||||
|
||||
protected FreeboxDownloadTask _task;
|
||||
|
||||
protected string _defaultDestination = @"/some/path";
|
||||
protected string _encodedDefaultDestination = "L3NvbWUvcGF0aA==";
|
||||
protected string _category = "somecat";
|
||||
protected string _encodedDefaultDestinationAndCategory = "L3NvbWUvcGF0aC9zb21lY2F0";
|
||||
protected string _destinationDirectory = @"/path/to/media";
|
||||
protected string _encodedDestinationDirectory = "L3BhdGgvdG8vbWVkaWE=";
|
||||
protected OsPath _physicalPath = new OsPath("/mnt/sdb1/mydata");
|
||||
protected string _downloadURL => "magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcad53426&dn=download";
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Subject.Definition = new DownloadClientDefinition();
|
||||
|
||||
_settings = new FreeboxDownloadSettings()
|
||||
{
|
||||
Host = "127.0.0.1",
|
||||
Port = 443,
|
||||
ApiUrl = "/api/v1/",
|
||||
AppId = "someid",
|
||||
AppToken = "S0mEv3RY1oN9T0k3n"
|
||||
};
|
||||
|
||||
Subject.Definition.Settings = _settings;
|
||||
|
||||
_downloadConfiguration = new FreeboxDownloadConfiguration()
|
||||
{
|
||||
DownloadDirectory = _encodedDefaultDestination
|
||||
};
|
||||
|
||||
_task = new FreeboxDownloadTask()
|
||||
{
|
||||
Id = "id0",
|
||||
Name = "name",
|
||||
DownloadDirectory = "L3NvbWUvcGF0aA==",
|
||||
InfoHash = "HASH",
|
||||
QueuePosition = 1,
|
||||
Status = FreeboxDownloadTaskStatus.Unknown,
|
||||
Eta = 0,
|
||||
Error = "none",
|
||||
Type = FreeboxDownloadTaskType.Bt.ToString(),
|
||||
IoPriority = FreeboxDownloadTaskIoPriority.Normal.ToString(),
|
||||
StopRatio = 150,
|
||||
PieceLength = 125,
|
||||
CreatedTimestamp = 1665261599,
|
||||
Size = 1000,
|
||||
ReceivedPrct = 0,
|
||||
ReceivedBytes = 0,
|
||||
ReceivedRate = 0,
|
||||
TransmittedPrct = 0,
|
||||
TransmittedBytes = 0,
|
||||
TransmittedRate = 0,
|
||||
};
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0]));
|
||||
}
|
||||
|
||||
protected void GivenCategory()
|
||||
{
|
||||
_settings.Category = _category;
|
||||
}
|
||||
|
||||
protected void GivenDestinationDirectory()
|
||||
{
|
||||
_settings.DestinationDirectory = _destinationDirectory;
|
||||
}
|
||||
|
||||
protected virtual void GivenDownloadConfiguration()
|
||||
{
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Setup(s => s.GetDownloadConfiguration(It.IsAny<FreeboxDownloadSettings>()))
|
||||
.Returns(_downloadConfiguration);
|
||||
}
|
||||
|
||||
protected virtual void GivenTasks(List<FreeboxDownloadTask> torrents)
|
||||
{
|
||||
if (torrents == null)
|
||||
{
|
||||
torrents = new List<FreeboxDownloadTask>();
|
||||
}
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Setup(s => s.GetTasks(It.IsAny<FreeboxDownloadSettings>()))
|
||||
.Returns(torrents);
|
||||
}
|
||||
|
||||
protected void PrepareClientToReturnQueuedItem()
|
||||
{
|
||||
_task.Status = FreeboxDownloadTaskStatus.Queued;
|
||||
|
||||
GivenTasks(new List<FreeboxDownloadTask>
|
||||
{
|
||||
_task
|
||||
});
|
||||
}
|
||||
|
||||
protected void GivenSuccessfulDownload()
|
||||
{
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000]));
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Setup(s => s.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()))
|
||||
.Callback(PrepareClientToReturnQueuedItem);
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Setup(s => s.AddTaskFromFile(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()))
|
||||
.Callback(PrepareClientToReturnQueuedItem);
|
||||
}
|
||||
|
||||
protected override RemoteMovie CreateRemoteMovie()
|
||||
{
|
||||
var movie = base.CreateRemoteMovie();
|
||||
|
||||
movie.Release.DownloadUrl = _downloadURL;
|
||||
|
||||
return movie;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_with_DestinationDirectory_should_force_directory()
|
||||
{
|
||||
GivenDestinationDirectory();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteMovie = CreateRemoteMovie();
|
||||
|
||||
Subject.Download(remoteMovie);
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), _encodedDestinationDirectory, It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_with_Category_should_force_directory()
|
||||
{
|
||||
GivenDownloadConfiguration();
|
||||
GivenCategory();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteMovie = CreateRemoteMovie();
|
||||
|
||||
Subject.Download(remoteMovie);
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), _encodedDefaultDestinationAndCategory, It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_without_DestinationDirectory_and_Category_should_use_default()
|
||||
{
|
||||
GivenDownloadConfiguration();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteMovie = CreateRemoteMovie();
|
||||
|
||||
Subject.Download(remoteMovie);
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), _encodedDefaultDestination, It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[TestCase(false, false)]
|
||||
[TestCase(true, true)]
|
||||
public void Download_should_pause_torrent_as_expected(bool addPausedSetting, bool toBePausedFlag)
|
||||
{
|
||||
_settings.AddPaused = addPausedSetting;
|
||||
|
||||
GivenDownloadConfiguration();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteMovie = CreateRemoteMovie();
|
||||
|
||||
Subject.Download(remoteMovie);
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), toBePausedFlag, It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[TestCase(0, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.First, true)]
|
||||
[TestCase(0, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.First, true)]
|
||||
[TestCase(0, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.Last, false)]
|
||||
[TestCase(0, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.Last, false)]
|
||||
[TestCase(22, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.First, true)]
|
||||
[TestCase(22, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.First, false)]
|
||||
[TestCase(22, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.Last, true)]
|
||||
[TestCase(22, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.Last, false)]
|
||||
public void Download_should_queue_torrent_first_as_expected(int ageDay, int olderPriority, int recentPriority, bool toBeQueuedFirstFlag)
|
||||
{
|
||||
_settings.OlderPriority = olderPriority;
|
||||
_settings.RecentPriority = recentPriority;
|
||||
|
||||
GivenDownloadConfiguration();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteMovie = CreateRemoteMovie();
|
||||
|
||||
remoteMovie.Movie.MovieMetadata.Value.PhysicalRelease = DateTime.UtcNow.AddDays(-ageDay);
|
||||
|
||||
Subject.Download(remoteMovie);
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), toBeQueuedFirstFlag, It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[TestCase(0, 0)]
|
||||
[TestCase(1.5, 150)]
|
||||
public void Download_should_define_seed_ratio_as_expected(double? providerSeedRatio, double? expectedSeedRatio)
|
||||
{
|
||||
GivenDownloadConfiguration();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteMovie = CreateRemoteMovie();
|
||||
|
||||
remoteMovie.SeedConfiguration = new TorrentSeedConfiguration();
|
||||
remoteMovie.SeedConfiguration.Ratio = providerSeedRatio;
|
||||
|
||||
Subject.Download(remoteMovie);
|
||||
|
||||
Mocker.GetMock<IFreeboxDownloadProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>(), expectedSeedRatio, It.IsAny<FreeboxDownloadSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_should_return_empty_list_if_no_tasks_available()
|
||||
{
|
||||
GivenTasks(new List<FreeboxDownloadTask>());
|
||||
|
||||
Subject.GetItems().Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_should_return_ignore_tasks_of_unknown_type()
|
||||
{
|
||||
_task.Status = FreeboxDownloadTaskStatus.Done;
|
||||
_task.Type = "toto";
|
||||
|
||||
GivenTasks(new List<FreeboxDownloadTask> { _task });
|
||||
|
||||
Subject.GetItems().Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_when_destinationdirectory_is_set_should_ignore_downloads_in_wrong_folder()
|
||||
{
|
||||
_settings.DestinationDirectory = @"/some/path/that/will/not/match";
|
||||
|
||||
_task.Status = FreeboxDownloadTaskStatus.Done;
|
||||
|
||||
GivenTasks(new List<FreeboxDownloadTask> { _task });
|
||||
|
||||
Subject.GetItems().Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_when_category_is_set_should_ignore_downloads_in_wrong_folder()
|
||||
{
|
||||
_settings.Category = "somecategory";
|
||||
|
||||
_task.Status = FreeboxDownloadTaskStatus.Done;
|
||||
|
||||
GivenTasks(new List<FreeboxDownloadTask> { _task });
|
||||
|
||||
Subject.GetItems().Should().BeEmpty();
|
||||
}
|
||||
|
||||
[TestCase(FreeboxDownloadTaskStatus.Downloading, false, false)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Done, true, true)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Seeding, false, false)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Stopped, false, false)]
|
||||
public void GetItems_should_return_canBeMoved_and_canBeDeleted_as_expected(FreeboxDownloadTaskStatus apiStatus, bool canMoveFilesExpected, bool canBeRemovedExpected)
|
||||
{
|
||||
_task.Status = apiStatus;
|
||||
|
||||
GivenTasks(new List<FreeboxDownloadTask>() { _task });
|
||||
|
||||
var items = Subject.GetItems();
|
||||
|
||||
items.Should().HaveCount(1);
|
||||
items.First().CanBeRemoved.Should().Be(canBeRemovedExpected);
|
||||
items.First().CanMoveFiles.Should().Be(canMoveFilesExpected);
|
||||
}
|
||||
|
||||
[TestCase(FreeboxDownloadTaskStatus.Stopped, DownloadItemStatus.Paused)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Stopping, DownloadItemStatus.Paused)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Queued, DownloadItemStatus.Queued)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Starting, DownloadItemStatus.Downloading)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Downloading, DownloadItemStatus.Downloading)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Retry, DownloadItemStatus.Downloading)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Checking, DownloadItemStatus.Downloading)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Error, DownloadItemStatus.Warning)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Seeding, DownloadItemStatus.Completed)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Done, DownloadItemStatus.Completed)]
|
||||
[TestCase(FreeboxDownloadTaskStatus.Unknown, DownloadItemStatus.Downloading)]
|
||||
public void GetItems_should_return_item_as_downloadItemStatus(FreeboxDownloadTaskStatus apiStatus, DownloadItemStatus expectedItemStatus)
|
||||
{
|
||||
_task.Status = apiStatus;
|
||||
|
||||
GivenTasks(new List<FreeboxDownloadTask>() { _task });
|
||||
|
||||
var items = Subject.GetItems();
|
||||
|
||||
items.Should().HaveCount(1);
|
||||
items.First().Status.Should().Be(expectedItemStatus);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_should_return_decoded_destination_directory()
|
||||
{
|
||||
var decodedDownloadDirectory = "/that/the/path";
|
||||
|
||||
_task.Status = FreeboxDownloadTaskStatus.Done;
|
||||
_task.DownloadDirectory = "L3RoYXQvdGhlL3BhdGg=";
|
||||
|
||||
GivenTasks(new List<FreeboxDownloadTask> { _task });
|
||||
|
||||
var items = Subject.GetItems();
|
||||
|
||||
items.Should().HaveCount(1);
|
||||
items.First().OutputPath.Should().Be(decodedDownloadDirectory);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_should_return_message_if_tasks_in_error()
|
||||
{
|
||||
_task.Status = FreeboxDownloadTaskStatus.Error;
|
||||
_task.Error = "internal";
|
||||
|
||||
GivenTasks(new List<FreeboxDownloadTask> { _task });
|
||||
|
||||
var items = Subject.GetItems();
|
||||
|
||||
items.Should().HaveCount(1);
|
||||
items.First().Message.Should().Be("Internal error.");
|
||||
items.First().Status.Should().Be(DownloadItemStatus.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
_downloadClients = new List<IDownloadClient>();
|
||||
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(v => v.GetDownloadClients())
|
||||
.Setup(v => v.GetDownloadClients(It.IsAny<bool>()))
|
||||
.Returns(_downloadClients);
|
||||
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
|
||||
@@ -89,6 +89,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
.With(h => h.Title = title)
|
||||
.With(h => h.Release = release)
|
||||
.With(h => h.Reason = reason)
|
||||
.With(h => h.ParsedMovieInfo = _parsedMovieInfo)
|
||||
.Build();
|
||||
|
||||
_heldReleases.AddRange(heldReleases);
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
_pending.Add(new PendingRelease
|
||||
{
|
||||
Id = id,
|
||||
Title = "Movie.Title.2020.720p-Radarr",
|
||||
ParsedMovieInfo = new ParsedMovieInfo { MovieTitles = new List<string> { title }, Year = year },
|
||||
MovieId = _movie.Id
|
||||
});
|
||||
|
||||
@@ -93,6 +93,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
.With(h => h.MovieId = _movie.Id)
|
||||
.With(h => h.Title = title)
|
||||
.With(h => h.Release = release)
|
||||
.With(h => h.ParsedMovieInfo = _parsedMovieInfo)
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<IPendingReleaseRepository>()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
public void should_return_warning_when_download_client_has_not_been_configured()
|
||||
{
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(s => s.GetDownloadClients())
|
||||
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
|
||||
.Returns(Array.Empty<IDownloadClient>());
|
||||
|
||||
Subject.Check().ShouldBeWarning();
|
||||
@@ -40,7 +40,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
.Throws<Exception>();
|
||||
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(s => s.GetDownloadClients())
|
||||
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
|
||||
.Returns(new IDownloadClient[] { downloadClient.Object });
|
||||
|
||||
Subject.Check().ShouldBeError();
|
||||
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
.Returns(new List<DownloadClientItem>());
|
||||
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(s => s.GetDownloadClients())
|
||||
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
|
||||
.Returns(new IDownloadClient[] { downloadClient.Object });
|
||||
|
||||
Subject.Check().ShouldBeOk();
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
.Returns(_clientStatus);
|
||||
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(s => s.GetDownloadClients())
|
||||
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
|
||||
.Returns(new IDownloadClient[] { _downloadClient.Object });
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.Clients;
|
||||
using NzbDrone.Core.HealthCheck.Checks;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
{
|
||||
[TestFixture]
|
||||
public class DownloadClientFolderCheckFixture : CoreTest<DownloadClientSortingCheck>
|
||||
{
|
||||
private DownloadClientInfo _clientStatus;
|
||||
private Mock<IDownloadClient> _downloadClient;
|
||||
|
||||
private static Exception[] DownloadClientExceptions =
|
||||
{
|
||||
new DownloadClientUnavailableException("error"),
|
||||
new DownloadClientAuthenticationException("error"),
|
||||
new DownloadClientException("error")
|
||||
};
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Mocker.GetMock<ILocalizationService>()
|
||||
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
|
||||
.Returns("Some Warning Message");
|
||||
|
||||
_clientStatus = new DownloadClientInfo
|
||||
{
|
||||
IsLocalhost = true,
|
||||
SortingMode = null
|
||||
};
|
||||
|
||||
_downloadClient = Mocker.GetMock<IDownloadClient>();
|
||||
_downloadClient.Setup(s => s.Definition)
|
||||
.Returns(new DownloadClientDefinition { Name = "Test" });
|
||||
|
||||
_downloadClient.Setup(s => s.GetStatus())
|
||||
.Returns(_clientStatus);
|
||||
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
|
||||
.Returns(new IDownloadClient[] { _downloadClient.Object });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_ok_if_sorting_is_not_enabled()
|
||||
{
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_warning_if_sorting_is_enabled()
|
||||
{
|
||||
_clientStatus.SortingMode = "TV";
|
||||
|
||||
Subject.Check().ShouldBeWarning();
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("DownloadClientExceptions")]
|
||||
public void should_return_ok_if_client_throws_downloadclientexception(Exception ex)
|
||||
{
|
||||
_downloadClient.Setup(s => s.GetStatus())
|
||||
.Throws(ex);
|
||||
|
||||
Subject.Check().ShouldBeOk();
|
||||
|
||||
ExceptionVerification.ExpectedErrors(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
.Returns(_clientStatus);
|
||||
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(s => s.GetDownloadClients())
|
||||
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
|
||||
.Returns(new IDownloadClient[] { _downloadClient.Object });
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.CustomFormats;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Profiles;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||
{
|
||||
[TestFixture]
|
||||
public class CleanupQualityProfileFormatItemsFixture : DbTest<CleanupQualityProfileFormatItems, Profile>
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Mocker.SetConstant<IQualityProfileFormatItemsCleanupRepository>(
|
||||
new QualityProfileFormatItemsCleanupRepository(Mocker.Resolve<IMainDatabase>(), Mocker.Resolve<IEventAggregator>()));
|
||||
|
||||
Mocker.SetConstant<ICustomFormatRepository>(
|
||||
new CustomFormatRepository(Mocker.Resolve<IMainDatabase>(), Mocker.Resolve<IEventAggregator>()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_remove_orphaned_custom_formats()
|
||||
{
|
||||
var qualityProfile = Builder<Profile>.CreateNew()
|
||||
.With(h => h.Items = Qualities.QualityFixture.GetDefaultQualities())
|
||||
.With(h => h.MinFormatScore = 50)
|
||||
.With(h => h.CutoffFormatScore = 100)
|
||||
.With(h => h.FormatItems = new List<ProfileFormatItem>
|
||||
{
|
||||
Builder<ProfileFormatItem>.CreateNew()
|
||||
.With(c => c.Format = new CustomFormat("My Custom Format") { Id = 0 })
|
||||
.Build()
|
||||
})
|
||||
.BuildNew();
|
||||
|
||||
Db.Insert(qualityProfile);
|
||||
Subject.Clean();
|
||||
|
||||
var result = AllStoredModels;
|
||||
|
||||
result.Should().HaveCount(1);
|
||||
result.First().FormatItems.Should().BeEmpty();
|
||||
result.First().MinFormatScore.Should().Be(0);
|
||||
result.First().CutoffFormatScore.Should().Be(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_remove_unorphaned_custom_formats()
|
||||
{
|
||||
var minFormatScore = 50;
|
||||
var cutoffFormatScore = 100;
|
||||
|
||||
var customFormat = Builder<CustomFormat>.CreateNew()
|
||||
.With(h => h.Specifications = new List<ICustomFormatSpecification>())
|
||||
.BuildNew();
|
||||
|
||||
Db.Insert(customFormat);
|
||||
|
||||
var qualityProfile = Builder<Profile>.CreateNew()
|
||||
.With(h => h.Items = Qualities.QualityFixture.GetDefaultQualities())
|
||||
.With(h => h.MinFormatScore = minFormatScore)
|
||||
.With(h => h.CutoffFormatScore = cutoffFormatScore)
|
||||
.With(h => h.FormatItems = new List<ProfileFormatItem>
|
||||
{
|
||||
Builder<ProfileFormatItem>.CreateNew()
|
||||
.With(c => c.Format = customFormat)
|
||||
.Build()
|
||||
})
|
||||
.BuildNew();
|
||||
|
||||
Db.Insert(qualityProfile);
|
||||
|
||||
Subject.Clean();
|
||||
var result = AllStoredModels;
|
||||
|
||||
result.Should().HaveCount(1);
|
||||
result.First().FormatItems.Should().HaveCount(1);
|
||||
result.First().MinFormatScore.Should().Be(minFormatScore);
|
||||
result.First().CutoffFormatScore.Should().Be(cutoffFormatScore);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_add_missing_custom_formats()
|
||||
{
|
||||
var minFormatScore = 50;
|
||||
var cutoffFormatScore = 100;
|
||||
|
||||
var customFormat1 = Builder<CustomFormat>.CreateNew()
|
||||
.With(h => h.Id = 1)
|
||||
.With(h => h.Name = "Custom Format 1")
|
||||
.With(h => h.Specifications = new List<ICustomFormatSpecification>())
|
||||
.BuildNew();
|
||||
|
||||
var customFormat2 = Builder<CustomFormat>.CreateNew()
|
||||
.With(h => h.Id = 2)
|
||||
.With(h => h.Name = "Custom Format 2")
|
||||
.With(h => h.Specifications = new List<ICustomFormatSpecification>())
|
||||
.BuildNew();
|
||||
|
||||
Db.Insert(customFormat1);
|
||||
Db.Insert(customFormat2);
|
||||
|
||||
var qualityProfile = Builder<Profile>.CreateNew()
|
||||
.With(h => h.Items = Qualities.QualityFixture.GetDefaultQualities())
|
||||
.With(h => h.MinFormatScore = minFormatScore)
|
||||
.With(h => h.CutoffFormatScore = cutoffFormatScore)
|
||||
.With(h => h.FormatItems = new List<ProfileFormatItem>
|
||||
{
|
||||
Builder<ProfileFormatItem>.CreateNew()
|
||||
.With(c => c.Format = customFormat1)
|
||||
.Build()
|
||||
})
|
||||
.BuildNew();
|
||||
|
||||
Db.Insert(qualityProfile);
|
||||
|
||||
Subject.Clean();
|
||||
var result = AllStoredModels;
|
||||
|
||||
result.Should().HaveCount(1);
|
||||
result.First().FormatItems.Should().HaveCount(2);
|
||||
result.First().MinFormatScore.Should().Be(minFormatScore);
|
||||
result.First().CutoffFormatScore.Should().Be(cutoffFormatScore);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.FileList;
|
||||
using NzbDrone.Core.Indexers.Newznab;
|
||||
using NzbDrone.Core.Indexers.Omgwtfnzbs;
|
||||
using NzbDrone.Core.Lifecycle;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||
_indexers = new List<IIndexer>();
|
||||
|
||||
_indexers.Add(Mocker.Resolve<Newznab>());
|
||||
_indexers.Add(Mocker.Resolve<Omgwtfnzbs>());
|
||||
_indexers.Add(Mocker.Resolve<FileList>());
|
||||
|
||||
Mocker.SetConstant<IEnumerable<IIndexer>>(_indexers);
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.Omgwtfnzbs;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests.OmgwtfnzbsTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class OmgwtfnzbsFixture : CoreTest<Omgwtfnzbs>
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Subject.Definition = new IndexerDefinition()
|
||||
{
|
||||
Name = "Omgwtfnzbs",
|
||||
Settings = new OmgwtfnzbsSettings()
|
||||
{
|
||||
ApiKey = "xxx",
|
||||
Username = "me@my.domain"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_omgwtfnzbs()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Omgwtfnzbs/Omgwtfnzbs.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(100);
|
||||
|
||||
var releaseInfo = releases.First();
|
||||
|
||||
releaseInfo.Title.Should().Be("Un.Petit.Boulot.2016.FRENCH.720p.BluRay.DTS.x264-LOST");
|
||||
releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Usenet);
|
||||
releaseInfo.DownloadUrl.Should().Be("https://api.omgwtfnzbs.me/nzb/?id=8a2Bw&user=nzbdrone&api=nzbdrone");
|
||||
releaseInfo.InfoUrl.Should().Be("https://omgwtfnzbs.me/details.php?id=8a2Bw");
|
||||
releaseInfo.CommentUrl.Should().BeNullOrEmpty();
|
||||
releaseInfo.Indexer.Should().Be(Subject.Definition.Name);
|
||||
releaseInfo.PublishDate.Should().Be(DateTime.Parse("2017/01/09 00:16:54"));
|
||||
releaseInfo.Size.Should().Be(5354909355);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -445,6 +445,58 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
.Verify(v => v.DeleteFolder(It.IsAny<string>(), true), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_rejection_if_nothing_imported_and_contains_rar_file()
|
||||
{
|
||||
GivenValidMovie();
|
||||
|
||||
var path = @"C:\media\ba09030e-1234-1234-1234-123456789abc\[HorribleSubs] American Psycho (2000) [720p]\[HorribleSubs] American Psycho (2000) [720p].mkv".AsOsAgnostic();
|
||||
var imported = new List<ImportDecision>();
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Setup(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Movie>(), It.IsAny<DownloadClientItem>(), null, true, true))
|
||||
.Returns(imported);
|
||||
|
||||
Mocker.GetMock<IImportApprovedMovie>()
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
|
||||
.Returns(imported.Select(i => new ImportResult(i)).ToList());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.GetFiles(It.IsAny<string>(), SearchOption.AllDirectories))
|
||||
.Returns(new[] { _videoFiles.First().Replace(".ext", ".rar") });
|
||||
|
||||
var result = Subject.ProcessPath(path);
|
||||
|
||||
result.Count.Should().Be(1);
|
||||
result.First().Result.Should().Be(ImportResultType.Rejected);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_rejection_if_nothing_imported_and_contains_executable_file()
|
||||
{
|
||||
GivenValidMovie();
|
||||
|
||||
var path = @"C:\media\ba09030e-1234-1234-1234-123456789abc\[HorribleSubs] American Psycho (2000) [720p]\[HorribleSubs] American Psycho (2000) [720p].mkv".AsOsAgnostic();
|
||||
var imported = new List<ImportDecision>();
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Setup(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Movie>(), It.IsAny<DownloadClientItem>(), null, true, true))
|
||||
.Returns(imported);
|
||||
|
||||
Mocker.GetMock<IImportApprovedMovie>()
|
||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
|
||||
.Returns(imported.Select(i => new ImportResult(i)).ToList());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.GetFiles(It.IsAny<string>(), SearchOption.AllDirectories))
|
||||
.Returns(new[] { _videoFiles.First().Replace(".ext", ".exe") });
|
||||
|
||||
var result = Subject.ProcessPath(path);
|
||||
|
||||
result.Count.Should().Be(1);
|
||||
result.First().Result.Should().Be(ImportResultType.Rejected);
|
||||
}
|
||||
|
||||
private void VerifyNoImport()
|
||||
{
|
||||
Mocker.GetMock<IImportApprovedMovie>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -13,10 +14,10 @@ using NzbDrone.Core.Test.Framework;
|
||||
namespace NzbDrone.Core.Test.NotificationTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TraktServiceFixture : CoreTest<TraktService>
|
||||
public class TraktServiceFixture : CoreTest<Trakt>
|
||||
{
|
||||
private DownloadMessage _downloadMessage;
|
||||
private TraktSettings _traktSettings;
|
||||
private NotificationDefinition _traktDefinition;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
@@ -34,11 +35,17 @@ namespace NzbDrone.Core.Test.NotificationTests
|
||||
}
|
||||
};
|
||||
|
||||
_traktSettings = new TraktSettings
|
||||
_traktDefinition = new NotificationDefinition
|
||||
{
|
||||
AccessToken = "",
|
||||
RefreshToken = ""
|
||||
Settings = new TraktSettings
|
||||
{
|
||||
AccessToken = "",
|
||||
RefreshToken = "",
|
||||
Expires = DateTime.Now.AddDays(1)
|
||||
}
|
||||
};
|
||||
|
||||
Subject.Definition = _traktDefinition;
|
||||
}
|
||||
|
||||
private void GiventValidMediaInfo(Quality quality, string audioChannels, string audioFormat, string scanType)
|
||||
@@ -56,7 +63,7 @@ namespace NzbDrone.Core.Test.NotificationTests
|
||||
[Test]
|
||||
public void should_add_collection_movie_if_null_mediainfo()
|
||||
{
|
||||
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
|
||||
Subject.OnDownload(_downloadMessage);
|
||||
|
||||
Mocker.GetMock<ITraktProxy>()
|
||||
.Verify(v => v.AddToCollection(It.IsAny<TraktCollectMoviesResource>(), It.IsAny<string>()), Times.Once());
|
||||
@@ -67,7 +74,7 @@ namespace NzbDrone.Core.Test.NotificationTests
|
||||
{
|
||||
GiventValidMediaInfo(Quality.Bluray1080p, "5.1", "DTS", "Progressive");
|
||||
|
||||
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
|
||||
Subject.OnDownload(_downloadMessage);
|
||||
|
||||
Mocker.GetMock<ITraktProxy>()
|
||||
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>
|
||||
@@ -83,7 +90,7 @@ namespace NzbDrone.Core.Test.NotificationTests
|
||||
{
|
||||
GiventValidMediaInfo(Quality.Bluray1080p, "2.0", "DTS", "Progressive");
|
||||
|
||||
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
|
||||
Subject.OnDownload(_downloadMessage);
|
||||
|
||||
Mocker.GetMock<ITraktProxy>()
|
||||
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie.Title.1990.Ultimate.Rekall.Edition.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA5.1-TWA", "Ultimate Rekall Edition")]
|
||||
[TestCase("Movie.Title.1971.Signature.Edition.1080p.BluRay.FLAC.2.0.x264-TDD", "Signature Edition")]
|
||||
[TestCase("Movie.1979.The.Imperial.Edition.BluRay.720p.DTS.x264-CtrlHD", "Imperial Edition")]
|
||||
[TestCase("Movie.1997.Open.Matte.1080p.BluRay.x264.DTS-FGT", "Open Matte")]
|
||||
public void should_parse_edition(string postTitle, string edition)
|
||||
{
|
||||
var parsed = Parser.Parser.ParseMovieTitle(postTitle);
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie Name 2018 NEW PROPER 720p HD-CAM X264 HQ-CPG", true)]
|
||||
[TestCase("Movie Name (2022) 1080p HQCAM ENG x264 AAC - QRips", false)]
|
||||
[TestCase("Movie Name (2018) 720p Hindi HQ CAMrip x264 AAC 1.4GB", false)]
|
||||
[TestCase("Movie Name (2022) New HDCAMRip 1080p [Love Rulz]", false)]
|
||||
public void should_parse_cam(string title, bool proper)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Source.CAM, proper, Resolution.Unknown);
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Some.Really.Bad.Movie.Title.[2021].1080p.WEB-HDRip.Dual.Audio.[Hindi.[Clean]. .English].x264.AAC.DD.2.0.By.Full4Movies.mkv-xpost", null)]
|
||||
[TestCase("The.Movie.Title.2013.1080p.10bit.AMZN.WEB-DL.DDP5.1.HEVC-Vyndros", "Vyndros")]
|
||||
[TestCase("Movie.Name.2022.1080p.BluRay.x264-[YTS.AG]", "YTS.AG")]
|
||||
[TestCase("Movie.Name.2022.1080p.BluRay.x264-VARYG", "VARYG")]
|
||||
[TestCase("Movie.Title.2019.1080p.AMZN.WEB-Rip.DDP.5.1.HEVC", null)]
|
||||
public void should_parse_expected_release_group(string title, string expected)
|
||||
{
|
||||
@@ -107,6 +108,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Why.Cant.You.Use.Normal.Characters.2021.2160p.UHD.HDR10+.BluRay.TrueHD.Atmos.7.1.x265-ZØNEHD", "ZØNEHD")]
|
||||
[TestCase("Movie.Should.Not.Use.Dots.2022.1080p.BluRay.x265.10bit.Tigole", "Tigole")]
|
||||
[TestCase("Movie.Title.2005.2160p.UHD.BluRay.TrueHD 7.1.Atmos.x265 - HQMUX", "HQMUX")]
|
||||
[TestCase("Movie.Name.2022.1080p.BluRay.x264-VARYG (Blue Lock, Multi-Subs)", "VARYG")]
|
||||
public void should_parse_exception_release_group(string title, string expected)
|
||||
{
|
||||
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||
Subject.GetLatestUpdate("develop", new Version(10, 0)).Should().BeNull();
|
||||
}
|
||||
|
||||
[Ignore("Pending linux-x86 release")]
|
||||
[Test]
|
||||
public void finds_update_when_version_lower()
|
||||
{
|
||||
@@ -40,7 +39,6 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||
Subject.GetLatestUpdate("invalid_branch", new Version(0, 2)).Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Ignore("Pending linux-x86 release")]
|
||||
[Test]
|
||||
public void should_get_recent_updates()
|
||||
{
|
||||
|
||||
@@ -198,7 +198,7 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
public string LogLevel => GetValue("LogLevel", "info").ToLowerInvariant();
|
||||
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
|
||||
public string Theme => GetValue("Theme", "light", persist: false);
|
||||
public string Theme => GetValue("Theme", "auto", persist: false);
|
||||
public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
|
||||
public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false);
|
||||
public string PostgresPassword => _postgresOptions?.Password ?? GetValue("PostgresPassword", string.Empty, persist: false);
|
||||
|
||||
14
src/NzbDrone.Core/Datastore/Migration/217_remove_omg.cs
Normal file
14
src/NzbDrone.Core/Datastore/Migration/217_remove_omg.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(217)]
|
||||
public class remove_omg : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Delete.FromTable("Indexers").Row(new { Implementation = "Omgwtfnzbs" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
if ((isQualityUpgrade || isCustomFormatUpgrade) && !qualityProfile.UpgradeAllowed)
|
||||
{
|
||||
_logger.Debug("Quality profile does not allow upgrades, skipping");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
{
|
||||
public static class EncodingForBase64
|
||||
{
|
||||
public static string EncodeBase64(this string text)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] textAsBytes = System.Text.Encoding.UTF8.GetBytes(text);
|
||||
return System.Convert.ToBase64String(textAsBytes);
|
||||
}
|
||||
|
||||
public static string DecodeBase64(this string encodedText)
|
||||
{
|
||||
if (encodedText == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] textAsBytes = System.Convert.FromBase64String(encodedText);
|
||||
return System.Text.Encoding.UTF8.GetString(textAsBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
{
|
||||
public class FreeboxDownloadException : DownloadClientException
|
||||
{
|
||||
public FreeboxDownloadException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
{
|
||||
public enum FreeboxDownloadPriority
|
||||
{
|
||||
Last = 0,
|
||||
First = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
{
|
||||
public interface IFreeboxDownloadProxy
|
||||
{
|
||||
void Authenticate(FreeboxDownloadSettings settings);
|
||||
string AddTaskFromUrl(string url, string directory, bool addPaused, bool addFirst, double? seedRatio, FreeboxDownloadSettings settings);
|
||||
string AddTaskFromFile(string fileName, byte[] fileContent, string directory, bool addPaused, bool addFirst, double? seedRatio, FreeboxDownloadSettings settings);
|
||||
void DeleteTask(string id, bool deleteData, FreeboxDownloadSettings settings);
|
||||
FreeboxDownloadConfiguration GetDownloadConfiguration(FreeboxDownloadSettings settings);
|
||||
List<FreeboxDownloadTask> GetTasks(FreeboxDownloadSettings settings);
|
||||
}
|
||||
|
||||
public class FreeboxDownloadProxy : IFreeboxDownloadProxy
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly Logger _logger;
|
||||
private ICached<string> _authSessionTokenCache;
|
||||
|
||||
public FreeboxDownloadProxy(ICacheManager cacheManager, IHttpClient httpClient, Logger logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
_authSessionTokenCache = cacheManager.GetCache<string>(GetType(), "authSessionToken");
|
||||
}
|
||||
|
||||
public void Authenticate(FreeboxDownloadSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/login").Build();
|
||||
|
||||
var response = ProcessRequest<FreeboxLogin>(request, settings);
|
||||
|
||||
if (response.Result.LoggedIn == false)
|
||||
{
|
||||
throw new DownloadClientAuthenticationException("Not logged");
|
||||
}
|
||||
}
|
||||
|
||||
public string AddTaskFromUrl(string url, string directory, bool addPaused, bool addFirst, double? seedRatio, FreeboxDownloadSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/downloads/add").Post();
|
||||
request.Headers.ContentType = "application/x-www-form-urlencoded";
|
||||
|
||||
request.AddFormParameter("download_url", System.Web.HttpUtility.UrlPathEncode(url));
|
||||
|
||||
if (!directory.IsNullOrWhiteSpace())
|
||||
{
|
||||
request.AddFormParameter("download_dir", directory);
|
||||
}
|
||||
|
||||
var response = ProcessRequest<FreeboxDownloadTask>(request.Build(), settings);
|
||||
|
||||
SetTorrentSettings(response.Result.Id, addPaused, addFirst, seedRatio, settings);
|
||||
|
||||
return response.Result.Id;
|
||||
}
|
||||
|
||||
public string AddTaskFromFile(string fileName, byte[] fileContent, string directory, bool addPaused, bool addFirst, double? seedRatio, FreeboxDownloadSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/downloads/add").Post();
|
||||
|
||||
request.AddFormUpload("download_file", fileName, fileContent, "multipart/form-data");
|
||||
|
||||
if (directory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.AddFormParameter("download_dir", directory);
|
||||
}
|
||||
|
||||
var response = ProcessRequest<FreeboxDownloadTask>(request.Build(), settings);
|
||||
|
||||
SetTorrentSettings(response.Result.Id, addPaused, addFirst, seedRatio, settings);
|
||||
|
||||
return response.Result.Id;
|
||||
}
|
||||
|
||||
public void DeleteTask(string id, bool deleteData, FreeboxDownloadSettings settings)
|
||||
{
|
||||
var uri = "/downloads/" + id;
|
||||
|
||||
if (deleteData == true)
|
||||
{
|
||||
uri += "/erase";
|
||||
}
|
||||
|
||||
var request = BuildRequest(settings).Resource(uri).Build();
|
||||
|
||||
request.Method = HttpMethod.Delete;
|
||||
|
||||
ProcessRequest<string>(request, settings);
|
||||
}
|
||||
|
||||
public FreeboxDownloadConfiguration GetDownloadConfiguration(FreeboxDownloadSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/downloads/config/").Build();
|
||||
|
||||
return ProcessRequest<FreeboxDownloadConfiguration>(request, settings).Result;
|
||||
}
|
||||
|
||||
public List<FreeboxDownloadTask> GetTasks(FreeboxDownloadSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/downloads/").Build();
|
||||
|
||||
return ProcessRequest<List<FreeboxDownloadTask>>(request, settings).Result;
|
||||
}
|
||||
|
||||
private static string BuildCachedHeaderKey(FreeboxDownloadSettings settings)
|
||||
{
|
||||
return $"{settings.Host}:{settings.AppId}:{settings.AppToken}";
|
||||
}
|
||||
|
||||
private void SetTorrentSettings(string id, bool addPaused, bool addFirst, double? seedRatio, FreeboxDownloadSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/downloads/" + id).Build();
|
||||
|
||||
request.Method = HttpMethod.Put;
|
||||
|
||||
var body = new Dictionary<string, object> { };
|
||||
|
||||
if (addPaused)
|
||||
{
|
||||
body.Add("status", FreeboxDownloadTaskStatus.Stopped.ToString().ToLower());
|
||||
}
|
||||
|
||||
if (addFirst)
|
||||
{
|
||||
body.Add("queue_pos", "1");
|
||||
}
|
||||
|
||||
if (seedRatio != null)
|
||||
{
|
||||
// 0 means unlimited seeding
|
||||
body.Add("stop_ratio", seedRatio);
|
||||
}
|
||||
|
||||
if (body.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
request.SetContent(body.ToJson());
|
||||
|
||||
ProcessRequest<FreeboxDownloadTask>(request, settings);
|
||||
}
|
||||
|
||||
private string GetSessionToken(HttpRequestBuilder requestBuilder, FreeboxDownloadSettings settings, bool force = false)
|
||||
{
|
||||
var sessionToken = _authSessionTokenCache.Find(BuildCachedHeaderKey(settings));
|
||||
|
||||
if (sessionToken == null || force)
|
||||
{
|
||||
_authSessionTokenCache.Remove(BuildCachedHeaderKey(settings));
|
||||
|
||||
_logger.Debug($"Client needs a new Session Token to reach the API with App ID '{settings.AppId}'");
|
||||
|
||||
// Obtaining a Session Token (from official documentation):
|
||||
// To protect the app_token secret, it will never be used directly to authenticate the
|
||||
// application, instead the API will provide a challenge the app will combine to its
|
||||
// app_token to open a session and get a session_token.
|
||||
// The validity of the session_token is limited in time and the app will have to renew
|
||||
// this session_token once in a while.
|
||||
|
||||
// Retrieving the 'challenge' value (it changes frequently and have a limited time validity)
|
||||
// needed to build password
|
||||
var challengeRequest = requestBuilder.Resource("/login").Build();
|
||||
challengeRequest.Method = HttpMethod.Get;
|
||||
|
||||
var challenge = ProcessRequest<FreeboxLogin>(challengeRequest, settings).Result.Challenge;
|
||||
|
||||
// The password is computed using the 'challenge' value and the 'app_token' ('App Token' setting)
|
||||
var enc = System.Text.Encoding.ASCII;
|
||||
var hmac = new HMACSHA1(enc.GetBytes(settings.AppToken));
|
||||
hmac.Initialize();
|
||||
var buffer = enc.GetBytes(challenge);
|
||||
var password = System.BitConverter.ToString(hmac.ComputeHash(buffer)).Replace("-", "").ToLower();
|
||||
|
||||
// Both 'app_id' ('App ID' setting) and computed password are set to get a Session Token
|
||||
var sessionRequest = requestBuilder.Resource("/login/session").Post().Build();
|
||||
var body = new Dictionary<string, object>
|
||||
{
|
||||
{ "app_id", settings.AppId },
|
||||
{ "password", password }
|
||||
};
|
||||
sessionRequest.SetContent(body.ToJson());
|
||||
|
||||
sessionToken = ProcessRequest<FreeboxLogin>(sessionRequest, settings).Result.SessionToken;
|
||||
|
||||
_authSessionTokenCache.Set(BuildCachedHeaderKey(settings), sessionToken);
|
||||
|
||||
_logger.Debug($"New Session Token stored in cache for App ID '{settings.AppId}', ready to reach API");
|
||||
}
|
||||
|
||||
return sessionToken;
|
||||
}
|
||||
|
||||
private HttpRequestBuilder BuildRequest(FreeboxDownloadSettings settings, bool authentication = true)
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port, settings.ApiUrl)
|
||||
{
|
||||
LogResponseContent = true
|
||||
};
|
||||
|
||||
requestBuilder.Headers.ContentType = "application/json";
|
||||
|
||||
if (authentication == true)
|
||||
{
|
||||
requestBuilder.SetHeader("X-Fbx-App-Auth", GetSessionToken(requestBuilder, settings));
|
||||
}
|
||||
|
||||
return requestBuilder;
|
||||
}
|
||||
|
||||
private FreeboxResponse<T> ProcessRequest<T>(HttpRequest request, FreeboxDownloadSettings settings)
|
||||
{
|
||||
request.LogResponseContent = true;
|
||||
request.SuppressHttpError = true;
|
||||
|
||||
HttpResponse response;
|
||||
|
||||
try
|
||||
{
|
||||
response = _httpClient.Execute(request);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
throw new DownloadClientUnavailableException($"Unable to reach Freebox API. Verify 'Host', 'Port' or 'Use SSL' settings. (Error: {ex.Message})", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
throw new DownloadClientUnavailableException("Unable to connect to Freebox API, please check your settings", ex);
|
||||
}
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Forbidden || response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
_authSessionTokenCache.Remove(BuildCachedHeaderKey(settings));
|
||||
|
||||
var responseContent = Json.Deserialize<FreeboxResponse<FreeboxLogin>>(response.Content);
|
||||
|
||||
var msg = $"Authentication to Freebox API failed. Reason: {responseContent.GetErrorDescription()}";
|
||||
_logger.Error(msg);
|
||||
throw new DownloadClientAuthenticationException(msg);
|
||||
}
|
||||
else if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
throw new FreeboxDownloadException("Unable to reach Freebox API. Verify 'API URL' setting for base URL and version.");
|
||||
}
|
||||
else if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
var responseContent = Json.Deserialize<FreeboxResponse<T>>(response.Content);
|
||||
|
||||
if (responseContent.Success)
|
||||
{
|
||||
return responseContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
var msg = $"Freebox API returned error: {responseContent.GetErrorDescription()}";
|
||||
_logger.Error(msg);
|
||||
throw new DownloadClientException(msg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DownloadClientException("Unable to connect to Freebox, please check your settings.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
{
|
||||
public class FreeboxDownloadSettingsValidator : AbstractValidator<FreeboxDownloadSettings>
|
||||
{
|
||||
public FreeboxDownloadSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.Host).ValidHost();
|
||||
RuleFor(c => c.Port).InclusiveBetween(1, 65535);
|
||||
RuleFor(c => c.ApiUrl).NotEmpty()
|
||||
.WithMessage("'API URL' must not be empty.");
|
||||
RuleFor(c => c.ApiUrl).ValidUrlBase();
|
||||
RuleFor(c => c.AppId).NotEmpty()
|
||||
.WithMessage("'App ID' must not be empty.");
|
||||
RuleFor(c => c.AppToken).NotEmpty()
|
||||
.WithMessage("'App Token' must not be empty.");
|
||||
RuleFor(c => c.Category).Matches(@"^\.?[-a-z]*$", RegexOptions.IgnoreCase)
|
||||
.WithMessage("Allowed characters a-z and -");
|
||||
RuleFor(c => c.DestinationDirectory).IsValidPath()
|
||||
.When(c => c.DestinationDirectory.IsNotNullOrWhiteSpace());
|
||||
RuleFor(c => c.DestinationDirectory).Empty()
|
||||
.When(c => c.Category.IsNotNullOrWhiteSpace())
|
||||
.WithMessage("Cannot use 'Category' and 'Destination Directory' at the same time.");
|
||||
RuleFor(c => c.Category).Empty()
|
||||
.When(c => c.DestinationDirectory.IsNotNullOrWhiteSpace())
|
||||
.WithMessage("Cannot use 'Category' and 'Destination Directory' at the same time.");
|
||||
}
|
||||
}
|
||||
|
||||
public class FreeboxDownloadSettings : IProviderConfig
|
||||
{
|
||||
private static readonly FreeboxDownloadSettingsValidator Validator = new FreeboxDownloadSettingsValidator();
|
||||
|
||||
public FreeboxDownloadSettings()
|
||||
{
|
||||
Host = "mafreebox.freebox.fr";
|
||||
Port = 443;
|
||||
UseSsl = true;
|
||||
ApiUrl = "/api/v1/";
|
||||
}
|
||||
|
||||
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox, HelpText = "Hostname or host IP address of the Freebox, defaults to 'mafreebox.freebox.fr' (will only work if on same network)")]
|
||||
public string Host { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox, HelpText = "Port used to access Freebox interface, defaults to '443'")]
|
||||
public int Port { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secured connection when connecting to Freebox API")]
|
||||
public bool UseSsl { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "API URL", Type = FieldType.Textbox, Advanced = true, HelpText = "Define Freebox API base URL with API version, eg http://[host]:[port]/[api_base_url]/[api_version]/, defaults to '/api/v1/'")]
|
||||
public string ApiUrl { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "App ID", Type = FieldType.Textbox, HelpText = "App ID given when creating access to Freebox API (ie 'app_id')")]
|
||||
public string AppId { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "App Token", Type = FieldType.Password, Privacy = PrivacyLevel.Password, HelpText = "App token retrieved when creating access to Freebox API (ie 'app_token')")]
|
||||
public string AppToken { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Destination Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Freebox download location")]
|
||||
public string DestinationDirectory { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated non-Sonarr downloads (will create a [category] subdirectory in the output directory)")]
|
||||
public string Category { get; set; }
|
||||
|
||||
[FieldDefinition(8, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
||||
public int RecentPriority { get; set; }
|
||||
|
||||
[FieldDefinition(9, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
||||
public int OlderPriority { get; set; }
|
||||
|
||||
[FieldDefinition(10, Label = "Add Paused", Type = FieldType.Checkbox)]
|
||||
public bool AddPaused { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
|
||||
{
|
||||
public class FreeboxDownloadConfiguration
|
||||
{
|
||||
[JsonProperty(PropertyName = "download_dir")]
|
||||
public string DownloadDirectory { get; set; }
|
||||
public string DecodedDownloadDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
return DownloadDirectory.DecodeBase64();
|
||||
}
|
||||
set
|
||||
{
|
||||
DownloadDirectory = value.EncodeBase64();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Serializer;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
|
||||
{
|
||||
public enum FreeboxDownloadTaskType
|
||||
{
|
||||
Bt,
|
||||
Nzb,
|
||||
Http,
|
||||
Ftp
|
||||
}
|
||||
|
||||
public enum FreeboxDownloadTaskStatus
|
||||
{
|
||||
Unknown,
|
||||
Stopped,
|
||||
Queued,
|
||||
Starting,
|
||||
Downloading,
|
||||
Stopping,
|
||||
Error,
|
||||
Done,
|
||||
Checking,
|
||||
Repairing,
|
||||
Extracting,
|
||||
Seeding,
|
||||
Retry
|
||||
}
|
||||
|
||||
public enum FreeboxDownloadTaskIoPriority
|
||||
{
|
||||
Low,
|
||||
Normal,
|
||||
High
|
||||
}
|
||||
|
||||
public class FreeboxDownloadTask
|
||||
{
|
||||
private static readonly Dictionary<string, string> Descriptions;
|
||||
|
||||
[JsonProperty(PropertyName = "id")]
|
||||
public string Id { get; set; }
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
public string Name { get; set; }
|
||||
[JsonProperty(PropertyName = "download_dir")]
|
||||
public string DownloadDirectory { get; set; }
|
||||
public string DecodedDownloadDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
return DownloadDirectory.DecodeBase64();
|
||||
}
|
||||
set
|
||||
{
|
||||
DownloadDirectory = value.EncodeBase64();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "info_hash")]
|
||||
public string InfoHash { get; set; }
|
||||
[JsonProperty(PropertyName = "queue_pos")]
|
||||
public int QueuePosition { get; set; }
|
||||
[JsonConverter(typeof(UnderscoreStringEnumConverter), FreeboxDownloadTaskStatus.Unknown)]
|
||||
public FreeboxDownloadTaskStatus Status { get; set; }
|
||||
[JsonProperty(PropertyName = "eta")]
|
||||
public long Eta { get; set; }
|
||||
[JsonProperty(PropertyName = "error")]
|
||||
public string Error { get; set; }
|
||||
[JsonProperty(PropertyName = "type")]
|
||||
public string Type { get; set; }
|
||||
[JsonProperty(PropertyName = "io_priority")]
|
||||
public string IoPriority { get; set; }
|
||||
[JsonProperty(PropertyName = "stop_ratio")]
|
||||
public long StopRatio { get; set; }
|
||||
[JsonProperty(PropertyName = "piece_length")]
|
||||
public long PieceLength { get; set; }
|
||||
[JsonProperty(PropertyName = "created_ts")]
|
||||
public long CreatedTimestamp { get; set; }
|
||||
[JsonProperty(PropertyName = "size")]
|
||||
public long Size { get; set; }
|
||||
[JsonProperty(PropertyName = "rx_pct")]
|
||||
public long ReceivedPrct { get; set; }
|
||||
[JsonProperty(PropertyName = "rx_bytes")]
|
||||
public long ReceivedBytes { get; set; }
|
||||
[JsonProperty(PropertyName = "rx_rate")]
|
||||
public long ReceivedRate { get; set; }
|
||||
[JsonProperty(PropertyName = "tx_pct")]
|
||||
public long TransmittedPrct { get; set; }
|
||||
[JsonProperty(PropertyName = "tx_bytes")]
|
||||
public long TransmittedBytes { get; set; }
|
||||
[JsonProperty(PropertyName = "tx_rate")]
|
||||
public long TransmittedRate { get; set; }
|
||||
|
||||
static FreeboxDownloadTask()
|
||||
{
|
||||
Descriptions = new Dictionary<string, string>
|
||||
{
|
||||
{ "internal", "Internal error." },
|
||||
{ "disk_full", "The disk is full." },
|
||||
{ "unknown", "Unknown error." },
|
||||
{ "parse_error", "Parse error." },
|
||||
{ "unknown_host", "Unknown host." },
|
||||
{ "timeout", "Timeout." },
|
||||
{ "bad_authentication", "Invalid credentials." },
|
||||
{ "connection_refused", "Remote host refused connection." },
|
||||
{ "bt_tracker_error", "Unable to announce on tracker." },
|
||||
{ "bt_missing_files", "Missing torrent files." },
|
||||
{ "bt_file_error", "Error accessing torrent files." },
|
||||
{ "missing_ctx_file", "Error accessing task context file." },
|
||||
{ "nzb_no_group", "Cannot find the requested group on server." },
|
||||
{ "nzb_not_found", "Article not fount on the server." },
|
||||
{ "nzb_invalid_crc", "Invalid article CRC." },
|
||||
{ "nzb_invalid_size", "Invalid article size." },
|
||||
{ "nzb_invalid_filename", "Invalid filename." },
|
||||
{ "nzb_open_failed", "Error opening." },
|
||||
{ "nzb_write_failed", "Error writing." },
|
||||
{ "nzb_missing_size", "Missing article size." },
|
||||
{ "nzb_decode_error", "Article decoding error." },
|
||||
{ "nzb_missing_segments", "Missing article segments." },
|
||||
{ "nzb_error", "Other nzb error." },
|
||||
{ "nzb_authentication_required", "Nzb server need authentication." }
|
||||
};
|
||||
}
|
||||
|
||||
public string GetErrorDescription()
|
||||
{
|
||||
if (Descriptions.ContainsKey(Error))
|
||||
{
|
||||
return Descriptions[Error];
|
||||
}
|
||||
|
||||
return $"{Error} - Unknown error";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
|
||||
{
|
||||
public class FreeboxLogin
|
||||
{
|
||||
[JsonProperty(PropertyName = "logged_in")]
|
||||
public bool LoggedIn { get; set; }
|
||||
[JsonProperty(PropertyName = "challenge")]
|
||||
public string Challenge { get; set; }
|
||||
[JsonProperty(PropertyName = "password_salt")]
|
||||
public string PasswordSalt { get; set; }
|
||||
[JsonProperty(PropertyName = "password_set")]
|
||||
public bool PasswordSet { get; set; }
|
||||
[JsonProperty(PropertyName = "session_token")]
|
||||
public string SessionToken { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload.Responses
|
||||
{
|
||||
public class FreeboxResponse<T>
|
||||
{
|
||||
private static readonly Dictionary<string, string> Descriptions;
|
||||
|
||||
[JsonProperty(PropertyName = "success")]
|
||||
public bool Success { get; set; }
|
||||
[JsonProperty(PropertyName = "msg")]
|
||||
public string Message { get; set; }
|
||||
[JsonProperty(PropertyName = "error_code")]
|
||||
public string ErrorCode { get; set; }
|
||||
[JsonProperty(PropertyName = "result")]
|
||||
public T Result { get; set; }
|
||||
|
||||
static FreeboxResponse()
|
||||
{
|
||||
Descriptions = new Dictionary<string, string>
|
||||
{
|
||||
// Common errors
|
||||
{ "invalid_request", "Your request is invalid." },
|
||||
{ "invalid_api_version", "Invalid API base url or unknown API version." },
|
||||
{ "internal_error", "Internal error." },
|
||||
|
||||
// Login API errors
|
||||
{ "auth_required", "Invalid session token, or no session token sent." },
|
||||
{ "invalid_token", "The app token you are trying to use is invalid or has been revoked." },
|
||||
{ "pending_token", "The app token you are trying to use has not been validated by user yet." },
|
||||
{ "insufficient_rights", "Your app permissions does not allow accessing this API." },
|
||||
{ "denied_from_external_ip", "You are trying to get an app_token from a remote IP." },
|
||||
{ "ratelimited", "Too many auth error have been made from your IP." },
|
||||
{ "new_apps_denied", "New application token request has been disabled." },
|
||||
{ "apps_denied", "API access from apps has been disabled." },
|
||||
|
||||
// Download API errors
|
||||
{ "task_not_found", "No task was found with the given id." },
|
||||
{ "invalid_operation", "Attempt to perform an invalid operation." },
|
||||
{ "invalid_file", "Error with the download file (invalid format ?)." },
|
||||
{ "invalid_url", "URL is invalid." },
|
||||
{ "not_implemented", "Method not implemented." },
|
||||
{ "out_of_memory", "No more memory available to perform the requested action." },
|
||||
{ "invalid_task_type", "The task type is invalid." },
|
||||
{ "hibernating", "The downloader is hibernating." },
|
||||
{ "need_bt_stopped_done", "This action is only valid for Bittorrent task in stopped or done state." },
|
||||
{ "bt_tracker_not_found", "Attempt to access an invalid tracker object." },
|
||||
{ "too_many_tasks", "Too many tasks." },
|
||||
{ "invalid_address", "Invalid peer address." },
|
||||
{ "port_conflict", "Port conflict when setting config." },
|
||||
{ "invalid_priority", "Invalid priority." },
|
||||
{ "ctx_file_error", "Failed to initialize task context file (need to check disk)." },
|
||||
{ "exists", "Same task already exists." },
|
||||
{ "port_outside_range", "Incoming port is not available for this customer." }
|
||||
};
|
||||
}
|
||||
|
||||
public string GetErrorDescription()
|
||||
{
|
||||
if (Descriptions.ContainsKey(ErrorCode))
|
||||
{
|
||||
return Descriptions[ErrorCode];
|
||||
}
|
||||
|
||||
return $"{ErrorCode} - Unknown error";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses;
|
||||
using NzbDrone.Core.Download.Clients.QBittorrent;
|
||||
using NzbDrone.Core.MediaFiles.TorrentInfo;
|
||||
using NzbDrone.Core.Organizer;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.RemotePathMappings;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
{
|
||||
public class TorrentFreeboxDownload : TorrentClientBase<FreeboxDownloadSettings>
|
||||
{
|
||||
private readonly IFreeboxDownloadProxy _proxy;
|
||||
|
||||
public TorrentFreeboxDownload(IFreeboxDownloadProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
IConfigService configService,
|
||||
INamingConfigService namingConfigService,
|
||||
IDiskProvider diskProvider,
|
||||
IRemotePathMappingService remotePathMappingService,
|
||||
ICacheManager cacheManager,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
public override string Name => "Freebox Download";
|
||||
|
||||
protected IEnumerable<FreeboxDownloadTask> GetTorrents()
|
||||
{
|
||||
return _proxy.GetTasks(Settings).Where(v => v.Type.ToLower() == FreeboxDownloadTaskType.Bt.ToString().ToLower());
|
||||
}
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
{
|
||||
var torrents = GetTorrents();
|
||||
|
||||
var queueItems = new List<DownloadClientItem>();
|
||||
|
||||
foreach (var torrent in torrents)
|
||||
{
|
||||
var outputPath = new OsPath(torrent.DecodedDownloadDirectory);
|
||||
|
||||
if (Settings.DestinationDirectory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
if (!new OsPath(Settings.DestinationDirectory).Contains(outputPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (Settings.Category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var directories = outputPath.FullPath.Split('\\', '/');
|
||||
|
||||
if (!directories.Contains(Settings.Category))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var item = new DownloadClientItem()
|
||||
{
|
||||
DownloadId = torrent.Id,
|
||||
Category = Settings.Category,
|
||||
Title = torrent.Name,
|
||||
TotalSize = torrent.Size,
|
||||
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
|
||||
RemainingSize = (long)(torrent.Size * (double)(1 - ((double)torrent.ReceivedPrct / 10000))),
|
||||
RemainingTime = torrent.Eta <= 0 ? null : TimeSpan.FromSeconds(torrent.Eta),
|
||||
SeedRatio = torrent.StopRatio <= 0 ? 0 : torrent.StopRatio / 100,
|
||||
OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, outputPath)
|
||||
};
|
||||
|
||||
switch (torrent.Status)
|
||||
{
|
||||
case FreeboxDownloadTaskStatus.Stopped: // task is stopped, can be resumed by setting the status to downloading
|
||||
case FreeboxDownloadTaskStatus.Stopping: // task is gracefully stopping
|
||||
item.Status = DownloadItemStatus.Paused;
|
||||
break;
|
||||
|
||||
case FreeboxDownloadTaskStatus.Queued: // task will start when a new download slot is available the queue position is stored in queue_pos attribute
|
||||
item.Status = DownloadItemStatus.Queued;
|
||||
break;
|
||||
|
||||
case FreeboxDownloadTaskStatus.Starting: // task is preparing to start download
|
||||
case FreeboxDownloadTaskStatus.Downloading:
|
||||
case FreeboxDownloadTaskStatus.Retry: // you can set a task status to ‘retry’ to restart the download task.
|
||||
case FreeboxDownloadTaskStatus.Checking: // checking data before lauching download.
|
||||
item.Status = DownloadItemStatus.Downloading;
|
||||
break;
|
||||
|
||||
case FreeboxDownloadTaskStatus.Error: // there was a problem with the download, you can get an error code in the error field
|
||||
item.Status = DownloadItemStatus.Warning;
|
||||
item.Message = torrent.GetErrorDescription();
|
||||
break;
|
||||
|
||||
case FreeboxDownloadTaskStatus.Done: // the download is over. For bt you can resume seeding setting the status to seeding if the ratio is not reached yet
|
||||
case FreeboxDownloadTaskStatus.Seeding: // download is over, the content is Change to being shared to other users. The task will automatically stop once the seed ratio has been reached
|
||||
item.Status = DownloadItemStatus.Completed;
|
||||
break;
|
||||
|
||||
case FreeboxDownloadTaskStatus.Unknown:
|
||||
default: // new status in API? default to downloading
|
||||
item.Message = "Unknown download state: " + torrent.Status;
|
||||
_logger.Info(item.Message);
|
||||
item.Status = DownloadItemStatus.Downloading;
|
||||
break;
|
||||
}
|
||||
|
||||
item.CanBeRemoved = item.CanMoveFiles = torrent.Status == FreeboxDownloadTaskStatus.Done;
|
||||
|
||||
queueItems.Add(item);
|
||||
}
|
||||
|
||||
return queueItems;
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
|
||||
{
|
||||
return _proxy.AddTaskFromUrl(magnetLink,
|
||||
GetDownloadDirectory().EncodeBase64(),
|
||||
ToBePaused(),
|
||||
ToBeQueuedFirst(remoteMovie),
|
||||
GetSeedRatio(remoteMovie),
|
||||
Settings);
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
return _proxy.AddTaskFromFile(filename,
|
||||
fileContent,
|
||||
GetDownloadDirectory().EncodeBase64(),
|
||||
ToBePaused(),
|
||||
ToBeQueuedFirst(remoteMovie),
|
||||
GetSeedRatio(remoteMovie),
|
||||
Settings);
|
||||
}
|
||||
|
||||
public override void RemoveItem(DownloadClientItem item, bool deleteData)
|
||||
{
|
||||
_proxy.DeleteTask(item.DownloadId, deleteData, Settings);
|
||||
}
|
||||
|
||||
public override DownloadClientInfo GetStatus()
|
||||
{
|
||||
var destDir = GetDownloadDirectory();
|
||||
|
||||
return new DownloadClientInfo
|
||||
{
|
||||
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "::1" || Settings.Host == "localhost",
|
||||
OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(destDir)) }
|
||||
};
|
||||
}
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
{
|
||||
try
|
||||
{
|
||||
_proxy.Authenticate(Settings);
|
||||
}
|
||||
catch (DownloadClientUnavailableException ex)
|
||||
{
|
||||
failures.Add(new ValidationFailure("Host", ex.Message));
|
||||
failures.Add(new ValidationFailure("Port", ex.Message));
|
||||
}
|
||||
catch (DownloadClientAuthenticationException ex)
|
||||
{
|
||||
failures.Add(new ValidationFailure("AppId", ex.Message));
|
||||
failures.Add(new ValidationFailure("AppToken", ex.Message));
|
||||
}
|
||||
catch (FreeboxDownloadException ex)
|
||||
{
|
||||
failures.Add(new ValidationFailure("ApiUrl", ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
private string GetDownloadDirectory()
|
||||
{
|
||||
if (Settings.DestinationDirectory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return Settings.DestinationDirectory.TrimEnd('/');
|
||||
}
|
||||
|
||||
var destDir = _proxy.GetDownloadConfiguration(Settings).DecodedDownloadDirectory.TrimEnd('/');
|
||||
|
||||
if (Settings.Category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
destDir = $"{destDir}/{Settings.Category}";
|
||||
}
|
||||
|
||||
return destDir;
|
||||
}
|
||||
|
||||
private bool ToBePaused()
|
||||
{
|
||||
return Settings.AddPaused;
|
||||
}
|
||||
|
||||
private bool ToBeQueuedFirst(RemoteMovie remoteMovie)
|
||||
{
|
||||
var isRecentMovie = remoteMovie.Movie.MovieMetadata.Value.IsRecentMovie;
|
||||
|
||||
if ((isRecentMovie && Settings.RecentPriority == (int)FreeboxDownloadPriority.First) ||
|
||||
(!isRecentMovie && Settings.OlderPriority == (int)FreeboxDownloadPriority.First))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private double? GetSeedRatio(RemoteMovie remoteMovie)
|
||||
{
|
||||
if (remoteMovie.SeedConfiguration == null || remoteMovie.SeedConfiguration.Ratio == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return remoteMovie.SeedConfiguration.Ratio.Value * 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,11 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
return true;
|
||||
}
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
throw new DownloadClientException("Failed to connect to qBittorrent. Check your settings and qBittorrent configuration.", new HttpException(response));
|
||||
}
|
||||
|
||||
if (response.HasHttpError)
|
||||
{
|
||||
throw new DownloadClientException("Failed to connect to qBittorrent, check your settings.", new HttpException(response));
|
||||
|
||||
@@ -262,6 +262,19 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
|
||||
if (category != null)
|
||||
{
|
||||
if (config.Misc.enable_tv_sorting && ContainsCategory(config.Misc.tv_categories, Settings.MovieCategory))
|
||||
{
|
||||
status.SortingMode = "TV";
|
||||
}
|
||||
else if (config.Misc.enable_movie_sorting && ContainsCategory(config.Misc.movie_categories, Settings.MovieCategory))
|
||||
{
|
||||
status.SortingMode = "Movie";
|
||||
}
|
||||
else if (config.Misc.enable_date_sorting && ContainsCategory(config.Misc.date_categories, Settings.MovieCategory))
|
||||
{
|
||||
status.SortingMode = "Date";
|
||||
}
|
||||
|
||||
status.OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.FullPath) };
|
||||
}
|
||||
|
||||
|
||||
@@ -134,14 +134,33 @@ namespace NzbDrone.Core.Download
|
||||
trackedDownload.Warn("No files found are eligible for import in {0}", outputPath);
|
||||
}
|
||||
|
||||
if (importResults.Count == 1)
|
||||
{
|
||||
var firstResult = importResults.First();
|
||||
|
||||
if (firstResult.Result == ImportResultType.Rejected && firstResult.ImportDecision.LocalMovie == null)
|
||||
{
|
||||
trackedDownload.Warn(new TrackedDownloadStatusMessage(firstResult.Errors.First(), new List<string>()));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var statusMessages = new List<TrackedDownloadStatusMessage>
|
||||
{
|
||||
new TrackedDownloadStatusMessage("One or more movies expected in this release were not imported or missing", new List<string>())
|
||||
};
|
||||
|
||||
if (importResults.Any(c => c.Result != ImportResultType.Imported))
|
||||
{
|
||||
var statusMessages = importResults
|
||||
statusMessages.AddRange(importResults
|
||||
.Where(v => v.Result != ImportResultType.Imported && v.ImportDecision.LocalMovie != null)
|
||||
.Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalMovie.Path), v.Errors))
|
||||
.ToArray();
|
||||
.Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalMovie.Path), v.Errors)));
|
||||
|
||||
trackedDownload.Warn(statusMessages);
|
||||
if (statusMessages.Any())
|
||||
{
|
||||
trackedDownload.Warn(statusMessages.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Disk;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
@@ -11,6 +11,7 @@ namespace NzbDrone.Core.Download
|
||||
}
|
||||
|
||||
public bool IsLocalhost { get; set; }
|
||||
public string SortingMode { get; set; }
|
||||
public List<OsPath> OutputRootFolders { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Download
|
||||
public interface IProvideDownloadClient
|
||||
{
|
||||
IDownloadClient GetDownloadClient(DownloadProtocol downloadProtocol, int indexerId = 0);
|
||||
IEnumerable<IDownloadClient> GetDownloadClients();
|
||||
IEnumerable<IDownloadClient> GetDownloadClients(bool filterBlockedClients = false);
|
||||
IDownloadClient Get(int id);
|
||||
}
|
||||
|
||||
@@ -86,14 +86,39 @@ namespace NzbDrone.Core.Download
|
||||
return provider;
|
||||
}
|
||||
|
||||
public IEnumerable<IDownloadClient> GetDownloadClients()
|
||||
public IEnumerable<IDownloadClient> GetDownloadClients(bool filterBlockedClients = false)
|
||||
{
|
||||
return _downloadClientFactory.GetAvailableProviders();
|
||||
var enabledClients = _downloadClientFactory.GetAvailableProviders();
|
||||
|
||||
if (filterBlockedClients)
|
||||
{
|
||||
return FilterBlockedIndexers(enabledClients).ToList();
|
||||
}
|
||||
|
||||
return enabledClients;
|
||||
}
|
||||
|
||||
public IDownloadClient Get(int id)
|
||||
{
|
||||
return _downloadClientFactory.GetAvailableProviders().Single(d => d.Definition.Id == id);
|
||||
}
|
||||
|
||||
private IEnumerable<IDownloadClient> FilterBlockedIndexers(IEnumerable<IDownloadClient> clients)
|
||||
{
|
||||
var blockedClients = _downloadClientStatusService.GetBlockedProviders().ToDictionary(v => v.ProviderId, v => v);
|
||||
|
||||
foreach (var client in clients)
|
||||
{
|
||||
DownloadClientStatus blockedClientStatus;
|
||||
|
||||
if (blockedClients.TryGetValue(client.Definition.Id, out blockedClientStatus))
|
||||
{
|
||||
_logger.Debug("Temporarily ignoring client {0} till {1} due to recent failures.", client.Definition.Name, blockedClientStatus.DisabledTill.Value.ToLocalTime());
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return client;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace NzbDrone.Core.Download
|
||||
movieGrabbedEvent.DownloadId = downloadClientId;
|
||||
}
|
||||
|
||||
_logger.ProgressInfo("Report sent to {0} from indexer {1}. {2}", downloadClient.Definition.Name, remoteMovie.Release.Indexer, downloadTitle);
|
||||
_logger.ProgressInfo("Report for {0} ({1}) sent to {2} from indexer {3}. {4}", remoteMovie.Movie.Title, remoteMovie.Movie.Year, downloadClient.Definition.Name, remoteMovie.Release.Indexer, downloadTitle);
|
||||
_eventAggregator.PublishEvent(movieGrabbedEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,6 +283,12 @@ namespace NzbDrone.Core.Download.Pending
|
||||
return null;
|
||||
}
|
||||
|
||||
// Languages will be empty if added before upgrading to v4, reparsing the languages if they're empty will set it to Unknown or better.
|
||||
if (release.ParsedMovieInfo.Languages.Empty())
|
||||
{
|
||||
release.ParsedMovieInfo.Languages = LanguageParser.ParseLanguages(release.Title);
|
||||
}
|
||||
|
||||
release.RemoteMovie = new RemoteMovie
|
||||
{
|
||||
Movie = movie,
|
||||
|
||||
@@ -343,9 +343,12 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
||||
|
||||
if (movieFile.MediaInfo.Subtitles != null && movieFile.MediaInfo.Subtitles.Count > 0)
|
||||
{
|
||||
var subtitle = new XElement("subtitle");
|
||||
subtitle.Add(new XElement("language", movieFile.MediaInfo.Subtitles));
|
||||
streamDetails.Add(subtitle);
|
||||
foreach (var s in movieFile.MediaInfo.Subtitles)
|
||||
{
|
||||
var subtitle = new XElement("subtitle");
|
||||
subtitle.Add(new XElement("language", s));
|
||||
streamDetails.Add(subtitle);
|
||||
}
|
||||
}
|
||||
|
||||
fileInfo.Add(streamDetails);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
@@ -37,7 +38,9 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
var clients = _downloadClientProvider.GetDownloadClients();
|
||||
// Only check clients not in failure status, those get another message
|
||||
var clients = _downloadClientProvider.GetDownloadClients(true);
|
||||
|
||||
var rootFolders = _rootFolderService.All();
|
||||
|
||||
foreach (var client in clients)
|
||||
@@ -58,6 +61,10 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unknown error occured in DownloadClientRootFolderCheck HealthCheck");
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.Clients;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.RemotePathMappings;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Core.ThingiProvider.Events;
|
||||
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
[CheckOn(typeof(ProviderUpdatedEvent<IDownloadClient>))]
|
||||
[CheckOn(typeof(ProviderDeletedEvent<IDownloadClient>))]
|
||||
[CheckOn(typeof(ModelEvent<RootFolder>))]
|
||||
[CheckOn(typeof(ModelEvent<RemotePathMapping>))]
|
||||
|
||||
public class DownloadClientSortingCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IProvideDownloadClient _downloadClientProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DownloadClientSortingCheck(IProvideDownloadClient downloadClientProvider,
|
||||
Logger logger,
|
||||
ILocalizationService localizationService)
|
||||
: base(localizationService)
|
||||
{
|
||||
_downloadClientProvider = downloadClientProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
var clients = _downloadClientProvider.GetDownloadClients();
|
||||
|
||||
foreach (var client in clients)
|
||||
{
|
||||
try
|
||||
{
|
||||
var clientName = client.Definition.Name;
|
||||
var status = client.GetStatus();
|
||||
|
||||
if (status.SortingMode.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("DownloadClientSortingCheckMessage"), clientName, status.SortingMode), "#download-folder-and-library-folder-not-different-folders");
|
||||
}
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unknown error occurred in DownloadClientSortingCheck HealthCheck");
|
||||
}
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
@@ -53,7 +54,8 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
|
||||
var clients = _downloadClientProvider.GetDownloadClients();
|
||||
// Only check clients not in failure status, those get another message
|
||||
var clients = _downloadClientProvider.GetDownloadClients(true);
|
||||
|
||||
foreach (var client in clients)
|
||||
{
|
||||
@@ -100,6 +102,10 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unknown error occured in RemotePathMapping HealthCheck");
|
||||
@@ -139,7 +145,14 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
|
||||
// If the previous case did not match then the failure occured in DownloadedMovieImportService,
|
||||
// while trying to locate the files reported by the download client
|
||||
var client = _downloadClientProvider.GetDownloadClients().FirstOrDefault(x => x.Definition.Name == failureMessage.DownloadClientInfo.Name);
|
||||
// Only check clients not in failure status, those get another message
|
||||
var client = _downloadClientProvider.GetDownloadClients(true).FirstOrDefault(x => x.Definition.Name == failureMessage.DownloadClientInfo.Name);
|
||||
|
||||
if (client == null)
|
||||
{
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var status = client.GetStatus();
|
||||
@@ -192,6 +205,10 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unknown error occured in RemotePathMapping HealthCheck");
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.CustomFormats;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Profiles;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
{
|
||||
public class CleanupQualityProfileFormatItems : IHousekeepingTask
|
||||
{
|
||||
private readonly IQualityProfileFormatItemsCleanupRepository _repository;
|
||||
private readonly ICustomFormatRepository _customFormatRepository;
|
||||
|
||||
public CleanupQualityProfileFormatItems(IQualityProfileFormatItemsCleanupRepository repository,
|
||||
ICustomFormatRepository customFormatRepository)
|
||||
{
|
||||
_repository = repository;
|
||||
_customFormatRepository = customFormatRepository;
|
||||
}
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var customFormats = _customFormatRepository.All().ToDictionary(c => c.Id);
|
||||
var profiles = _repository.All();
|
||||
var updatedProfiles = new List<Profile>();
|
||||
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
var formatItems = new List<ProfileFormatItem>();
|
||||
|
||||
// Make sure the profile doesn't include formats that have been removed
|
||||
profile.FormatItems.ForEach(p =>
|
||||
{
|
||||
if (p.Format != null && customFormats.ContainsKey(p.Format.Id))
|
||||
{
|
||||
formatItems.Add(p);
|
||||
}
|
||||
});
|
||||
|
||||
// Make sure the profile includes all available formats
|
||||
foreach (var customFormat in customFormats)
|
||||
{
|
||||
if (formatItems.None(f => f.Format.Id == customFormat.Key))
|
||||
{
|
||||
formatItems.Insert(0, new ProfileFormatItem
|
||||
{
|
||||
Format = customFormat.Value,
|
||||
Score = 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var previousIds = profile.FormatItems.Select(i => i.Format.Id).ToList();
|
||||
var ids = formatItems.Select(i => i.Format.Id).ToList();
|
||||
|
||||
// Update the profile if any formats were added or removed
|
||||
if (ids.Except(previousIds).Any() || previousIds.Except(ids).Any())
|
||||
{
|
||||
profile.FormatItems = formatItems;
|
||||
|
||||
if (profile.FormatItems.Empty())
|
||||
{
|
||||
profile.MinFormatScore = 0;
|
||||
profile.CutoffFormatScore = 0;
|
||||
}
|
||||
|
||||
updatedProfiles.Add(profile);
|
||||
}
|
||||
}
|
||||
|
||||
if (updatedProfiles.Any())
|
||||
{
|
||||
_repository.SetFields(updatedProfiles, p => p.FormatItems, p => p.MinFormatScore, p => p.CutoffFormatScore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IQualityProfileFormatItemsCleanupRepository : IBasicRepository<Profile>
|
||||
{
|
||||
}
|
||||
|
||||
public class QualityProfileFormatItemsCleanupRepository : BasicRepository<Profile>, IQualityProfileFormatItemsCleanupRepository
|
||||
{
|
||||
public QualityProfileFormatItemsCleanupRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||
: base(database, eventAggregator)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.List
|
||||
var listName = Parser.Parser.ToUrlSlug(Settings.Listname.Trim(), true, "-", "-");
|
||||
link += $"users/{Settings.Username.Trim()}/lists/{listName}/items/movies?limit={Settings.Limit}";
|
||||
|
||||
var request = new ImportListRequest(_traktProxy.BuildTraktRequest(link, HttpMethod.Get, Settings.AccessToken));
|
||||
var request = new ImportListRequest(_traktProxy.BuildRequest(link, HttpMethod.Get, Settings.AccessToken));
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.ImportLists.ImportListMovies;
|
||||
using NzbDrone.Core.Notifications.Trakt.Resource;
|
||||
|
||||
@@ -31,11 +31,11 @@ namespace NzbDrone.Core.ImportLists.Trakt.Popular
|
||||
|
||||
if (_settings.TraktListType == (int)TraktPopularListType.Popular)
|
||||
{
|
||||
jsonResponse = JsonConvert.DeserializeObject<List<TraktMovieResource>>(_importResponse.Content);
|
||||
jsonResponse = STJson.Deserialize<List<TraktMovieResource>>(_importResponse.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonResponse = JsonConvert.DeserializeObject<List<TraktListResource>>(_importResponse.Content).SelectList(c => c.Movie);
|
||||
jsonResponse = STJson.Deserialize<List<TraktListResource>>(_importResponse.Content).SelectList(c => c.Movie);
|
||||
}
|
||||
|
||||
// no movies were return
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.Popular
|
||||
|
||||
link += filtersAndLimit;
|
||||
|
||||
var request = new ImportListRequest(_traktProxy.BuildTraktRequest(link, HttpMethod.Get, Settings.AccessToken));
|
||||
var request = new ImportListRequest(_traktProxy.BuildRequest(link, HttpMethod.Get, Settings.AccessToken));
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.ImportLists.Exceptions;
|
||||
using NzbDrone.Core.ImportLists.ImportListMovies;
|
||||
using NzbDrone.Core.Notifications.Trakt.Resource;
|
||||
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.ImportLists.Trakt
|
||||
return movies;
|
||||
}
|
||||
|
||||
var jsonResponse = JsonConvert.DeserializeObject<List<TraktListResource>>(_importResponse.Content);
|
||||
var jsonResponse = STJson.Deserialize<List<TraktListResource>>(_importResponse.Content);
|
||||
|
||||
// no movies were return
|
||||
if (jsonResponse == null)
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.User
|
||||
break;
|
||||
}
|
||||
|
||||
var request = new ImportListRequest(_traktProxy.BuildTraktRequest(link, HttpMethod.Get, Settings.AccessToken));
|
||||
var request = new ImportListRequest(_traktProxy.BuildRequest(link, HttpMethod.Get, Settings.AccessToken));
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
yield return GetDefinition("NZBFinder.ws", GetSettings("https://nzbfinder.ws", categories: new[] { 2030, 2040, 2045, 2050, 2060, 2070, 2080, 2090 }));
|
||||
yield return GetDefinition("NZBgeek", GetSettings("https://api.nzbgeek.info"));
|
||||
yield return GetDefinition("nzbplanet.net", GetSettings("https://api.nzbplanet.net"));
|
||||
yield return GetDefinition("omgwtfnzbs", GetSettings("https://api.omgwtfnzbs.me", categories: new[] { 2000, 2020, 2030, 2040, 2045, 2050, 2070 }));
|
||||
yield return GetDefinition("OZnzb.com", GetSettings("https://api.oznzb.com"));
|
||||
yield return GetDefinition("SimplyNZBs", GetSettings("https://simplynzbs.com"));
|
||||
yield return GetDefinition("Tabula Rasa", GetSettings("https://www.tabula-rasa.pw", apiPath: @"/api/v1/api"));
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Parser;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Omgwtfnzbs
|
||||
{
|
||||
public class Omgwtfnzbs : HttpIndexerBase<OmgwtfnzbsSettings>
|
||||
{
|
||||
public override string Name => "omgwtfnzbs";
|
||||
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
|
||||
|
||||
public Omgwtfnzbs(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
|
||||
: base(httpClient, indexerStatusService, configService, parsingService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new OmgwtfnzbsRequestGenerator() { Settings = Settings };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new OmgwtfnzbsRssParser();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Omgwtfnzbs
|
||||
{
|
||||
public class OmgwtfnzbsRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public string BaseUrl { get; set; }
|
||||
public OmgwtfnzbsSettings Settings { get; set; }
|
||||
|
||||
public OmgwtfnzbsRequestGenerator()
|
||||
{
|
||||
BaseUrl = "https://rss.omgwtfnzbs.me/rss-download.php";
|
||||
}
|
||||
|
||||
public virtual IndexerPageableRequestChain GetRecentRequests()
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(null));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
foreach (var queryTitle in searchCriteria.CleanSceneTitles)
|
||||
{
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}",
|
||||
queryTitle)));
|
||||
}
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string query)
|
||||
{
|
||||
var url = new StringBuilder();
|
||||
url.AppendFormat("{0}?catid=15,16,17,18,31,35&user={1}&api={2}&eng=1&delay={3}", BaseUrl, Settings.Username, Settings.ApiKey, Settings.Delay);
|
||||
|
||||
if (query.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
url = url.Replace("rss-download.php", "rss-search.php");
|
||||
url.AppendFormat("&search={0}", query);
|
||||
}
|
||||
|
||||
yield return new IndexerRequest(url.ToString(), HttpAccept.Rss);
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Omgwtfnzbs
|
||||
{
|
||||
public class OmgwtfnzbsRssParser : RssParser
|
||||
{
|
||||
public OmgwtfnzbsRssParser()
|
||||
{
|
||||
UseEnclosureUrl = true;
|
||||
UseEnclosureLength = true;
|
||||
}
|
||||
|
||||
protected override bool PreProcess(IndexerResponse indexerResponse)
|
||||
{
|
||||
var xdoc = LoadXmlDocument(indexerResponse);
|
||||
var notice = xdoc.Descendants("notice").FirstOrDefault();
|
||||
|
||||
if (notice == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!notice.Value.ContainsIgnoreCase("api"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new ApiKeyException(notice.Value);
|
||||
}
|
||||
|
||||
protected override string GetInfoUrl(XElement item)
|
||||
{
|
||||
// Todo: Me thinks I need to parse details to get this...
|
||||
var match = Regex.Match(item.Description(),
|
||||
@"(?:\<b\>View NZB\:\<\/b\>\s\<a\shref\=\"")(?<URL>.+?)(?:\"")",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
return match.Groups["URL"].Value;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Omgwtfnzbs
|
||||
{
|
||||
public class OmgwtfnzbsSettingsValidator : AbstractValidator<OmgwtfnzbsSettings>
|
||||
{
|
||||
public OmgwtfnzbsSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.Username).NotEmpty();
|
||||
RuleFor(c => c.ApiKey).NotEmpty();
|
||||
RuleFor(c => c.Delay).GreaterThanOrEqualTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
public class OmgwtfnzbsSettings : IIndexerSettings
|
||||
{
|
||||
private static readonly OmgwtfnzbsSettingsValidator Validator = new OmgwtfnzbsSettingsValidator();
|
||||
|
||||
public OmgwtfnzbsSettings()
|
||||
{
|
||||
Delay = 30;
|
||||
MultiLanguages = new List<int>();
|
||||
}
|
||||
|
||||
// Unused since Omg has a hardcoded url.
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(0, Label = "Username", Privacy = PrivacyLevel.UserName)]
|
||||
public string Username { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Delay", HelpText = "Time in minutes to delay new nzbs before they appear on the RSS feed", Advanced = true)]
|
||||
public int Delay { get; set; }
|
||||
|
||||
[FieldDefinition(3, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
|
||||
public IEnumerable<int> MultiLanguages { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,7 @@
|
||||
{}
|
||||
{
|
||||
"About": "সম্পর্কিত",
|
||||
"AcceptConfirmationModal": "নিশ্চিতকরণ মডেল গ্রহণ করুন",
|
||||
"Actions": "ক্রিয়াকাণ্ড",
|
||||
"Activity": "কার্যকলাপ",
|
||||
"Add": "যোগ করুন"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"KeyboardShortcuts": "Keyboard Genveje",
|
||||
"Info": "Information",
|
||||
"IndexerStatusCheckSingleClientMessage": "Indexere utilgængelige på grund af fejl: {0}",
|
||||
"IndexerStatusCheckAllClientMessage": "Alle indexere er utilgængelige på grund af fejl",
|
||||
"IndexerStatusCheckAllClientMessage": "Alle indeksører er utilgængelige på grund af fejl",
|
||||
"IndexersSettingsSummary": "Indexer og ugivelses restriktioner",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Ingen indexere er tilgængelige med Interaktiv Søg aktiveret, Radarr vil ikke give nogle interaktive søge resultater",
|
||||
"IndexerSearchCheckNoAvailableIndexersMessage": "Alle søge-mulige indexere er midlertidigt utilgængelige på grund af nylige indexer fejl",
|
||||
@@ -89,7 +89,7 @@
|
||||
"AudioInfo": "Lyd Info",
|
||||
"Apply": "Anvend",
|
||||
"AppDataLocationHealthCheckMessage": "Opdatering vil ikke være muligt for at undgå at slette AppData under opdatering",
|
||||
"Analytics": "Analyser",
|
||||
"Analytics": "Analyse",
|
||||
"AlternativeTitle": "Alternativ Titel",
|
||||
"AllMoviesHiddenDueToFilter": "Alle film er gemt på grund af aktivt filter.",
|
||||
"All": "Alt",
|
||||
@@ -140,7 +140,7 @@
|
||||
"IncludeUnknownMovieItemsHelpText": "Vis emner uden en film i køen. Dette kan omfatte fjernede film eller andet i Radarrs kategori",
|
||||
"IndexerPriorityHelpText": "Indekseringsprioritet fra 1 (højest) til 50 (lavest). Standard: 25.",
|
||||
"LogLevelTraceHelpTextWarning": "Sporlogning bør kun aktiveres midlertidigt",
|
||||
"MappedDrivesRunningAsService": "Kortlagte netværksdrev er ikke tilgængelige, når de kører som en Windows-tjeneste. Se FAQ for mere information",
|
||||
"MappedDrivesRunningAsService": "Tilsluttede netværksdrev er ikke tilgængelige, når programmet kører som en Windows-tjeneste. Se FAQ'en for mere information",
|
||||
"MassMovieSearch": "Massefilmsøgning",
|
||||
"MIA": "MIA",
|
||||
"MonitoredOnly": "Kun overvåget",
|
||||
@@ -170,7 +170,7 @@
|
||||
"UnableToLoadHistory": "Kunne ikke indlæse historikken",
|
||||
"UnableToLoadMediaManagementSettings": "Kan ikke indlæse indstillinger for mediestyring",
|
||||
"UnableToUpdateRadarrDirectly": "Kan ikke opdatere Radarr direkte,",
|
||||
"BindAddressHelpText": "Gyldig IP4-adresse eller '*' for alle grænseflader",
|
||||
"BindAddressHelpText": "Gyldig IP4-adresse, 'localhost' eller '*' for alle grænseflader",
|
||||
"CreateEmptyMovieFoldersHelpText": "Opret manglende filmmapper under diskscanning",
|
||||
"CouldNotConnectSignalR": "Kunne ikke oprette forbindelse til SignalR, UI opdateres ikke",
|
||||
"Restrictions": "Begrænsninger",
|
||||
@@ -212,7 +212,7 @@
|
||||
"ChangeHasNotBeenSavedYet": "Ændring er endnu ikke gemt",
|
||||
"CheckDownloadClientForDetails": "tjek download klient for flere detaljer",
|
||||
"CheckForFinishedDownloadsInterval": "Kontroller for færdige downloadsinterval",
|
||||
"AddIndexer": "Tilføj indexer",
|
||||
"AddIndexer": "Tilføj indeksør",
|
||||
"ChmodFolder": "chmod mappe",
|
||||
"ChmodFolderHelpText": "Oktal, anvendt under import / omdøbning til mediemapper og filer (uden udførelse af bits)",
|
||||
"ChmodFolderHelpTextWarning": "Dette fungerer kun, hvis den bruger, der kører Radarr, er ejeren af filen. Det er bedre at sikre, at downloadklienten indstiller tilladelserne korrekt.",
|
||||
@@ -275,8 +275,8 @@
|
||||
"ImportFailedInterp": "Import mislykkedes: {0}",
|
||||
"IllRestartLater": "Jeg genstarter senere",
|
||||
"InvalidFormat": "Ugyldigt format",
|
||||
"LastDuration": "lastDuration",
|
||||
"LastExecution": "Sidste henrettelse",
|
||||
"LastDuration": "Seneste varighed",
|
||||
"LastExecution": "Seneste udførelse",
|
||||
"ListSyncLevelHelpTextWarning": "Filmfiler slettes permanent, dette kan resultere i at slette dit bibliotek, hvis dine lister er tomme",
|
||||
"ListTagsHelpText": "Elementerne på tags-listen tilføjes med",
|
||||
"MinAvailability": "Min tilgængelighed",
|
||||
@@ -309,10 +309,10 @@
|
||||
"ImportIncludeQuality": "Sørg for, at dine filer inkluderer kvaliteten i deres filnavne. f.eks. {0}",
|
||||
"ImportListSyncIntervalHelpText": "Hvor ofte synkroniserer Radarr med dine lister. Minimumsværdi på 6 timer",
|
||||
"IncludeHealthWarningsHelpText": "Inkluder sundhedsadvarsler",
|
||||
"Max": "Maks",
|
||||
"Max": "Maks.",
|
||||
"Medium": "Medium",
|
||||
"MovieFilesTotaling": "Totale filmfiler",
|
||||
"OnGrab": "On Grab",
|
||||
"OnGrab": "ved hentning",
|
||||
"OnHealthIssue": "Om sundhedsspørgsmål",
|
||||
"OnImport": "Ved import",
|
||||
"OnLatestVersion": "Den seneste version af Radarr er allerede installeret",
|
||||
@@ -407,7 +407,7 @@
|
||||
"MovieIsMonitored": "Film overvåges",
|
||||
"MovieIsUnmonitored": "Filmen overvåges ikke",
|
||||
"Movies": "Film",
|
||||
"NetCore": ".NET Core",
|
||||
"NetCore": ".NET",
|
||||
"OnHealthIssueHelpText": "Om sundhedsspørgsmål",
|
||||
"OnRenameHelpText": "Om omdøb",
|
||||
"OnUpgradeHelpText": "Ved opgradering",
|
||||
@@ -563,7 +563,7 @@
|
||||
"DestinationPath": "Destinationssti",
|
||||
"DestinationRelativePath": "Destinationsrelateret sti",
|
||||
"DetailedProgressBar": "Detaljeret statuslinje",
|
||||
"Discord": "Uenighed",
|
||||
"Discord": "Discord",
|
||||
"Docker": "Docker",
|
||||
"Donations": "Donationer",
|
||||
"DoneEditingGroups": "Udført redigering af grupper",
|
||||
@@ -689,7 +689,7 @@
|
||||
"ManualImportSelectMovie": "Manuel import - Vælg film",
|
||||
"ManualImportSelectQuality": " Manuel import - Vælg kvalitet",
|
||||
"MarkAsFailedMessageText": "Er du sikker på, at du vil markere '{0}' som mislykket?",
|
||||
"MaximumLimits": "Maksimale grænser",
|
||||
"MaximumLimits": "Maksimumgrænser",
|
||||
"MaximumSize": "Maksimal størrelse",
|
||||
"MegabytesPerMinute": "Megabyte pr. Minut",
|
||||
"Message": "Besked",
|
||||
@@ -951,7 +951,7 @@
|
||||
"UnableToLoadDownloadClients": "Kunne ikke indlæse downloadklienter",
|
||||
"UnableToLoadGeneralSettings": "Kan ikke indlæse generelle indstillinger",
|
||||
"UnableToLoadIndexerOptions": "Kan ikke indlæse indekseringsindstillinger",
|
||||
"UnableToLoadIndexers": "Kan ikke indlæse indeksatorer",
|
||||
"UnableToLoadIndexers": "Kan ikke indlæse indeksørerne",
|
||||
"UnableToLoadLanguages": "Sprog kunne ikke indlæses",
|
||||
"UnableToLoadListExclusions": "Kunne ikke indlæse listeekskluderinger",
|
||||
"UnableToLoadListOptions": "Kan ikke indlæse listeindstillinger",
|
||||
|
||||
@@ -329,7 +329,7 @@
|
||||
"BackupRetentionHelpText": "Automatische Backups, die älter als die Aufbewahrungsfrist sind, werden automatisch gelöscht",
|
||||
"Backups": "Backups",
|
||||
"BindAddress": "Adresse binden",
|
||||
"BindAddressHelpText": "Gültige IPv4 Adresse oder \"*\" für alle Netzwerke",
|
||||
"BindAddressHelpText": "Gültige IP Adresse oder \"*\" für alle Netzwerke",
|
||||
"Branch": "Git-Branch",
|
||||
"BypassProxyForLocalAddresses": "Proxy für lokale Adressen umgehen",
|
||||
"CertificateValidation": "Zertifikat Validierung",
|
||||
@@ -979,7 +979,7 @@
|
||||
"EditCustomFormat": "Eigenes Format bearbeiten",
|
||||
"DoNotUpgradeAutomatically": "Nicht automatisch upgraden",
|
||||
"DoNotPrefer": "Nicht bevorzugen",
|
||||
"DoneEditingGroups": "Gruppen bearbeiten abgechlossen",
|
||||
"DoneEditingGroups": "Editieren der Gruppen abschließen",
|
||||
"Donations": "Spenden",
|
||||
"DockerUpdater": "aktualisiere den Docker Container um das Update zu erhalten",
|
||||
"Discord": "Discord",
|
||||
@@ -1150,8 +1150,10 @@
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Sicher, dass die Qualitätsdefinitionen zurückgesetzt werden sollen?",
|
||||
"ResetDefinitions": "Definitionen zurücksetzen",
|
||||
"ResetQualityDefinitions": "Qualitätsdefinitionen zurücksetzen",
|
||||
"SettingsThemeHelpText": "Anwendungsdesign ändern, inspiriert von Theme.Park",
|
||||
"SettingsThemeHelpText": "Anwendungsdesign ändern, das 'Auto' Design passt sich an den Light/Dark-Mode deines Systems an. Inspiriert von Theme.Park",
|
||||
"ResetTitles": "Titel zurücksetzen",
|
||||
"ResetTitlesHelpText": "Definitionstitel und Werte zurücksetzen",
|
||||
"SettingsTheme": "Design"
|
||||
"SettingsTheme": "Design",
|
||||
"RSSHelpText": "Wird benutzt, wenn Radarr mittels RSS-Sync regelmäßig nach Releases schaut",
|
||||
"DownloadClientSortingCheckMessage": "Downloader {0} hat die {1} Sortierung für Radarrs Kategorie aktiviert. Dies sollte deaktiviert werden, um Import-Probleme zu vermeiden."
|
||||
}
|
||||
|
||||
@@ -57,11 +57,11 @@
|
||||
"DownloadClientStatusCheckAllClientMessage": "Όλα τα προγράμματα λήψης είναι μη διαθέσιμα λόγων αποτυχιών",
|
||||
"DownloadClientsSettingsSummary": "Προγράμματα λήψης, διαχείριση λήψεων και αντιστοίχηση remote path",
|
||||
"DelayProfiles": "Προφίλ χρονοκαθυστέρησης",
|
||||
"CustomFormatsSettingsSummary": "Custom Formats και Ρυθμίσεις",
|
||||
"CustomFormats": "Custom Formats",
|
||||
"CustomFormatsSettingsSummary": "Προσαρμοσμένες Μορφές και Ρυθμίσεις",
|
||||
"CustomFormats": "Προσαρμοσμένες Μορφές",
|
||||
"CustomFilters": "Custom Φιλτρα",
|
||||
"Crew": "Ομάδα",
|
||||
"ConnectSettingsSummary": "Ειδοποιήσεις, συνδέσεις σε media servers/players και custom scripts",
|
||||
"ConnectSettingsSummary": "Ειδοποιήσεις, συνδέσεις με διακομιστές πολυμέσων/προγράμματα αναπαραγωγής και προσαρμοσμένα σενάρια",
|
||||
"AppDataLocationHealthCheckMessage": "Η αναβάθμιση δεν είναι πιθανό να αποτρέψει την διαγραφή των AppData κατά την αναβάθμιση",
|
||||
"AddNewTmdbIdMessage": "Μπορείτε επίσης να ψάξετε μέσω του TMDB Id της ταινίας. π.χ. tmdb:71663",
|
||||
"AddNewMessage": "Είναι εύκολο να προσθέσετε μια καινούρια ταινία, απλά πληκτρολογήστε το όνομα της ταινίας",
|
||||
@@ -73,7 +73,7 @@
|
||||
"AudioInfo": "Στοιχεία ήχου",
|
||||
"Apply": "Εφαρμογή",
|
||||
"AlternativeTitle": "Εναλακτικός Τίτλος",
|
||||
"AllMoviesHiddenDueToFilter": "Όλες οι ταινίες έχουν κρυφτεί λόγω εφαρμογής φίλτρου",
|
||||
"AllMoviesHiddenDueToFilter": "Όλες οι ταινίες έχουν κρυφτεί λόγω εφαρμογής φίλτρου.",
|
||||
"Age": "Ηλικία",
|
||||
"AddNewMovie": "Προσθήκη Νέας Ταινίας",
|
||||
"AddList": "Προσθήκη Λίστας",
|
||||
@@ -189,7 +189,7 @@
|
||||
"AutoUnmonitorPreviouslyDownloadedMoviesHelpText": "Οι ταινίες που διαγράφονται από το δίσκο δεν παρακολουθούνται αυτόματα στο Radarr",
|
||||
"AvailabilityDelayHelpText": "Ποσό χρόνου πριν ή μετά τη διαθέσιμη ημερομηνία για αναζήτηση ταινίας",
|
||||
"BindAddress": "Δεσμευμένη διεύθυνση",
|
||||
"BindAddressHelpText": "Έγκυρη διεύθυνση IP4 ή «*» για όλες τις διεπαφές",
|
||||
"BindAddressHelpText": "Έγκυρη διεύθυνση IP, localhost ή '*' για όλες τις διεπαφές",
|
||||
"Branch": "Κλαδί",
|
||||
"ChangeHasNotBeenSavedYet": "Η αλλαγή δεν έχει αποθηκευτεί ακόμα",
|
||||
"CheckDownloadClientForDetails": "ελέγξτε το πρόγραμμα-πελάτη λήψης για περισσότερες λεπτομέρειες",
|
||||
@@ -205,7 +205,7 @@
|
||||
"CancelProcessing": "Ακύρωση επεξεργασίας",
|
||||
"CantFindMovie": "Γιατί δεν μπορώ να βρω την ταινία μου;",
|
||||
"CertificateValidation": "Επικύρωση πιστοποιητικού",
|
||||
"CertificateValidationHelpText": "Αλλάξτε πόσο αυστηρή είναι η επικύρωση πιστοποίησης HTTPS",
|
||||
"CertificateValidationHelpText": "Αλλάξτε πόσο αυστηρή είναι η επικύρωση πιστοποίησης HTTPS.",
|
||||
"CleanLibraryLevel": "Καθαρό επίπεδο βιβλιοθήκης",
|
||||
"ChmodFolderHelpTextWarning": "Αυτό λειτουργεί μόνο εάν ο χρήστης που εκτελεί το Radarr είναι ο κάτοχος του αρχείου. Είναι καλύτερο να διασφαλίσετε ότι ο πελάτης λήψης ορίζει σωστά τα δικαιώματα.",
|
||||
"DeleteBackup": "Διαγραφή αντιγράφων ασφαλείας",
|
||||
@@ -266,7 +266,7 @@
|
||||
"ImportMechanismHealthCheckMessage": "Ενεργοποίηση ολοκληρωμένου χειρισμού λήψεων",
|
||||
"IncludeHealthWarningsHelpText": "Συμπεριλάβετε προειδοποιήσεις για την υγεία",
|
||||
"InvalidFormat": "Ακυρη μορφή",
|
||||
"LastDuration": "τελευταία Διάρκεια",
|
||||
"LastDuration": "Τελευταία Διάρκεια",
|
||||
"LastExecution": "Τελευταία εκτέλεση",
|
||||
"ListSyncLevelHelpTextWarning": "Τα αρχεία ταινιών θα διαγραφούν οριστικά. Αυτό μπορεί να οδηγήσει σε διαγραφή της βιβλιοθήκης σας εάν οι λίστες σας είναι κενές",
|
||||
"Max": "Μέγιστη",
|
||||
@@ -276,7 +276,7 @@
|
||||
"MinimumAge": "Ελάχιστη ηλικία",
|
||||
"MinimumFreeSpace": "Ελάχιστος ελεύθερος χώρος",
|
||||
"Minutes": "Λεπτά",
|
||||
"MissingFromDisk": "Ο Radarr δεν μπόρεσε να βρει το αρχείο στο δίσκο και έτσι καταργήθηκε",
|
||||
"MissingFromDisk": "Το Radarr δεν μπόρεσε να βρει το αρχείο στο δίσκο και έτσι αφαιρέθηκε το αρχείο αποσυνδέθηκε από την ταινία στη βάση δεδομένων",
|
||||
"Monday": "Δευτέρα",
|
||||
"MoveFiles": "Μετακίνηση αρχείων",
|
||||
"MovieFilesTotaling": "Συνολικά αρχεία ταινιών",
|
||||
@@ -318,8 +318,8 @@
|
||||
"PriorityHelpText": "Προτεραιότητα πολλαπλών πελατών λήψης. Το Round-Robin χρησιμοποιείται για πελάτες με την ίδια προτεραιότητα.",
|
||||
"Profiles": "Προφίλ",
|
||||
"ProxyType": "Τύπος διακομιστή μεσολάβησης",
|
||||
"QualitiesHelpText": "Προτιμώνται οι υψηλότερες ποιότητες στη λίστα. Οι ιδιότητες εντός της ίδιας ομάδας είναι ίδιες. Απαιτούνται μόνο ελεγμένες ιδιότητες",
|
||||
"QualityProfileInUse": "Δεν είναι δυνατή η διαγραφή ενός ποιοτικού προφίλ που είναι συνημμένο σε μια ταινία",
|
||||
"QualitiesHelpText": "Ποιότητες υψηλότερες στη λίστα προτιμώνται περισσότερο, ακόμη και αν δεν έχουν επιλεγεί. Οι ιδιότητες στην ίδια ομάδα είναι ίσες. Ζητούνται μόνο ελεγμένες ιδιότητες",
|
||||
"QualityProfileInUse": "Δεν είναι δυνατή η διαγραφή ενός προφίλ ποιότητας που είναι συνδεδεμένο σε ταινία, λίστα ή συλλογή",
|
||||
"QualitySettings": "Ρυθμίσεις ποιότητας",
|
||||
"QueueIsEmpty": "Η ουρά είναι κενή",
|
||||
"RadarrCalendarFeed": "Ροή ημερολογίου Radarr",
|
||||
@@ -414,7 +414,7 @@
|
||||
"MustContain": "Πρέπει να περιέχει",
|
||||
"MustNotContain": "Δεν πρέπει να περιέχει",
|
||||
"NamingSettings": "Ρυθμίσεις ονομάτων",
|
||||
"NetCore": ".NET Core",
|
||||
"NetCore": ".NET",
|
||||
"NoBackupsAreAvailable": "Δεν υπάρχουν διαθέσιμα αντίγραφα ασφαλείας",
|
||||
"NoChange": "Καμία αλλαγή",
|
||||
"NoHistory": "Χωρίς ιστορία",
|
||||
@@ -457,8 +457,8 @@
|
||||
"AllowHardcodedSubsHelpText": "Οι ανιχνευόμενοι κωδικοποιημένοι συνδρομητές θα ληφθούν αυτόματα",
|
||||
"QualityProfile": "Ποιοτικό προφίλ",
|
||||
"QualityProfiles": "Προφίλ ποιότητας",
|
||||
"QuickImport": "Γρήγορη εισαγωγή",
|
||||
"RadarrSupportsAnyDownloadClient": "Το Radarr υποστηρίζει οποιονδήποτε πελάτη λήψης που χρησιμοποιεί το πρότυπο Newznab, καθώς και άλλους πελάτες λήψης που αναφέρονται παρακάτω.",
|
||||
"QuickImport": "Αυτόματη Μετακίνησή",
|
||||
"RadarrSupportsAnyDownloadClient": "Το Radarr υποστηρίζει πολλούς δημοφιλείς πελάτες λήψης torrent και usenet.",
|
||||
"AlreadyInYourLibrary": "Ήδη στη βιβλιοθήκη σας",
|
||||
"RecycleBinCleanupDaysHelpTextWarning": "Τα αρχεία στον κάδο ανακύκλωσης παλαιότερα από τον επιλεγμένο αριθμό ημερών θα καθαρίζονται αυτόματα",
|
||||
"RecycleBinHelpText": "Τα αρχεία ταινιών θα μεταβούν εδώ όταν διαγραφούν αντί να διαγραφούν οριστικά",
|
||||
@@ -641,7 +641,7 @@
|
||||
"IncludeUnmonitored": "Συμπερίληψη χωρίς παρακολούθηση",
|
||||
"ImportMovies": "Εισαγωγή ταινιών",
|
||||
"IndexerPriority": "Προτεραιότητα ευρετηρίου",
|
||||
"IndexerPriorityHelpText": "Προτεραιότητα ευρετηρίου από 1 (Υψηλότερη) έως 50 (Χαμηλότερη). Προεπιλογή: 25.",
|
||||
"IndexerPriorityHelpText": "Προτεραιότητα δείκτη από 1 (υψηλότερη) έως 50 (χαμηλότερη). Προεπιλογή: 25. Χρησιμοποιείται κατά την κατάκτηση εκδόσεων ως ισοπαλία για ίσες εκδόσεις, το Radarr θα εξακολουθεί να χρησιμοποιεί όλα τα ενεργοποιημένα ευρετήρια για RSS Sync και Search",
|
||||
"IndexerRssHealthCheckNoAvailableIndexers": "Όλοι οι δείκτες με δυνατότητα rss δεν είναι διαθέσιμοι προσωρινά λόγω πρόσφατων σφαλμάτων ευρετηρίου",
|
||||
"IndexerRssHealthCheckNoIndexers": "Δεν υπάρχουν διαθέσιμα ευρετήρια με ενεργοποιημένο τον συγχρονισμό RSS, το Radarr δεν θα τραβήξει αυτόματα νέες κυκλοφορίες",
|
||||
"Indexers": "Ευρετήρια",
|
||||
@@ -824,7 +824,7 @@
|
||||
"SearchMissing": "Λείπει η αναζήτηση",
|
||||
"SearchMovie": "Αναζήτηση ταινίας",
|
||||
"SearchOnAdd": "Αναζήτηση στο Προσθήκη",
|
||||
"SearchOnAddHelpText": "Αναζητήστε ταινίες σε αυτήν τη λίστα όταν προστεθείτε στο Radarr",
|
||||
"SearchOnAddHelpText": "Αναζητήστε ταινίες σε αυτήν τη λίστα όταν προστεθούν στη βιβλιοθήκη",
|
||||
"SearchSelected": "Επιλεγμένη αναζήτηση",
|
||||
"Seconds": "Δευτερόλεπτα",
|
||||
"Security": "Ασφάλεια",
|
||||
@@ -854,7 +854,7 @@
|
||||
"SettingsTimeFormat": "Μορφή ώρας",
|
||||
"SettingsWeekColumnHeader": "Κεφαλίδα στήλης εβδομάδας",
|
||||
"SettingsWeekColumnHeaderHelpText": "Εμφανίζεται πάνω από κάθε στήλη όταν η εβδομάδα είναι η ενεργή προβολή",
|
||||
"ShouldMonitorHelpText": "Εάν ενεργοποιηθεί, προστίθενται και παρακολουθούνται ταινίες που προστίθενται από αυτήν τη λίστα",
|
||||
"ShouldMonitorHelpText": "Εάν οι ταινίες ή οι συλλογές που προστίθενται από αυτήν τη λίστα πρέπει να προστεθούν υπό παρακολούθηση",
|
||||
"ShowAdvanced": "Εμφάνιση για προχωρημένους",
|
||||
"ShowAsAllDayEvents": "Εμφάνιση ως ολοήμερων εκδηλώσεων",
|
||||
"ShowCutoffUnmetIconHelpText": "Εμφάνιση εικονιδίου για αρχεία όταν δεν έχει τηρηθεί το όριο",
|
||||
@@ -912,7 +912,7 @@
|
||||
"Tasks": "Καθήκοντα",
|
||||
"Test": "Δοκιμή",
|
||||
"TestAll": "Δοκιμάστε όλα",
|
||||
"ThisCannotBeCancelled": "Αυτό δεν μπορεί να ακυρωθεί μόλις ξεκινήσει χωρίς επανεκκίνηση του Radarr.",
|
||||
"ThisCannotBeCancelled": "Αυτό δεν μπορεί να ακυρωθεί μόλις ξεκινήσει η απενεργοποίηση όλων των ευρετηριωτών σας.",
|
||||
"Time": "χρόνος",
|
||||
"Title": "Τίτλος",
|
||||
"Titles": "Τίτλοι",
|
||||
@@ -989,7 +989,7 @@
|
||||
"UpgradeUntilCustomFormatScore": "Αναβάθμιση Μέχρι Βαθμολογία προσαρμοσμένης μορφής",
|
||||
"UpgradeUntilQuality": "Αναβάθμιση έως την ποιότητα",
|
||||
"UpgradeUntilThisQualityIsMetOrExceeded": "Αναβαθμίστε έως ότου ικανοποιηθεί ή ξεπεραστεί αυτή η ποιότητα",
|
||||
"UpperCase": "Άνω θήκη",
|
||||
"UpperCase": "Κεφαλαία",
|
||||
"URLBase": "Βάση διεύθυνσης URL",
|
||||
"UrlBaseHelpText": "Για αντίστροφη υποστήριξη διακομιστή μεσολάβησης, η προεπιλογή είναι άδεια",
|
||||
"UseHardlinksInsteadOfCopy": "Χρησιμοποιήστε Hardlinks αντί για Αντιγραφή",
|
||||
@@ -1058,9 +1058,102 @@
|
||||
"List": "Τόπος αγώνων",
|
||||
"Rating": "Ακροαματικότητα",
|
||||
"Filters": "Φίλτρο",
|
||||
"AllCollectionsHiddenDueToFilter": "Όλες οι ταινίες έχουν κρυφτεί λόγω εφαρμογής φίλτρου",
|
||||
"AllCollectionsHiddenDueToFilter": "Όλες οι ταινίες έχουν κρυφτεί λόγω εφαρμογής φίλτρου.",
|
||||
"Collections": "Συλλογή",
|
||||
"MonitorMovies": "Παρακολούθηση ταινίας",
|
||||
"NoCollections": "Δεν βρέθηκαν ταινίες, για να ξεκινήσετε θα θέλετε να προσθέσετε μια νέα ταινία ή να εισαγάγετε ορισμένες υπάρχουσες.",
|
||||
"RssSyncHelpText": "Διάστημα σε λεπτά. Ρυθμίστε στο μηδέν για απενεργοποίηση (αυτό θα σταματήσει όλες τις αυτόματες αρπάξεις απελευθέρωσης)"
|
||||
"NoCollections": "Δεν βρέθηκαν ταινίες, για να ξεκινήσετε θα θέλετε να προσθέσετε μια νέα ταινία ή να εισαγάγετε ορισμένες υπάρχουσες",
|
||||
"RssSyncHelpText": "Διάστημα σε λεπτά. Ρυθμίστε στο μηδέν για απενεργοποίηση (αυτό θα σταματήσει όλες τις αυτόματες αρπάξεις απελευθέρωσης)",
|
||||
"CollectionsSelectedInterp": "Επιλέχθηκαν {0} συλλογές",
|
||||
"MovieOnly": "Μόνο ταινία",
|
||||
"ChooseImportMode": "Επιλέξτε Λειτουργία εισαγωγής",
|
||||
"Duration": "Διάρκεια",
|
||||
"ImdbVotes": "Ψήφοι στο IMDb",
|
||||
"ImportListMultipleMissingRoots": "Λείπουν πολλοί ριζικοί φάκελοι για λίστες εισαγωγής: {0}",
|
||||
"MonitorCollection": "Συλλογή οθονών",
|
||||
"MonitoredCollectionHelpText": "Παρακολουθήστε για αυτόματη προσθήκη στη βιβλιοθήκη ταινιών από αυτήν τη συλλογή",
|
||||
"MovieCollectionMultipleMissingRoots": "Λείπουν πολλοί ριζικοί φάκελοι για τις συλλογές ταινιών: {0}",
|
||||
"IndexerJackettAll": "Ευρετήρια που χρησιμοποιούν το μη υποστηριζόμενο τελικό σημείο Jackett 'all': {0}",
|
||||
"AnnouncedMsg": "Η ταινία έχει ανακοινωθεί",
|
||||
"Letterboxd": "Γραμματοκιβώτιο",
|
||||
"EditCollection": "Επεξεργασία συλλογής",
|
||||
"ImdbRating": "Αξιολόγηση στο IMDb",
|
||||
"InstanceNameHelpText": "Όνομα παρουσίας στην καρτέλα και για όνομα εφαρμογής Syslog",
|
||||
"MovieCollectionMissingRoot": "Λείπει ο ριζικός φάκελος για τη συλλογή ταινιών: {0}",
|
||||
"OnApplicationUpdate": "Στην ενημέρωση της εφαρμογής",
|
||||
"OnApplicationUpdateHelpText": "Στην ενημέρωση της εφαρμογής",
|
||||
"OnMovieAdded": "Προστέθηκε στην ταινία",
|
||||
"OnMovieAddedHelpText": "Προστέθηκε στην ταινία",
|
||||
"ApplicationUrlHelpText": "Το εξωτερικό URL αυτής της εφαρμογής, συμπεριλαμβανομένων των http(s)://, της θύρας και της βάσης URL",
|
||||
"Auto": "Αυτόματο",
|
||||
"BlocklistHelpText": "Αποτρέπει το Radarr από το να πάρει ξανά αυτόματα αυτήν την έκδοση",
|
||||
"BypassDelayIfHighestQualityHelpText": "Παράκαμψη καθυστέρησης όταν η έκδοση έχει την υψηλότερη ενεργοποιημένη ποιότητα στο προφίλ ποιότητας με το προτιμώμενο πρωτόκολλο",
|
||||
"ClickToChangeReleaseGroup": "Κάντε κλικ για να αλλάξετε την ομάδα κυκλοφορίας",
|
||||
"DiscordUrlInSlackNotification": "Έχετε μια ρύθμιση ειδοποίησης Discord ως ειδοποίηση Slack. Ρυθμίστε το ως ειδοποίηση Discord για καλύτερη λειτουργικότητα. Οι ειδοποιήσεις που πραγματοποιούνται είναι: {0}",
|
||||
"DownloadClientSortingCheckMessage": "Ο πελάτης λήψης {0} έχει ενεργοποιήσει την ταξινόμηση {1} για την κατηγορία του Radarr. Θα πρέπει να απενεργοποιήσετε την ταξινόμηση στο πρόγραμμα-πελάτη λήψης για να αποφύγετε προβλήματα εισαγωγής.",
|
||||
"ImportListMissingRoot": "Λείπει ο ριζικός φάκελος για λίστες εισαγωγής: {0}",
|
||||
"IndexerDownloadClientHelpText": "Καθορίστε ποιο πρόγραμμα-πελάτη λήψης χρησιμοποιείται για αρπαγές από αυτό το ευρετήριο",
|
||||
"ManualImportSetReleaseGroup": "Μη αυτόματη εισαγωγή - Ορισμός ομάδας απελευθέρωσης",
|
||||
"OriginalLanguage": "Γλώσσα Πρωτότυπου",
|
||||
"OriginalTitle": "Πρωτότυπος τίτλος",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Είστε βέβαιοι ότι θέλετε να επαναφέρετε τους ορισμούς ποιότητας;",
|
||||
"CollectionOptions": "Επιλογές συλλογής",
|
||||
"CollectionShowDetailsHelpText": "Εμφάνιση κατάστασης και ιδιοτήτων συλλογής",
|
||||
"CollectionShowOverviewsHelpText": "Εμφάνιση επισκοπήσεων συλλογής",
|
||||
"CollectionShowPostersHelpText": "Εμφάνιση αφισών αντικειμένων συλλογής",
|
||||
"Database": "Βάση δεδομένων",
|
||||
"IndexerTagHelpText": "Χρησιμοποιήστε αυτό το ευρετήριο μόνο για ταινίες με τουλάχιστον μία αντίστοιχη ετικέτα. Αφήστε το κενό για χρήση με όλες τις ταινίες.",
|
||||
"MovieAndCollection": "Ταινία και συλλογή",
|
||||
"ApplicationURL": "Διεύθυνση URL εφαρμογής",
|
||||
"Never": "Ποτέ",
|
||||
"NotificationTriggersHelpText": "Επιλέξτε ποια συμβάντα θα ενεργοποιήσουν αυτήν την ειδοποίηση",
|
||||
"InstanceName": "Όνομα παράδειγμα",
|
||||
"PreferredProtocol": "Προτιμώμενο πρωτόκολλο",
|
||||
"From": "απο",
|
||||
"BypassDelayIfHighestQuality": "Παράκαμψη εάν είναι στην υψηλότερη ποιότητα",
|
||||
"ShowPosters": "Εμφάνιση αφισών",
|
||||
"RemotePathMappingCheckFilesWrongOSPath": "Ο πελάτης απομακρυσμένης λήψης {0} ανέφερε αρχεία στο {1} αλλά αυτή δεν είναι έγκυρη διαδρομή {2}. Ελέγξτε τις απομακρυσμένες αντιστοιχίσεις διαδρομής και κατεβάστε τις ρυθμίσεις πελάτη.",
|
||||
"RemotePathMappingCheckLocalWrongOSPath": "Το πρόγραμμα-πελάτης τοπικής λήψης {0} τοποθετεί λήψεις στο {1} αλλά αυτή δεν είναι έγκυρη διαδρομή {2}. Ελέγξτε τις ρυθμίσεις του προγράμματος-πελάτη λήψης.",
|
||||
"RemoveCompleted": "Κατάργηση Ολοκληρώθηκε",
|
||||
"RemoveSelectedItems": "Αφαίρεση επιλεγμένων αντικειμένων",
|
||||
"TmdbRating": "Αξιολόγηση TMDb",
|
||||
"TmdbVotes": "Ψήφοι TMDb",
|
||||
"RefreshCollections": "Ανανέωση Συλλογών",
|
||||
"RemotePathMappingCheckFileRemoved": "Το αρχείο {0} καταργήθηκε εν μέρει κατά την επεξεργασία.",
|
||||
"RemotePathMappingCheckFolderPermissions": "Το Radarr μπορεί να δει αλλά δεν έχει πρόσβαση στον κατάλογο λήψεων {0}. Πιθανό σφάλμα αδειών.",
|
||||
"RemotePathMappingCheckGenericPermissions": "Το πρόγραμμα-πελάτης λήψης {0} τοποθετεί λήψεις στο {1} αλλά το Radarr δεν μπορεί να δει αυτόν τον κατάλογο. Ίσως χρειαστεί να προσαρμόσετε τα δικαιώματα του φακέλου.",
|
||||
"RemotePathMappingCheckRemoteDownloadClient": "Ο πελάτης απομακρυσμένης λήψης {0} ανέφερε αρχεία στο {1} αλλά αυτός ο κατάλογος δεν φαίνεται να υπάρχει. Πιθανότατα λείπει η απομακρυσμένη χαρτογράφηση διαδρομής.",
|
||||
"RemoveFailed": "Η αφαίρεση απέτυχε",
|
||||
"ResetDefinitions": "Επαναφορά ορισμών",
|
||||
"ResetQualityDefinitions": "Επαναφορά ορισμών ποιότητας",
|
||||
"ResetTitles": "Επαναφορά τίτλων",
|
||||
"RottenTomatoesRating": "Βαθμολογία ντομάτας",
|
||||
"ShowOverview": "Εμφάνιση επισκόπησης",
|
||||
"UnableToLoadCollections": "Δεν είναι δυνατή η φόρτωση των συλλογών",
|
||||
"Started": "Ξεκίνησε",
|
||||
"RefreshMonitoredIntervalHelpText": "Πόσο συχνά να ανανεώνετε τις παρακολουθούμενες λήψεις από προγράμματα-πελάτες λήψης, τουλάχιστον 1 λεπτό",
|
||||
"RemotePathMappingCheckBadDockerPath": "Χρησιμοποιείτε docker. πελάτης λήψης {0} τοποθετεί λήψεις στο {1} αλλά αυτή δεν είναι έγκυρη διαδρομή {2}. Ελέγξτε τις απομακρυσμένες αντιστοιχίσεις διαδρομής και κατεβάστε τις ρυθμίσεις πελάτη.",
|
||||
"RemotePathMappingCheckDockerFolderMissing": "Χρησιμοποιείτε docker. πελάτης λήψης {0} τοποθετεί λήψεις στο {1} αλλά αυτός ο κατάλογος δεν φαίνεται να υπάρχει μέσα στο κοντέινερ. Ελέγξτε τις απομακρυσμένες αντιστοιχίσεις διαδρομής και τις ρυθμίσεις όγκου κοντέινερ.",
|
||||
"RemotePathMappingCheckDownloadPermissions": "Το Radarr μπορεί να δει αλλά δεν έχει πρόσβαση στην ταινία που έχει ληφθεί {0}. Πιθανό σφάλμα αδειών.",
|
||||
"RemotePathMappingCheckFilesBadDockerPath": "Χρησιμοποιείτε docker. λήψη αρχείων πελάτη {0} που αναφέρθηκαν στο {1} αλλά αυτή δεν είναι έγκυρη διαδρομή {2}. Ελέγξτε τις απομακρυσμένες αντιστοιχίσεις διαδρομής και κατεβάστε τις ρυθμίσεις πελάτη.",
|
||||
"RemotePathMappingCheckFilesGenericPermissions": "Λήψη αρχείων πελάτη {0} που αναφέρθηκαν στο {1} αλλά το Radarr δεν μπορεί να δει αυτόν τον κατάλογο. Ίσως χρειαστεί να προσαρμόσετε τα δικαιώματα του φακέλου.",
|
||||
"RemotePathMappingCheckImportFailed": "Η Radarr απέτυχε να εισαγάγει μια ταινία. Ελέγξτε τα αρχεία καταγραφής σας για λεπτομέρειες.",
|
||||
"RemotePathMappingCheckLocalFolderMissing": "Το πρόγραμμα-πελάτης απομακρυσμένης λήψης {0} τοποθετεί λήψεις στο {1} αλλά αυτός ο κατάλογος δεν φαίνεται να υπάρχει. Πιθανόν να λείπει ή να είναι εσφαλμένη η απομακρυσμένη αντιστοίχιση διαδρομής.",
|
||||
"RemotePathMappingCheckWrongOSPath": "Το πρόγραμμα-πελάτης απομακρυσμένης λήψης {0} τοποθετεί λήψεις στο {1} αλλά αυτή δεν είναι έγκυρη διαδρομή {2}. Ελέγξτε τις απομακρυσμένες αντιστοιχίσεις διαδρομής και κατεβάστε τις ρυθμίσεις πελάτη.",
|
||||
"RemoveDownloadsAlert": "Οι ρυθμίσεις κατάργησης μετακινήθηκαν στις μεμονωμένες ρυθμίσεις Download Client στον παραπάνω πίνακα.",
|
||||
"RemoveSelectedItem": "Αφαίρεση επιλεγμένου αντικειμένου",
|
||||
"ResetTitlesHelpText": "Επαναφέρετε τίτλους ορισμού καθώς και τιμές",
|
||||
"RSSHelpText": "Θα χρησιμοποιηθεί όταν το Radarr αναζητά περιοδικά εκδόσεις μέσω RSS Sync",
|
||||
"SearchOnAddCollectionHelpText": "Αναζητήστε ταινίες σε αυτήν τη συλλογή όταν προστεθούν στη βιβλιοθήκη",
|
||||
"SelectReleaseGroup": "Επιλέξτε Ομάδα έκδοσης",
|
||||
"SetReleaseGroup": "Ορισμός ομάδας απελευθέρωσης",
|
||||
"SettingsThemeHelpText": "Αλλαγή του θέματος διεπαφής χρήστη εφαρμογής, το θέμα «Αυτόματο» θα χρησιμοποιήσει το Θέμα του λειτουργικού σας συστήματος για να ρυθμίσει τη λειτουργία Light ή Dark. Εμπνευσμένο από το Theme.Park",
|
||||
"ScrollMovies": "Ταινίες με κύλιση",
|
||||
"RemotePathMappingCheckFilesLocalWrongOSPath": "Τοπικό πρόγραμμα-πελάτη λήψης {0} ανέφερε αρχεία στο {1} αλλά αυτή δεν είναι έγκυρη διαδρομή {2}. Ελέγξτε τις ρυθμίσεις του προγράμματος-πελάτη λήψης.",
|
||||
"SettingsTheme": "Θέμα",
|
||||
"ShowCollectionDetails": "Εμφάνιση κατάστασης συλλογής",
|
||||
"TaskUserAgentTooltip": "User-Agent που παρέχεται από την εφαρμογή που κάλεσε το API",
|
||||
"TotalMovies": "Σύνολο ταινιών",
|
||||
"Waiting": "Αναμονή",
|
||||
"UpdateAvailable": "Νέα ενημέρωση είναι διαθέσιμη",
|
||||
"SizeLimit": "Όριο μεγέθους"
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
"Backups": "Backups",
|
||||
"BeforeUpdate": "Before update",
|
||||
"BindAddress": "Bind Address",
|
||||
"BindAddressHelpText": "Valid IPv4 address or '*' for all interfaces",
|
||||
"BindAddressHelpText": "Valid IP address, localhost or '*' for all interfaces",
|
||||
"Blocklist": "Blocklist",
|
||||
"Blocklisted": "Blocklisted",
|
||||
"BlocklistHelpText": "Prevents Radarr from automatically grabbing this release again",
|
||||
@@ -258,6 +258,7 @@
|
||||
"DownloadClients": "Download Clients",
|
||||
"DownloadClientSettings": "Download Client Settings",
|
||||
"DownloadClientsSettingsSummary": "Download clients, download handling and remote path mappings",
|
||||
"DownloadClientSortingCheckMessage": "Download client {0} has {1} sorting enabled for Radarr's category. You should disable sorting in your download client to avoid import issues.",
|
||||
"DownloadClientStatusCheckAllClientMessage": "All download clients are unavailable due to failures",
|
||||
"DownloadClientStatusCheckSingleClientMessage": "Download clients unavailable due to failures: {0}",
|
||||
"DownloadClientUnavailable": "Download client is unavailable",
|
||||
@@ -869,6 +870,7 @@
|
||||
"RootFolders": "Root Folders",
|
||||
"RottenTomatoesRating": "Tomato Rating",
|
||||
"RSS": "RSS",
|
||||
"RSSHelpText": "Will be used when Radarr periodically looks for releases via RSS Sync",
|
||||
"RSSIsNotSupportedWithThisIndexer": "RSS is not supported with this indexer",
|
||||
"RSSSync": "RSS Sync",
|
||||
"RssSyncHelpText": "Interval in minutes. Set to zero to disable (this will stop all automatic release grabbing)",
|
||||
@@ -928,7 +930,7 @@
|
||||
"SettingsShowRelativeDates": "Show Relative Dates",
|
||||
"SettingsShowRelativeDatesHelpText": "Show relative (Today/Yesterday/etc) or absolute dates",
|
||||
"SettingsTheme": "Theme",
|
||||
"SettingsThemeHelpText": "Change Application UI Theme, Inspired by Theme.Park",
|
||||
"SettingsThemeHelpText": "Change Application UI Theme, 'Auto' Theme will use your OS Theme to set Light or Dark mode. Inspired by Theme.Park",
|
||||
"SettingsTimeFormat": "Time Format",
|
||||
"SettingsWeekColumnHeader": "Week Column Header",
|
||||
"SettingsWeekColumnHeaderHelpText": "Shown above each column when week is the active view",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"BackupIntervalHelpText": "Prowlarrin tietokannan ja asetusten automaattisen varmuuskopioinnin suoritusaikaväli.",
|
||||
"AppDataDirectory": "AppData-kansio",
|
||||
"BackupNow": "Varmuuskopioi nyt",
|
||||
"BindAddressHelpText": "Toimiva IPv4-osoite tai '*' (tähti) kaikille yhteyksille.",
|
||||
"BindAddressHelpText": "Toimiva IP-osoite, localhost tai '*' (tähti) kaikille yhteyksille.",
|
||||
"Branch": "Kehityshaara",
|
||||
"BuiltIn": "Sisäänrakennettu",
|
||||
"CalendarOptions": "Kalenterin asetukset",
|
||||
@@ -252,7 +252,7 @@
|
||||
"Profiles": "Profiilit",
|
||||
"ProxyType": "Välityspalvelimen tyyppi",
|
||||
"PtpOldSettingsCheckMessage": "Seuraavat PassThePopcorn-tietolähteet sisältävät vanhentuneita asetuksia, jotka olisi syytä päivittää: {0}",
|
||||
"QualitiesHelpText": "Listalla ylempänä olevia laatuja painotetaan. Saman ryhmän laadut ovat tasaveroisia. Valitse vain haluamasi laadut.",
|
||||
"QualitiesHelpText": "Listalla ylempänä olevia laatuja painotetaan enemmän vaikkei niitä ole valittu. Samoissa ryhmissä olevat laadut ovat tasaveroisia. Valitse vain halutut laadut.",
|
||||
"QualityProfileInUse": "Elokuvaan, listaan tai kokelmaan liitettyä laatuprofiilia ei voida poistaa.",
|
||||
"QueueIsEmpty": "Jono on tyhjä",
|
||||
"RadarrCalendarFeed": "Radarr-kalenterisyöte",
|
||||
@@ -333,7 +333,7 @@
|
||||
"CreateEmptyMovieFoldersHelpText": "Luo puuttuvat elokuvakansiot levyn tarkistuksen yhteydessä.",
|
||||
"DeleteDownloadClient": "Poista lataustyökalu",
|
||||
"ImportHeader": "Lisää Radarriin elokuvia tuomalla olemassa oleva, järjestetty kirjasto.",
|
||||
"ImportMechanismHealthCheckMessage": "Ota valmiiden latausten käsittely käyttöön",
|
||||
"ImportMechanismHealthCheckMessage": "Käytä valmiiden latausten käsittelyä",
|
||||
"MinAvailability": "Pienin saatavuus",
|
||||
"MovieIsUnmonitored": "Elokuvaa ei valvota",
|
||||
"MovieNaming": "Elokuvien nimeäminen",
|
||||
@@ -435,7 +435,7 @@
|
||||
"StartTypingOrSelectAPathBelow": "Aloita kirjoitus tai valitse sijainti alta",
|
||||
"StartupDirectory": "Käynnistyskansio",
|
||||
"System": "Järjestelmä",
|
||||
"SystemTimeCheckMessage": "Järjestelmän aika on heittä yli vuorokauden verran. Ajoitetut tehtävät eivät luultavasti toimi oikein ennen ajan korjausta.",
|
||||
"SystemTimeCheckMessage": "Järjestelmän aika on pielessä yli vuorokauden. Ajoitetut tehtävät eivät luultavasti toimi oikein ennen sen korjausta.",
|
||||
"Posters": "Julisteet",
|
||||
"PosterSize": "Julisteen koko",
|
||||
"TagCannotBeDeletedWhileInUse": "Tunnistetta ei voi poistaa, koska se on käytössä",
|
||||
@@ -653,9 +653,9 @@
|
||||
"IndexerPriority": "Tietolähteiden painotus",
|
||||
"IndexerPriorityHelpText": "Tietolähteen painotus: 1 (korkein) - 50 (matalin). Oletusarvo on 25. Käytetään muutoin tasaveroisten julkaisujen sieppauspäätökseen. Kaikkia käytössä olevia tietolähteitä käytetään edelleen RSS-synkronointiin ja hakuun.",
|
||||
"IndexerRssHealthCheckNoAvailableIndexers": "Kaikki RSS-tietolähteet ovat tilapaisesti tavoittamattomissa viimeaikaisten tietolähdevirheiden vuoksi.",
|
||||
"IndexerRssHealthCheckNoIndexers": "Yhtään RSS-synkronointia käyttävää tietolähdettä ei ole käytettävissä, eikä Radarr tämän vuoksi sieppaa uusia julkaisuja automaattisesti.",
|
||||
"IndexerRssHealthCheckNoIndexers": "Yhtään RSS-synkronointia käyttävää tietolähdettä ei ole käytettävissä, eikä uusia julkaisuja sen vuoksi siepata automaattisesti.",
|
||||
"Indexers": "Tietolähteet",
|
||||
"IndexerSearchCheckNoAutomaticMessage": "Ei hakemistoja, joissa automaattinen haku on käytössä, Radarr ei tarjoa automaattisia hakutuloksia",
|
||||
"IndexerSearchCheckNoAutomaticMessage": "Automaattihaussa käytettäviä tietolähteitä ei ole käytettävissä, eikä automaattisia hakutuloksia ole tämän vuoksi saatavilla.",
|
||||
"IndexerSearchCheckNoAvailableIndexersMessage": "Kaikki hakukelpoiset tietolähteet ovat tilapaisesti tavoittamattomissa viimeaikaisten tietolähdevirheiden vuoksi.",
|
||||
"IndexerSettings": "Tietolähteiden asetukset",
|
||||
"IndexerStatusCheckSingleClientMessage": "Tietolähteet eivät ole käytettävissä virheiden vuoksi: {0}",
|
||||
@@ -750,7 +750,7 @@
|
||||
"QualityDefinitions": "Laatumääritykset",
|
||||
"QualityLimitsHelpText": "Rajoitukset suhteutetaan automaattisesti elokuvan kestoon.",
|
||||
"QualityProfileDeleteConfirm": "Haluatko varmasti poistaa laatuprofiilin {0}",
|
||||
"QualitySettingsSummary": "Laatumääritykset erilaisia sisältömuotoja ja teidostokokoja varten.",
|
||||
"QualitySettingsSummary": "Laatumääritykset erilaisia sisältömuotoja ja tiedostokokoja varten.",
|
||||
"RadarrSupportsAnyIndexer": "Radarr tukee Newznab- ja Torznab-yhteensopivien tietolähteiden ohella myös monia muita alla lueteltuja tietolähteitä.",
|
||||
"RadarrSupportsCustomConditionsAgainstTheReleasePropertiesBelow": "Radarr tukee mukautettuja ehtoja perustuen julkaisujen alla oleviin ominaisuuksiin.",
|
||||
"Ratings": "Arviot",
|
||||
@@ -1146,5 +1146,13 @@
|
||||
"TotalMovies": "Elokuvia yhteensä",
|
||||
"ApplicationURL": "Sovelluksen URL-osoite",
|
||||
"ApplicationUrlHelpText": "Sovelluksen ulkoinen URL-osoite, johon sisältyy http(s)://, portti ja URL-perusta.",
|
||||
"PreferredProtocol": "Ensisijainen protokolla"
|
||||
"PreferredProtocol": "Ensisijainen protokolla",
|
||||
"ResetQualityDefinitions": "Palauta laatumääritykset",
|
||||
"ResetTitles": "Palauta nimet",
|
||||
"SettingsTheme": "Teema",
|
||||
"SettingsThemeHelpText": "Vaihda sovelluksen käyttöliittymän ulkoasua. \"Automaattinen\" vaihtaa vaalean ja tumman tilan käyttöjärjestelmäsi teemaa vastaavaksi. Innoittanut Theme.Park.",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Haluatko varmasti palauttaa laatumääritykset?",
|
||||
"ResetDefinitions": "Palauta määritykset",
|
||||
"ResetTitlesHelpText": "Palauta määritysten nimet ja arvot.",
|
||||
"RSSHelpText": "Käytetään etsittäessä julkaisuja RSS-syötteistä ajoitetusti."
|
||||
}
|
||||
|
||||
@@ -962,8 +962,8 @@
|
||||
"MovieChat": "Movie Chat",
|
||||
"MovieInvalidFormat": "Film : format non valide",
|
||||
"MultiLanguage": "Multi-langue",
|
||||
"Negate": "Nier",
|
||||
"Negated": "Nié",
|
||||
"Negate": "Inverser",
|
||||
"Negated": "Inversé",
|
||||
"NoListRecommendations": "Aucun élément de liste ou recommandation n'a été trouvé, pour commencer, vous voudrez ajouter un nouveau film, importer des films existants ou ajouter une liste.",
|
||||
"OrganizeConfirm": "Voulez-vous vraiment organiser tous les fichiers dans les {0} films sélectionnés ?",
|
||||
"OrganizeSelectedMovies": "Organiser les films sélectionnés",
|
||||
@@ -1113,9 +1113,9 @@
|
||||
"SelectReleaseGroup": "Sélectionner le groupe de publication",
|
||||
"SetReleaseGroup": "Régler le groupe de publication",
|
||||
"RefreshMonitoredIntervalHelpText": "Intervalle en minutes entre chaque vérification des téléchargements, minimum 1 minute",
|
||||
"AllCollectionsHiddenDueToFilter": "Tous les films sont masqués en raison du filtre appliqué.",
|
||||
"AllCollectionsHiddenDueToFilter": "Toutes les collections sont masquées en raison du filtre appliqué.",
|
||||
"Collections": "Collections",
|
||||
"MonitorMovies": "Surveiller le film",
|
||||
"MonitorMovies": "Surveiller les films",
|
||||
"NoCollections": "Aucun film trouvé, pour commencer, vous voudrez ajouter un nouveau film ou importer des films existants.",
|
||||
"RssSyncHelpText": "Intervalle en minutes. Mettre à zéro pour désactiver (cela arrêtera tous les téléchargements automatiques)",
|
||||
"CollectionsSelectedInterp": "{0} Collections(s) Sélectionnée(s)",
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
"BranchUpdateMechanism": "A külső frissítési mechanizmus által használt ágazat",
|
||||
"BranchUpdate": "Ágazattípus a Radarr frissítéseihez",
|
||||
"Branch": "Ágazat",
|
||||
"BindAddressHelpText": "Érvényes IPv4-cím, vagy „*” minden interfészhez",
|
||||
"BindAddressHelpText": "Érvényes IP-cím, localhost vagy '*' minden interfészhez",
|
||||
"BindAddress": "Kapcsolási Cím",
|
||||
"BeforeUpdate": "Alkalmazásfrissítés előtt",
|
||||
"Backups": "Biztonsági Mentés",
|
||||
@@ -838,7 +838,7 @@
|
||||
"EditCustomFormat": "Egyéni Formátumok szerkesztése",
|
||||
"DoNotUpgradeAutomatically": "Ne frissítsen automatikusan",
|
||||
"DoNotPrefer": "Nem preferált",
|
||||
"DoneEditingGroups": "Kész szerkesztő csoportok",
|
||||
"DoneEditingGroups": "A csoportok szerkesztése kész",
|
||||
"Donations": "Adományok",
|
||||
"DockerUpdater": "A Frissítéshez frissítenie kell a Docker tárolót",
|
||||
"Discord": "Discord",
|
||||
@@ -965,7 +965,7 @@
|
||||
"QueueIsEmpty": "A várakozási sor üres",
|
||||
"QualityProfileInUse": "A filmhez, listához vagy gyűjteményhez csatolt minőségi profil nem törölhető",
|
||||
"QualityLimitsHelpText": "A korlátozások automatikusan beállítódnak a film hossza szerint.",
|
||||
"QualitiesHelpText": "A jobb minőség a listában jobban preferált. Ugyanazon minőségek a csoportban egyenlőek. Csak a bejelölt minőségek a kívánt minőségek",
|
||||
"QualitiesHelpText": "A listán magasabb minőségek előnyösebbek, még akkor is, ha nincs bejelölve. Ugyanazon csoporton belül a tulajdonságok egyenlőek. Csak ellenőrzött minőségek szükségesek",
|
||||
"Qualities": "Minőségek",
|
||||
"PrioritySettings": "Prioritás: {0}",
|
||||
"PreviewRenameHelpText": "Tipp: Átnevezés előnézetéhez... válassza a 'Visszavonást' majd kattintson a film címére és használja a",
|
||||
@@ -1149,9 +1149,11 @@
|
||||
"PreferredProtocol": "Preferált protokoll",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Biztos visszaállítod a minőségi definíciókat?",
|
||||
"SettingsTheme": "Téma",
|
||||
"SettingsThemeHelpText": "Kezelőfelület témájának módosítása, a Theme.Park jóvoltából",
|
||||
"SettingsThemeHelpText": "Változtasd meg az alkalmazás felhasználói felület témáját, az „Auto” téma az operációs rendszer témáját használja a Világos vagy Sötét mód beállításához. A Theme.Park ihlette",
|
||||
"ResetDefinitions": "Definíciók visszaállítása",
|
||||
"ResetQualityDefinitions": "Állítsd vissza a minőségi meghatározásokat",
|
||||
"ResetTitles": "Címek visszaállítása",
|
||||
"ResetTitlesHelpText": "A definíciócímek és értékek visszaállítása"
|
||||
"ResetTitlesHelpText": "A definíciócímek és értékek visszaállítása",
|
||||
"RSSHelpText": "Akkor használatos, amikor a Radarr rendszeresen keres kiadásokat az RSS Sync segítségével",
|
||||
"DownloadClientSortingCheckMessage": "A(z) {0} letöltési kliensben engedélyezve van a(z) {1} rendezés a Radarr kategóriájához. Az importálási problémák elkerülése érdekében le kell tiltania a rendezést a letöltési kliensben."
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
"AppDataLocationHealthCheckMessage": "L'aggiornamento non sarà possibile per evitare la cancellazione di AppData durante l'aggiornamento",
|
||||
"Analytics": "Analitica",
|
||||
"Added": "Aggiunto",
|
||||
"About": "Informazioni",
|
||||
"About": "Info",
|
||||
"Year": "Anno",
|
||||
"Week": "Settimana",
|
||||
"Updates": "Aggiornamenti",
|
||||
@@ -189,7 +189,7 @@
|
||||
"ChooseAnotherFolder": "Scegli un'altra Cartella",
|
||||
"Cast": "Cast",
|
||||
"Calendar": "Calendario",
|
||||
"BackupNow": "Effettua il Backup adesso",
|
||||
"BackupNow": "Esegui backup ora",
|
||||
"Backup": "Backup",
|
||||
"All": "Tutti",
|
||||
"Agenda": "Agenda",
|
||||
@@ -220,12 +220,12 @@
|
||||
"ChangeFileDate": "Cambiare la Data del File",
|
||||
"CertificationCountryHelpText": "Seleziona il Paese per le Certificazioni dei Film",
|
||||
"CertificationCountry": "Paese di Certificazione",
|
||||
"CertificateValidationHelpText": "Cambia quanto è rigorosa la convalida del certificato HTTPS. Non cambiare a meno che tu non comprenda i rischi.",
|
||||
"CertificateValidationHelpText": "Cambia quanto rigorosamente vengono validati i certificati HTTPS. Non cambiare senza conoscerne i rischi.",
|
||||
"CertificateValidation": "Convalida del Certificato",
|
||||
"Cancel": "Annulla",
|
||||
"BypassProxyForLocalAddresses": "Evita il Proxy per gli Indirizzi Locali",
|
||||
"Branch": "Ramo",
|
||||
"BindAddressHelpText": "Indirizzo IPv4 valido o '*' per tutte le interfacce",
|
||||
"BindAddressHelpText": "Indirizzo IPV4 valido o '*' per tutte le interfacce di rete",
|
||||
"BindAddress": "Indirizzo di Bind",
|
||||
"Backups": "I Backup",
|
||||
"BackupRetentionHelpText": "I backup automatici più vecchi del periodo di conservazione verranno eliminati automaticamente",
|
||||
@@ -261,7 +261,7 @@
|
||||
"TotalSpace": "Spazio Totale",
|
||||
"Title": "Titolo",
|
||||
"Time": "Ora",
|
||||
"TestAll": "Testa Tutti",
|
||||
"TestAll": "Prova Tutti",
|
||||
"Test": "Test",
|
||||
"TableOptionsColumnsMessage": "Scegli quali colonne rendere visibili ed il loro ordine",
|
||||
"TableOptions": "Opzioni della tabella",
|
||||
@@ -452,7 +452,7 @@
|
||||
"RadarrSupportsAnyRSSMovieListsAsWellAsTheOneStatedBelow": "Radarr supporta qualunque Lista di film RSS, cosi come le altre sotto.",
|
||||
"RadarrSupportsAnyDownloadClient": "Radarr supporta qualunque client di download che usi gli standard Newznab, cosi come gli altri client sotto.",
|
||||
"QuickImport": "Sposta automaticamente",
|
||||
"Queued": "Messo in coda",
|
||||
"Queued": "In coda",
|
||||
"QualitySettings": "Impostazione di Qualità",
|
||||
"QualityProfileDeleteConfirm": "Sicuro di voler cancellare il profilo di qualità {0}",
|
||||
"QualityCutoffHasNotBeenMet": "Il qualità di taglio non è stata raggiunta",
|
||||
@@ -713,8 +713,8 @@
|
||||
"TimeFormat": "Formato orario",
|
||||
"ThisConditionMatchesUsingRegularExpressions": "Questa condizione si applica usando espressione regolari. Nota che i caratteri {0} hanno significati speciali e devono essere evitati con un {1}",
|
||||
"TestAllLists": "Testa tutte le liste",
|
||||
"TestAllIndexers": "Testa tutti gli Indicizzatori",
|
||||
"TestAllClients": "Testa Tutti i Client",
|
||||
"TestAllIndexers": "Prova tutti gli indicizzatori",
|
||||
"TestAllClients": "Testa tutti i client",
|
||||
"TagsHelpText": "Si applica ai film con almeno un tag corrispondente",
|
||||
"TagIsNotUsedAndCanBeDeleted": "L'etichetta non è in uso e può essere eliminata",
|
||||
"TagCannotBeDeletedWhileInUse": "Non può essere cancellato mentre è in uso",
|
||||
@@ -793,7 +793,7 @@
|
||||
"MovieDetailsPreviousMovie": "Dettagli del film: film precedente",
|
||||
"FocusSearchBox": "Evidenzia casella di ricerca",
|
||||
"CloseCurrentModal": "Chiudi la Modale Attuale",
|
||||
"AcceptConfirmationModal": "Acetta Conferma Modale",
|
||||
"AcceptConfirmationModal": "Accetta Conferma Modale",
|
||||
"StartSearchForMissingMovie": "Avvia ricerca film mancanti",
|
||||
"StartProcessing": "Avvia Lavorazione",
|
||||
"StartImport": "Avvia Importazione",
|
||||
@@ -842,7 +842,7 @@
|
||||
"AddQualityProfile": "Aggiungi Profilo Qualità",
|
||||
"AddNotification": "Aggiungi Notifica",
|
||||
"AddedToDownloadQueue": "Aggiunto alla coda di download",
|
||||
"AddDownloadClient": "Aggiungi Client di Download",
|
||||
"AddDownloadClient": "Aggiungi Downloader",
|
||||
"AddCustomFormat": "Aggiungi Formato Personalizzato",
|
||||
"Add": "Aggiungi",
|
||||
"ImportLibrary": "Importazione della libreria",
|
||||
@@ -1091,11 +1091,21 @@
|
||||
"ClickToChangeReleaseGroup": "Clicca per cambiare gruppo di rilascio",
|
||||
"DiscordUrlInSlackNotification": "Hai una notifica Discord configurata come notifica Slack. Configurala come notifica Discord per una miglior funzionalità. Le notifiche interessate sono: {0}",
|
||||
"Duration": "Durata",
|
||||
"InstanceNameHelpText": "Nome dell'istanza nella scheda e per il nome dell'applicazione Syslog",
|
||||
"InstanceNameHelpText": "Nome istanza nella scheda e per il nome dell'app nel Syslog",
|
||||
"InstanceName": "Nome Istanza",
|
||||
"AllCollectionsHiddenDueToFilter": "Tutti i film sono nascosti a causa del filtro applicato.",
|
||||
"Collections": "Collezione",
|
||||
"MonitorMovies": "Monitora Film",
|
||||
"NoCollections": "Nessun film trovato, per iniziare ti consigliamo di aggiungere un nuovo film o importarne alcuni esistenti.",
|
||||
"RssSyncHelpText": "Intervallo in minuti. Imposta zero per disabilitarlo (ciò fermerà il recupero automatico di tutte le release)"
|
||||
"RssSyncHelpText": "Intervallo in minuti. Imposta zero per disabilitarlo (ciò fermerà il recupero automatico di tutte le release)",
|
||||
"ApplicationURL": "URL Applicazione",
|
||||
"ApplicationUrlHelpText": "L'URL esterno di questa applicazione, incluso http(s)://, porta e URL base",
|
||||
"CollectionOptions": "Opzioni della Collezione",
|
||||
"CollectionShowDetailsHelpText": "Mostra lo stato e le proprietà della collezione",
|
||||
"Started": "Iniziato",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Sei sicuro di voler ripristinare le definizioni della qualità?",
|
||||
"ChooseImportMode": "Selezionare Metodo di Importazione",
|
||||
"EditCollection": "Modifica Connessione",
|
||||
"SettingsThemeHelpText": "Cambia il Tema dell'interfaccia dell’applicazione, il Tema 'Auto' userà il suo Tema di Sistema per impostare la modalità Chiara o Scura. Ispirato da {0}",
|
||||
"PreferredProtocol": "Protocollo Preferito"
|
||||
}
|
||||
|
||||
@@ -372,7 +372,7 @@
|
||||
"CertificateValidation": "Certificaat Validatie",
|
||||
"BypassProxyForLocalAddresses": "Omzeil Proxy voor Lokale Adressen",
|
||||
"Branch": "Branch",
|
||||
"BindAddressHelpText": "Geldig IPv4-adres of '*' voor alle interfaces",
|
||||
"BindAddressHelpText": "Geldig IP-adres, localhost of '*' voor alle interfaces",
|
||||
"DeleteBackup": "Verwijder Veiligheidskopie",
|
||||
"BackupIntervalHelpText": "Tussentijd voor automatische back-up",
|
||||
"Backups": "Veiligheidskopieën",
|
||||
@@ -723,7 +723,7 @@
|
||||
"HaveNotAddedMovies": "U heeft nog geen films toegevoegd, wilt u eerst enkele of al uw films importeren?",
|
||||
"ForMoreInformationOnTheIndividualIndexers": "Voor meer informatie over de individuele indexeerders, klik op de info knoppen.",
|
||||
"ForMoreInformationOnTheIndividualImportListsClinkOnTheInfoButtons": "Voor meer informatie over de individuele lijsten, klik op de info knoppen.",
|
||||
"ForMoreInformationOnTheIndividualDownloadClients": "Voor meer informatie over de individuele downloaders, klik op de info knoppen.",
|
||||
"ForMoreInformationOnTheIndividualDownloadClients": "Voor meer informatie over de individuele download applicaties, klik op de info knoppen.",
|
||||
"FailedLoadingSearchResults": "Laden van zoekresultaten is mislukt, gelieve opnieuw te proberen.",
|
||||
"ExtraFileExtensionsHelpTexts1": "Komma gescheiden lijst met extra bestanden om te importeren (.nfo zal als .nfo-orig worden geïmporteerd)",
|
||||
"CouldNotFindResults": "Kon geen resultaten vinden voor '{0}'",
|
||||
@@ -1045,7 +1045,7 @@
|
||||
"DeleteFileLabel": "Verwijder {0} Film Bestanden",
|
||||
"UnableToAddRootFolder": "Kon hoofdmappen niet inladen",
|
||||
"UpdateAvailable": "Nieuwe update is beschikbaar",
|
||||
"From": "Van",
|
||||
"From": "van",
|
||||
"RemotePathMappingCheckDownloadPermissions": "Radarr kan gedownloade film {0} zien, maar niet openen. Waarschijnlijk fout met machtigingen.",
|
||||
"RemotePathMappingCheckFileRemoved": "Bestand {0} is halverwege de verwerking verwijderd.",
|
||||
"RemotePathMappingCheckFilesBadDockerPath": "U gebruikt docker; download client {0} gerapporteerde bestanden in {1} maar dit is geen geldig {2} pad. Controleer uw externe padtoewijzingen en download clientinstellingen.",
|
||||
@@ -1072,7 +1072,7 @@
|
||||
"BlocklistRelease": "Uitgave op blokkeerlijst zetten",
|
||||
"RemoveFromBlocklist": "Verwijder van zwarte lijst",
|
||||
"UnableToLoadBlocklist": "Kon zwarte lijst niet laden",
|
||||
"Blocklisted": "Blokkeerlijst",
|
||||
"Blocklisted": "Geblokeerde lijst",
|
||||
"AreYouSureYouWantToRemoveSelectedItemFromQueue": "Ben je zeker dat je {1} item{2} uit de wachtrij wilt verwijderen?",
|
||||
"BlocklistReleases": "Uitgave op blokkeerlijst zetten",
|
||||
"LocalPath": "Lokaal Pad",
|
||||
@@ -1113,5 +1113,8 @@
|
||||
"ChooseImportMode": "Kies Importmodus",
|
||||
"CollectionOptions": "Collectieopties",
|
||||
"CollectionShowDetailsHelpText": "Collectie status en details weergeven",
|
||||
"CollectionShowOverviewsHelpText": "Collectieoverzicht weergeven"
|
||||
"CollectionShowOverviewsHelpText": "Collectieoverzicht weergeven",
|
||||
"EditCollection": "Bewerk collectie",
|
||||
"BypassDelayIfHighestQualityHelpText": "Vertraging negeren wanneer een release de hoogst mogelijke kwaliteit heeft in het kwaliteitsprofiel van het geprefereerde protocol",
|
||||
"CollectionShowPostersHelpText": "Posters van de collectie tonen"
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"IndexerStatusCheckAllClientMessage": "Todos os indexadores estão indisponíveis devido a falhas",
|
||||
"IndexersSettingsSummary": "Indexadores e restrições de lançamento",
|
||||
"IndexerSettings": "Configurações do indexador",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa interativa ativada, o Radarr não fornecerá nenhum resultado de pesquisa interativa",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa Interativa habilitada, o Radarr não fornecerá nenhum resultado de pesquisa interativa",
|
||||
"IndexerSearchCheckNoAvailableIndexersMessage": "Todos os indexadores com capacidade de pesquisa estão temporariamente indisponíveis devido a erros recentes do indexador",
|
||||
"IndexerSearchCheckNoAutomaticMessage": "Nenhum indexador disponível com a Pesquisa automática habilitada, o Radarr não fornecerá nenhum resultado de pesquisa automática",
|
||||
"Indexers": "Indexadores",
|
||||
@@ -84,7 +84,7 @@
|
||||
"HealthNoIssues": "Nenhum problema com sua configuração",
|
||||
"Health": "Integridade",
|
||||
"HaveNotAddedMovies": "Você ainda não adicionou nenhum filme, deseja importar alguns ou todos os seus filmes primeiro?",
|
||||
"HardlinkCopyFiles": "Vínculo real/Copiar arquivos",
|
||||
"HardlinkCopyFiles": "Hardlink/Copiar arquivos",
|
||||
"Group": "Grupo",
|
||||
"GrabSelected": "Obter selecionado",
|
||||
"GrabReleaseMessageText": "O Radarr não conseguiu determinar a qual filme este lançamento está relacionado. O Radarr pode não conseguir importar automaticamente este lançamento. Quer obter \"{0}\"?",
|
||||
@@ -174,7 +174,7 @@
|
||||
"EditListExclusion": "Editar exclusão da lista",
|
||||
"Edition": "Edição",
|
||||
"EditIndexer": "Editar indexador",
|
||||
"EditGroups": "Editar grupos",
|
||||
"EditGroups": "Editar Grupos",
|
||||
"EditDelayProfile": "Editar perfil de atraso",
|
||||
"EditCustomFormat": "Editar formato personalizado",
|
||||
"Edit": "Editar",
|
||||
@@ -202,7 +202,7 @@
|
||||
"DownloadClient": "Cliente de download",
|
||||
"DoNotUpgradeAutomatically": "Não atualizar automaticamente",
|
||||
"DoNotPrefer": "Não preferir",
|
||||
"DoneEditingGroups": "Edição de grupos concluída",
|
||||
"DoneEditingGroups": "Concluído a Edição de Grupos",
|
||||
"Donations": "Doações",
|
||||
"DockerUpdater": "atualizar o contêiner do Docker para receber a atualização",
|
||||
"Docker": "Docker",
|
||||
@@ -267,7 +267,7 @@
|
||||
"CustomFormatUnknownCondition": "Condição de formato personalizado \"{0}\" desconhecida",
|
||||
"CustomFormatsSettingsSummary": "Configurações e formatos personalizados",
|
||||
"CustomFormatsSettings": "Configurações de formatos personalizados",
|
||||
"CustomFormatScore": "Pontuação de formato personalizado",
|
||||
"CustomFormatScore": "Pontuação do Formato Personalizado",
|
||||
"CustomFormats": "Formatos personalizados",
|
||||
"CustomFormatJSON": "JSON de formato personalizado",
|
||||
"CustomFormatHelpText": "O Radarr pontua cada versão usando a soma das pontuações para formatos personalizados encontrados. Se uma nova versão tiver melhor pontuação, com a mesma qualidade ou melhor, o Radarr o capturará.",
|
||||
@@ -340,7 +340,7 @@
|
||||
"BranchUpdateMechanism": "Ramificação usada pelo mecanismo de atualização externo",
|
||||
"BranchUpdate": "Ramificação para atualização do Radarr",
|
||||
"Branch": "Ramo",
|
||||
"BindAddressHelpText": "Endereço IPv4 Válido ou '*' para todas as interfaces",
|
||||
"BindAddressHelpText": "Endereço IP válido, localhost ou '*' para todas as interfaces",
|
||||
"BindAddress": "Fixar Endereço",
|
||||
"BeforeUpdate": "Antes da atualização",
|
||||
"Backups": "Backups",
|
||||
@@ -402,7 +402,7 @@
|
||||
"MinimumLimits": "Limites mínimos",
|
||||
"MinimumFreeSpaceWhenImportingHelpText": "Impedir a importação se deixar menos do que esta quantidade de espaço em disco disponível",
|
||||
"MinimumFreeSpace": "Mínimo de espaço livre",
|
||||
"MinimumCustomFormatScore": "Pontuação mínima de formato personalizado",
|
||||
"MinimumCustomFormatScore": "Pontuação Mínima do Formato Personalizado",
|
||||
"MinimumAgeHelpText": "Somente Usenet: tempo de vida mínimo, em minutos, dos NZBs, para que sejam capturados. Use isto para dar às novas versões tempo de propagar-se em seu fornecedor de Usenet.",
|
||||
"MinimumAge": "Tempo de vida mínimo",
|
||||
"MinimumAvailability": "Disponibilidade mínima",
|
||||
@@ -484,7 +484,7 @@
|
||||
"AddMovies": "Adicionar filmes",
|
||||
"AddMovie": "Adicionar filme",
|
||||
"AddListExclusion": "Adicionar exclusão à lista",
|
||||
"AddList": "Adicionar à Lista",
|
||||
"AddList": "Adicionar Lista",
|
||||
"AddingTag": "Adicionando etiqueta",
|
||||
"AddIndexer": "Adicionar indexador",
|
||||
"AddImportExclusionHelpText": "Impedir a adição do filme ao Radarr por listas",
|
||||
@@ -752,7 +752,7 @@
|
||||
"Restrictions": "Restrições",
|
||||
"RestoreBackup": "Restaurar backup",
|
||||
"Restore": "Restaurar",
|
||||
"RestartRequiredHelpTextWarning": "Requer reinicio para fazer efeito",
|
||||
"RestartRequiredHelpTextWarning": "Requer reinicialização para ter efeito",
|
||||
"RestartRadarr": "Reiniciar o Radarr",
|
||||
"RestartNow": "Reiniciar agora",
|
||||
"Restart": "Reiniciar",
|
||||
@@ -854,7 +854,7 @@
|
||||
"QualityDefinitions": "Definições de qualidade",
|
||||
"QualityCutoffHasNotBeenMet": "Limite de qualidade não atingido",
|
||||
"Quality": "Qualidade",
|
||||
"QualitiesHelpText": "As qualidades mais altas na lista são mais preferidas, mesmo que não estejam marcadas. As qualidades dentro do mesmo grupo são iguais. Apenas qualidades marcadas são desejadas",
|
||||
"QualitiesHelpText": "As qualidades mais altas na lista são mais preferidas, mesmo que não sejam verificadas. As qualidades dentro do mesmo grupo são iguais. Somente qualidades verificadas são desejadas",
|
||||
"Qualities": "Qualidades",
|
||||
"PublishedDate": "Data de publicação",
|
||||
"PtpOldSettingsCheckMessage": "As configurações dos seguintes indexadores do PassThePopcorn são obsoletas e devem ser atualizadas: {0}",
|
||||
@@ -868,7 +868,7 @@
|
||||
"Proxy": "Proxy",
|
||||
"ProtocolHelpText": "Escolha que protocolo(s) utilizar e qual o preferido ao escolher entre versões iguais",
|
||||
"Protocol": "Protocolo",
|
||||
"Proper": "Proper",
|
||||
"Proper": "Apropriado",
|
||||
"Progress": "Progresso",
|
||||
"ProfilesSettingsSummary": "Perfis de qualidade, idioma e atraso",
|
||||
"Profiles": "Perfis",
|
||||
@@ -923,7 +923,7 @@
|
||||
"OnRenameHelpText": "Ao renomear",
|
||||
"OnRename": "Ao Renomear",
|
||||
"OnlyUsenet": "Apenas Usenet",
|
||||
"OnlyTorrent": "Apenas torrents",
|
||||
"OnlyTorrent": "Apenas Torrents",
|
||||
"OnLatestVersion": "A versão mais recente do Radarr já está instalada",
|
||||
"OnImport": "Ao importar",
|
||||
"OnHealthIssueHelpText": "Ao ter problema de integridade",
|
||||
@@ -1053,7 +1053,7 @@
|
||||
"RemotePathMappingCheckImportFailed": "O Radarr não conseguiu importar um filme. Verifique os logs para saber mais.",
|
||||
"RemotePathMappingCheckFileRemoved": "O arquivo {0} foi removido no meio do processamento.",
|
||||
"RemotePathMappingCheckDownloadPermissions": "O Radarr pode ver, mas não pode acessar o filme baixado {0}. Provável erro de permissões.",
|
||||
"RemotePathMappingCheckGenericPermissions": "O cliente de download {0} coloca downloads em {1} mas o Radarr não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.",
|
||||
"RemotePathMappingCheckGenericPermissions": "O cliente de download {0} coloca os downloads em {1}, mas o Radarr não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.",
|
||||
"RemotePathMappingCheckWrongOSPath": "O cliente de download remoto {0} coloca downloads em {1}, mas este não é um caminho {2} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.",
|
||||
"RemotePathMappingCheckLocalWrongOSPath": "O cliente de download local {0} coloca downloads em {1}, mas este não é um caminho {2} válido. Revise as configurações do seu cliente de download.",
|
||||
"RemotePathMappingCheckLocalFolderMissing": "O cliente de download remoto {0} coloca downloads em {1}, mas esse diretório parece não existir. Mapeamento de caminho remoto provavelmente ausente ou incorreto.",
|
||||
@@ -1078,7 +1078,7 @@
|
||||
"AreYouSureYouWantToRemoveSelectedItemFromQueue": "Você tem certeza de que deseja remover {0} item{1} da fila?",
|
||||
"BlocklistReleases": "Lançamento na lista de bloqueio",
|
||||
"RemoveSelectedItems": "Remover Itens Selecionados",
|
||||
"IndexerTagHelpText": "Só use este indexador para filmes com ao menos uma etiqueta correspondente. Deixe em branco para usar com todos os filmes.",
|
||||
"IndexerTagHelpText": "Use este indexador apenas para filmes com pelo menos uma etiqueta correspondente. Deixe em branco para usar com todos os filmes.",
|
||||
"RemoveSelectedItem": "Remover Item Selecionado",
|
||||
"RemoveFailed": "Falha na Remoção",
|
||||
"RemoveCompleted": "Remover Completos",
|
||||
@@ -1087,13 +1087,13 @@
|
||||
"OnApplicationUpdateHelpText": "Na Atualização do Aplicativo",
|
||||
"DiscordUrlInSlackNotification": "Você tem uma notificação do Discord configurado como uma notificação do Slack. Definir isso como uma notificação do Discord para melhor funcionalidade. Com efeito, notificações são: {0}",
|
||||
"AnnouncedMsg": "Filme foi anunciado",
|
||||
"IndexerDownloadClientHelpText": "Especificar em que cliente de download é usado para baixar deste indexador",
|
||||
"IndexerDownloadClientHelpText": "Especificar qual cliente de download é utilizado para baixar a partir deste indexador",
|
||||
"LocalPath": "Caminho Local",
|
||||
"ManualImportSetReleaseGroup": "Importar Manual - Definir Grupo de Lançamento",
|
||||
"SelectLanguages": "Selecionar Idiomas",
|
||||
"SelectReleaseGroup": "Selecionar Grupo do Lançamento",
|
||||
"SetReleaseGroup": "Definir Grupo do Lançamento",
|
||||
"ClickToChangeReleaseGroup": "Clique para mudar o grupo do lançamento",
|
||||
"ClickToChangeReleaseGroup": "Clique para alterar o grupo do lançamento",
|
||||
"Filters": "Filtros",
|
||||
"RemotePath": "Caminho Remoto",
|
||||
"SizeLimit": "Tamanho Limite",
|
||||
@@ -1147,11 +1147,13 @@
|
||||
"ApplicationURL": "URL do Aplicativo",
|
||||
"ApplicationUrlHelpText": "A URL externa deste aplicativo, incluindo http(s)://, porta e base da URL",
|
||||
"PreferredProtocol": "Protocolo Preferido",
|
||||
"SettingsThemeHelpText": "Alterar o tema da interface do usuário do aplicativo, inspirado no Theme.Park",
|
||||
"SettingsThemeHelpText": "Alterar o tema da interface do usuário do aplicativo, o tema 'Auto' usará o tema do sistema operacional para definir o modo Claro ou Escuro. Inspirado por Theme.Park",
|
||||
"ResetDefinitions": "Redefinir definições",
|
||||
"ResetQualityDefinitions": "Redefinir Definições de Qualidade",
|
||||
"ResetTitles": "Redefinir Títulos",
|
||||
"ResetTitlesHelpText": "Redefinir títulos de definição, bem como valores",
|
||||
"SettingsTheme": "Tema",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Tem certeza de que deseja redefinir as definições de qualidade?"
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Tem certeza de que deseja redefinir as definições de qualidade?",
|
||||
"RSSHelpText": "Será usado quando o Radarr procurar periodicamente lançamentos via RSS Sync",
|
||||
"DownloadClientSortingCheckMessage": "O cliente de download {0} tem classificação {1} habilitada para a categoria do Radarr. Você deve desativar a classificação em seu cliente de download para evitar problemas de importação."
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
"ExcludeTitle": "Исключить {0}? Radarr не будет автоматически добавлять сканируя лист.",
|
||||
"InCinemasMsg": "Фильмы в кинотеатрах",
|
||||
"IncludeRecommendationsHelpText": "Включить в отображении найденного фильмы рекомендованные Radar",
|
||||
"IndexerPriorityHelpText": "Приоритет индексатора от 1 (самый высокий) до 50 (самый низкий). По умолчанию: 25. Используется при захвате выпусков в качестве средства разрешения конфликтов для равных в остальном выпусков, Radarr по-прежнему будет использовать все включенные индексаторы для синхронизации и поиска RSS.",
|
||||
"IndexerPriorityHelpText": "Приоритет индексатора от 1 (самый высокий) до 50 (самый низкий). По умолчанию: 25. Используется при захвате выпусков в качестве средства разрешения конфликтов для равных в остальном выпусков, Radarr по-прежнему будет использовать все включенные индексаторы для синхронизации и поиска RSS",
|
||||
"IndexerSearchCheckNoAvailableIndexersMessage": "Все индексаторы с возможностью поиска временно выключены из-за ошибок",
|
||||
"KeyboardShortcuts": "Горячие клавиши",
|
||||
"CantFindMovie": "Почему я не могу найти фильм?",
|
||||
@@ -330,7 +330,7 @@
|
||||
"Connect": "Подключить",
|
||||
"Connections": "Соединения",
|
||||
"CompletedDownloadHandling": "Обработка завершенных скачиваний",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Нет доступных индексаторов с интерактивным поиском, Radarr не будет предоставлять результаты интерактивного поиска",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Нет доступных индексаторов с включенным интерактивным поиском, Radarr не будет предоставлять результаты интерактивного поиска",
|
||||
"IndexersSettingsSummary": "Ограничения для индексаторов и релизов",
|
||||
"Language": "Язык",
|
||||
"ImportListStatusCheckAllClientMessage": "Все листы недоступны из-за ошибок",
|
||||
@@ -421,7 +421,7 @@
|
||||
"QualitySettingsSummary": "Качественные размеры и наименования",
|
||||
"QualitySettings": "Настройки качества",
|
||||
"QualityProfiles": "Профили качества",
|
||||
"QualityProfileInUse": "Невозможно удалить профиль качества прикрепленный к фильму",
|
||||
"QualityProfileInUse": "Невозможно удалить профиль качества, прикрепленный к фильму, списку или коллекции",
|
||||
"QualityProfileDeleteConfirm": "Вы действительно хотите удалить профиль качества {0}",
|
||||
"QualityProfile": "Профиль качества",
|
||||
"QualityOrLangCutoffHasNotBeenMet": "Не соблюдено ограничение по качеству или языку",
|
||||
@@ -429,7 +429,7 @@
|
||||
"QualityDefinitions": "Определения качества",
|
||||
"QualityCutoffHasNotBeenMet": "Порог качества не соблюден",
|
||||
"Quality": "Качество",
|
||||
"QualitiesHelpText": "Качества, расположенные выше в списке, более предпочтительны. Качества внутри одной группы равны. Требуются только отмеченные качества",
|
||||
"QualitiesHelpText": "Качества, расположенные выше в списке, являются более предпочтительными, даже если они не отмечены. Качества внутри одной группы равны. Требуются только отмеченные качества",
|
||||
"Qualities": "Качества",
|
||||
"PublishedDate": "Дата публикации",
|
||||
"PtpOldSettingsCheckMessage": "Следующие индексаторы PassThePopcorn устарели и должны быть обновлены: {0}",
|
||||
@@ -747,7 +747,7 @@
|
||||
"BranchUpdateMechanism": "Ветвь, используемая внешним механизмом обновления",
|
||||
"BranchUpdate": "Ветвь для обновления Radarr",
|
||||
"Branch": "Ветка",
|
||||
"BindAddressHelpText": "Действительный IPv4-адрес или '*' для всех интерфейсов",
|
||||
"BindAddressHelpText": "Действительный IP-адрес, локальный адрес или '*' для всех интерфейсов",
|
||||
"BackupFolderHelpText": "Относительные пути будут в каталоге AppData Radarr",
|
||||
"AvailabilityDelayHelpText": "Время до или после доступной даты для поиска фильма",
|
||||
"AvailabilityDelay": "Задержка доступности",
|
||||
@@ -1047,7 +1047,7 @@
|
||||
"RemotePathMappingCheckRemoteDownloadClient": "Удалённый клиент загрузки {0} сообщил о файлах в {1}, но эта директория, похоже, не существует. Вероятно, отсутствует сопоставление удаленных путей.",
|
||||
"RemotePathMappingCheckLocalWrongOSPath": "Локальный клиент загрузки {0} загружает файлы в {1}, но это не правильный путь {2}. Проверьте настройки клиента загрузки.",
|
||||
"RemotePathMappingCheckLocalFolderMissing": "Удалённый клиент загрузки {0} загружает файлы в {1}, но эта директория, похоже, не существует. Вероятно, отсутствует или неправильно указан удаленный путь.",
|
||||
"RemotePathMappingCheckGenericPermissions": "Клиент загрузки {0} загружает файлы в {1}, но Radarr не может найти эту директорию. Возможно, вам нужно настроить права доступа к данной директории.",
|
||||
"RemotePathMappingCheckGenericPermissions": "Клиент загрузки {0} загружает файлы в {1}, но Radarr не может видеть этот каталог. Возможно, вам нужно настроить права доступа к данной директории.",
|
||||
"RemotePathMappingCheckFilesBadDockerPath": "Вы используете docker; клиент загрузки {0} сообщил о файлах в {1}, но это не корректный путь {2}. Проверьте правильность указанного пути и настройки клиента загрузки.",
|
||||
"RemotePathMappingCheckImportFailed": "Radarr не удалось импортировать фильм. Проверьте ваши логи для более подробной информации.",
|
||||
"RemotePathMappingCheckFolderPermissions": "Radarr видит директорию загрузки {0}, но не имеет доступа к ней. Возможно, ошибка в правах доступа.",
|
||||
@@ -1108,7 +1108,7 @@
|
||||
"AllCollectionsHiddenDueToFilter": "Все фильмы скрыты в соответствии с фильтром.",
|
||||
"Collections": "Коллекции",
|
||||
"MonitorMovies": "Отслеживать фильм",
|
||||
"NoCollections": "Коллекции не найдены. Для начала вам нужно добавить новый фильм или импортировать несколько существующих.",
|
||||
"NoCollections": "Коллекции не найдены. Для начала вам нужно добавить новый фильм или импортировать несколько существующих",
|
||||
"CollectionOptions": "Параметры коллекции",
|
||||
"CollectionShowDetailsHelpText": "Показать статус и свойства коллекции",
|
||||
"CollectionShowOverviewsHelpText": "Показать обзоры коллекций",
|
||||
@@ -1142,5 +1142,18 @@
|
||||
"RottenTomatoesRating": "Tomato рейтинг",
|
||||
"Started": "Запущено",
|
||||
"Database": "База данных",
|
||||
"From": "из"
|
||||
"From": "из",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Вы уверены, что хотите сбросить определения качества?",
|
||||
"ResetDefinitions": "Сбросить определения",
|
||||
"ResetQualityDefinitions": "Сбросить определения качества",
|
||||
"ResetTitles": "Сбросить заголовки",
|
||||
"ResetTitlesHelpText": "Сбросить заголовки определений, а также значения",
|
||||
"RSSHelpText": "Будет использоваться, когда Radarr будет периодически искать выпуски через RSS Sync",
|
||||
"SettingsTheme": "Тема",
|
||||
"SettingsThemeHelpText": "Измените тему пользовательского интерфейса приложения, тема «Авто» будет использовать тему вашей ОС для установки светлого или темного режима. Вдохновленный Theme.Park",
|
||||
"PreferredProtocol": "Предпочтительный протокол",
|
||||
"ApplicationURL": "URL-адрес приложения",
|
||||
"ApplicationUrlHelpText": "Внешний URL-адрес этого приложения, включая http(s)://, порт и базовый URL-адрес",
|
||||
"ScrollMovies": "Прокрутите фильмы",
|
||||
"DownloadClientSortingCheckMessage": "В загрузочном клиенте {0} включена {1} сортировка для категории Radarr. Вам следует отключить сортировку в вашем клиенте загрузки, чтобы избежать проблем с импортом."
|
||||
}
|
||||
|
||||
@@ -170,5 +170,85 @@
|
||||
"CloneIndexer": "Klonovať indexer",
|
||||
"CloneProfile": "Klonovať profil",
|
||||
"Close": "Zatvoriť",
|
||||
"CloseCurrentModal": "Zatvoriť aktuálne okno"
|
||||
"CloseCurrentModal": "Zatvoriť aktuálne okno",
|
||||
"Filters": "Filtre",
|
||||
"Reload": "Obnoviť",
|
||||
"Trakt": "Trakt",
|
||||
"UI": "UI",
|
||||
"Crew": "Štáb",
|
||||
"Deleted": "Vymazať",
|
||||
"DownloadClient": "Klient na sťahovanie",
|
||||
"Filter": "Filter",
|
||||
"Languages": "jazyk",
|
||||
"Posters": "Plagáty",
|
||||
"Connections": "Spojenia",
|
||||
"DelayProfile": "Profil oneskorenia",
|
||||
"Grab": "Grab",
|
||||
"Language": "jazyk",
|
||||
"DelayProfiles": "Profil oneskorenia",
|
||||
"Delete": "Vymazať",
|
||||
"Events": "Udalosť",
|
||||
"Password": "Heslo",
|
||||
"Peers": "Peeri",
|
||||
"Remove": "Odstrániť",
|
||||
"Required": "Vyžadovať",
|
||||
"Details": "podrobnosti",
|
||||
"DownloadClients": "Klient na sťahovanie",
|
||||
"Torrents": "Torrenty",
|
||||
"ICalFeed": "iCal Feed",
|
||||
"Indexer": "Indexer",
|
||||
"Port": "Port",
|
||||
"QualityProfile": "Profil kvality",
|
||||
"QualityProfiles": "Profil kvality",
|
||||
"Rating": "Hodnotenie",
|
||||
"Search": "Hľadať",
|
||||
"Seeders": "Seederi",
|
||||
"Settings": "Nastavenia",
|
||||
"Usenet": "Usenet",
|
||||
"Custom": "Vlastné",
|
||||
"CustomFormat": "Vlastný formát",
|
||||
"Enable": "Povoliť",
|
||||
"IMDb": "IMDb",
|
||||
"Info": "Info",
|
||||
"LocalPath": "Miestna cesta",
|
||||
"Queued": "Fronta",
|
||||
"Updates": "Aktualizovať",
|
||||
"Indexers": "Indexery",
|
||||
"BlocklistReleases": "Blocklistnúť vydanie",
|
||||
"CustomFormats": "Vlastný formát",
|
||||
"Cutoff": "Hranica",
|
||||
"DeleteCustomFormat": "Klonovať vlastný formát",
|
||||
"Disabled": "zakázané",
|
||||
"Formats": "Formát",
|
||||
"Host": "Hostiteľ",
|
||||
"Hostname": "Názov hostiteľa",
|
||||
"iCalLink": "iCal Link",
|
||||
"List": "Zoznam",
|
||||
"Lists": "Zoznam",
|
||||
"Metadata": "metadáta",
|
||||
"Movie": "Film",
|
||||
"Quality": "kvalita",
|
||||
"Queue": "Fronta",
|
||||
"RecyclingBin": "Kôš",
|
||||
"Refresh": "Obnoviť",
|
||||
"RemotePathMappings": "Vzdialené mapovanie cesty",
|
||||
"Replace": "Nahradiť",
|
||||
"Runtime": "Beh programu",
|
||||
"Scheduled": "Naplánované",
|
||||
"SettingsRemotePathMappingLocalPath": "Miestna cesta",
|
||||
"UpdateMechanismHelpText": "Použiť vstavaný Prowlarr aktualizátor alebo skript",
|
||||
"URLBase": "Základ URL",
|
||||
"Protocol": "Protokol",
|
||||
"Files": "Súbor",
|
||||
"Enabled": "Povoliť",
|
||||
"Movies": "Film",
|
||||
"New": "Nový",
|
||||
"Ratings": "Hodnotenie",
|
||||
"Restrictions": "Obmedzenie",
|
||||
"RootFolder": "Koreňový priečinok",
|
||||
"RootFolders": "Koreňový priečinok",
|
||||
"RSS": "RSS",
|
||||
"Title": "Názov",
|
||||
"Titles": "Názov",
|
||||
"Username": "Používateľské meno"
|
||||
}
|
||||
|
||||
@@ -508,13 +508,13 @@
|
||||
"Add": "Lägg till",
|
||||
"CertValidationNoLocal": "Inaktiverad för lokala adresser",
|
||||
"BindAddress": "Bindningsadress",
|
||||
"AreYouSureYouWantToRemoveSelectedItemsFromQueue": "Är du säker på vill ta bort {0} föremål{1} från kön?",
|
||||
"AreYouSureYouWantToRemoveSelectedItemsFromQueue": "Är du säker på vill ta bort {0} föremål från kön?",
|
||||
"Announced": "Utannonserad",
|
||||
"Always": "Alltid",
|
||||
"AddToDownloadQueue": "Tillagd till nedladdnings kö",
|
||||
"AddToDownloadQueue": "Lägg till i nedladdningskö",
|
||||
"AddRestriction": "Lägg till restriktion",
|
||||
"AddImportExclusionHelpText": "Förhindra film att läggas till i Radarr från listor",
|
||||
"AddedToDownloadQueue": "Lägg till i nedladdningskö",
|
||||
"AddedToDownloadQueue": "Tillagd i nedladdningskö",
|
||||
"Unreleased": "Inte tillgänglig",
|
||||
"LinkHere": "här",
|
||||
"Paused": "Pausad",
|
||||
@@ -1066,5 +1066,8 @@
|
||||
"Collections": "Samling",
|
||||
"MonitorMovies": "Bevaka film",
|
||||
"NoCollections": "Inga filmer hittades, för att komma igång lägg till en ny film eller importera några befintliga.",
|
||||
"RssSyncHelpText": "Intervall i minuter. Ställ in på noll för att inaktivera (detta kommer stoppa all automatisk hämntning av utgåvor)"
|
||||
"RssSyncHelpText": "Intervall i minuter. Ställ in på noll för att inaktivera (detta kommer stoppa all automatisk hämntning av utgåvor)",
|
||||
"AnnouncedMsg": "Filmen tillkännages",
|
||||
"ApplicationURL": "Applikations-URL",
|
||||
"ApplicationUrlHelpText": "Denna applikations externa URL inklusive http(s)://, port och URL-bas"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user