Compare commits

..

3 Commits

Author SHA1 Message Date
Robin Dadswell
0150988bcd Added Indexer Factory 2021-06-04 23:08:02 +01:00
Robin Dadswell
593231c3af Updated config definition to match Jacketts config definition 2021-06-04 23:07:40 +01:00
Robin Dadswell
dbbb6bf0d1 Starts of Jackett Migrations
request extension

start migration controller/resource

removed unneccesary using - probalby more to go still

more scaffolding

added more framework; contemplating using the importlists from other arrs as a base for this

Revert "jackett config"

This reverts commit 6523eaf55450ceed84b3667421595a9d9e34dc51.

added stuff from nit's radarr pr

neated code up a little bit, more to do on this though

get config sorted, api logic also added - migration todo and indexer config to do
2021-06-03 11:07:56 +01:00
204 changed files with 1083 additions and 5058 deletions

View File

@@ -7,7 +7,6 @@ assignees: ''
---
<!-- Support Requests will be closed immediately, if you are unsure go to our Reddit or Discord first. Exceptions do not mean you found a bug! -->
<!-- Note: Text between <!- and -> will be hidden -->
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
@@ -34,4 +33,4 @@ assignees: ''
Turn on Trace logs under Settings -> General and wait for the bug to occur again.
**Upload the full log file here (or another site (e.g. pastebin) and link it). Issues will be closed, if they do not include this!**
<!-- Trace logs are named Prowlarr.trace.txt or Prowlarr.trace.#.txt and will contain "trace" in them-->
<!-- Trace logs are named Prowlarr.trace.txt or Prowlarr.trace.#.txt and will contain "trace" in them-->

View File

@@ -1,8 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Indexer Requests
url: https://requests.prowlarr.com/
about: Request new indexers to be added. Vote on existing requests.
- name: Support via Discord
url: https://prowlarr.com/discord
about: Chat with users and devs on support and setup related topics.

View File

@@ -3,31 +3,15 @@
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](https://wiki.servarr.com/prowlarr) the better.
Setup guides, FAQ, the more information we have on the wiki the better.
## Development ##
### 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:9696
See the readme for information on setting up your development environment.
### 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 Radarr's develop branch, don't merge
- Rebase from Prowlarr'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
@@ -36,10 +20,6 @@ Setup guides, FAQ, the more information we have on the [wiki](https://wiki.serva
- 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

View File

@@ -2,7 +2,7 @@
[![Build Status](https://dev.azure.com/Prowlarr/Prowlarr/_apis/build/status/Prowlarr.Prowlarr?branchName=develop)](https://dev.azure.com/Prowlarr/Prowlarr/_build/latest?definitionId=1&branchName=develop)
[![Translated](https://translate.servarr.com/widgets/servarr/-/prowlarr/svg-badge.svg)](https://translate.servarr.com/engage/prowlarr/?utm_source=widget)
[![Docker Pulls](https://img.shields.io/docker/pulls/hotio/prowlarr.svg)](https://wiki.servarr.com/prowlarr/installation#docker)
[![Docker Pulls](https://img.shields.io/docker/pulls/hotio/prowlarr.svg)](https://wiki.servarr.com/Prowlarr_Installation#Docker)
![Github Downloads](https://img.shields.io/github/downloads/Prowlarr/Prowlarr/total.svg)
[![Backers on Open Collective](https://opencollective.com/Prowlarr/backers/badge.svg)](#backers)
[![Sponsors on Open Collective](https://opencollective.com/Prowlarr/sponsors/badge.svg)](#sponsors)
@@ -10,14 +10,12 @@
Prowlarr is a indexer manager/proxy built on the popular arr .net/reactjs base stack to integrate with your various PVR apps. Prowlarr supports both Torrent Trackers and Usenet Indexers. It integrates seamlessly with Sonarr, Radarr, Lidarr, and Readarr offering complete management of your indexers with no per app Indexer setup required (we do it all).
## Major Features Include:
- Usenet support for 24 indexers natively, including Headphones VIP, and support for any Newznab compatible indexer via "Generic Newznab"
- Torrent support for almost 500 trackers & more coming soon
- Torrent support for any Torznab compatible tracker via "Generic Torznab"
- Usenet support for any Newznab compatible indexer, including Headphones VIP
- Torrent support 400+ trackers & more coming soon
- Indexer Sync to Sonarr/Radarr/Readarr/Lidarr, so no manual configuration of the other applications are required
- Indexer History and Statistics
- Manual Searching of Trackers & Indexers at a category level
- Support for pushing releases directly to your download clients from Prowlarr
- Indexer health and status notifications
## Support
Note: Prowlarr is currently early in life, thus bugs should be expected
@@ -25,14 +23,7 @@ Note: Prowlarr is currently early in life, thus bugs should be expected
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://prowlarr.com/discord)
[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60)](https://www.reddit.com/r/Prowlarr)
[![GitHub - Bugs and Feature Requests Only](https://img.shields.io/badge/github-issues-red.svg?maxAge=60)](https://github.com/Prowlarr/Prowlarr/issues)
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](https://wiki.servarr.com/prowlarr)
## Indexers/Trackers
[Supported Indexers](https://wiki.servarr.com/en/prowlarr/supported-indexers)
[Indexer Requests](https://requests.prowlarr.com)
- Request or vote on an existing request for a new tracker/indexer
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](https://wiki.servarr.com/Prowlarr)
## Feature Requests

View File

@@ -23,16 +23,6 @@ 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: {
@@ -74,8 +64,7 @@ BarChart.propTypes = {
horizontal: PropTypes.bool,
legend: PropTypes.bool,
title: PropTypes.string.isRequired,
kind: PropTypes.oneOf(kinds.all).isRequired,
stepSize: PropTypes.number
kind: PropTypes.oneOf(kinds.all).isRequired
};
BarChart.defaultProps = {
@@ -83,8 +72,7 @@ BarChart.defaultProps = {
horizontal: false,
legend: false,
title: '',
kind: kinds.INFO,
stepSize: 1
kind: kinds.INFO
};
export default BarChart;

View File

@@ -16,16 +16,10 @@ class StackedBarChart extends Component {
maintainAspectRatio: false,
scales: {
x: {
stacked: true,
ticks: {
stepSize: this.props.stepSize
}
stacked: true
},
y: {
stacked: true,
ticks: {
stepSize: this.props.stepSize
}
stacked: true
}
},
plugins: {
@@ -69,13 +63,11 @@ class StackedBarChart extends Component {
StackedBarChart.propTypes = {
data: PropTypes.object.isRequired,
title: PropTypes.string.isRequired,
stepSize: PropTypes.number
title: PropTypes.string.isRequired
};
StackedBarChart.defaultProps = {
title: '',
stepSize: 1
title: ''
};
export default StackedBarChart;

View File

@@ -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://wiki.servarr.com/Prowlarr_FAQ#Why_cant_Prowlarr_see_my_files_on_a_remote_server">
{translate('MappedDrivesRunningAsService')}
</Link>
</Alert>

View File

@@ -25,7 +25,7 @@ function FormInputHelpText(props) {
isCheckInput && styles.isCheckInput
)}
>
<div dangerouslySetInnerHTML={{ __html: text }} />
{text}
{
link ?

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -73,7 +73,7 @@ class HistoryOptions extends Component {
}
HistoryOptions.propTypes = {
historyCleanupDays: PropTypes.number.isRequired,
historyCleanupDays: PropTypes.bool.isRequired,
dispatchSaveGeneralSettings: PropTypes.func.isRequired
};

View File

@@ -20,25 +20,19 @@ import styles from './AddIndexerModalContent.css';
const columns = [
{
name: 'protocol',
label: translate('Protocol'),
label: 'Protocol',
isSortable: true,
isVisible: true
},
{
name: 'name',
label: translate('Name'),
isSortable: true,
isVisible: true
},
{
name: 'language',
label: translate('Language'),
label: 'Name',
isSortable: true,
isVisible: true
},
{
name: 'privacy',
label: translate('Privacy'),
label: 'Privacy',
isSortable: true,
isVisible: true
}

View File

@@ -26,8 +26,7 @@ class SelectIndexerRow extends Component {
const {
protocol,
privacy,
name,
language
name
} = this.props;
return (
@@ -42,10 +41,6 @@ class SelectIndexerRow extends Component {
{name}
</TableRowCell>
<TableRowCell>
{language}
</TableRowCell>
<TableRowCell>
{privacy}
</TableRowCell>
@@ -58,7 +53,6 @@ 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
};

View File

@@ -99,7 +99,7 @@ function EditIndexerModalContent(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="redirect"
helpText={translate('RedirectHelpText')}
helpText={'Redirect incoming download requests for indexer instead of Proxying using Prowlarr'}
isDisabled={!supportsRedirect.value}
{...redirect}
onChange={onInputChange}
@@ -113,7 +113,6 @@ function EditIndexerModalContent(props) {
type={inputTypes.APP_PROFILE_SELECT}
name="appProfileId"
{...appProfileId}
helpText={translate('AppProfileSelectHelpText')}
onChange={onInputChange}
/>
</FormGroup>

View File

@@ -50,7 +50,7 @@ class TagsModalContent extends Component {
render() {
const {
indexerTags,
movieTags,
tagList,
onModalClose
} = this.props;
@@ -108,7 +108,7 @@ class TagsModalContent extends Component {
<div className={styles.result}>
{
indexerTags.map((t) => {
movieTags.map((t) => {
const tag = _.find(tagList, { id: t });
if (!tag) {
@@ -140,7 +140,7 @@ class TagsModalContent extends Component {
return null;
}
if (indexerTags.indexOf(t) > -1) {
if (movieTags.indexOf(t) > -1) {
return null;
}
@@ -179,7 +179,7 @@ class TagsModalContent extends Component {
}
TagsModalContent.propTypes = {
indexerTags: PropTypes.arrayOf(PropTypes.number).isRequired,
movieTags: PropTypes.arrayOf(PropTypes.number).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onModalClose: PropTypes.func.isRequired,
onApplyTagsPress: PropTypes.func.isRequired

View File

@@ -10,15 +10,15 @@ function createMapStateToProps() {
(state, { indexerIds }) => indexerIds,
createAllIndexersSelector(),
createTagsSelector(),
(indexerIds, allIndexers, tagList) => {
const indexers = _.intersectionWith(allIndexers, indexerIds, (s, id) => {
(indexerIds, allMovies, tagList) => {
const movies = _.intersectionWith(allMovies, indexerIds, (s, id) => {
return s.id === id;
});
const indexerTags = _.uniq(_.concat(..._.map(indexers, 'tags')));
const movieTags = _.uniq(_.concat(..._.map(movies, 'tags')));
return {
indexerTags,
movieTags,
tagList
};
}

View File

@@ -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 { saveIndexerEditor, setMovieFilter, setMovieSort, setMovieTableOption } from 'Store/Actions/indexerIndexActions';
import { saveMovieEditor, 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 }));
},
dispatchSaveIndexerEditor(payload) {
dispatch(saveIndexerEditor(payload));
dispatchSaveMovieEditor(payload) {
dispatch(saveMovieEditor(payload));
},
onTestAllPress() {
@@ -56,7 +56,7 @@ class IndexerIndexConnector extends Component {
// Listeners
onSaveSelected = (payload) => {
this.props.dispatchSaveIndexerEditor(payload);
this.props.dispatchSaveMovieEditor(payload);
}
onScroll = ({ scrollTop }) => {
@@ -79,7 +79,7 @@ class IndexerIndexConnector extends Component {
IndexerIndexConnector.propTypes = {
isSmallScreen: PropTypes.bool.isRequired,
dispatchSaveIndexerEditor: PropTypes.func.isRequired,
dispatchSaveMovieEditor: PropTypes.func.isRequired,
items: PropTypes.arrayOf(PropTypes.object)
};

View File

@@ -248,7 +248,7 @@ class IndexerIndexRow extends Component {
className={styles.externalLink}
name={icons.EXTERNAL_LINK}
title={'Website'}
to={baseUrl.replace('api.', '')}
to={baseUrl}
/>
<IconButton

View File

@@ -19,10 +19,6 @@ function getAverageResponseTimeData(indexerStats) {
};
});
data.sort((a, b) => {
return b.value - a.value;
});
return data;
}

View File

@@ -4,7 +4,6 @@ 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';
@@ -39,11 +38,9 @@ class SearchFooter extends Component {
searchQuery
} = this.state;
if (searchQuery !== '' || searchCategories.length > 0 || searchIndexerIds.length > 0) {
if (searchQuery !== '' || searchCategories !== [] || searchIndexerIds !== []) {
this.onSearchPress();
}
this.props.bindShortcut('enter', this.onSearchPress, { isGlobal: true });
}
componentDidUpdate(prevProps) {
@@ -117,7 +114,6 @@ class SearchFooter extends Component {
<TextInput
name='searchQuery'
autoFocus={true}
value={searchQuery}
isDisabled={isFetching}
onChange={onInputChange}
@@ -185,8 +181,7 @@ SearchFooter.propTypes = {
onSearchPress: PropTypes.func.isRequired,
hasIndexers: PropTypes.bool.isRequired,
onInputChange: PropTypes.func.isRequired,
searchError: PropTypes.object,
bindShortcut: PropTypes.func.isRequired
searchError: PropTypes.object
};
export default keyboardShortcuts(SearchFooter);
export default SearchFooter;

View File

@@ -18,9 +18,9 @@ import translate from 'Utilities/String/translate';
import styles from './EditApplicationModalContent.css';
const syncLevelOptions = [
{ key: 'disabled', value: translate('Disabled') },
{ key: 'addOnly', value: translate('AddRemoveOnly') },
{ key: 'fullSync', value: translate('FullSync') }
{ key: 'disabled', value: 'Disabled' },
{ key: 'addOnly', value: 'Add and Remove Only' },
{ key: 'fullSync', value: 'Full Sync' }
];
function EditApplicationModalContent(props) {
@@ -53,7 +53,7 @@ function EditApplicationModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{`${id ? translate('Edit') : translate('Add')} ${translate('Application')}`}
{`${id ? 'Edit' : 'Add'} Application`}
</ModalHeader>
<ModalBody>
@@ -94,13 +94,13 @@ function EditApplicationModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>{translate('SyncLevel')}</FormLabel>
<FormLabel>{'Sync Level'}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
values={syncLevelOptions}
name="syncLevel"
helpText={`${translate('SyncLevelAddRemove')}<br>${translate('SyncLevelFull')}`}
helpText={'Sync Level'}
{...syncLevel}
onChange={onInputChange}
/>

View File

@@ -53,9 +53,6 @@ class AddDownloadClientModalContent extends Component {
<div>
<Alert kind={kinds.INFO}>
<div>
{translate('AddDownloadClientToProwlarr')}
</div>
<div>
{translate('ProwlarrSupportsAnyDownloadClient')}
</div>

View File

@@ -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://wiki.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://wiki.servarr.com/Prowlarr_Settings#Updates"
onChange={onInputChange}
{...updateMechanism}
/>

View File

@@ -25,8 +25,8 @@ function NotificationEventItems(props) {
<FormLabel>{translate('NotificationTriggers')}</FormLabel>
<div>
<FormInputHelpText
text={translate('NotificationTriggersHelpText')}
link="https://wiki.servarr.com/prowlarr/settings#connections"
text={translate('NotifcationTriggersHelpText')}
link="https://wiki.servarr.com/Prowlarr_Settings#Connections"
/>
<div className={styles.events}>
<div>

View File

@@ -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_INDEXER_EDITOR = 'indexerIndex/saveIndexerEditor';
export const SAVE_MOVIE_EDITOR = 'indexerIndex/saveMovieEditor';
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 saveIndexerEditor = createThunk(SAVE_INDEXER_EDITOR);
export const saveMovieEditor = createThunk(SAVE_MOVIE_EDITOR);
export const bulkDeleteIndexers = createThunk(BULK_DELETE_INDEXERS);
//
// Action Handlers
export const actionHandlers = handleThunks({
[SAVE_INDEXER_EDITOR]: function(getState, payload, dispatch) {
[SAVE_MOVIE_EDITOR]: function(getState, payload, dispatch) {
dispatch(set({
section,
isSaving: true

View File

@@ -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://wiki.servarr.com/Prowlarr_System#Could_not_connect_to_signalR'
});
}

View File

@@ -1,64 +0,0 @@
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;

View File

@@ -15,32 +15,32 @@ class MoreInfo extends Component {
return (
<FieldSet legend={translate('MoreInfo')}>
<DescriptionList>
<DescriptionListItemTitle>{translate('HomePage')}</DescriptionListItemTitle>
<DescriptionListItemTitle>Home page</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to="https://prowlarr.com/">prowlarr.com</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>{translate('Wiki')}</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to="https://wiki.servarr.com/prowlarr">wiki.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>
<DescriptionListItemTitle>Discord</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to="https://prowlarr.com/discord">prowlarr.com/discord</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>{translate('Source')}</DescriptionListItemTitle>
<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>
<DescriptionListItemDescription>
<Link to="https://github.com/Prowlarr/Prowlarr/">github.com/Prowlarr/Prowlarr</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>{translate('FeatureRequests')}</DescriptionListItemTitle>
<DescriptionListItemTitle>Feature Requests</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to="https://github.com/Prowlarr/Prowlarr/issues">github.com/Prowlarr/Prowlarr/issues</Link>
</DescriptionListItemDescription>

View File

@@ -3,7 +3,6 @@ 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';
@@ -19,7 +18,6 @@ class Status extends Component {
<HealthConnector />
<AboutConnector />
<MoreInfo />
<Donations />
</PageContentBody>
</PageContent>
);

View File

@@ -1,17 +0,0 @@
.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;
}

View File

@@ -24,7 +24,7 @@ class UpdateChanges extends Component {
<ul>
{
changes.map((change, index) => {
const checkChange = change.replace(/#\d{3,5}\b/g, (match, contents) => {
const checkChange = change.replace(/#\d{4,5}\b/g, (match, contents) => {
return `[${match}](https://github.com/Prowlarr/Prowlarr/issues/${match.substring(1)})`;
});

View File

@@ -252,7 +252,7 @@
</span>
<a
href="https://wiki.servarr.com/prowlarr/faq#help-i-have-locked-myself-out"
href="https://wiki.servarr.com/Prowlarr_FAQ#Help_I_have_locked_my_self_out_of_Prowlarr_and_do_not_know_the_password"
class="forgot-password"
>Forgot your password?</a
>

View File

@@ -17,8 +17,6 @@ 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"" }, ")]

View File

@@ -84,13 +84,6 @@ 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);

View File

@@ -62,20 +62,6 @@ 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>();

View File

@@ -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(@"(?<=\?|&| )[^=]*?(_?token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=\?|&)[^=]*?(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,6 +77,7 @@ 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())

View File

@@ -1,227 +0,0 @@
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);
}
}
}

View File

@@ -1,161 +0,0 @@
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));
}
}
}

View File

@@ -33,8 +33,14 @@ 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)

View File

@@ -1,4 +1,4 @@
using FluentAssertions;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.HealthCheck;
using NzbDrone.Core.Test.Framework;
@@ -10,9 +10,9 @@ namespace NzbDrone.Core.Test.HealthCheck
{
private const string WikiRoot = "https://wiki.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 + "prowlarr/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 + "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);

View File

@@ -2,7 +2,6 @@ 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;
@@ -22,33 +21,6 @@ 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",
@@ -66,7 +38,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
[Test]
public void should_use_categories_for_feed()
{
var results = Subject.GetSearchRequests(new MovieSearchCriteria { Categories = new int[] { NewznabStandardCategory.MoviesSD.Id, NewznabStandardCategory.MoviesDVD.Id } });
var results = Subject.GetSearchRequests(new MovieSearchCriteria { Categories = new int[] { 1, 2 } });
results.GetAllTiers().Should().HaveCount(1);
@@ -78,7 +50,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
[Test]
public void should_not_search_by_imdbid_if_not_supported()
{
_movieSearchCriteria.ImdbId = "0076759";
_movieSearchCriteria.ImdbId = "tt0076759";
var results = Subject.GetSearchRequests(_movieSearchCriteria);
results.GetAllTiers().Should().HaveCount(1);

View File

@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
_movieSearchCriteria = new MovieSearchCriteria
{
Categories = new int[] { 2000, 2010 },
ImdbId = "0076759"
ImdbId = "tt0076759"
};
}

View File

@@ -48,7 +48,7 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
_movieSearchCriteria = new MovieSearchCriteria
{
Categories = new int[] { 2000, 2010 },
ImdbId = "0076759"
ImdbId = "tt0076759"
};
}
@@ -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);
var imdbQuery = int.Parse(_movieSearchCriteria.ImdbId.Substring(2));
results.GetAllTiers().Should().HaveCount(1);

View File

@@ -1,13 +1,12 @@
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 : UsenetIndexerBase<TestIndexerSettings>
public class TestIndexer : HttpIndexerBase<TestIndexerSettings>
{
public override string Name => "Test Indexer";
public override string BaseUrl => "http://testindexer.com";
@@ -19,8 +18,8 @@ namespace NzbDrone.Core.Test.IndexerTests
public int _supportedPageSize;
public override int PageSize => _supportedPageSize;
public TestIndexer(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, IValidateNzbs nzbValidationService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, nzbValidationService, logger)
public TestIndexer(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json.Linq;
using NLog;
@@ -32,25 +31,7 @@ namespace NzbDrone.Core.Applications.Lidarr
{
var failures = new List<ValidationFailure>();
var testIndexer = new IndexerDefinition
{
Id = 0,
Name = "Test",
Protocol = DownloadProtocol.Usenet,
Capabilities = new IndexerCapabilities()
};
testIndexer.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.Audio);
try
{
failures.AddIfNotNull(_lidarrV1Proxy.TestConnection(BuildLidarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Lidarr"));
}
failures.AddIfNotNull(_lidarrV1Proxy.Test(Settings));
return new ValidationResult(failures);
}
@@ -165,7 +146,7 @@ namespace NzbDrone.Core.Applications.Lidarr
Fields = schema.Fields,
};
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{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()));

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Applications.Lidarr
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
other.EnableInteractiveSearch == EnableAutomaticSearch &&
other.Name == Name &&
other.Implementation == Implementation &&
other.Priority == Priority &&

View File

@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Applications.Lidarr
public IEnumerable<int> SyncCategories { get; set; }
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Lidarr sees it, including http(s)://, port, and urlbase if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Lidarr sees it, including http(s):// and port if needed")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Lidarr Server", HelpText = "Lidarr server URL, including http(s):// and port if needed")]

View File

@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Applications.Lidarr
List<LidarrIndexer> GetIndexerSchema(LidarrSettings settings);
void RemoveIndexer(int indexerId, LidarrSettings settings);
LidarrIndexer UpdateIndexer(LidarrIndexer indexer, LidarrSettings settings);
ValidationFailure TestConnection(LidarrIndexer indexer, LidarrSettings settings);
ValidationFailure Test(LidarrSettings settings);
}
public class LidarrV1Proxy : ILidarrV1Proxy
@@ -91,15 +91,11 @@ namespace NzbDrone.Core.Applications.Lidarr
return Execute<LidarrIndexer>(request);
}
public ValidationFailure TestConnection(LidarrIndexer indexer, LidarrSettings settings)
public ValidationFailure Test(LidarrSettings settings)
{
var request = BuildRequest(settings, $"/api/v1/indexer/test", HttpMethod.POST);
request.SetContent(indexer.ToJson());
try
{
Execute<LidarrIndexer>(request);
GetStatus(settings);
}
catch (HttpException ex)
{
@@ -109,14 +105,8 @@ namespace NzbDrone.Core.Applications.Lidarr
return new ValidationFailure("ApiKey", "API Key is invalid");
}
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Error(ex, "Prowlarr URL is invalid");
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Lidarr cannot connect to Prowlarr");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
return new ValidationFailure("ApiKey", "Unable to send test message");
}
catch (Exception ex)
{

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json.Linq;
using NLog;
@@ -32,25 +31,7 @@ namespace NzbDrone.Core.Applications.Radarr
{
var failures = new List<ValidationFailure>();
var testIndexer = new IndexerDefinition
{
Id = 0,
Name = "Test",
Protocol = DownloadProtocol.Usenet,
Capabilities = new IndexerCapabilities()
};
testIndexer.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies);
try
{
failures.AddIfNotNull(_radarrV3Proxy.TestConnection(BuildRadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Radarr"));
}
failures.AddIfNotNull(_radarrV3Proxy.Test(Settings));
return new ValidationResult(failures);
}
@@ -165,7 +146,7 @@ namespace NzbDrone.Core.Applications.Radarr
Fields = schema.Fields,
};
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{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()));

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Applications.Radarr
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
other.EnableInteractiveSearch == EnableAutomaticSearch &&
other.Name == Name &&
other.Implementation == Implementation &&
other.Priority == Priority &&

View File

@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Applications.Radarr
public IEnumerable<int> SyncCategories { get; set; }
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Radarr sees it, including http(s)://, port, and urlbase if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Radarr sees it, including http(s):// and port if needed")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Radarr Server", HelpText = "Radarr server URL, including http(s):// and port if needed")]

View File

@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Applications.Radarr
List<RadarrIndexer> GetIndexerSchema(RadarrSettings settings);
void RemoveIndexer(int indexerId, RadarrSettings settings);
RadarrIndexer UpdateIndexer(RadarrIndexer indexer, RadarrSettings settings);
ValidationFailure TestConnection(RadarrIndexer indexer, RadarrSettings settings);
ValidationFailure Test(RadarrSettings settings);
}
public class RadarrV3Proxy : IRadarrV3Proxy
@@ -91,15 +91,11 @@ namespace NzbDrone.Core.Applications.Radarr
return Execute<RadarrIndexer>(request);
}
public ValidationFailure TestConnection(RadarrIndexer indexer, RadarrSettings settings)
public ValidationFailure Test(RadarrSettings settings)
{
var request = BuildRequest(settings, $"/api/v3/indexer/test", HttpMethod.POST);
request.SetContent(indexer.ToJson());
try
{
Execute<RadarrIndexer>(request);
GetStatus(settings);
}
catch (HttpException ex)
{
@@ -109,14 +105,8 @@ namespace NzbDrone.Core.Applications.Radarr
return new ValidationFailure("ApiKey", "API Key is invalid");
}
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Error(ex, "Prowlarr URL is invalid");
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Radarr cannot connect to Prowlarr");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
return new ValidationFailure("ApiKey", "Unable to send test message");
}
catch (Exception ex)
{

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json.Linq;
using NLog;
@@ -32,25 +31,7 @@ namespace NzbDrone.Core.Applications.Readarr
{
var failures = new List<ValidationFailure>();
var testIndexer = new IndexerDefinition
{
Id = 0,
Name = "Test",
Protocol = DownloadProtocol.Usenet,
Capabilities = new IndexerCapabilities()
};
testIndexer.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.Books);
try
{
failures.AddIfNotNull(_readarrV1Proxy.TestConnection(BuildReadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Readarr"));
}
failures.AddIfNotNull(_readarrV1Proxy.Test(Settings));
return new ValidationResult(failures);
}
@@ -165,7 +146,7 @@ namespace NzbDrone.Core.Applications.Readarr
Fields = schema.Fields,
};
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{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()));

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Applications.Readarr
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
other.EnableInteractiveSearch == EnableAutomaticSearch &&
other.Name == Name &&
other.Implementation == Implementation &&
other.Priority == Priority &&

View File

@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Applications.Readarr
public IEnumerable<int> SyncCategories { get; set; }
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Readarr sees it, including http(s)://, port, and urlbase if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Readarr sees it, including http(s):// and port if needed")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Readarr Server", HelpText = "Readarr server URL, including http(s):// and port if needed")]

View File

@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Applications.Readarr
List<ReadarrIndexer> GetIndexerSchema(ReadarrSettings settings);
void RemoveIndexer(int indexerId, ReadarrSettings settings);
ReadarrIndexer UpdateIndexer(ReadarrIndexer indexer, ReadarrSettings settings);
ValidationFailure TestConnection(ReadarrIndexer indexer, ReadarrSettings settings);
ValidationFailure Test(ReadarrSettings settings);
}
public class ReadarrV1Proxy : IReadarrV1Proxy
@@ -91,15 +91,11 @@ namespace NzbDrone.Core.Applications.Readarr
return Execute<ReadarrIndexer>(request);
}
public ValidationFailure TestConnection(ReadarrIndexer indexer, ReadarrSettings settings)
public ValidationFailure Test(ReadarrSettings settings)
{
var request = BuildRequest(settings, $"/api/v1/indexer/test", HttpMethod.POST);
request.SetContent(indexer.ToJson());
try
{
Execute<ReadarrIndexer>(request);
GetStatus(settings);
}
catch (HttpException ex)
{
@@ -109,14 +105,8 @@ namespace NzbDrone.Core.Applications.Readarr
return new ValidationFailure("ApiKey", "API Key is invalid");
}
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Error(ex, "Prowlarr URL is invalid");
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Readarr cannot connect to Prowlarr");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
return new ValidationFailure("ApiKey", "Unable to send test message");
}
catch (Exception ex)
{

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json.Linq;
using NLog;
@@ -32,25 +31,7 @@ namespace NzbDrone.Core.Applications.Sonarr
{
var failures = new List<ValidationFailure>();
var testIndexer = new IndexerDefinition
{
Id = 0,
Name = "Test",
Protocol = DownloadProtocol.Usenet,
Capabilities = new IndexerCapabilities()
};
testIndexer.Capabilities.Categories.AddCategoryMapping(1, NewznabStandardCategory.TV);
try
{
failures.AddIfNotNull(_sonarrV3Proxy.TestConnection(BuildSonarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Sonarr"));
}
failures.AddIfNotNull(_sonarrV3Proxy.Test(Settings));
return new ValidationResult(failures);
}
@@ -165,7 +146,7 @@ namespace NzbDrone.Core.Applications.Sonarr
Fields = schema.Fields,
};
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{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()));

View File

@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Applications.Sonarr
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
other.EnableInteractiveSearch == EnableAutomaticSearch &&
other.Name == Name &&
other.Implementation == Implementation &&
other.Priority == Priority &&

View File

@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Applications.Sonarr
public IEnumerable<int> SyncCategories { get; set; }
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Sonarr sees it, including http(s)://, port, and urlbase if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Sonarr sees it, including http(s):// and port if needed")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Sonarr Server", HelpText = "Sonarr server URL, including http(s):// and port if needed")]

View File

@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Applications.Sonarr
List<SonarrIndexer> GetIndexerSchema(SonarrSettings settings);
void RemoveIndexer(int indexerId, SonarrSettings settings);
SonarrIndexer UpdateIndexer(SonarrIndexer indexer, SonarrSettings settings);
ValidationFailure TestConnection(SonarrIndexer indexer, SonarrSettings settings);
ValidationFailure Test(SonarrSettings settings);
}
public class SonarrV3Proxy : ISonarrV3Proxy
@@ -91,15 +91,11 @@ namespace NzbDrone.Core.Applications.Sonarr
return Execute<SonarrIndexer>(request);
}
public ValidationFailure TestConnection(SonarrIndexer indexer, SonarrSettings settings)
public ValidationFailure Test(SonarrSettings settings)
{
var request = BuildRequest(settings, $"/api/v3/indexer/test", HttpMethod.POST);
request.SetContent(indexer.ToJson());
try
{
Execute<SonarrIndexer>(request);
GetStatus(settings);
}
catch (HttpException ex)
{
@@ -109,14 +105,8 @@ namespace NzbDrone.Core.Applications.Sonarr
return new ValidationFailure("ApiKey", "API Key is invalid");
}
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Error(ex, "Prowlarr URL is invalid");
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Sonarr cannot connect to Prowlarr");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
return new ValidationFailure("ApiKey", "Unable to send test message");
}
catch (Exception ex)
{

View File

@@ -1,36 +0,0 @@
using System.Collections.Generic;
using System.Data;
using System.Text.Json;
using Dapper;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Core.Datastore.Converters
{
public class CookieConverter : SqlMapper.TypeHandler<IDictionary<string, string>>
{
protected readonly JsonSerializerOptions SerializerSettings;
public CookieConverter()
{
var serializerSettings = new JsonSerializerOptions
{
AllowTrailingCommas = true,
IgnoreNullValues = true,
PropertyNameCaseInsensitive = true,
WriteIndented = true
};
SerializerSettings = serializerSettings;
}
public override void SetValue(IDbDataParameter parameter, IDictionary<string, string> value)
{
parameter.Value = JsonSerializer.Serialize(value, SerializerSettings);
}
public override IDictionary<string, string> Parse(object value)
{
return JsonSerializer.Deserialize<Dictionary<string, string>>((string)value, SerializerSettings);
}
}
}

View File

@@ -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://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://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://wiki.servarr.com/Prowlarr_FAQ#I_am_getting_an_error_Database_disk_image_is_malformed", e, fileName);
}
catch (Exception e)
{

View File

@@ -100,7 +100,7 @@ namespace NzbDrone.Core.Datastore
SqlMapper.RemoveTypeMap(typeof(DateTime));
SqlMapper.AddTypeHandler(new DapperUtcConverter());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<Dictionary<string, string>>());
SqlMapper.AddTypeHandler(new CookieConverter());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<IDictionary<string, string>>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<int>>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<KeyValuePair<string, int>>>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<KeyValuePair<string, int>>());

View File

@@ -31,8 +31,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
IValidateNzbs nzbValidationService,
Logger logger)
: base(httpClient, configService, diskProvider, logger)
: base(httpClient, configService, diskProvider, nzbValidationService, logger)
{
_dsInfoProxy = dsInfoProxy;
_dsTaskProxy = dsTaskProxy;

View File

@@ -20,8 +20,9 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
IValidateNzbs nzbValidationService,
Logger logger)
: base(httpClient, configService, diskProvider, logger)
: base(httpClient, configService, diskProvider, nzbValidationService, logger)
{
_proxy = proxy;
}

View File

@@ -25,8 +25,9 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
IValidateNzbs nzbValidationService,
Logger logger)
: base(httpClient, configService, diskProvider, logger)
: base(httpClient, configService, diskProvider, nzbValidationService, logger)
{
_proxy = proxy;
}

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
@@ -16,20 +14,24 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
{
public class Pneumatic : DownloadClientBase<PneumaticSettings>
{
public Pneumatic(IConfigService configService,
private readonly IHttpClient _httpClient;
public Pneumatic(IHttpClient httpClient,
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 async Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer)
public override string Download(ReleaseInfo release, bool redirect)
{
var url = new Uri(release.DownloadUrl);
var url = release.DownloadUrl;
var title = release.Title;
title = StringUtil.CleanFileName(title);
@@ -38,10 +40,7 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
var nzbFile = Path.Combine(Settings.NzbFolder, title + ".nzb");
_logger.Debug("Downloading NZB from: {0} to: {1}", url, nzbFile);
var nzbData = await indexer.Download(url);
File.WriteAllBytes(nzbFile, nzbData);
_httpClient.DownloadFile(url, nzbFile);
_logger.Debug("NZB Download succeeded, saved to: {0}", nzbFile);

View File

@@ -22,8 +22,9 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
IValidateNzbs nzbValidationService,
Logger logger)
: base(httpClient, configService, diskProvider, logger)
: base(httpClient, configService, diskProvider, nzbValidationService, logger)
{
_proxy = proxy;
}

View File

@@ -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("Downloading torrent failed");
throw new ReleaseDownloadException(release, "Downloading torrent failed");
}
return hash;

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
@@ -55,7 +54,7 @@ namespace NzbDrone.Core.Download
get;
}
public abstract Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer);
public abstract string Download(ReleaseInfo release, bool redirect);
public ValidationResult Test()
{

View File

@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Download
{
public interface IDownloadService
{
Task SendReportToClient(ReleaseInfo release, string source, string host, bool redirect);
void 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 async Task SendReportToClient(ReleaseInfo release, string source, string host, bool redirect)
public void SendReportToClient(ReleaseInfo release, string source, string host, bool redirect)
{
var downloadTitle = release.Title;
var downloadClient = _downloadClientProvider.GetDownloadClient(release.DownloadProtocol);
@@ -69,12 +69,10 @@ namespace NzbDrone.Core.Download
_rateLimitService.WaitAndPulse(url.Host, TimeSpan.FromSeconds(2));
}
var indexer = _indexerFactory.GetInstance(_indexerFactory.Get(release.IndexerId));
string downloadClientId;
try
{
downloadClientId = await downloadClient.Download(release, redirect, indexer);
downloadClientId = downloadClient.Download(release, redirect);
_downloadClientStatusService.RecordSuccess(downloadClient.Definition.Id);
_indexerStatusService.RecordSuccess(release.IndexerId);
}

View File

@@ -1,4 +1,3 @@
using System.Threading.Tasks;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
@@ -8,6 +7,6 @@ namespace NzbDrone.Core.Download
public interface IDownloadClient : IProvider
{
DownloadProtocol Protocol { get; }
Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer);
string Download(ReleaseInfo release, bool redirect);
}
}

View File

@@ -8,12 +8,12 @@ namespace NzbDrone.Core.Download
{
public interface IValidateNzbs
{
void Validate(byte[] fileContent);
void Validate(string filename, byte[] fileContent);
}
public class NzbValidationService : IValidateNzbs
{
public void Validate(byte[] fileContent)
public void Validate(string filename, 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");
throw new InvalidNzbException("Invalid NZB: No Root element [{0}]", filename);
}
// 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}'", nzb.Name.LocalName);
throw new InvalidNzbException("Invalid NZB: Unexpected root element. Expected 'nzb' found '{0}' [{1}]", nzb.Name.LocalName, filename);
}
var ns = nzb.Name.Namespace;
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Download
if (files.Empty())
{
throw new InvalidNzbException("Invalid NZB: No files");
throw new InvalidNzbException("Invalid NZB: No files [{0}]", filename);
}
}
}

View File

@@ -1,7 +1,5 @@
using System;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using MonoTorrent;
using NLog;
using NzbDrone.Common.Disk;
@@ -41,7 +39,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 async Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer)
public override string Download(ReleaseInfo release, bool redirect)
{
var torrentInfo = release as TorrentInfo;
@@ -68,7 +66,7 @@ namespace NzbDrone.Core.Download
{
try
{
return await DownloadFromWebUrl(release, indexer, torrentUrl);
return DownloadFromWebUrl(release, torrentUrl);
}
catch (Exception ex)
{
@@ -89,7 +87,7 @@ namespace NzbDrone.Core.Download
}
catch (NotSupportedException ex)
{
throw new ReleaseDownloadException("Magnet not supported by download client. ({0})", ex.Message);
throw new ReleaseDownloadException(release, "Magnet not supported by download client. ({0})", ex.Message);
}
}
}
@@ -105,7 +103,7 @@ namespace NzbDrone.Core.Download
{
if (torrentUrl.IsNullOrWhiteSpace())
{
throw new ReleaseDownloadException("Magnet not supported by download client. ({0})", ex.Message);
throw new ReleaseDownloadException(release, "Magnet not supported by download client. ({0})", ex.Message);
}
_logger.Debug("Magnet not supported by download client, trying torrent. ({0})", ex.Message);
@@ -114,31 +112,74 @@ namespace NzbDrone.Core.Download
if (torrentUrl.IsNotNullOrWhiteSpace())
{
return await DownloadFromWebUrl(release, indexer, torrentUrl);
return DownloadFromWebUrl(release, torrentUrl);
}
}
return null;
}
private async Task<string> DownloadFromWebUrl(ReleaseInfo release, IIndexer indexer, string torrentUrl)
private string DownloadFromWebUrl(ReleaseInfo release, string torrentUrl)
{
byte[] torrentFile = null;
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)
try
{
var magnetUrl = Encoding.UTF8.GetString(torrentFile);
return DownloadFromMagnetUrl(release, magnetUrl);
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 filename = string.Format("{0}.torrent", StringUtil.CleanFileName(release.Title));

View File

@@ -1,9 +1,9 @@
using System;
using System.Threading.Tasks;
using System.Net;
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,14 +15,17 @@ 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;
@@ -30,9 +33,9 @@ namespace NzbDrone.Core.Download
protected abstract string AddFromNzbFile(ReleaseInfo release, string filename, byte[] fileContents);
protected abstract string AddFromLink(ReleaseInfo release);
public override async Task<string> Download(ReleaseInfo release, bool redirect, IIndexer indexer)
public override string Download(ReleaseInfo release, bool redirect)
{
var url = new Uri(release.DownloadUrl);
var url = release.DownloadUrl;
if (redirect)
{
@@ -43,7 +46,40 @@ namespace NzbDrone.Core.Download
byte[] nzbData;
nzbData = await indexer.Download(url);
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);
_logger.Info("Adding report [{0}] to the queue.", release.Title);
return AddFromNzbFile(release, filename, nzbData);

View File

@@ -1,33 +1,28 @@
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(message, args)
: base(release, message, args)
{
Release = release;
}
public DownloadClientRejectedReleaseException(ReleaseInfo release, string message)
: base(message)
: base(release, message)
{
Release = release;
}
public DownloadClientRejectedReleaseException(ReleaseInfo release, string message, Exception innerException, params object[] args)
: base(message, innerException, args)
: base(release, message, innerException, args)
{
Release = release;
}
public DownloadClientRejectedReleaseException(ReleaseInfo release, string message, Exception innerException)
: base(message, innerException)
: base(release, message, innerException)
{
Release = release;
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using NzbDrone.Common.Exceptions;
using NzbDrone.Core.Parser.Model;
@@ -6,24 +6,30 @@ namespace NzbDrone.Core.Exceptions
{
public class ReleaseDownloadException : NzbDroneException
{
public ReleaseDownloadException(string message, params object[] args)
public ReleaseInfo Release { get; set; }
public ReleaseDownloadException(ReleaseInfo release, string message, params object[] args)
: base(message, args)
{
Release = release;
}
public ReleaseDownloadException(string message)
public ReleaseDownloadException(ReleaseInfo release, string message)
: base(message)
{
Release = release;
}
public ReleaseDownloadException(string message, Exception innerException, params object[] args)
public ReleaseDownloadException(ReleaseInfo release, string message, Exception innerException, params object[] args)
: base(message, innerException, args)
{
Release = release;
}
public ReleaseDownloadException(string message, Exception innerException)
public ReleaseDownloadException(ReleaseInfo release, string message, Exception innerException)
: base(message, innerException)
{
Release = release;
}
}
}

View File

@@ -1,27 +1,27 @@
using System;
using System;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Exceptions
{
public class ReleaseUnavailableException : ReleaseDownloadException
{
public ReleaseUnavailableException(string message, params object[] args)
: base(message, args)
public ReleaseUnavailableException(ReleaseInfo release, string message, params object[] args)
: base(release, message, args)
{
}
public ReleaseUnavailableException(string message)
: base(message)
public ReleaseUnavailableException(ReleaseInfo release, string message)
: base(release, message)
{
}
public ReleaseUnavailableException(string message, Exception innerException, params object[] args)
: base(message, innerException, args)
public ReleaseUnavailableException(ReleaseInfo release, string message, Exception innerException, params object[] args)
: base(release, message, innerException, args)
{
}
public ReleaseUnavailableException(string message, Exception innerException)
: base(message, innerException)
public ReleaseUnavailableException(ReleaseInfo release, string message, Exception innerException)
: base(release, message, innerException)
{
}
}

View File

@@ -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");
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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());

View File

@@ -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;

View File

@@ -23,7 +23,12 @@ namespace NzbDrone.Core.HealthCheck.Checks
if (!Enum.GetNames(typeof(ReleaseBranches)).Any(x => x.ToLower() == currentBranch))
{
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("ReleaseBranchCheckOfficialBranchMessage"), _configFileService.Branch), "#branch-is-not-a-valid-release-branch");
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());
@@ -31,7 +36,6 @@ namespace NzbDrone.Core.HealthCheck.Checks
public enum ReleaseBranches
{
Develop,
Nightly
}
}

View File

@@ -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");
}
}

View File

@@ -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://wiki.servarr.com/Prowlarr_System#") + new HttpUri(fragment);
}
}

View File

@@ -119,14 +119,14 @@ namespace NzbDrone.Core.History
if (message.Query is MovieSearchCriteria)
{
history.Data.Add("ImdbId", ((MovieSearchCriteria)message.Query).FullImdbId ?? string.Empty);
history.Data.Add("ImdbId", ((MovieSearchCriteria)message.Query).ImdbId ?? 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).FullImdbId ?? string.Empty);
history.Data.Add("ImdbId", ((TvSearchCriteria)message.Query).ImdbId ?? 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);

View File

@@ -1,5 +1,4 @@
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.IndexerSearch.Definitions
{
@@ -21,7 +20,5 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
return false;
}
}
public string FullImdbId => ParseUtil.GetFullImdbId(ImdbId);
}
}

View File

@@ -19,8 +19,6 @@ 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

View File

@@ -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.Categories == null ? null : from c in r.Categories select new XElement("category", c.Id),
r.Category == null ? null : from c in r.Category 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.Categories == null ? null : from c in r.Categories select GetNabElement("category", c.Id, protocol),
r.Category == null ? null : from c in r.Category 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),

View File

@@ -4,7 +4,6 @@ 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;

View File

@@ -6,7 +6,6 @@ using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Cardigann;
using NzbDrone.Core.Messaging.Commands;
@@ -25,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>() { "aither", "animeworld", "blutopia", "beyond-hd", "beyond-hd-oneurl", "hdbits", "shareisland" };
private readonly List<string> _defintionBlacklist = new List<string>() { "blutopia", "beyond-hd", "beyond-hd-oneurl", "hdbits" };
private readonly IHttpClient _httpClient;
private readonly IAppFolderInfo _appFolderInfo;
@@ -60,40 +59,6 @@ namespace NzbDrone.Core.IndexerVersions
var request = new HttpRequest($"https://indexers.prowlarr.com/master/{DEFINITION_VERSION}");
var response = _httpClient.Get<List<CardigannMetaDefinition>>(request);
indexerList = response.Resource.Where(i => !_defintionBlacklist.Contains(i.File)).ToList();
var definitionFolder = Path.Combine(_appFolderInfo.AppDataFolder, "Definitions", "Custom");
var directoryInfo = new DirectoryInfo(definitionFolder);
if (directoryInfo.Exists)
{
var files = directoryInfo.GetFiles($"*.yml");
foreach (var file in files)
{
_logger.Debug("Loading Custom Cardigann definition " + file.FullName);
try
{
var definitionString = File.ReadAllText(file.FullName);
var definition = _deserializer.Deserialize<CardigannMetaDefinition>(definitionString);
definition.File = Path.GetFileNameWithoutExtension(file.Name);
if (indexerList.Any(i => i.File == definition.File || i.Name == definition.Name))
{
_logger.Warn("Custom Cardigann definition {0} does not have unique file name or Indexer name", file.FullName);
continue;
}
indexerList.Add(definition);
}
catch (Exception e)
{
_logger.Error($"Error while parsing custom Cardigann definition {file.FullName}\n{e}");
}
}
}
}
catch
{
@@ -124,8 +89,7 @@ namespace NzbDrone.Core.IndexerVersions
{
var req = new HttpRequest($"https://indexers.prowlarr.com/master/{DEFINITION_VERSION}/{id}");
var response = _httpClient.Get(req);
var definition = _deserializer.Deserialize<CardigannDefinition>(response.Content);
return CleanIndexerDefinition(definition);
return _deserializer.Deserialize<CardigannDefinition>(response.Content);
}
private CardigannDefinition LoadIndexerDef(string fileKey)
@@ -143,7 +107,7 @@ namespace NzbDrone.Core.IndexerVersions
if (directoryInfo.Exists)
{
var files = directoryInfo.GetFiles($"{fileKey}.yml", SearchOption.AllDirectories);
var files = directoryInfo.GetFiles($"{fileKey}.yml");
if (files.Any())
{
@@ -154,7 +118,42 @@ namespace NzbDrone.Core.IndexerVersions
var definitionString = File.ReadAllText(file.FullName);
var definition = _deserializer.Deserialize<CardigannDefinition>(definitionString);
return CleanIndexerDefinition(definition);
//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;
}
catch (Exception e)
{
@@ -166,45 +165,6 @@ 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();

View File

@@ -1,59 +0,0 @@
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;
}
}
}

View File

@@ -23,7 +23,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
public class AnimeBytes : TorrentIndexerBase<AnimeBytesSettings>
public class AnimeBytes : HttpIndexerBase<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,
Categories = category,
Category = category,
Description = description,
Size = size,
Seeders = seeders,
@@ -494,10 +494,10 @@ namespace NzbDrone.Core.Indexers.Definitions
Username = "";
}
[FieldDefinition(1, Label = "Passkey", Privacy = PrivacyLevel.Password, Type = FieldType.Password, HelpText = "Site Passkey")]
[FieldDefinition(1, Label = "Passkey", HelpText = "Site Passkey")]
public string Passkey { get; set; }
[FieldDefinition(1, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
[FieldDefinition(1, Label = "Username", HelpText = "Site username")]
public string Username { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -21,7 +21,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
public class AnimeTorrents : TorrentIndexerBase<AnimeTorrentsSettings>
public class AnimeTorrents : HttpIndexerBase<AnimeTorrentsSettings>
{
public override string Name => "AnimeTorrents";
@@ -294,7 +294,7 @@ namespace NzbDrone.Core.Indexers.Definitions
rCat = rCat.Substring(rCatIdx + 4);
}
release.Categories = _categories.MapTrackerCatToNewznab(rCat);
release.Category = _categories.MapTrackerCatToNewznab(rCat);
if (row.QuerySelector("img[alt=\"Gold Torrent\"]") != null)
{
@@ -350,10 +350,10 @@ namespace NzbDrone.Core.Indexers.Definitions
Password = "";
}
[FieldDefinition(1, Label = "Username", HelpText = "Site Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
[FieldDefinition(1, Label = "Username", HelpText = "Site username")]
public string Username { get; set; }
[FieldDefinition(2, Label = "Password", HelpText = "Site Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
[FieldDefinition(2, Label = "Password", Type = FieldType.Password, HelpText = "Site password", Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -1,61 +0,0 @@
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;
}
}
}

Some files were not shown because too many files have changed in this diff Show More