mirror of
https://github.com/Radarr/Radarr
synced 2025-10-05 23:42:44 +02:00
Fixed: Backend/Frontend Cleanup
This commit is contained in:
@@ -2,14 +2,14 @@
|
|||||||
# editorconfig.org
|
# editorconfig.org
|
||||||
root = true
|
root = true
|
||||||
|
|
||||||
[*.{cs,html,js,hbs}]
|
[*.{cs}]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
[*.less]
|
[*.{js,html,js,hbs,less,css}]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
@@ -7,21 +7,7 @@ Setup guides, FAQ, the more information we have on the wiki the better.
|
|||||||
|
|
||||||
## Development ##
|
## Development ##
|
||||||
|
|
||||||
### Tools required ###
|
See the readme for information on setting up your development environment.
|
||||||
- Visual Studio 2015
|
|
||||||
- HTML/Javascript editor of choice (Sublime Text/Webstorm/Atom/etc)
|
|
||||||
- npm (node package manager)
|
|
||||||
- git
|
|
||||||
|
|
||||||
### Getting started ###
|
|
||||||
|
|
||||||
1. Fork Radarr
|
|
||||||
2. Clone (develop branch) *you may need pull in submodules separately if you client doesn't clone them automatically (CurlSharp)*
|
|
||||||
3. Run `npm install`
|
|
||||||
4. Run `npm start` - Used to compile the UI components and copy them.
|
|
||||||
Leave this window open.
|
|
||||||
If you have gulp globally installed you can use `gulp watch` instead
|
|
||||||
5. Compile in Visual Studio
|
|
||||||
|
|
||||||
### Contributing Code ###
|
### Contributing Code ###
|
||||||
- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Radarr/Radarr/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first)
|
- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Radarr/Radarr/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first)
|
||||||
|
@@ -107,14 +107,15 @@ See the [Roadmap blogpost](https://blog.radarr.video/development/update/2018/11/
|
|||||||
* [Visual Studio Community 2017](https://www.visualstudio.com/vs/community/) or [Rider](http://www.jetbrains.com/rider/)
|
* [Visual Studio Community 2017](https://www.visualstudio.com/vs/community/) or [Rider](http://www.jetbrains.com/rider/)
|
||||||
* [Git](https://git-scm.com/downloads)
|
* [Git](https://git-scm.com/downloads)
|
||||||
* [Node.js](https://nodejs.org/en/download/)
|
* [Node.js](https://nodejs.org/en/download/)
|
||||||
|
* [Yarn](https://yarnpkg.com/)
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
* Make sure all the required software mentioned above are installed
|
* Make sure all the required software mentioned above are installed
|
||||||
* Clone the repository into your development machine ([*info*](https://help.github.com/desktop/guides/contributing/working-with-your-remote-repository-on-github-or-github-enterprise))
|
* Clone the repository into your development machine ([*info*](https://help.github.com/desktop/guides/contributing/working-with-your-remote-repository-on-github-or-github-enterprise))
|
||||||
* Grab the submodules `git submodule init && git submodule update`
|
* Grab the submodules `git submodule init && git submodule update`
|
||||||
* Install the required Node Packages `npm install`
|
* Install the required Node Packages `yarn install`
|
||||||
* Start gulp to monitor your dev environment for any changes that need post processing using `npm start` command.
|
* Start gulp to monitor your dev environment for any changes that need post processing using `yarn start` command.
|
||||||
|
|
||||||
> **Notice**
|
> **Notice**
|
||||||
> Gulp must be running at all times while you are working with Radarr client source files.
|
> Gulp must be running at all times while you are working with Radarr client source files.
|
||||||
@@ -127,7 +128,7 @@ See the [Roadmap blogpost](https://blog.radarr.video/development/update/2018/11/
|
|||||||
|
|
||||||
### Development
|
### Development
|
||||||
|
|
||||||
* Open `NzbDrone.sln` in Visual Studio 2017 or run the build.sh script, if Mono is installed. Alternatively you can use Jetbrains Rider, since it works on all Platforms.
|
* Open `Radarr.sln` in Visual Studio 2017 or run the build.sh script, if Mono is installed. Alternatively you can use Jetbrains Rider, since it works on all Platforms.
|
||||||
* Make sure `NzbDrone.Console` is set as the startup project
|
* Make sure `NzbDrone.Console` is set as the startup project
|
||||||
* Run `build.sh` before running
|
* Run `build.sh` before running
|
||||||
|
|
||||||
@@ -158,4 +159,4 @@ Thank you to [<img src="/Logo/jetbrains.svg" alt="JetBrains" width="32"> JetBrai
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
||||||
* Copyright 2010-2018
|
* Copyright 2010-2019
|
||||||
|
@@ -163,7 +163,7 @@ function HistoryDetails(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventType === 'episodeFileDeleted') {
|
if (eventType === 'movieFileDeleted') {
|
||||||
const {
|
const {
|
||||||
reason
|
reason
|
||||||
} = data;
|
} = data;
|
||||||
@@ -199,7 +199,7 @@ function HistoryDetails(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventType === 'episodeFileRenamed') {
|
if (eventType === 'movieFileRenamed') {
|
||||||
const {
|
const {
|
||||||
sourcePath,
|
sourcePath,
|
||||||
sourceRelativePath,
|
sourceRelativePath,
|
||||||
|
@@ -18,11 +18,11 @@ function getHeaderTitle(eventType) {
|
|||||||
case 'downloadFailed':
|
case 'downloadFailed':
|
||||||
return 'Download Failed';
|
return 'Download Failed';
|
||||||
case 'downloadFolderImported':
|
case 'downloadFolderImported':
|
||||||
return 'Episode Imported';
|
return 'Movie Imported';
|
||||||
case 'episodeFileDeleted':
|
case 'movieFileDeleted':
|
||||||
return 'Episode File Deleted';
|
return 'Movie File Deleted';
|
||||||
case 'episodeFileRenamed':
|
case 'movieFileRenamed':
|
||||||
return 'Episode File Renamed';
|
return 'Movie File Renamed';
|
||||||
default:
|
default:
|
||||||
return 'Unknown';
|
return 'Unknown';
|
||||||
}
|
}
|
||||||
|
@@ -71,6 +71,7 @@ class QueueRow extends Component {
|
|||||||
quality,
|
quality,
|
||||||
protocol,
|
protocol,
|
||||||
indexer,
|
indexer,
|
||||||
|
outputPath,
|
||||||
downloadClient,
|
downloadClient,
|
||||||
estimatedCompletionTime,
|
estimatedCompletionTime,
|
||||||
timeleft,
|
timeleft,
|
||||||
@@ -195,6 +196,14 @@ class QueueRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'outputPath') {
|
||||||
|
return (
|
||||||
|
<TableRowCell key={name}>
|
||||||
|
{outputPath}
|
||||||
|
</TableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'estimatedCompletionTime') {
|
if (name === 'estimatedCompletionTime') {
|
||||||
return (
|
return (
|
||||||
<TimeleftCell
|
<TimeleftCell
|
||||||
@@ -297,6 +306,7 @@ QueueRow.propTypes = {
|
|||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
protocol: PropTypes.string.isRequired,
|
protocol: PropTypes.string.isRequired,
|
||||||
indexer: PropTypes.string,
|
indexer: PropTypes.string,
|
||||||
|
outputPath: PropTypes.string,
|
||||||
downloadClient: PropTypes.string,
|
downloadClient: PropTypes.string,
|
||||||
estimatedCompletionTime: PropTypes.string,
|
estimatedCompletionTime: PropTypes.string,
|
||||||
timeleft: PropTypes.string,
|
timeleft: PropTypes.string,
|
||||||
|
@@ -12,8 +12,12 @@ function createMapStateToProps() {
|
|||||||
(state) => state.queue.options.includeUnknownMovieItems,
|
(state) => state.queue.options.includeUnknownMovieItems,
|
||||||
(app, status, includeUnknownMovieItems) => {
|
(app, status, includeUnknownMovieItems) => {
|
||||||
const {
|
const {
|
||||||
|
errors,
|
||||||
|
warnings,
|
||||||
|
unknownErrors,
|
||||||
|
unknownWarnings,
|
||||||
count,
|
count,
|
||||||
unknownCount
|
totalCount
|
||||||
} = status.item;
|
} = status.item;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -21,7 +25,9 @@ function createMapStateToProps() {
|
|||||||
isReconnecting: app.isReconnecting,
|
isReconnecting: app.isReconnecting,
|
||||||
isPopulated: status.isPopulated,
|
isPopulated: status.isPopulated,
|
||||||
...status.item,
|
...status.item,
|
||||||
count: includeUnknownMovieItems ? count : count - unknownCount
|
count: includeUnknownMovieItems ? totalCount : count,
|
||||||
|
errors: includeUnknownMovieItems ? errors || unknownErrors : errors,
|
||||||
|
warnings: includeUnknownMovieItems ? warnings || unknownWarnings : warnings
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@@ -13,10 +13,10 @@ function createMapStateToProps() {
|
|||||||
createMovieFileSelector(),
|
createMovieFileSelector(),
|
||||||
createQueueItemSelector(),
|
createQueueItemSelector(),
|
||||||
createUISettingsSelector(),
|
createUISettingsSelector(),
|
||||||
(calendarOptions, series, episodeFile, queueItem, uiSettings) => {
|
(calendarOptions, movie, movieFile, queueItem, uiSettings) => {
|
||||||
return {
|
return {
|
||||||
series,
|
movie,
|
||||||
episodeFile,
|
movieFile,
|
||||||
queueItem,
|
queueItem,
|
||||||
...calendarOptions,
|
...calendarOptions,
|
||||||
timeFormat: uiSettings.timeFormat,
|
timeFormat: uiSettings.timeFormat,
|
||||||
|
@@ -6,11 +6,11 @@ import CalendarEventGroup from './CalendarEventGroup';
|
|||||||
|
|
||||||
function createIsDownloadingSelector() {
|
function createIsDownloadingSelector() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { episodeIds }) => episodeIds,
|
(state, { movieIds }) => movieIds,
|
||||||
(state) => state.queue.details,
|
(state) => state.queue.details,
|
||||||
(episodeIds, details) => {
|
(movieIds, details) => {
|
||||||
return details.items.some((item) => {
|
return details.items.some((item) => {
|
||||||
return episodeIds.includes(item.episode.id);
|
return item.movie && movieIds.includes(item.movie.id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -22,9 +22,9 @@ function createMapStateToProps() {
|
|||||||
createMovieSelector(),
|
createMovieSelector(),
|
||||||
createIsDownloadingSelector(),
|
createIsDownloadingSelector(),
|
||||||
createUISettingsSelector(),
|
createUISettingsSelector(),
|
||||||
(calendarOptions, series, isDownloading, uiSettings) => {
|
(calendarOptions, movie, isDownloading, uiSettings) => {
|
||||||
return {
|
return {
|
||||||
series,
|
movie,
|
||||||
isDownloading,
|
isDownloading,
|
||||||
...calendarOptions,
|
...calendarOptions,
|
||||||
timeFormat: uiSettings.timeFormat,
|
timeFormat: uiSettings.timeFormat,
|
||||||
|
@@ -83,6 +83,7 @@ class CalendarHeader extends Component {
|
|||||||
end,
|
end,
|
||||||
longDateFormat,
|
longDateFormat,
|
||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
|
collapseViewButtons,
|
||||||
onTodayPress,
|
onTodayPress,
|
||||||
onPreviousPress,
|
onPreviousPress,
|
||||||
onNextPress
|
onNextPress
|
||||||
@@ -145,7 +146,7 @@ class CalendarHeader extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
isSmallScreen ?
|
collapseViewButtons ?
|
||||||
<Menu
|
<Menu
|
||||||
className={styles.viewMenu}
|
className={styles.viewMenu}
|
||||||
alignMenu={align.RIGHT}
|
alignMenu={align.RIGHT}
|
||||||
@@ -158,6 +159,18 @@ class CalendarHeader extends Component {
|
|||||||
</MenuButton>
|
</MenuButton>
|
||||||
|
|
||||||
<MenuContent>
|
<MenuContent>
|
||||||
|
{
|
||||||
|
isSmallScreen ?
|
||||||
|
null :
|
||||||
|
<ViewMenuItem
|
||||||
|
name={calendarViews.MONTH}
|
||||||
|
selectedView={view}
|
||||||
|
onPress={this.onViewChange}
|
||||||
|
>
|
||||||
|
Month
|
||||||
|
</ViewMenuItem>
|
||||||
|
}
|
||||||
|
|
||||||
<ViewMenuItem
|
<ViewMenuItem
|
||||||
name={calendarViews.WEEK}
|
name={calendarViews.WEEK}
|
||||||
selectedView={view}
|
selectedView={view}
|
||||||
@@ -243,6 +256,7 @@ CalendarHeader.propTypes = {
|
|||||||
end: PropTypes.string.isRequired,
|
end: PropTypes.string.isRequired,
|
||||||
view: PropTypes.oneOf(calendarViews.all).isRequired,
|
view: PropTypes.oneOf(calendarViews.all).isRequired,
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
|
collapseViewButtons: PropTypes.bool.isRequired,
|
||||||
longDateFormat: PropTypes.string.isRequired,
|
longDateFormat: PropTypes.string.isRequired,
|
||||||
onViewChange: PropTypes.func.isRequired,
|
onViewChange: PropTypes.func.isRequired,
|
||||||
onTodayPress: PropTypes.func.isRequired,
|
onTodayPress: PropTypes.func.isRequired,
|
||||||
|
@@ -23,6 +23,7 @@ function createMapStateToProps() {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
result.isSmallScreen = dimensions.isSmallScreen;
|
result.isSmallScreen = dimensions.isSmallScreen;
|
||||||
|
result.collapseViewButtons = dimensions.isLargeScreen;
|
||||||
result.longDateFormat = uiSettings.longDateFormat;
|
result.longDateFormat = uiSettings.longDateFormat;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@@ -78,8 +78,8 @@
|
|||||||
color: $disabledColor;
|
color: $disabledColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.addNewSeriesSuggestion {
|
.addNewMovieSuggestion {
|
||||||
padding: 0 3px;
|
padding: 5px 3px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -76,7 +76,7 @@ class MovieSearchInput extends Component {
|
|||||||
renderSuggestion(item, { query }) {
|
renderSuggestion(item, { query }) {
|
||||||
if (item.type === ADD_NEW_TYPE) {
|
if (item.type === ADD_NEW_TYPE) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.addNewSeriesSuggestion}>
|
<div className={styles.addNewMovieSuggestion}>
|
||||||
Search for {query}
|
Search for {query}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -1,10 +1,40 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import createDeepEqualSelector from 'Store/Selectors/createDeepEqualSelector';
|
||||||
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import MovieIndexFooter from './MovieIndexFooter';
|
import MovieIndexFooter from './MovieIndexFooter';
|
||||||
|
|
||||||
|
function createUnoptimizedSelector() {
|
||||||
|
return createSelector(
|
||||||
|
createClientSideCollectionSelector('movies', 'movieIndex'),
|
||||||
|
(movies) => {
|
||||||
|
return movies.items.map((s) => {
|
||||||
|
const {
|
||||||
|
monitored,
|
||||||
|
status,
|
||||||
|
statistics
|
||||||
|
} = s;
|
||||||
|
|
||||||
|
return {
|
||||||
|
monitored,
|
||||||
|
status,
|
||||||
|
statistics
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMoviesSelector() {
|
||||||
|
return createDeepEqualSelector(
|
||||||
|
createUnoptimizedSelector(),
|
||||||
|
(movies) => movies
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.movies.items,
|
createMoviesSelector(),
|
||||||
(movies) => {
|
(movies) => {
|
||||||
return {
|
return {
|
||||||
movies
|
movies
|
||||||
|
@@ -32,7 +32,7 @@ function BackupSettings(props) {
|
|||||||
<FormLabel>Folder</FormLabel>
|
<FormLabel>Folder</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.TEXT}
|
type={inputTypes.PATH}
|
||||||
name="backupFolder"
|
name="backupFolder"
|
||||||
helpText="Relative paths will be under Radarr's AppData directory"
|
helpText="Relative paths will be under Radarr's AppData directory"
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
|
@@ -83,10 +83,6 @@ class NamingModal extends Component {
|
|||||||
value,
|
value,
|
||||||
isOpen,
|
isOpen,
|
||||||
advancedSettings,
|
advancedSettings,
|
||||||
season,
|
|
||||||
episode,
|
|
||||||
daily,
|
|
||||||
anime,
|
|
||||||
additional,
|
additional,
|
||||||
onInputChange,
|
onInputChange,
|
||||||
onModalClose
|
onModalClose
|
||||||
@@ -112,58 +108,21 @@ class NamingModal extends Component {
|
|||||||
|
|
||||||
const fileNameTokens = [
|
const fileNameTokens = [
|
||||||
{
|
{
|
||||||
token: '{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}',
|
token: '{Movie Title} - {Quality Full}',
|
||||||
example: 'Series Title (2010) - S01E01 - Episode Title HDTV-720p Proper'
|
example: 'Movie Title (2010) - HDTV-720p Proper'
|
||||||
},
|
|
||||||
{
|
|
||||||
token: '{Series Title} - {season:0}x{episode:00} - {Episode Title} {Quality Full}',
|
|
||||||
example: 'Series Title (2010) - 1x01 - Episode Title HDTV-720p Proper'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
token: '{Series.Title}.S{season:00}E{episode:00}.{EpisodeClean.Title}.{Quality.Full}',
|
|
||||||
example: 'Series.Title.(2010).S01E01.Episode.Title.HDTV-720p'
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const seriesTokens = [
|
const movieTokens = [
|
||||||
{ token: '{Series Title}', example: 'Series Title!' },
|
{ token: '{Movie Title}', example: 'Movie Title!' },
|
||||||
{ token: '{Series CleanTitle}', example: 'Series Title' },
|
{ token: '{Movie CleanTitle}', example: 'Movie Title' },
|
||||||
{ token: '{Series CleanTitleYear}', example: 'Series Title 2010' },
|
{ token: '{Movie TitleThe}', example: 'Movie Title, The' }
|
||||||
{ token: '{Series TitleThe}', example: 'Series Title, The' },
|
|
||||||
{ token: '{Series TitleTheYear}', example: 'Series Title, The (2010)' },
|
|
||||||
{ token: '{Series TitleYear}', example: 'Series Title (2010)' }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const seriesIdTokens = [
|
const movieIdTokens = [
|
||||||
{ token: '{ImdbId}', example: 'tt12345' },
|
{ token: '{ImdbId}', example: 'tt12345' },
|
||||||
{ token: '{TvdbId}', example: '12345' },
|
{ token: '{TmdbId}', example: '123456' }
|
||||||
{ token: '{TvMazeId}', example: '54321' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const seasonTokens = [
|
|
||||||
{ token: '{season:0}', example: '1' },
|
|
||||||
{ token: '{season:00}', example: '01' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const episodeTokens = [
|
|
||||||
{ token: '{episode:0}', example: '1' },
|
|
||||||
{ token: '{episode:00}', example: '01' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const airDateTokens = [
|
|
||||||
{ token: '{Air-Date}', example: '2016-03-20' },
|
|
||||||
{ token: '{Air Date}', example: '2016 03 20' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const absoluteTokens = [
|
|
||||||
{ token: '{absolute:0}', example: '1' },
|
|
||||||
{ token: '{absolute:00}', example: '01' },
|
|
||||||
{ token: '{absolute:000}', example: '001' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const episodeTitleTokens = [
|
|
||||||
{ token: '{Episode Title}', example: 'Episode Title' },
|
|
||||||
{ token: '{Episode CleanTitle}', example: 'Episode Title' }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const qualityTokens = [
|
const qualityTokens = [
|
||||||
@@ -175,8 +134,14 @@ class NamingModal extends Component {
|
|||||||
{ token: '{MediaInfo Simple}', example: 'x264 DTS' },
|
{ token: '{MediaInfo Simple}', example: 'x264 DTS' },
|
||||||
{ token: '{MediaInfo Full}', example: 'x264 DTS [EN+DE]' },
|
{ token: '{MediaInfo Full}', example: 'x264 DTS [EN+DE]' },
|
||||||
{ token: '{MediaInfo VideoCodec}', example: 'x264' },
|
{ token: '{MediaInfo VideoCodec}', example: 'x264' },
|
||||||
{ token: '{MediaInfo AudioFormat}', example: 'DTS' },
|
{ token: '{MediaInfo AudioCodec}', example: 'DTS' },
|
||||||
{ token: '{MediaInfo AudioChannels}', example: '5.1' }
|
{ token: '{MediaInfo AudioChannels}', example: '5.1' },
|
||||||
|
{ token: '{MediaInfo AudioLanguages}', example: '[EN+DE]' },
|
||||||
|
{ token: '{MediaInfo SubtitleLanguages}', example: '[DE]' },
|
||||||
|
|
||||||
|
{ token: '{MediaInfo VideoCodec}', example: 'x264' },
|
||||||
|
{ token: '{MediaInfo VideoBitDepth}', example: '10' },
|
||||||
|
{ token: '{MediaInfo VideoDynamicRange}', example: 'HDR' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const releaseGroupTokens = [
|
const releaseGroupTokens = [
|
||||||
@@ -184,8 +149,8 @@ class NamingModal extends Component {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const originalTokens = [
|
const originalTokens = [
|
||||||
{ token: '{Original Title}', example: 'Series.Title.S01E01.HDTV.x264-EVOLVE' },
|
{ token: '{Original Title}', example: 'Movie.Title.HDTV.x264-EVOLVE' },
|
||||||
{ token: '{Original Filename}', example: 'series.title.s01e01.hdtv.x264-EVOLVE' }
|
{ token: '{Original Filename}', example: 'Movie.title.hdtv.x264-EVOLVE' }
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -244,10 +209,10 @@ class NamingModal extends Component {
|
|||||||
</FieldSet>
|
</FieldSet>
|
||||||
}
|
}
|
||||||
|
|
||||||
<FieldSet legend="Series">
|
<FieldSet legend="Movie">
|
||||||
<div className={styles.groups}>
|
<div className={styles.groups}>
|
||||||
{
|
{
|
||||||
seriesTokens.map(({ token, example }) => {
|
movieTokens.map(({ token, example }) => {
|
||||||
return (
|
return (
|
||||||
<NamingOption
|
<NamingOption
|
||||||
key={token}
|
key={token}
|
||||||
@@ -266,10 +231,10 @@ class NamingModal extends Component {
|
|||||||
</div>
|
</div>
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
|
|
||||||
<FieldSet legend="Series ID">
|
<FieldSet legend="Movie ID">
|
||||||
<div className={styles.groups}>
|
<div className={styles.groups}>
|
||||||
{
|
{
|
||||||
seriesIdTokens.map(({ token, example }) => {
|
movieIdTokens.map(({ token, example }) => {
|
||||||
return (
|
return (
|
||||||
<NamingOption
|
<NamingOption
|
||||||
key={token}
|
key={token}
|
||||||
@@ -288,133 +253,9 @@ class NamingModal extends Component {
|
|||||||
</div>
|
</div>
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
|
|
||||||
{
|
|
||||||
season &&
|
|
||||||
<FieldSet legend="Season">
|
|
||||||
<div className={styles.groups}>
|
|
||||||
{
|
|
||||||
seasonTokens.map(({ token, example }) => {
|
|
||||||
return (
|
|
||||||
<NamingOption
|
|
||||||
key={token}
|
|
||||||
name={name}
|
|
||||||
value={value}
|
|
||||||
token={token}
|
|
||||||
example={example}
|
|
||||||
tokenSeparator={tokenSeparator}
|
|
||||||
tokenCase={tokenCase}
|
|
||||||
onPress={this.onOptionPress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</FieldSet>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
episode &&
|
|
||||||
<div>
|
|
||||||
<FieldSet legend="Episode">
|
|
||||||
<div className={styles.groups}>
|
|
||||||
{
|
|
||||||
episodeTokens.map(({ token, example }) => {
|
|
||||||
return (
|
|
||||||
<NamingOption
|
|
||||||
key={token}
|
|
||||||
name={name}
|
|
||||||
value={value}
|
|
||||||
token={token}
|
|
||||||
example={example}
|
|
||||||
tokenSeparator={tokenSeparator}
|
|
||||||
tokenCase={tokenCase}
|
|
||||||
onPress={this.onOptionPress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</FieldSet>
|
|
||||||
|
|
||||||
{
|
|
||||||
daily &&
|
|
||||||
<FieldSet legend="Air-Date">
|
|
||||||
<div className={styles.groups}>
|
|
||||||
{
|
|
||||||
airDateTokens.map(({ token, example }) => {
|
|
||||||
return (
|
|
||||||
<NamingOption
|
|
||||||
key={token}
|
|
||||||
name={name}
|
|
||||||
value={value}
|
|
||||||
token={token}
|
|
||||||
example={example}
|
|
||||||
tokenSeparator={tokenSeparator}
|
|
||||||
tokenCase={tokenCase}
|
|
||||||
onPress={this.onOptionPress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</FieldSet>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
anime &&
|
|
||||||
<FieldSet legend="Absolute Episode Number">
|
|
||||||
<div className={styles.groups}>
|
|
||||||
{
|
|
||||||
absoluteTokens.map(({ token, example }) => {
|
|
||||||
return (
|
|
||||||
<NamingOption
|
|
||||||
key={token}
|
|
||||||
name={name}
|
|
||||||
value={value}
|
|
||||||
token={token}
|
|
||||||
example={example}
|
|
||||||
tokenSeparator={tokenSeparator}
|
|
||||||
tokenCase={tokenCase}
|
|
||||||
onPress={this.onOptionPress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</FieldSet>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
additional &&
|
additional &&
|
||||||
<div>
|
<div>
|
||||||
<FieldSet legend="Episode Title">
|
|
||||||
<div className={styles.groups}>
|
|
||||||
{
|
|
||||||
episodeTitleTokens.map(({ token, example }) => {
|
|
||||||
return (
|
|
||||||
<NamingOption
|
|
||||||
key={token}
|
|
||||||
name={name}
|
|
||||||
value={value}
|
|
||||||
token={token}
|
|
||||||
example={example}
|
|
||||||
tokenSeparator={tokenSeparator}
|
|
||||||
tokenCase={tokenCase}
|
|
||||||
onPress={this.onOptionPress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</FieldSet>
|
|
||||||
|
|
||||||
<FieldSet legend="Quality">
|
<FieldSet legend="Quality">
|
||||||
<div className={styles.groups}>
|
<div className={styles.groups}>
|
||||||
{
|
{
|
||||||
@@ -529,20 +370,12 @@ NamingModal.propTypes = {
|
|||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
advancedSettings: PropTypes.bool.isRequired,
|
advancedSettings: PropTypes.bool.isRequired,
|
||||||
season: PropTypes.bool.isRequired,
|
|
||||||
episode: PropTypes.bool.isRequired,
|
|
||||||
daily: PropTypes.bool.isRequired,
|
|
||||||
anime: PropTypes.bool.isRequired,
|
|
||||||
additional: PropTypes.bool.isRequired,
|
additional: PropTypes.bool.isRequired,
|
||||||
onInputChange: PropTypes.func.isRequired,
|
onInputChange: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
NamingModal.defaultProps = {
|
NamingModal.defaultProps = {
|
||||||
season: false,
|
|
||||||
episode: false,
|
|
||||||
daily: false,
|
|
||||||
anime: false,
|
|
||||||
additional: false
|
additional: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
.option {
|
.option {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin: 3px;
|
margin: 3px;
|
||||||
border: 1px solid $borderColor;
|
border: 1px solid $borderColor;
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.small {
|
.small {
|
||||||
width: 420px;
|
width: 480px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.large {
|
.large {
|
||||||
@@ -32,6 +32,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.example {
|
.example {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
align-self: stretch;
|
||||||
flex: 0 0 50%;
|
flex: 0 0 50%;
|
||||||
padding: 6px 16px;
|
padding: 6px 16px;
|
||||||
background-color: #ddd;
|
background-color: #ddd;
|
||||||
|
@@ -84,7 +84,7 @@ class Notification extends Component {
|
|||||||
{
|
{
|
||||||
supportsOnDownload && onDownload &&
|
supportsOnDownload && onDownload &&
|
||||||
<Label kind={kinds.SUCCESS}>
|
<Label kind={kinds.SUCCESS}>
|
||||||
On Download
|
On Import
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -19,6 +19,11 @@ class MoreInfo extends Component {
|
|||||||
<Link to="https://radarr.video/">radarr.video</Link>
|
<Link to="https://radarr.video/">radarr.video</Link>
|
||||||
</DescriptionListItemDescription>
|
</DescriptionListItemDescription>
|
||||||
|
|
||||||
|
<DescriptionListItemTitle>Discord</DescriptionListItemTitle>
|
||||||
|
<DescriptionListItemDescription>
|
||||||
|
<Link to="https://discord.gg/AD3UP37">discord.gg/AD3UP37</Link>
|
||||||
|
</DescriptionListItemDescription>
|
||||||
|
|
||||||
<DescriptionListItemTitle>Wiki</DescriptionListItemTitle>
|
<DescriptionListItemTitle>Wiki</DescriptionListItemTitle>
|
||||||
<DescriptionListItemDescription>
|
<DescriptionListItemDescription>
|
||||||
<Link to="https://github.com/Radarr/Radarr/wiki">github.com/Radarr/Radarr/wiki</Link>
|
<Link to="https://github.com/Radarr/Radarr/wiki">github.com/Radarr/Radarr/wiki</Link>
|
||||||
|
Binary file not shown.
Binary file not shown.
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
@@ -24,18 +25,64 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
[TestFixture(typeof(CurlHttpDispatcher))]
|
[TestFixture(typeof(CurlHttpDispatcher))]
|
||||||
public class HttpClientFixture<TDispatcher> : TestBase<HttpClient> where TDispatcher : IHttpDispatcher
|
public class HttpClientFixture<TDispatcher> : TestBase<HttpClient> where TDispatcher : IHttpDispatcher
|
||||||
{
|
{
|
||||||
private static string[] _httpBinHosts = new[] { "eu.httpbin.org", "httpbin.org" };
|
private string[] _httpBinHosts;
|
||||||
private static int _httpBinRandom;
|
private int _httpBinSleep;
|
||||||
|
private int _httpBinRandom;
|
||||||
private string _httpBinHost;
|
private string _httpBinHost;
|
||||||
|
private string _httpBinHost2;
|
||||||
|
|
||||||
|
[OneTimeSetUp]
|
||||||
|
public void FixtureSetUp()
|
||||||
|
{
|
||||||
|
var candidates = new[] { "eu.httpbin.org", /*"httpbin.org",*/ "www.httpbin.org" };
|
||||||
|
// httpbin.org is broken right now, occassionally redirecting to https if it's unavailable.
|
||||||
|
_httpBinHosts = candidates.Where(IsTestSiteAvailable).ToArray();
|
||||||
|
|
||||||
|
TestLogger.Info($"{candidates.Length} TestSites available.");
|
||||||
|
|
||||||
|
_httpBinSleep = _httpBinHosts.Count() < 2 ? 100 : 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsTestSiteAvailable(string site)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var req = WebRequest.Create($"http://{site}/get") as HttpWebRequest;
|
||||||
|
var res = req.GetResponse() as HttpWebResponse;
|
||||||
|
if (res.StatusCode != HttpStatusCode.OK) return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
req = WebRequest.Create($"http://{site}/status/429") as HttpWebRequest;
|
||||||
|
res = req.GetResponse() as HttpWebResponse;
|
||||||
|
}
|
||||||
|
catch (WebException ex)
|
||||||
|
{
|
||||||
|
res = ex.Response as HttpWebResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res == null || res.StatusCode != (HttpStatusCode)429) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp()
|
public void SetUp()
|
||||||
{
|
{
|
||||||
|
if (!_httpBinHosts.Any())
|
||||||
|
{
|
||||||
|
Assert.Inconclusive("No TestSites available");
|
||||||
|
}
|
||||||
|
|
||||||
Mocker.GetMock<IPlatformInfo>().Setup(c => c.Version).Returns(new Version("1.0.0"));
|
Mocker.GetMock<IPlatformInfo>().Setup(c => c.Version).Returns(new Version("1.0.0"));
|
||||||
Mocker.GetMock<IOsInfo>().Setup(c => c.Name).Returns("TestOS");
|
Mocker.GetMock<IOsInfo>().Setup(c => c.Name).Returns("TestOS");
|
||||||
Mocker.GetMock<IOsInfo>().Setup(c => c.Version).Returns("9.0.0");
|
Mocker.GetMock<IOsInfo>().Setup(c => c.Version).Returns("9.0.0");
|
||||||
|
|
||||||
Mocker.SetConstant<IUserAgentBuilder>(Mocker.Resolve<UserAgentBuilder>());
|
Mocker.SetConstant<IUserAgentBuilder>(Mocker.Resolve<UserAgentBuilder>());
|
||||||
|
|
||||||
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
|
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
|
||||||
@@ -51,6 +98,13 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
|
|
||||||
// Roundrobin over the two servers, to reduce the chance of hitting the ratelimiter.
|
// Roundrobin over the two servers, to reduce the chance of hitting the ratelimiter.
|
||||||
_httpBinHost = _httpBinHosts[_httpBinRandom++ % _httpBinHosts.Length];
|
_httpBinHost = _httpBinHosts[_httpBinRandom++ % _httpBinHosts.Length];
|
||||||
|
_httpBinHost2 = _httpBinHosts[_httpBinRandom % _httpBinHosts.Length];
|
||||||
|
}
|
||||||
|
|
||||||
|
[TearDown]
|
||||||
|
public void TearDown()
|
||||||
|
{
|
||||||
|
Thread.Sleep(_httpBinSleep);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -76,11 +130,12 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
[Test]
|
[Test]
|
||||||
public void should_execute_typed_get()
|
public void should_execute_typed_get()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
var request = new HttpRequest($"http://{_httpBinHost}/get?test=1");
|
||||||
|
|
||||||
var response = Subject.Get<HttpBinResource>(request);
|
var response = Subject.Get<HttpBinResource>(request);
|
||||||
|
|
||||||
response.Resource.Url.Should().Be(request.Url.FullUri);
|
response.Resource.Url.EndsWith("/get?test=1");
|
||||||
|
response.Resource.Args.Should().Contain("test", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -163,6 +218,11 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
[Test]
|
[Test]
|
||||||
public void should_follow_redirects_to_https()
|
public void should_follow_redirects_to_https()
|
||||||
{
|
{
|
||||||
|
if (typeof(TDispatcher) == typeof(ManagedHttpDispatcher) && PlatformInfo.IsMono)
|
||||||
|
{
|
||||||
|
Assert.Ignore("Will fail on tls1.2 via managed dispatcher, ignore.");
|
||||||
|
}
|
||||||
|
|
||||||
var request = new HttpRequestBuilder($"http://{_httpBinHost}/redirect-to")
|
var request = new HttpRequestBuilder($"http://{_httpBinHost}/redirect-to")
|
||||||
.AddQueryParam("url", $"https://sonarr.tv/")
|
.AddQueryParam("url", $"https://sonarr.tv/")
|
||||||
.Build();
|
.Build();
|
||||||
@@ -241,7 +301,12 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
|
|
||||||
public void GivenOldCookie()
|
public void GivenOldCookie()
|
||||||
{
|
{
|
||||||
var oldRequest = new HttpRequest("http://eu.httpbin.org/get");
|
if (_httpBinHost == _httpBinHost2)
|
||||||
|
{
|
||||||
|
Assert.Inconclusive("Need both httpbin.org and eu.httpbin.org to run this test.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldRequest = new HttpRequest($"http://{_httpBinHost2}/get");
|
||||||
oldRequest.Cookies["my"] = "cookie";
|
oldRequest.Cookies["my"] = "cookie";
|
||||||
|
|
||||||
var oldClient = new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<ICacheManager>(), Mocker.Resolve<IRateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), Mocker.GetMock<IUserAgentBuilder>().Object, Mocker.Resolve<Logger>());
|
var oldClient = new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<ICacheManager>(), Mocker.Resolve<IRateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), Mocker.GetMock<IUserAgentBuilder>().Object, Mocker.Resolve<Logger>());
|
||||||
@@ -258,7 +323,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
{
|
{
|
||||||
GivenOldCookie();
|
GivenOldCookie();
|
||||||
|
|
||||||
var request = new HttpRequest("http://eu.httpbin.org/get");
|
var request = new HttpRequest($"http://{_httpBinHost2}/get");
|
||||||
|
|
||||||
var response = Subject.Get<HttpBinResource>(request);
|
var response = Subject.Get<HttpBinResource>(request);
|
||||||
|
|
||||||
@@ -274,7 +339,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
{
|
{
|
||||||
GivenOldCookie();
|
GivenOldCookie();
|
||||||
|
|
||||||
var request = new HttpRequest("http://httpbin.org/get");
|
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||||
|
|
||||||
var response = Subject.Get<HttpBinResource>(request);
|
var response = Subject.Get<HttpBinResource>(request);
|
||||||
|
|
||||||
@@ -334,6 +399,28 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
responseCookies.Resource.Cookies.Should().BeEmpty();
|
responseCookies.Resource.Cookies.Should().BeEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_clear_request_cookie()
|
||||||
|
{
|
||||||
|
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies");
|
||||||
|
requestSet.Cookies.Add("my", "cookie");
|
||||||
|
requestSet.AllowAutoRedirect = false;
|
||||||
|
requestSet.StoreRequestCookie = true;
|
||||||
|
requestSet.StoreResponseCookie = false;
|
||||||
|
|
||||||
|
var responseSet = Subject.Get<HttpCookieResource>(requestSet);
|
||||||
|
|
||||||
|
var requestClear = new HttpRequest($"http://{_httpBinHost}/cookies");
|
||||||
|
requestClear.Cookies.Add("my", null);
|
||||||
|
requestClear.AllowAutoRedirect = false;
|
||||||
|
requestClear.StoreRequestCookie = true;
|
||||||
|
requestClear.StoreResponseCookie = false;
|
||||||
|
|
||||||
|
var responseClear = Subject.Get<HttpCookieResource>(requestClear);
|
||||||
|
|
||||||
|
responseClear.Resource.Cookies.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_store_response_cookie()
|
public void should_not_store_response_cookie()
|
||||||
{
|
{
|
||||||
@@ -518,20 +605,6 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
ExceptionVerification.IgnoreErrors();
|
ExceptionVerification.IgnoreErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_send_old_cookie()
|
|
||||||
{
|
|
||||||
GivenOldCookie();
|
|
||||||
|
|
||||||
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
|
|
||||||
requestCookies.IgnorePersistentCookies = true;
|
|
||||||
requestCookies.StoreRequestCookie = false;
|
|
||||||
requestCookies.StoreResponseCookie = false;
|
|
||||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
|
||||||
|
|
||||||
responseCookies.Resource.Cookies.Should().BeEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_throw_on_http429_too_many_requests()
|
public void should_throw_on_http429_too_many_requests()
|
||||||
{
|
{
|
||||||
@@ -610,8 +683,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string url =
|
string url = $"http://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeUriString(malformedCookie)}";
|
||||||
$"http://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeUriString(malformedCookie)}";
|
|
||||||
|
|
||||||
var requestSet = new HttpRequest(url);
|
var requestSet = new HttpRequest(url);
|
||||||
requestSet.AllowAutoRedirect = false;
|
requestSet.AllowAutoRedirect = false;
|
||||||
@@ -635,6 +707,7 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
|
|
||||||
public class HttpBinResource
|
public class HttpBinResource
|
||||||
{
|
{
|
||||||
|
public Dictionary<string, object> Args { get; set; }
|
||||||
public Dictionary<string, object> Headers { get; set; }
|
public Dictionary<string, object> Headers { get; set; }
|
||||||
public string Origin { get; set; }
|
public string Origin { get; set; }
|
||||||
public string Url { get; set; }
|
public string Url { get; set; }
|
||||||
|
@@ -21,13 +21,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
|||||||
{
|
{
|
||||||
Subject.Definition = new DownloadClientDefinition();
|
Subject.Definition = new DownloadClientDefinition();
|
||||||
Subject.Definition.Settings = new QBittorrentSettings
|
Subject.Definition.Settings = new QBittorrentSettings
|
||||||
{
|
{
|
||||||
Host = "127.0.0.1",
|
Host = "127.0.0.1",
|
||||||
Port = 2222,
|
Port = 2222,
|
||||||
Username = "admin",
|
Username = "admin",
|
||||||
Password = "pass",
|
Password = "pass",
|
||||||
MovieCategory = "movies-radarr"
|
MovieCategory = "movies-radarr"
|
||||||
};
|
};
|
||||||
|
|
||||||
Mocker.GetMock<ITorrentFileInfoReader>()
|
Mocker.GetMock<ITorrentFileInfoReader>()
|
||||||
.Setup(s => s.GetHashFromTorrentFile(It.IsAny<Byte[]>()))
|
.Setup(s => s.GetHashFromTorrentFile(It.IsAny<Byte[]>()))
|
||||||
|
@@ -4,11 +4,9 @@ using FluentAssertions;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.Indexers.Nyaa;
|
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
|
||||||
using NzbDrone.Test.Common.Categories;
|
using NzbDrone.Test.Common.Categories;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
|
namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
|
||||||
@@ -30,40 +28,6 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void nyaa_fetch_recent()
|
|
||||||
{
|
|
||||||
var indexer = Mocker.Resolve<Nyaa>();
|
|
||||||
|
|
||||||
indexer.Definition = new IndexerDefinition
|
|
||||||
{
|
|
||||||
Name = "MyIndexer",
|
|
||||||
Settings = new NyaaSettings()
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = indexer.FetchRecent();
|
|
||||||
|
|
||||||
ValidateTorrentResult(result, hasSize: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void nyaa_search_single()
|
|
||||||
{
|
|
||||||
var indexer = Mocker.Resolve<Nyaa>();
|
|
||||||
|
|
||||||
indexer.Definition = new IndexerDefinition
|
|
||||||
{
|
|
||||||
Name = "MyIndexer",
|
|
||||||
Settings = new NyaaSettings()
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = indexer.Fetch(_singleSearchCriteria);
|
|
||||||
|
|
||||||
ValidateTorrentResult(result, hasSize: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void ValidateTorrentResult(IList<ReleaseInfo> reports, bool hasSize = false, bool hasInfoUrl = false, bool hasMagnet = false)
|
private void ValidateTorrentResult(IList<ReleaseInfo> reports, bool hasSize = false, bool hasInfoUrl = false, bool hasMagnet = false)
|
||||||
{
|
{
|
||||||
reports.Should().OnlyContain(c => c.GetType() == typeof(TorrentInfo));
|
reports.Should().OnlyContain(c => c.GetType() == typeof(TorrentInfo));
|
||||||
|
@@ -1,145 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using FizzWare.NBuilder;
|
|
||||||
using Moq;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using NzbDrone.Common.Disk;
|
|
||||||
using NzbDrone.Core.Download;
|
|
||||||
using NzbDrone.Core.Download.TrackedDownloads;
|
|
||||||
using NzbDrone.Core.MediaFiles;
|
|
||||||
using NzbDrone.Core.MediaFiles.Commands;
|
|
||||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
|
||||||
using NzbDrone.Core.Parser.Model;
|
|
||||||
using NzbDrone.Core.Test.Framework;
|
|
||||||
using NzbDrone.Core.Tv;
|
|
||||||
using NzbDrone.Test.Common;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.MediaFiles
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class DownloadedEpisodesCommandServiceFixture : CoreTest<DownloadedEpisodesCommandService>
|
|
||||||
{
|
|
||||||
private string _downloadFolder = "c:\\drop_other\\Show.S01E01\\".AsOsAgnostic();
|
|
||||||
private string _downloadFile = "c:\\drop_other\\Show.S01E01.mkv".AsOsAgnostic();
|
|
||||||
|
|
||||||
private TrackedDownload _trackedDownload;
|
|
||||||
|
|
||||||
[SetUp]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
|
||||||
.Setup(v => v.ProcessRootFolder(It.IsAny<DirectoryInfo>()))
|
|
||||||
.Returns(new List<ImportResult>());
|
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
|
||||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
|
||||||
.Returns(new List<ImportResult>());
|
|
||||||
|
|
||||||
var downloadItem = Builder<DownloadClientItem>.CreateNew()
|
|
||||||
.With(v => v.DownloadId = "sab1")
|
|
||||||
.With(v => v.Status = DownloadItemStatus.Downloading)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var remoteEpisode = Builder<RemoteEpisode>.CreateNew()
|
|
||||||
.With(v => v.Series = new Series())
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
_trackedDownload = new TrackedDownload
|
|
||||||
{
|
|
||||||
DownloadItem = downloadItem,
|
|
||||||
RemoteEpisode = remoteEpisode,
|
|
||||||
State = TrackedDownloadStage.Downloading
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenExistingFolder(string path)
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists(It.IsAny<string>()))
|
|
||||||
.Returns(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenExistingFile(string path)
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FileExists(It.IsAny<string>()))
|
|
||||||
.Returns(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenValidQueueItem()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<ITrackedDownloadService>()
|
|
||||||
.Setup(s => s.Find("sab1"))
|
|
||||||
.Returns(_trackedDownload);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_skip_import_if_dronefactory_doesnt_exist()
|
|
||||||
{
|
|
||||||
Assert.Throws<ArgumentException>(() => Subject.Execute(new DownloadedEpisodesScanCommand()));
|
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessRootFolder(It.IsAny<DirectoryInfo>()), Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_process_folder_if_downloadclientid_is_not_specified()
|
|
||||||
{
|
|
||||||
GivenExistingFolder(_downloadFolder);
|
|
||||||
|
|
||||||
Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder });
|
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessPath(It.IsAny<string>(), ImportMode.Auto, null, null), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_process_file_if_downloadclientid_is_not_specified()
|
|
||||||
{
|
|
||||||
GivenExistingFile(_downloadFile);
|
|
||||||
|
|
||||||
Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFile });
|
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessPath(It.IsAny<string>(), ImportMode.Auto, null, null), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_process_folder_with_downloadclientitem_if_available()
|
|
||||||
{
|
|
||||||
GivenExistingFolder(_downloadFolder);
|
|
||||||
GivenValidQueueItem();
|
|
||||||
|
|
||||||
Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder, DownloadClientId = "sab1" });
|
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessPath(_downloadFolder, ImportMode.Auto, _trackedDownload.RemoteEpisode.Series, _trackedDownload.DownloadItem), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_process_folder_without_downloadclientitem_if_not_available()
|
|
||||||
{
|
|
||||||
GivenExistingFolder(_downloadFolder);
|
|
||||||
|
|
||||||
Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder, DownloadClientId = "sab1" });
|
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessPath(_downloadFolder, ImportMode.Auto, null, null), Times.Once());
|
|
||||||
|
|
||||||
ExceptionVerification.ExpectedWarns(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_warn_if_neither_folder_or_file_exists()
|
|
||||||
{
|
|
||||||
Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder });
|
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessPath(It.IsAny<string>(), ImportMode.Auto, null, null), Times.Never());
|
|
||||||
|
|
||||||
ExceptionVerification.ExpectedWarns(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_override_import_mode()
|
|
||||||
{
|
|
||||||
GivenExistingFile(_downloadFile);
|
|
||||||
|
|
||||||
Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFile, ImportMode = ImportMode.Copy });
|
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessPath(It.IsAny<string>(), ImportMode.Copy, null, null), Times.Once());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,377 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using FizzWare.NBuilder;
|
|
||||||
using Moq;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using NzbDrone.Common.Disk;
|
|
||||||
using NzbDrone.Core.MediaFiles;
|
|
||||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
|
||||||
using NzbDrone.Core.Parser;
|
|
||||||
using NzbDrone.Core.Parser.Model;
|
|
||||||
using NzbDrone.Core.Qualities;
|
|
||||||
using NzbDrone.Core.Test.Framework;
|
|
||||||
using NzbDrone.Core.Tv;
|
|
||||||
using NzbDrone.Test.Common;
|
|
||||||
using FluentAssertions;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.MediaFiles
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class DownloadedEpisodesImportServiceFixture : CoreTest<DownloadedEpisodesImportService>
|
|
||||||
{
|
|
||||||
private string _droneFactory = "c:\\drop\\".AsOsAgnostic();
|
|
||||||
private string[] _subFolders = new[] { "c:\\root\\foldername".AsOsAgnostic() };
|
|
||||||
private string[] _videoFiles = new[] { "c:\\root\\foldername\\30.rock.s01e01.ext".AsOsAgnostic() };
|
|
||||||
|
|
||||||
[SetUp]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IDiskScanService>().Setup(c => c.GetVideoFiles(It.IsAny<string>(), It.IsAny<bool>()))
|
|
||||||
.Returns(_videoFiles);
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.GetDirectories(It.IsAny<string>()))
|
|
||||||
.Returns(_subFolders);
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists(It.IsAny<string>()))
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
Mocker.GetMock<IImportApprovedEpisodes>()
|
|
||||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
|
|
||||||
.Returns(new List<ImportResult>());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenValidSeries()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IParsingService>()
|
|
||||||
.Setup(s => s.GetSeries(It.IsAny<string>()))
|
|
||||||
.Returns(Builder<Series>.CreateNew().Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_search_for_series_using_folder_name()
|
|
||||||
{
|
|
||||||
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
|
||||||
|
|
||||||
Mocker.GetMock<IParsingService>().Verify(c => c.GetSeries("foldername"), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_skip_if_file_is_in_use_by_another_process()
|
|
||||||
{
|
|
||||||
GivenValidSeries();
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.IsFileLocked(It.IsAny<string>()))
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
|
||||||
|
|
||||||
VerifyNoImport();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_skip_if_no_series_found()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IParsingService>().Setup(c => c.GetSeries("foldername")).Returns((Series)null);
|
|
||||||
|
|
||||||
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
|
||||||
|
|
||||||
Mocker.GetMock<IMakeImportDecision>()
|
|
||||||
.Verify(c => c.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), It.IsAny<bool>()),
|
|
||||||
Times.Never());
|
|
||||||
|
|
||||||
VerifyNoImport();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_import_if_folder_is_a_series_path()
|
|
||||||
{
|
|
||||||
GivenValidSeries();
|
|
||||||
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
|
||||||
.Setup(s => s.SeriesPathExists(It.IsAny<string>()))
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskScanService>()
|
|
||||||
.Setup(c => c.GetVideoFiles(It.IsAny<string>(), It.IsAny<bool>()))
|
|
||||||
.Returns(new string[0]);
|
|
||||||
|
|
||||||
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskScanService>()
|
|
||||||
.Verify(v => v.GetVideoFiles(It.IsAny<string>(), true), Times.Never());
|
|
||||||
|
|
||||||
ExceptionVerification.ExpectedWarns(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_delete_folder_if_no_files_were_imported()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IImportApprovedEpisodes>()
|
|
||||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), false, null, ImportMode.Auto))
|
|
||||||
.Returns(new List<ImportResult>());
|
|
||||||
|
|
||||||
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Verify(v => v.GetFolderSize(It.IsAny<string>()), Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_delete_folder_if_files_were_imported_and_video_files_remain()
|
|
||||||
{
|
|
||||||
GivenValidSeries();
|
|
||||||
|
|
||||||
var localEpisode = new LocalEpisode();
|
|
||||||
|
|
||||||
var imported = new List<ImportDecision>();
|
|
||||||
imported.Add(new ImportDecision(localEpisode));
|
|
||||||
|
|
||||||
Mocker.GetMock<IMakeImportDecision>()
|
|
||||||
.Setup(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Series>(), null, true))
|
|
||||||
.Returns(imported);
|
|
||||||
|
|
||||||
Mocker.GetMock<IImportApprovedEpisodes>()
|
|
||||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
|
|
||||||
.Returns(imported.Select(i => new ImportResult(i)).ToList());
|
|
||||||
|
|
||||||
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Verify(v => v.DeleteFolder(It.IsAny<string>(), true), Times.Never());
|
|
||||||
|
|
||||||
ExceptionVerification.ExpectedWarns(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_delete_folder_if_files_were_imported_and_only_sample_files_remain()
|
|
||||||
{
|
|
||||||
GivenValidSeries();
|
|
||||||
|
|
||||||
var localEpisode = new LocalEpisode();
|
|
||||||
|
|
||||||
var imported = new List<ImportDecision>();
|
|
||||||
imported.Add(new ImportDecision(localEpisode));
|
|
||||||
|
|
||||||
Mocker.GetMock<IMakeImportDecision>()
|
|
||||||
.Setup(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Series>(), null, true))
|
|
||||||
.Returns(imported);
|
|
||||||
|
|
||||||
Mocker.GetMock<IImportApprovedEpisodes>()
|
|
||||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
|
|
||||||
.Returns(imported.Select(i => new ImportResult(i)).ToList());
|
|
||||||
|
|
||||||
Mocker.GetMock<IDetectSample>()
|
|
||||||
.Setup(s => s.IsSample(It.IsAny<Series>(),
|
|
||||||
It.IsAny<QualityModel>(),
|
|
||||||
It.IsAny<string>(),
|
|
||||||
It.IsAny<long>(),
|
|
||||||
It.IsAny<bool>()))
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Verify(v => v.DeleteFolder(It.IsAny<string>(), true), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestCase("_UNPACK_")]
|
|
||||||
[TestCase("_FAILED_")]
|
|
||||||
public void should_remove_unpack_from_folder_name(string prefix)
|
|
||||||
{
|
|
||||||
var folderName = "30.rock.s01e01.pilot.hdtv-lol";
|
|
||||||
var folders = new[] { string.Format(@"C:\Test\Unsorted\{0}{1}", prefix, folderName).AsOsAgnostic() };
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Setup(c => c.GetDirectories(It.IsAny<string>()))
|
|
||||||
.Returns(folders);
|
|
||||||
|
|
||||||
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
|
||||||
|
|
||||||
Mocker.GetMock<IParsingService>()
|
|
||||||
.Verify(v => v.GetSeries(folderName), Times.Once());
|
|
||||||
|
|
||||||
Mocker.GetMock<IParsingService>()
|
|
||||||
.Verify(v => v.GetSeries(It.Is<string>(s => s.StartsWith(prefix))), Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_return_importresult_on_unknown_series()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists(It.IsAny<string>()))
|
|
||||||
.Returns(false);
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FileExists(It.IsAny<string>()))
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
var fileName = @"C:\folder\file.mkv".AsOsAgnostic();
|
|
||||||
|
|
||||||
var result = Subject.ProcessPath(fileName);
|
|
||||||
|
|
||||||
result.Should().HaveCount(1);
|
|
||||||
result.First().ImportDecision.Should().NotBeNull();
|
|
||||||
result.First().ImportDecision.LocalEpisode.Should().NotBeNull();
|
|
||||||
result.First().ImportDecision.LocalEpisode.Path.Should().Be(fileName);
|
|
||||||
result.First().Result.Should().Be(ImportResultType.Rejected);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_delete_if_there_is_large_rar_file()
|
|
||||||
{
|
|
||||||
GivenValidSeries();
|
|
||||||
|
|
||||||
var localEpisode = new LocalEpisode();
|
|
||||||
|
|
||||||
var imported = new List<ImportDecision>();
|
|
||||||
imported.Add(new ImportDecision(localEpisode));
|
|
||||||
|
|
||||||
Mocker.GetMock<IMakeImportDecision>()
|
|
||||||
.Setup(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Series>(), null, true))
|
|
||||||
.Returns(imported);
|
|
||||||
|
|
||||||
Mocker.GetMock<IImportApprovedEpisodes>()
|
|
||||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
|
|
||||||
.Returns(imported.Select(i => new ImportResult(i)).ToList());
|
|
||||||
|
|
||||||
Mocker.GetMock<IDetectSample>()
|
|
||||||
.Setup(s => s.IsSample(It.IsAny<Series>(),
|
|
||||||
It.IsAny<QualityModel>(),
|
|
||||||
It.IsAny<string>(),
|
|
||||||
It.IsAny<long>(),
|
|
||||||
It.IsAny<bool>()))
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Setup(s => s.GetFiles(It.IsAny<string>(), SearchOption.AllDirectories))
|
|
||||||
.Returns(new []{ _videoFiles.First().Replace(".ext", ".rar") });
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Setup(s => s.GetFileSize(It.IsAny<string>()))
|
|
||||||
.Returns(15.Megabytes());
|
|
||||||
|
|
||||||
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Verify(v => v.DeleteFolder(It.IsAny<string>(), true), Times.Never());
|
|
||||||
|
|
||||||
ExceptionVerification.ExpectedWarns(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_use_folder_if_folder_import()
|
|
||||||
{
|
|
||||||
GivenValidSeries();
|
|
||||||
|
|
||||||
var folderName = @"C:\media\ba09030e-1234-1234-1234-123456789abc\[HorribleSubs] Maria the Virgin Witch - 09 [720p]".AsOsAgnostic();
|
|
||||||
var fileName = @"C:\media\ba09030e-1234-1234-1234-123456789abc\[HorribleSubs] Maria the Virgin Witch - 09 [720p]\[HorribleSubs] Maria the Virgin Witch - 09 [720p].mkv".AsOsAgnostic();
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists(folderName))
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.GetFiles(folderName, SearchOption.TopDirectoryOnly))
|
|
||||||
.Returns(new[] { fileName });
|
|
||||||
|
|
||||||
var localEpisode = new LocalEpisode();
|
|
||||||
|
|
||||||
var imported = new List<ImportDecision>();
|
|
||||||
imported.Add(new ImportDecision(localEpisode));
|
|
||||||
|
|
||||||
|
|
||||||
Subject.ProcessPath(fileName);
|
|
||||||
|
|
||||||
Mocker.GetMock<IMakeImportDecision>()
|
|
||||||
.Verify(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Series>(), It.Is<ParsedEpisodeInfo>(v => v.AbsoluteEpisodeNumbers.First() == 9), true), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_use_folder_if_file_import()
|
|
||||||
{
|
|
||||||
GivenValidSeries();
|
|
||||||
|
|
||||||
var fileName = @"C:\media\ba09030e-1234-1234-1234-123456789abc\Torrents\[HorribleSubs] Maria the Virgin Witch - 09 [720p].mkv".AsOsAgnostic();
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists(fileName))
|
|
||||||
.Returns(false);
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FileExists(fileName))
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
var localEpisode = new LocalEpisode();
|
|
||||||
|
|
||||||
var imported = new List<ImportDecision>();
|
|
||||||
imported.Add(new ImportDecision(localEpisode));
|
|
||||||
|
|
||||||
var result = Subject.ProcessPath(fileName);
|
|
||||||
|
|
||||||
Mocker.GetMock<IMakeImportDecision>()
|
|
||||||
.Verify(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Series>(), null, true), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_process_if_file_and_folder_do_not_exist()
|
|
||||||
{
|
|
||||||
var folderName = @"C:\media\ba09030e-1234-1234-1234-123456789abc\[HorribleSubs] Maria the Virgin Witch - 09 [720p]".AsOsAgnostic();
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists(folderName))
|
|
||||||
.Returns(false);
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FileExists(folderName))
|
|
||||||
.Returns(false);
|
|
||||||
|
|
||||||
Subject.ProcessPath(folderName).Should().BeEmpty();
|
|
||||||
|
|
||||||
Mocker.GetMock<IParsingService>()
|
|
||||||
.Verify(v => v.GetSeries(It.IsAny<string>()), Times.Never());
|
|
||||||
|
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_delete_if_no_files_were_imported()
|
|
||||||
{
|
|
||||||
GivenValidSeries();
|
|
||||||
|
|
||||||
var localEpisode = new LocalEpisode();
|
|
||||||
|
|
||||||
var imported = new List<ImportDecision>();
|
|
||||||
imported.Add(new ImportDecision(localEpisode));
|
|
||||||
|
|
||||||
Mocker.GetMock<IMakeImportDecision>()
|
|
||||||
.Setup(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Series>(), null, true))
|
|
||||||
.Returns(imported);
|
|
||||||
|
|
||||||
Mocker.GetMock<IImportApprovedEpisodes>()
|
|
||||||
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
|
|
||||||
.Returns(new List<ImportResult>());
|
|
||||||
|
|
||||||
Mocker.GetMock<IDetectSample>()
|
|
||||||
.Setup(s => s.IsSample(It.IsAny<Series>(),
|
|
||||||
It.IsAny<QualityModel>(),
|
|
||||||
It.IsAny<string>(),
|
|
||||||
It.IsAny<long>(),
|
|
||||||
It.IsAny<bool>()))
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Setup(s => s.GetFileSize(It.IsAny<string>()))
|
|
||||||
.Returns(15.Megabytes());
|
|
||||||
|
|
||||||
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Verify(v => v.DeleteFolder(It.IsAny<string>(), true), Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifyNoImport()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IImportApprovedEpisodes>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto),
|
|
||||||
Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifyImport()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IImportApprovedEpisodes>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto),
|
|
||||||
Times.Once());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
|||||||
info.AudioBitrate.Should().Be(128000);
|
info.AudioBitrate.Should().Be(128000);
|
||||||
info.AudioChannels.Should().Be(2);
|
info.AudioChannels.Should().Be(2);
|
||||||
info.AudioLanguages.Should().Be("English");
|
info.AudioLanguages.Should().Be("English");
|
||||||
info.AudioAdditionalFeatures.Should().Be("LC");
|
info.AudioAdditionalFeatures.Should().BeOneOf("", "LC");
|
||||||
info.Height.Should().Be(320);
|
info.Height.Should().Be(320);
|
||||||
info.RunTime.Seconds.Should().Be(10);
|
info.RunTime.Seconds.Should().Be(10);
|
||||||
info.ScanType.Should().Be("Progressive");
|
info.ScanType.Should().Be("Progressive");
|
||||||
@@ -90,7 +90,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
|||||||
info.AudioBitrate.Should().Be(128000);
|
info.AudioBitrate.Should().Be(128000);
|
||||||
info.AudioChannels.Should().Be(2);
|
info.AudioChannels.Should().Be(2);
|
||||||
info.AudioLanguages.Should().Be("English");
|
info.AudioLanguages.Should().Be("English");
|
||||||
info.AudioAdditionalFeatures.Should().Be("LC");
|
info.AudioAdditionalFeatures.Should().BeOneOf("", "LC");
|
||||||
info.Height.Should().Be(320);
|
info.Height.Should().Be(320);
|
||||||
info.RunTime.Seconds.Should().Be(10);
|
info.RunTime.Seconds.Should().Be(10);
|
||||||
info.ScanType.Should().Be("Progressive");
|
info.ScanType.Should().Be("Progressive");
|
||||||
|
@@ -27,6 +27,9 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||||||
[TestCase("Seed S02E09 HDTV x264-2HD [eztv]-[rarbg.com]", "2HD")]
|
[TestCase("Seed S02E09 HDTV x264-2HD [eztv]-[rarbg.com]", "2HD")]
|
||||||
[TestCase("7s-atlantis-s02e01-720p.mkv", null)]
|
[TestCase("7s-atlantis-s02e01-720p.mkv", null)]
|
||||||
[TestCase("The.Middle.720p.HEVC.x265-MeGusta-Pre", "MeGusta")]
|
[TestCase("The.Middle.720p.HEVC.x265-MeGusta-Pre", "MeGusta")]
|
||||||
|
[TestCase("Blue.Bloods.S08E05.The.Forgotten.1080p.AMZN.WEB-DL.DDP5.1.H.264-NTb-Rakuv", "NTb")]
|
||||||
|
[TestCase("Lie.To.Me.S01E13.720p.BluRay.x264-SiNNERS-Rakuvfinhel", "SiNNERS")]
|
||||||
|
[TestCase("Who.is.America.S01E01.INTERNAL.720p.HDTV.x264-aAF-RakuvUS-Obfuscated", "aAF")]
|
||||||
[TestCase("Haunted.Hayride.2018.720p.WEBRip.DDP5.1.x264-NTb-postbot", "NTb")]
|
[TestCase("Haunted.Hayride.2018.720p.WEBRip.DDP5.1.x264-NTb-postbot", "NTb")]
|
||||||
[TestCase("Haunted.Hayride.2018.720p.WEBRip.DDP5.1.x264-NTb-xpost", "NTb")]
|
[TestCase("Haunted.Hayride.2018.720p.WEBRip.DDP5.1.x264-NTb-xpost", "NTb")]
|
||||||
//[TestCase("", "")]
|
//[TestCase("", "")]
|
||||||
|
@@ -142,7 +142,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
|
|||||||
if (image == null)
|
if (image == null)
|
||||||
{
|
{
|
||||||
_logger.Trace("Failed to find suitable Movie image for movie {0}.", movie.Title);
|
_logger.Trace("Failed to find suitable Movie image for movie {0}.", movie.Title);
|
||||||
return null;
|
return new List<ImageFileResult>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var source = _mediaCoverService.GetCoverPath(movie.Id, image.CoverType);
|
var source = _mediaCoverService.GetCoverPath(movie.Id, image.CoverType);
|
||||||
|
@@ -110,7 +110,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
|
|||||||
|
|
||||||
if (movie == null)
|
if (movie == null)
|
||||||
{
|
{
|
||||||
movie = trackedDownload.RemoteMovie.Movie;
|
movie = trackedDownload.RemoteMovie?.Movie;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
107
src/NzbDrone.Core/Notifications/Discord/Discord.cs
Normal file
107
src/NzbDrone.Core/Notifications/Discord/Discord.cs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Notifications.Discord.Payloads;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Discord
|
||||||
|
{
|
||||||
|
public class Discord : NotificationBase<DiscordSettings>
|
||||||
|
{
|
||||||
|
private readonly IDiscordProxy _proxy;
|
||||||
|
|
||||||
|
public Discord(IDiscordProxy proxy)
|
||||||
|
{
|
||||||
|
_proxy = proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name => "Discord";
|
||||||
|
public override string Link => "https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks";
|
||||||
|
|
||||||
|
public override void OnGrab(GrabMessage message)
|
||||||
|
{
|
||||||
|
var embeds = new List<Embed>
|
||||||
|
{
|
||||||
|
new Embed
|
||||||
|
{
|
||||||
|
Description = message.Message,
|
||||||
|
Title = message.Movie.Title,
|
||||||
|
Text = message.Message,
|
||||||
|
Color = (int)DiscordColors.Warning
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var payload = CreatePayload($"Grabbed: {message.Message}", embeds);
|
||||||
|
|
||||||
|
_proxy.SendPayload(payload, Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnDownload(DownloadMessage message)
|
||||||
|
{
|
||||||
|
var embeds = new List<Embed>
|
||||||
|
{
|
||||||
|
new Embed
|
||||||
|
{
|
||||||
|
Description = message.Message,
|
||||||
|
Title = message.Movie.Title,
|
||||||
|
Text = message.Message,
|
||||||
|
Color = (int)DiscordColors.Success
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var payload = CreatePayload($"Imported: {message.Message}", embeds);
|
||||||
|
|
||||||
|
_proxy.SendPayload(payload, Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ValidationResult Test()
|
||||||
|
{
|
||||||
|
var failures = new List<ValidationFailure>();
|
||||||
|
|
||||||
|
failures.AddIfNotNull(TestMessage());
|
||||||
|
|
||||||
|
return new ValidationResult(failures);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidationFailure TestMessage()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var message = $"Test message from Radarr posted at {DateTime.Now}";
|
||||||
|
var payload = CreatePayload(message);
|
||||||
|
|
||||||
|
_proxy.SendPayload(payload, Settings);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (DiscordException ex)
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationFailure("Unable to post", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DiscordPayload CreatePayload(string message, List<Embed> embeds = null)
|
||||||
|
{
|
||||||
|
var avatar = Settings.Avatar;
|
||||||
|
|
||||||
|
var payload = new DiscordPayload
|
||||||
|
{
|
||||||
|
Username = Settings.Username,
|
||||||
|
Content = message,
|
||||||
|
Embeds = embeds
|
||||||
|
};
|
||||||
|
|
||||||
|
if (avatar.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
payload.AvatarUrl = avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Settings.Username.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
payload.Username = Settings.Username;
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
src/NzbDrone.Core/Notifications/Discord/DiscordColors.cs
Normal file
9
src/NzbDrone.Core/Notifications/Discord/DiscordColors.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace NzbDrone.Core.Notifications.Discord
|
||||||
|
{
|
||||||
|
public enum DiscordColors
|
||||||
|
{
|
||||||
|
Danger = 15749200,
|
||||||
|
Success = 2605644,
|
||||||
|
Warning = 16753920
|
||||||
|
}
|
||||||
|
}
|
16
src/NzbDrone.Core/Notifications/Discord/DiscordException.cs
Normal file
16
src/NzbDrone.Core/Notifications/Discord/DiscordException.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using NzbDrone.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Discord
|
||||||
|
{
|
||||||
|
class DiscordException : NzbDroneException
|
||||||
|
{
|
||||||
|
public DiscordException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordException(string message, Exception innerException, params object[] args) : base(message, innerException, args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
src/NzbDrone.Core/Notifications/Discord/DiscordProxy.cs
Normal file
46
src/NzbDrone.Core/Notifications/Discord/DiscordProxy.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
|
using NzbDrone.Core.Notifications.Discord.Payloads;
|
||||||
|
using NzbDrone.Core.Rest;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Discord
|
||||||
|
{
|
||||||
|
public interface IDiscordProxy
|
||||||
|
{
|
||||||
|
void SendPayload(DiscordPayload payload, DiscordSettings settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DiscordProxy : IDiscordProxy
|
||||||
|
{
|
||||||
|
private readonly IHttpClient _httpClient;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public DiscordProxy(IHttpClient httpClient, Logger logger)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendPayload(DiscordPayload payload, DiscordSettings settings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new HttpRequestBuilder(settings.WebHookUrl)
|
||||||
|
.Accept(HttpAccept.Json)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
request.Method = HttpMethod.POST;
|
||||||
|
request.Headers.ContentType = "application/json";
|
||||||
|
request.SetContent(payload.ToJson());
|
||||||
|
|
||||||
|
_httpClient.Execute(request);
|
||||||
|
}
|
||||||
|
catch (RestException ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Unable to post payload {0}", payload);
|
||||||
|
throw new DiscordException("Unable to post payload", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
src/NzbDrone.Core/Notifications/Discord/DiscordSettings.cs
Normal file
35
src/NzbDrone.Core/Notifications/Discord/DiscordSettings.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Discord
|
||||||
|
{
|
||||||
|
public class DiscordSettingsValidator : AbstractValidator<DiscordSettings>
|
||||||
|
{
|
||||||
|
public DiscordSettingsValidator()
|
||||||
|
{
|
||||||
|
RuleFor(c => c.WebHookUrl).IsValidUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DiscordSettings : IProviderConfig
|
||||||
|
{
|
||||||
|
private static readonly DiscordSettingsValidator Validator = new DiscordSettingsValidator();
|
||||||
|
|
||||||
|
[FieldDefinition(0, Label = "Webhook URL", HelpText = "Discord channel webhook url")]
|
||||||
|
public string WebHookUrl { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(1, Label = "Username", HelpText = "The username to post as, defaults to Discord webhook default")]
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(2, Label = "Avatar", HelpText = "Change the avatar that is used for messages from this integration", Type = FieldType.Textbox)]
|
||||||
|
public string Avatar { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public NzbDroneValidationResult Validate()
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,17 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Discord.Payloads
|
||||||
|
{
|
||||||
|
public class DiscordPayload
|
||||||
|
{
|
||||||
|
public string Content { get; set; }
|
||||||
|
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("avatar_url")]
|
||||||
|
public string AvatarUrl { get; set; }
|
||||||
|
|
||||||
|
public List<Embed> Embeds { get; set; }
|
||||||
|
}
|
||||||
|
}
|
10
src/NzbDrone.Core/Notifications/Discord/Payloads/Embed.cs
Normal file
10
src/NzbDrone.Core/Notifications/Discord/Payloads/Embed.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace NzbDrone.Core.Notifications.Discord.Payloads
|
||||||
|
{
|
||||||
|
public class Embed
|
||||||
|
{
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Text { get; set; }
|
||||||
|
public int Color { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
namespace NzbDrone.Core.Notifications.Gotify
|
namespace NzbDrone.Core.Notifications.Gotify
|
||||||
|
|
||||||
{
|
{
|
||||||
public enum GotifyPriority
|
public enum GotifyPriority
|
||||||
{
|
{
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using FluentValidation.Results;
|
using FluentValidation.Results;
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Notifications.Slack.Payloads;
|
using NzbDrone.Core.Notifications.Slack.Payloads;
|
||||||
using NzbDrone.Core.Movies;
|
using NzbDrone.Core.Movies;
|
||||||
@@ -13,12 +12,10 @@ namespace NzbDrone.Core.Notifications.Slack
|
|||||||
public class Slack : NotificationBase<SlackSettings>
|
public class Slack : NotificationBase<SlackSettings>
|
||||||
{
|
{
|
||||||
private readonly ISlackProxy _proxy;
|
private readonly ISlackProxy _proxy;
|
||||||
private readonly Logger _logger;
|
|
||||||
|
|
||||||
public Slack(ISlackProxy proxy, Logger logger)
|
public Slack(ISlackProxy proxy)
|
||||||
{
|
{
|
||||||
_proxy = proxy;
|
_proxy = proxy;
|
||||||
_logger = logger;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Name => "Slack";
|
public override string Name => "Slack";
|
||||||
|
@@ -964,10 +964,17 @@
|
|||||||
<Compile Include="Extras\Metadata\MetadataType.cs" />
|
<Compile Include="Extras\Metadata\MetadataType.cs" />
|
||||||
<Compile Include="MetadataSource\TmdbConfigurationService.cs" />
|
<Compile Include="MetadataSource\TmdbConfigurationService.cs" />
|
||||||
<Compile Include="NetImport\NetImportSyncCommand.cs" />
|
<Compile Include="NetImport\NetImportSyncCommand.cs" />
|
||||||
|
<Compile Include="Notifications\Discord\Discord.cs" />
|
||||||
|
<Compile Include="Notifications\Discord\DiscordColors.cs" />
|
||||||
|
<Compile Include="Notifications\Discord\DiscordException.cs" />
|
||||||
|
<Compile Include="Notifications\Discord\DiscordProxy.cs" />
|
||||||
|
<Compile Include="Notifications\Discord\DiscordSettings.cs" />
|
||||||
|
<Compile Include="Notifications\Discord\Payloads\DiscordPayload.cs" />
|
||||||
|
<Compile Include="Notifications\Discord\Payloads\Embed.cs" />
|
||||||
|
<Compile Include="Notifications\Gotify\GotifyProxy.cs" />
|
||||||
<Compile Include="Notifications\Gotify\InvalidResponseException.cs" />
|
<Compile Include="Notifications\Gotify\InvalidResponseException.cs" />
|
||||||
<Compile Include="Notifications\Gotify\Gotify.cs" />
|
<Compile Include="Notifications\Gotify\Gotify.cs" />
|
||||||
<Compile Include="Notifications\Gotify\GotifyPriority.cs" />
|
<Compile Include="Notifications\Gotify\GotifyPriority.cs" />
|
||||||
<Compile Include="Notifications\Gotify\GotifyService.cs" />
|
|
||||||
<Compile Include="Notifications\Gotify\GotifySettings.cs" />
|
<Compile Include="Notifications\Gotify\GotifySettings.cs" />
|
||||||
<Compile Include="Notifications\Join\JoinAuthException.cs" />
|
<Compile Include="Notifications\Join\JoinAuthException.cs" />
|
||||||
<Compile Include="Notifications\Join\JoinInvalidDeviceException.cs" />
|
<Compile Include="Notifications\Join\JoinInvalidDeviceException.cs" />
|
||||||
|
@@ -35,18 +35,9 @@ namespace NzbDrone.Core.Organizer
|
|||||||
private static readonly Regex TitleRegex = new Regex(@"\{(?<prefix>[- ._\[(]*)(?<token>(?:[a-z0-9]+)(?:(?<separator>[- ._]+)(?:[a-z0-9]+))?)(?::(?<customFormat>[a-z0-9]+))?(?<suffix>[- ._)\]]*)\}",
|
private static readonly Regex TitleRegex = new Regex(@"\{(?<prefix>[- ._\[(]*)(?<token>(?:[a-z0-9]+)(?:(?<separator>[- ._]+)(?:[a-z0-9]+))?)(?::(?<customFormat>[a-z0-9]+))?(?<suffix>[- ._)\]]*)\}",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private static readonly Regex EpisodeRegex = new Regex(@"(?<episode>\{episode(?:\:0+)?})",
|
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
||||||
|
|
||||||
private static readonly Regex TagsRegex = new Regex(@"(?<tags>\{tags(?:\:0+)?})",
|
private static readonly Regex TagsRegex = new Regex(@"(?<tags>\{tags(?:\:0+)?})",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private static readonly Regex SeasonRegex = new Regex(@"(?<season>\{season(?:\:0+)?})",
|
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
||||||
|
|
||||||
private static readonly Regex AbsoluteEpisodeRegex = new Regex(@"(?<absolute>\{absolute(?:\:0+)?})",
|
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
||||||
|
|
||||||
public static readonly Regex SeasonEpisodePatternRegex = new Regex(@"(?<separator>(?<=})[- ._]+?)?(?<seasonEpisode>s?{season(?:\:0+)?}(?<episodeSeparator>[- ._]?[ex])(?<episode>{episode(?:\:0+)?}))(?<separator>[- ._]+?(?={))?",
|
public static readonly Regex SeasonEpisodePatternRegex = new Regex(@"(?<separator>(?<=})[- ._]+?)?(?<seasonEpisode>s?{season(?:\:0+)?}(?<episodeSeparator>[- ._]?[ex])(?<episode>{episode(?:\:0+)?}))(?<separator>[- ._]+?(?={))?",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
@@ -178,48 +169,6 @@ namespace NzbDrone.Core.Organizer
|
|||||||
{
|
{
|
||||||
return new BasicNamingConfig(); //For now let's be lazy
|
return new BasicNamingConfig(); //For now let's be lazy
|
||||||
|
|
||||||
//var episodeFormat = GetEpisodeFormat(nameSpec.StandardMovieFormat).LastOrDefault();
|
|
||||||
|
|
||||||
//if (episodeFormat == null)
|
|
||||||
//{
|
|
||||||
// return new BasicNamingConfig();
|
|
||||||
//}
|
|
||||||
|
|
||||||
//var basicNamingConfig = new BasicNamingConfig
|
|
||||||
//{
|
|
||||||
// Separator = episodeFormat.Separator,
|
|
||||||
// NumberStyle = episodeFormat.SeasonEpisodePattern
|
|
||||||
//};
|
|
||||||
|
|
||||||
//var titleTokens = TitleRegex.Matches(nameSpec.StandardMovieFormat);
|
|
||||||
|
|
||||||
//foreach (Match match in titleTokens)
|
|
||||||
//{
|
|
||||||
// var separator = match.Groups["separator"].Value;
|
|
||||||
// var token = match.Groups["token"].Value;
|
|
||||||
|
|
||||||
// if (!separator.Equals(" "))
|
|
||||||
// {
|
|
||||||
// basicNamingConfig.ReplaceSpaces = true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (token.StartsWith("{Series", StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
// {
|
|
||||||
// basicNamingConfig.IncludeSeriesTitle = true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (token.StartsWith("{Episode", StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
// {
|
|
||||||
// basicNamingConfig.IncludeEpisodeTitle = true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (token.StartsWith("{Quality", StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
// {
|
|
||||||
// basicNamingConfig.IncludeQuality = true;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
//return basicNamingConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetMovieFolder(Movie movie, NamingConfig namingConfig = null)
|
public string GetMovieFolder(Movie movie, NamingConfig namingConfig = null)
|
||||||
@@ -521,18 +470,6 @@ namespace NzbDrone.Core.Organizer
|
|||||||
return value.ToString(split[1]);
|
return value.ToString(split[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EpisodeFormat[] GetEpisodeFormat(string pattern)
|
|
||||||
{
|
|
||||||
return _episodeFormatCache.Get(pattern, () => SeasonEpisodePatternRegex.Matches(pattern).OfType<Match>()
|
|
||||||
.Select(match => new EpisodeFormat
|
|
||||||
{
|
|
||||||
EpisodeSeparator = match.Groups["episodeSeparator"].Value,
|
|
||||||
Separator = match.Groups["separator"].Value,
|
|
||||||
EpisodePattern = match.Groups["episode"].Value,
|
|
||||||
SeasonEpisodePattern = match.Groups["seasonEpisode"].Value,
|
|
||||||
}).ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetQualityProper(Movie movie, QualityModel quality)
|
private string GetQualityProper(Movie movie, QualityModel quality)
|
||||||
{
|
{
|
||||||
if (quality.Revision.Version > 1)
|
if (quality.Revision.Version > 1)
|
||||||
|
@@ -113,7 +113,7 @@ namespace NzbDrone.Core.Parser
|
|||||||
private static readonly Regex SixDigitAirDateRegex = new Regex(@"(?<=[_.-])(?<airdate>(?<!\d)(?<airyear>[1-9]\d{1})(?<airmonth>[0-1][0-9])(?<airday>[0-3][0-9]))(?=[_.-])",
|
private static readonly Regex SixDigitAirDateRegex = new Regex(@"(?<=[_.-])(?<airdate>(?<!\d)(?<airyear>[1-9]\d{1})(?<airmonth>[0-1][0-9])(?<airday>[0-3][0-9]))(?=[_.-])",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
||||||
private static readonly Regex CleanReleaseGroupRegex = new Regex(@"^(.*?[-._ ](S\d+E\d+)[-._ ])|(-(RP|1|NZBGeek|Obfuscated|sample|Pre|postbot|xpost))+$",
|
private static readonly Regex CleanReleaseGroupRegex = new Regex(@"^(.*?[-._ ](S\d+E\d+)[-._ ])|(-(RP|1|NZBGeek|Obfuscated|sample|Pre|postbot|xpost|Rakuv[a-z]*|WhiteRev|BUYMORE))+$",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
||||||
private static readonly Regex CleanTorrentSuffixRegex = new Regex(@"\[(?:ettv|rartv|rarbg|cttv)\]$",
|
private static readonly Regex CleanTorrentSuffixRegex = new Regex(@"\[(?:ettv|rartv|rarbg|cttv)\]$",
|
||||||
|
@@ -16,10 +16,12 @@ namespace NzbDrone.Integration.Test
|
|||||||
config.LogLevel = "Trace";
|
config.LogLevel = "Trace";
|
||||||
HostConfig.Put(config);
|
HostConfig.Put(config);
|
||||||
|
|
||||||
|
var resultGet = Movies.All();
|
||||||
|
|
||||||
var logFile = Path.Combine(_runner.AppData, "logs", "radarr.trace.txt");
|
var logFile = Path.Combine(_runner.AppData, "logs", "radarr.trace.txt");
|
||||||
var logLines = File.ReadAllLines(logFile);
|
var logLines = File.ReadAllLines(logFile);
|
||||||
|
|
||||||
var result = Movies.InvalidPost(new MovieResource());
|
var resultPost = Movies.InvalidPost(new MovieResource());
|
||||||
|
|
||||||
logLines = File.ReadAllLines(logFile).Skip(logLines.Length).ToArray();
|
logLines = File.ReadAllLines(logFile).Skip(logLines.Length).ToArray();
|
||||||
|
|
||||||
|
@@ -125,6 +125,9 @@ namespace NzbDrone.Integration.Test
|
|||||||
public void IntegrationSetUp()
|
public void IntegrationSetUp()
|
||||||
{
|
{
|
||||||
TempDirectory = Path.Combine(TestContext.CurrentContext.TestDirectory, "_test_" + DateTime.UtcNow.Ticks);
|
TempDirectory = Path.Combine(TestContext.CurrentContext.TestDirectory, "_test_" + DateTime.UtcNow.Ticks);
|
||||||
|
|
||||||
|
// Wait for things to get quiet, otherwise the previous test might influence the current one.
|
||||||
|
Commands.WaitAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TearDown]
|
[TearDown]
|
||||||
|
@@ -26,4 +26,4 @@ namespace NzbDrone.Mono.Test.EnvironmentInfo
|
|||||||
info.Version.Should().NotBeNullOrWhiteSpace();
|
info.Version.Should().NotBeNullOrWhiteSpace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -74,4 +74,4 @@ namespace NzbDrone.Mono.Test.EnvironmentInfo.VersionAdapters
|
|||||||
.Verify(c => c.GetFiles(It.IsAny<string>(), SearchOption.TopDirectoryOnly), Times.Never());
|
.Verify(c => c.GetFiles(It.IsAny<string>(), SearchOption.TopDirectoryOnly), Times.Never());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -79,4 +79,4 @@ namespace NzbDrone.Mono.Test.EnvironmentInfo.VersionAdapters
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -10,6 +10,7 @@ namespace NzbDrone.Mono.Disk
|
|||||||
{
|
{
|
||||||
{ "afpfs", DriveType.Network },
|
{ "afpfs", DriveType.Network },
|
||||||
{ "apfs", DriveType.Fixed },
|
{ "apfs", DriveType.Fixed },
|
||||||
|
{ "fuse.mergerfs", DriveType.Fixed },
|
||||||
{ "zfs", DriveType.Fixed }
|
{ "zfs", DriveType.Fixed }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -88,7 +88,7 @@
|
|||||||
<Compile Include="SignalRDependencyResolver.cs" />
|
<Compile Include="SignalRDependencyResolver.cs" />
|
||||||
<Compile Include="SignalRJsonSerializer.cs" />
|
<Compile Include="SignalRJsonSerializer.cs" />
|
||||||
<Compile Include="SignalRMessage.cs" />
|
<Compile Include="SignalRMessage.cs" />
|
||||||
<Compile Include="SonarrPerformanceCounterManager.cs" />
|
<Compile Include="RadarrPerformanceCounterManager.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\NzbDrone.Common\NzbDrone.Common.csproj">
|
<ProjectReference Include="..\NzbDrone.Common\NzbDrone.Common.csproj">
|
||||||
|
@@ -3,7 +3,7 @@ using Microsoft.AspNet.SignalR.Infrastructure;
|
|||||||
|
|
||||||
namespace NzbDrone.SignalR
|
namespace NzbDrone.SignalR
|
||||||
{
|
{
|
||||||
public class SonarrPerformanceCounterManager : IPerformanceCounterManager
|
public class RadarrPerformanceCounterManager : IPerformanceCounterManager
|
||||||
{
|
{
|
||||||
private readonly IPerformanceCounter _counter = new NoOpPerformanceCounter();
|
private readonly IPerformanceCounter _counter = new NoOpPerformanceCounter();
|
||||||
|
|
@@ -17,7 +17,7 @@ namespace NzbDrone.SignalR
|
|||||||
private SignalRDependencyResolver(IContainer container)
|
private SignalRDependencyResolver(IContainer container)
|
||||||
{
|
{
|
||||||
_container = container;
|
_container = container;
|
||||||
var performanceCounterManager = new SonarrPerformanceCounterManager();
|
var performanceCounterManager = new RadarrPerformanceCounterManager();
|
||||||
Register(typeof(IPerformanceCounterManager), () => performanceCounterManager);
|
Register(typeof(IPerformanceCounterManager), () => performanceCounterManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -36,7 +36,6 @@ namespace Radarr.Api.V2.Calendar
|
|||||||
var start = DateTime.Today.AddDays(-pastDays);
|
var start = DateTime.Today.AddDays(-pastDays);
|
||||||
var end = DateTime.Today.AddDays(futureDays);
|
var end = DateTime.Today.AddDays(futureDays);
|
||||||
var unmonitored = false;
|
var unmonitored = false;
|
||||||
//var premiersOnly = false;
|
|
||||||
var tags = new List<int>();
|
var tags = new List<int>();
|
||||||
|
|
||||||
// TODO: Remove start/end parameters in v3, they don't work well for iCal
|
// TODO: Remove start/end parameters in v3, they don't work well for iCal
|
||||||
@@ -45,7 +44,6 @@ namespace Radarr.Api.V2.Calendar
|
|||||||
var queryPastDays = Request.Query.PastDays;
|
var queryPastDays = Request.Query.PastDays;
|
||||||
var queryFutureDays = Request.Query.FutureDays;
|
var queryFutureDays = Request.Query.FutureDays;
|
||||||
var queryUnmonitored = Request.Query.Unmonitored;
|
var queryUnmonitored = Request.Query.Unmonitored;
|
||||||
// var queryPremiersOnly = Request.Query.PremiersOnly;
|
|
||||||
var queryTags = Request.Query.Tags;
|
var queryTags = Request.Query.Tags;
|
||||||
|
|
||||||
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
|
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
|
||||||
@@ -68,11 +66,6 @@ namespace Radarr.Api.V2.Calendar
|
|||||||
unmonitored = bool.Parse(queryUnmonitored.Value);
|
unmonitored = bool.Parse(queryUnmonitored.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (queryPremiersOnly.HasValue)
|
|
||||||
//{
|
|
||||||
// premiersOnly = bool.Parse(queryPremiersOnly.Value);
|
|
||||||
//}
|
|
||||||
|
|
||||||
if (queryTags.HasValue)
|
if (queryTags.HasValue)
|
||||||
{
|
{
|
||||||
var tagInput = (string)queryTags.Value.ToString();
|
var tagInput = (string)queryTags.Value.ToString();
|
||||||
@@ -116,7 +109,7 @@ namespace Radarr.Api.V2.Calendar
|
|||||||
}
|
}
|
||||||
|
|
||||||
var occurrence = calendar.Create<Event>();
|
var occurrence = calendar.Create<Event>();
|
||||||
occurrence.Uid = "NzbDrone_movie_" + movie.Id + (cinemasRelease ? "_cinemas" : "_physical");
|
occurrence.Uid = "Radarr_movie_" + movie.Id + (cinemasRelease ? "_cinemas" : "_physical");
|
||||||
occurrence.Status = movie.Status == MovieStatusType.Announced ? EventStatus.Tentative : EventStatus.Confirmed;
|
occurrence.Status = movie.Status == MovieStatusType.Announced ? EventStatus.Tentative : EventStatus.Confirmed;
|
||||||
|
|
||||||
occurrence.Start = new CalDateTime(date.Value);
|
occurrence.Start = new CalDateTime(date.Value);
|
||||||
|
@@ -2,9 +2,9 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Nancy;
|
using Nancy;
|
||||||
using Nancy.ModelBinding;
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Cache;
|
using NzbDrone.Common.Cache;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.DecisionEngine;
|
using NzbDrone.Core.DecisionEngine;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.Exceptions;
|
using NzbDrone.Core.Exceptions;
|
||||||
@@ -43,12 +43,12 @@ namespace Radarr.Api.V2.Indexers
|
|||||||
_downloadService = downloadService;
|
_downloadService = downloadService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
GetResourceAll = GetReleases;
|
|
||||||
Post["/"] = x => DownloadRelease(ReadResourceFromRequest());
|
|
||||||
|
|
||||||
PostValidator.RuleFor(s => s.IndexerId).ValidId();
|
PostValidator.RuleFor(s => s.IndexerId).ValidId();
|
||||||
PostValidator.RuleFor(s => s.Guid).NotEmpty();
|
PostValidator.RuleFor(s => s.Guid).NotEmpty();
|
||||||
|
|
||||||
|
GetResourceAll = GetReleases;
|
||||||
|
Post["/"] = x => DownloadRelease(ReadResourceFromRequest());
|
||||||
|
|
||||||
_remoteMovieCache = cacheManager.GetCache<RemoteMovie>(GetType(), "remoteMovies");
|
_remoteMovieCache = cacheManager.GetCache<RemoteMovie>(GetType(), "remoteMovies");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ namespace Radarr.Api.V2.Indexers
|
|||||||
}
|
}
|
||||||
catch (ReleaseDownloadException ex)
|
catch (ReleaseDownloadException ex)
|
||||||
{
|
{
|
||||||
_logger.ErrorException(ex.Message, ex);
|
_logger.Error(ex, ex.Message);
|
||||||
throw new NzbDroneClientException(HttpStatusCode.Conflict, "Getting release from indexer failed");
|
throw new NzbDroneClientException(HttpStatusCode.Conflict, "Getting release from indexer failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -56,10 +56,10 @@ namespace Radarr.Api.V2.Indexers
|
|||||||
|
|
||||||
if (firstDecision?.RemoteMovie.ParsedMovieInfo == null)
|
if (firstDecision?.RemoteMovie.ParsedMovieInfo == null)
|
||||||
{
|
{
|
||||||
throw new ValidationException(new List<ValidationFailure> { new ValidationFailure("Title", "Unable to parse", release.Title) });
|
throw new ValidationException(new List<ValidationFailure>{ new ValidationFailure("Title", "Unable to parse", release.Title) });
|
||||||
}
|
}
|
||||||
|
|
||||||
return MapDecisions(new[] { firstDecision }).AsResponse();
|
return MapDecisions(new [] { firstDecision }).AsResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResolveIndexer(ReleaseInfo release)
|
private void ResolveIndexer(ReleaseInfo release)
|
||||||
@@ -74,7 +74,7 @@ namespace Radarr.Api.V2.Indexers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Debug("Push Release {0} not associated with unknown indexer {1}.", release.Title, release.Indexer);
|
_logger.Debug("Push Release {0} not associated with known indexer {1}.", release.Title, release.Indexer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (release.IndexerId != 0 && release.Indexer.IsNullOrWhiteSpace())
|
else if (release.IndexerId != 0 && release.Indexer.IsNullOrWhiteSpace())
|
||||||
@@ -87,7 +87,7 @@ namespace Radarr.Api.V2.Indexers
|
|||||||
}
|
}
|
||||||
catch (ModelNotFoundException)
|
catch (ModelNotFoundException)
|
||||||
{
|
{
|
||||||
_logger.Debug("Push Release {0} not associated with unknown indexer {0}.", release.Title, release.IndexerId);
|
_logger.Debug("Push Release {0} not associated with known indexer {0}.", release.Title, release.IndexerId);
|
||||||
release.IndexerId = 0;
|
release.IndexerId = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using NzbDrone.Core.DecisionEngine;
|
using NzbDrone.Core.DecisionEngine;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.Languages;
|
using NzbDrone.Core.Languages;
|
||||||
@@ -46,11 +47,16 @@ namespace Radarr.Api.V2.Indexers
|
|||||||
public int? Leechers { get; set; }
|
public int? Leechers { get; set; }
|
||||||
public DownloadProtocol Protocol { get; set; }
|
public DownloadProtocol Protocol { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public bool IsDaily { get; set; }
|
public bool IsDaily { get; set; }
|
||||||
public bool IsAbsoluteNumbering { get; set; }
|
public bool IsAbsoluteNumbering { get; set; }
|
||||||
public bool IsPossibleSpecialEpisode { get; set; }
|
public bool IsPossibleSpecialEpisode { get; set; }
|
||||||
public bool Special { get; set; }
|
public bool Special { get; set; }
|
||||||
|
|
||||||
|
// Sent when queuing an unknown release
|
||||||
|
|
||||||
|
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||||
|
public int? MovieId { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ReleaseResourceMapper
|
public static class ReleaseResourceMapper
|
||||||
@@ -88,7 +94,7 @@ namespace Radarr.Api.V2.Indexers
|
|||||||
CommentUrl = releaseInfo.CommentUrl,
|
CommentUrl = releaseInfo.CommentUrl,
|
||||||
DownloadUrl = releaseInfo.DownloadUrl,
|
DownloadUrl = releaseInfo.DownloadUrl,
|
||||||
InfoUrl = releaseInfo.InfoUrl,
|
InfoUrl = releaseInfo.InfoUrl,
|
||||||
// DownloadAllowed = remoteMovie.DownloadAllowed,
|
DownloadAllowed = remoteMovie.DownloadAllowed,
|
||||||
//ReleaseWeight
|
//ReleaseWeight
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user