mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-03-24 17:44:06 -04:00
Compare commits
105 Commits
changelog-
...
cardigann-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c45eb68fa | ||
|
|
38ba810ae8 | ||
|
|
4e3f460a24 | ||
|
|
0d918a0aa9 | ||
|
|
a110412665 | ||
|
|
6c97f1b6ee | ||
|
|
470779ead2 | ||
|
|
b371f2d913 | ||
|
|
3ff3452e2d | ||
|
|
df13537e29 | ||
|
|
5d2fefde8f | ||
|
|
ffb3f83324 | ||
|
|
1c125733b2 | ||
|
|
2af7fac15e | ||
|
|
f172d17ecc | ||
|
|
c69843931e | ||
|
|
cd3e99ad87 | ||
|
|
1cce39b404 | ||
|
|
9b46ab73e4 | ||
|
|
a352c053ab | ||
|
|
b33e45d266 | ||
|
|
817d61de91 | ||
|
|
c7e5cc6462 | ||
|
|
25596fc2e8 | ||
|
|
9ff0b90626 | ||
|
|
4f4c011436 | ||
|
|
bd0115931f | ||
|
|
a0d18c546e | ||
|
|
d935b0df82 | ||
|
|
9e37f69224 | ||
|
|
2805c4f18b | ||
|
|
dae21f22b9 | ||
|
|
7ddbe09eca | ||
|
|
90e3c809c3 | ||
|
|
ec8cf5f57a | ||
|
|
f4bbf2f8af | ||
|
|
ea98d41472 | ||
|
|
b8cb0fd291 | ||
|
|
d3dfa620ac | ||
|
|
049668f307 | ||
|
|
c400575aac | ||
|
|
6f122fb2e4 | ||
|
|
a9c210f8e7 | ||
|
|
1068ba8915 | ||
|
|
635335d876 | ||
|
|
2ed51cd933 | ||
|
|
b74c46c554 | ||
|
|
7029e0d6ee | ||
|
|
438ea380f5 | ||
|
|
4eec675d61 | ||
|
|
0a9bd8287f | ||
|
|
b583ac3a97 | ||
|
|
4be41ff3fb | ||
|
|
b911f8cc08 | ||
|
|
22face385f | ||
|
|
3e700b63c2 | ||
|
|
df0b8fc660 | ||
|
|
f96dbbfc21 | ||
|
|
4a75f92cb5 | ||
|
|
dd05a9dbd4 | ||
|
|
e78b8d5346 | ||
|
|
74a1d95ab7 | ||
|
|
f929a7e62f | ||
|
|
e9e4248af4 | ||
|
|
9e3b43ef12 | ||
|
|
738a690aac | ||
|
|
3b7c59e9bb | ||
|
|
b8ca28d955 | ||
|
|
8797bb7d1c | ||
|
|
be430732f5 | ||
|
|
e7b1380b85 | ||
|
|
c29735741c | ||
|
|
f56a13a375 | ||
|
|
148d8ee249 | ||
|
|
3547028b96 | ||
|
|
e4ffa1873e | ||
|
|
2e85a21576 | ||
|
|
0a111e7572 | ||
|
|
25217c0ee8 | ||
|
|
791592927c | ||
|
|
4137193a60 | ||
|
|
99816bfd36 | ||
|
|
59e5b5bd52 | ||
|
|
7fa0a2b33c | ||
|
|
0593ca6b9e | ||
|
|
06a26b5c87 | ||
|
|
dcae6dc151 | ||
|
|
04e3ed0ffe | ||
|
|
1ed5ed9179 | ||
|
|
d292d086ee | ||
|
|
f68915c5dd | ||
|
|
01e970e1a7 | ||
|
|
68df439498 | ||
|
|
33de7ca7ab | ||
|
|
ae2d9b795b | ||
|
|
eadea745f8 | ||
|
|
f958c4aefa | ||
|
|
4cf9fb0e79 | ||
|
|
bfa68347e6 | ||
|
|
f97b35403d | ||
|
|
bf2e057247 | ||
|
|
5a278f4e9d | ||
|
|
232a6efd0d | ||
|
|
7e01c93b2c | ||
|
|
d58f6551e6 |
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -5,9 +5,9 @@ body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the bug you encountered.
|
||||
description: Please search to see if an open or closed issue already exists for the bug you encountered. If a bug exists and is closed note that it may only be fixed in an unstable branch.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
- label: I have searched the existing open and closed issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -5,9 +5,9 @@ body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the feature you are requesting.
|
||||
description: Please search to see if an open or closed issue already exists for the feature you are requesting. If a request exists and is closed note that it may only be fixed in an unstable branch.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
- label: I have searched the existing open and closed issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
||||
132
CODE_OF_CONDUCT.md
Normal file
132
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
<development@prowlarr.com>.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '0.4.5'
|
||||
majorVersion: '0.4.11'
|
||||
minorVersion: $[counter('minorVersion', 1)]
|
||||
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||
@@ -748,7 +748,7 @@ stages:
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: Packages
|
||||
itemPattern: '/$(pattern)'
|
||||
itemPattern: '**/$(pattern)'
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
- bash: |
|
||||
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
|
||||
@@ -1108,4 +1108,5 @@ stages:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
DISCORDCHANNELID: $(discordChannelId)
|
||||
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
||||
DISCORDTHREADID: $(discordThreadId)
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import FormInputHelpText from './FormInputHelpText';
|
||||
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
|
||||
import InfoInput from './InfoInput';
|
||||
import KeyValueListInput from './KeyValueListInput';
|
||||
import NewznabCategorySelectInputConnector from './NewznabCategorySelectInputConnector';
|
||||
import NumberInput from './NumberInput';
|
||||
import OAuthInputConnector from './OAuthInputConnector';
|
||||
import PasswordInput from './PasswordInput';
|
||||
@@ -68,6 +69,9 @@ function getComponent(type) {
|
||||
case inputTypes.PATH:
|
||||
return PathInputConnector;
|
||||
|
||||
case inputTypes.CATEGORY_SELECT:
|
||||
return NewznabCategorySelectInputConnector;
|
||||
|
||||
case inputTypes.INDEXER_FLAGS_SELECT:
|
||||
return IndexerFlagsSelectInputConnector;
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ function createMapStateToProps() {
|
||||
});
|
||||
|
||||
return {
|
||||
value,
|
||||
value: value || [],
|
||||
values
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export const DEVICE = 'device';
|
||||
export const KEY_VALUE_LIST = 'keyValueList';
|
||||
export const INFO = 'info';
|
||||
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
|
||||
export const CATEGORY_SELECT = 'newznabCategorySelect';
|
||||
export const NUMBER = 'number';
|
||||
export const OAUTH = 'oauth';
|
||||
export const PASSWORD = 'password';
|
||||
@@ -32,6 +33,7 @@ export const all = [
|
||||
KEY_VALUE_LIST,
|
||||
INFO,
|
||||
MOVIE_MONITORED_SELECT,
|
||||
CATEGORY_SELECT,
|
||||
NUMBER,
|
||||
OAUTH,
|
||||
PASSWORD,
|
||||
|
||||
@@ -221,7 +221,7 @@ class IndexerIndex extends Component {
|
||||
|
||||
onKeyUp = (event) => {
|
||||
const jumpBarItems = this.state.jumpBarItems.order;
|
||||
if (event.path.length === 4) {
|
||||
if (event.composedPath && event.composedPath().length === 4) {
|
||||
if (event.keyCode === keyCodes.HOME && event.ctrlKey) {
|
||||
this.setState({ jumpToCharacter: jumpBarItems[0] });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import AddCategoryModalContentConnector from './AddCategoryModalContentConnector';
|
||||
|
||||
function AddCategoryModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
size={sizes.MEDIUM}
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<AddCategoryModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
AddCategoryModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AddCategoryModal;
|
||||
@@ -0,0 +1,50 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||
import AddCategoryModal from './AddCategoryModal';
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
const section = 'settings.downloadClientCategories';
|
||||
|
||||
return {
|
||||
dispatchClearPendingChanges() {
|
||||
dispatch(clearPendingChanges({ section }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class AddCategoryModalConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onModalClose = () => {
|
||||
this.props.dispatchClearPendingChanges();
|
||||
this.props.onModalClose();
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
dispatchClearPendingChanges,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<AddCategoryModal
|
||||
{...otherProps}
|
||||
onModalClose={this.onModalClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddCategoryModalConnector.propTypes = {
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
dispatchClearPendingChanges: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(null, createMapDispatchToProps)(AddCategoryModalConnector);
|
||||
@@ -0,0 +1,5 @@
|
||||
.deleteButton {
|
||||
composes: button from '~Components/Link/Button.css';
|
||||
|
||||
margin-right: auto;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Button from 'Components/Link/Button';
|
||||
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
||||
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 { inputTypes, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './AddCategoryModalContent.css';
|
||||
|
||||
function AddCategoryModalContent(props) {
|
||||
const {
|
||||
advancedSettings,
|
||||
item,
|
||||
onInputChange,
|
||||
onFieldChange,
|
||||
onCancelPress,
|
||||
onSavePress,
|
||||
onDeleteSpecificationPress,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const {
|
||||
id,
|
||||
clientCategory,
|
||||
categories
|
||||
} = item;
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onCancelPress}>
|
||||
<ModalHeader>
|
||||
{`${id ? 'Edit' : 'Add'} Category`}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<Form
|
||||
{...otherProps}
|
||||
>
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
{translate('DownloadClientCategory')}
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="clientCategory"
|
||||
{...clientCategory}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
{translate('MappedCategories')}
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CATEGORY_SELECT}
|
||||
name="categories"
|
||||
{...categories}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{
|
||||
id &&
|
||||
<Button
|
||||
className={styles.deleteButton}
|
||||
kind={kinds.DANGER}
|
||||
onPress={onDeleteSpecificationPress}
|
||||
>
|
||||
{translate('Delete')}
|
||||
</Button>
|
||||
}
|
||||
|
||||
<Button
|
||||
onPress={onCancelPress}
|
||||
>
|
||||
{translate('Cancel')}
|
||||
</Button>
|
||||
|
||||
<SpinnerErrorButton
|
||||
isSpinning={false}
|
||||
onPress={onSavePress}
|
||||
>
|
||||
{translate('Save')}
|
||||
</SpinnerErrorButton>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
|
||||
AddCategoryModalContent.propTypes = {
|
||||
advancedSettings: PropTypes.bool.isRequired,
|
||||
item: PropTypes.object.isRequired,
|
||||
onInputChange: PropTypes.func.isRequired,
|
||||
onFieldChange: PropTypes.func.isRequired,
|
||||
onCancelPress: PropTypes.func.isRequired,
|
||||
onSavePress: PropTypes.func.isRequired,
|
||||
onDeleteSpecificationPress: PropTypes.func
|
||||
};
|
||||
|
||||
export default AddCategoryModalContent;
|
||||
@@ -0,0 +1,78 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { clearDownloadClientCategoryPending, saveDownloadClientCategory, setDownloadClientCategoryFieldValue, setDownloadClientCategoryValue } from 'Store/Actions/settingsActions';
|
||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||
import AddCategoryModalContent from './AddCategoryModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.advancedSettings,
|
||||
createProviderSettingsSelector('downloadClientCategories'),
|
||||
(advancedSettings, specification) => {
|
||||
return {
|
||||
advancedSettings,
|
||||
...specification
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setDownloadClientCategoryValue,
|
||||
setDownloadClientCategoryFieldValue,
|
||||
saveDownloadClientCategory,
|
||||
clearDownloadClientCategoryPending
|
||||
};
|
||||
|
||||
class AddCategoryModalContentConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onInputChange = ({ name, value }) => {
|
||||
this.props.setDownloadClientCategoryValue({ name, value });
|
||||
};
|
||||
|
||||
onFieldChange = ({ name, value }) => {
|
||||
this.props.setDownloadClientCategoryFieldValue({ name, value });
|
||||
};
|
||||
|
||||
onCancelPress = () => {
|
||||
this.props.clearDownloadClientCategoryPending();
|
||||
this.props.onModalClose();
|
||||
};
|
||||
|
||||
onSavePress = () => {
|
||||
this.props.saveDownloadClientCategory({ id: this.props.id });
|
||||
this.props.onModalClose();
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AddCategoryModalContent
|
||||
{...this.props}
|
||||
onCancelPress={this.onCancelPress}
|
||||
onSavePress={this.onSavePress}
|
||||
onInputChange={this.onInputChange}
|
||||
onFieldChange={this.onFieldChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddCategoryModalContentConnector.propTypes = {
|
||||
id: PropTypes.number,
|
||||
item: PropTypes.object.isRequired,
|
||||
setDownloadClientCategoryValue: PropTypes.func.isRequired,
|
||||
setDownloadClientCategoryFieldValue: PropTypes.func.isRequired,
|
||||
clearDownloadClientCategoryPending: PropTypes.func.isRequired,
|
||||
saveDownloadClientCategory: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(AddCategoryModalContentConnector);
|
||||
@@ -0,0 +1,32 @@
|
||||
.customFormat {
|
||||
composes: card from '~Components/Card.css';
|
||||
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.nameContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.name {
|
||||
@add-mixin truncate;
|
||||
|
||||
margin-bottom: 5px;
|
||||
font-weight: 300;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.labels {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 5px;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.tooltipLabel {
|
||||
composes: label from '~Components/Label.css';
|
||||
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Card from 'Components/Card';
|
||||
import Label from 'Components/Label';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AddCategoryModalConnector from './AddCategoryModalConnector';
|
||||
import styles from './Category.css';
|
||||
|
||||
class Category extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isEditSpecificationModalOpen: false,
|
||||
isDeleteSpecificationModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onEditSpecificationPress = () => {
|
||||
this.setState({ isEditSpecificationModalOpen: true });
|
||||
};
|
||||
|
||||
onEditSpecificationModalClose = () => {
|
||||
this.setState({ isEditSpecificationModalOpen: false });
|
||||
};
|
||||
|
||||
onDeleteSpecificationPress = () => {
|
||||
this.setState({
|
||||
isEditSpecificationModalOpen: false,
|
||||
isDeleteSpecificationModalOpen: true
|
||||
});
|
||||
};
|
||||
|
||||
onDeleteSpecificationModalClose = () => {
|
||||
this.setState({ isDeleteSpecificationModalOpen: false });
|
||||
};
|
||||
|
||||
onConfirmDeleteSpecification = () => {
|
||||
this.props.onConfirmDeleteSpecification(this.props.id);
|
||||
};
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
clientCategory,
|
||||
categories
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={styles.customFormat}
|
||||
overlayContent={true}
|
||||
onPress={this.onEditSpecificationPress}
|
||||
>
|
||||
<div className={styles.nameContainer}>
|
||||
<div className={styles.name}>
|
||||
{clientCategory}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Label kind={kinds.PRIMARY}>
|
||||
{`${categories.length} ${categories.length > 1 ? translate('Categories') : translate('Category')}`}
|
||||
</Label>
|
||||
|
||||
<AddCategoryModalConnector
|
||||
id={id}
|
||||
isOpen={this.state.isEditSpecificationModalOpen}
|
||||
onModalClose={this.onEditSpecificationModalClose}
|
||||
onDeleteSpecificationPress={this.onDeleteSpecificationPress}
|
||||
/>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={this.state.isDeleteSpecificationModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('DeleteClientCategory')}
|
||||
message={
|
||||
<div>
|
||||
<div>
|
||||
{translate('AreYouSureYouWantToDeleteCategory', [name])}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
confirmLabel={translate('Delete')}
|
||||
onConfirm={this.onConfirmDeleteSpecification}
|
||||
onCancel={this.onDeleteSpecificationModalClose}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Category.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
categories: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
clientCategory: PropTypes.string.isRequired,
|
||||
onConfirmDeleteSpecification: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default Category;
|
||||
@@ -1,11 +1,14 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Card from 'Components/Card';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup';
|
||||
import Icon from 'Components/Icon';
|
||||
import Button from 'Components/Link/Button';
|
||||
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
@@ -13,12 +16,33 @@ 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 { inputTypes, kinds } from 'Helpers/Props';
|
||||
import { icons, inputTypes, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AddCategoryModalConnector from './Categories/AddCategoryModalConnector';
|
||||
import Category from './Categories/Category';
|
||||
import styles from './EditDownloadClientModalContent.css';
|
||||
|
||||
class EditDownloadClientModalContent extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isAddCategoryModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
onAddCategoryPress = () => {
|
||||
this.setState({ isAddCategoryModalOpen: true });
|
||||
};
|
||||
|
||||
onAddCategoryModalClose = () => {
|
||||
this.setState({ isAddCategoryModalOpen: false });
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -27,6 +51,7 @@ class EditDownloadClientModalContent extends Component {
|
||||
advancedSettings,
|
||||
isFetching,
|
||||
error,
|
||||
categories,
|
||||
isSaving,
|
||||
isTesting,
|
||||
saveError,
|
||||
@@ -37,19 +62,27 @@ class EditDownloadClientModalContent extends Component {
|
||||
onSavePress,
|
||||
onTestPress,
|
||||
onDeleteDownloadClientPress,
|
||||
onConfirmDeleteCategory,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isAddCategoryModalOpen
|
||||
} = this.state;
|
||||
|
||||
const {
|
||||
id,
|
||||
implementationName,
|
||||
name,
|
||||
enable,
|
||||
priority,
|
||||
supportsCategories,
|
||||
fields,
|
||||
message
|
||||
} = item;
|
||||
|
||||
console.log(supportsCategories);
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
@@ -136,6 +169,43 @@ class EditDownloadClientModalContent extends Component {
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{
|
||||
supportsCategories.value ?
|
||||
<FieldSet legend={translate('MappedCategories')}>
|
||||
<div className={styles.customFormats}>
|
||||
{
|
||||
categories.map((tag) => {
|
||||
return (
|
||||
<Category
|
||||
key={tag.id}
|
||||
{...tag}
|
||||
onConfirmDeleteSpecification={onConfirmDeleteCategory}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
<Card
|
||||
className={styles.addCategory}
|
||||
onPress={this.onAddCategoryPress}
|
||||
>
|
||||
<div className={styles.center}>
|
||||
<Icon
|
||||
name={icons.ADD}
|
||||
size={25}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</FieldSet> :
|
||||
null
|
||||
}
|
||||
|
||||
<AddCategoryModalConnector
|
||||
isOpen={isAddCategoryModalOpen}
|
||||
onModalClose={this.onAddCategoryModalClose}
|
||||
/>
|
||||
|
||||
</Form>
|
||||
}
|
||||
</ModalBody>
|
||||
@@ -185,13 +255,15 @@ EditDownloadClientModalContent.propTypes = {
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
saveError: PropTypes.object,
|
||||
isTesting: PropTypes.bool.isRequired,
|
||||
categories: PropTypes.arrayOf(PropTypes.object),
|
||||
item: PropTypes.object.isRequired,
|
||||
onInputChange: PropTypes.func.isRequired,
|
||||
onFieldChange: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
onSavePress: PropTypes.func.isRequired,
|
||||
onTestPress: PropTypes.func.isRequired,
|
||||
onDeleteDownloadClientPress: PropTypes.func
|
||||
onDeleteDownloadClientPress: PropTypes.func,
|
||||
onConfirmDeleteCategory: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default EditDownloadClientModalContent;
|
||||
|
||||
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { saveDownloadClient, setDownloadClientFieldValue, setDownloadClientValue, testDownloadClient } from 'Store/Actions/settingsActions';
|
||||
import { deleteDownloadClientCategory, fetchDownloadClientCategories, saveDownloadClient, setDownloadClientFieldValue, setDownloadClientValue, testDownloadClient } from 'Store/Actions/settingsActions';
|
||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||
import EditDownloadClientModalContent from './EditDownloadClientModalContent';
|
||||
|
||||
@@ -10,10 +10,12 @@ function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.advancedSettings,
|
||||
createProviderSettingsSelector('downloadClients'),
|
||||
(advancedSettings, downloadClient) => {
|
||||
(state) => state.settings.downloadClientCategories,
|
||||
(advancedSettings, downloadClient, categories) => {
|
||||
return {
|
||||
advancedSettings,
|
||||
...downloadClient
|
||||
...downloadClient,
|
||||
categories: categories.items
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -23,7 +25,9 @@ const mapDispatchToProps = {
|
||||
setDownloadClientValue,
|
||||
setDownloadClientFieldValue,
|
||||
saveDownloadClient,
|
||||
testDownloadClient
|
||||
testDownloadClient,
|
||||
fetchDownloadClientCategories,
|
||||
deleteDownloadClientCategory
|
||||
};
|
||||
|
||||
class EditDownloadClientModalContentConnector extends Component {
|
||||
@@ -31,6 +35,14 @@ class EditDownloadClientModalContentConnector extends Component {
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
id,
|
||||
tagsFromId
|
||||
} = this.props;
|
||||
this.props.fetchDownloadClientCategories({ id: tagsFromId || id });
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
|
||||
this.props.onModalClose();
|
||||
@@ -56,6 +68,10 @@ class EditDownloadClientModalContentConnector extends Component {
|
||||
this.props.testDownloadClient({ id: this.props.id });
|
||||
};
|
||||
|
||||
onConfirmDeleteCategory = (id) => {
|
||||
this.props.deleteDownloadClientCategory({ id });
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -67,6 +83,7 @@ class EditDownloadClientModalContentConnector extends Component {
|
||||
onTestPress={this.onTestPress}
|
||||
onInputChange={this.onInputChange}
|
||||
onFieldChange={this.onFieldChange}
|
||||
onConfirmDeleteCategory={this.onConfirmDeleteCategory}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -74,10 +91,13 @@ class EditDownloadClientModalContentConnector extends Component {
|
||||
|
||||
EditDownloadClientModalContentConnector.propTypes = {
|
||||
id: PropTypes.number,
|
||||
tagsFromId: PropTypes.number,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
saveError: PropTypes.object,
|
||||
item: PropTypes.object.isRequired,
|
||||
fetchDownloadClientCategories: PropTypes.func.isRequired,
|
||||
deleteDownloadClientCategory: PropTypes.func.isRequired,
|
||||
setDownloadClientValue: PropTypes.func.isRequired,
|
||||
setDownloadClientFieldValue: PropTypes.func.isRequired,
|
||||
saveDownloadClient: PropTypes.func.isRequired,
|
||||
|
||||
171
frontend/src/Store/Actions/Settings/downloadClientCategories.js
Normal file
171
frontend/src/Store/Actions/Settings/downloadClientCategories.js
Normal file
@@ -0,0 +1,171 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import createClearReducer from 'Store/Actions/Creators/Reducers/createClearReducer';
|
||||
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
|
||||
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||
import { createThunk } from 'Store/thunks';
|
||||
import getNextId from 'Utilities/State/getNextId';
|
||||
import getProviderState from 'Utilities/State/getProviderState';
|
||||
import getSectionState from 'Utilities/State/getSectionState';
|
||||
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
|
||||
import { removeItem, set, update, updateItem } from '../baseActions';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
||||
const section = 'settings.downloadClientCategories';
|
||||
|
||||
//
|
||||
// Actions Types
|
||||
|
||||
export const FETCH_DOWNLOAD_CLIENT_CATEGORIES = 'settings/downloadClientCategories/fetchDownloadClientCategories';
|
||||
export const FETCH_DOWNLOAD_CLIENT_CATEGORY_SCHEMA = 'settings/downloadClientCategories/fetchDownloadClientCategorySchema';
|
||||
export const SELECT_DOWNLOAD_CLIENT_CATEGORY_SCHEMA = 'settings/downloadClientCategories/selectDownloadClientCategorySchema';
|
||||
export const SET_DOWNLOAD_CLIENT_CATEGORY_VALUE = 'settings/downloadClientCategories/setDownloadClientCategoryValue';
|
||||
export const SET_DOWNLOAD_CLIENT_CATEGORY_FIELD_VALUE = 'settings/downloadClientCategories/setDownloadClientCategoryFieldValue';
|
||||
export const SAVE_DOWNLOAD_CLIENT_CATEGORY = 'settings/downloadClientCategories/saveDownloadClientCategory';
|
||||
export const DELETE_DOWNLOAD_CLIENT_CATEGORY = 'settings/downloadClientCategories/deleteDownloadClientCategory';
|
||||
export const DELETE_ALL_DOWNLOAD_CLIENT_CATEGORY = 'settings/downloadClientCategories/deleteAllDownloadClientCategory';
|
||||
export const CLEAR_DOWNLOAD_CLIENT_CATEGORIES = 'settings/downloadClientCategories/clearDownloadClientCategories';
|
||||
export const CLEAR_DOWNLOAD_CLIENT_CATEGORY_PENDING = 'settings/downloadClientCategories/clearDownloadClientCategoryPending';
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const fetchDownloadClientCategories = createThunk(FETCH_DOWNLOAD_CLIENT_CATEGORIES);
|
||||
export const fetchDownloadClientCategorySchema = createThunk(FETCH_DOWNLOAD_CLIENT_CATEGORY_SCHEMA);
|
||||
export const selectDownloadClientCategorySchema = createAction(SELECT_DOWNLOAD_CLIENT_CATEGORY_SCHEMA);
|
||||
|
||||
export const saveDownloadClientCategory = createThunk(SAVE_DOWNLOAD_CLIENT_CATEGORY);
|
||||
export const deleteDownloadClientCategory = createThunk(DELETE_DOWNLOAD_CLIENT_CATEGORY);
|
||||
export const deleteAllDownloadClientCategory = createThunk(DELETE_ALL_DOWNLOAD_CLIENT_CATEGORY);
|
||||
|
||||
export const setDownloadClientCategoryValue = createAction(SET_DOWNLOAD_CLIENT_CATEGORY_VALUE, (payload) => {
|
||||
return {
|
||||
section,
|
||||
...payload
|
||||
};
|
||||
});
|
||||
|
||||
export const setDownloadClientCategoryFieldValue = createAction(SET_DOWNLOAD_CLIENT_CATEGORY_FIELD_VALUE, (payload) => {
|
||||
return {
|
||||
section,
|
||||
...payload
|
||||
};
|
||||
});
|
||||
|
||||
export const clearDownloadClientCategory = createAction(CLEAR_DOWNLOAD_CLIENT_CATEGORIES);
|
||||
|
||||
export const clearDownloadClientCategoryPending = createThunk(CLEAR_DOWNLOAD_CLIENT_CATEGORY_PENDING);
|
||||
|
||||
//
|
||||
// Details
|
||||
|
||||
export default {
|
||||
|
||||
//
|
||||
// State
|
||||
|
||||
defaultState: {
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
isSchemaFetching: false,
|
||||
isSchemaPopulated: false,
|
||||
schemaError: null,
|
||||
schema: [],
|
||||
selectedSchema: {},
|
||||
isSaving: false,
|
||||
saveError: null,
|
||||
items: [],
|
||||
pendingChanges: {}
|
||||
},
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
actionHandlers: {
|
||||
[FETCH_DOWNLOAD_CLIENT_CATEGORIES]: (getState, payload, dispatch) => {
|
||||
let tags = [];
|
||||
if (payload.id) {
|
||||
const cfState = getSectionState(getState(), 'settings.downloadClients', true);
|
||||
const cf = cfState.items[cfState.itemMap[payload.id]];
|
||||
tags = cf.categories.map((tag, i) => {
|
||||
return {
|
||||
id: i + 1,
|
||||
...tag
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
dispatch(batchActions([
|
||||
update({ section, data: tags }),
|
||||
set({
|
||||
section,
|
||||
isPopulated: true
|
||||
})
|
||||
]));
|
||||
},
|
||||
|
||||
[SAVE_DOWNLOAD_CLIENT_CATEGORY]: (getState, payload, dispatch) => {
|
||||
const {
|
||||
id,
|
||||
...otherPayload
|
||||
} = payload;
|
||||
|
||||
const saveData = getProviderState({ id, ...otherPayload }, getState, section, false);
|
||||
|
||||
console.log(saveData);
|
||||
|
||||
// we have to set id since not actually posting to server yet
|
||||
if (!saveData.id) {
|
||||
saveData.id = getNextId(getState().settings.downloadClientCategories.items);
|
||||
}
|
||||
|
||||
dispatch(batchActions([
|
||||
updateItem({ section, ...saveData }),
|
||||
set({
|
||||
section,
|
||||
pendingChanges: {}
|
||||
})
|
||||
]));
|
||||
},
|
||||
|
||||
[DELETE_DOWNLOAD_CLIENT_CATEGORY]: (getState, payload, dispatch) => {
|
||||
const id = payload.id;
|
||||
return dispatch(removeItem({ section, id }));
|
||||
},
|
||||
|
||||
[DELETE_ALL_DOWNLOAD_CLIENT_CATEGORY]: (getState, payload, dispatch) => {
|
||||
return dispatch(set({
|
||||
section,
|
||||
items: []
|
||||
}));
|
||||
},
|
||||
|
||||
[CLEAR_DOWNLOAD_CLIENT_CATEGORY_PENDING]: (getState, payload, dispatch) => {
|
||||
return dispatch(set({
|
||||
section,
|
||||
pendingChanges: {}
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// Reducers
|
||||
|
||||
reducers: {
|
||||
[SET_DOWNLOAD_CLIENT_CATEGORY_VALUE]: createSetSettingValueReducer(section),
|
||||
[SET_DOWNLOAD_CLIENT_CATEGORY_FIELD_VALUE]: createSetProviderFieldValueReducer(section),
|
||||
|
||||
[SELECT_DOWNLOAD_CLIENT_CATEGORY_SCHEMA]: (state, { payload }) => {
|
||||
return selectProviderSchema(state, section, payload, (selectedSchema) => {
|
||||
return selectedSchema;
|
||||
});
|
||||
},
|
||||
|
||||
[CLEAR_DOWNLOAD_CLIENT_CATEGORIES]: createClearReducer(section, {
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
items: []
|
||||
})
|
||||
}
|
||||
};
|
||||
@@ -9,6 +9,7 @@ import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/
|
||||
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||
import { createThunk } from 'Store/thunks';
|
||||
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
|
||||
import { set } from '../baseActions';
|
||||
|
||||
//
|
||||
// Variables
|
||||
@@ -90,10 +91,34 @@ export default {
|
||||
[FETCH_DOWNLOAD_CLIENTS]: createFetchHandler(section, '/downloadclient'),
|
||||
[FETCH_DOWNLOAD_CLIENT_SCHEMA]: createFetchSchemaHandler(section, '/downloadclient/schema'),
|
||||
|
||||
[SAVE_DOWNLOAD_CLIENT]: createSaveProviderHandler(section, '/downloadclient'),
|
||||
[SAVE_DOWNLOAD_CLIENT]: (getState, payload, dispatch) => {
|
||||
// move the format tags in as a pending change
|
||||
const state = getState();
|
||||
const pendingChanges = state.settings.downloadClients.pendingChanges;
|
||||
pendingChanges.categories = state.settings.downloadClientCategories.items;
|
||||
dispatch(set({
|
||||
section,
|
||||
pendingChanges
|
||||
}));
|
||||
|
||||
createSaveProviderHandler(section, '/downloadclient')(getState, payload, dispatch);
|
||||
},
|
||||
|
||||
[CANCEL_SAVE_DOWNLOAD_CLIENT]: createCancelSaveProviderHandler(section),
|
||||
[DELETE_DOWNLOAD_CLIENT]: createRemoveItemHandler(section, '/downloadclient'),
|
||||
[TEST_DOWNLOAD_CLIENT]: createTestProviderHandler(section, '/downloadclient'),
|
||||
|
||||
[TEST_DOWNLOAD_CLIENT]: (getState, payload, dispatch) => {
|
||||
const state = getState();
|
||||
const pendingChanges = state.settings.downloadClients.pendingChanges;
|
||||
pendingChanges.categories = state.settings.downloadClientCategories.items;
|
||||
dispatch(set({
|
||||
section,
|
||||
pendingChanges
|
||||
}));
|
||||
|
||||
createTestProviderHandler(section, '/downloadclient')(getState, payload, dispatch);
|
||||
},
|
||||
|
||||
[CANCEL_TEST_DOWNLOAD_CLIENT]: createCancelTestProviderHandler(section),
|
||||
[TEST_ALL_DOWNLOAD_CLIENTS]: createTestAllProvidersHandler(section, '/downloadclient')
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import createHandleActions from './Creators/createHandleActions';
|
||||
import applications from './Settings/applications';
|
||||
import appProfiles from './Settings/appProfiles';
|
||||
import development from './Settings/development';
|
||||
import downloadClientCategories from './Settings/downloadClientCategories';
|
||||
import downloadClients from './Settings/downloadClients';
|
||||
import general from './Settings/general';
|
||||
import indexerCategories from './Settings/indexerCategories';
|
||||
@@ -11,6 +12,7 @@ import indexerProxies from './Settings/indexerProxies';
|
||||
import notifications from './Settings/notifications';
|
||||
import ui from './Settings/ui';
|
||||
|
||||
export * from './Settings/downloadClientCategories';
|
||||
export * from './Settings/downloadClients';
|
||||
export * from './Settings/general';
|
||||
export * from './Settings/indexerCategories';
|
||||
@@ -32,6 +34,7 @@ export const section = 'settings';
|
||||
export const defaultState = {
|
||||
advancedSettings: false,
|
||||
|
||||
downloadClientCategories: downloadClientCategories.defaultState,
|
||||
downloadClients: downloadClients.defaultState,
|
||||
general: general.defaultState,
|
||||
indexerCategories: indexerCategories.defaultState,
|
||||
@@ -61,6 +64,7 @@ export const toggleAdvancedSettings = createAction(TOGGLE_ADVANCED_SETTINGS);
|
||||
// Action Handlers
|
||||
|
||||
export const actionHandlers = handleThunks({
|
||||
...downloadClientCategories.actionHandlers,
|
||||
...downloadClients.actionHandlers,
|
||||
...general.actionHandlers,
|
||||
...indexerCategories.actionHandlers,
|
||||
@@ -81,6 +85,7 @@ export const reducers = createHandleActions({
|
||||
return Object.assign({}, state, { advancedSettings: !state.advancedSettings });
|
||||
},
|
||||
|
||||
...downloadClientCategories.reducers,
|
||||
...downloadClients.reducers,
|
||||
...general.reducers,
|
||||
...indexerCategories.reducers,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import * as dark from './dark';
|
||||
import * as light from './light';
|
||||
|
||||
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const auto = defaultDark ? { ...dark } : { ...light };
|
||||
|
||||
export default {
|
||||
auto,
|
||||
light,
|
||||
dark
|
||||
};
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Globalization;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Test.ExtensionTests.StringExtensionTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class IsValidIPAddressFixture
|
||||
{
|
||||
[TestCase("192.168.0.1")]
|
||||
[TestCase("::1")]
|
||||
[TestCase("2001:db8:4006:812::200e")]
|
||||
public void should_validate_ip_address(string input)
|
||||
{
|
||||
input.IsValidIpAddress().Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("sonarr.tv")]
|
||||
public void should_not_parse_non_ip_address(string input)
|
||||
{
|
||||
input.IsValidIpAddress().Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,6 +212,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Platform(Exclude = "MacOsX", Reason = "Azure agent update prevents brotli on OSX")]
|
||||
public void should_execute_get_using_brotli()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/brotli");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using FluentAssertions;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Test.Common;
|
||||
@@ -10,6 +10,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[TestCase("abc://my_host.com:8080/root/api/")]
|
||||
[TestCase("abc://my_host.com:8080//root/api/")]
|
||||
[TestCase("abc://my_host.com:8080/root//api/")]
|
||||
[TestCase("abc://[::1]:8080/root//api/")]
|
||||
public void should_parse(string uri)
|
||||
{
|
||||
var newUri = new HttpUri(uri);
|
||||
|
||||
@@ -98,15 +98,30 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
// Internal
|
||||
[TestCase(@"[Info] MigrationController: *** Migrating Database=prowlarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;Enlist=False ***")]
|
||||
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
|
||||
|
||||
[TestCase(@"[Info] MigrationController: *** Migrating Database=prowlarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
|
||||
public void should_clean_message(string message)
|
||||
{
|
||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||
|
||||
cleansedMessage.Should().NotContain("mySecret");
|
||||
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
|
||||
cleansedMessage.Should().NotContain("01233210");
|
||||
}
|
||||
|
||||
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
|
||||
public void should_keep_message(string message)
|
||||
{
|
||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||
|
||||
cleansedMessage.Should().NotContain("mySecret");
|
||||
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
|
||||
cleansedMessage.Should().NotContain("01233210");
|
||||
|
||||
cleansedMessage.Should().Contain("shouldkeep1");
|
||||
cleansedMessage.Should().Contain("shouldkeep2");
|
||||
cleansedMessage.Should().Contain("shouldkeep3");
|
||||
}
|
||||
|
||||
[TestCase(@"Some message (from 32.2.3.5 user agent)")]
|
||||
[TestCase(@"Auth-Invalidated ip 32.2.3.5")]
|
||||
[TestCase(@"Auth-Success ip 32.2.3.5")]
|
||||
|
||||
@@ -7,34 +7,50 @@ namespace NzbDrone.Common.Extensions
|
||||
{
|
||||
public static bool IsLocalAddress(this IPAddress ipAddress)
|
||||
{
|
||||
if (ipAddress.IsIPv6LinkLocal)
|
||||
// Map back to IPv4 if mapped to IPv6, for example "::ffff:1.2.3.4" to "1.2.3.4".
|
||||
if (ipAddress.IsIPv4MappedToIPv6)
|
||||
{
|
||||
return true;
|
||||
ipAddress = ipAddress.MapToIPv4();
|
||||
}
|
||||
|
||||
// Checks loopback ranges for both IPv4 and IPv6.
|
||||
if (IPAddress.IsLoopback(ipAddress))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// IPv4
|
||||
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
byte[] bytes = ipAddress.GetAddressBytes();
|
||||
switch (bytes[0])
|
||||
{
|
||||
case 10:
|
||||
case 127:
|
||||
return true;
|
||||
case 172:
|
||||
return bytes[1] < 32 && bytes[1] >= 16;
|
||||
case 192:
|
||||
return bytes[1] == 168;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return IsLocalIPv4(ipAddress.GetAddressBytes());
|
||||
}
|
||||
|
||||
// IPv6
|
||||
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
return ipAddress.IsIPv6LinkLocal ||
|
||||
ipAddress.IsIPv6UniqueLocal ||
|
||||
ipAddress.IsIPv6SiteLocal;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsLocalIPv4(byte[] ipv4Bytes)
|
||||
{
|
||||
// Link local (no IP assigned by DHCP): 169.254.0.0 to 169.254.255.255 (169.254.0.0/16)
|
||||
bool IsLinkLocal() => ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;
|
||||
|
||||
// Class A private range: 10.0.0.0 – 10.255.255.255 (10.0.0.0/8)
|
||||
bool IsClassA() => ipv4Bytes[0] == 10;
|
||||
|
||||
// Class B private range: 172.16.0.0 – 172.31.255.255 (172.16.0.0/12)
|
||||
bool IsClassB() => ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;
|
||||
|
||||
// Class C private range: 192.168.0.0 – 192.168.255.255 (192.168.0.0/16)
|
||||
bool IsClassC() => ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;
|
||||
|
||||
return IsLinkLocal() || IsClassA() || IsClassC() || IsClassB();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
@@ -231,5 +232,30 @@ namespace NzbDrone.Common.Extensions
|
||||
.Replace("'", "%27")
|
||||
.Replace("%7E", "~");
|
||||
}
|
||||
|
||||
public static bool IsValidIpAddress(this string value)
|
||||
{
|
||||
if (!IPAddress.TryParse(value, out var parsedAddress))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsedAddress.Equals(IPAddress.Parse("255.255.255.255")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsedAddress.IsIPv6Multicast)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return parsedAddress.AddressFamily == AddressFamily.InterNetwork || parsedAddress.AddressFamily == AddressFamily.InterNetworkV6;
|
||||
}
|
||||
|
||||
public static string ToUrlHost(this string input)
|
||||
{
|
||||
return input.Contains(":") ? $"[{input}]" : input;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
AddRequestHeaders(requestMessage, request.Headers);
|
||||
}
|
||||
|
||||
var httpClient = GetClient(request.Url);
|
||||
var httpClient = GetClient(request.Url, request.ProxySettings);
|
||||
|
||||
var sw = new Stopwatch();
|
||||
|
||||
@@ -154,9 +154,9 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual System.Net.Http.HttpClient GetClient(HttpUri uri)
|
||||
protected virtual System.Net.Http.HttpClient GetClient(HttpUri uri, HttpProxySettings requestProxy)
|
||||
{
|
||||
var proxySettings = _proxySettingsProvider.GetProxySettings(uri);
|
||||
var proxySettings = requestProxy ?? _proxySettingsProvider.GetProxySettings(uri);
|
||||
|
||||
var key = proxySettings?.Key ?? NO_PROXY_KEY;
|
||||
|
||||
@@ -174,6 +174,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
PreAuthenticate = true,
|
||||
MaxConnectionsPerServer = 12,
|
||||
ConnectCallback = onConnect,
|
||||
PooledConnectionLifetime = TimeSpan.FromMinutes(10),
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = _certificateValidationService.ShouldByPassValidationError
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Net.Http;
|
||||
using System.Text;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
@@ -37,7 +38,7 @@ namespace NzbDrone.Common.Http
|
||||
public HttpMethod Method { get; set; }
|
||||
public HttpHeader Headers { get; set; }
|
||||
public Encoding Encoding { get; set; }
|
||||
public IWebProxy Proxy { get; set; }
|
||||
public HttpProxySettings ProxySettings { get; set; }
|
||||
public byte[] ContentData { get; set; }
|
||||
public string ContentSummary { get; set; }
|
||||
public ICredentials Credentials { get; set; }
|
||||
|
||||
@@ -89,13 +89,13 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
return (Request.Url += new HttpUri(match.Groups[2].Value)).FullUri;
|
||||
return (Request.Url + new HttpUri(match.Groups[2].Value)).FullUri;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return (Request.Url += new HttpUri(newUrl)).FullUri;
|
||||
return (Request.Url + new HttpUri(newUrl)).FullUri;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Common.Http
|
||||
{
|
||||
public class HttpUri : IEquatable<HttpUri>
|
||||
{
|
||||
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+)(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+|\[[[A-F0-9:]+\])(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private readonly string _uri;
|
||||
public string FullUri => _uri;
|
||||
@@ -70,6 +70,8 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
private void Parse()
|
||||
{
|
||||
var parseSuccess = Uri.TryCreate(_uri, UriKind.RelativeOrAbsolute, out var uri);
|
||||
|
||||
var match = RegexUri.Match(_uri);
|
||||
|
||||
var scheme = match.Groups["scheme"];
|
||||
@@ -79,7 +81,7 @@ namespace NzbDrone.Common.Http
|
||||
var query = match.Groups["query"];
|
||||
var fragment = match.Groups["fragment"];
|
||||
|
||||
if (!match.Success || (scheme.Success && !host.Success && path.Success))
|
||||
if (!parseSuccess || (scheme.Success && !host.Success && path.Success))
|
||||
{
|
||||
throw new ArgumentException("Uri didn't match expected pattern: " + _uri);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
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),
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DryIoc.dll" Version="4.8.8" />
|
||||
<PackageReference Include="DryIoc.dll" Version="5.2.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
|
||||
<PackageReference Include="Sentry" Version="3.19.0" />
|
||||
<PackageReference Include="Sentry" Version="3.21.0" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
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 orpheus_apiFixture : MigrationTest<orpheus_api>
|
||||
{
|
||||
[Test]
|
||||
public void should_convert_and_disable_orpheus_instance()
|
||||
{
|
||||
var db = WithMigrationTestDb(c =>
|
||||
{
|
||||
c.Insert.IntoTable("Indexers").Row(new
|
||||
{
|
||||
Enable = true,
|
||||
Name = "Orpheus",
|
||||
Priority = 25,
|
||||
Added = DateTime.UtcNow,
|
||||
Implementation = "Orpheus",
|
||||
Settings = new GazelleIndexerSettings021
|
||||
{
|
||||
Username = "some name",
|
||||
Password = "some pass"
|
||||
}.ToJson(),
|
||||
ConfigContract = "GazelleSettings"
|
||||
});
|
||||
});
|
||||
|
||||
var items = db.Query<IndexerDefinition022>("SELECT \"Id\", \"Enable\", \"ConfigContract\", \"Settings\" FROM \"Indexers\"");
|
||||
|
||||
items.Should().HaveCount(1);
|
||||
items.First().ConfigContract.Should().Be("OrpheusSettings");
|
||||
items.First().Enable.Should().Be(false);
|
||||
items.First().Settings.Should().NotContain("username");
|
||||
items.First().Settings.Should().NotContain("password");
|
||||
}
|
||||
}
|
||||
|
||||
public class IndexerDefinition022
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public bool Enable { get; set; }
|
||||
public string ConfigContract { get; set; }
|
||||
public string Settings { get; set; }
|
||||
}
|
||||
|
||||
public class GazelleIndexerSettings021
|
||||
{
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "success",
|
||||
"response": []
|
||||
}
|
||||
2578
src/NzbDrone.Core.Test/Files/Indexers/Orpheus/recentfeed.json
Normal file
2578
src/NzbDrone.Core.Test/Files/Indexers/Orpheus/recentfeed.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.History;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.IndexerStats;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerStatsTests
|
||||
{
|
||||
public class IndexerStatisticsServiceFixture : CoreTest<IndexerStatisticsService>
|
||||
{
|
||||
private IndexerDefinition _indexer;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_indexer = Builder<IndexerDefinition>.CreateNew().With(x => x.Id = 5).Build();
|
||||
|
||||
Mocker.GetMock<IIndexerFactory>()
|
||||
.Setup(o => o.All())
|
||||
.Returns(new List<IndexerDefinition> { _indexer });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_pull_stats_if_all_events_are_failures()
|
||||
{
|
||||
var history = new List<History.History>
|
||||
{
|
||||
new History.History
|
||||
{
|
||||
Date = DateTime.UtcNow.AddHours(-1),
|
||||
EventType = HistoryEventType.IndexerRss,
|
||||
Successful = false,
|
||||
Id = 8,
|
||||
IndexerId = 5,
|
||||
Data = new Dictionary<string, string> { { "source", "prowlarr" } }
|
||||
}
|
||||
};
|
||||
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(o => o.Between(It.IsAny<DateTime>(), It.IsAny<DateTime>()))
|
||||
.Returns<DateTime, DateTime>((s, f) => history);
|
||||
|
||||
var statistics = Subject.IndexerStatistics(DateTime.UtcNow.AddMonths(-1), DateTime.UtcNow);
|
||||
|
||||
statistics.IndexerStatistics.Count.Should().Be(1);
|
||||
statistics.IndexerStatistics.First().AverageResponseTime.Should().Be(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
|
||||
torrentInfo.InfoUrl.Should().Be("https://avistaz.to/torrent/187240-japan-sinks-people-of-hope-2021-s01e05-720p-nf-web-dl-ddp20-x264-seikel");
|
||||
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
|
||||
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
|
||||
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-14 23:26:21"));
|
||||
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-15 04:26:21"));
|
||||
torrentInfo.Size.Should().Be(935127615);
|
||||
torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2");
|
||||
torrentInfo.MagnetUrl.Should().Be(null);
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
|
||||
torrentInfo.InfoUrl.Should().Be("https://exoticaz.to/torrent/64040-ssis-419-my-first-experience-is-yua-mikami-from-the-day-i-lost-my-virginity-i-was-devoted-to-sex");
|
||||
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
|
||||
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
|
||||
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-06-11 11:04:50"));
|
||||
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-06-11 16:04:50"));
|
||||
torrentInfo.Size.Should().Be(7085405541);
|
||||
torrentInfo.InfoHash.Should().Be("asdjfiasdf54asd7f4a2sdf544asdf");
|
||||
torrentInfo.MagnetUrl.Should().Be(null);
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
|
||||
torrentInfo.InfoUrl.Should().Be("https://privatehd.to/torrent/78506-godzilla-2014-2160p-uhd-bluray-remux-hdr-hevc-atmos-triton");
|
||||
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
|
||||
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
|
||||
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-03-21 00:24:49"));
|
||||
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-03-21 05:24:49"));
|
||||
torrentInfo.Size.Should().Be(69914591044);
|
||||
torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2");
|
||||
torrentInfo.MagnetUrl.Should().Be(null);
|
||||
|
||||
@@ -12,6 +12,7 @@ using NzbDrone.Core.Indexers.Definitions;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
|
||||
{
|
||||
@@ -64,5 +65,19 @@ namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
|
||||
torrentInfo.DownloadVolumeFactor.Should().Be(1);
|
||||
torrentInfo.UploadVolumeFactor.Should().Be(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task should_not_error_if_empty_response()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/GazelleGames/recentfeed-empty.json");
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
|
||||
|
||||
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new int[] { 2000 } })).Releases;
|
||||
|
||||
releases.Should().HaveCount(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,14 +15,14 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
|
||||
[TestFixture]
|
||||
public class NewznabCapabilitiesProviderFixture : CoreTest<NewznabCapabilitiesProvider>
|
||||
{
|
||||
private NewznabSettings _settings;
|
||||
private GenericNewznabSettings _settings;
|
||||
private IndexerDefinition _definition;
|
||||
private string _caps;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_settings = new NewznabSettings()
|
||||
_settings = new GenericNewznabSettings()
|
||||
{
|
||||
BaseUrl = "http://indxer.local"
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
|
||||
|
||||
_caps = new IndexerCapabilities();
|
||||
Mocker.GetMock<INewznabCapabilitiesProvider>()
|
||||
.Setup(v => v.GetCapabilities(It.IsAny<NewznabSettings>(), It.IsAny<IndexerDefinition>()))
|
||||
.Setup(v => v.GetCapabilities(It.IsAny<GenericNewznabSettings>(), It.IsAny<IndexerDefinition>()))
|
||||
.Returns(_caps);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
|
||||
{
|
||||
public class NewznabRequestGeneratorFixture : CoreTest<NewznabRequestGenerator>
|
||||
public class NewznabRequestGeneratorFixture : CoreTest<GenericNewznabRequestGenerator>
|
||||
{
|
||||
private MovieSearchCriteria _movieSearchCriteria;
|
||||
private TvSearchCriteria _tvSearchCriteria;
|
||||
@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
Subject.Settings = new NewznabSettings()
|
||||
Subject.Settings = new GenericNewznabSettings()
|
||||
{
|
||||
BaseUrl = "http://127.0.0.1:1234/",
|
||||
ApiKey = "abcd",
|
||||
@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
|
||||
_capabilities = new IndexerCapabilities();
|
||||
|
||||
Mocker.GetMock<INewznabCapabilitiesProvider>()
|
||||
.Setup(v => v.GetCapabilities(It.IsAny<NewznabSettings>(), It.IsAny<IndexerDefinition>()))
|
||||
.Setup(v => v.GetCapabilities(It.IsAny<GenericNewznabSettings>(), It.IsAny<IndexerDefinition>()))
|
||||
.Returns(_capabilities);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.Definitions;
|
||||
using NzbDrone.Core.Indexers.Gazelle;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests.OrpheusTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class OrpheusFixture : CoreTest<Orpheus>
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Subject.Definition = new IndexerDefinition()
|
||||
{
|
||||
Name = "Orpheus",
|
||||
Settings = new OrpheusSettings() { Apikey = "somekey" }
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task should_parse_recent_feed_from_GazelleGames()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Orpheus/recentfeed.json");
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
|
||||
|
||||
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new int[] { 2000 } })).Releases;
|
||||
|
||||
releases.Should().HaveCount(65);
|
||||
releases.First().Should().BeOfType<GazelleInfo>();
|
||||
|
||||
var torrentInfo = releases.First() as GazelleInfo;
|
||||
|
||||
torrentInfo.Title.Should().Be("The Beatles - Abbey Road (1969) [MP3 V2 (VBR)] [BD]");
|
||||
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
|
||||
torrentInfo.DownloadUrl.Should().Be("https://orpheus.network/ajax.php?action=download&id=1902448");
|
||||
torrentInfo.InfoUrl.Should().Be("https://orpheus.network/torrents.php?id=466&torrentid=1902448");
|
||||
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
|
||||
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
|
||||
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-08-08 2:07:39"));
|
||||
torrentInfo.Size.Should().Be(68296866);
|
||||
torrentInfo.InfoHash.Should().Be(null);
|
||||
torrentInfo.MagnetUrl.Should().Be(null);
|
||||
torrentInfo.Peers.Should().Be(0);
|
||||
torrentInfo.Seeders.Should().Be(0);
|
||||
torrentInfo.ImdbId.Should().Be(0);
|
||||
torrentInfo.TmdbId.Should().Be(0);
|
||||
torrentInfo.TvdbId.Should().Be(0);
|
||||
torrentInfo.Languages.Should().HaveCount(0);
|
||||
torrentInfo.Subs.Should().HaveCount(0);
|
||||
torrentInfo.DownloadVolumeFactor.Should().Be(1);
|
||||
torrentInfo.UploadVolumeFactor.Should().Be(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
|
||||
};
|
||||
|
||||
Mocker.GetMock<IRarbgTokenProvider>()
|
||||
.Setup(v => v.GetToken(It.IsAny<RarbgSettings>(), It.IsAny<string>()))
|
||||
.Setup(v => v.GetToken(It.IsAny<RarbgSettings>()))
|
||||
.Returns("validtoken");
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
|
||||
|
||||
_caps = new IndexerCapabilities();
|
||||
Mocker.GetMock<INewznabCapabilitiesProvider>()
|
||||
.Setup(v => v.GetCapabilities(It.IsAny<NewznabSettings>(), It.IsAny<IndexerDefinition>()))
|
||||
.Setup(v => v.GetCapabilities(It.IsAny<GenericNewznabSettings>(), It.IsAny<IndexerDefinition>()))
|
||||
.Returns(_caps);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||
<PackageReference Include="NBuilder" Version="6.1.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="12.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />
|
||||
@@ -21,7 +21,4 @@
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Datastore\Migration\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -206,7 +206,7 @@ namespace NzbDrone.Core.Configuration
|
||||
public string PostgresMainDb => _postgresOptions?.MainDb ?? GetValue("PostgresMainDb", "prowlarr-main", persist: false);
|
||||
public string PostgresLogDb => _postgresOptions?.LogDb ?? GetValue("PostgresLogDb", "prowlarr-log", persist: false);
|
||||
public int PostgresPort => (_postgresOptions?.Port ?? 0) != 0 ? _postgresOptions.Port : GetValueInt("PostgresPort", 5432, persist: false);
|
||||
public string Theme => GetValue("Theme", "light", persist: false);
|
||||
public string Theme => GetValue("Theme", "auto", persist: false);
|
||||
public bool LogSql => GetValueBoolean("LogSql", false, persist: false);
|
||||
public int LogRotate => GetValueInt("LogRotate", 50, persist: false);
|
||||
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Data.SQLite;
|
||||
using System.Net.Sockets;
|
||||
using NLog;
|
||||
using Npgsql;
|
||||
using NzbDrone.Common.Disk;
|
||||
@@ -125,6 +126,37 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/prowlarr/faq#i-am-getting-an-error-database-disk-image-is-malformed", e, fileName);
|
||||
}
|
||||
catch (NpgsqlException e)
|
||||
{
|
||||
if (e.InnerException is SocketException)
|
||||
{
|
||||
var retryCount = 3;
|
||||
|
||||
while (true)
|
||||
{
|
||||
Logger.Error(e, "Failure to connect to Postgres DB, {0} retries remaining", retryCount);
|
||||
|
||||
try
|
||||
{
|
||||
_migrationController.Migrate(connectionString, migrationContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (--retryCount > 0)
|
||||
{
|
||||
System.Threading.Thread.Sleep(5000);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new ProwlarrStartupException(ex, "Error creating main database");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ProwlarrStartupException(e, "Error creating main database");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ProwlarrStartupException(e, "Error creating main database");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using FluentMigrator;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@@ -21,6 +22,8 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
cmd.Transaction = tran;
|
||||
cmd.CommandText = "SELECT \"Id\", \"Settings\" FROM \"Indexers\" WHERE \"Implementation\" = 'Redacted'";
|
||||
|
||||
var updatedIndexers = new List<Indexer008>();
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
@@ -45,19 +48,26 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
|
||||
// write new json back to db, switch to new ConfigContract, and disable the indexer
|
||||
settings = jsonObject.ToJson();
|
||||
using (var updateCmd = conn.CreateCommand())
|
||||
|
||||
updatedIndexers.Add(new Indexer008
|
||||
{
|
||||
updateCmd.Transaction = tran;
|
||||
updateCmd.CommandText = "UPDATE \"Indexers\" SET \"Settings\" = ?, \"ConfigContract\" = ?, \"Enable\" = 0 WHERE \"Id\" = ?";
|
||||
updateCmd.AddParameter(settings);
|
||||
updateCmd.AddParameter("RedactedSettings");
|
||||
updateCmd.AddParameter(id);
|
||||
updateCmd.ExecuteNonQuery();
|
||||
}
|
||||
Id = id,
|
||||
Settings = settings,
|
||||
ConfigContract = "RedactedSettings",
|
||||
Enable = false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Indexer008
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Settings { get; set; }
|
||||
public string ConfigContract { get; set; }
|
||||
public bool Enable { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
71
src/NzbDrone.Core/Datastore/Migration/022_orpheus_api.cs
Normal file
71
src/NzbDrone.Core/Datastore/Migration/022_orpheus_api.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using Dapper;
|
||||
using FluentMigrator;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
using static NzbDrone.Core.Datastore.Migration.redacted_api;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(22)]
|
||||
public class orpheus_api : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Execute.WithConnection(MigrateToRedactedApi);
|
||||
}
|
||||
|
||||
private void MigrateToRedactedApi(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.Transaction = tran;
|
||||
cmd.CommandText = "SELECT \"Id\", \"Settings\" FROM \"Indexers\" WHERE \"Implementation\" = 'Orpheus'";
|
||||
|
||||
var updatedIndexers = new List<Indexer008>();
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
var id = reader.GetInt32(0);
|
||||
var settings = reader.GetString(1);
|
||||
if (!string.IsNullOrWhiteSpace(settings))
|
||||
{
|
||||
var jsonObject = Json.Deserialize<JObject>(settings);
|
||||
|
||||
// Remove username
|
||||
if (jsonObject.ContainsKey("username"))
|
||||
{
|
||||
jsonObject.Remove("username");
|
||||
}
|
||||
|
||||
// Remove password
|
||||
if (jsonObject.ContainsKey("password"))
|
||||
{
|
||||
jsonObject.Remove("password");
|
||||
}
|
||||
|
||||
// write new json back to db, switch to new ConfigContract, and disable the indexer
|
||||
settings = jsonObject.ToJson();
|
||||
|
||||
updatedIndexers.Add(new Indexer008
|
||||
{
|
||||
Id = id,
|
||||
Settings = settings,
|
||||
ConfigContract = "OrpheusSettings",
|
||||
Enable = false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var updateSql = "UPDATE \"Indexers\" SET \"Settings\" = @Settings, \"ConfigContract\" = @ConfigContract, \"Enable\" = @Enable WHERE \"Id\" = @Id";
|
||||
conn.Execute(updateSql, updatedIndexers, transaction: tran);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(023)]
|
||||
public class download_client_categories : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("DownloadClients")
|
||||
.AddColumn("Categories").AsString().WithDefaultValue("[]");
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/NzbDrone.Core/Datastore/Migration/024_newznab_yml.cs
Normal file
14
src/NzbDrone.Core/Datastore/Migration/024_newznab_yml.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(24)]
|
||||
public class newznab_yml : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Update.Table("Indexers").Set(new { Implementation = "GenericNewznab", ConfigContract = "GenericNewznabSettings" }).Where(new { Implementation = "Newznab" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,8 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
Mapper.Entity<DownloadClientDefinition>("DownloadClients").RegisterModel()
|
||||
.Ignore(x => x.ImplementationName)
|
||||
.Ignore(i => i.Protocol)
|
||||
.Ignore(d => d.SupportsCategories)
|
||||
.Ignore(d => d.Protocol)
|
||||
.Ignore(d => d.Tags);
|
||||
|
||||
Mapper.Entity<NotificationDefinition>("Notifications").RegisterModel()
|
||||
@@ -115,6 +116,7 @@ namespace NzbDrone.Core.Datastore
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<string>>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<ReleaseInfo>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<HashSet<int>>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<DownloadClientCategory>>());
|
||||
SqlMapper.AddTypeHandler(new OsPathConverter());
|
||||
SqlMapper.RemoveTypeMap(typeof(Guid));
|
||||
SqlMapper.RemoveTypeMap(typeof(Guid?));
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace NzbDrone.Core.Download.Clients.Aria2
|
||||
|
||||
public override string Name => "Aria2";
|
||||
|
||||
public override bool SupportsCategories => false;
|
||||
|
||||
public Aria2(IAria2Proxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
|
||||
@@ -74,6 +74,8 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
|
||||
public override string Name => "Torrent Blackhole";
|
||||
|
||||
public override bool SupportsCategories => false;
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
{
|
||||
failures.AddIfNotNull(TestFolder(Settings.TorrentFolder, "TorrentFolder"));
|
||||
|
||||
@@ -45,6 +45,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
}
|
||||
|
||||
public override string Name => "Usenet Blackhole";
|
||||
public override bool SupportsCategories => false;
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
@@ -38,9 +39,10 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
}
|
||||
|
||||
// _proxy.SetTorrentSeedingConfiguration(actualHash, remoteMovie.SeedConfiguration, Settings);
|
||||
if (Settings.Category.IsNotNullOrWhiteSpace())
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
if (category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_proxy.SetTorrentLabel(actualHash, Settings.Category, Settings);
|
||||
_proxy.SetTorrentLabel(actualHash, category, Settings);
|
||||
}
|
||||
|
||||
if (Settings.Priority == (int)DelugePriority.First)
|
||||
@@ -61,9 +63,10 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
}
|
||||
|
||||
// _proxy.SetTorrentSeedingConfiguration(actualHash, release.SeedConfiguration, Settings);
|
||||
if (Settings.Category.IsNotNullOrWhiteSpace())
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
if (category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_proxy.SetTorrentLabel(actualHash, Settings.Category, Settings);
|
||||
_proxy.SetTorrentLabel(actualHash, category, Settings);
|
||||
}
|
||||
|
||||
if (Settings.Priority == (int)DelugePriority.First)
|
||||
@@ -75,6 +78,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
}
|
||||
|
||||
public override string Name => "Deluge";
|
||||
public override bool SupportsCategories => true;
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
{
|
||||
@@ -139,7 +143,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
|
||||
private ValidationFailure TestCategory()
|
||||
{
|
||||
if (Settings.Category.IsNullOrWhiteSpace())
|
||||
if (Categories.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -156,23 +160,42 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
|
||||
var labels = _proxy.GetAvailableLabels(Settings);
|
||||
|
||||
if (Settings.Category.IsNotNullOrWhiteSpace() && !labels.Contains(Settings.Category))
|
||||
{
|
||||
_proxy.AddLabel(Settings.Category, Settings);
|
||||
labels = _proxy.GetAvailableLabels(Settings);
|
||||
var categories = Categories.Select(c => c.ClientCategory).ToList();
|
||||
categories.Add(Settings.Category);
|
||||
|
||||
if (!labels.Contains(Settings.Category))
|
||||
foreach (var category in categories)
|
||||
{
|
||||
if (category.IsNotNullOrWhiteSpace() && !labels.Contains(category))
|
||||
{
|
||||
return new NzbDroneValidationFailure("Category", "Configuration of label failed")
|
||||
_proxy.AddLabel(category, Settings);
|
||||
labels = _proxy.GetAvailableLabels(Settings);
|
||||
|
||||
if (!labels.Contains(category))
|
||||
{
|
||||
DetailedDescription = "Prowlarr was unable to add the label to Deluge."
|
||||
};
|
||||
return new NzbDroneValidationFailure("Category", "Configuration of label failed")
|
||||
{
|
||||
DetailedDescription = "Prowlarr was unable to add the label to Deluge."
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override void ValidateCategories(List<ValidationFailure> failures)
|
||||
{
|
||||
base.ValidateCategories(failures);
|
||||
|
||||
foreach (var label in Categories)
|
||||
{
|
||||
if (!Regex.IsMatch(label.ClientCategory, "^[-a-z0-9]*$"))
|
||||
{
|
||||
failures.AddIfNotNull(new ValidationFailure(string.Empty, "Mapped Categories allowed characters a-z, 0-9 and -"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ValidationFailure TestGetTorrents()
|
||||
{
|
||||
try
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
string[] GetAvailablePlugins(DelugeSettings settings);
|
||||
string[] GetEnabledPlugins(DelugeSettings settings);
|
||||
string[] GetAvailableLabels(DelugeSettings settings);
|
||||
DelugeLabel GetLabelOptions(DelugeSettings settings);
|
||||
DelugeLabel GetLabelOptions(DelugeSettings settings, string label);
|
||||
void SetTorrentLabel(string hash, string label, DelugeSettings settings);
|
||||
void SetTorrentConfiguration(string hash, string key, object value, DelugeSettings settings);
|
||||
void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, DelugeSettings settings);
|
||||
@@ -158,9 +158,9 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
return response;
|
||||
}
|
||||
|
||||
public DelugeLabel GetLabelOptions(DelugeSettings settings)
|
||||
public DelugeLabel GetLabelOptions(DelugeSettings settings, string label)
|
||||
{
|
||||
var response = ProcessRequest<DelugeLabel>(settings, "label.get_options", settings.Category);
|
||||
var response = ProcessRequest<DelugeLabel>(settings, "label.get_options", label);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
[FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
[FieldDefinition(5, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback Category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
public string Category { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing items")]
|
||||
|
||||
@@ -18,9 +18,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
.When(c => c.TvDirectory.IsNotNullOrWhiteSpace())
|
||||
.WithMessage("Cannot start with /");
|
||||
|
||||
RuleFor(c => c.TvCategory).Matches(@"^\.?[-a-z]*$", RegexOptions.IgnoreCase).WithMessage("Allowed characters a-z and -");
|
||||
RuleFor(c => c.Category).Matches(@"^\.?[-a-z]*$", RegexOptions.IgnoreCase).WithMessage("Allowed characters a-z and -");
|
||||
|
||||
RuleFor(c => c.TvCategory).Empty()
|
||||
RuleFor(c => c.Category).Empty()
|
||||
.When(c => c.TvDirectory.IsNotNullOrWhiteSpace())
|
||||
.WithMessage("Cannot use Category and Directory");
|
||||
}
|
||||
@@ -45,8 +45,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
[FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
|
||||
public string TvCategory { get; set; }
|
||||
[FieldDefinition(5, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
|
||||
public string Category { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, HelpText = "Optional shared folder to put downloads into, leave blank to use the default Download Station location")]
|
||||
public string TvDirectory { get; set; }
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
}
|
||||
|
||||
public override string Name => "Download Station";
|
||||
public override bool SupportsCategories => false;
|
||||
|
||||
public override ProviderMessage Message => new ProviderMessage("Prowlarr is unable to connect to Download Station if 2-Factor Authentication is enabled on your DSM account", ProviderMessageType.Warning);
|
||||
|
||||
@@ -198,7 +199,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
if (downloadDir != null)
|
||||
{
|
||||
var sharedFolder = downloadDir.Split('\\', '/')[0];
|
||||
var fieldName = Settings.TvDirectory.IsNotNullOrWhiteSpace() ? nameof(Settings.TvDirectory) : nameof(Settings.TvCategory);
|
||||
var fieldName = Settings.TvDirectory.IsNotNullOrWhiteSpace() ? nameof(Settings.TvDirectory) : nameof(Settings.Category);
|
||||
|
||||
var folderInfo = _fileStationProxy.GetInfoFileOrDirectory($"/{downloadDir}", Settings);
|
||||
|
||||
@@ -311,11 +312,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
{
|
||||
return Settings.TvDirectory.TrimStart('/');
|
||||
}
|
||||
else if (Settings.TvCategory.IsNotNullOrWhiteSpace())
|
||||
else if (Settings.Category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var destDir = GetDefaultDir();
|
||||
|
||||
return $"{destDir.TrimEnd('/')}/{Settings.TvCategory}";
|
||||
return $"{destDir.TrimEnd('/')}/{Settings.Category}";
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
}
|
||||
|
||||
public override string Name => "Download Station";
|
||||
public override bool SupportsCategories => false;
|
||||
|
||||
public override ProviderMessage Message => new ProviderMessage("Prowlarr is unable to connect to Download Station if 2-Factor Authentication is enabled on your DSM account", ProviderMessageType.Warning);
|
||||
|
||||
@@ -101,7 +102,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
if (downloadDir != null)
|
||||
{
|
||||
var sharedFolder = downloadDir.Split('\\', '/')[0];
|
||||
var fieldName = Settings.TvDirectory.IsNotNullOrWhiteSpace() ? nameof(Settings.TvDirectory) : nameof(Settings.TvCategory);
|
||||
var fieldName = Settings.TvDirectory.IsNotNullOrWhiteSpace() ? nameof(Settings.TvDirectory) : nameof(Settings.Category);
|
||||
|
||||
var folderInfo = _fileStationProxy.GetInfoFileOrDirectory($"/{downloadDir}", Settings);
|
||||
|
||||
@@ -272,11 +273,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
{
|
||||
return Settings.TvDirectory.TrimStart('/');
|
||||
}
|
||||
else if (Settings.TvCategory.IsNotNullOrWhiteSpace())
|
||||
else if (Settings.Category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var destDir = GetDefaultDir();
|
||||
|
||||
return $"{destDir.TrimEnd('/')}/{Settings.TvCategory}";
|
||||
return $"{destDir.TrimEnd('/')}/{Settings.Category}";
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Download.Clients.Flood
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> HandleTags(ReleaseInfo release, FloodSettings settings)
|
||||
private static IEnumerable<string> HandleTags(ReleaseInfo release, FloodSettings settings, string mappedCategory)
|
||||
{
|
||||
var result = new HashSet<string>();
|
||||
|
||||
@@ -36,6 +36,11 @@ namespace NzbDrone.Core.Download.Clients.Flood
|
||||
result.UnionWith(settings.Tags);
|
||||
}
|
||||
|
||||
if (mappedCategory != null)
|
||||
{
|
||||
result.Add(mappedCategory);
|
||||
}
|
||||
|
||||
if (settings.AdditionalTags.Any())
|
||||
{
|
||||
foreach (var additionalTag in settings.AdditionalTags)
|
||||
@@ -55,18 +60,19 @@ namespace NzbDrone.Core.Download.Clients.Flood
|
||||
}
|
||||
|
||||
public override string Name => "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)
|
||||
{
|
||||
_proxy.AddTorrentByFile(Convert.ToBase64String(fileContent), HandleTags(release, Settings), Settings);
|
||||
_proxy.AddTorrentByFile(Convert.ToBase64String(fileContent), HandleTags(release, Settings, GetCategoryForRelease(release)), Settings);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
|
||||
{
|
||||
_proxy.AddTorrentByUrl(magnetLink, HandleTags(release, Settings), Settings);
|
||||
_proxy.AddTorrentByUrl(magnetLink, HandleTags(release, Settings, GetCategoryForRelease(release)), Settings);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
|
||||
}
|
||||
|
||||
public override string Name => "Hadouken";
|
||||
public override bool SupportsCategories => true;
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
{
|
||||
@@ -41,14 +42,14 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
|
||||
|
||||
protected override string AddFromMagnetLink(ReleaseInfo release, string hash, string magnetLink)
|
||||
{
|
||||
_proxy.AddTorrentUri(Settings, magnetLink);
|
||||
_proxy.AddTorrentUri(Settings, magnetLink, GetCategoryForRelease(release) ?? Settings.Category);
|
||||
|
||||
return hash.ToUpper();
|
||||
}
|
||||
|
||||
protected override string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
return _proxy.AddTorrentFile(Settings, fileContent).ToUpper();
|
||||
return _proxy.AddTorrentFile(Settings, fileContent, GetCategoryForRelease(release) ?? Settings.Category).ToUpper();
|
||||
}
|
||||
|
||||
private ValidationFailure TestConnection()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
@@ -13,8 +13,8 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
|
||||
HadoukenSystemInfo GetSystemInfo(HadoukenSettings settings);
|
||||
HadoukenTorrent[] GetTorrents(HadoukenSettings settings);
|
||||
IReadOnlyDictionary<string, object> GetConfig(HadoukenSettings settings);
|
||||
string AddTorrentFile(HadoukenSettings settings, byte[] fileContent);
|
||||
void AddTorrentUri(HadoukenSettings settings, string torrentUrl);
|
||||
string AddTorrentFile(HadoukenSettings settings, byte[] fileContent, string label);
|
||||
void AddTorrentUri(HadoukenSettings settings, string torrentUrl, string label);
|
||||
void RemoveTorrent(HadoukenSettings settings, string downloadId);
|
||||
void RemoveTorrentAndData(HadoukenSettings settings, string downloadId);
|
||||
}
|
||||
@@ -47,14 +47,14 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
|
||||
return ProcessRequest<IReadOnlyDictionary<string, object>>(settings, "webui.getSettings");
|
||||
}
|
||||
|
||||
public string AddTorrentFile(HadoukenSettings settings, byte[] fileContent)
|
||||
public string AddTorrentFile(HadoukenSettings settings, byte[] fileContent, string label)
|
||||
{
|
||||
return ProcessRequest<string>(settings, "webui.addTorrent", "file", Convert.ToBase64String(fileContent), new { label = settings.Category });
|
||||
return ProcessRequest<string>(settings, "webui.addTorrent", "file", Convert.ToBase64String(fileContent), new { label });
|
||||
}
|
||||
|
||||
public void AddTorrentUri(HadoukenSettings settings, string torrentUrl)
|
||||
public void AddTorrentUri(HadoukenSettings settings, string torrentUrl, string label)
|
||||
{
|
||||
ProcessRequest<string>(settings, "webui.addTorrent", "url", torrentUrl, new { label = settings.Category });
|
||||
ProcessRequest<string>(settings, "webui.addTorrent", "url", torrentUrl, new { label });
|
||||
}
|
||||
|
||||
public void RemoveTorrent(HadoukenSettings settings, string downloadId)
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
|
||||
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox)]
|
||||
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release.")]
|
||||
public string Category { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
|
||||
@@ -29,8 +29,9 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
protected override string AddFromNzbFile(ReleaseInfo release, string filename, byte[] fileContents)
|
||||
{
|
||||
var priority = Settings.Priority;
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
|
||||
var response = _proxy.DownloadNzb(fileContents, filename, priority, Settings);
|
||||
var response = _proxy.DownloadNzb(fileContents, filename, priority, Settings, category);
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
@@ -41,6 +42,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
}
|
||||
|
||||
public override string Name => "NZBVortex";
|
||||
public override bool SupportsCategories => true;
|
||||
|
||||
protected List<NzbVortexGroup> GetGroups()
|
||||
{
|
||||
@@ -111,19 +113,27 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
|
||||
private ValidationFailure TestCategory()
|
||||
{
|
||||
var group = GetGroups().FirstOrDefault(c => c.GroupName == Settings.Category);
|
||||
var groups = GetGroups();
|
||||
|
||||
if (group == null)
|
||||
foreach (var category in Categories)
|
||||
{
|
||||
if (Settings.Category.IsNotNullOrWhiteSpace())
|
||||
if (!category.ClientCategory.IsNullOrWhiteSpace() && !groups.Any(v => v.GroupName == category.ClientCategory))
|
||||
{
|
||||
return new NzbDroneValidationFailure("Category", "Group does not exist")
|
||||
return new NzbDroneValidationFailure(string.Empty, "Group does not exist")
|
||||
{
|
||||
DetailedDescription = "The Group you entered doesn't exist in NzbVortex. Go to NzbVortex to create it."
|
||||
DetailedDescription = "A mapped category you entered doesn't exist in NzbVortex. Go to NzbVortex to create it."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!Settings.Category.IsNullOrWhiteSpace() && !groups.Any(v => v.GroupName == Settings.Category))
|
||||
{
|
||||
return new NzbDroneValidationFailure("Category", "Category does not exist")
|
||||
{
|
||||
DetailedDescription = "The category you entered doesn't exist in NzbVortex. Go to NzbVortex to create it."
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
public interface INzbVortexProxy
|
||||
{
|
||||
string DownloadNzb(byte[] nzbData, string filename, int priority, NzbVortexSettings settings);
|
||||
string DownloadNzb(byte[] nzbData, string filename, int priority, NzbVortexSettings settings, string group);
|
||||
void Remove(int id, bool deleteData, NzbVortexSettings settings);
|
||||
NzbVortexVersionResponse GetVersion(NzbVortexSettings settings);
|
||||
NzbVortexApiVersionResponse GetApiVersion(NzbVortexSettings settings);
|
||||
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
_authSessionIdCache = cacheManager.GetCache<string>(GetType(), "authCache");
|
||||
}
|
||||
|
||||
public string DownloadNzb(byte[] nzbData, string filename, int priority, NzbVortexSettings settings)
|
||||
public string DownloadNzb(byte[] nzbData, string filename, int priority, NzbVortexSettings settings, string group)
|
||||
{
|
||||
var requestBuilder = BuildRequest(settings).Resource("nzb/add")
|
||||
.Post()
|
||||
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
|
||||
if (settings.Category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
requestBuilder.AddQueryParam("groupname", settings.Category);
|
||||
requestBuilder.AddQueryParam("groupname", group);
|
||||
}
|
||||
|
||||
requestBuilder.AddFormUpload("name", filename, nzbData, "application/x-nzb");
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
[FieldDefinition(3, Label = "API Key", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Group", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
[FieldDefinition(4, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
public string Category { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing items")]
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
|
||||
protected override string AddFromNzbFile(ReleaseInfo release, string filename, byte[] fileContent)
|
||||
{
|
||||
var category = Settings.Category;
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
|
||||
var priority = Settings.Priority;
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
|
||||
protected override string AddFromLink(ReleaseInfo release)
|
||||
{
|
||||
var category = Settings.Category;
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
|
||||
var priority = Settings.Priority;
|
||||
|
||||
@@ -66,6 +66,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
}
|
||||
|
||||
public override string Name => "NZBGet";
|
||||
public override bool SupportsCategories => true;
|
||||
|
||||
protected IEnumerable<NzbgetCategory> GetCategories(Dictionary<string, string> config)
|
||||
{
|
||||
@@ -139,6 +140,18 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
var config = _proxy.GetConfig(Settings);
|
||||
var categories = GetCategories(config);
|
||||
|
||||
foreach (var category in Categories)
|
||||
{
|
||||
if (!category.ClientCategory.IsNullOrWhiteSpace() && !categories.Any(v => v.Name == category.ClientCategory))
|
||||
{
|
||||
return new NzbDroneValidationFailure(string.Empty, "Category does not exist")
|
||||
{
|
||||
InfoLink = _proxy.GetBaseUrl(Settings),
|
||||
DetailedDescription = "A mapped category you entered doesn't exist in NZBGet. Go to NZBGet to create it."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!Settings.Category.IsNullOrWhiteSpace() && !categories.Any(v => v.Name == Settings.Category))
|
||||
{
|
||||
return new NzbDroneValidationFailure("Category", "Category does not exist")
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
public string Category { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority for items added from Prowlarr")]
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
|
||||
}
|
||||
|
||||
public override string Name => "Pneumatic";
|
||||
public override bool SupportsCategories => false;
|
||||
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
|
||||
|
||||
|
||||
@@ -52,8 +52,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
//var addHasSetShareLimits = setShareLimits && ProxyApiVersion >= new Version(2, 8, 1);
|
||||
var itemToTop = Settings.Priority == (int)QBittorrentPriority.First;
|
||||
var forceStart = (QBittorrentState)Settings.InitialState == QBittorrentState.ForceStart;
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
|
||||
Proxy.AddTorrentFromUrl(magnetLink, null, Settings);
|
||||
Proxy.AddTorrentFromUrl(magnetLink, null, Settings, category);
|
||||
|
||||
if (itemToTop || forceStart)
|
||||
{
|
||||
@@ -100,8 +101,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
//var addHasSetShareLimits = setShareLimits && ProxyApiVersion >= new Version(2, 8, 1);
|
||||
var itemToTop = Settings.Priority == (int)QBittorrentPriority.First;
|
||||
var forceStart = (QBittorrentState)Settings.InitialState == QBittorrentState.ForceStart;
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
|
||||
Proxy.AddTorrentFromFile(filename, fileContent, null, Settings);
|
||||
Proxy.AddTorrentFromFile(filename, fileContent, null, Settings, category);
|
||||
|
||||
if (itemToTop || forceStart)
|
||||
{
|
||||
@@ -167,6 +169,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
|
||||
public override string Name => "qBittorrent";
|
||||
public override bool SupportsCategories => true;
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
{
|
||||
@@ -197,7 +200,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
else if (version < Version.Parse("1.6"))
|
||||
{
|
||||
// API version 6 introduced support for labels
|
||||
if (Settings.Category.IsNotNullOrWhiteSpace())
|
||||
if (Settings.Category.IsNotNullOrWhiteSpace() || Categories.Count > 0)
|
||||
{
|
||||
return new NzbDroneValidationFailure("Category", "Category is not supported")
|
||||
{
|
||||
@@ -205,15 +208,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
};
|
||||
}
|
||||
}
|
||||
else if (Settings.Category.IsNullOrWhiteSpace())
|
||||
{
|
||||
// warn if labels are supported, but category is not provided
|
||||
return new NzbDroneValidationFailure("Category", "Category is recommended")
|
||||
{
|
||||
IsWarning = true,
|
||||
DetailedDescription = "Prowlarr will not attempt to import completed downloads without a category."
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (DownloadClientAuthenticationException ex)
|
||||
{
|
||||
@@ -251,7 +245,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
private ValidationFailure TestCategory()
|
||||
{
|
||||
if (Settings.Category.IsNullOrWhiteSpace())
|
||||
if (Settings.Category.IsNullOrWhiteSpace() && Categories.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -265,6 +259,23 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
Dictionary<string, QBittorrentLabel> labels = Proxy.GetLabels(Settings);
|
||||
|
||||
foreach (var category in Categories)
|
||||
{
|
||||
if (category.ClientCategory.IsNotNullOrWhiteSpace() && !labels.ContainsKey(category.ClientCategory))
|
||||
{
|
||||
Proxy.AddLabel(category.ClientCategory, Settings);
|
||||
labels = Proxy.GetLabels(Settings);
|
||||
|
||||
if (!labels.ContainsKey(category.ClientCategory))
|
||||
{
|
||||
return new NzbDroneValidationFailure(string.Empty, "Configuration of label failed")
|
||||
{
|
||||
DetailedDescription = "Prowlarr was unable to add the label to qBittorrent."
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Settings.Category.IsNotNullOrWhiteSpace() && !labels.ContainsKey(Settings.Category))
|
||||
{
|
||||
Proxy.AddLabel(Settings.Category, Settings);
|
||||
|
||||
@@ -18,8 +18,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
QBittorrentTorrentProperties GetTorrentProperties(string hash, QBittorrentSettings settings);
|
||||
List<QBittorrentTorrentFile> GetTorrentFiles(string hash, QBittorrentSettings settings);
|
||||
|
||||
void AddTorrentFromUrl(string torrentUrl, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings);
|
||||
void AddTorrentFromFile(string fileName, byte[] fileContent, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings);
|
||||
void AddTorrentFromUrl(string torrentUrl, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings, string category);
|
||||
void AddTorrentFromFile(string fileName, byte[] fileContent, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings, string category);
|
||||
|
||||
void RemoveTorrent(string hash, bool removeData, QBittorrentSettings settings);
|
||||
void SetTorrentLabel(string hash, string label, QBittorrentSettings settings);
|
||||
|
||||
@@ -113,15 +113,15 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
return response;
|
||||
}
|
||||
|
||||
public void AddTorrentFromUrl(string torrentUrl, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings)
|
||||
public void AddTorrentFromUrl(string torrentUrl, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings, string category)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/command/download")
|
||||
.Post()
|
||||
.AddFormParameter("urls", torrentUrl);
|
||||
|
||||
if (settings.Category.IsNotNullOrWhiteSpace())
|
||||
if (category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.AddFormParameter("category", settings.Category);
|
||||
request.AddFormParameter("category", category);
|
||||
}
|
||||
|
||||
// Note: ForceStart is handled by separate api call
|
||||
@@ -143,15 +143,15 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
}
|
||||
|
||||
public void AddTorrentFromFile(string fileName, byte[] fileContent, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings)
|
||||
public void AddTorrentFromFile(string fileName, byte[] fileContent, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings, string category)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/command/upload")
|
||||
.Post()
|
||||
.AddFormUpload("torrents", fileName, fileContent);
|
||||
|
||||
if (settings.Category.IsNotNullOrWhiteSpace())
|
||||
if (category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.AddFormParameter("category", settings.Category);
|
||||
request.AddFormParameter("category", category);
|
||||
}
|
||||
|
||||
// Note: ForceStart is handled by separate api call
|
||||
|
||||
@@ -119,14 +119,14 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
return response;
|
||||
}
|
||||
|
||||
public void AddTorrentFromUrl(string torrentUrl, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings)
|
||||
public void AddTorrentFromUrl(string torrentUrl, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings, string category)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/add")
|
||||
.Post()
|
||||
.AddFormParameter("urls", torrentUrl);
|
||||
if (settings.Category.IsNotNullOrWhiteSpace())
|
||||
if (category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.AddFormParameter("category", settings.Category);
|
||||
request.AddFormParameter("category", category);
|
||||
}
|
||||
|
||||
// Note: ForceStart is handled by separate api call
|
||||
@@ -153,15 +153,15 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
}
|
||||
|
||||
public void AddTorrentFromFile(string fileName, byte[] fileContent, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings)
|
||||
public void AddTorrentFromFile(string fileName, byte[] fileContent, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings, string category)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/add")
|
||||
.Post()
|
||||
.AddFormUpload("torrents", fileName, fileContent);
|
||||
|
||||
if (settings.Category.IsNotNullOrWhiteSpace())
|
||||
if (category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.AddFormParameter("category", settings.Category);
|
||||
request.AddFormParameter("category", category);
|
||||
}
|
||||
|
||||
// Note: ForceStart is handled by separate api call
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
public string Category { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing items")]
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
|
||||
protected override string AddFromNzbFile(ReleaseInfo release, string filename, byte[] fileContent)
|
||||
{
|
||||
var category = Settings.Category;
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
var priority = Settings.Priority;
|
||||
|
||||
var response = _proxy.DownloadNzb(fileContent, filename, category, priority, Settings);
|
||||
@@ -48,7 +48,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
|
||||
protected override string AddFromLink(ReleaseInfo release)
|
||||
{
|
||||
var category = Settings.Category;
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
var priority = Settings.Priority;
|
||||
|
||||
var response = _proxy.DownloadNzbByUrl(release.DownloadUrl, category, priority, Settings);
|
||||
@@ -62,6 +62,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
}
|
||||
|
||||
public override string Name => "SABnzbd";
|
||||
public override bool SupportsCategories => true;
|
||||
|
||||
protected IEnumerable<SabnzbdCategory> GetCategories(SabnzbdConfig config)
|
||||
{
|
||||
@@ -260,29 +261,27 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
private ValidationFailure TestCategory()
|
||||
{
|
||||
var config = _proxy.GetConfig(Settings);
|
||||
var category = GetCategories(config).FirstOrDefault((SabnzbdCategory v) => v.Name == Settings.Category);
|
||||
var categories = GetCategories(config);
|
||||
|
||||
if (category != null)
|
||||
foreach (var category in Categories)
|
||||
{
|
||||
if (category.Dir.EndsWith("*"))
|
||||
if (!category.ClientCategory.IsNullOrWhiteSpace() && !categories.Any(v => v.Name == category.ClientCategory))
|
||||
{
|
||||
return new NzbDroneValidationFailure("Category", "Enable Job folders")
|
||||
return new NzbDroneValidationFailure(string.Empty, "Category does not exist")
|
||||
{
|
||||
InfoLink = _proxy.GetBaseUrl(Settings, "config/categories/"),
|
||||
DetailedDescription = "Prowlarr prefers each download to have a separate folder. With * appended to the Folder/Path SABnzbd will not create these job folders. Go to SABnzbd to fix it."
|
||||
InfoLink = _proxy.GetBaseUrl(Settings),
|
||||
DetailedDescription = "A mapped category you entered doesn't exist in Sabnzbd. Go to Sabnzbd to create it."
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if (!Settings.Category.IsNullOrWhiteSpace() && !categories.Any(v => v.Name == Settings.Category))
|
||||
{
|
||||
if (!Settings.Category.IsNullOrWhiteSpace())
|
||||
return new NzbDroneValidationFailure("Category", "Category does not exist")
|
||||
{
|
||||
return new NzbDroneValidationFailure("Category", "Category does not exist")
|
||||
{
|
||||
InfoLink = _proxy.GetBaseUrl(Settings, "config/categories/"),
|
||||
DetailedDescription = "The category you entered doesn't exist in SABnzbd. Go to SABnzbd to create it."
|
||||
};
|
||||
}
|
||||
InfoLink = _proxy.GetBaseUrl(Settings),
|
||||
DetailedDescription = "The category you entered doesn't exist in Sabnzbd. Go to Sabnzbd to create it."
|
||||
};
|
||||
}
|
||||
|
||||
if (config.Misc.enable_tv_sorting && ContainsCategory(config.Misc.tv_categories, Settings.Category))
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
[FieldDefinition(6, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
[FieldDefinition(7, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
public string Category { get; set; }
|
||||
|
||||
[FieldDefinition(8, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing items")]
|
||||
|
||||
@@ -38,5 +38,6 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
}
|
||||
|
||||
public override string Name => "Transmission";
|
||||
public override bool SupportsCategories => false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
|
||||
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
|
||||
public string Category { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Transmission location")]
|
||||
|
||||
@@ -58,5 +58,6 @@ namespace NzbDrone.Core.Download.Clients.Vuze
|
||||
}
|
||||
|
||||
public override string Name => "Vuze";
|
||||
public override bool SupportsCategories => false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
{
|
||||
var priority = (RTorrentPriority)Settings.Priority;
|
||||
|
||||
_proxy.AddTorrentFromUrl(magnetLink, Settings.Category, priority, Settings.Directory, Settings);
|
||||
_proxy.AddTorrentFromUrl(magnetLink, GetCategoryForRelease(release) ?? Settings.Category, priority, Settings.Directory, Settings);
|
||||
|
||||
var tries = 10;
|
||||
var retryDelay = 500;
|
||||
@@ -58,7 +58,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
{
|
||||
var priority = (RTorrentPriority)Settings.Priority;
|
||||
|
||||
_proxy.AddTorrentFromFile(filename, fileContent, Settings.Category, priority, Settings.Directory, Settings);
|
||||
_proxy.AddTorrentFromFile(filename, fileContent, GetCategoryForRelease(release) ?? Settings.Category, priority, Settings.Directory, Settings);
|
||||
|
||||
var tries = 10;
|
||||
var retryDelay = 500;
|
||||
@@ -73,6 +73,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
}
|
||||
|
||||
public override string Name => "rTorrent";
|
||||
public override bool SupportsCategories => true;
|
||||
|
||||
public override ProviderMessage Message => new ProviderMessage("Prowlarr is unable to remove torrents that have finished seeding when using rTorrent", ProviderMessageType.Warning);
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional.")]
|
||||
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional.")]
|
||||
public string Category { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default rTorrent location")]
|
||||
|
||||
@@ -38,9 +38,10 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
_proxy.AddTorrentFromUrl(magnetLink, Settings);
|
||||
|
||||
//_proxy.SetTorrentSeedingConfiguration(hash, release.SeedConfiguration, Settings);
|
||||
if (Settings.Category.IsNotNullOrWhiteSpace())
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
if (GetCategoryForRelease(release).IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_proxy.SetTorrentLabel(hash, Settings.Category, Settings);
|
||||
_proxy.SetTorrentLabel(hash, category, Settings);
|
||||
}
|
||||
|
||||
if (Settings.Priority == (int)UTorrentPriority.First)
|
||||
@@ -58,9 +59,10 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
_proxy.AddTorrentFromFile(filename, fileContent, Settings);
|
||||
|
||||
//_proxy.SetTorrentSeedingConfiguration(hash, release.SeedConfiguration, Settings);
|
||||
if (Settings.Category.IsNotNullOrWhiteSpace())
|
||||
var category = GetCategoryForRelease(release) ?? Settings.Category;
|
||||
if (category.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_proxy.SetTorrentLabel(hash, Settings.Category, Settings);
|
||||
_proxy.SetTorrentLabel(hash, category, Settings);
|
||||
}
|
||||
|
||||
if (Settings.Priority == (int)UTorrentPriority.First)
|
||||
@@ -74,40 +76,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
}
|
||||
|
||||
public override string Name => "uTorrent";
|
||||
|
||||
private List<UTorrentTorrent> GetTorrents()
|
||||
{
|
||||
List<UTorrentTorrent> torrents;
|
||||
|
||||
var cacheKey = string.Format("{0}:{1}:{2}", Settings.Host, Settings.Port, Settings.Category);
|
||||
var cache = _torrentCache.Find(cacheKey);
|
||||
|
||||
var response = _proxy.GetTorrents(cache == null ? null : cache.CacheID, Settings);
|
||||
|
||||
if (cache != null && response.Torrents == null)
|
||||
{
|
||||
var removedAndUpdated = new HashSet<string>(response.TorrentsChanged.Select(v => v.Hash).Concat(response.TorrentsRemoved));
|
||||
|
||||
torrents = cache.Torrents
|
||||
.Where(v => !removedAndUpdated.Contains(v.Hash))
|
||||
.Concat(response.TorrentsChanged)
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
torrents = response.Torrents;
|
||||
}
|
||||
|
||||
cache = new UTorrentTorrentCache
|
||||
{
|
||||
CacheID = response.CacheNumber,
|
||||
Torrents = torrents
|
||||
};
|
||||
|
||||
_torrentCache.Set(cacheKey, cache, TimeSpan.FromMinutes(15));
|
||||
|
||||
return torrents;
|
||||
}
|
||||
public override bool SupportsCategories => true;
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
{
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
public string Category { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing items")]
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
using Org.BouncyCastle.Crypto.Tls;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
@@ -50,6 +53,9 @@ namespace NzbDrone.Core.Download
|
||||
return GetType().Name;
|
||||
}
|
||||
|
||||
protected List<DownloadClientCategory> Categories => ((DownloadClientDefinition)Definition).Categories;
|
||||
public abstract bool SupportsCategories { get; }
|
||||
|
||||
public abstract DownloadProtocol Protocol
|
||||
{
|
||||
get;
|
||||
@@ -57,12 +63,54 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
public abstract Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer);
|
||||
|
||||
protected string GetCategoryForRelease(ReleaseInfo release)
|
||||
{
|
||||
var categories = ((DownloadClientDefinition)Definition).Categories;
|
||||
if (categories.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check for direct mapping
|
||||
var category = categories.FirstOrDefault(x => x.Categories.Intersect(release.Categories.Select(c => c.Id)).Any())?.ClientCategory;
|
||||
|
||||
// Check for parent mapping
|
||||
if (category == null)
|
||||
{
|
||||
foreach (var cat in categories)
|
||||
{
|
||||
var mappedCat = NewznabStandardCategory.AllCats.Where(x => cat.Categories.Contains(x.Id));
|
||||
var subCats = mappedCat.SelectMany(x => x.SubCategories);
|
||||
|
||||
if (subCats.Intersect(release.Categories).Any())
|
||||
{
|
||||
category = cat.ClientCategory;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return category;
|
||||
}
|
||||
|
||||
protected virtual void ValidateCategories(List<ValidationFailure> failures)
|
||||
{
|
||||
foreach (var category in ((DownloadClientDefinition)Definition).Categories)
|
||||
{
|
||||
if (category.ClientCategory.IsNullOrWhiteSpace())
|
||||
{
|
||||
failures.AddIfNotNull(new ValidationFailure(string.Empty, "Category can not be empty"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ValidationResult Test()
|
||||
{
|
||||
var failures = new List<ValidationFailure>();
|
||||
|
||||
try
|
||||
{
|
||||
ValidateCategories(failures);
|
||||
Test(failures);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -97,5 +145,17 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool HasConcreteImplementation(string methodName)
|
||||
{
|
||||
var method = GetType().GetMethod(methodName);
|
||||
|
||||
if (method == null)
|
||||
{
|
||||
throw new MissingMethodException(GetType().Name, Name);
|
||||
}
|
||||
|
||||
return !method.DeclaringType.IsAbstract;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
14
src/NzbDrone.Core/Download/DownloadClientCategory.cs
Normal file
14
src/NzbDrone.Core/Download/DownloadClientCategory.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
public class DownloadClientCategory
|
||||
{
|
||||
public string ClientCategory { get; set; }
|
||||
public List<int> Categories { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,18 @@
|
||||
using NzbDrone.Core.Indexers;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
public class DownloadClientDefinition : ProviderDefinition
|
||||
{
|
||||
public DownloadClientDefinition()
|
||||
{
|
||||
Categories = new List<DownloadClientCategory>();
|
||||
}
|
||||
|
||||
public List<DownloadClientCategory> Categories { get; set; }
|
||||
public bool SupportsCategories { get; set; }
|
||||
public DownloadProtocol Protocol { get; set; }
|
||||
public int Priority { get; set; } = 1;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace NzbDrone.Core.Download
|
||||
base.SetProviderCharacteristics(provider, definition);
|
||||
|
||||
definition.Protocol = provider.Protocol;
|
||||
definition.SupportsCategories = provider.SupportsCategories;
|
||||
}
|
||||
|
||||
public List<IDownloadClient> DownloadHandlingEnabled(bool filterBlockedClients = true)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -7,6 +8,7 @@ namespace NzbDrone.Core.Download
|
||||
{
|
||||
public interface IDownloadClient : IProvider
|
||||
{
|
||||
bool SupportsCategories { get; }
|
||||
DownloadProtocol Protocol { get; }
|
||||
Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Applications;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.ThingiProvider.Events;
|
||||
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
[CheckOn(typeof(ProviderUpdatedEvent<IApplication>))]
|
||||
[CheckOn(typeof(ProviderDeletedEvent<IApplication>))]
|
||||
[CheckOn(typeof(ProviderStatusChangedEvent<IApplication>))]
|
||||
public class ApplicationLongTermStatusCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IApplicationFactory _providerFactory;
|
||||
private readonly IApplicationStatusService _providerStatusService;
|
||||
|
||||
public ApplicationLongTermStatusCheck(IApplicationFactory providerFactory,
|
||||
IApplicationStatusService providerStatusService,
|
||||
ILocalizationService localizationService)
|
||||
: base(localizationService)
|
||||
{
|
||||
_providerFactory = providerFactory;
|
||||
_providerStatusService = providerStatusService;
|
||||
}
|
||||
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
var enabledProviders = _providerFactory.GetAvailableProviders();
|
||||
var backOffProviders = enabledProviders.Join(_providerStatusService.GetBlockedProviders(),
|
||||
i => i.Definition.Id,
|
||||
s => s.ProviderId,
|
||||
(i, s) => new { Provider = i, Status = s })
|
||||
.Where(p => p.Status.InitialFailure.HasValue &&
|
||||
p.Status.InitialFailure.Value.Before(
|
||||
DateTime.UtcNow.AddHours(-6)))
|
||||
.ToList();
|
||||
|
||||
if (backOffProviders.Empty())
|
||||
{
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
|
||||
if (backOffProviders.Count == enabledProviders.Count)
|
||||
{
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Error,
|
||||
_localizationService.GetLocalizedString("ApplicationLongTermStatusCheckAllClientMessage"),
|
||||
"#applications-are-unavailable-due-to-failures");
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Warning,
|
||||
string.Format(_localizationService.GetLocalizedString("ApplicationLongTermStatusCheckSingleClientMessage"),
|
||||
string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))),
|
||||
"#applications-are-unavailable-due-to-failures");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.PassThePopcorn;
|
||||
using NzbDrone.Core.Localization;
|
||||
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
public class PTPOldSettingsCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
|
||||
public PTPOldSettingsCheck(IIndexerFactory indexerFactory, ILocalizationService localizationService)
|
||||
: base(localizationService)
|
||||
{
|
||||
_indexerFactory = indexerFactory;
|
||||
}
|
||||
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
var ptpIndexers = _indexerFactory.All().Where(i => i.Settings.GetType() == typeof(PassThePopcornSettings));
|
||||
|
||||
var ptpIndexerOldSettings = ptpIndexers
|
||||
.Where(i => (i.Settings as PassThePopcornSettings).APIUser.IsNullOrWhiteSpace()).Select(i => i.Name);
|
||||
|
||||
if (ptpIndexerOldSettings.Any())
|
||||
{
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("PtpOldSettingsCheckMessage"), string.Join(", ", ptpIndexerOldSettings)));
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Http.CloudFlare;
|
||||
using NzbDrone.Core.Localization;
|
||||
@@ -20,10 +21,12 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
public class FlareSolverr : HttpIndexerProxyBase<FlareSolverrSettings>
|
||||
{
|
||||
private readonly ICached<string> _cache;
|
||||
private readonly IHttpProxySettingsProvider _proxySettingsProvider;
|
||||
|
||||
public FlareSolverr(IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger, ILocalizationService localizationService, ICacheManager cacheManager)
|
||||
public FlareSolverr(IHttpProxySettingsProvider proxySettingsProvider, IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger, ILocalizationService localizationService, ICacheManager cacheManager)
|
||||
: base(cloudRequestBuilder, httpClient, logger, localizationService)
|
||||
{
|
||||
_proxySettingsProvider = proxySettingsProvider;
|
||||
_cache = cacheManager.GetCache<string>(typeof(string), "UserAgent");
|
||||
}
|
||||
|
||||
@@ -100,6 +103,10 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
var userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36";
|
||||
var maxTimeout = Settings.RequestTimeout * 1000;
|
||||
|
||||
// Use Proxy if no credentials are set (creds not supported as of FS 2.2.9)
|
||||
var proxySettings = _proxySettingsProvider.GetProxySettings();
|
||||
var proxyUrl = proxySettings != null && proxySettings.Username.IsNullOrWhiteSpace() && proxySettings.Password.IsNullOrWhiteSpace() ? GetProxyUri(proxySettings) : null;
|
||||
|
||||
if (request.Method == HttpMethod.Get)
|
||||
{
|
||||
req = new FlareSolverrRequestGet
|
||||
@@ -107,7 +114,11 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
Cmd = "request.get",
|
||||
Url = url,
|
||||
MaxTimeout = maxTimeout,
|
||||
UserAgent = userAgent
|
||||
UserAgent = userAgent,
|
||||
Proxy = new FlareSolverrProxy
|
||||
{
|
||||
Url = proxyUrl?.AbsoluteUri
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (request.Method == HttpMethod.Post)
|
||||
@@ -130,7 +141,11 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
ContentLength = null
|
||||
},
|
||||
MaxTimeout = maxTimeout,
|
||||
UserAgent = userAgent
|
||||
UserAgent = userAgent,
|
||||
Proxy = new FlareSolverrProxy
|
||||
{
|
||||
Url = proxyUrl?.AbsoluteUri
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (contentTypeType.Contains("multipart/form-data")
|
||||
@@ -191,38 +206,59 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
return new ValidationResult(failures);
|
||||
}
|
||||
|
||||
public class FlareSolverrRequest
|
||||
private Uri GetProxyUri(HttpProxySettings proxySettings)
|
||||
{
|
||||
switch (proxySettings.Type)
|
||||
{
|
||||
case ProxyType.Http:
|
||||
return new Uri("http://" + proxySettings.Host + ":" + proxySettings.Port);
|
||||
case ProxyType.Socks4:
|
||||
return new Uri("socks4://" + proxySettings.Host + ":" + proxySettings.Port);
|
||||
case ProxyType.Socks5:
|
||||
return new Uri("socks5://" + proxySettings.Host + ":" + proxySettings.Port);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class FlareSolverrRequest
|
||||
{
|
||||
public string Cmd { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string UserAgent { get; set; }
|
||||
public Cookie[] Cookies { get; set; }
|
||||
public FlareSolverrProxy Proxy { get; set; }
|
||||
}
|
||||
|
||||
public class FlareSolverrRequestGet : FlareSolverrRequest
|
||||
private class FlareSolverrRequestGet : FlareSolverrRequest
|
||||
{
|
||||
public string Headers { get; set; }
|
||||
public int MaxTimeout { get; set; }
|
||||
}
|
||||
|
||||
public class FlareSolverrRequestPost : FlareSolverrRequest
|
||||
private class FlareSolverrRequestPost : FlareSolverrRequest
|
||||
{
|
||||
public string PostData { get; set; }
|
||||
public int MaxTimeout { get; set; }
|
||||
}
|
||||
|
||||
public class FlareSolverrRequestPostUrlEncoded : FlareSolverrRequestPost
|
||||
private class FlareSolverrRequestPostUrlEncoded : FlareSolverrRequestPost
|
||||
{
|
||||
public HeadersPost Headers { get; set; }
|
||||
}
|
||||
|
||||
public class HeadersPost
|
||||
private class FlareSolverrProxy
|
||||
{
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
private class HeadersPost
|
||||
{
|
||||
public string ContentType { get; set; }
|
||||
public string ContentLength { get; set; }
|
||||
}
|
||||
|
||||
public class FlareSolverrResponse
|
||||
private class FlareSolverrResponse
|
||||
{
|
||||
public string Status { get; set; }
|
||||
public string Message { get; set; }
|
||||
@@ -232,7 +268,7 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
public Solution Solution { get; set; }
|
||||
}
|
||||
|
||||
public class Solution
|
||||
private class Solution
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public string Status { get; set; }
|
||||
@@ -242,7 +278,7 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
public string UserAgent { get; set; }
|
||||
}
|
||||
|
||||
public class Cookie
|
||||
private class Cookie
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
@@ -259,7 +295,7 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
public System.Net.Cookie ToCookieObj() => new System.Net.Cookie(Name, Value);
|
||||
}
|
||||
|
||||
public class Headers
|
||||
private class Headers
|
||||
{
|
||||
public string Status { get; set; }
|
||||
public string Date { get; set; }
|
||||
|
||||
@@ -3,7 +3,9 @@ using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.Notifications.Prowl;
|
||||
|
||||
namespace NzbDrone.Core.IndexerProxies.Http
|
||||
{
|
||||
@@ -18,14 +20,13 @@ namespace NzbDrone.Core.IndexerProxies.Http
|
||||
|
||||
public override HttpRequest PreRequest(HttpRequest request)
|
||||
{
|
||||
if (Settings.Username.IsNotNullOrWhiteSpace() && Settings.Password.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.Proxy = new WebProxy(Settings.Host + ":" + Settings.Port, false, null, new NetworkCredential(Settings.Username, Settings.Password));
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Proxy = new WebProxy(Settings.Host + ":" + Settings.Port, false, null);
|
||||
}
|
||||
request.ProxySettings = new HttpProxySettings(ProxyType.Http,
|
||||
Settings.Host,
|
||||
Settings.Port,
|
||||
null,
|
||||
false,
|
||||
Settings.Username,
|
||||
Settings.Password);
|
||||
|
||||
_logger.Debug("Applying HTTP(S) Proxy {0} to request {1}", Name, request.Url);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Localization;
|
||||
|
||||
namespace NzbDrone.Core.IndexerProxies.Socks4
|
||||
@@ -25,14 +26,13 @@ namespace NzbDrone.Core.IndexerProxies.Socks4
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Settings.Username.IsNotNullOrWhiteSpace() && Settings.Password.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.Proxy = new WebProxy(uri, false, null, new NetworkCredential(Settings.Username, Settings.Password));
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Proxy = new WebProxy(uri);
|
||||
}
|
||||
request.ProxySettings = new HttpProxySettings(ProxyType.Socks4,
|
||||
Settings.Host,
|
||||
Settings.Port,
|
||||
null,
|
||||
false,
|
||||
Settings.Username,
|
||||
Settings.Password);
|
||||
|
||||
_logger.Debug("Applying Socks4 Proxy {0} to request {1}", Name, request.Url);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Localization;
|
||||
|
||||
namespace NzbDrone.Core.IndexerProxies.Socks5
|
||||
@@ -26,14 +27,13 @@ namespace NzbDrone.Core.IndexerProxies.Socks5
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Settings.Username.IsNotNullOrWhiteSpace() && Settings.Password.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.Proxy = new WebProxy(uri, false, null, new NetworkCredential(Settings.Username, Settings.Password));
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Proxy = new WebProxy(uri);
|
||||
}
|
||||
request.ProxySettings = new HttpProxySettings(ProxyType.Socks5,
|
||||
Settings.Host,
|
||||
Settings.Port,
|
||||
null,
|
||||
false,
|
||||
Settings.Username,
|
||||
Settings.Password);
|
||||
|
||||
_logger.Debug("Applying Socks5 Proxy {0} to request {1}", Name, request.Url);
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
|
||||
public void HandleAsync(IndexerQueryEvent message)
|
||||
{
|
||||
if (message.QueryResult?.Releases != null)
|
||||
if (_analyticsService.IsEnabled && message.QueryResult?.Releases != null)
|
||||
{
|
||||
lock (_pendingUpdates)
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user