mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-03-05 13:40:08 -05:00
Compare commits
74 Commits
v1.5.2.348
...
v1.6.3.360
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f20319fff1 | ||
|
|
20bcc00662 | ||
|
|
c4af3e746f | ||
|
|
660a162b7e | ||
|
|
20a3cad7fb | ||
|
|
77fe3f78fe | ||
|
|
d777cb8e29 | ||
|
|
15e7cc7ea8 | ||
|
|
04cf061275 | ||
|
|
d4cdeac69a | ||
|
|
e60fe05ee0 | ||
|
|
9a4c23797a | ||
|
|
acfdb5bae3 | ||
|
|
e2e65627ee | ||
|
|
4b8906ea62 | ||
|
|
f0c5d8ceea | ||
|
|
427802a50e | ||
|
|
0c9eae244a | ||
|
|
75ff2f41d3 | ||
|
|
d1ba208243 | ||
|
|
4e03ebadc4 | ||
|
|
0155ff60fd | ||
|
|
f0915638f3 | ||
|
|
56eb58aed1 | ||
|
|
8a891d07cf | ||
|
|
40a932cd28 | ||
|
|
4a81630073 | ||
|
|
0ff0fe2e68 | ||
|
|
51e33740b0 | ||
|
|
119164f729 | ||
|
|
ef0f8e25fd | ||
|
|
d21debe77f | ||
|
|
a3ccc3d0cf | ||
|
|
46d930e903 | ||
|
|
4561859c2b | ||
|
|
83166fb0b5 | ||
|
|
b98f9a945d | ||
|
|
e658e3fe48 | ||
|
|
9042525f22 | ||
|
|
7b551a0af1 | ||
|
|
31c2917bad | ||
|
|
419cce53f7 | ||
|
|
48cd1d9f6b | ||
|
|
8bd6a313b7 | ||
|
|
7cb465787e | ||
|
|
0b610ff9c8 | ||
|
|
5187460298 | ||
|
|
f0d9b43480 | ||
|
|
a1081cc554 | ||
|
|
c4bb1ba69a | ||
|
|
3a4c8db98c | ||
|
|
a522796798 | ||
|
|
e012eda0cf | ||
|
|
72ab2b34c4 | ||
|
|
aaba5b7499 | ||
|
|
455b76c45c | ||
|
|
596d3297da | ||
|
|
d05128ca33 | ||
|
|
f5b57db753 | ||
|
|
f7d7cca982 | ||
|
|
7c5409383e | ||
|
|
98db8f8bf8 | ||
|
|
88e793d76d | ||
|
|
0f31af6b89 | ||
|
|
65adf30f59 | ||
|
|
da75519524 | ||
|
|
ed1fb58242 | ||
|
|
d5daf6791c | ||
|
|
1f1a345d25 | ||
|
|
76a2f51533 | ||
|
|
8c0bc9ab4e | ||
|
|
b0c2b9119b | ||
|
|
87fdf17926 | ||
|
|
0f1b466a19 |
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -74,7 +74,7 @@ body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Trace Logs have been provided as applicable. Reports may be closed if the required logs are not provided.
|
||||
description: Trace logs are generally required for all bug reports
|
||||
description: Trace logs are generally required for all bug reports and contain `trace`. Info logs are invalid for bug reports and do not contain `debug` nor `trace`
|
||||
options:
|
||||
- label: I have followed the steps in the wiki link above and provided the required trace logs that are relevant and show this issue.
|
||||
- label: I have read and followed the steps in the wiki link above and provided the required trace logs - the logs contain `trace` - that are relevant and show this issue.
|
||||
required: true
|
||||
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '1.5.2'
|
||||
majorVersion: '1.6.3'
|
||||
minorVersion: $[counter('minorVersion', 1)]
|
||||
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||
@@ -1003,7 +1003,7 @@ stages:
|
||||
git add .
|
||||
if git status | grep -q modified
|
||||
then
|
||||
git commit -am 'Automated API Docs update'
|
||||
git commit -am 'Automated API Docs update [skip ci]'
|
||||
git push -f --set-upstream origin api-docs
|
||||
curl -X POST -H "Authorization: token ${GITHUBTOKEN}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/prowlarr/prowlarr/pulls -d '{"head":"api-docs","base":"develop","title":"Update API docs"}'
|
||||
else
|
||||
|
||||
@@ -578,7 +578,7 @@ EnhancedSelectInput.propTypes = {
|
||||
className: PropTypes.string,
|
||||
disabledClassName: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.number)]).isRequired,
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -12,7 +12,7 @@ function createMapStateToProps() {
|
||||
(state) => state.indexers,
|
||||
(value, indexers) => {
|
||||
const values = [];
|
||||
const groupedIndexers = _(indexers.items).groupBy((x) => x.protocol).map((val, key) => ({ protocol: key, indexers: val })).value();
|
||||
const groupedIndexers = _.map(_.groupBy(indexers.items, 'protocol'), (val, key) => ({ protocol: key, indexers: val }));
|
||||
|
||||
groupedIndexers.forEach((element) => {
|
||||
values.push({
|
||||
@@ -21,10 +21,11 @@ function createMapStateToProps() {
|
||||
});
|
||||
|
||||
if (element.indexers && element.indexers.length > 0) {
|
||||
element.indexers.forEach((subCat) => {
|
||||
element.indexers.forEach((indexer) => {
|
||||
values.push({
|
||||
key: subCat.id,
|
||||
value: subCat.name,
|
||||
key: indexer.id,
|
||||
value: indexer.name,
|
||||
isDisabled: !indexer.enable,
|
||||
parentKey: element.protocol === 'usenet' ? -1 : -2
|
||||
});
|
||||
});
|
||||
|
||||
@@ -67,6 +67,7 @@ function ProviderFieldFormGroup(props) {
|
||||
name,
|
||||
label,
|
||||
helpText,
|
||||
helpTextWarning,
|
||||
helpLink,
|
||||
placeholder,
|
||||
value,
|
||||
@@ -100,6 +101,7 @@ function ProviderFieldFormGroup(props) {
|
||||
name={name}
|
||||
label={label}
|
||||
helpText={helpText}
|
||||
helpTextWarning={helpTextWarning}
|
||||
helpLink={helpLink}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
@@ -126,6 +128,7 @@ ProviderFieldFormGroup.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.string,
|
||||
helpText: PropTypes.string,
|
||||
helpTextWarning: PropTypes.string,
|
||||
helpLink: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
value: PropTypes.any,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
|
||||
function PageSectionContent(props) {
|
||||
const {
|
||||
@@ -17,7 +19,7 @@ function PageSectionContent(props) {
|
||||
);
|
||||
} else if (!isFetching && !!error) {
|
||||
return (
|
||||
<div>{errorMessage}</div>
|
||||
<Alert kind={kinds.DANGER}>{errorMessage}</Alert>
|
||||
);
|
||||
} else if (isPopulated && !error) {
|
||||
return (
|
||||
|
||||
@@ -16,6 +16,38 @@
|
||||
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
|
||||
color: var(--white);
|
||||
transition: width 0.6s ease;
|
||||
|
||||
&.primary {
|
||||
background-color: var(--primaryColor);
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background-color: var(--dangerColor);
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, color(var(--dangerColor) shade(5%)), color(var(--dangerColor) shade(5%)) 5px, color(var(--dangerColor) shade(15%)) 5px, color(var(--dangerColor) shade(15%)) 10px);
|
||||
}
|
||||
}
|
||||
|
||||
&.success {
|
||||
background-color: var(--successColor);
|
||||
}
|
||||
|
||||
&.purple {
|
||||
background-color: var(--purple);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background-color: var(--warningColor);
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(45deg, var(--warningColor), var(--warningColor) 5px, color(var(--warningColor) tint(15%)) 5px, color(var(--warningColor) tint(15%)) 10px);
|
||||
}
|
||||
}
|
||||
|
||||
&.info {
|
||||
background-color: var(--infoColor);
|
||||
}
|
||||
}
|
||||
|
||||
.frontTextContainer {
|
||||
@@ -41,38 +73,6 @@
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.primary {
|
||||
background-color: var(--primaryColor);
|
||||
}
|
||||
|
||||
.danger {
|
||||
background-color: var(--dangerColor);
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, color(var(--dangerColor) shade(5%)), color(var(--dangerColor) shade(5%)) 5px, color(var(--dangerColor) shade(15%)) 5px, color(var(--dangerColor) shade(15%)) 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.success {
|
||||
background-color: var(--successColor);
|
||||
}
|
||||
|
||||
.purple {
|
||||
background-color: var(--purple);
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: var(--warningColor);
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(45deg, var(--warningColor), var(--warningColor) 5px, color(var(--warningColor) tint(15%)) 5px, color(var(--warningColor) tint(15%)) 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
background-color: var(--infoColor);
|
||||
}
|
||||
|
||||
.small {
|
||||
height: $progressBarSmallHeight;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ function ProgressBar(props) {
|
||||
{
|
||||
showText && width ?
|
||||
<div
|
||||
className={styles.backTextContainer}
|
||||
className={classNames(styles.backTextContainer, styles[kind])}
|
||||
style={{ width: actualWidth }}
|
||||
>
|
||||
<div className={styles.backText}>
|
||||
@@ -67,7 +67,7 @@ function ProgressBar(props) {
|
||||
{
|
||||
showText ?
|
||||
<div
|
||||
className={styles.frontTextContainer}
|
||||
className={classNames(styles.frontTextContainer, styles[kind])}
|
||||
style={{ width: progressPercent }}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
@@ -121,9 +122,9 @@ class History extends Component {
|
||||
|
||||
{
|
||||
!isFetchingAny && hasError &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadHistory')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
@@ -131,9 +132,9 @@ class History extends Component {
|
||||
// wait for the episodes to populate because they are never coming.
|
||||
|
||||
isPopulated && !hasError && !items.length &&
|
||||
<div>
|
||||
No history found
|
||||
</div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoHistoryFound')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
margin-right: 12px;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.filterContainer:last-child {
|
||||
|
||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import EnhancedSelectInput from 'Components/Form/EnhancedSelectInput';
|
||||
import NewznabCategorySelectInputConnector from 'Components/Form/NewznabCategorySelectInputConnector';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import Button from 'Components/Link/Button';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
@@ -26,7 +27,7 @@ const columns = [
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
name: 'sortName',
|
||||
label: translate('Name'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
@@ -89,7 +90,8 @@ class AddIndexerModalContent extends Component {
|
||||
filter: '',
|
||||
filterProtocols: [],
|
||||
filterLanguages: [],
|
||||
filterPrivacyLevels: []
|
||||
filterPrivacyLevels: [],
|
||||
filterCategories: []
|
||||
};
|
||||
}
|
||||
|
||||
@@ -121,7 +123,13 @@ class AddIndexerModalContent extends Component {
|
||||
.map((language) => ({ key: language, value: language }));
|
||||
|
||||
const filteredIndexers = indexers.filter((indexer) => {
|
||||
const { filter, filterProtocols, filterLanguages, filterPrivacyLevels } = this.state;
|
||||
const {
|
||||
filter,
|
||||
filterProtocols,
|
||||
filterLanguages,
|
||||
filterPrivacyLevels,
|
||||
filterCategories
|
||||
} = this.state;
|
||||
|
||||
if (!indexer.name.toLowerCase().includes(filter.toLocaleLowerCase()) && !indexer.description.toLowerCase().includes(filter.toLocaleLowerCase())) {
|
||||
return false;
|
||||
@@ -139,6 +147,18 @@ class AddIndexerModalContent extends Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filterCategories.length) {
|
||||
const { categories = [] } = indexer.capabilities || {};
|
||||
const flat = ({ id, subCategories = [] }) => [id, ...subCategories.flatMap(flat)];
|
||||
const flatCategories = categories
|
||||
.filter((item) => item.id < 100000)
|
||||
.flatMap(flat);
|
||||
|
||||
if (!filterCategories.every((item) => flatCategories.includes(item))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -165,7 +185,7 @@ class AddIndexerModalContent extends Component {
|
||||
|
||||
<div className={styles.filterRow}>
|
||||
<div className={styles.filterContainer}>
|
||||
<label className={styles.filterLabel}>Protocol</label>
|
||||
<label className={styles.filterLabel}>{translate('Protocol')}</label>
|
||||
<EnhancedSelectInput
|
||||
name="indexerProtocols"
|
||||
value={this.state.filterProtocols}
|
||||
@@ -175,7 +195,7 @@ class AddIndexerModalContent extends Component {
|
||||
</div>
|
||||
|
||||
<div className={styles.filterContainer}>
|
||||
<label className={styles.filterLabel}>Language</label>
|
||||
<label className={styles.filterLabel}>{translate('Language')}</label>
|
||||
<EnhancedSelectInput
|
||||
name="indexerLanguages"
|
||||
value={this.state.filterLanguages}
|
||||
@@ -185,7 +205,7 @@ class AddIndexerModalContent extends Component {
|
||||
</div>
|
||||
|
||||
<div className={styles.filterContainer}>
|
||||
<label className={styles.filterLabel}>Privacy</label>
|
||||
<label className={styles.filterLabel}>{translate('Privacy')}</label>
|
||||
<EnhancedSelectInput
|
||||
name="indexerPrivacyLevels"
|
||||
value={this.state.filterPrivacyLevels}
|
||||
@@ -193,6 +213,15 @@ class AddIndexerModalContent extends Component {
|
||||
onChange={({ value }) => this.setState({ filterPrivacyLevels: value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.filterContainer}>
|
||||
<label className={styles.filterLabel}>{translate('Categories')}</label>
|
||||
<NewznabCategorySelectInputConnector
|
||||
name="indexerCategories"
|
||||
value={this.state.filterCategories}
|
||||
onChange={({ value }) => this.setState({ filterCategories: value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Alert
|
||||
@@ -212,7 +241,7 @@ class AddIndexerModalContent extends Component {
|
||||
isFetching ? <LoadingIndicator /> : null
|
||||
}
|
||||
{
|
||||
error ? <div>{errorMessage}</div> : null
|
||||
error ? <Alert kind={kinds.DANGER}>{errorMessage}</Alert> : null
|
||||
}
|
||||
{
|
||||
isPopulated && !!indexers.length ?
|
||||
@@ -237,6 +266,15 @@ class AddIndexerModalContent extends Component {
|
||||
</Table> :
|
||||
null
|
||||
}
|
||||
{
|
||||
isPopulated && !!indexers.length && !filteredIndexers.length ?
|
||||
<Alert
|
||||
kind={kinds.WARNING}
|
||||
>
|
||||
{translate('NoIndexersFound')}
|
||||
</Alert> :
|
||||
null
|
||||
}
|
||||
</Scroller>
|
||||
</ModalBody>
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ function IndexerStatusCell(props: IndexerStatusCellProps) {
|
||||
className={styles.statusIcon}
|
||||
kind={enabled ? enableKind : kinds.DEFAULT}
|
||||
name={enabled ? enableIcon : icons.BLOCKLIST}
|
||||
title={enabled ? enableTitle : translate('EnabledIndexerIsDisabled')}
|
||||
title={enabled ? enableTitle : translate('Disabled')}
|
||||
/>
|
||||
}
|
||||
{status ? (
|
||||
|
||||
@@ -13,6 +13,10 @@ import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import TagListConnector from 'Components/TagListConnector';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import DeleteIndexerModal from 'Indexer/Delete/DeleteIndexerModal';
|
||||
@@ -149,6 +153,7 @@ function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
|
||||
</DescriptionList>
|
||||
</div>
|
||||
</FieldSet>
|
||||
|
||||
<FieldSet legend={translate('SearchCapabilities')}>
|
||||
<div>
|
||||
<DescriptionList>
|
||||
@@ -237,6 +242,54 @@ function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
|
||||
</DescriptionList>
|
||||
</div>
|
||||
</FieldSet>
|
||||
|
||||
{capabilities.categories !== null &&
|
||||
capabilities.categories.length > 0 ? (
|
||||
<FieldSet legend={translate('IndexerCategories')}>
|
||||
<Table
|
||||
columns={[
|
||||
{
|
||||
name: 'id',
|
||||
label: translate('Id'),
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: translate('Name'),
|
||||
isVisible: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{capabilities.categories
|
||||
.sort((a, b) => a.id - b.id)
|
||||
.map((category) => {
|
||||
return (
|
||||
<TableBody key={category.id}>
|
||||
<TableRow key={category.id}>
|
||||
<TableRowCell>{category.id}</TableRowCell>
|
||||
<TableRowCell>{category.name}</TableRowCell>
|
||||
</TableRow>
|
||||
{category.subCategories !== null &&
|
||||
category.subCategories.length > 0
|
||||
? category.subCategories
|
||||
.sort((a, b) => a.id - b.id)
|
||||
.map((subCategory) => {
|
||||
return (
|
||||
<TableRow key={subCategory.id}>
|
||||
<TableRowCell>{subCategory.id}</TableRowCell>
|
||||
<TableRowCell>
|
||||
{subCategory.name}
|
||||
</TableRowCell>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</TableBody>
|
||||
);
|
||||
})}
|
||||
</Table>
|
||||
</FieldSet>
|
||||
) : null}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import BarChart from 'Components/Chart/BarChart';
|
||||
import DoughnutChart from 'Components/Chart/DoughnutChart';
|
||||
import StackedBarChart from 'Components/Chart/StackedBarChart';
|
||||
@@ -178,9 +179,9 @@ function Stats(props) {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div className={styles.errorMessage}>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{getErrorMessage(error, 'Failed to load indexer stats from API')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
@@ -10,7 +11,9 @@ import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import { align, icons, sortDirections } from 'Helpers/Props';
|
||||
import { align, icons, kinds, sortDirections } from 'Helpers/Props';
|
||||
import AddIndexerModal from 'Indexer/Add/AddIndexerModal';
|
||||
import EditIndexerModalConnector from 'Indexer/Edit/EditIndexerModalConnector';
|
||||
import NoIndexer from 'Indexer/NoIndexer';
|
||||
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
@@ -53,7 +56,9 @@ class SearchIndex extends Component {
|
||||
lastToggled: null,
|
||||
allSelected: false,
|
||||
allUnselected: false,
|
||||
selectedState: {}
|
||||
selectedState: {},
|
||||
isAddIndexerModalOpen: false,
|
||||
isEditIndexerModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -180,6 +185,22 @@ class SearchIndex extends Component {
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onAddIndexerPress = () => {
|
||||
this.setState({ isAddIndexerModalOpen: true });
|
||||
};
|
||||
|
||||
onAddIndexerModalClose = () => {
|
||||
this.setState({ isAddIndexerModalOpen: false });
|
||||
};
|
||||
|
||||
onAddIndexerSelectIndexer = () => {
|
||||
this.setState({ isEditIndexerModalOpen: true });
|
||||
};
|
||||
|
||||
onEditIndexerModalClose = () => {
|
||||
this.setState({ isEditIndexerModalOpen: false });
|
||||
};
|
||||
|
||||
onJumpBarItemPress = (jumpToCharacter) => {
|
||||
this.setState({ jumpToCharacter });
|
||||
};
|
||||
@@ -251,7 +272,9 @@ class SearchIndex extends Component {
|
||||
jumpToCharacter,
|
||||
selectedState,
|
||||
allSelected,
|
||||
allUnselected
|
||||
allUnselected,
|
||||
isAddIndexerModalOpen,
|
||||
isEditIndexerModalOpen
|
||||
} = this.state;
|
||||
|
||||
const selectedIndexerIds = this.getSelectedIds();
|
||||
@@ -309,9 +332,9 @@ class SearchIndex extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div className={styles.errorMessage}>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{getErrorMessage(error, 'Failed to load search results from API')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
@@ -347,6 +370,17 @@ class SearchIndex extends Component {
|
||||
!error && !isFetching && hasIndexers && !items.length &&
|
||||
<NoSearchResults totalItems={totalItems} />
|
||||
}
|
||||
|
||||
<AddIndexerModal
|
||||
isOpen={isAddIndexerModalOpen}
|
||||
onModalClose={this.onAddIndexerModalClose}
|
||||
onSelectIndexer={this.onAddIndexerSelectIndexer}
|
||||
/>
|
||||
|
||||
<EditIndexerModalConnector
|
||||
isOpen={isEditIndexerModalOpen}
|
||||
onModalClose={this.onEditIndexerModalClose}
|
||||
/>
|
||||
</PageContentBody>
|
||||
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Label from 'Components/Label';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import { kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import Tooltip from '../../Components/Tooltip/Tooltip';
|
||||
|
||||
function CategoryLabel({ categories }) {
|
||||
const sortedCategories = categories.filter((cat) => cat.name !== undefined).sort((c) => c.id);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
@@ -8,7 +9,7 @@ import FormLabel from 'Components/Form/FormLabel';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
@@ -49,9 +50,9 @@ class DevelopmentSettings extends Component {
|
||||
|
||||
{
|
||||
!isFetching && error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadDevelopmentSettings')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Form from 'Components/Form/Form';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
@@ -123,9 +124,9 @@ class GeneralSettings extends Component {
|
||||
|
||||
{
|
||||
!isFetching && error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadGeneralSettings')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
@@ -8,7 +9,7 @@ import FormLabel from 'Components/Form/FormLabel';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
||||
import themes from 'Styles/Themes';
|
||||
import titleCase from 'Utilities/String/titleCase';
|
||||
@@ -80,9 +81,9 @@ class UISettings extends Component {
|
||||
|
||||
{
|
||||
!isFetching && error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadUISettings')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -41,7 +41,7 @@ export const defaultState = {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
sortKey: 'name',
|
||||
sortKey: 'sortName',
|
||||
sortDirection: sortDirections.ASCENDING,
|
||||
items: []
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export const defaultState = {
|
||||
columns: [
|
||||
{
|
||||
name: 'status',
|
||||
columnLabel: translate('ReleaseStatus'),
|
||||
columnLabel: translate('IndexerStatus'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
isModifiable: false
|
||||
|
||||
@@ -162,7 +162,7 @@ module.exports = {
|
||||
inputHoverBackgroundColor: 'rgba(255, 255, 255, 0.20)',
|
||||
inputSelectedBackgroundColor: 'rgba(255, 255, 255, 0.05)',
|
||||
advancedFormLabelColor: '#ff902b',
|
||||
disabledCheckInputColor: '#ddd',
|
||||
disabledCheckInputColor: '#999',
|
||||
disabledInputColor: '#808080',
|
||||
|
||||
//
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
@@ -8,7 +9,7 @@ import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import BackupRow from './BackupRow';
|
||||
import RestoreBackupModalConnector from './RestoreBackupModalConnector';
|
||||
@@ -107,16 +108,16 @@ class Backups extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadBackups')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
noBackups &&
|
||||
<div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoBackupsAreAvailable')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
@@ -11,7 +12,7 @@ import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import LogsTableRow from './LogsTableRow';
|
||||
|
||||
@@ -82,9 +83,9 @@ function LogsTable(props) {
|
||||
|
||||
{
|
||||
isPopulated && !error && !items.length &&
|
||||
<div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
No events found
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -96,7 +96,14 @@ class LogsTableConnector extends Component {
|
||||
};
|
||||
|
||||
onClearLogsPress = () => {
|
||||
this.props.executeCommand({ name: commandNames.CLEAR_LOGS });
|
||||
this.props.executeCommand({
|
||||
name: commandNames.CLEAR_LOGS,
|
||||
commandFinished: this.onCommandFinished
|
||||
});
|
||||
};
|
||||
|
||||
onCommandFinished = () => {
|
||||
this.props.gotoLogsFirstPage();
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
@@ -11,7 +11,7 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import LogsNavMenu from '../LogsNavMenu';
|
||||
import LogFilesTableRow from './LogFilesTableRow';
|
||||
@@ -118,9 +118,9 @@ class LogFiles extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !items.length &&
|
||||
<div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoLogFiles')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
</PageContentBody>
|
||||
</PageContent>
|
||||
|
||||
@@ -50,12 +50,6 @@ class LogFilesConnector extends Component {
|
||||
this.props.fetchLogFiles();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.deleteFilesExecuting && !this.props.deleteFilesExecuting) {
|
||||
this.props.fetchLogFiles();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
@@ -64,7 +58,14 @@ class LogFilesConnector extends Component {
|
||||
};
|
||||
|
||||
onDeleteFilesPress = () => {
|
||||
this.props.executeCommand({ name: commandNames.DELETE_LOG_FILES });
|
||||
this.props.executeCommand({
|
||||
name: commandNames.DELETE_LOG_FILES,
|
||||
commandFinished: this.onCommandFinished
|
||||
});
|
||||
};
|
||||
|
||||
onCommandFinished = () => {
|
||||
this.props.fetchLogFiles();
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Icon from 'Components/Icon';
|
||||
import Label from 'Components/Label';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
@@ -61,9 +62,9 @@ class Updates extends Component {
|
||||
|
||||
{
|
||||
noUpdates &&
|
||||
<div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoUpdatesAreAvailable')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||
<PackageReference Include="NunitXml.TestLogger" Version="3.0.131" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -10,7 +10,9 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
// Indexer Urls
|
||||
[TestCase(@"https://iptorrents.com/torrents/rss?u=mySecret;tp=mySecret;l5;download")]
|
||||
[TestCase(@"http://rss.torrentleech.org/mySecret")]
|
||||
[TestCase(@"http://rss.torrentleech.org/rss/download/12345/01233210/filename.torrent")]
|
||||
[TestCase(@"https://rss24h.torrentleech.org/mySecret")]
|
||||
[TestCase(@"http://rss.torrentleech.org/rss/download/12345/01233210/file.name-RLSGRP.torrent")]
|
||||
[TestCase(@"https://www.torrentleech.org/rss/download/12345/01233210/file.name-RLSGRP.torrent")]
|
||||
[TestCase(@"http://www.bitmetv.org/rss.php?uid=mySecret&passkey=mySecret")]
|
||||
[TestCase(@"https://rss.omgwtfnzbs.org/rss-search.php?catid=19,20&user=sonarr&api=mySecret&eng=1")]
|
||||
[TestCase(@"https://dognzb.cr/fetch/2b51db35e1912ffc138825a12b9933d2/2b51db35e1910123321025a12b9933d2")]
|
||||
@@ -79,6 +81,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
// Deluge
|
||||
[TestCase(@",{""download_location"": ""C:\Users\\mySecret mySecret\\Downloads""}")]
|
||||
[TestCase(@",{""download_location"": ""/home/mySecret/Downloads""}")]
|
||||
[TestCase(@",{""download_location"": ""/Users/mySecret/Downloads""}")]
|
||||
[TestCase(@"auth.login(""mySecret"")")]
|
||||
|
||||
// Download Station
|
||||
|
||||
@@ -91,6 +91,7 @@ namespace NzbDrone.Common.Http
|
||||
{
|
||||
request.Method = HttpMethod.Get;
|
||||
request.ContentData = null;
|
||||
request.ContentSummary = null;
|
||||
}
|
||||
|
||||
// Save to add to final response
|
||||
|
||||
@@ -63,6 +63,8 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
public bool HasHttpError => (int)StatusCode >= 400;
|
||||
|
||||
public bool HasHttpServerError => (int)StatusCode >= 500;
|
||||
|
||||
public bool HasHttpRedirect => StatusCode == HttpStatusCode.Moved ||
|
||||
StatusCode == HttpStatusCode.Found ||
|
||||
StatusCode == HttpStatusCode.SeeOther ||
|
||||
|
||||
@@ -9,61 +9,61 @@ namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
private static readonly Regex[] CleansingRules =
|
||||
{
|
||||
// Url
|
||||
new Regex(@"(?<=[?&: ;])(apikey|api_key|(?:(?:access|api)[-_]?)?token|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pid|pwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"rss\.torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"rss\.torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
|
||||
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=authkey = "")(?<secret>[^&=]+?)(?="")", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=beyond-hd\.[a-z]+/api/torrents/)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=beyond-hd\.[a-z]+/torrent/download/[\w\d-]+[.]\d+[.])(?<secret>[a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// Url
|
||||
new (@"(?<=[?&: ;])(apikey|api_key|(?:(?:access|api)[-_]?)?token|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pid|pwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"rss(24h)?\.torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
|
||||
new (@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"(?<=authkey = "")(?<secret>[^&=]+?)(?="")", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"(?<=beyond-hd\.[a-z]+/api/torrents/)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"(?<=beyond-hd\.[a-z]+/torrent/download/[\w\d-]+[.]\d+[.])(?<secret>[a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// UNIT3D
|
||||
new Regex(@"(?<=[a-z0-9-]+\.[a-z]+/torrent/download/\d+\.)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// UNIT3D
|
||||
new (@"(?<=[a-z0-9-]+\.[a-z]+/torrent/download/\d+\.)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Path
|
||||
new Regex(@"""C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"""/home/(?<secret>[^/""]+?)(/|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// Path
|
||||
new (@"""C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"""/(home|Users)/(?<secret>[^/""]+?)(/|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
|
||||
new Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
|
||||
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
|
||||
new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
|
||||
|
||||
// NzbGet
|
||||
new Regex(@"""Name""\s*:\s*""[^""]*(username|password)""\s*,\s*""Value""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// NzbGet
|
||||
new (@"""Name""\s*:\s*""[^""]*(username|password)""\s*,\s*""Value""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Sabnzbd
|
||||
new Regex(@"""[^""]*(username|password|api_?key|nzb_key)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"""email_(account|to|from|pwd)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// Sabnzbd
|
||||
new (@"""[^""]*(username|password|api_?key|nzb_key)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"""email_(account|to|from|pwd)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// uTorrent
|
||||
new Regex(@"\[""[a-z._]*(username|password)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"\[""(boss_key|boss_key_salt|proxy\.proxy)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// uTorrent
|
||||
new (@"\[""[a-z._]*(username|password)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"\[""(boss_key|boss_key_salt|proxy\.proxy)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Deluge
|
||||
new Regex(@"auth.login\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// Deluge
|
||||
new (@"auth.login\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// BroadcastheNet (;torrent_pass|torrents_notify_ is for MTV)
|
||||
new Regex(@"""?method""?\s*:\s*""(getTorrents)"",\s*""?params""?\s*:\s*\[\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"getTorrents\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=\?|&|;|=)(authkey|torrent_pass|torrents_notify)[_=](?<secret>[^&=]+?)(?=""|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// BroadcastheNet (;torrent_pass|torrents_notify_ is for MTV)
|
||||
new (@"""?method""?\s*:\s*""(getTorrents)"",\s*""?params""?\s*:\s*\[\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"getTorrents\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"(?<=\?|&|;|=)(authkey|torrent_pass|torrents_notify)[_=](?<secret>[^&=]+?)(?=""|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Plex
|
||||
new Regex(@"(?<=\?|&)(X-Plex-Client-Identifier|X-Plex-Token)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// Plex
|
||||
new (@"(?<=\?|&)(X-Plex-Client-Identifier|X-Plex-Token)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Indexer Responses
|
||||
new Regex(@"(?:avistaz|exoticaz|cinemaz|privatehd)\.[a-z]{2,3}/rss/download/(?<secret>[^&=]+?)/(?<secret>[^&=]+?)\.torrent", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?:animebytes)\.[a-z]{2,3}/torrent/[0-9]+/download/(?<secret>[^&=]+?)[""]", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@",""info_hash"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"""token"":""(?<secret>[^&=]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@",""pass[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@",""rss[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// Indexer Responses
|
||||
new (@"(?:avistaz|exoticaz|cinemaz|privatehd)\.[a-z]{2,3}/rss/download/(?<secret>[^&=]+?)/(?<secret>[^&=]+?)\.torrent", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"(?:animebytes)\.[a-z]{2,3}/torrent/[0-9]+/download/(?<secret>[^&=]+?)[""]", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@",""info_hash"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"""token"":""(?<secret>[^&=]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@",""pass[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@",""rss[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
};
|
||||
|
||||
private static readonly Regex CleanseRemoteIPRegex = new Regex(@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
|
||||
private static readonly Regex CleanseRemoteIPRegex = new (@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
|
||||
|
||||
public static string Cleanse(string message)
|
||||
{
|
||||
@@ -75,15 +75,15 @@ namespace NzbDrone.Common.Instrumentation
|
||||
foreach (var regex in CleansingRules)
|
||||
{
|
||||
message = regex.Replace(message, m =>
|
||||
{
|
||||
var value = m.Value;
|
||||
foreach (var capture in m.Groups["secret"].Captures.OfType<Capture>().Reverse())
|
||||
{
|
||||
var value = m.Value;
|
||||
foreach (var capture in m.Groups["secret"].Captures.OfType<Capture>().Reverse())
|
||||
{
|
||||
value = value.Replace(capture.Index - m.Index, capture.Length, "(removed)");
|
||||
}
|
||||
value = value.Replace(capture.Index - m.Index, capture.Length, "(removed)");
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
message = CleanseRemoteIPRegex.Replace(message, CleanseRemoteIP);
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NLog" Version="5.1.0" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.0" />
|
||||
<PackageReference Include="NLog" Version="5.2.0" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.0" />
|
||||
<PackageReference Include="Npgsql" Version="5.0.11" />
|
||||
<PackageReference Include="Sentry" Version="3.29.1" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Datastore.Migration;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Datastore.Migration
|
||||
{
|
||||
[TestFixture]
|
||||
public class apprise_server_urlFixture : MigrationTest<apprise_server_url>
|
||||
{
|
||||
[Test]
|
||||
public void should_rename_server_url_setting_for_apprise()
|
||||
{
|
||||
var db = WithMigrationTestDb(c =>
|
||||
{
|
||||
c.Insert.IntoTable("Notifications").Row(new
|
||||
{
|
||||
Name = "Apprise",
|
||||
Implementation = "Apprise",
|
||||
Settings = new
|
||||
{
|
||||
BaseUrl = "http://localhost:8000",
|
||||
NotificationType = 0
|
||||
}.ToJson(),
|
||||
ConfigContract = "AppriseSettings",
|
||||
OnHealthIssue = true,
|
||||
IncludeHealthWarnings = true,
|
||||
OnApplicationUpdate = true,
|
||||
OnGrab = true,
|
||||
IncludeManualGrabs = true
|
||||
});
|
||||
});
|
||||
|
||||
var items = db.Query<NotificationDefinition31>("SELECT * FROM \"Notifications\"");
|
||||
|
||||
items.Should().HaveCount(1);
|
||||
|
||||
items.First().Settings.Should().NotContainKey("baseUrl");
|
||||
items.First().Settings.Should().ContainKey("serverUrl");
|
||||
items.First().Settings.GetValueOrDefault("serverUrl").Should().Be("http://localhost:8000");
|
||||
}
|
||||
}
|
||||
|
||||
public class NotificationDefinition31
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int Priority { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Implementation { get; set; }
|
||||
public Dictionary<string, string> Settings { get; set; }
|
||||
public string ConfigContract { get; set; }
|
||||
public bool OnHealthIssue { get; set; }
|
||||
public bool IncludeHealthWarnings { get; set; }
|
||||
public bool OnApplicationUpdate { get; set; }
|
||||
public bool OnGrab { get; set; }
|
||||
public bool IncludeManualGrabs { get; set; }
|
||||
public List<int> Tags { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ namespace NzbDrone.Core.Annotations
|
||||
public string Label { get; set; }
|
||||
public string Unit { get; set; }
|
||||
public string HelpText { get; set; }
|
||||
public string HelpTextWarning { get; set; }
|
||||
public string HelpLink { get; set; }
|
||||
public FieldType Type { get; set; }
|
||||
public bool Advanced { get; set; }
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace NzbDrone.Core.Applications
|
||||
yield return new ApplicationDefinition
|
||||
{
|
||||
Name = GetType().Name,
|
||||
SyncLevel = ApplicationSyncLevel.AddOnly,
|
||||
SyncLevel = ApplicationSyncLevel.FullSync,
|
||||
Implementation = GetType().Name,
|
||||
Settings = config
|
||||
};
|
||||
|
||||
@@ -6,6 +6,6 @@ namespace NzbDrone.Core.Applications
|
||||
{
|
||||
public ApplicationSyncLevel SyncLevel { get; set; }
|
||||
|
||||
public override bool Enable => SyncLevel == ApplicationSyncLevel.AddOnly || SyncLevel == ApplicationSyncLevel.FullSync;
|
||||
public override bool Enable => SyncLevel is ApplicationSyncLevel.AddOnly or ApplicationSyncLevel.FullSync;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,12 +183,12 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
{
|
||||
var cacheKey = $"{Settings.BaseUrl}";
|
||||
var schemas = _schemaCache.Get(cacheKey, () => _sonarrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
|
||||
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "animeCategories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.seasonPackSeedTime" };
|
||||
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "animeCategories", "animeStandardFormatSearch", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.seasonPackSeedTime" };
|
||||
|
||||
if (id == 0)
|
||||
{
|
||||
// Ensuring backward compatibility with older versions on first sync
|
||||
syncFields.AddRange(new List<string> { "animeStandardFormatSearch", "additionalParameters" });
|
||||
syncFields.AddRange(new List<string> { "additionalParameters" });
|
||||
}
|
||||
|
||||
var newznab = schemas.First(i => i.Implementation == "Newznab");
|
||||
@@ -218,6 +218,11 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "animeCategories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.AnimeSyncCategories.ToArray()));
|
||||
|
||||
if (sonarrIndexer.Fields.Any(x => x.Name == "animeStandardFormatSearch"))
|
||||
{
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "animeStandardFormatSearch").Value = Settings.SyncAnimeStandardFormatSearch;
|
||||
}
|
||||
|
||||
if (indexer.Protocol == DownloadProtocol.Torrent)
|
||||
{
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders;
|
||||
|
||||
@@ -38,6 +38,10 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
var otherApiPath = other.Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var apiPathCompare = apiPath.Equals(otherApiPath);
|
||||
|
||||
var animeStandardFormatSearch = Fields.FirstOrDefault(x => x.Name == "animeStandardFormatSearch")?.Value == null ? null : (bool?)Convert.ToBoolean(Fields.FirstOrDefault(x => x.Name == "animeStandardFormatSearch").Value);
|
||||
var otherAnimeStandardFormatSearch = other.Fields.FirstOrDefault(x => x.Name == "animeStandardFormatSearch")?.Value == null ? null : (bool?)Convert.ToBoolean(other.Fields.FirstOrDefault(x => x.Name == "animeStandardFormatSearch").Value);
|
||||
var animeStandardFormatSearchCompare = animeStandardFormatSearch == otherAnimeStandardFormatSearch;
|
||||
|
||||
var minimumSeeders = Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
var otherMinimumSeeders = other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
var minimumSeedersCompare = minimumSeeders == otherMinimumSeeders;
|
||||
@@ -61,7 +65,7 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
other.Implementation == Implementation &&
|
||||
other.Priority == Priority &&
|
||||
other.Id == Id &&
|
||||
apiKey && apiPathCompare && baseUrl && cats && animeCats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && seasonSeedTimeCompare;
|
||||
apiKey && apiPathCompare && baseUrl && cats && animeCats && animeStandardFormatSearchCompare && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && seasonSeedTimeCompare;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,9 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
[FieldDefinition(4, Label = "Anime Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]
|
||||
public IEnumerable<int> AnimeSyncCategories { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Sync Anime Standard Format Search", Type = FieldType.Checkbox, Advanced = true, HelpText = "Sync also searching for anime using the standard numbering")]
|
||||
public bool SyncAnimeStandardFormatSearch { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using Dapper;
|
||||
using FluentMigrator;
|
||||
@@ -17,33 +18,43 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
|
||||
private void MigrateToServerUrl(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
using var selectCommand = conn.CreateCommand();
|
||||
selectCommand.Transaction = tran;
|
||||
selectCommand.CommandText = "SELECT \"Id\", \"Settings\" FROM \"Notifications\" WHERE \"Implementation\" = 'Apprise'";
|
||||
var updatedNotifications = new List<object>();
|
||||
|
||||
using var reader = selectCommand.ExecuteReader();
|
||||
|
||||
while (reader.Read())
|
||||
using (var selectCommand = conn.CreateCommand())
|
||||
{
|
||||
var id = reader.GetInt32(0);
|
||||
var settings = reader.GetString(1);
|
||||
selectCommand.Transaction = tran;
|
||||
selectCommand.CommandText = "SELECT \"Id\", \"Settings\" FROM \"Notifications\" WHERE \"Implementation\" = 'Apprise'";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(settings))
|
||||
using var reader = selectCommand.ExecuteReader();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
var jsonObject = Json.Deserialize<JObject>(settings);
|
||||
var id = reader.GetInt32(0);
|
||||
var settings = reader.GetString(1);
|
||||
|
||||
if (jsonObject.ContainsKey("baseUrl"))
|
||||
if (!string.IsNullOrWhiteSpace(settings))
|
||||
{
|
||||
jsonObject.Add("serverUrl", jsonObject.Value<string>("baseUrl"));
|
||||
jsonObject.Remove("baseUrl");
|
||||
var jsonObject = Json.Deserialize<JObject>(settings);
|
||||
|
||||
if (jsonObject.ContainsKey("baseUrl"))
|
||||
{
|
||||
jsonObject.Add("serverUrl", jsonObject.Value<string>("baseUrl"));
|
||||
jsonObject.Remove("baseUrl");
|
||||
}
|
||||
|
||||
settings = jsonObject.ToJson();
|
||||
}
|
||||
|
||||
settings = jsonObject.ToJson();
|
||||
updatedNotifications.Add(new
|
||||
{
|
||||
Id = id,
|
||||
Settings = settings
|
||||
});
|
||||
}
|
||||
|
||||
var parameters = new { Settings = settings, Id = id };
|
||||
conn.Execute("UPDATE Notifications SET Settings = @Settings WHERE Id = @Id", parameters, transaction: tran);
|
||||
}
|
||||
|
||||
var updateNotificationsSql = "UPDATE \"Notifications\" SET \"Settings\" = @Settings WHERE \"Id\" = @Id";
|
||||
conn.Execute(updateNotificationsSql, updatedNotifications, transaction: tran);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
@@ -22,16 +22,16 @@ namespace NzbDrone.Core.Download.Clients.Aria2
|
||||
|
||||
public Aria2(IAria2Proxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
|
||||
protected override string AddFromMagnetLink(TorrentInfo release, string hash, string magnetLink)
|
||||
{
|
||||
var gid = _proxy.AddUri(Settings, magnetLink);
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace NzbDrone.Core.Download.Clients.Aria2
|
||||
return hash;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent)
|
||||
protected override string AddFromTorrentFile(TorrentInfo release, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
var gid = _proxy.AddTorrent(Settings, fileContent);
|
||||
|
||||
@@ -120,7 +120,7 @@ namespace NzbDrone.Core.Download.Clients.Aria2
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentLink(ReleaseInfo release, string hash, string torrentLink)
|
||||
protected override string AddFromTorrentLink(TorrentInfo release, string hash, string torrentLink)
|
||||
{
|
||||
var gid = _proxy.AddUri(Settings, torrentLink);
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
@@ -17,20 +17,20 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
public override bool PreferTorrentFile => true;
|
||||
|
||||
public TorrentBlackhole(ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentLink(ReleaseInfo release, string hash, string torrentLink)
|
||||
protected override string AddFromTorrentLink(TorrentInfo release, string hash, string torrentLink)
|
||||
{
|
||||
throw new NotImplementedException("Blackhole does not support redirected indexers.");
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
|
||||
protected override string AddFromMagnetLink(TorrentInfo release, string hash, string magnetLink)
|
||||
{
|
||||
if (!Settings.SaveMagnetFiles)
|
||||
{
|
||||
@@ -54,7 +54,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent)
|
||||
protected override string AddFromTorrentFile(TorrentInfo release, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
var title = release.Title;
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
@@ -20,16 +20,16 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
|
||||
public Deluge(IDelugeProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
|
||||
protected override string AddFromMagnetLink(TorrentInfo release, string hash, string magnetLink)
|
||||
{
|
||||
var actualHash = _proxy.AddTorrentFromMagnet(magnetLink, Settings);
|
||||
|
||||
@@ -38,8 +38,10 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
throw new DownloadClientException("Deluge failed to add magnet " + magnetLink);
|
||||
}
|
||||
|
||||
// _proxy.SetTorrentSeedingConfiguration(actualHash, remoteMovie.SeedConfiguration, Settings);
|
||||
_proxy.SetTorrentSeedingConfiguration(actualHash, release.SeedConfiguration, Settings);
|
||||
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
|
||||
if (category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_proxy.SetTorrentLabel(actualHash, category, Settings);
|
||||
@@ -53,7 +55,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
return actualHash.ToUpper();
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent)
|
||||
protected override string AddFromTorrentFile(TorrentInfo release, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
var actualHash = _proxy.AddTorrentFromFile(filename, fileContent, Settings);
|
||||
|
||||
@@ -62,8 +64,10 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
throw new DownloadClientException("Deluge failed to add torrent " + filename);
|
||||
}
|
||||
|
||||
// _proxy.SetTorrentSeedingConfiguration(actualHash, release.SeedConfiguration, Settings);
|
||||
_proxy.SetTorrentSeedingConfiguration(actualHash, release.SeedConfiguration, Settings);
|
||||
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
|
||||
if (category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_proxy.SetTorrentLabel(actualHash, category, Settings);
|
||||
@@ -117,12 +121,12 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
case WebExceptionStatus.ConnectionClosed:
|
||||
return new NzbDroneValidationFailure("UseSsl", "Verify SSL settings")
|
||||
{
|
||||
DetailedDescription = "Please verify your SSL configuration on both Deluge and NzbDrone."
|
||||
DetailedDescription = "Please verify your SSL configuration on both Deluge and Prowlarr."
|
||||
};
|
||||
case WebExceptionStatus.SecureChannelFailure:
|
||||
return new NzbDroneValidationFailure("UseSsl", "Unable to connect through SSL")
|
||||
{
|
||||
DetailedDescription = "Drone is unable to connect to Deluge using SSL. This problem could be computer related. Please try to configure both drone and Deluge to not use SSL."
|
||||
DetailedDescription = "Prowlarr is unable to connect to Deluge using SSL. This problem could be computer related. Please try to configure both Prowlarr and Deluge to not use SSL."
|
||||
};
|
||||
default:
|
||||
return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message);
|
||||
@@ -211,7 +215,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentLink(ReleaseInfo release, string hash, string torrentLink)
|
||||
protected override string AddFromTorrentLink(TorrentInfo release, string hash, string torrentLink)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
{
|
||||
public class DelugeException : DownloadClientException
|
||||
{
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
var filter = new Dictionary<string, object>();
|
||||
|
||||
// TODO: get_torrents_status returns the files as well, which starts to cause deluge timeouts when you get enough season packs.
|
||||
//var response = ProcessRequest<Dictionary<String, DelugeTorrent>>(settings, "core.get_torrents_status", filter, new String[0]);
|
||||
// var response = ProcessRequest<Dictionary<String, DelugeTorrent>>(settings, "core.get_torrents_status", filter, new String[0]);
|
||||
var response = ProcessRequest<DelugeUpdateUIResult>(settings, "web.update_ui", RequiredProperties, filter);
|
||||
|
||||
return GetTorrents(response);
|
||||
@@ -93,7 +93,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
var filter = new Dictionary<string, object>();
|
||||
filter.Add("label", label);
|
||||
|
||||
//var response = ProcessRequest<Dictionary<String, DelugeTorrent>>(settings, "core.get_torrents_status", filter, new String[0]);
|
||||
// var response = ProcessRequest<Dictionary<String, DelugeTorrent>>(settings, "core.get_torrents_status", filter, new String[0]);
|
||||
var response = ProcessRequest<DelugeUpdateUIResult>(settings, "web.update_ui", RequiredProperties, filter);
|
||||
|
||||
return GetTorrents(response);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
{
|
||||
internal class DelugeTorrentStatus
|
||||
public class DelugeTorrentStatus
|
||||
{
|
||||
public const string Paused = "Paused";
|
||||
public const string Queued = "Queued";
|
||||
|
||||
@@ -7,9 +7,9 @@ using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
@@ -30,11 +30,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
IDownloadStationInfoProxy dsInfoProxy,
|
||||
IDownloadStationTaskProxy dsTaskProxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_dsInfoProxy = dsInfoProxy;
|
||||
_dsTaskProxy = dsTaskProxy;
|
||||
@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
return _dsTaskProxy.GetTasks(Settings).Where(v => v.Type.ToLower() == DownloadStationTaskType.BT.ToString().ToLower());
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
|
||||
protected override string AddFromMagnetLink(TorrentInfo release, string hash, string magnetLink)
|
||||
{
|
||||
var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings);
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
throw new DownloadClientException("Failed to add magnet task to Download Station");
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent)
|
||||
protected override string AddFromTorrentFile(TorrentInfo release, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings);
|
||||
|
||||
@@ -315,7 +315,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentLink(ReleaseInfo release, string hash, string torrentLink)
|
||||
protected override string AddFromTorrentLink(TorrentInfo release, string hash, string torrentLink)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ using System.Linq;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download.Clients.Flood.Models;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
@@ -18,11 +18,11 @@ namespace NzbDrone.Core.Download.Clients.Flood
|
||||
|
||||
public Flood(IFloodProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
@@ -63,14 +63,14 @@ namespace NzbDrone.Core.Download.Clients.Flood
|
||||
public override bool SupportsCategories => true;
|
||||
public override ProviderMessage Message => new ProviderMessage("Prowlarr is unable to remove torrents that have finished seeding when using Flood", ProviderMessageType.Warning);
|
||||
|
||||
protected override string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent)
|
||||
protected override string AddFromTorrentFile(TorrentInfo release, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
_proxy.AddTorrentByFile(Convert.ToBase64String(fileContent), HandleTags(release, Settings, GetCategoryForRelease(release)), Settings);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
|
||||
protected override string AddFromMagnetLink(TorrentInfo release, string hash, string magnetLink)
|
||||
{
|
||||
_proxy.AddTorrentByUrl(magnetLink, HandleTags(release, Settings, GetCategoryForRelease(release)), Settings);
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace NzbDrone.Core.Download.Clients.Flood
|
||||
}
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentLink(ReleaseInfo release, string hash, string torrentLink)
|
||||
protected override string AddFromTorrentLink(TorrentInfo release, string hash, string torrentLink)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
public interface IFreeboxDownloadProxy
|
||||
{
|
||||
void Authenticate(FreeboxDownloadSettings settings);
|
||||
string AddTaskFromUrl(string url, string directory, bool addPaused, bool addFirst, FreeboxDownloadSettings settings);
|
||||
string AddTaskFromFile(string fileName, byte[] fileContent, string directory, bool addPaused, bool addFirst, 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);
|
||||
@@ -46,7 +46,7 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
}
|
||||
}
|
||||
|
||||
public string AddTaskFromUrl(string url, string directory, bool addPaused, bool addFirst, FreeboxDownloadSettings settings)
|
||||
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";
|
||||
@@ -60,12 +60,12 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
|
||||
var response = ProcessRequest<FreeboxDownloadTask>(request.Build(), settings);
|
||||
|
||||
SetTorrentSettings(response.Result.Id, addPaused, addFirst, 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, FreeboxDownloadSettings settings)
|
||||
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();
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
|
||||
var response = ProcessRequest<FreeboxDownloadTask>(request.Build(), settings);
|
||||
|
||||
SetTorrentSettings(response.Result.Id, addPaused, addFirst, settings);
|
||||
SetTorrentSettings(response.Result.Id, addPaused, addFirst, seedRatio, settings);
|
||||
|
||||
return response.Result.Id;
|
||||
}
|
||||
@@ -118,7 +118,7 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
return $"{settings.Host}:{settings.AppId}:{settings.AppToken}";
|
||||
}
|
||||
|
||||
private void SetTorrentSettings(string id, bool addPaused, bool addFirst, FreeboxDownloadSettings settings)
|
||||
private void SetTorrentSettings(string id, bool addPaused, bool addFirst, double? seedRatio, FreeboxDownloadSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/downloads/" + id).Build();
|
||||
|
||||
@@ -136,6 +136,12 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
body.Add("queue_pos", "1");
|
||||
}
|
||||
|
||||
if (seedRatio != null)
|
||||
{
|
||||
// 0 means unlimited seeding
|
||||
body.Add("stop_ratio", seedRatio);
|
||||
}
|
||||
|
||||
if (body.Count == 0)
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
|
||||
public class FreeboxDownloadSettings : IProviderConfig
|
||||
{
|
||||
private static readonly FreeboxDownloadSettingsValidator Validator = new FreeboxDownloadSettingsValidator();
|
||||
private static readonly FreeboxDownloadSettingsValidator Validator = new ();
|
||||
|
||||
public FreeboxDownloadSettings()
|
||||
{
|
||||
@@ -73,7 +73,7 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
[FieldDefinition(8, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "Priority to use when grabbing")]
|
||||
public int Priority { get; set; }
|
||||
|
||||
[FieldDefinition(10, Label = "Add Paused", Type = FieldType.Checkbox)]
|
||||
[FieldDefinition(9, Label = "Add Paused", Type = FieldType.Checkbox)]
|
||||
public bool AddPaused { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
@@ -18,49 +16,46 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
|
||||
public TorrentFreeboxDownload(IFreeboxDownloadProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
public override string Name => "Freebox Download";
|
||||
|
||||
public override bool SupportsCategories => true;
|
||||
|
||||
protected IEnumerable<FreeboxDownloadTask> GetTorrents()
|
||||
{
|
||||
return _proxy.GetTasks(Settings).Where(v => v.Type.ToLower() == FreeboxDownloadTaskType.Bt.ToString().ToLower());
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
|
||||
protected override string AddFromMagnetLink(TorrentInfo release, string hash, string magnetLink)
|
||||
{
|
||||
return _proxy.AddTaskFromUrl(magnetLink,
|
||||
GetDownloadDirectory(release).EncodeBase64(),
|
||||
ToBePaused(),
|
||||
ToBeQueuedFirst(),
|
||||
GetSeedRatio(release),
|
||||
Settings);
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent)
|
||||
protected override string AddFromTorrentFile(TorrentInfo release, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
return _proxy.AddTaskFromFile(filename,
|
||||
fileContent,
|
||||
GetDownloadDirectory(release).EncodeBase64(),
|
||||
ToBePaused(),
|
||||
ToBeQueuedFirst(),
|
||||
GetSeedRatio(release),
|
||||
Settings);
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentLink(ReleaseInfo release, string hash, string torrentLink)
|
||||
protected override string AddFromTorrentLink(TorrentInfo release, string hash, string torrentLink)
|
||||
{
|
||||
return _proxy.AddTaskFromUrl(torrentLink,
|
||||
GetDownloadDirectory(release).EncodeBase64(),
|
||||
ToBePaused(),
|
||||
ToBeQueuedFirst(),
|
||||
GetSeedRatio(release),
|
||||
Settings);
|
||||
}
|
||||
|
||||
@@ -108,16 +103,21 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
|
||||
var destDir = _proxy.GetDownloadConfiguration(Settings).DecodedDownloadDirectory.TrimEnd('/');
|
||||
|
||||
if (Settings.Category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
|
||||
if (category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
destDir = $"{destDir}/{category}";
|
||||
}
|
||||
|
||||
return destDir;
|
||||
}
|
||||
|
||||
private bool ToBePaused()
|
||||
{
|
||||
return Settings.AddPaused;
|
||||
}
|
||||
|
||||
private bool ToBeQueuedFirst()
|
||||
{
|
||||
if (Settings.Priority == (int)FreeboxDownloadPriority.First)
|
||||
@@ -128,9 +128,14 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool ToBePaused()
|
||||
private double? GetSeedRatio(TorrentInfo release)
|
||||
{
|
||||
return Settings.AddPaused;
|
||||
if (release.SeedConfiguration == null || release.SeedConfiguration.Ratio == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return release.SeedConfiguration.Ratio.Value * 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
@@ -17,11 +17,11 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
|
||||
|
||||
public Hadouken(IHadoukenProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
@@ -40,14 +40,14 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
|
||||
failures.AddIfNotNull(TestGetTorrents());
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
|
||||
protected override string AddFromMagnetLink(TorrentInfo release, string hash, string magnetLink)
|
||||
{
|
||||
_proxy.AddTorrentUri(Settings, magnetLink, GetCategoryForRelease(release) ?? Settings.Category);
|
||||
|
||||
return hash.ToUpper();
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent)
|
||||
protected override string AddFromTorrentFile(TorrentInfo release, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
return _proxy.AddTorrentFile(Settings, fileContent, GetCategoryForRelease(release) ?? Settings.Category).ToUpper();
|
||||
}
|
||||
@@ -97,7 +97,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentLink(ReleaseInfo release, string hash, string torrentLink)
|
||||
protected override string AddFromTorrentLink(TorrentInfo release, string hash, string torrentLink)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ 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.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
@@ -26,12 +26,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
public QBittorrent(IQBittorrentProxySelector proxySelector,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
ICacheManager cacheManager,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxySelector = proxySelector;
|
||||
|
||||
@@ -41,33 +41,41 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
private IQBittorrentProxy Proxy => _proxySelector.GetProxy(Settings);
|
||||
private Version ProxyApiVersion => _proxySelector.GetApiVersion(Settings);
|
||||
|
||||
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
|
||||
protected override string AddFromMagnetLink(TorrentInfo release, string hash, string magnetLink)
|
||||
{
|
||||
if (!Proxy.GetConfig(Settings).DhtEnabled && !magnetLink.Contains("&tr="))
|
||||
{
|
||||
throw new NotSupportedException("Magnet Links without trackers not supported if DHT is disabled");
|
||||
}
|
||||
|
||||
//var setShareLimits = release.SeedConfiguration != null && (release.SeedConfiguration.Ratio.HasValue || release.SeedConfiguration.SeedTime.HasValue);
|
||||
//var addHasSetShareLimits = setShareLimits && ProxyApiVersion >= new Version(2, 8, 1);
|
||||
var itemToTop = Settings.Priority == (int)QBittorrentPriority.First;
|
||||
var setShareLimits = release.SeedConfiguration != null && (release.SeedConfiguration.Ratio.HasValue || release.SeedConfiguration.SeedTime.HasValue);
|
||||
var addHasSetShareLimits = setShareLimits && ProxyApiVersion >= new Version(2, 8, 1);
|
||||
var moveToTop = Settings.Priority == (int)QBittorrentPriority.First;
|
||||
var forceStart = (QBittorrentState)Settings.InitialState == QBittorrentState.ForceStart;
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
|
||||
Proxy.AddTorrentFromUrl(magnetLink, null, Settings, category);
|
||||
Proxy.AddTorrentFromUrl(magnetLink, addHasSetShareLimits && setShareLimits ? release.SeedConfiguration : null, Settings, category);
|
||||
|
||||
if (itemToTop || forceStart)
|
||||
if ((!addHasSetShareLimits && setShareLimits) || moveToTop || forceStart)
|
||||
{
|
||||
if (!WaitForTorrent(hash))
|
||||
{
|
||||
return hash;
|
||||
}
|
||||
|
||||
//if (!addHasSetShareLimits && setShareLimits)
|
||||
//{
|
||||
// Proxy.SetTorrentSeedingConfiguration(hash.ToLower(), release.SeedConfiguration, Settings);
|
||||
//}
|
||||
if (itemToTop)
|
||||
if (!addHasSetShareLimits && setShareLimits)
|
||||
{
|
||||
try
|
||||
{
|
||||
Proxy.SetTorrentSeedingConfiguration(hash.ToLower(), release.SeedConfiguration, Settings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "Failed to set the torrent seed criteria for {0}.", hash);
|
||||
}
|
||||
}
|
||||
|
||||
if (moveToTop)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -95,28 +103,36 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
return hash;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent)
|
||||
protected override string AddFromTorrentFile(TorrentInfo release, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
//var setShareLimits = release.SeedConfiguration != null && (release.SeedConfiguration.Ratio.HasValue || release.SeedConfiguration.SeedTime.HasValue);
|
||||
//var addHasSetShareLimits = setShareLimits && ProxyApiVersion >= new Version(2, 8, 1);
|
||||
var itemToTop = Settings.Priority == (int)QBittorrentPriority.First;
|
||||
var setShareLimits = release.SeedConfiguration != null && (release.SeedConfiguration.Ratio.HasValue || release.SeedConfiguration.SeedTime.HasValue);
|
||||
var addHasSetShareLimits = setShareLimits && ProxyApiVersion >= new Version(2, 8, 1);
|
||||
var moveToTop = Settings.Priority == (int)QBittorrentPriority.First;
|
||||
var forceStart = (QBittorrentState)Settings.InitialState == QBittorrentState.ForceStart;
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
|
||||
Proxy.AddTorrentFromFile(filename, fileContent, null, Settings, category);
|
||||
Proxy.AddTorrentFromFile(filename, fileContent, addHasSetShareLimits ? release.SeedConfiguration : null, Settings, category);
|
||||
|
||||
if (itemToTop || forceStart)
|
||||
if ((!addHasSetShareLimits && setShareLimits) || moveToTop || forceStart)
|
||||
{
|
||||
if (!WaitForTorrent(hash))
|
||||
{
|
||||
return hash;
|
||||
}
|
||||
|
||||
//if (!addHasSetShareLimits && setShareLimits)
|
||||
//{
|
||||
// Proxy.SetTorrentSeedingConfiguration(hash.ToLower(), release.SeedConfiguration, Settings);
|
||||
//}
|
||||
if (itemToTop)
|
||||
if (!addHasSetShareLimits && setShareLimits)
|
||||
{
|
||||
try
|
||||
{
|
||||
Proxy.SetTorrentSeedingConfiguration(hash.ToLower(), release.SeedConfiguration, Settings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "Failed to set the torrent seed criteria for {0}.", hash);
|
||||
}
|
||||
}
|
||||
|
||||
if (moveToTop)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -146,14 +162,16 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
protected bool WaitForTorrent(string hash)
|
||||
{
|
||||
var count = 5;
|
||||
var count = 10;
|
||||
|
||||
while (count != 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
Proxy.GetTorrentProperties(hash.ToLower(), Settings);
|
||||
return true;
|
||||
if (Proxy.IsTorrentLoaded(hash.ToLower(), Settings))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -235,9 +253,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
_logger.Error(ex, "Unable to test qBittorrent");
|
||||
|
||||
return new NzbDroneValidationFailure("Host", "Unable to connect to qBittorrent")
|
||||
{
|
||||
DetailedDescription = ex.Message
|
||||
};
|
||||
{
|
||||
DetailedDescription = ex.Message
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -297,11 +315,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
var recentPriorityDefault = Settings.Priority == (int)QBittorrentPriority.Last;
|
||||
|
||||
if (recentPriorityDefault)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var config = Proxy.GetConfig(Settings);
|
||||
@@ -450,7 +463,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
torrent.SeedingTime = torrentProperties.SeedingTime;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentLink(ReleaseInfo release, string hash, string torrentLink)
|
||||
protected override string AddFromTorrentLink(TorrentInfo release, string hash, string torrentLink)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
public class QBittorrentLabel
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
public enum QBittorrentPriority
|
||||
{
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
|
||||
using NzbDrone.Common.Http;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
public interface IQBittorrentProxy
|
||||
@@ -15,6 +12,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
string GetVersion(QBittorrentSettings settings);
|
||||
QBittorrentPreferences GetConfig(QBittorrentSettings settings);
|
||||
List<QBittorrentTorrent> GetTorrents(QBittorrentSettings settings);
|
||||
bool IsTorrentLoaded(string hash, QBittorrentSettings settings);
|
||||
QBittorrentTorrentProperties GetTorrentProperties(string hash, QBittorrentSettings settings);
|
||||
List<QBittorrentTorrentFile> GetTorrentFiles(string hash, QBittorrentSettings settings);
|
||||
|
||||
@@ -40,7 +38,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
public class QBittorrentProxySelector : IQBittorrentProxySelector
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly ICached<Tuple<IQBittorrentProxy, Version>> _proxyCache;
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -49,11 +46,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
public QBittorrentProxySelector(QBittorrentProxyV1 proxyV1,
|
||||
QBittorrentProxyV2 proxyV2,
|
||||
IHttpClient httpClient,
|
||||
ICacheManager cacheManager,
|
||||
Logger logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_proxyCache = cacheManager.GetCache<Tuple<IQBittorrentProxy, Version>>(GetType());
|
||||
_logger = logger;
|
||||
|
||||
|
||||
@@ -97,6 +97,23 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
return response;
|
||||
}
|
||||
|
||||
public bool IsTorrentLoaded(string hash, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource($"/query/propertiesGeneral/{hash}");
|
||||
request.LogHttpError = false;
|
||||
|
||||
try
|
||||
{
|
||||
ProcessRequest(request, settings);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public QBittorrentTorrentProperties GetTorrentProperties(string hash, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource($"/query/propertiesGeneral/{hash}");
|
||||
@@ -295,15 +312,14 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
request.LogResponseContent = true;
|
||||
request.SuppressHttpErrorStatusCodes = new[] { HttpStatusCode.Forbidden };
|
||||
|
||||
HttpResponse response;
|
||||
try
|
||||
{
|
||||
response = _httpClient.Execute(request);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.Forbidden)
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
_logger.Debug("Authentication required, logging in.");
|
||||
|
||||
@@ -313,10 +329,10 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
response = _httpClient.Execute(request);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DownloadClientException("Failed to connect to qBittorrent, check your settings.", ex);
|
||||
}
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
throw new DownloadClientException("Failed to connect to qBittorrent, check your settings.", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
@@ -367,9 +383,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
throw new DownloadClientUnavailableException("Failed to connect to qBittorrent, please check your settings.", ex);
|
||||
}
|
||||
|
||||
// returns "Fails." on bad login
|
||||
if (response.Content != "Ok.")
|
||||
{
|
||||
// returns "Fails." on bad login
|
||||
_logger.Debug("qbitTorrent authentication failed.");
|
||||
throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.");
|
||||
}
|
||||
|
||||
@@ -106,6 +106,24 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
return response;
|
||||
}
|
||||
|
||||
public bool IsTorrentLoaded(string hash, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/properties")
|
||||
.AddQueryParam("hash", hash);
|
||||
request.LogHttpError = false;
|
||||
|
||||
try
|
||||
{
|
||||
ProcessRequest(request, settings);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public QBittorrentTorrentProperties GetTorrentProperties(string hash, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/properties")
|
||||
@@ -129,24 +147,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/add")
|
||||
.Post()
|
||||
.AddFormParameter("urls", torrentUrl);
|
||||
if (category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.AddFormParameter("category", category);
|
||||
}
|
||||
|
||||
// Note: ForceStart is handled by separate api call
|
||||
if ((QBittorrentState)settings.InitialState == QBittorrentState.Start)
|
||||
{
|
||||
request.AddFormParameter("paused", false);
|
||||
}
|
||||
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
|
||||
{
|
||||
request.AddFormParameter("paused", true);
|
||||
}
|
||||
AddTorrentDownloadFormParameters(request, settings, category);
|
||||
|
||||
if (seedConfiguration != null)
|
||||
{
|
||||
AddTorrentSeedingFormParameters(request, seedConfiguration, settings);
|
||||
AddTorrentSeedingFormParameters(request, seedConfiguration);
|
||||
}
|
||||
|
||||
var result = ProcessRequest(request, settings);
|
||||
@@ -164,24 +170,11 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
.Post()
|
||||
.AddFormUpload("torrents", fileName, fileContent);
|
||||
|
||||
if (category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.AddFormParameter("category", category);
|
||||
}
|
||||
|
||||
// Note: ForceStart is handled by separate api call
|
||||
if ((QBittorrentState)settings.InitialState == QBittorrentState.Start)
|
||||
{
|
||||
request.AddFormParameter("paused", false);
|
||||
}
|
||||
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
|
||||
{
|
||||
request.AddFormParameter("paused", true);
|
||||
}
|
||||
AddTorrentDownloadFormParameters(request, settings, category);
|
||||
|
||||
if (seedConfiguration != null)
|
||||
{
|
||||
AddTorrentSeedingFormParameters(request, seedConfiguration, settings);
|
||||
AddTorrentSeedingFormParameters(request, seedConfiguration);
|
||||
}
|
||||
|
||||
var result = ProcessRequest(request, settings);
|
||||
@@ -230,29 +223,57 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
return Json.Deserialize<Dictionary<string, QBittorrentLabel>>(ProcessRequest(request, settings));
|
||||
}
|
||||
|
||||
private void AddTorrentSeedingFormParameters(HttpRequestBuilder request, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings)
|
||||
private void AddTorrentSeedingFormParameters(HttpRequestBuilder request, TorrentSeedConfiguration seedConfiguration, bool always = false)
|
||||
{
|
||||
var ratioLimit = seedConfiguration.Ratio.HasValue ? seedConfiguration.Ratio : -2;
|
||||
var seedingTimeLimit = seedConfiguration.SeedTime.HasValue ? (long)seedConfiguration.SeedTime.Value.TotalMinutes : -2;
|
||||
|
||||
if (ratioLimit != -2)
|
||||
if (ratioLimit != -2 || always)
|
||||
{
|
||||
request.AddFormParameter("ratioLimit", ratioLimit);
|
||||
}
|
||||
|
||||
if (seedingTimeLimit != -2)
|
||||
if (seedingTimeLimit != -2 || always)
|
||||
{
|
||||
request.AddFormParameter("seedingTimeLimit", seedingTimeLimit);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTorrentDownloadFormParameters(HttpRequestBuilder request, QBittorrentSettings settings, string category)
|
||||
{
|
||||
if (category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.AddFormParameter("category", category);
|
||||
}
|
||||
|
||||
// Note: ForceStart is handled by separate api call
|
||||
if ((QBittorrentState)settings.InitialState == QBittorrentState.Start)
|
||||
{
|
||||
request.AddFormParameter("paused", false);
|
||||
}
|
||||
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
|
||||
{
|
||||
request.AddFormParameter("paused", true);
|
||||
}
|
||||
|
||||
if (settings.SequentialOrder)
|
||||
{
|
||||
request.AddFormParameter("sequentialDownload", true);
|
||||
}
|
||||
|
||||
if (settings.FirstAndLast)
|
||||
{
|
||||
request.AddFormParameter("firstLastPiecePrio", true);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/setShareLimits")
|
||||
.Post()
|
||||
.AddFormParameter("hashes", hash);
|
||||
|
||||
AddTorrentSeedingFormParameters(request, seedConfiguration, settings);
|
||||
AddTorrentSeedingFormParameters(request, seedConfiguration, true);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -341,15 +362,14 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
request.LogResponseContent = true;
|
||||
request.SuppressHttpErrorStatusCodes = new[] { HttpStatusCode.Forbidden };
|
||||
|
||||
HttpResponse response;
|
||||
try
|
||||
{
|
||||
response = _httpClient.Execute(request);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.Forbidden)
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
_logger.Debug("Authentication required, logging in.");
|
||||
|
||||
@@ -359,10 +379,10 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
response = _httpClient.Execute(request);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DownloadClientException("Failed to connect to qBittorrent, check your settings.", ex);
|
||||
}
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
throw new DownloadClientException("Failed to connect to qBittorrent, check your settings.", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
@@ -418,9 +438,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
throw new DownloadClientUnavailableException("Failed to connect to qBittorrent, please check your settings.", ex);
|
||||
}
|
||||
|
||||
// returns "Fails." on bad login
|
||||
if (response.Content != "Ok.")
|
||||
{
|
||||
// returns "Fails." on bad login
|
||||
_logger.Debug("qbitTorrent authentication failed.");
|
||||
throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.");
|
||||
}
|
||||
|
||||
@@ -56,6 +56,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
[FieldDefinition(8, Label = "Initial State", Type = FieldType.Select, SelectOptions = typeof(QBittorrentState), HelpText = "Initial state for torrents added to qBittorrent")]
|
||||
public int InitialState { get; set; }
|
||||
|
||||
[FieldDefinition(9, Label = "Sequential Order", Type = FieldType.Checkbox, HelpText = "Download in sequential order (qBittorrent 4.1.0+)")]
|
||||
public bool SequentialOrder { get; set; }
|
||||
|
||||
[FieldDefinition(10, Label = "First and Last First", Type = FieldType.Checkbox, HelpText = "Download first and last pieces first (qBittorrent 4.1.0+)")]
|
||||
public bool FirstAndLast { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -3,8 +3,8 @@ using System.Text.RegularExpressions;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
{
|
||||
@@ -12,11 +12,11 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
{
|
||||
public Transmission(ITransmissionProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(proxy, torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
@@ -17,60 +17,20 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
|
||||
public TransmissionBase(ITransmissionProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
protected bool HasReachedSeedLimit(TransmissionTorrent torrent, double? ratio, Lazy<TransmissionConfig> config)
|
||||
{
|
||||
var isStopped = torrent.Status == TransmissionTorrentStatus.Stopped;
|
||||
var isSeeding = torrent.Status == TransmissionTorrentStatus.Seeding;
|
||||
|
||||
if (torrent.SeedRatioMode == 1)
|
||||
{
|
||||
if (isStopped && ratio.HasValue && ratio >= torrent.SeedRatioLimit)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (torrent.SeedRatioMode == 0)
|
||||
{
|
||||
if (isStopped && config.Value.SeedRatioLimited && ratio >= config.Value.SeedRatioLimit)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Transmission doesn't support SeedTimeLimit, use/abuse seed idle limit, but only if it was set per-torrent.
|
||||
if (torrent.SeedIdleMode == 1)
|
||||
{
|
||||
if ((isStopped || isSeeding) && torrent.SecondsSeeding > torrent.SeedIdleLimit * 60)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (torrent.SeedIdleMode == 0)
|
||||
{
|
||||
// The global idle limit is a real idle limit, if it's configured then 'Stopped' is enough.
|
||||
if (isStopped && config.Value.IdleSeedingLimitEnabled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
|
||||
protected override string AddFromMagnetLink(TorrentInfo release, string hash, string magnetLink)
|
||||
{
|
||||
_proxy.AddTorrentFromUrl(magnetLink, GetDownloadDirectory(), Settings);
|
||||
_proxy.SetTorrentSeedingConfiguration(hash, release.SeedConfiguration, Settings);
|
||||
|
||||
//_proxy.SetTorrentSeedingConfiguration(hash, release.SeedConfiguration, Settings);
|
||||
if (Settings.Priority == (int)TransmissionPriority.First)
|
||||
{
|
||||
_proxy.MoveTorrentToTopInQueue(hash, Settings);
|
||||
@@ -79,11 +39,11 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
return hash;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent)
|
||||
protected override string AddFromTorrentFile(TorrentInfo release, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
_proxy.AddTorrentFromData(fileContent, GetDownloadDirectory(), Settings);
|
||||
_proxy.SetTorrentSeedingConfiguration(hash, release.SeedConfiguration, Settings);
|
||||
|
||||
//_proxy.SetTorrentSeedingConfiguration(hash, release.SeedConfiguration, Settings);
|
||||
if (Settings.Priority == (int)TransmissionPriority.First)
|
||||
{
|
||||
_proxy.MoveTorrentToTopInQueue(hash, Settings);
|
||||
@@ -92,7 +52,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
return hash;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentLink(ReleaseInfo release, string hash, string torrentLink)
|
||||
protected override string AddFromTorrentLink(TorrentInfo release, string hash, string torrentLink)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
{
|
||||
public class TransmissionException : DownloadClientException
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
{
|
||||
public enum TransmissionPriority
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@@ -141,7 +141,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
|
||||
private TransmissionResponse GetSessionVariables(TransmissionSettings settings)
|
||||
{
|
||||
// Retrieve transmission information such as the default download directory, bandwith throttling and seed ratio.
|
||||
// Retrieve transmission information such as the default download directory, bandwidth throttling and seed ratio.
|
||||
return ProcessRequest("session-get", null, settings);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
{
|
||||
public class TransmissionTorrent
|
||||
{
|
||||
@@ -9,7 +9,7 @@
|
||||
public long TotalSize { get; set; }
|
||||
public long LeftUntilDone { get; set; }
|
||||
public bool IsFinished { get; set; }
|
||||
public int Eta { get; set; }
|
||||
public long Eta { get; set; }
|
||||
public TransmissionTorrentStatus Status { get; set; }
|
||||
public int SecondsDownloading { get; set; }
|
||||
public int SecondsSeeding { get; set; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
{
|
||||
public enum TransmissionTorrentStatus
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download.Clients.Transmission;
|
||||
using NzbDrone.Core.Indexers;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Vuze
|
||||
{
|
||||
@@ -13,11 +13,11 @@ namespace NzbDrone.Core.Download.Clients.Vuze
|
||||
|
||||
public Vuze(ITransmissionProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(proxy, torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Download.Clients.Vuze
|
||||
// - A multi-file torrent is downloaded in a job folder and 'outputPath' points to that directory directly.
|
||||
// - A single-file torrent is downloaded in the root folder and 'outputPath' poinst to that root folder.
|
||||
// We have to make sure the return value points to the job folder OR file.
|
||||
if (outputPath == default || outputPath.FileName == torrent.Name || torrent.FileCount > 1)
|
||||
if (outputPath.FileName == torrent.Name || torrent.FileCount > 1)
|
||||
{
|
||||
_logger.Trace("Vuze output directory: {0}", outputPath);
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download.Clients.rTorrent;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
@@ -23,18 +23,18 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
|
||||
public RTorrent(IRTorrentProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
IRTorrentDirectoryValidator rTorrentDirectoryValidator,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
_rTorrentDirectoryValidator = rTorrentDirectoryValidator;
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
|
||||
protected override string AddFromMagnetLink(TorrentInfo release, string hash, string magnetLink)
|
||||
{
|
||||
var priority = (RTorrentPriority)Settings.Priority;
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
return hash;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent)
|
||||
protected override string AddFromTorrentFile(TorrentInfo release, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
var priority = (RTorrentPriority)Settings.Priority;
|
||||
|
||||
@@ -157,7 +157,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentLink(ReleaseInfo release, string hash, string torrentLink)
|
||||
protected override string AddFromTorrentLink(TorrentInfo release, string hash, string torrentLink)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@ using System.Collections.Generic;
|
||||
using System.Net;
|
||||
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.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
@@ -16,29 +15,26 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
public class UTorrent : TorrentClientBase<UTorrentSettings>
|
||||
{
|
||||
private readonly IUTorrentProxy _proxy;
|
||||
private readonly ICached<UTorrentTorrentCache> _torrentCache;
|
||||
|
||||
public UTorrent(IUTorrentProxy proxy,
|
||||
ICacheManager cacheManager,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
|
||||
_torrentCache = cacheManager.GetCache<UTorrentTorrentCache>(GetType(), "differentialTorrents");
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
|
||||
protected override string AddFromMagnetLink(TorrentInfo release, string hash, string magnetLink)
|
||||
{
|
||||
_proxy.AddTorrentFromUrl(magnetLink, Settings);
|
||||
_proxy.SetTorrentSeedingConfiguration(hash, release.SeedConfiguration, Settings);
|
||||
|
||||
//_proxy.SetTorrentSeedingConfiguration(hash, release.SeedConfiguration, Settings);
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
if (GetCategoryForRelease(release).IsNotNullOrWhiteSpace())
|
||||
|
||||
if (category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_proxy.SetTorrentLabel(hash, category, Settings);
|
||||
}
|
||||
@@ -53,12 +49,13 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
return hash;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent)
|
||||
protected override string AddFromTorrentFile(TorrentInfo release, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
_proxy.AddTorrentFromFile(filename, fileContent, Settings);
|
||||
_proxy.SetTorrentSeedingConfiguration(hash, release.SeedConfiguration, Settings);
|
||||
|
||||
//_proxy.SetTorrentSeedingConfiguration(hash, release.SeedConfiguration, Settings);
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
|
||||
if (category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_proxy.SetTorrentLabel(hash, category, Settings);
|
||||
@@ -125,9 +122,9 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
_logger.Error(ex, "Failed to test uTorrent");
|
||||
|
||||
return new NzbDroneValidationFailure("Host", "Unable to connect to uTorrent")
|
||||
{
|
||||
DetailedDescription = ex.Message
|
||||
};
|
||||
{
|
||||
DetailedDescription = ex.Message
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -148,7 +145,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentLink(ReleaseInfo release, string hash, string torrentLink)
|
||||
protected override string AddFromTorrentLink(TorrentInfo release, string hash, string torrentLink)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
public object Unknown28 { get; set; }
|
||||
}
|
||||
|
||||
internal class UTorrentTorrentJsonConverter : JsonConverter
|
||||
public class UTorrentTorrentJsonConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
|
||||
@@ -27,7 +27,20 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
public virtual ProviderMessage Message => null;
|
||||
|
||||
public IEnumerable<ProviderDefinition> DefaultDefinitions => new List<ProviderDefinition>();
|
||||
public IEnumerable<ProviderDefinition> DefaultDefinitions
|
||||
{
|
||||
get
|
||||
{
|
||||
var config = (IProviderConfig)new TSettings();
|
||||
|
||||
yield return new DownloadClientDefinition
|
||||
{
|
||||
Name = GetType().Name,
|
||||
Implementation = GetType().Name,
|
||||
Settings = config
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public ProviderDefinition Definition { get; set; }
|
||||
|
||||
|
||||
@@ -58,8 +58,6 @@ namespace NzbDrone.Core.Download
|
||||
throw new DownloadClientUnavailableException($"{release.DownloadProtocol} Download client isn't configured yet");
|
||||
}
|
||||
|
||||
// Get the seed configuration for this release.
|
||||
// remoteMovie.SeedConfiguration = _seedConfigProvider.GetSeedConfiguration(remoteMovie);
|
||||
var indexer = _indexerFactory.GetInstance(_indexerFactory.Get(release.IndexerId));
|
||||
|
||||
var grabEvent = new IndexerDownloadEvent(release, true, source, host, release.Title, release.DownloadUrl)
|
||||
|
||||
@@ -5,7 +5,6 @@ using MonoTorrent;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.Indexers;
|
||||
@@ -18,32 +17,38 @@ namespace NzbDrone.Core.Download
|
||||
public abstract class TorrentClientBase<TSettings> : DownloadClientBase<TSettings>
|
||||
where TSettings : IProviderConfig, new()
|
||||
{
|
||||
protected readonly IHttpClient _httpClient;
|
||||
protected readonly ITorrentFileInfoReader _torrentFileInfoReader;
|
||||
private readonly ITorrentFileInfoReader _torrentFileInfoReader;
|
||||
private readonly ISeedConfigProvider _seedConfigProvider;
|
||||
|
||||
protected TorrentClientBase(ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(configService, diskProvider, logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_torrentFileInfoReader = torrentFileInfoReader;
|
||||
_seedConfigProvider = seedConfigProvider;
|
||||
}
|
||||
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
|
||||
public virtual bool PreferTorrentFile => false;
|
||||
|
||||
protected abstract string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink);
|
||||
protected abstract string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent);
|
||||
protected abstract string AddFromTorrentLink(ReleaseInfo release, string hash, string torrentLink);
|
||||
protected abstract string AddFromMagnetLink(TorrentInfo release, string hash, string magnetLink);
|
||||
protected abstract string AddFromTorrentFile(TorrentInfo release, string hash, string filename, byte[] fileContent);
|
||||
protected abstract string AddFromTorrentLink(TorrentInfo release, string hash, string torrentLink);
|
||||
|
||||
public override async Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer)
|
||||
{
|
||||
var torrentInfo = release as TorrentInfo;
|
||||
|
||||
if (torrentInfo != null)
|
||||
{
|
||||
// Get the seed configuration for this release.
|
||||
torrentInfo.SeedConfiguration = _seedConfigProvider.GetSeedConfiguration(release);
|
||||
}
|
||||
|
||||
string magnetUrl = null;
|
||||
string torrentUrl = null;
|
||||
|
||||
@@ -67,7 +72,7 @@ namespace NzbDrone.Core.Download
|
||||
{
|
||||
try
|
||||
{
|
||||
return await DownloadFromWebUrl(release, indexer, torrentUrl);
|
||||
return await DownloadFromWebUrl(torrentInfo, indexer, torrentUrl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -84,7 +89,7 @@ namespace NzbDrone.Core.Download
|
||||
{
|
||||
try
|
||||
{
|
||||
return DownloadFromMagnetUrl(release, magnetUrl);
|
||||
return DownloadFromMagnetUrl(torrentInfo, magnetUrl);
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
@@ -98,7 +103,7 @@ namespace NzbDrone.Core.Download
|
||||
{
|
||||
try
|
||||
{
|
||||
return DownloadFromMagnetUrl(release, magnetUrl);
|
||||
return DownloadFromMagnetUrl(torrentInfo, magnetUrl);
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
@@ -113,14 +118,14 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
if (torrentUrl.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return await DownloadFromWebUrl(release, indexer, torrentUrl);
|
||||
return await DownloadFromWebUrl(torrentInfo, indexer, torrentUrl);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string> DownloadFromWebUrl(ReleaseInfo release, IIndexer indexer, string torrentUrl)
|
||||
private async Task<string> DownloadFromWebUrl(TorrentInfo release, IIndexer indexer, string torrentUrl)
|
||||
{
|
||||
byte[] torrentFile = null;
|
||||
|
||||
@@ -155,7 +160,7 @@ namespace NzbDrone.Core.Download
|
||||
return actualHash;
|
||||
}
|
||||
|
||||
private string DownloadFromMagnetUrl(ReleaseInfo release, string magnetUrl)
|
||||
private string DownloadFromMagnetUrl(TorrentInfo release, string magnetUrl)
|
||||
{
|
||||
string hash = null;
|
||||
string actualHash = null;
|
||||
|
||||
@@ -14,13 +14,11 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM ""Users""
|
||||
WHERE ""Id"" NOT IN (
|
||||
SELECT ""Id"" FROM ""Users""
|
||||
LIMIT 1)");
|
||||
}
|
||||
using var mapper = _database.OpenConnection();
|
||||
mapper.Execute(@"DELETE FROM ""Users""
|
||||
WHERE ""Id"" NOT IN (
|
||||
SELECT ""Id"" FROM ""Users""
|
||||
LIMIT 1)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var mapper = _database.OpenConnection();
|
||||
|
||||
using var mapper = _database.OpenConnection();
|
||||
mapper.Execute(@"DELETE FROM ""ApplicationStatus""
|
||||
WHERE ""Id"" IN (
|
||||
SELECT ""ApplicationStatus"".""Id"" FROM ""ApplicationStatus""
|
||||
LEFT OUTER JOIN ""Applications""
|
||||
ON ""ApplicationStatus"".""ProviderId"" = ""Applications"".""Id""
|
||||
WHERE ""Applications"".""Id"" IS NULL)");
|
||||
WHERE ""Id"" IN (
|
||||
SELECT ""ApplicationStatus"".""Id"" FROM ""ApplicationStatus""
|
||||
LEFT OUTER JOIN ""Applications""
|
||||
ON ""ApplicationStatus"".""ProviderId"" = ""Applications"".""Id""
|
||||
WHERE ""Applications"".""Id"" IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var mapper = _database.OpenConnection();
|
||||
|
||||
using var mapper = _database.OpenConnection();
|
||||
mapper.Execute(@"DELETE FROM ""DownloadClientStatus""
|
||||
WHERE ""Id"" IN (
|
||||
SELECT ""DownloadClientStatus"".""Id"" FROM ""DownloadClientStatus""
|
||||
LEFT OUTER JOIN ""DownloadClients""
|
||||
ON ""DownloadClientStatus"".""ProviderId"" = ""DownloadClients"".""Id""
|
||||
WHERE ""DownloadClients"".""Id"" IS NULL)");
|
||||
WHERE ""Id"" IN (
|
||||
SELECT ""DownloadClientStatus"".""Id"" FROM ""DownloadClientStatus""
|
||||
LEFT OUTER JOIN ""DownloadClients""
|
||||
ON ""DownloadClientStatus"".""ProviderId"" = ""DownloadClients"".""Id""
|
||||
WHERE ""DownloadClients"".""Id"" IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,15 +19,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
private void CleanupOrphanedByIndexer()
|
||||
{
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM ""History""
|
||||
WHERE ""Id"" IN (
|
||||
SELECT ""History"".""Id"" FROM ""History""
|
||||
LEFT OUTER JOIN ""Indexers""
|
||||
ON ""History"".""IndexerId"" = ""Indexers"".""Id""
|
||||
WHERE ""Indexers"".""Id"" IS NULL)");
|
||||
}
|
||||
using var mapper = _database.OpenConnection();
|
||||
mapper.Execute(@"DELETE FROM ""History""
|
||||
WHERE ""Id"" IN (
|
||||
SELECT ""History"".""Id"" FROM ""History""
|
||||
LEFT OUTER JOIN ""Indexers""
|
||||
ON ""History"".""IndexerId"" = ""Indexers"".""Id""
|
||||
WHERE ""Indexers"".""Id"" IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,15 +14,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM ""IndexerStatus""
|
||||
WHERE ""Id"" IN (
|
||||
SELECT ""IndexerStatus"".""Id"" FROM ""IndexerStatus""
|
||||
LEFT OUTER JOIN ""Indexers""
|
||||
ON ""IndexerStatus"".""ProviderId"" = ""Indexers"".""Id""
|
||||
WHERE ""Indexers"".""Id"" IS NULL)");
|
||||
}
|
||||
using var mapper = _database.OpenConnection();
|
||||
mapper.Execute(@"DELETE FROM ""IndexerStatus""
|
||||
WHERE ""Id"" IN (
|
||||
SELECT ""IndexerStatus"".""Id"" FROM ""IndexerStatus""
|
||||
LEFT OUTER JOIN ""Indexers""
|
||||
ON ""IndexerStatus"".""ProviderId"" = ""Indexers"".""Id""
|
||||
WHERE ""Indexers"".""Id"" IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,23 +17,21 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
using (var mapper = _database.OpenConnection())
|
||||
using var mapper = _database.OpenConnection();
|
||||
var usedTags = new[] { "Notifications", "IndexerProxies", "Indexers", "Applications" }
|
||||
.SelectMany(v => GetUsedTags(v, mapper))
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
if (usedTags.Length > 0)
|
||||
{
|
||||
var usedTags = new[] { "Notifications", "IndexerProxies", "Indexers", "Applications" }
|
||||
.SelectMany(v => GetUsedTags(v, mapper))
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
var usedTagsList = string.Join(",", usedTags.Select(d => d.ToString()).ToArray());
|
||||
|
||||
if (usedTags.Length > 0)
|
||||
{
|
||||
var usedTagsList = string.Join(",", usedTags.Select(d => d.ToString()).ToArray());
|
||||
|
||||
mapper.Execute($"DELETE FROM \"Tags\" WHERE NOT \"Id\" IN ({usedTagsList})");
|
||||
}
|
||||
else
|
||||
{
|
||||
mapper.Execute($"DELETE FROM \"Tags\"");
|
||||
}
|
||||
mapper.Execute($"DELETE FROM \"Tags\" WHERE NOT \"Id\" IN ({usedTagsList})");
|
||||
}
|
||||
else
|
||||
{
|
||||
mapper.Execute("DELETE FROM \"Tags\"");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,13 +24,11 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
_logger.Debug("Not running scheduled task last execution cleanup during debug");
|
||||
}
|
||||
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"UPDATE ""ScheduledTasks""
|
||||
SET ""LastExecution"" = @time
|
||||
WHERE ""LastExecution"" > @time",
|
||||
new { time = DateTime.UtcNow });
|
||||
}
|
||||
using var mapper = _database.OpenConnection();
|
||||
mapper.Execute(@"UPDATE ""ScheduledTasks""
|
||||
SET ""LastExecution"" = @time
|
||||
WHERE ""LastExecution"" > @time",
|
||||
new { time = DateTime.UtcNow });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
@@ -16,5 +17,10 @@ namespace NzbDrone.Core.IndexerProxies
|
||||
: base(providerRepository, providers, container, eventAggregator, logger)
|
||||
{
|
||||
}
|
||||
|
||||
protected override List<IndexerProxyDefinition> Active()
|
||||
{
|
||||
return All().Where(c => c.Enable && c.Settings.Validate().IsValid).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
public class NewznabRequest
|
||||
{
|
||||
private static readonly Regex TvRegex = new Regex(@"\{((?:imdbid\:)(?<imdbid>[^{]+)|(?:rid\:)(?<rid>[^{]+)|(?:tvdbid\:)(?<tvdbid>[^{]+)|(?:tmdbid\:)(?<tmdbid>[^{]+)|(?:doubanid\:)(?<doubanid>[^{]+)|(?:season\:)(?<season>[^{]+)|(?:episode\:)(?<episode>[^{]+)|(?:year\:)(?<year>[^{]+)|(?:genre\:)(?<genre>[^{]+))\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex MovieRegex = new Regex(@"\{((?:imdbid\:)(?<imdbid>[^{]+)|(?:doubanid\:)(?<doubanid>[^{]+)|(?:tmdbid\:)(?<tmdbid>[^{]+)|(?:traktid\:)(?<traktid>[^{]+)|(?:year\:)(?<year>[^{]+)|(?:genre\:)(?<genre>[^{]+))\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex MusicRegex = new Regex(@"\{((?:artist\:)(?<artist>[^{]+)|(?:album\:)(?<album>[^{]+)|(?:track\:)(?<track>[^{]+)|(?:label\:)(?<label>[^{]+)|(?:year\:)(?<year>[^{]+)|(?:genre\:)(?<genre>[^{]+))\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex BookRegex = new Regex(@"\{((?:author\:)(?<author>[^{]+)|(?:publisher\:)(?<publisher>[^{]+)|(?:title\:)(?<title>[^{]+)|(?:year\:)(?<year>[^{]+)|(?:genre\:)(?<genre>[^{]+))\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex TvRegex = new (@"\{((?:imdbid\:)(?<imdbid>[^{]+)|(?:rid\:)(?<rid>[^{]+)|(?:tvdbid\:)(?<tvdbid>[^{]+)|(?:tmdbid\:)(?<tmdbid>[^{]+)|(?:doubanid\:)(?<doubanid>[^{]+)|(?:season\:)(?<season>[^{]+)|(?:episode\:)(?<episode>[^{]+)|(?:year\:)(?<year>[^{]+)|(?:genre\:)(?<genre>[^{]+))\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex MovieRegex = new (@"\{((?:imdbid\:)(?<imdbid>[^{]+)|(?:doubanid\:)(?<doubanid>[^{]+)|(?:tmdbid\:)(?<tmdbid>[^{]+)|(?:traktid\:)(?<traktid>[^{]+)|(?:year\:)(?<year>[^{]+)|(?:genre\:)(?<genre>[^{]+))\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex MusicRegex = new (@"\{((?:artist\:)(?<artist>[^{]+)|(?:album\:)(?<album>[^{]+)|(?:track\:)(?<track>[^{]+)|(?:label\:)(?<label>[^{]+)|(?:year\:)(?<year>[^{]+)|(?:genre\:)(?<genre>[^{]+))\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex BookRegex = new (@"\{((?:author\:)(?<author>[^{]+)|(?:publisher\:)(?<publisher>[^{]+)|(?:title\:)(?<title>[^{]+)|(?:year\:)(?<year>[^{]+)|(?:genre\:)(?<genre>[^{]+))\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public string t { get; set; }
|
||||
public string q { get; set; }
|
||||
@@ -40,6 +41,11 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
|
||||
public void QueryToParams()
|
||||
{
|
||||
if (q.IsNullOrWhiteSpace())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (t == "tvsearch")
|
||||
{
|
||||
var matches = TvRegex.Matches(q);
|
||||
|
||||
@@ -76,8 +76,12 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
select new XElement("item",
|
||||
new XElement("title", RemoveInvalidXMLChars(r.Title)),
|
||||
new XElement("description", RemoveInvalidXMLChars(r.Description)),
|
||||
new XElement("guid", r.Guid), // GUID and (Link or Magnet) are mandatory
|
||||
new XElement("prowlarrindexer", new XAttribute("id", r.IndexerId), r.Indexer),
|
||||
new XElement("guid", r.Guid), // GUID and (Link or Magnet) are mandatory
|
||||
new XElement(
|
||||
"prowlarrindexer",
|
||||
new XAttribute("id", r.IndexerId),
|
||||
new XAttribute("type", r.IndexerPrivacy switch { IndexerPrivacy.Private => "private", IndexerPrivacy.Public => "public", _ => "semi-private" }),
|
||||
r.Indexer),
|
||||
r.InfoUrl == null ? null : new XElement("comments", r.InfoUrl),
|
||||
r.PublishDate == DateTime.MinValue ? new XElement("pubDate", XmlDateFormat(DateTime.Now)) : new XElement("pubDate", XmlDateFormat(r.PublishDate)),
|
||||
new XElement("size", r.Size),
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.Events;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@@ -157,15 +158,22 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
var indexers = _indexerFactory.Enabled();
|
||||
|
||||
if (criteriaBase.IndexerIds != null && criteriaBase.IndexerIds.Count > 0)
|
||||
if (criteriaBase.IndexerIds is { Count: > 0 })
|
||||
{
|
||||
indexers = indexers.Where(i => criteriaBase.IndexerIds.Contains(i.Definition.Id) ||
|
||||
(criteriaBase.IndexerIds.Contains(-1) && i.Protocol == DownloadProtocol.Usenet) ||
|
||||
(criteriaBase.IndexerIds.Contains(-2) && i.Protocol == DownloadProtocol.Torrent))
|
||||
.ToList();
|
||||
|
||||
if (indexers.Count == 0)
|
||||
{
|
||||
_logger.Debug("Search failed due to all selected indexers being unavailable: {0}", string.Join(", ", criteriaBase.IndexerIds));
|
||||
|
||||
throw new SearchFailedException("Search failed due to all selected indexers being unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
if (criteriaBase.Categories != null && criteriaBase.Categories.Length > 0)
|
||||
if (criteriaBase.Categories is { Length: > 0 })
|
||||
{
|
||||
//Only query supported indexers
|
||||
indexers = indexers.Where(i => ((IndexerDefinition)i.Definition).Capabilities.Categories.SupportedCategories(criteriaBase.Categories).Any()).ToList();
|
||||
@@ -173,6 +181,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
if (indexers.Count == 0)
|
||||
{
|
||||
_logger.Debug("All provided categories are unsupported by selected indexers: {0}", string.Join(", ", criteriaBase.Categories));
|
||||
|
||||
return Array.Empty<ReleaseInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Description => "Anidex is a Public torrent tracker and indexer, primarily for English fansub groups of anime";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
|
||||
@@ -25,10 +25,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public override string Name => "Anidub";
|
||||
public override string[] IndexerUrls => new[] { "https://tr.anidub.com/" };
|
||||
public override string Description => "Anidub is russian anime voiceover group and eponymous anime tracker.";
|
||||
public override string Description => "Anidub is RUSSIAN anime voiceover group and eponymous anime tracker.";
|
||||
public override string Language => "ru-RU";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.SemiPrivate;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Description => "AnimeBytes (AB) is the largest private torrent tracker that specialises in anime and anime-related content.";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
public override TimeSpan RateLimit => TimeSpan.FromSeconds(4);
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Name => "AnimeTorrents";
|
||||
public override string[] IndexerUrls => new[] { "https://animetorrents.me/" };
|
||||
public override string Description => "Definitive source for anime and manga";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override bool SupportsPagination => true;
|
||||
public override TimeSpan RateLimit => TimeSpan.FromSeconds(4);
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Description => "Animedia is RUSSIAN anime voiceover group and eponymous anime tracker.";
|
||||
public override string Language => "ru-RU";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Description => "A movies tracker";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
|
||||
@@ -59,7 +59,6 @@ public class AudioBookBay : TorrentIndexerBase<NoAuthTorrentBaseSettings>
|
||||
};
|
||||
public override string Description => "AudioBook Bay (ABB) is a public Torrent Tracker for AUDIOBOOKS";
|
||||
public override string Language => "en-US";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
public override int PageSize => 15;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
@@ -12,7 +12,6 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
{
|
||||
public abstract class AvistazBase : TorrentIndexerBase<AvistazSettings>
|
||||
{
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override bool SupportsRss => true;
|
||||
public override bool SupportsSearch => true;
|
||||
public override bool SupportsPagination => true;
|
||||
@@ -93,12 +92,10 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
var jsonResponse = new HttpResponse<AvistazErrorResponse>(ex.Response);
|
||||
return new ValidationFailure(string.Empty, jsonResponse.Resource?.Message ?? "Unauthorized request to indexer");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn(ex, "Unable to connect to indexer");
|
||||
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log above the ValidationFailure for more details. " + ex.Message);
|
||||
}
|
||||
_logger.Warn(ex, "Unable to connect to indexer");
|
||||
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log above the ValidationFailure for more details. " + ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -107,7 +104,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log above the ValidationFailure for more details");
|
||||
}
|
||||
|
||||
return null;
|
||||
return await base.TestConnection();
|
||||
}
|
||||
|
||||
private async Task<string> GetToken()
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Description => "BB is a Private Torrent Tracker for 0DAY / GENERAL";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public override string[] IndexerUrls => new[] { "https://bakabt.me/" };
|
||||
public override string Description => "Anime Community";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
@@ -413,7 +412,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
FreeleechOnly = false;
|
||||
}
|
||||
|
||||
[FieldDefinition(4, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Search freeleech torrents only")]
|
||||
[FieldDefinition(4, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Show freeleech torrents only")]
|
||||
public bool FreeleechOnly { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Add Romaji Title", Type = FieldType.Checkbox, HelpText = "Add releases for Romaji Title")]
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public override string[] IndexerUrls => new string[] { "https://beyond-hd.me/" };
|
||||
public override string Description => "BeyondHD (BHD) is a Private Torrent Tracker for HD MOVIES / TV";
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override string Description => "The binary Usenet search engine";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
public override bool SupportsRss => false;
|
||||
public override bool SupportsPagination => true;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user