mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-03-13 15:54:10 -04:00
Compare commits
61 Commits
jackettmig
...
v0.1.0.447
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8fdd46cd3 | ||
|
|
b6a08bdd9e | ||
|
|
59df0351ac | ||
|
|
0fd242cd62 | ||
|
|
ed7c5a937f | ||
|
|
f6906d0f18 | ||
|
|
561563b48c | ||
|
|
e383036c84 | ||
|
|
202f439a60 | ||
|
|
449e60afc0 | ||
|
|
9675171aff | ||
|
|
bcee5f1754 | ||
|
|
d1a3e61979 | ||
|
|
b4a0c272c9 | ||
|
|
75cbabf716 | ||
|
|
6612202384 | ||
|
|
6b52dd6e7a | ||
|
|
974ab6387f | ||
|
|
9ac435bc41 | ||
|
|
b0f04bb9d7 | ||
|
|
52bff3d7bd | ||
|
|
d965cb3c98 | ||
|
|
b592a137cf | ||
|
|
b0819c97ed | ||
|
|
5802d20b93 | ||
|
|
58e30cc9a6 | ||
|
|
513b9d2324 | ||
|
|
3ebdc1c1ac | ||
|
|
e9a5c5f7d2 | ||
|
|
23c01f7dbe | ||
|
|
c35864cc7f | ||
|
|
e545e0a129 | ||
|
|
1e72944998 | ||
|
|
d234cbda8a | ||
|
|
09a9731dae | ||
|
|
3b4df1706e | ||
|
|
20038e4757 | ||
|
|
c57c77d8e7 | ||
|
|
c61c3a9c45 | ||
|
|
d6d418f7b3 | ||
|
|
ab5cf45d88 | ||
|
|
40d55b915e | ||
|
|
4a851c37d5 | ||
|
|
63b6adf0e1 | ||
|
|
d9e211472b | ||
|
|
fa05dbc642 | ||
|
|
709dc0ee5f | ||
|
|
67f26fe185 | ||
|
|
5f4218ae91 | ||
|
|
30b54d8340 | ||
|
|
ee6ae386ca | ||
|
|
b3fb640969 | ||
|
|
8b0a8e82b5 | ||
|
|
f25998959e | ||
|
|
f56ce129e6 | ||
|
|
ae9930a03f | ||
|
|
85be0be455 | ||
|
|
cf1c44ed75 | ||
|
|
f062fafe82 | ||
|
|
1032d8b3ab | ||
|
|
99c4ed7dbc |
22
.github/ISSUE_TEMPLATE/indexer_request.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/indexer_request.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: Indexer Request
|
||||
about: Request an indexer for Prowlarr. Check the pinned Jackett parity issue prior to submitting a request. Duplicated requests will be closed without warning. Please search GitHub prior to requesting.
|
||||
title: '(Indexer) '
|
||||
labels: 'Type: Indexer Request'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Check the pinned Jackett parity issue prior to submitting a request. Duplicated requests or requests covered in existing GitHub Issues will be closed without warning. Please search GitHub prior to requesting.-->
|
||||
|
||||
**Type:** <Usenet|Torrents>
|
||||
|
||||
**Tracker:** <Indexer/Tracker Name>
|
||||
|
||||
**URL:** <Indexer/Tracker URL>
|
||||
|
||||
**In Jackett?:** <Yes|No>
|
||||
<!-- Check the pinned Jackett parity issue prior to submitting a request. Duplicated requests or requests covered in existing GitHub Issues will be closed without warning. Please search GitHub prior to requesting.-->
|
||||
|
||||
**Additional Context:**
|
||||
<!-- Add any other context or screenshots about the request here. -->
|
||||
@@ -3,15 +3,31 @@
|
||||
We're always looking for people to help make Prowlarr even better, there are a number of ways to contribute.
|
||||
|
||||
## Documentation ##
|
||||
Setup guides, FAQ, the more information we have on the wiki the better.
|
||||
Setup guides, FAQ, the more information we have on the [wiki](https://wikijs.servarr.com/prowlarr) the better.
|
||||
|
||||
## Development ##
|
||||
|
||||
See the readme for information on setting up your development environment.
|
||||
### Tools required ###
|
||||
- Visual Studio 2019 or higher (https://www.visualstudio.com/vs/). The community version is free and works (https://www.visualstudio.com/downloads/).
|
||||
- HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [NodeJS](https://nodejs.org/en/download/) (Node 12.X.X or higher)
|
||||
- [Yarn](https://yarnpkg.com/)
|
||||
- .NET Core 5.0.
|
||||
|
||||
### Getting started ###
|
||||
|
||||
1. Fork Prowlarr
|
||||
2. Clone the repository into your development machine. [*info*](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository-from-github)
|
||||
3. Install the required Node Packages `yarn install`
|
||||
4. Start gulp to monitor your dev environment for any changes that need post processing using `yarn start` command.
|
||||
5. Build the project in Visual Studio, Setting startup project to `Prowlarr.Console` and framework to `net5.0`
|
||||
6. Debug the project in Visual Studio
|
||||
7. Open http://localhost:7878
|
||||
|
||||
### Contributing Code ###
|
||||
- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Prowlarr/Prowlarr/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first)
|
||||
- Rebase from Prowlarr's develop branch, don't merge
|
||||
- Rebase from Radarr's develop branch, don't merge
|
||||
- Make meaningful commits, or squash them
|
||||
- Feel free to make a pull request before work is complete, this will let us see where its at and make comments/suggest improvements
|
||||
- Reach out to us on the discord if you have any questions
|
||||
@@ -20,6 +36,10 @@ See the readme for information on setting up your development environment.
|
||||
- One feature/bug fix per pull request to keep things clean and easy to understand
|
||||
- Use 4 spaces instead of tabs, this is the default for VS 2019 and WebStorm (to my knowledge)
|
||||
|
||||
### Contributing Indexers ###
|
||||
- If you're contributing an indexer please phrase your commit as something like: `New: (Indexer) {Indexer Name}`, `New: (Indexer) {Usenet|Torrent} {Indexer Name}`, `New: (Indexer) {Torznab|Newznab} {Indexer Name}`
|
||||
- If you're updating an indexer please phrase your commit as something like: `Fixed: (Indexer) {Indexer Name} {changes}` e.g. `Fixed: (Indexer) Changed BHD to use API`
|
||||
|
||||
### Pull Requesting ###
|
||||
- Only make pull requests to develop, never master, if you make a PR to master we'll comment on it and close it
|
||||
- You're probably going to get some comments or questions from us, they will be to ensure consistency and maintainability
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://dev.azure.com/Prowlarr/Prowlarr/_build/latest?definitionId=1&branchName=develop)
|
||||
[](https://translate.servarr.com/engage/prowlarr/?utm_source=widget)
|
||||
[](https://wiki.servarr.com/Prowlarr_Installation#Docker)
|
||||
[](https://wikijs.servarr.com/prowlarr/installation#docker)
|
||||

|
||||
[](#backers)
|
||||
[](#sponsors)
|
||||
@@ -23,7 +23,7 @@ Note: Prowlarr is currently early in life, thus bugs should be expected
|
||||
[](https://prowlarr.com/discord)
|
||||
[](https://www.reddit.com/r/Prowlarr)
|
||||
[](https://github.com/Prowlarr/Prowlarr/issues)
|
||||
[](https://wiki.servarr.com/Prowlarr)
|
||||
[](https://wikijs.servarr.com/prowlarr)
|
||||
|
||||
## Feature Requests
|
||||
|
||||
|
||||
@@ -23,6 +23,16 @@ class BarChart extends Component {
|
||||
this.myChart = new Chart(this.canvasRef.current, {
|
||||
type: 'bar',
|
||||
options: {
|
||||
x: {
|
||||
ticks: {
|
||||
stepSize: this.props.stepSize
|
||||
}
|
||||
},
|
||||
y: {
|
||||
ticks: {
|
||||
stepSize: this.props.stepSize
|
||||
}
|
||||
},
|
||||
indexAxis: this.props.horizontal ? 'y' : 'x',
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
@@ -64,7 +74,8 @@ BarChart.propTypes = {
|
||||
horizontal: PropTypes.bool,
|
||||
legend: PropTypes.bool,
|
||||
title: PropTypes.string.isRequired,
|
||||
kind: PropTypes.oneOf(kinds.all).isRequired
|
||||
kind: PropTypes.oneOf(kinds.all).isRequired,
|
||||
stepSize: PropTypes.number
|
||||
};
|
||||
|
||||
BarChart.defaultProps = {
|
||||
@@ -72,7 +83,8 @@ BarChart.defaultProps = {
|
||||
horizontal: false,
|
||||
legend: false,
|
||||
title: '',
|
||||
kind: kinds.INFO
|
||||
kind: kinds.INFO,
|
||||
stepSize: 1
|
||||
};
|
||||
|
||||
export default BarChart;
|
||||
|
||||
@@ -16,10 +16,16 @@ class StackedBarChart extends Component {
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true
|
||||
stacked: true,
|
||||
ticks: {
|
||||
stepSize: this.props.stepSize
|
||||
}
|
||||
},
|
||||
y: {
|
||||
stacked: true
|
||||
stacked: true,
|
||||
ticks: {
|
||||
stepSize: this.props.stepSize
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
@@ -63,11 +69,13 @@ class StackedBarChart extends Component {
|
||||
|
||||
StackedBarChart.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
title: PropTypes.string.isRequired
|
||||
title: PropTypes.string.isRequired,
|
||||
stepSize: PropTypes.number
|
||||
};
|
||||
|
||||
StackedBarChart.defaultProps = {
|
||||
title: ''
|
||||
title: '',
|
||||
stepSize: 1
|
||||
};
|
||||
|
||||
export default StackedBarChart;
|
||||
|
||||
@@ -129,7 +129,7 @@ class FileBrowserModalContent extends Component {
|
||||
className={styles.mappedDrivesWarning}
|
||||
kind={kinds.WARNING}
|
||||
>
|
||||
<Link to="https://wiki.servarr.com/Prowlarr_FAQ#Why_cant_Prowlarr_see_my_files_on_a_remote_server">
|
||||
<Link to="https://wikijs.servarr.com/prowlarr/faq#why-cant-prowlarr-see-my-files-on-a-remote-server">
|
||||
{translate('MappedDrivesRunningAsService')}
|
||||
</Link>
|
||||
</Alert>
|
||||
|
||||
@@ -25,7 +25,7 @@ function FormInputHelpText(props) {
|
||||
isCheckInput && styles.isCheckInput
|
||||
)}
|
||||
>
|
||||
{text}
|
||||
<div dangerouslySetInnerHTML={{ __html: text }} />
|
||||
|
||||
{
|
||||
link ?
|
||||
|
||||
BIN
frontend/src/Content/Images/Icons/logo-lidarr.png
Normal file
BIN
frontend/src/Content/Images/Icons/logo-lidarr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
frontend/src/Content/Images/Icons/logo-prowlarr.png
Normal file
BIN
frontend/src/Content/Images/Icons/logo-prowlarr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
BIN
frontend/src/Content/Images/Icons/logo-radarr.png
Normal file
BIN
frontend/src/Content/Images/Icons/logo-radarr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
BIN
frontend/src/Content/Images/Icons/logo-readarr.png
Normal file
BIN
frontend/src/Content/Images/Icons/logo-readarr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
frontend/src/Content/Images/Icons/logo-sonarr.png
Normal file
BIN
frontend/src/Content/Images/Icons/logo-sonarr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
@@ -73,7 +73,7 @@ class HistoryOptions extends Component {
|
||||
}
|
||||
|
||||
HistoryOptions.propTypes = {
|
||||
historyCleanupDays: PropTypes.bool.isRequired,
|
||||
historyCleanupDays: PropTypes.number.isRequired,
|
||||
dispatchSaveGeneralSettings: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -20,19 +20,25 @@ import styles from './AddIndexerModalContent.css';
|
||||
const columns = [
|
||||
{
|
||||
name: 'protocol',
|
||||
label: 'Protocol',
|
||||
label: translate('Protocol'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
label: translate('Name'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'language',
|
||||
label: translate('Language'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'privacy',
|
||||
label: 'Privacy',
|
||||
label: translate('Privacy'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ class SelectIndexerRow extends Component {
|
||||
const {
|
||||
protocol,
|
||||
privacy,
|
||||
name
|
||||
name,
|
||||
language
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@@ -41,6 +42,10 @@ class SelectIndexerRow extends Component {
|
||||
{name}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell>
|
||||
{language}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell>
|
||||
{privacy}
|
||||
</TableRowCell>
|
||||
@@ -53,6 +58,7 @@ SelectIndexerRow.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
protocol: PropTypes.string.isRequired,
|
||||
privacy: PropTypes.string.isRequired,
|
||||
language: PropTypes.string.isRequired,
|
||||
implementation: PropTypes.string.isRequired,
|
||||
onIndexerSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -99,7 +99,7 @@ function EditIndexerModalContent(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="redirect"
|
||||
helpText={'Redirect incoming download requests for indexer instead of Proxying using Prowlarr'}
|
||||
helpText={translate('RedirectHelpText')}
|
||||
isDisabled={!supportsRedirect.value}
|
||||
{...redirect}
|
||||
onChange={onInputChange}
|
||||
@@ -113,6 +113,7 @@ function EditIndexerModalContent(props) {
|
||||
type={inputTypes.APP_PROFILE_SELECT}
|
||||
name="appProfileId"
|
||||
{...appProfileId}
|
||||
helpText={translate('AppProfileSelectHelpText')}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -50,7 +50,7 @@ class TagsModalContent extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
movieTags,
|
||||
indexerTags,
|
||||
tagList,
|
||||
onModalClose
|
||||
} = this.props;
|
||||
@@ -108,7 +108,7 @@ class TagsModalContent extends Component {
|
||||
|
||||
<div className={styles.result}>
|
||||
{
|
||||
movieTags.map((t) => {
|
||||
indexerTags.map((t) => {
|
||||
const tag = _.find(tagList, { id: t });
|
||||
|
||||
if (!tag) {
|
||||
@@ -140,7 +140,7 @@ class TagsModalContent extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (movieTags.indexOf(t) > -1) {
|
||||
if (indexerTags.indexOf(t) > -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ class TagsModalContent extends Component {
|
||||
}
|
||||
|
||||
TagsModalContent.propTypes = {
|
||||
movieTags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
indexerTags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
onApplyTagsPress: PropTypes.func.isRequired
|
||||
|
||||
@@ -10,15 +10,15 @@ function createMapStateToProps() {
|
||||
(state, { indexerIds }) => indexerIds,
|
||||
createAllIndexersSelector(),
|
||||
createTagsSelector(),
|
||||
(indexerIds, allMovies, tagList) => {
|
||||
const movies = _.intersectionWith(allMovies, indexerIds, (s, id) => {
|
||||
(indexerIds, allIndexers, tagList) => {
|
||||
const indexers = _.intersectionWith(allIndexers, indexerIds, (s, id) => {
|
||||
return s.id === id;
|
||||
});
|
||||
|
||||
const movieTags = _.uniq(_.concat(..._.map(movies, 'tags')));
|
||||
const indexerTags = _.uniq(_.concat(..._.map(indexers, 'tags')));
|
||||
|
||||
return {
|
||||
movieTags,
|
||||
indexerTags,
|
||||
tagList
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import withScrollPosition from 'Components/withScrollPosition';
|
||||
import { testAllIndexers } from 'Store/Actions/indexerActions';
|
||||
import { saveMovieEditor, setMovieFilter, setMovieSort, setMovieTableOption } from 'Store/Actions/indexerIndexActions';
|
||||
import { saveIndexerEditor, setMovieFilter, setMovieSort, setMovieTableOption } from 'Store/Actions/indexerIndexActions';
|
||||
import scrollPositions from 'Store/scrollPositions';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createIndexerClientSideCollectionItemsSelector from 'Store/Selectors/createIndexerClientSideCollectionItemsSelector';
|
||||
@@ -40,8 +40,8 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
dispatch(setMovieFilter({ selectedFilterKey }));
|
||||
},
|
||||
|
||||
dispatchSaveMovieEditor(payload) {
|
||||
dispatch(saveMovieEditor(payload));
|
||||
dispatchSaveIndexerEditor(payload) {
|
||||
dispatch(saveIndexerEditor(payload));
|
||||
},
|
||||
|
||||
onTestAllPress() {
|
||||
@@ -56,7 +56,7 @@ class IndexerIndexConnector extends Component {
|
||||
// Listeners
|
||||
|
||||
onSaveSelected = (payload) => {
|
||||
this.props.dispatchSaveMovieEditor(payload);
|
||||
this.props.dispatchSaveIndexerEditor(payload);
|
||||
}
|
||||
|
||||
onScroll = ({ scrollTop }) => {
|
||||
@@ -79,7 +79,7 @@ class IndexerIndexConnector extends Component {
|
||||
|
||||
IndexerIndexConnector.propTypes = {
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
dispatchSaveMovieEditor: PropTypes.func.isRequired,
|
||||
dispatchSaveIndexerEditor: PropTypes.func.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object)
|
||||
};
|
||||
|
||||
|
||||
@@ -248,7 +248,7 @@ class IndexerIndexRow extends Component {
|
||||
className={styles.externalLink}
|
||||
name={icons.EXTERNAL_LINK}
|
||||
title={'Website'}
|
||||
to={baseUrl}
|
||||
to={baseUrl.replace('api.', '')}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
|
||||
@@ -4,6 +4,7 @@ import React, { Component } from 'react';
|
||||
import IndexersSelectInputConnector from 'Components/Form/IndexersSelectInputConnector';
|
||||
import NewznabCategorySelectInputConnector from 'Components/Form/NewznabCategorySelectInputConnector';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import keyboardShortcuts from 'Components/keyboardShortcuts';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
import PageContentFooter from 'Components/Page/PageContentFooter';
|
||||
import SearchFooterLabel from './SearchFooterLabel';
|
||||
@@ -38,9 +39,11 @@ class SearchFooter extends Component {
|
||||
searchQuery
|
||||
} = this.state;
|
||||
|
||||
if (searchQuery !== '' || searchCategories !== [] || searchIndexerIds !== []) {
|
||||
if (searchQuery !== '' || searchCategories.length > 0 || searchIndexerIds.length > 0) {
|
||||
this.onSearchPress();
|
||||
}
|
||||
|
||||
this.props.bindShortcut('enter', this.onSearchPress, { isGlobal: true });
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -114,6 +117,7 @@ class SearchFooter extends Component {
|
||||
|
||||
<TextInput
|
||||
name='searchQuery'
|
||||
autoFocus={true}
|
||||
value={searchQuery}
|
||||
isDisabled={isFetching}
|
||||
onChange={onInputChange}
|
||||
@@ -181,7 +185,8 @@ SearchFooter.propTypes = {
|
||||
onSearchPress: PropTypes.func.isRequired,
|
||||
hasIndexers: PropTypes.bool.isRequired,
|
||||
onInputChange: PropTypes.func.isRequired,
|
||||
searchError: PropTypes.object
|
||||
searchError: PropTypes.object,
|
||||
bindShortcut: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SearchFooter;
|
||||
export default keyboardShortcuts(SearchFooter);
|
||||
|
||||
@@ -18,9 +18,9 @@ import translate from 'Utilities/String/translate';
|
||||
import styles from './EditApplicationModalContent.css';
|
||||
|
||||
const syncLevelOptions = [
|
||||
{ key: 'disabled', value: 'Disabled' },
|
||||
{ key: 'addOnly', value: 'Add and Remove Only' },
|
||||
{ key: 'fullSync', value: 'Full Sync' }
|
||||
{ key: 'disabled', value: translate('Disabled') },
|
||||
{ key: 'addOnly', value: translate('AddRemoveOnly') },
|
||||
{ key: 'fullSync', value: translate('FullSync') }
|
||||
];
|
||||
|
||||
function EditApplicationModalContent(props) {
|
||||
@@ -53,7 +53,7 @@ function EditApplicationModalContent(props) {
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{`${id ? 'Edit' : 'Add'} Application`}
|
||||
{`${id ? translate('Edit') : translate('Add')} ${translate('Application')}`}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
@@ -94,13 +94,13 @@ function EditApplicationModalContent(props) {
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{'Sync Level'}</FormLabel>
|
||||
<FormLabel>{translate('SyncLevel')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
values={syncLevelOptions}
|
||||
name="syncLevel"
|
||||
helpText={'Sync Level'}
|
||||
helpText={`${translate('SyncLevelAddRemove')}<br>${translate('SyncLevelFull')}`}
|
||||
{...syncLevel}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
|
||||
@@ -53,6 +53,9 @@ class AddDownloadClientModalContent extends Component {
|
||||
<div>
|
||||
|
||||
<Alert kind={kinds.INFO}>
|
||||
<div>
|
||||
{translate('AddDownloadClientToProwlarr')}
|
||||
</div>
|
||||
<div>
|
||||
{translate('ProwlarrSupportsAnyDownloadClient')}
|
||||
</div>
|
||||
|
||||
@@ -55,7 +55,7 @@ function UpdateSettings(props) {
|
||||
type={inputTypes.TEXT}
|
||||
name="branch"
|
||||
helpText={usingExternalUpdateMechanism ? translate('BranchUpdateMechanism') : translate('BranchUpdate')}
|
||||
helpLink="https://wiki.servarr.com/Prowlarr_Settings#Updates"
|
||||
helpLink="https://wikijs.servarr.com/prowlarr/settings#updates"
|
||||
{...branch}
|
||||
onChange={onInputChange}
|
||||
readOnly={usingExternalUpdateMechanism}
|
||||
@@ -92,7 +92,7 @@ function UpdateSettings(props) {
|
||||
name="updateMechanism"
|
||||
values={updateOptions}
|
||||
helpText={translate('UpdateMechanismHelpText')}
|
||||
helpLink="https://wiki.servarr.com/Prowlarr_Settings#Updates"
|
||||
helpLink="https://wikijs.servarr.com/prowlarr/settings#updates"
|
||||
onChange={onInputChange}
|
||||
{...updateMechanism}
|
||||
/>
|
||||
|
||||
@@ -25,8 +25,8 @@ function NotificationEventItems(props) {
|
||||
<FormLabel>{translate('NotificationTriggers')}</FormLabel>
|
||||
<div>
|
||||
<FormInputHelpText
|
||||
text={translate('NotifcationTriggersHelpText')}
|
||||
link="https://wiki.servarr.com/Prowlarr_Settings#Connections"
|
||||
text={translate('NotificationTriggersHelpText')}
|
||||
link="https://wikijs.servarr.com/prowlarr/settings#connections"
|
||||
/>
|
||||
<div className={styles.events}>
|
||||
<div>
|
||||
|
||||
@@ -176,7 +176,7 @@ export const SET_MOVIE_SORT = 'indexerIndex/setMovieSort';
|
||||
export const SET_MOVIE_FILTER = 'indexerIndex/setMovieFilter';
|
||||
export const SET_MOVIE_VIEW = 'indexerIndex/setMovieView';
|
||||
export const SET_MOVIE_TABLE_OPTION = 'indexerIndex/setMovieTableOption';
|
||||
export const SAVE_MOVIE_EDITOR = 'indexerIndex/saveMovieEditor';
|
||||
export const SAVE_INDEXER_EDITOR = 'indexerIndex/saveIndexerEditor';
|
||||
export const BULK_DELETE_INDEXERS = 'indexerIndex/bulkDeleteIndexers';
|
||||
|
||||
//
|
||||
@@ -186,14 +186,14 @@ export const setMovieSort = createAction(SET_MOVIE_SORT);
|
||||
export const setMovieFilter = createAction(SET_MOVIE_FILTER);
|
||||
export const setMovieView = createAction(SET_MOVIE_VIEW);
|
||||
export const setMovieTableOption = createAction(SET_MOVIE_TABLE_OPTION);
|
||||
export const saveMovieEditor = createThunk(SAVE_MOVIE_EDITOR);
|
||||
export const saveIndexerEditor = createThunk(SAVE_INDEXER_EDITOR);
|
||||
export const bulkDeleteIndexers = createThunk(BULK_DELETE_INDEXERS);
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
export const actionHandlers = handleThunks({
|
||||
[SAVE_MOVIE_EDITOR]: function(getState, payload, dispatch) {
|
||||
[SAVE_INDEXER_EDITOR]: function(getState, payload, dispatch) {
|
||||
dispatch(set({
|
||||
section,
|
||||
isSaving: true
|
||||
|
||||
@@ -13,7 +13,7 @@ function createHealthCheckSelector() {
|
||||
source: 'UI',
|
||||
type: 'warning',
|
||||
message: translate('CouldNotConnectSignalR'),
|
||||
wikiUrl: 'https://wiki.servarr.com/Prowlarr_System#Could_not_connect_to_signalR'
|
||||
wikiUrl: 'https://wikijs.servarr.com/prowlarr/system#could-not-connect-to-signalr'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
64
frontend/src/System/Status/Donations/Donations.js
Normal file
64
frontend/src/System/Status/Donations/Donations.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import React, { Component } from 'react';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Link from 'Components/Link/Link';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from '../styles.css';
|
||||
|
||||
class Donations extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FieldSet legend={translate('Donations')}>
|
||||
<div className={styles.logoContainer} title="Radarr">
|
||||
<Link to="https://opencollective.com/radarr">
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-radarr.png`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.logoContainer} title="Lidarr">
|
||||
<Link to="https://opencollective.com/lidarr">
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-lidarr.png`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.logoContainer} title="Readarr">
|
||||
<Link to="https://opencollective.com/readarr">
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-readarr.png`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.logoContainer} title="Prowlarr">
|
||||
<Link to="https://opencollective.com/prowlarr">
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-prowlarr.png`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.logoContainer} title="Sonarr">
|
||||
<Link to="https://opencollective.com/sonarr">
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-sonarr.png`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</FieldSet>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Donations.propTypes = {
|
||||
|
||||
};
|
||||
|
||||
export default Donations;
|
||||
@@ -15,32 +15,32 @@ class MoreInfo extends Component {
|
||||
return (
|
||||
<FieldSet legend={translate('MoreInfo')}>
|
||||
<DescriptionList>
|
||||
<DescriptionListItemTitle>Home page</DescriptionListItemTitle>
|
||||
<DescriptionListItemTitle>{translate('HomePage')}</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://prowlarr.com/">prowlarr.com</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>Discord</DescriptionListItemTitle>
|
||||
<DescriptionListItemTitle>{translate('Wiki')}</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://wikijs.servarr.com/prowlarr">wikijs.servarr.com/prowlarr</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>{translate('Reddit')}</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://reddit.com/r/prowlarr">r/prowlarr</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>{translate('Discord')}</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://prowlarr.com/discord">prowlarr.com/discord</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>Wiki</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://wiki.servarr.com/Prowlarr">wiki.servarr.com/Prowlarr</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>Donations</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://opencollective.com/prowlarr">opencollective.com/prowlarr</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>Source</DescriptionListItemTitle>
|
||||
<DescriptionListItemTitle>{translate('Source')}</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://github.com/Prowlarr/Prowlarr/">github.com/Prowlarr/Prowlarr</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>Feature Requests</DescriptionListItemTitle>
|
||||
<DescriptionListItemTitle>{translate('FeatureRequests')}</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://github.com/Prowlarr/Prowlarr/issues">github.com/Prowlarr/Prowlarr/issues</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
@@ -3,6 +3,7 @@ import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AboutConnector from './About/AboutConnector';
|
||||
import Donations from './Donations/Donations';
|
||||
import HealthConnector from './Health/HealthConnector';
|
||||
import MoreInfo from './MoreInfo/MoreInfo';
|
||||
|
||||
@@ -18,6 +19,7 @@ class Status extends Component {
|
||||
<HealthConnector />
|
||||
<AboutConnector />
|
||||
<MoreInfo />
|
||||
<Donations />
|
||||
</PageContentBody>
|
||||
</PageContent>
|
||||
);
|
||||
|
||||
17
frontend/src/System/Status/styles.css
Normal file
17
frontend/src/System/Status/styles.css
Normal file
@@ -0,0 +1,17 @@
|
||||
.logo {
|
||||
margin: auto;
|
||||
padding: 9px;
|
||||
}
|
||||
|
||||
.logoContainer {
|
||||
display: inline-block;
|
||||
margin: 0.5em;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
outline: none;
|
||||
border: solid 1px #e6e6e6;
|
||||
border-radius: 0.5em;
|
||||
background: #f8f8ff;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ class UpdateChanges extends Component {
|
||||
<ul>
|
||||
{
|
||||
changes.map((change, index) => {
|
||||
const checkChange = change.replace(/#\d{4,5}\b/g, (match, contents) => {
|
||||
const checkChange = change.replace(/#\d{3,5}\b/g, (match, contents) => {
|
||||
return `[${match}](https://github.com/Prowlarr/Prowlarr/issues/${match.substring(1)})`;
|
||||
});
|
||||
|
||||
|
||||
@@ -252,7 +252,7 @@
|
||||
</span>
|
||||
|
||||
<a
|
||||
href="https://wiki.servarr.com/Prowlarr_FAQ#Help_I_have_locked_my_self_out_of_Prowlarr_and_do_not_know_the_password"
|
||||
href="https://wikijs.servarr.com/prowlarr/faq#help-i-have-locked-myself-out"
|
||||
class="forgot-password"
|
||||
>Forgot your password?</a
|
||||
>
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
[TestCase(@"https://baconbits.org/feeds.php?feed=torrents_tv&user=12345&auth=2b51db35e1910123321025a12b9933d2&passkey=mySecret&authkey=2b51db35e1910123321025a12b9933d2")]
|
||||
[TestCase(@"http://127.0.0.1:9117/dl/indexername?jackett_apikey=flwjiefewklfjacketmySecretsdfldskjfsdlk&path=we0re9f0sdfbase64sfdkfjsdlfjk&file=The+Torrent+File+Name.torrent")]
|
||||
[TestCase(@"http://nzb.su/getnzb/2b51db35e1912ffc138825a12b9933d2.nzb&i=37292&r=2b51db35e1910123321025a12b9933d2")]
|
||||
[TestCase(@"https://horrorcharnel.org/takeloginhorror.php: username=mySecret&password=mySecret&use_sslvalue==&perm_ssl=1&submitme=X&use_ssl=1&returnto=%2F&captchaSelection=1230456")]
|
||||
[TestCase(@"https://torrentdb.net/login: _token=2b51db35e1912ffc138825a12b9933d2&username=mySecret&password=mySecret&remember=on")]
|
||||
|
||||
// NzbGet
|
||||
[TestCase(@"{ ""Name"" : ""ControlUsername"", ""Value"" : ""mySecret"" }, { ""Name"" : ""ControlPassword"", ""Value"" : ""mySecret"" }, ")]
|
||||
|
||||
@@ -84,6 +84,13 @@ namespace NzbDrone.Common.Http
|
||||
throw new WebException($"Too many automatic redirections were attempted for {autoRedirectChain.Join(" -> ")}", WebExceptionStatus.ProtocolError);
|
||||
}
|
||||
|
||||
// 302 or 303 should default to GET on redirect even if POST on original
|
||||
if (response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.RedirectMethod)
|
||||
{
|
||||
request.Method = HttpMethod.GET;
|
||||
request.ContentData = null;
|
||||
}
|
||||
|
||||
response = await ExecuteRequestAsync(request, cookieContainer);
|
||||
}
|
||||
while (response.HasHttpRedirect);
|
||||
|
||||
@@ -62,6 +62,20 @@ namespace NzbDrone.Common.Http
|
||||
StatusCode == HttpStatusCode.TemporaryRedirect ||
|
||||
StatusCode == HttpStatusCode.Found;
|
||||
|
||||
public string RedirectUrl
|
||||
{
|
||||
get
|
||||
{
|
||||
var newUrl = Headers["Location"];
|
||||
if (newUrl == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return (Request.Url += new HttpUri(newUrl)).FullUri;
|
||||
}
|
||||
}
|
||||
|
||||
public string[] GetCookieHeaders()
|
||||
{
|
||||
return Headers.GetValues("Set-Cookie") ?? Array.Empty<string>();
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
// Url
|
||||
new Regex(@"(?<=\?|&|: |;)(apikey|token|passkey|auth|authkey|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=\?|&)[^=]*?(username|password)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=\?|&| )[^=]*?(_?token|username|password)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
@@ -77,7 +77,6 @@ namespace NzbDrone.Common.Instrumentation
|
||||
private static string CleanseRemoteIP(Match match)
|
||||
{
|
||||
var group = match.Groups[1];
|
||||
var valueAll = match.Value;
|
||||
var valueIP = group.Value;
|
||||
|
||||
if (IPAddress.TryParse(valueIP, out var address) && !address.IsLocalAddress())
|
||||
|
||||
227
src/NzbDrone.Core.Test/Download/DownloadClientProviderFixture.cs
Normal file
227
src/NzbDrone.Core.Test/Download/DownloadClientProviderFixture.cs
Normal file
@@ -0,0 +1,227 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Download
|
||||
{
|
||||
[TestFixture]
|
||||
public class DownloadClientProviderFixture : CoreTest<DownloadClientProvider>
|
||||
{
|
||||
private List<IDownloadClient> _downloadClients;
|
||||
private List<DownloadClientStatus> _blockedProviders;
|
||||
private int _nextId;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_downloadClients = new List<IDownloadClient>();
|
||||
_blockedProviders = new List<DownloadClientStatus>();
|
||||
_nextId = 1;
|
||||
|
||||
Mocker.GetMock<IDownloadClientFactory>()
|
||||
.Setup(v => v.GetAvailableProviders())
|
||||
.Returns(_downloadClients);
|
||||
|
||||
Mocker.GetMock<IDownloadClientStatusService>()
|
||||
.Setup(v => v.GetBlockedProviders())
|
||||
.Returns(_blockedProviders);
|
||||
}
|
||||
|
||||
private Mock<IDownloadClient> WithUsenetClient(int priority = 0)
|
||||
{
|
||||
var mock = new Mock<IDownloadClient>(MockBehavior.Default);
|
||||
mock.SetupGet(s => s.Definition)
|
||||
.Returns(Builder<DownloadClientDefinition>
|
||||
.CreateNew()
|
||||
.With(v => v.Id = _nextId++)
|
||||
.With(v => v.Priority = priority)
|
||||
.Build());
|
||||
|
||||
_downloadClients.Add(mock.Object);
|
||||
|
||||
mock.SetupGet(v => v.Protocol).Returns(DownloadProtocol.Usenet);
|
||||
|
||||
return mock;
|
||||
}
|
||||
|
||||
private Mock<IDownloadClient> WithTorrentClient(int priority = 0)
|
||||
{
|
||||
var mock = new Mock<IDownloadClient>(MockBehavior.Default);
|
||||
mock.SetupGet(s => s.Definition)
|
||||
.Returns(Builder<DownloadClientDefinition>
|
||||
.CreateNew()
|
||||
.With(v => v.Id = _nextId++)
|
||||
.With(v => v.Priority = priority)
|
||||
.Build());
|
||||
|
||||
_downloadClients.Add(mock.Object);
|
||||
|
||||
mock.SetupGet(v => v.Protocol).Returns(DownloadProtocol.Torrent);
|
||||
|
||||
return mock;
|
||||
}
|
||||
|
||||
private void GivenBlockedClient(int id)
|
||||
{
|
||||
_blockedProviders.Add(new DownloadClientStatus
|
||||
{
|
||||
ProviderId = id,
|
||||
DisabledTill = DateTime.UtcNow.AddHours(3)
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_roundrobin_over_usenet_client()
|
||||
{
|
||||
WithUsenetClient();
|
||||
WithUsenetClient();
|
||||
WithUsenetClient();
|
||||
WithTorrentClient();
|
||||
|
||||
var client1 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
|
||||
var client2 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
|
||||
var client3 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
|
||||
var client4 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
|
||||
var client5 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
|
||||
|
||||
client1.Definition.Id.Should().Be(1);
|
||||
client2.Definition.Id.Should().Be(2);
|
||||
client3.Definition.Id.Should().Be(3);
|
||||
client4.Definition.Id.Should().Be(1);
|
||||
client5.Definition.Id.Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_roundrobin_over_torrent_client()
|
||||
{
|
||||
WithUsenetClient();
|
||||
WithTorrentClient();
|
||||
WithTorrentClient();
|
||||
WithTorrentClient();
|
||||
|
||||
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
|
||||
client1.Definition.Id.Should().Be(2);
|
||||
client2.Definition.Id.Should().Be(3);
|
||||
client3.Definition.Id.Should().Be(4);
|
||||
client4.Definition.Id.Should().Be(2);
|
||||
client5.Definition.Id.Should().Be(3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_roundrobin_over_protocol_separately()
|
||||
{
|
||||
WithUsenetClient();
|
||||
WithTorrentClient();
|
||||
WithTorrentClient();
|
||||
|
||||
var client1 = Subject.GetDownloadClient(DownloadProtocol.Usenet);
|
||||
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
|
||||
client1.Definition.Id.Should().Be(1);
|
||||
client2.Definition.Id.Should().Be(2);
|
||||
client3.Definition.Id.Should().Be(3);
|
||||
client4.Definition.Id.Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_skip_blocked_torrent_client()
|
||||
{
|
||||
WithUsenetClient();
|
||||
WithTorrentClient();
|
||||
WithTorrentClient();
|
||||
WithTorrentClient();
|
||||
|
||||
GivenBlockedClient(3);
|
||||
|
||||
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
|
||||
client1.Definition.Id.Should().Be(2);
|
||||
client2.Definition.Id.Should().Be(4);
|
||||
client3.Definition.Id.Should().Be(2);
|
||||
client4.Definition.Id.Should().Be(4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_skip_blocked_torrent_client_if_all_blocked()
|
||||
{
|
||||
WithUsenetClient();
|
||||
WithTorrentClient();
|
||||
WithTorrentClient();
|
||||
WithTorrentClient();
|
||||
|
||||
GivenBlockedClient(2);
|
||||
GivenBlockedClient(3);
|
||||
GivenBlockedClient(4);
|
||||
|
||||
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
|
||||
client1.Definition.Id.Should().Be(2);
|
||||
client2.Definition.Id.Should().Be(3);
|
||||
client3.Definition.Id.Should().Be(4);
|
||||
client4.Definition.Id.Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_skip_secondary_prio_torrent_client()
|
||||
{
|
||||
WithUsenetClient();
|
||||
WithTorrentClient(2);
|
||||
WithTorrentClient();
|
||||
WithTorrentClient();
|
||||
|
||||
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
|
||||
client1.Definition.Id.Should().Be(3);
|
||||
client2.Definition.Id.Should().Be(4);
|
||||
client3.Definition.Id.Should().Be(3);
|
||||
client4.Definition.Id.Should().Be(4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_skip_secondary_prio_torrent_client_if_primary_blocked()
|
||||
{
|
||||
WithUsenetClient();
|
||||
WithTorrentClient(2);
|
||||
WithTorrentClient(2);
|
||||
WithTorrentClient();
|
||||
|
||||
GivenBlockedClient(4);
|
||||
|
||||
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent);
|
||||
|
||||
client1.Definition.Id.Should().Be(2);
|
||||
client2.Definition.Id.Should().Be(3);
|
||||
client3.Definition.Id.Should().Be(2);
|
||||
client4.Definition.Id.Should().Be(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Download
|
||||
{
|
||||
public class DownloadClientStatusServiceFixture : CoreTest<DownloadClientStatusService>
|
||||
{
|
||||
private DateTime _epoch;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_epoch = DateTime.UtcNow;
|
||||
|
||||
Mocker.GetMock<IRuntimeInfo>()
|
||||
.SetupGet(v => v.StartTime)
|
||||
.Returns(_epoch - TimeSpan.FromHours(1));
|
||||
}
|
||||
|
||||
private DownloadClientStatus WithStatus(DownloadClientStatus status)
|
||||
{
|
||||
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||
.Setup(v => v.FindByProviderId(1))
|
||||
.Returns(status);
|
||||
|
||||
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||
.Setup(v => v.All())
|
||||
.Returns(new[] { status });
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private void VerifyUpdate()
|
||||
{
|
||||
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||
.Verify(v => v.Upsert(It.IsAny<DownloadClientStatus>()), Times.Once());
|
||||
}
|
||||
|
||||
private void VerifyNoUpdate()
|
||||
{
|
||||
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||
.Verify(v => v.Upsert(It.IsAny<DownloadClientStatus>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_consider_blocked_within_5_minutes_since_initial_failure()
|
||||
{
|
||||
WithStatus(new DownloadClientStatus
|
||||
{
|
||||
InitialFailure = _epoch - TimeSpan.FromMinutes(4),
|
||||
MostRecentFailure = _epoch - TimeSpan.FromSeconds(4),
|
||||
EscalationLevel = 3
|
||||
});
|
||||
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
VerifyUpdate();
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().BeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_consider_blocked_after_5_minutes_since_initial_failure()
|
||||
{
|
||||
WithStatus(new DownloadClientStatus
|
||||
{
|
||||
InitialFailure = _epoch - TimeSpan.FromMinutes(6),
|
||||
MostRecentFailure = _epoch - TimeSpan.FromSeconds(120),
|
||||
EscalationLevel = 3
|
||||
});
|
||||
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
VerifyUpdate();
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_escalate_further_till_after_5_minutes_since_initial_failure()
|
||||
{
|
||||
var origStatus = WithStatus(new DownloadClientStatus
|
||||
{
|
||||
InitialFailure = _epoch - TimeSpan.FromMinutes(4),
|
||||
MostRecentFailure = _epoch - TimeSpan.FromSeconds(4),
|
||||
EscalationLevel = 3
|
||||
});
|
||||
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().BeNull();
|
||||
|
||||
origStatus.EscalationLevel.Should().Be(3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_escalate_further_after_5_minutes_since_initial_failure()
|
||||
{
|
||||
WithStatus(new DownloadClientStatus
|
||||
{
|
||||
InitialFailure = _epoch - TimeSpan.FromMinutes(6),
|
||||
MostRecentFailure = _epoch - TimeSpan.FromSeconds(120),
|
||||
EscalationLevel = 3
|
||||
});
|
||||
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().NotBeNull();
|
||||
|
||||
status.EscalationLevel.Should().BeGreaterThan(3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_escalate_beyond_3_hours()
|
||||
{
|
||||
WithStatus(new DownloadClientStatus
|
||||
{
|
||||
InitialFailure = _epoch - TimeSpan.FromMinutes(6),
|
||||
MostRecentFailure = _epoch - TimeSpan.FromSeconds(120),
|
||||
EscalationLevel = 3
|
||||
});
|
||||
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().NotBeNull();
|
||||
status.DisabledTill.Should().HaveValue();
|
||||
status.DisabledTill.Should().NotBeAfter(_epoch + TimeSpan.FromHours(3.1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,14 +33,8 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
Subject.Check().ShouldBeWarning();
|
||||
}
|
||||
|
||||
[TestCase("Develop")]
|
||||
[TestCase("develop")]
|
||||
public void should_return_error_when_branch_is_v1(string branch)
|
||||
{
|
||||
GivenValidBranch(branch);
|
||||
|
||||
Subject.Check().ShouldBeError();
|
||||
}
|
||||
|
||||
[TestCase("nightly")]
|
||||
[TestCase("Nightly")]
|
||||
public void should_return_no_warning_when_branch_valid(string branch)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using FluentAssertions;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.HealthCheck;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
@@ -8,11 +8,11 @@ namespace NzbDrone.Core.Test.HealthCheck
|
||||
[TestFixture]
|
||||
public class HealthCheckFixture : CoreTest
|
||||
{
|
||||
private const string WikiRoot = "https://wiki.servarr.com/";
|
||||
private const string WikiRoot = "https://wikijs.servarr.com/";
|
||||
|
||||
[TestCase("I blew up because of some weird user mistake", null, WikiRoot + "Prowlarr_System#i_blew_up_because_of_some_weird_user_mistake")]
|
||||
[TestCase("I blew up because of some weird user mistake", "#my_health_check", WikiRoot + "Prowlarr_System#my_health_check")]
|
||||
[TestCase("I blew up because of some weird user mistake", "Custom-Page#my_health_check", WikiRoot + "Custom-Page#my_health_check")]
|
||||
[TestCase("I blew up because of some weird user mistake", null, WikiRoot + "prowlarr/system#i-blew-up-because-of-some-weird-user-mistake")]
|
||||
[TestCase("I blew up because of some weird user mistake", "#my-health-check", WikiRoot + "prowlarr/system#my-health-check")]
|
||||
[TestCase("I blew up because of some weird user mistake", "custom-page#my-health-check", WikiRoot + "prowlarr/custom-page#my-health-check")]
|
||||
public void should_format_wiki_url(string message, string wikiFragment, string expectedUrl)
|
||||
{
|
||||
var subject = new NzbDrone.Core.HealthCheck.HealthCheck(typeof(HealthCheckBase), HealthCheckResult.Warning, message, wikiFragment);
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.FileList;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
@@ -21,6 +22,33 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
|
||||
Username = "somename"
|
||||
};
|
||||
|
||||
Subject.Capabilities = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
},
|
||||
Flags = new List<IndexerFlag>
|
||||
{
|
||||
IndexerFlag.FreeLeech
|
||||
}
|
||||
};
|
||||
|
||||
Subject.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesSD, "Filme SD");
|
||||
Subject.Capabilities.Categories.AddCategoryMapping(2, NewznabStandardCategory.MoviesDVD, "Filme DVD");
|
||||
|
||||
_movieSearchCriteria = new MovieSearchCriteria
|
||||
{
|
||||
SearchTerm = "Star Wars",
|
||||
@@ -38,7 +66,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
|
||||
[Test]
|
||||
public void should_use_categories_for_feed()
|
||||
{
|
||||
var results = Subject.GetSearchRequests(new MovieSearchCriteria { Categories = new int[] { 1, 2 } });
|
||||
var results = Subject.GetSearchRequests(new MovieSearchCriteria { Categories = new int[] { NewznabStandardCategory.MoviesSD.Id, NewznabStandardCategory.MoviesDVD.Id } });
|
||||
|
||||
results.GetAllTiers().Should().HaveCount(1);
|
||||
|
||||
@@ -50,7 +78,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
|
||||
[Test]
|
||||
public void should_not_search_by_imdbid_if_not_supported()
|
||||
{
|
||||
_movieSearchCriteria.ImdbId = "tt0076759";
|
||||
_movieSearchCriteria.ImdbId = "0076759";
|
||||
var results = Subject.GetSearchRequests(_movieSearchCriteria);
|
||||
|
||||
results.GetAllTiers().Should().HaveCount(1);
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
|
||||
_movieSearchCriteria = new MovieSearchCriteria
|
||||
{
|
||||
Categories = new int[] { 2000, 2010 },
|
||||
ImdbId = "tt0076759"
|
||||
ImdbId = "0076759"
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
|
||||
_movieSearchCriteria = new MovieSearchCriteria
|
||||
{
|
||||
Categories = new int[] { 2000, 2010 },
|
||||
ImdbId = "tt0076759"
|
||||
ImdbId = "0076759"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
|
||||
public void should_search_by_imdbid_if_supported()
|
||||
{
|
||||
var results = Subject.GetSearchRequests(_movieSearchCriteria);
|
||||
var imdbQuery = int.Parse(_movieSearchCriteria.ImdbId.Substring(2));
|
||||
var imdbQuery = int.Parse(_movieSearchCriteria.ImdbId);
|
||||
|
||||
results.GetAllTiers().Should().HaveCount(1);
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests
|
||||
{
|
||||
public class TestIndexer : HttpIndexerBase<TestIndexerSettings>
|
||||
public class TestIndexer : UsenetIndexerBase<TestIndexerSettings>
|
||||
{
|
||||
public override string Name => "Test Indexer";
|
||||
public override string BaseUrl => "http://testindexer.com";
|
||||
@@ -18,8 +19,8 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||
public int _supportedPageSize;
|
||||
public override int PageSize => _supportedPageSize;
|
||||
|
||||
public TestIndexer(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
public TestIndexer(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, IValidateNzbs nzbValidationService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, nzbValidationService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
Fields = schema.Fields,
|
||||
};
|
||||
|
||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/";
|
||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
|
||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
|
||||
return other.EnableRss == EnableRss &&
|
||||
other.EnableAutomaticSearch == EnableAutomaticSearch &&
|
||||
other.EnableInteractiveSearch == EnableAutomaticSearch &&
|
||||
other.EnableInteractiveSearch == EnableInteractiveSearch &&
|
||||
other.Name == Name &&
|
||||
other.Implementation == Implementation &&
|
||||
other.Priority == Priority &&
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
Fields = schema.Fields,
|
||||
};
|
||||
|
||||
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/";
|
||||
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
|
||||
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
|
||||
return other.EnableRss == EnableRss &&
|
||||
other.EnableAutomaticSearch == EnableAutomaticSearch &&
|
||||
other.EnableInteractiveSearch == EnableAutomaticSearch &&
|
||||
other.EnableInteractiveSearch == EnableInteractiveSearch &&
|
||||
other.Name == Name &&
|
||||
other.Implementation == Implementation &&
|
||||
other.Priority == Priority &&
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
Fields = schema.Fields,
|
||||
};
|
||||
|
||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/";
|
||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
|
||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
|
||||
return other.EnableRss == EnableRss &&
|
||||
other.EnableAutomaticSearch == EnableAutomaticSearch &&
|
||||
other.EnableInteractiveSearch == EnableAutomaticSearch &&
|
||||
other.EnableInteractiveSearch == EnableInteractiveSearch &&
|
||||
other.Name == Name &&
|
||||
other.Implementation == Implementation &&
|
||||
other.Priority == Priority &&
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
Fields = schema.Fields,
|
||||
};
|
||||
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/";
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
|
||||
return other.EnableRss == EnableRss &&
|
||||
other.EnableAutomaticSearch == EnableAutomaticSearch &&
|
||||
other.EnableInteractiveSearch == EnableAutomaticSearch &&
|
||||
other.EnableInteractiveSearch == EnableInteractiveSearch &&
|
||||
other.Name == Name &&
|
||||
other.Implementation == Implementation &&
|
||||
other.Priority == Priority &&
|
||||
|
||||
@@ -108,10 +108,10 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
if (OsInfo.IsOsx)
|
||||
{
|
||||
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/Prowlarr_FAQ#I_use_Prowlarr_on_a_Mac_and_it_suddenly_stopped_working_What_happened", e, fileName);
|
||||
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wikijs.servarr.com/prowlarr/faq#i-use-prowlarr-on-a-mac-and-it-suddenly-stopped-working-what-happened", e, fileName);
|
||||
}
|
||||
|
||||
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);
|
||||
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wikijs.servarr.com/prowlarr/faq#i-am-getting-an-error-database-disk-image-is-malformed", e, fileName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -31,9 +31,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
IHttpClient httpClient,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
IValidateNzbs nzbValidationService,
|
||||
Logger logger)
|
||||
: base(httpClient, configService, diskProvider, nzbValidationService, logger)
|
||||
: base(httpClient, configService, diskProvider, logger)
|
||||
{
|
||||
_dsInfoProxy = dsInfoProxy;
|
||||
_dsTaskProxy = dsTaskProxy;
|
||||
|
||||
@@ -20,9 +20,8 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
IHttpClient httpClient,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
IValidateNzbs nzbValidationService,
|
||||
Logger logger)
|
||||
: base(httpClient, configService, diskProvider, nzbValidationService, logger)
|
||||
: base(httpClient, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
@@ -25,9 +25,8 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
IHttpClient httpClient,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
IValidateNzbs nzbValidationService,
|
||||
Logger logger)
|
||||
: base(httpClient, configService, diskProvider, nzbValidationService, logger)
|
||||
: base(httpClient, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
@@ -14,24 +16,20 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
|
||||
{
|
||||
public class Pneumatic : DownloadClientBase<PneumaticSettings>
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
|
||||
public Pneumatic(IHttpClient httpClient,
|
||||
IConfigService configService,
|
||||
public Pneumatic(IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(configService, diskProvider, logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public override string Name => "Pneumatic";
|
||||
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
|
||||
|
||||
public override string Download(ReleaseInfo release, bool redirect)
|
||||
public override async Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer)
|
||||
{
|
||||
var url = release.DownloadUrl;
|
||||
var url = new Uri(release.DownloadUrl);
|
||||
var title = release.Title;
|
||||
|
||||
title = StringUtil.CleanFileName(title);
|
||||
@@ -40,7 +38,10 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
|
||||
var nzbFile = Path.Combine(Settings.NzbFolder, title + ".nzb");
|
||||
|
||||
_logger.Debug("Downloading NZB from: {0} to: {1}", url, nzbFile);
|
||||
_httpClient.DownloadFile(url, nzbFile);
|
||||
|
||||
var nzbData = await indexer.Download(url);
|
||||
|
||||
File.WriteAllBytes(nzbFile, nzbData);
|
||||
|
||||
_logger.Debug("NZB Download succeeded, saved to: {0}", nzbFile);
|
||||
|
||||
|
||||
@@ -22,9 +22,8 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
IHttpClient httpClient,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
IValidateNzbs nzbValidationService,
|
||||
Logger logger)
|
||||
: base(httpClient, configService, diskProvider, nzbValidationService, logger)
|
||||
: base(httpClient, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
{
|
||||
_logger.Debug("rTorrent didn't add the torrent within {0} seconds: {1}.", tries * retryDelay / 1000, filename);
|
||||
|
||||
throw new ReleaseDownloadException(release, "Downloading torrent failed");
|
||||
throw new ReleaseDownloadException("Downloading torrent failed");
|
||||
}
|
||||
|
||||
return hash;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
@@ -54,7 +55,7 @@ namespace NzbDrone.Core.Download
|
||||
get;
|
||||
}
|
||||
|
||||
public abstract string Download(ReleaseInfo release, bool redirect);
|
||||
public abstract Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer);
|
||||
|
||||
public ValidationResult Test()
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Security;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
public interface IDownloadMappingService
|
||||
{
|
||||
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Download
|
||||
{
|
||||
public interface IDownloadService
|
||||
{
|
||||
void SendReportToClient(ReleaseInfo release, string source, string host, bool redirect);
|
||||
Task SendReportToClient(ReleaseInfo release, string source, string host, bool redirect);
|
||||
Task<byte[]> DownloadReport(string link, int indexerId, string source, string host, string title);
|
||||
void RecordRedirect(string link, int indexerId, string source, string host, string title);
|
||||
}
|
||||
@@ -49,7 +49,7 @@ namespace NzbDrone.Core.Download
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void SendReportToClient(ReleaseInfo release, string source, string host, bool redirect)
|
||||
public async Task SendReportToClient(ReleaseInfo release, string source, string host, bool redirect)
|
||||
{
|
||||
var downloadTitle = release.Title;
|
||||
var downloadClient = _downloadClientProvider.GetDownloadClient(release.DownloadProtocol);
|
||||
@@ -69,10 +69,12 @@ namespace NzbDrone.Core.Download
|
||||
_rateLimitService.WaitAndPulse(url.Host, TimeSpan.FromSeconds(2));
|
||||
}
|
||||
|
||||
var indexer = _indexerFactory.GetInstance(_indexerFactory.Get(release.IndexerId));
|
||||
|
||||
string downloadClientId;
|
||||
try
|
||||
{
|
||||
downloadClientId = downloadClient.Download(release, redirect);
|
||||
downloadClientId = await downloadClient.Download(release, redirect, indexer);
|
||||
_downloadClientStatusService.RecordSuccess(downloadClient.Definition.Id);
|
||||
_indexerStatusService.RecordSuccess(release.IndexerId);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Threading.Tasks;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
@@ -7,6 +8,6 @@ namespace NzbDrone.Core.Download
|
||||
public interface IDownloadClient : IProvider
|
||||
{
|
||||
DownloadProtocol Protocol { get; }
|
||||
string Download(ReleaseInfo release, bool redirect);
|
||||
Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ namespace NzbDrone.Core.Download
|
||||
{
|
||||
public interface IValidateNzbs
|
||||
{
|
||||
void Validate(string filename, byte[] fileContent);
|
||||
void Validate(byte[] fileContent);
|
||||
}
|
||||
|
||||
public class NzbValidationService : IValidateNzbs
|
||||
{
|
||||
public void Validate(string filename, byte[] fileContent)
|
||||
public void Validate(byte[] fileContent)
|
||||
{
|
||||
var reader = new StreamReader(new MemoryStream(fileContent));
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
if (nzb == null)
|
||||
{
|
||||
throw new InvalidNzbException("Invalid NZB: No Root element [{0}]", filename);
|
||||
throw new InvalidNzbException("Invalid NZB: No Root element");
|
||||
}
|
||||
|
||||
// nZEDb has an bug in their error reporting code spitting out invalid http status codes
|
||||
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
if (!nzb.Name.LocalName.Equals("nzb"))
|
||||
{
|
||||
throw new InvalidNzbException("Invalid NZB: Unexpected root element. Expected 'nzb' found '{0}' [{1}]", nzb.Name.LocalName, filename);
|
||||
throw new InvalidNzbException("Invalid NZB: Unexpected root element. Expected 'nzb' found '{0}'", nzb.Name.LocalName);
|
||||
}
|
||||
|
||||
var ns = nzb.Name.Namespace;
|
||||
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
if (files.Empty())
|
||||
{
|
||||
throw new InvalidNzbException("Invalid NZB: No files [{0}]", filename);
|
||||
throw new InvalidNzbException("Invalid NZB: No files");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MonoTorrent;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
@@ -39,7 +41,7 @@ namespace NzbDrone.Core.Download
|
||||
protected abstract string AddFromTorrentFile(ReleaseInfo release, string hash, string filename, byte[] fileContent);
|
||||
protected abstract string AddFromTorrentLink(ReleaseInfo release, string hash, string torrentLink);
|
||||
|
||||
public override string Download(ReleaseInfo release, bool redirect)
|
||||
public override async Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer)
|
||||
{
|
||||
var torrentInfo = release as TorrentInfo;
|
||||
|
||||
@@ -66,7 +68,7 @@ namespace NzbDrone.Core.Download
|
||||
{
|
||||
try
|
||||
{
|
||||
return DownloadFromWebUrl(release, torrentUrl);
|
||||
return await DownloadFromWebUrl(release, indexer, torrentUrl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -87,7 +89,7 @@ namespace NzbDrone.Core.Download
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
throw new ReleaseDownloadException(release, "Magnet not supported by download client. ({0})", ex.Message);
|
||||
throw new ReleaseDownloadException("Magnet not supported by download client. ({0})", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,7 +105,7 @@ namespace NzbDrone.Core.Download
|
||||
{
|
||||
if (torrentUrl.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new ReleaseDownloadException(release, "Magnet not supported by download client. ({0})", ex.Message);
|
||||
throw new ReleaseDownloadException("Magnet not supported by download client. ({0})", ex.Message);
|
||||
}
|
||||
|
||||
_logger.Debug("Magnet not supported by download client, trying torrent. ({0})", ex.Message);
|
||||
@@ -112,74 +114,31 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
if (torrentUrl.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return DownloadFromWebUrl(release, torrentUrl);
|
||||
return await DownloadFromWebUrl(release, indexer, torrentUrl);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string DownloadFromWebUrl(ReleaseInfo release, string torrentUrl)
|
||||
private async Task<string> DownloadFromWebUrl(ReleaseInfo release, IIndexer indexer, string torrentUrl)
|
||||
{
|
||||
byte[] torrentFile = null;
|
||||
|
||||
try
|
||||
torrentFile = await indexer.Download(new Uri(torrentUrl));
|
||||
|
||||
// handle magnet URLs
|
||||
if (torrentFile.Length >= 7
|
||||
&& torrentFile[0] == 0x6d
|
||||
&& torrentFile[1] == 0x61
|
||||
&& torrentFile[2] == 0x67
|
||||
&& torrentFile[3] == 0x6e
|
||||
&& torrentFile[4] == 0x65
|
||||
&& torrentFile[5] == 0x74
|
||||
&& torrentFile[6] == 0x3a)
|
||||
{
|
||||
var request = new HttpRequest(torrentUrl);
|
||||
request.Headers.Accept = "application/x-bittorrent";
|
||||
request.AllowAutoRedirect = false;
|
||||
|
||||
var response = _httpClient.Get(request);
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.MovedPermanently ||
|
||||
response.StatusCode == HttpStatusCode.Found ||
|
||||
response.StatusCode == HttpStatusCode.SeeOther)
|
||||
{
|
||||
var locationHeader = response.Headers.GetSingleValue("Location");
|
||||
|
||||
_logger.Trace("Torrent request is being redirected to: {0}", locationHeader);
|
||||
|
||||
if (locationHeader != null)
|
||||
{
|
||||
if (locationHeader.StartsWith("magnet:"))
|
||||
{
|
||||
return DownloadFromMagnetUrl(release, locationHeader);
|
||||
}
|
||||
|
||||
return DownloadFromWebUrl(release, locationHeader);
|
||||
}
|
||||
|
||||
throw new WebException("Remote website tried to redirect without providing a location.");
|
||||
}
|
||||
|
||||
torrentFile = response.ResponseData;
|
||||
|
||||
_logger.Debug("Downloading torrent for release '{0}' finished ({1} bytes from {2})", release.Title, torrentFile.Length, torrentUrl);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release '{0}' failed since it no longer exists ({1})", release.Title, torrentUrl);
|
||||
throw new ReleaseUnavailableException(release, "Downloading torrent failed", ex);
|
||||
}
|
||||
|
||||
if ((int)ex.Response.StatusCode == 429)
|
||||
{
|
||||
_logger.Error("API Grab Limit reached for {0}", torrentUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release '{0}' failed ({1})", release.Title, torrentUrl);
|
||||
}
|
||||
|
||||
throw new ReleaseDownloadException(release, "Downloading torrent failed", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release '{0}' failed ({1})", release.Title, torrentUrl);
|
||||
|
||||
throw new ReleaseDownloadException(release, "Downloading torrent failed", ex);
|
||||
var magnetUrl = Encoding.UTF8.GetString(torrentFile);
|
||||
return DownloadFromMagnetUrl(release, magnetUrl);
|
||||
}
|
||||
|
||||
var filename = string.Format("{0}.torrent", StringUtil.CleanFileName(release.Title));
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Net;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -15,17 +15,14 @@ namespace NzbDrone.Core.Download
|
||||
where TSettings : IProviderConfig, new()
|
||||
{
|
||||
protected readonly IHttpClient _httpClient;
|
||||
private readonly IValidateNzbs _nzbValidationService;
|
||||
|
||||
protected UsenetClientBase(IHttpClient httpClient,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
IValidateNzbs nzbValidationService,
|
||||
Logger logger)
|
||||
: base(configService, diskProvider, logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_nzbValidationService = nzbValidationService;
|
||||
}
|
||||
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
|
||||
@@ -33,9 +30,9 @@ namespace NzbDrone.Core.Download
|
||||
protected abstract string AddFromNzbFile(ReleaseInfo release, string filename, byte[] fileContents);
|
||||
protected abstract string AddFromLink(ReleaseInfo release);
|
||||
|
||||
public override string Download(ReleaseInfo release, bool redirect)
|
||||
public override async Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer)
|
||||
{
|
||||
var url = release.DownloadUrl;
|
||||
var url = new Uri(release.DownloadUrl);
|
||||
|
||||
if (redirect)
|
||||
{
|
||||
@@ -46,40 +43,7 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
byte[] nzbData;
|
||||
|
||||
try
|
||||
{
|
||||
var request = new HttpRequest(url);
|
||||
nzbData = _httpClient.Get(request).ResponseData;
|
||||
|
||||
_logger.Debug("Downloaded nzb for release '{0}' finished ({1} bytes from {2})", release.Title, nzbData.Length, url);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
_logger.Error(ex, "Downloading nzb file for release '{0}' failed since it no longer exists ({1})", release.Title, url);
|
||||
throw new ReleaseUnavailableException(release, "Downloading nzb failed", ex);
|
||||
}
|
||||
|
||||
if ((int)ex.Response.StatusCode == 429)
|
||||
{
|
||||
_logger.Error("API Grab Limit reached for {0}", url);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(ex, "Downloading nzb for release '{0}' failed ({1})", release.Title, url);
|
||||
}
|
||||
|
||||
throw new ReleaseDownloadException(release, "Downloading nzb failed", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex, "Downloading nzb for release '{0}' failed ({1})", release.Title, url);
|
||||
|
||||
throw new ReleaseDownloadException(release, "Downloading nzb failed", ex);
|
||||
}
|
||||
|
||||
_nzbValidationService.Validate(filename, nzbData);
|
||||
nzbData = await indexer.Download(url);
|
||||
|
||||
_logger.Info("Adding report [{0}] to the queue.", release.Title);
|
||||
return AddFromNzbFile(release, filename, nzbData);
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
using System;
|
||||
using System;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Exceptions
|
||||
{
|
||||
public class DownloadClientRejectedReleaseException : ReleaseDownloadException
|
||||
{
|
||||
public ReleaseInfo Release { get; set; }
|
||||
public DownloadClientRejectedReleaseException(ReleaseInfo release, string message, params object[] args)
|
||||
: base(release, message, args)
|
||||
: base(message, args)
|
||||
{
|
||||
Release = release;
|
||||
}
|
||||
|
||||
public DownloadClientRejectedReleaseException(ReleaseInfo release, string message)
|
||||
: base(release, message)
|
||||
: base(message)
|
||||
{
|
||||
Release = release;
|
||||
}
|
||||
|
||||
public DownloadClientRejectedReleaseException(ReleaseInfo release, string message, Exception innerException, params object[] args)
|
||||
: base(release, message, innerException, args)
|
||||
: base(message, innerException, args)
|
||||
{
|
||||
Release = release;
|
||||
}
|
||||
|
||||
public DownloadClientRejectedReleaseException(ReleaseInfo release, string message, Exception innerException)
|
||||
: base(release, message, innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
Release = release;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using NzbDrone.Common.Exceptions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
@@ -6,30 +6,24 @@ namespace NzbDrone.Core.Exceptions
|
||||
{
|
||||
public class ReleaseDownloadException : NzbDroneException
|
||||
{
|
||||
public ReleaseInfo Release { get; set; }
|
||||
|
||||
public ReleaseDownloadException(ReleaseInfo release, string message, params object[] args)
|
||||
public ReleaseDownloadException(string message, params object[] args)
|
||||
: base(message, args)
|
||||
{
|
||||
Release = release;
|
||||
}
|
||||
|
||||
public ReleaseDownloadException(ReleaseInfo release, string message)
|
||||
public ReleaseDownloadException(string message)
|
||||
: base(message)
|
||||
{
|
||||
Release = release;
|
||||
}
|
||||
|
||||
public ReleaseDownloadException(ReleaseInfo release, string message, Exception innerException, params object[] args)
|
||||
public ReleaseDownloadException(string message, Exception innerException, params object[] args)
|
||||
: base(message, innerException, args)
|
||||
{
|
||||
Release = release;
|
||||
}
|
||||
|
||||
public ReleaseDownloadException(ReleaseInfo release, string message, Exception innerException)
|
||||
public ReleaseDownloadException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
Release = release;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
using System;
|
||||
using System;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Exceptions
|
||||
{
|
||||
public class ReleaseUnavailableException : ReleaseDownloadException
|
||||
{
|
||||
public ReleaseUnavailableException(ReleaseInfo release, string message, params object[] args)
|
||||
: base(release, message, args)
|
||||
public ReleaseUnavailableException(string message, params object[] args)
|
||||
: base(message, args)
|
||||
{
|
||||
}
|
||||
|
||||
public ReleaseUnavailableException(ReleaseInfo release, string message)
|
||||
: base(release, message)
|
||||
public ReleaseUnavailableException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public ReleaseUnavailableException(ReleaseInfo release, string message, Exception innerException, params object[] args)
|
||||
: base(release, message, innerException, args)
|
||||
public ReleaseUnavailableException(string message, Exception innerException, params object[] args)
|
||||
: base(message, innerException, args)
|
||||
{
|
||||
}
|
||||
|
||||
public ReleaseUnavailableException(ReleaseInfo release, string message, Exception innerException)
|
||||
: base(release, message, innerException)
|
||||
public ReleaseUnavailableException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,14 +44,14 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Error,
|
||||
_localizationService.GetLocalizedString("ApplicationStatusCheckAllClientMessage"),
|
||||
"#applications_are_unavailable_due_to_failures");
|
||||
"#applications-are-unavailable-due-to-failures");
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Warning,
|
||||
string.Format(_localizationService.GetLocalizedString("ApplicationStatusCheckSingleClientMessage"),
|
||||
string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))),
|
||||
"#applications_are_unavailable_due_to_failures");
|
||||
"#applications-are-unavailable-due-to-failures");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,10 +37,10 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
|
||||
if (backOffProviders.Count == enabledProviders.Count)
|
||||
{
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Error, _localizationService.GetLocalizedString("DownloadClientStatusCheckAllClientMessage"), "#download_clients_are_unavailable_due_to_failures");
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Error, _localizationService.GetLocalizedString("DownloadClientStatusCheckAllClientMessage"), "#download-clients-are-unavailable-due-to-failures");
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("DownloadClientStatusCheckSingleClientMessage"), string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))), "#download_clients_are_unavailable_due_to_failures");
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("DownloadClientStatusCheckSingleClientMessage"), string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))), "#download-clients-are-unavailable-due-to-failures");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,14 +46,14 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Error,
|
||||
_localizationService.GetLocalizedString("IndexerLongTermStatusCheckAllClientMessage"),
|
||||
"#indexers_are_unavailable_due_to_failures");
|
||||
"#indexers-are-unavailable-due-to-failures");
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Warning,
|
||||
string.Format(_localizationService.GetLocalizedString("IndexerLongTermStatusCheckSingleClientMessage"),
|
||||
string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))),
|
||||
"#indexers_are_unavailable_due_to_failures");
|
||||
"#indexers-are-unavailable-due-to-failures");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,14 +44,14 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Error,
|
||||
_localizationService.GetLocalizedString("IndexerStatusCheckAllClientMessage"),
|
||||
"#indexers_are_unavailable_due_to_failures");
|
||||
"#indexers-are-unavailable-due-to-failures");
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Warning,
|
||||
string.Format(_localizationService.GetLocalizedString("IndexerStatusCheckSingleClientMessage"),
|
||||
string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))),
|
||||
"#indexers_are_unavailable_due_to_failures");
|
||||
"#indexers-are-unavailable-due-to-failures");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
HealthCheckResult.Warning,
|
||||
string.Format(_localizationService.GetLocalizedString("NewznabVipCheckExpiringClientMessage"),
|
||||
string.Join(", ", expiringProviders.Select(v => v.Definition.Name))),
|
||||
"#newznab_vip_expiring");
|
||||
"#newznab-vip-expiring");
|
||||
}
|
||||
|
||||
if (!expiredProviders.Empty())
|
||||
@@ -64,7 +64,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
HealthCheckResult.Warning,
|
||||
string.Format(_localizationService.GetLocalizedString("NewznabVipCheckExpiredClientMessage"),
|
||||
string.Join(", ", expiredProviders.Select(v => v.Definition.Name))),
|
||||
"#newznab_vip_expired");
|
||||
"#newznab-vip-expired");
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType());
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
HealthCheckResult.Warning,
|
||||
string.Format(_localizationService.GetLocalizedString("IndexerObsoleteCheckMessage"),
|
||||
string.Join(", ", oldIndexers.Select(v => v.Name))),
|
||||
"#indexers_are_obsolete");
|
||||
"#indexers-are-obsolete");
|
||||
}
|
||||
|
||||
public override bool CheckOnSchedule => false;
|
||||
|
||||
@@ -23,12 +23,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
|
||||
if (!Enum.GetNames(typeof(ReleaseBranches)).Any(x => x.ToLower() == currentBranch))
|
||||
{
|
||||
if (currentBranch == "develop")
|
||||
{
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("ReleaseBranchCheckPreviousVersionMessage"), _configFileService.Branch), "#branch_is_for_a_previous_version");
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("ReleaseBranchCheckOfficialBranchMessage"), _configFileService.Branch), "#branch_is_not_a_valid_release_branch");
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("ReleaseBranchCheckOfficialBranchMessage"), _configFileService.Branch), "#branch-is-not-a-valid-release-branch");
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType());
|
||||
@@ -36,6 +31,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
|
||||
public enum ReleaseBranches
|
||||
{
|
||||
Develop,
|
||||
Nightly
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Error,
|
||||
string.Format(_localizationService.GetLocalizedString("UpdateCheckStartupTranslocationMessage"), startupFolder),
|
||||
"#cannot_install_update_because_startup_folder_is_in_an_app_translocation_folder.");
|
||||
"#cannot-install-update-because-startup-folder-is-in-an-app-translocation-folder.");
|
||||
}
|
||||
|
||||
if (!_diskProvider.FolderWritable(startupFolder))
|
||||
@@ -56,7 +56,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Error,
|
||||
string.Format(_localizationService.GetLocalizedString("UpdateCheckStartupNotWritableMessage"), startupFolder, Environment.UserName),
|
||||
"#cannot_install_update_because_startup_folder_is_not_writable_by_the_user");
|
||||
"#cannot-install-update-because-startup-folder-is-not-writable-by-the-user");
|
||||
}
|
||||
|
||||
if (!_diskProvider.FolderWritable(uiFolder))
|
||||
@@ -64,7 +64,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Error,
|
||||
string.Format(_localizationService.GetLocalizedString("UpdateCheckUINotWritableMessage"), uiFolder, Environment.UserName),
|
||||
"#cannot_install_update_because_ui_folder_is_not_writable_by_the_user");
|
||||
"#cannot-install-update-because-ui-folder-is-not-writable-by-the-user");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,12 +34,12 @@ namespace NzbDrone.Core.HealthCheck
|
||||
|
||||
private static string MakeWikiFragment(string message)
|
||||
{
|
||||
return "#" + CleanFragmentRegex.Replace(message.ToLower(), string.Empty).Replace(' ', '_');
|
||||
return "#" + CleanFragmentRegex.Replace(message.ToLower(), string.Empty).Replace(' ', '-');
|
||||
}
|
||||
|
||||
private static HttpUri MakeWikiUrl(string fragment)
|
||||
{
|
||||
return new HttpUri("https://wiki.servarr.com/Prowlarr_System#") + new HttpUri(fragment);
|
||||
return new HttpUri("https://wikijs.servarr.com/prowlarr/system#") + new HttpUri(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -119,14 +119,14 @@ namespace NzbDrone.Core.History
|
||||
|
||||
if (message.Query is MovieSearchCriteria)
|
||||
{
|
||||
history.Data.Add("ImdbId", ((MovieSearchCriteria)message.Query).ImdbId ?? string.Empty);
|
||||
history.Data.Add("ImdbId", ((MovieSearchCriteria)message.Query).FullImdbId ?? string.Empty);
|
||||
history.Data.Add("TmdbId", ((MovieSearchCriteria)message.Query).TmdbId?.ToString() ?? string.Empty);
|
||||
history.Data.Add("TraktId", ((MovieSearchCriteria)message.Query).TraktId?.ToString() ?? string.Empty);
|
||||
}
|
||||
|
||||
if (message.Query is TvSearchCriteria)
|
||||
{
|
||||
history.Data.Add("ImdbId", ((TvSearchCriteria)message.Query).ImdbId ?? string.Empty);
|
||||
history.Data.Add("ImdbId", ((TvSearchCriteria)message.Query).FullImdbId ?? string.Empty);
|
||||
history.Data.Add("TvdbId", ((TvSearchCriteria)message.Query).TvdbId?.ToString() ?? string.Empty);
|
||||
history.Data.Add("TraktId", ((TvSearchCriteria)message.Query).TraktId?.ToString() ?? string.Empty);
|
||||
history.Data.Add("RId", ((TvSearchCriteria)message.Query).RId?.ToString() ?? string.Empty);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Parser;
|
||||
|
||||
namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
{
|
||||
@@ -20,5 +21,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string FullImdbId => ParseUtil.GetFullImdbId(ImdbId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
public string SanitizedTvSearchString => (SanitizedSearchTerm + " " + EpisodeSearchString).Trim();
|
||||
public string EpisodeSearchString => GetEpisodeSearchString();
|
||||
|
||||
public string FullImdbId => ParseUtil.GetFullImdbId(ImdbId);
|
||||
|
||||
public override bool RssSearch
|
||||
{
|
||||
get
|
||||
|
||||
@@ -80,13 +80,13 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
r.InfoUrl == null ? null : new XElement("comments", r.InfoUrl),
|
||||
r.PublishDate == DateTime.MinValue ? new XElement("pubDate", XmlDateFormat(DateTime.Now)) : new XElement("pubDate", XmlDateFormat(r.PublishDate)),
|
||||
new XElement("size", r.Size),
|
||||
r.Category == null ? null : from c in r.Category select new XElement("category", c.Id),
|
||||
r.Categories == null ? null : from c in r.Categories select new XElement("category", c.Id),
|
||||
new XElement(
|
||||
"enclosure",
|
||||
new XAttribute("url", r.DownloadUrl ?? t.MagnetUrl ?? string.Empty),
|
||||
r.Size == null ? null : new XAttribute("length", r.Size),
|
||||
new XAttribute("type", protocol == DownloadProtocol.Torrent ? "application/x-bittorrent" : "application/x-nzb")),
|
||||
r.Category == null ? null : from c in r.Category select GetNabElement("category", c.Id, protocol),
|
||||
r.Categories == null ? null : from c in r.Categories select GetNabElement("category", c.Id, protocol),
|
||||
r.IndexerFlags == null ? null : from f in r.IndexerFlags select GetNabElement("tag", f.Name, protocol),
|
||||
GetNabElement("rageid", r.TvRageId, protocol),
|
||||
GetNabElement("thetvdb", r.TvdbId, protocol),
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.Events;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
public class IndexerDefinitionUpdateService : IIndexerDefinitionUpdateService, IExecute<IndexerDefinitionUpdateCommand>
|
||||
{
|
||||
private const int DEFINITION_VERSION = 1;
|
||||
private readonly List<string> _defintionBlacklist = new List<string>() { "blutopia", "beyond-hd", "beyond-hd-oneurl", "hdbits" };
|
||||
private readonly List<string> _defintionBlacklist = new List<string>() { "aither", "animeworld", "blutopia", "beyond-hd", "beyond-hd-oneurl", "hdbits" };
|
||||
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
@@ -89,7 +89,8 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
{
|
||||
var req = new HttpRequest($"https://indexers.prowlarr.com/master/{DEFINITION_VERSION}/{id}");
|
||||
var response = _httpClient.Get(req);
|
||||
return _deserializer.Deserialize<CardigannDefinition>(response.Content);
|
||||
var definition = _deserializer.Deserialize<CardigannDefinition>(response.Content);
|
||||
return CleanIndexerDefinition(definition);
|
||||
}
|
||||
|
||||
private CardigannDefinition LoadIndexerDef(string fileKey)
|
||||
@@ -118,42 +119,7 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
var definitionString = File.ReadAllText(file.FullName);
|
||||
var definition = _deserializer.Deserialize<CardigannDefinition>(definitionString);
|
||||
|
||||
//defaults
|
||||
if (definition.Settings == null)
|
||||
{
|
||||
definition.Settings = new List<SettingsField>
|
||||
{
|
||||
new SettingsField { Name = "username", Label = "Username", Type = "text" },
|
||||
new SettingsField { Name = "password", Label = "Password", Type = "password" }
|
||||
};
|
||||
}
|
||||
|
||||
if (definition.Encoding == null)
|
||||
{
|
||||
definition.Encoding = "UTF-8";
|
||||
}
|
||||
|
||||
if (definition.Login != null && definition.Login.Method == null)
|
||||
{
|
||||
definition.Login.Method = "form";
|
||||
}
|
||||
|
||||
if (definition.Search.Paths == null)
|
||||
{
|
||||
definition.Search.Paths = new List<SearchPathBlock>();
|
||||
}
|
||||
|
||||
// convert definitions with a single search Path to a Paths entry
|
||||
if (definition.Search.Path != null)
|
||||
{
|
||||
definition.Search.Paths.Add(new SearchPathBlock
|
||||
{
|
||||
Path = definition.Search.Path,
|
||||
Inheritinputs = true
|
||||
});
|
||||
}
|
||||
|
||||
return definition;
|
||||
return CleanIndexerDefinition(definition);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -165,6 +131,45 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
return GetHttpDefinition(fileKey);
|
||||
}
|
||||
|
||||
private CardigannDefinition CleanIndexerDefinition(CardigannDefinition definition)
|
||||
{
|
||||
if (definition.Settings == null)
|
||||
{
|
||||
definition.Settings = new List<SettingsField>
|
||||
{
|
||||
new SettingsField { Name = "username", Label = "Username", Type = "text" },
|
||||
new SettingsField { Name = "password", Label = "Password", Type = "password" }
|
||||
};
|
||||
}
|
||||
|
||||
if (definition.Encoding == null)
|
||||
{
|
||||
definition.Encoding = "UTF-8";
|
||||
}
|
||||
|
||||
if (definition.Login != null && definition.Login.Method == null)
|
||||
{
|
||||
definition.Login.Method = "form";
|
||||
}
|
||||
|
||||
if (definition.Search.Paths == null)
|
||||
{
|
||||
definition.Search.Paths = new List<SearchPathBlock>();
|
||||
}
|
||||
|
||||
// convert definitions with a single search Path to a Paths entry
|
||||
if (definition.Search.Path != null)
|
||||
{
|
||||
definition.Search.Paths.Add(new SearchPathBlock
|
||||
{
|
||||
Path = definition.Search.Path,
|
||||
Inheritinputs = true
|
||||
});
|
||||
}
|
||||
|
||||
return definition;
|
||||
}
|
||||
|
||||
public void Execute(IndexerDefinitionUpdateCommand message)
|
||||
{
|
||||
UpdateLocalDefinitions();
|
||||
|
||||
59
src/NzbDrone.Core/Indexers/Definitions/Aither.cs
Normal file
59
src/NzbDrone.Core/Indexers/Definitions/Aither.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Definitions.UNIT3D;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class Aither : Unit3dBase
|
||||
{
|
||||
public override string Name => "Aither";
|
||||
public override string BaseUrl => "https://aither.cc/";
|
||||
public override string Description => "Aither is a Private Torrent Tracker for HD MOVIES / TV";
|
||||
public override string Language => "en-us";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public Aither(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies, "Movie");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TV, "TV");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Audio, "Music");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.PCGames, "Games");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.XXX, "XXX");
|
||||
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.TVSport, "Sport");
|
||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.PC, "Software/Apps");
|
||||
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.BooksEBook, "Ebooks/Magazines");
|
||||
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.AudioAudiobook, "AudioBooks");
|
||||
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.Other, "Education");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class AnimeBytes : HttpIndexerBase<AnimeBytesSettings>
|
||||
public class AnimeBytes : TorrentIndexerBase<AnimeBytesSettings>
|
||||
{
|
||||
public override string Name => "AnimeBytes";
|
||||
public override string BaseUrl => "https://animebytes.tv/";
|
||||
@@ -449,7 +449,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
Guid = guid.AbsoluteUri,
|
||||
DownloadUrl = linkUri.AbsoluteUri,
|
||||
PublishDate = publishDate,
|
||||
Category = category,
|
||||
Categories = category,
|
||||
Description = description,
|
||||
Size = size,
|
||||
Seeders = seeders,
|
||||
|
||||
@@ -21,7 +21,7 @@ using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class AnimeTorrents : HttpIndexerBase<AnimeTorrentsSettings>
|
||||
public class AnimeTorrents : TorrentIndexerBase<AnimeTorrentsSettings>
|
||||
{
|
||||
public override string Name => "AnimeTorrents";
|
||||
|
||||
@@ -294,7 +294,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
rCat = rCat.Substring(rCatIdx + 4);
|
||||
}
|
||||
|
||||
release.Category = _categories.MapTrackerCatToNewznab(rCat);
|
||||
release.Categories = _categories.MapTrackerCatToNewznab(rCat);
|
||||
|
||||
if (row.QuerySelector("img[alt=\"Gold Torrent\"]") != null)
|
||||
{
|
||||
|
||||
61
src/NzbDrone.Core/Indexers/Definitions/AnimeWorld.cs
Normal file
61
src/NzbDrone.Core/Indexers/Definitions/AnimeWorld.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Definitions.UNIT3D;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class AnimeWorld : Unit3dBase
|
||||
{
|
||||
public override string Name => "AnimeWorld";
|
||||
public override string BaseUrl => "https://animeworld.cx/";
|
||||
public override string Description => "AnimeWorld (AW) is a GERMAN Private site for ANIME / MANGA / HENTAI";
|
||||
public override string Language => "de-de";
|
||||
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public AnimeWorld(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies, "Anime Movie");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "Anime Series");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Audio, "Anime Musik/OST");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.PCGames, "Anime Spiele");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.XXX, "Hentai");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.PCGames, "Spiele Linux");
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.Other, "Sonstiges");
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.Movies, "Filme");
|
||||
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.TV, "Serien");
|
||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.PCGames, "Spiele");
|
||||
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.Audio, "Musik");
|
||||
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.BooksComics, "Mangas");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
{
|
||||
public abstract class AvistazBase : HttpIndexerBase<AvistazSettings>
|
||||
public abstract class AvistazBase : TorrentIndexerBase<AvistazSettings>
|
||||
{
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override string BaseUrl => "";
|
||||
|
||||
@@ -24,6 +24,11 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
{
|
||||
var torrentInfos = new List<TorrentInfo>();
|
||||
|
||||
if (indexerResponse.HttpResponse.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
return torrentInfos.ToArray();
|
||||
}
|
||||
|
||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
|
||||
@@ -50,7 +55,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
InfoHash = row.InfoHash,
|
||||
InfoUrl = details,
|
||||
Guid = details,
|
||||
Category = cats,
|
||||
Categories = cats,
|
||||
PublishDate = row.CreatedAt,
|
||||
Size = row.FileSize,
|
||||
Files = row.FileCount,
|
||||
|
||||
@@ -74,9 +74,9 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
{
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories);
|
||||
|
||||
if (searchCriteria.ImdbId != null)
|
||||
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Add("imdb", searchCriteria.ImdbId);
|
||||
parameters.Add("imdb", searchCriteria.FullImdbId);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -103,9 +103,9 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
{
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories);
|
||||
|
||||
if (searchCriteria.ImdbId != null)
|
||||
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Add("imdb", searchCriteria.ImdbId);
|
||||
parameters.Add("imdb", searchCriteria.FullImdbId);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class BakaBT : HttpIndexerBase<BakaBTSettings>
|
||||
public class BakaBT : TorrentIndexerBase<BakaBTSettings>
|
||||
{
|
||||
public override string Name => "BakaBT";
|
||||
|
||||
@@ -297,7 +297,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
release.Title = release.Title.Substring(0, insertPoint) + " Season 1 " + release.Title.Substring(insertPoint);
|
||||
}
|
||||
|
||||
release.Category = currentCategories;
|
||||
release.Categories = currentCategories;
|
||||
|
||||
//release.Description = row.QuerySelector("span.tags")?.TextContent;
|
||||
release.Guid = _baseUrl + qTitleLink.GetAttribute("href");
|
||||
|
||||
@@ -21,7 +21,7 @@ using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class BeyondHD : HttpIndexerBase<BeyondHDSettings>
|
||||
public class BeyondHD : TorrentIndexerBase<BeyondHDSettings>
|
||||
{
|
||||
public override string Name => "BeyondHD";
|
||||
|
||||
@@ -123,7 +123,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.ImdbId, searchCriteria.TmdbId.GetValueOrDefault()));
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.FullImdbId, searchCriteria.TmdbId.GetValueOrDefault()));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -141,7 +141,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories, searchCriteria.ImdbId));
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories, searchCriteria.FullImdbId));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -209,7 +209,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
InfoHash = row.InfoHash,
|
||||
InfoUrl = details,
|
||||
Guid = details,
|
||||
Category = _categories.MapTrackerCatDescToNewznab(row.Category),
|
||||
Categories = _categories.MapTrackerCatDescToNewznab(row.Category),
|
||||
PublishDate = DateTime.Parse(row.CreatedAt, CultureInfo.InvariantCulture),
|
||||
Size = row.Size,
|
||||
Grabs = row.Grabs,
|
||||
|
||||
@@ -6,7 +6,7 @@ using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.BroadcastheNet
|
||||
{
|
||||
public class BroadcastheNet : HttpIndexerBase<BroadcastheNetSettings>
|
||||
public class BroadcastheNet : TorrentIndexerBase<BroadcastheNetSettings>
|
||||
{
|
||||
public override string Name => "BroadcasTheNet";
|
||||
|
||||
|
||||
@@ -104,12 +104,12 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
|
||||
torrentInfo.DownloadVolumeFactor = 0;
|
||||
torrentInfo.MinimumRatio = 1;
|
||||
|
||||
torrentInfo.Category = _categories.MapTrackerCatToNewznab(torrent.Resolution);
|
||||
torrentInfo.Categories = _categories.MapTrackerCatToNewznab(torrent.Resolution);
|
||||
|
||||
// Default to TV if category could not be mapped
|
||||
if (torrentInfo.Category == null || !torrentInfo.Category.Any())
|
||||
if (torrentInfo.Categories == null || !torrentInfo.Categories.Any())
|
||||
{
|
||||
torrentInfo.Category = new List<IndexerCategory> { NewznabStandardCategory.TV };
|
||||
torrentInfo.Categories = new List<IndexerCategory> { NewznabStandardCategory.TV };
|
||||
}
|
||||
|
||||
results.Add(torrentInfo);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation.Results;
|
||||
@@ -7,6 +8,7 @@ using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
@@ -14,7 +16,7 @@ using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Cardigann
|
||||
{
|
||||
public class Cardigann : HttpIndexerBase<CardigannSettings>
|
||||
public class Cardigann : TorrentIndexerBase<CardigannSettings>
|
||||
{
|
||||
private readonly IIndexerDefinitionUpdateService _definitionService;
|
||||
private readonly ICached<CardigannRequestGenerator> _generatorCache;
|
||||
@@ -105,6 +107,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
{
|
||||
Enable = true,
|
||||
Name = definition.Name,
|
||||
Language = definition.Language,
|
||||
Implementation = GetType().Name,
|
||||
Settings = new CardigannSettings { DefinitionFile = definition.File },
|
||||
Protocol = DownloadProtocol.Torrent,
|
||||
@@ -147,6 +150,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
if (request.Url.Scheme == "magnet")
|
||||
{
|
||||
ValidateMagnet(request.Url.FullUri);
|
||||
return Encoding.UTF8.GetBytes(request.Url.FullUri);
|
||||
}
|
||||
|
||||
@@ -159,10 +163,36 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
var response = await _httpClient.ExecuteAsync(request);
|
||||
downloadBytes = response.ResponseData;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release failed since it no longer exists ({0})", request.Url.FullUri);
|
||||
throw new ReleaseUnavailableException("Downloading torrent failed", ex);
|
||||
}
|
||||
|
||||
if ((int)ex.Response.StatusCode == 429)
|
||||
{
|
||||
_logger.Error("API Grab Limit reached for {0}", request.Url.FullUri);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release failed ({0})", request.Url.FullUri);
|
||||
}
|
||||
|
||||
throw new ReleaseDownloadException("Downloading torrent failed", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release failed ({0})", request.Url.FullUri);
|
||||
|
||||
throw new ReleaseDownloadException("Downloading torrent failed", ex);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_indexerStatusService.RecordFailure(Definition.Id);
|
||||
_logger.Error("Download failed");
|
||||
_logger.Error("Downloading torrent failed");
|
||||
throw;
|
||||
}
|
||||
|
||||
return downloadBytes;
|
||||
|
||||
@@ -237,7 +237,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
}
|
||||
else if (setting.Type == "checkbox")
|
||||
{
|
||||
variables[name] = ((bool)value) ? ".True" : ".False";
|
||||
variables[name] = ((bool)value) ? ".True" : null;
|
||||
}
|
||||
else if (setting.Type == "select")
|
||||
{
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
// Remove cookie cache
|
||||
if (indexerResponse.HttpResponse.HasHttpRedirect && indexerResponse.HttpResponse.Headers["Location"]
|
||||
if (indexerResponse.HttpResponse.HasHttpRedirect && indexerResponse.HttpResponse.RedirectUrl
|
||||
.ContainsIgnoreCase("login.php"))
|
||||
{
|
||||
CookiesUpdater(null, null);
|
||||
@@ -194,17 +194,17 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
var cats = MapTrackerCatToNewznab(value);
|
||||
if (cats.Any())
|
||||
{
|
||||
if (release.Category == null || fieldModifiers.Contains("noappend"))
|
||||
if (release.Categories == null || fieldModifiers.Contains("noappend"))
|
||||
{
|
||||
release.Category = cats;
|
||||
release.Categories = cats;
|
||||
}
|
||||
else
|
||||
{
|
||||
release.Category = release.Category.Union(cats).ToList();
|
||||
release.Categories = release.Categories.Union(cats).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
value = release.Category.ToString();
|
||||
value = release.Categories.ToString();
|
||||
break;
|
||||
case "size":
|
||||
release.Size = ReleaseInfo.GetBytes(value);
|
||||
|
||||
@@ -43,8 +43,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
variables[".Query.Movie"] = null;
|
||||
variables[".Query.Year"] = null;
|
||||
variables[".Query.IMDBID"] = searchCriteria.ImdbId;
|
||||
variables[".Query.IMDBIDShort"] = searchCriteria.ImdbId?.TrimStart('t') ?? null;
|
||||
variables[".Query.IMDBID"] = searchCriteria.FullImdbId;
|
||||
variables[".Query.IMDBIDShort"] = searchCriteria.ImdbId;
|
||||
variables[".Query.TMDBID"] = searchCriteria.TmdbId;
|
||||
variables[".Query.TraktID"] = searchCriteria.TraktId;
|
||||
|
||||
@@ -78,8 +78,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
variables[".Query.Series"] = null;
|
||||
variables[".Query.Ep"] = searchCriteria.Episode;
|
||||
variables[".Query.Season"] = searchCriteria.Season;
|
||||
variables[".Query.IMDBID"] = searchCriteria.ImdbId;
|
||||
variables[".Query.IMDBIDShort"] = searchCriteria.ImdbId?.Replace("tt", "") ?? null;
|
||||
variables[".Query.IMDBID"] = searchCriteria.FullImdbId;
|
||||
variables[".Query.IMDBIDShort"] = searchCriteria.ImdbId;
|
||||
variables[".Query.TVDBID"] = searchCriteria.TvdbId;
|
||||
variables[".Query.TVRageID"] = searchCriteria.RId;
|
||||
variables[".Query.TVMazeID"] = searchCriteria.TvMazeId;
|
||||
@@ -635,14 +635,14 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
{
|
||||
if (requestUrl.StartsWith(SiteLink) && !redirectUrl.StartsWith(SiteLink))
|
||||
{
|
||||
var uri = new Uri(redirectUrl);
|
||||
var uri = new HttpUri(redirectUrl);
|
||||
return uri.Scheme + "://" + uri.Host + "/";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected string GetRedirectDomainHint(HttpResponse result) => GetRedirectDomainHint(result.Request.Url.ToString(), result.Headers.GetSingleValue("Location"));
|
||||
protected string GetRedirectDomainHint(HttpResponse result) => GetRedirectDomainHint(result.Request.Url.ToString(), result.RedirectUrl);
|
||||
|
||||
protected async Task<HttpResponse> HandleRequest(RequestBlock request, Dictionary<string, object> variables = null, string referer = null)
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user