mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-25 17:35:35 -04:00
Compare commits
89 Commits
v3.1.0.489
...
v3.2.2.508
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bca1a71a2 | ||
|
|
4f009bb81d | ||
|
|
bdc7733faf | ||
|
|
7f4be53db0 | ||
|
|
27d998d6f2 | ||
|
|
5eb593f453 | ||
|
|
4652db0583 | ||
|
|
35d43480bf | ||
|
|
3e7c136a7f | ||
|
|
05f9f6b413 | ||
|
|
67f6eb544a | ||
|
|
3c11e934a8 | ||
|
|
627a39b8fc | ||
|
|
95c7b96dff | ||
|
|
dadd59fc3a | ||
|
|
e67d3d3666 | ||
|
|
f4718243ed | ||
|
|
fcec787eb6 | ||
|
|
5fe8f65d64 | ||
|
|
c2a21cd238 | ||
|
|
a31ca4e80b | ||
|
|
db14ac4605 | ||
|
|
5f229b78be | ||
|
|
543f2e7ddc | ||
|
|
d6b7ab6260 | ||
|
|
d7ab9292fb | ||
|
|
4300d8d8c6 | ||
|
|
446b2ffff9 | ||
|
|
695720b552 | ||
|
|
c47934c5ca | ||
|
|
9938737cd7 | ||
|
|
58326f05e0 | ||
|
|
04ad5ec9c0 | ||
|
|
2c008384dd | ||
|
|
a9b605c872 | ||
|
|
cd9b469823 | ||
|
|
df4bfa501c | ||
|
|
194a1e5154 | ||
|
|
e53b2bb83c | ||
|
|
10772c09ef | ||
|
|
f9ed15409a | ||
|
|
f75ab93458 | ||
|
|
7755a8bd3b | ||
|
|
017c7998be | ||
|
|
f40ddfef10 | ||
|
|
80049909eb | ||
|
|
fc22264f89 | ||
|
|
aba2e10b5c | ||
|
|
4b6874d551 | ||
|
|
58934a30ce | ||
|
|
33a960f325 | ||
|
|
8560ff43fe | ||
|
|
c88a47b275 | ||
|
|
67fe9101d9 | ||
|
|
af99c78352 | ||
|
|
df3253f55c | ||
|
|
b9abc1be11 | ||
|
|
5c0ee04271 | ||
|
|
5e2cd3798b | ||
|
|
83041b1d37 | ||
|
|
9f6c48191b | ||
|
|
5696fa2efe | ||
|
|
d38311b717 | ||
|
|
aa522066ee | ||
|
|
d2ba70c4d7 | ||
|
|
ca2e62492d | ||
|
|
02bcb4d865 | ||
|
|
36962f176f | ||
|
|
0a2afe692f | ||
|
|
5140ee8f2e | ||
|
|
e47ceae0c5 | ||
|
|
906f8c1049 | ||
|
|
27e871656e | ||
|
|
1ff147c541 | ||
|
|
7028bfb082 | ||
|
|
efdb9c20d4 | ||
|
|
9bf872c9fa | ||
|
|
feaeda9186 | ||
|
|
e9cf9d05f5 | ||
|
|
a1211f54de | ||
|
|
c7b5e6204c | ||
|
|
788404ee27 | ||
|
|
8ef4429b9e | ||
|
|
7c5fc1e4b0 | ||
|
|
c66f7abea5 | ||
|
|
ce6f52552a | ||
|
|
7807e2e13a | ||
|
|
d5aa73fe8f | ||
|
|
cb5bf86d8e |
2
.github/config.yml
vendored
2
.github/config.yml
vendored
@@ -1,2 +0,0 @@
|
||||
todo:
|
||||
keyword: "TODO"
|
||||
1
.github/reaction.yml
vendored
1
.github/reaction.yml
vendored
@@ -1 +0,0 @@
|
||||
|
||||
13
.github/support.yml
vendored
13
.github/support.yml
vendored
@@ -1,13 +0,0 @@
|
||||
# Configuration for support-requests - https://github.com/dessant/support-requests
|
||||
|
||||
# Label used to mark issues as support requests
|
||||
supportLabel: 'Type: Support'
|
||||
# Comment to post on issues marked as support requests. Add a link
|
||||
# to a support page, or set to `false` to disable
|
||||
supportComment: >
|
||||
We use the issue tracker exclusively for bug reports and feature requests.
|
||||
However, this issue appears to be a support request. Please hop over onto our [Discord](https://discord.gg/r5wJPt9) or [Subreddit](https://reddit.com/r/radarr)
|
||||
# Whether to close issues marked as support requests
|
||||
close: true
|
||||
# Whether to lock issues marked as support requests
|
||||
lock: false
|
||||
21
.github/workflows/support.yml
vendored
Normal file
21
.github/workflows/support.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: 'Support requests'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled, unlabeled, reopened]
|
||||
|
||||
jobs:
|
||||
support:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/support-requests@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
support-label: 'Type: Support'
|
||||
issue-comment: >
|
||||
:wave: @{issue-author}, we use the issue tracker exclusively
|
||||
for bug reports and feature requests. However, this issue appears
|
||||
to be a support request. Please hop over onto our [Discord](https://radarr.video/discord)
|
||||
or [Subreddit](https://reddit.com/r/radarr)
|
||||
close-issue: true
|
||||
lock-issue: false
|
||||
@@ -7,7 +7,7 @@ variables:
|
||||
outputFolder: './_output'
|
||||
artifactsFolder: './_artifacts'
|
||||
testsFolder: './_tests'
|
||||
majorVersion: '3.1.0'
|
||||
majorVersion: '3.2.2'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
@@ -23,7 +23,12 @@ trigger:
|
||||
- master
|
||||
|
||||
pr:
|
||||
- develop
|
||||
branches:
|
||||
include:
|
||||
- develop
|
||||
paths:
|
||||
exclude:
|
||||
- src/NzbDrone.Core/Localization/Core
|
||||
|
||||
stages:
|
||||
- stage: Setup
|
||||
@@ -651,7 +656,7 @@ stages:
|
||||
testResultsFormat: 'NUnit'
|
||||
testResultsFiles: '**/TestResult.xml'
|
||||
testRunTitle: 'FreeBSD Integration Tests'
|
||||
failTaskOnFailedTests: false
|
||||
failTaskOnFailedTests: true
|
||||
displayName: Publish Test Results
|
||||
|
||||
- job: Integration_Docker
|
||||
|
||||
10
build.sh
10
build.sh
@@ -87,11 +87,11 @@ YarnInstall()
|
||||
ProgressEnd 'yarn install'
|
||||
}
|
||||
|
||||
RunGulp()
|
||||
RunWebpack()
|
||||
{
|
||||
ProgressStart 'Running gulp'
|
||||
yarn run build --production
|
||||
ProgressEnd 'Running gulp'
|
||||
ProgressStart 'Running webpack'
|
||||
yarn run build --env production
|
||||
ProgressEnd 'Running webpack'
|
||||
}
|
||||
|
||||
PackageFiles()
|
||||
@@ -350,7 +350,7 @@ fi
|
||||
if [ "$FRONTEND" = "YES" ];
|
||||
then
|
||||
YarnInstall
|
||||
RunGulp
|
||||
RunWebpack
|
||||
fi
|
||||
|
||||
if [ "$LINT" = "YES" ];
|
||||
|
||||
@@ -6,8 +6,10 @@ const dirs = fs
|
||||
.map((dirent) => dirent.name)
|
||||
.join('|');
|
||||
|
||||
const frontendFolder = __dirname;
|
||||
|
||||
module.exports = {
|
||||
parser: 'babel-eslint',
|
||||
parser: '@babel/eslint-parser',
|
||||
|
||||
env: {
|
||||
browser: true,
|
||||
@@ -25,6 +27,9 @@ module.exports = {
|
||||
parserOptions: {
|
||||
ecmaVersion: 6,
|
||||
sourceType: 'module',
|
||||
babelOptions: {
|
||||
configFile: `${frontendFolder}/babel.config.js`
|
||||
},
|
||||
ecmaFeatures: {
|
||||
modules: true,
|
||||
impliedStrict: true
|
||||
@@ -271,7 +276,7 @@ module.exports = {
|
||||
|
||||
// ImportSort
|
||||
|
||||
'simple-import-sort/sort': 'error',
|
||||
'simple-import-sort/imports': 'error',
|
||||
'import/newline-after-import': 'error',
|
||||
|
||||
// React
|
||||
@@ -309,7 +314,7 @@ module.exports = {
|
||||
{
|
||||
files: ['*.js'],
|
||||
rules: {
|
||||
'simple-import-sort/sort': [
|
||||
'simple-import-sort/imports': [
|
||||
'error',
|
||||
{
|
||||
groups: [
|
||||
|
||||
269
frontend/build/webpack.config.js
Normal file
269
frontend/build/webpack.config.js
Normal file
@@ -0,0 +1,269 @@
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const LiveReloadPlugin = require('webpack-livereload-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
|
||||
module.exports = (env) => {
|
||||
const uiFolder = 'UI';
|
||||
const frontendFolder = path.join(__dirname, '..');
|
||||
const srcFolder = path.join(frontendFolder, 'src');
|
||||
const isProduction = !!env.production;
|
||||
const isProfiling = isProduction && !!env.profile;
|
||||
const inlineWebWorkers = 'no-fallback';
|
||||
|
||||
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
|
||||
|
||||
console.log('Source Folder:', srcFolder);
|
||||
console.log('Output Folder:', distFolder);
|
||||
console.log('isProduction:', isProduction);
|
||||
console.log('isProfiling:', isProfiling);
|
||||
|
||||
const config = {
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
devtool: 'source-map',
|
||||
|
||||
stats: {
|
||||
children: false
|
||||
},
|
||||
|
||||
watchOptions: {
|
||||
ignored: /node_modules/
|
||||
},
|
||||
|
||||
entry: {
|
||||
index: 'index.js'
|
||||
},
|
||||
|
||||
resolve: {
|
||||
modules: [
|
||||
srcFolder,
|
||||
path.join(srcFolder, 'Shims'),
|
||||
'node_modules'
|
||||
],
|
||||
alias: {
|
||||
jquery: 'jquery/src/jquery'
|
||||
},
|
||||
fallback: {
|
||||
buffer: false,
|
||||
http: false,
|
||||
https: false,
|
||||
url: false,
|
||||
util: false,
|
||||
net: false
|
||||
}
|
||||
},
|
||||
|
||||
output: {
|
||||
path: distFolder,
|
||||
publicPath: '/',
|
||||
filename: '[name].js',
|
||||
sourceMapFilename: '[file].map'
|
||||
},
|
||||
|
||||
optimization: {
|
||||
moduleIds: 'deterministic',
|
||||
chunkIds: 'named',
|
||||
splitChunks: {
|
||||
chunks: 'initial',
|
||||
name: 'vendors'
|
||||
}
|
||||
},
|
||||
|
||||
performance: {
|
||||
hints: false
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
__DEV__: !isProduction,
|
||||
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
|
||||
}),
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'Content/styles.css'
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'frontend/src/index.html',
|
||||
filename: 'index.html',
|
||||
publicPath: '/'
|
||||
}),
|
||||
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
// HTML
|
||||
{
|
||||
from: 'frontend/src/*.html',
|
||||
to: path.join(distFolder, '[name][ext]'),
|
||||
globOptions: {
|
||||
ignore: ['**/index.html']
|
||||
}
|
||||
},
|
||||
|
||||
// Fonts
|
||||
{
|
||||
from: 'frontend/src/Content/Fonts/*.*',
|
||||
to: path.join(distFolder, 'Content/Fonts', '[name][ext]')
|
||||
},
|
||||
|
||||
// Icon Images
|
||||
{
|
||||
from: 'frontend/src/Content/Images/Icons/*.*',
|
||||
to: path.join(distFolder, 'Content/Images/Icons', '[name][ext]')
|
||||
},
|
||||
|
||||
// Images
|
||||
{
|
||||
from: 'frontend/src/Content/Images/*.*',
|
||||
to: path.join(distFolder, 'Content/Images', '[name][ext]')
|
||||
},
|
||||
|
||||
// Robots
|
||||
{
|
||||
from: 'frontend/src/Content/robots.txt',
|
||||
to: path.join(distFolder, 'Content', '[name][ext]')
|
||||
}
|
||||
]
|
||||
}),
|
||||
|
||||
new LiveReloadPlugin()
|
||||
],
|
||||
|
||||
resolveLoader: {
|
||||
modules: [
|
||||
'node_modules',
|
||||
'frontend/build/webpack/'
|
||||
]
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.worker\.js$/,
|
||||
use: {
|
||||
loader: 'worker-loader',
|
||||
options: {
|
||||
filename: '[name].js',
|
||||
inline: inlineWebWorkers
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js?$/,
|
||||
exclude: /(node_modules|JsLibraries)/,
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
configFile: `${frontendFolder}/babel.config.js`,
|
||||
envName: isProduction ? 'production' : 'development',
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
modules: false,
|
||||
loose: true,
|
||||
debug: false,
|
||||
useBuiltIns: 'entry',
|
||||
corejs: 3
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// CSS Modules
|
||||
{
|
||||
test: /\.css$/,
|
||||
exclude: /(node_modules|globals.css)/,
|
||||
use: [
|
||||
{ loader: MiniCssExtractPlugin.loader },
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: {
|
||||
localIdentName: '[name]/[local]/[hash:base64:5]'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
postcssOptions: {
|
||||
config: 'frontend/postcss.config.js'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Global styles
|
||||
{
|
||||
test: /\.css$/,
|
||||
include: /(node_modules|globals.css)/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Fonts
|
||||
{
|
||||
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10240,
|
||||
mimetype: 'application/font-woff',
|
||||
emitFile: false,
|
||||
name: 'Content/Fonts/[name].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
emitFile: false,
|
||||
name: 'Content/Fonts/[name].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
if (isProfiling) {
|
||||
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
|
||||
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
|
||||
|
||||
config.optimization.minimizer = [
|
||||
new TerserPlugin({
|
||||
cache: true,
|
||||
parallel: true,
|
||||
sourceMap: true, // Must be set to true if using source-maps in production
|
||||
terserOptions: {
|
||||
mangle: false,
|
||||
keep_classnames: true,
|
||||
keep_fnames: true
|
||||
}
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
return config;
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
const gulp = require('gulp');
|
||||
|
||||
require('./clean');
|
||||
require('./copy');
|
||||
require('./webpack');
|
||||
|
||||
gulp.task('build',
|
||||
gulp.series('clean',
|
||||
gulp.parallel(
|
||||
'webpack',
|
||||
'copyHtml',
|
||||
'copyFonts',
|
||||
'copyImages',
|
||||
'copyRobots'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
const gulp = require('gulp');
|
||||
const del = require('del');
|
||||
|
||||
const paths = require('./helpers/paths');
|
||||
|
||||
gulp.task('clean', () => {
|
||||
return del([paths.dest.root]);
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
const path = require('path');
|
||||
const gulp = require('gulp');
|
||||
const print = require('gulp-print').default;
|
||||
const cache = require('gulp-cached');
|
||||
const livereload = require('gulp-livereload');
|
||||
const paths = require('./helpers/paths.js');
|
||||
|
||||
gulp.task('copyHtml', () => {
|
||||
return gulp.src(paths.src.html, { base: paths.src.root })
|
||||
.pipe(cache('copyHtml'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.root))
|
||||
.pipe(livereload());
|
||||
});
|
||||
|
||||
gulp.task('copyFonts', () => {
|
||||
return gulp.src(
|
||||
path.join(paths.src.fonts, '**', '*.*'), { base: paths.src.root }
|
||||
)
|
||||
.pipe(cache('copyFonts'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.root))
|
||||
.pipe(livereload());
|
||||
});
|
||||
|
||||
gulp.task('copyImages', () => {
|
||||
return gulp.src(
|
||||
path.join(paths.src.images, '**', '*.*'), { base: paths.src.root }
|
||||
)
|
||||
.pipe(cache('copyImages'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.root))
|
||||
.pipe(livereload());
|
||||
});
|
||||
|
||||
gulp.task('copyRobots', () => {
|
||||
return gulp.src(paths.src.robots, { base: paths.src.root })
|
||||
.pipe(cache('copyRobots'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.root))
|
||||
.pipe(livereload());
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
require('./build.js');
|
||||
require('./clean.js');
|
||||
require('./copy.js');
|
||||
require('./watch.js');
|
||||
require('./webpack.js');
|
||||
@@ -1,6 +0,0 @@
|
||||
const colors = require('ansi-colors');
|
||||
|
||||
module.exports = function errorHandler(error) {
|
||||
console.log(colors.red(`Error (${error.plugin}): ${error.message}`));
|
||||
this.emit('end');
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
const root = './frontend/src';
|
||||
|
||||
const paths = {
|
||||
src: {
|
||||
root,
|
||||
html: `${root}/*.html`,
|
||||
scripts: `${root}/**/*.js`,
|
||||
content: `${root}/Content/`,
|
||||
fonts: `${root}/Content/Fonts/`,
|
||||
images: `${root}/Content/Images/`,
|
||||
robots: `${root}/Content/robots.txt`,
|
||||
exclude: {
|
||||
libs: `!${root}/JsLibraries/**`
|
||||
}
|
||||
},
|
||||
dest: {
|
||||
root: './_output/UI/',
|
||||
content: './_output/UI/Content/',
|
||||
fonts: './_output/UI/Content/Fonts/',
|
||||
images: './_output/UI/Content/Images/'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = paths;
|
||||
@@ -1,19 +0,0 @@
|
||||
const gulp = require('gulp');
|
||||
const livereload = require('gulp-livereload');
|
||||
const gulpWatch = require('gulp-watch');
|
||||
const paths = require('./helpers/paths.js');
|
||||
|
||||
require('./copy.js');
|
||||
require('./webpack.js');
|
||||
|
||||
function watch() {
|
||||
livereload.listen({ start: true });
|
||||
|
||||
gulp.task('webpackWatch')();
|
||||
gulpWatch(paths.src.html, gulp.series('copyHtml'));
|
||||
gulpWatch(`${paths.src.fonts}**/*.*`, gulp.series('copyFonts'));
|
||||
gulpWatch(`${paths.src.images}**/*.*`, gulp.series('copyImages'));
|
||||
gulpWatch(paths.src.robots, gulp.series('copyRobots'));
|
||||
}
|
||||
|
||||
gulp.task('watch', gulp.series('build', watch));
|
||||
@@ -1,275 +0,0 @@
|
||||
const gulp = require('gulp');
|
||||
const webpackStream = require('webpack-stream');
|
||||
const livereload = require('gulp-livereload');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const errorHandler = require('./helpers/errorHandler');
|
||||
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const HtmlWebpackPluginHtmlTags = require('html-webpack-plugin/lib/html-tags');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
|
||||
const uiFolder = 'UI';
|
||||
const frontendFolder = path.join(__dirname, '..');
|
||||
const srcFolder = path.join(frontendFolder, 'src');
|
||||
const isProduction = process.argv.indexOf('--production') > -1;
|
||||
const isProfiling = isProduction && process.argv.indexOf('--profile') > -1;
|
||||
const inlineWebWorkers = 'no-fallback';
|
||||
|
||||
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
|
||||
|
||||
console.log('Source Folder:', srcFolder);
|
||||
console.log('Output Folder:', distFolder);
|
||||
console.log('isProduction:', isProduction);
|
||||
console.log('isProfiling:', isProfiling);
|
||||
|
||||
const cssVarsFiles = [
|
||||
'../src/Styles/Variables/colors',
|
||||
'../src/Styles/Variables/dimensions',
|
||||
'../src/Styles/Variables/fonts',
|
||||
'../src/Styles/Variables/animations',
|
||||
'../src/Styles/Variables/zIndexes'
|
||||
].map(require.resolve);
|
||||
|
||||
// Override the way HtmlWebpackPlugin injects the scripts
|
||||
// TODO: Find a better way to get these paths without
|
||||
HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, assets, assetTags) {
|
||||
const head = assetTags.headTags.map((v) => {
|
||||
const href = v.attributes.href
|
||||
.replace('\\', '/')
|
||||
.replace('%5C', '/');
|
||||
|
||||
v.attributes = { rel: 'stylesheet', type: 'text/css', href: `/${href}` };
|
||||
return HtmlWebpackPluginHtmlTags.htmlTagObjectToString(v, this.options.xhtml);
|
||||
});
|
||||
const body = assetTags.bodyTags.map((v) => {
|
||||
v.attributes = { src: `/${v.attributes.src}` };
|
||||
return HtmlWebpackPluginHtmlTags.htmlTagObjectToString(v, this.options.xhtml);
|
||||
});
|
||||
|
||||
return html
|
||||
.replace('<!-- webpack bundles head -->', head.join('\r\n '))
|
||||
.replace('<!-- webpack bundles body -->', body.join('\r\n '));
|
||||
};
|
||||
|
||||
const plugins = [
|
||||
new webpack.IgnorePlugin({
|
||||
resourceRegExp: /(fetch-cookie|node-fetch|tough-cookie)/
|
||||
}),
|
||||
|
||||
new OptimizeCssAssetsPlugin({}),
|
||||
|
||||
new webpack.DefinePlugin({
|
||||
__DEV__: !isProduction,
|
||||
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
|
||||
}),
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
filename: path.join('Content', 'styles.css')
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'frontend/src/index.html',
|
||||
filename: 'index.html'
|
||||
})
|
||||
];
|
||||
|
||||
const config = {
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
devtool: '#source-map',
|
||||
|
||||
stats: {
|
||||
children: false
|
||||
},
|
||||
|
||||
watchOptions: {
|
||||
ignored: /node_modules/
|
||||
},
|
||||
|
||||
entry: {
|
||||
index: 'index.js'
|
||||
},
|
||||
|
||||
resolve: {
|
||||
modules: [
|
||||
srcFolder,
|
||||
path.join(srcFolder, 'Shims'),
|
||||
'node_modules'
|
||||
],
|
||||
alias: {
|
||||
jquery: 'jquery/src/jquery'
|
||||
}
|
||||
},
|
||||
|
||||
output: {
|
||||
path: distFolder,
|
||||
filename: '[name].js',
|
||||
sourceMapFilename: '[file].map'
|
||||
},
|
||||
|
||||
optimization: {
|
||||
chunkIds: 'named',
|
||||
splitChunks: {
|
||||
chunks: 'initial'
|
||||
}
|
||||
},
|
||||
|
||||
performance: {
|
||||
hints: false
|
||||
},
|
||||
|
||||
plugins,
|
||||
|
||||
resolveLoader: {
|
||||
modules: [
|
||||
'node_modules',
|
||||
'frontend/gulp/webpack/'
|
||||
]
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.worker\.js$/,
|
||||
use: {
|
||||
loader: 'worker-loader',
|
||||
options: {
|
||||
filename: '[name].js',
|
||||
inline: inlineWebWorkers
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js?$/,
|
||||
exclude: /(node_modules|JsLibraries)/,
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
configFile: `${frontendFolder}/babel.config.js`,
|
||||
envName: isProduction ? 'production' : 'development',
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
modules: false,
|
||||
loose: true,
|
||||
debug: false,
|
||||
useBuiltIns: 'entry',
|
||||
corejs: 3
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// CSS Modules
|
||||
{
|
||||
test: /\.css$/,
|
||||
exclude: /(node_modules|globals.css)/,
|
||||
use: [
|
||||
{ loader: MiniCssExtractPlugin.loader },
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: {
|
||||
localIdentName: '[name]/[local]/[hash:base64:5]'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
ident: 'postcss',
|
||||
config: {
|
||||
ctx: {
|
||||
cssVarsFiles
|
||||
},
|
||||
path: 'frontend/postcss.config.js'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Global styles
|
||||
{
|
||||
test: /\.css$/,
|
||||
include: /(node_modules|globals.css)/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Fonts
|
||||
{
|
||||
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10240,
|
||||
mimetype: 'application/font-woff',
|
||||
emitFile: false,
|
||||
name: 'Content/Fonts/[name].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
emitFile: false,
|
||||
name: 'Content/Fonts/[name].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
if (isProfiling) {
|
||||
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
|
||||
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
|
||||
|
||||
config.optimization.minimizer = [
|
||||
new TerserPlugin({
|
||||
cache: true,
|
||||
parallel: true,
|
||||
sourceMap: true, // Must be set to true if using source-maps in production
|
||||
terserOptions: {
|
||||
mangle: false,
|
||||
keep_classnames: true,
|
||||
keep_fnames: true
|
||||
}
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
gulp.task('webpack', () => {
|
||||
return webpackStream(config)
|
||||
.pipe(gulp.dest('_output/UI'));
|
||||
});
|
||||
|
||||
gulp.task('webpackWatch', () => {
|
||||
config.watch = true;
|
||||
|
||||
return webpackStream(config, webpack)
|
||||
.on('error', errorHandler)
|
||||
.pipe(gulp.dest('_output/UI'))
|
||||
.on('error', errorHandler)
|
||||
.pipe(livereload())
|
||||
.on('error', errorHandler);
|
||||
});
|
||||
@@ -1,23 +1,32 @@
|
||||
const reload = require('require-nocache')(module);
|
||||
|
||||
module.exports = (ctx, configPath, options) => {
|
||||
const config = {
|
||||
plugins: {
|
||||
'postcss-mixins': {
|
||||
mixinsDir: [
|
||||
'frontend/src/Styles/Mixins'
|
||||
]
|
||||
},
|
||||
'postcss-simple-vars': {
|
||||
variables: () =>
|
||||
ctx.options.cssVarsFiles.reduce((acc, vars) => {
|
||||
return Object.assign(acc, reload(vars));
|
||||
}, {})
|
||||
},
|
||||
'postcss-color-function': {},
|
||||
'postcss-nested': {}
|
||||
}
|
||||
};
|
||||
const cssVarsFiles = [
|
||||
'./src/Styles/Variables/colors',
|
||||
'./src/Styles/Variables/dimensions',
|
||||
'./src/Styles/Variables/fonts',
|
||||
'./src/Styles/Variables/animations',
|
||||
'./src/Styles/Variables/zIndexes'
|
||||
].map(require.resolve);
|
||||
|
||||
return config;
|
||||
};
|
||||
const mixinsFiles = [
|
||||
'frontend/src/Styles/Mixins/cover.css',
|
||||
'frontend/src/Styles/Mixins/linkOverlay.css',
|
||||
'frontend/src/Styles/Mixins/scroller.css',
|
||||
'frontend/src/Styles/Mixins/truncate.css'
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
['postcss-mixins', {
|
||||
mixinsFiles
|
||||
}],
|
||||
['postcss-simple-vars', {
|
||||
variables: () =>
|
||||
cssVarsFiles.reduce((acc, vars) => {
|
||||
return Object.assign(acc, reload(vars));
|
||||
}, {})
|
||||
}],
|
||||
'postcss-color-function',
|
||||
'postcss-nested'
|
||||
]
|
||||
};
|
||||
@@ -43,6 +43,7 @@ class QueueConnector extends Component {
|
||||
const {
|
||||
useCurrentPage,
|
||||
fetchQueue,
|
||||
fetchQueueStatus,
|
||||
gotoQueueFirstPage
|
||||
} = this.props;
|
||||
|
||||
@@ -53,6 +54,8 @@ class QueueConnector extends Component {
|
||||
} else {
|
||||
gotoQueueFirstPage();
|
||||
}
|
||||
|
||||
fetchQueueStatus();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -152,6 +155,7 @@ QueueConnector.propTypes = {
|
||||
useCurrentPage: PropTypes.bool.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchQueue: PropTypes.func.isRequired,
|
||||
fetchQueueStatus: PropTypes.func.isRequired,
|
||||
gotoQueueFirstPage: PropTypes.func.isRequired,
|
||||
gotoQueuePreviousPage: PropTypes.func.isRequired,
|
||||
gotoQueueNextPage: PropTypes.func.isRequired,
|
||||
|
||||
@@ -92,6 +92,19 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.certification {
|
||||
margin-left: 2px;
|
||||
padding: 0 5px;
|
||||
border: 1px solid;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.runtime {
|
||||
margin-left: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.statusContainer {
|
||||
margin-right: 22px;
|
||||
font-weight: bold;
|
||||
@@ -103,10 +116,3 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.certification {
|
||||
margin-right: 5px;
|
||||
padding: 0 5px;
|
||||
border: 1px solid;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ class AddNewMovieSearchResult extends Component {
|
||||
|
||||
{
|
||||
!!runtime &&
|
||||
<span>
|
||||
<span className={styles.runtime}>
|
||||
{formatRuntime(runtime, movieRuntimeFormat)}
|
||||
</span>
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ function createTagListSelector() {
|
||||
(selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ||
|
||||
selectedFilterBuilderProp.type === filterBuilderTypes.STRING) &&
|
||||
filterType !== filterTypes.EQUAL &&
|
||||
filterType !== filterBuilderTypes.NOT_EQUAL ||
|
||||
filterType !== filterTypes.NOT_EQUAL ||
|
||||
!selectedFilterBuilderProp.optionsSelector
|
||||
) {
|
||||
return [];
|
||||
|
||||
@@ -479,6 +479,7 @@ class EnhancedSelectInput extends Component {
|
||||
<OptionComponent
|
||||
key={v.key}
|
||||
id={v.key}
|
||||
dividerAfter={v.dividerAfter}
|
||||
depth={depth}
|
||||
isSelected={isSelectedItem(index, this.props)}
|
||||
isDisabled={parentSelected}
|
||||
@@ -539,6 +540,7 @@ class EnhancedSelectInput extends Component {
|
||||
<OptionComponent
|
||||
key={v.key}
|
||||
id={v.key}
|
||||
dividerAfter={v.dividerAfter}
|
||||
depth={depth}
|
||||
isSelected={isSelectedItem(index, this.props)}
|
||||
isMultiSelect={isMultiSelect}
|
||||
|
||||
@@ -13,6 +13,7 @@ import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
|
||||
import FormInputHelpText from './FormInputHelpText';
|
||||
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
|
||||
import KeyValueListInput from './KeyValueListInput';
|
||||
import LanguageSelectInputConnector from './LanguageSelectInputConnector';
|
||||
import MovieMonitoredSelectInput from './MovieMonitoredSelectInput';
|
||||
import NumberInput from './NumberInput';
|
||||
import OAuthInputConnector from './OAuthInputConnector';
|
||||
@@ -72,6 +73,9 @@ function getComponent(type) {
|
||||
case inputTypes.INDEXER_FLAGS_SELECT:
|
||||
return IndexerFlagsSelectInputConnector;
|
||||
|
||||
case inputTypes.LANGUAGE_SELECT:
|
||||
return LanguageSelectInputConnector;
|
||||
|
||||
case inputTypes.SELECT:
|
||||
return EnhancedSelectInput;
|
||||
|
||||
|
||||
@@ -21,3 +21,8 @@
|
||||
color: $darkGray;
|
||||
font-size: $smallFontSize;
|
||||
}
|
||||
|
||||
.divider {
|
||||
border: none;
|
||||
border-bottom: 1px solid $lightGray;
|
||||
}
|
||||
|
||||
@@ -12,37 +12,46 @@ function HintedSelectInputOption(props) {
|
||||
depth,
|
||||
isSelected,
|
||||
isDisabled,
|
||||
dividerAfter,
|
||||
isMultiSelect,
|
||||
isMobile,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<EnhancedSelectInputOption
|
||||
id={id}
|
||||
depth={depth}
|
||||
isSelected={isSelected}
|
||||
isDisabled={isDisabled}
|
||||
isHidden={isDisabled}
|
||||
isMultiSelect={isMultiSelect}
|
||||
isMobile={isMobile}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={classNames(
|
||||
styles.optionText,
|
||||
isMobile && styles.isMobile
|
||||
)}
|
||||
<div>
|
||||
<EnhancedSelectInputOption
|
||||
id={id}
|
||||
depth={depth}
|
||||
isSelected={isSelected}
|
||||
isDisabled={isDisabled}
|
||||
isHidden={isDisabled}
|
||||
isMultiSelect={isMultiSelect}
|
||||
isMobile={isMobile}
|
||||
{...otherProps}
|
||||
>
|
||||
<div>{value}</div>
|
||||
<div className={classNames(
|
||||
styles.optionText,
|
||||
isMobile && styles.isMobile
|
||||
)}
|
||||
>
|
||||
<div>{value}</div>
|
||||
|
||||
{
|
||||
hint != null &&
|
||||
<div className={styles.hintText}>
|
||||
{hint}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</EnhancedSelectInputOption>
|
||||
{
|
||||
hint != null &&
|
||||
<div className={styles.hintText}>
|
||||
{hint}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</EnhancedSelectInputOption>
|
||||
|
||||
{
|
||||
dividerAfter ?
|
||||
<div className={styles.divider} /> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,15 +59,18 @@ HintedSelectInputOption.propTypes = {
|
||||
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
hint: PropTypes.node,
|
||||
name: PropTypes.string,
|
||||
depth: PropTypes.number,
|
||||
isSelected: PropTypes.bool.isRequired,
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
dividerAfter: PropTypes.bool.isRequired,
|
||||
isMultiSelect: PropTypes.bool.isRequired,
|
||||
isMobile: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
HintedSelectInputOption.defaultProps = {
|
||||
isDisabled: false,
|
||||
dividerAfter: false,
|
||||
isHidden: false,
|
||||
isMultiSelect: false
|
||||
};
|
||||
|
||||
52
frontend/src/Components/Form/LanguageSelectInputConnector.js
Normal file
52
frontend/src/Components/Form/LanguageSelectInputConnector.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { values }) => values,
|
||||
( languages ) => {
|
||||
|
||||
const minId = languages.reduce((min, v) => (v.key < 1 ? v.key : min), languages[0].key);
|
||||
|
||||
const values = languages.map(({ key, value }) => {
|
||||
return {
|
||||
key,
|
||||
value,
|
||||
dividerAfter: minId < 1 ? key === minId : false
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
values
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
class LanguageSelectInputConnector extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<EnhancedSelectInput
|
||||
{...this.props}
|
||||
onChange={this.props.onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LanguageSelectInputConnector.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.number]).isRequired,
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps)(LanguageSelectInputConnector);
|
||||
@@ -49,6 +49,7 @@ function getSelectValues(selectOptions) {
|
||||
result.push({
|
||||
key: option.value,
|
||||
value: option.name,
|
||||
dividerAfter: option.dividerAfter,
|
||||
hint: option.hint
|
||||
});
|
||||
|
||||
|
||||
@@ -38,11 +38,12 @@ class Link extends Component {
|
||||
const linkProps = { target };
|
||||
let el = component;
|
||||
|
||||
if (to) {
|
||||
if (to && typeof to === 'string') {
|
||||
if ((/\w+?:\/\//).test(to)) {
|
||||
el = 'a';
|
||||
linkProps.href = to;
|
||||
linkProps.target = target || '_blank';
|
||||
linkProps.rel = 'noreferrer';
|
||||
} else if (noRouter) {
|
||||
el = 'a';
|
||||
linkProps.href = to;
|
||||
@@ -52,6 +53,18 @@ class Link extends Component {
|
||||
linkProps.to = `${window.Radarr.urlBase}/${to.replace(/^\//, '')}`;
|
||||
linkProps.target = target;
|
||||
}
|
||||
} else if (to && typeof to === 'object') {
|
||||
el = RouterLink;
|
||||
linkProps.target = target;
|
||||
if (to.pathname.startsWith(`${window.Radarr.urlBase}/`)) {
|
||||
linkProps.to = to;
|
||||
} else {
|
||||
const pathname = `${window.Radarr.urlBase}/${to.pathname.replace(/^\//, '')}`;
|
||||
linkProps.to = {
|
||||
...to,
|
||||
pathname
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (el === 'button' || el === 'input') {
|
||||
@@ -82,7 +95,7 @@ class Link extends Component {
|
||||
Link.propTypes = {
|
||||
className: PropTypes.string,
|
||||
component: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||
to: PropTypes.string,
|
||||
to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
target: PropTypes.string,
|
||||
isDisabled: PropTypes.bool,
|
||||
noRouter: PropTypes.bool,
|
||||
|
||||
@@ -53,7 +53,13 @@ class PageHeader extends Component {
|
||||
return (
|
||||
<div className={styles.header}>
|
||||
<div className={styles.logoContainer}>
|
||||
<Link to={'/'}>
|
||||
<Link
|
||||
className={styles.logoLink}
|
||||
to={{
|
||||
pathname: '/',
|
||||
state: { restoreScrollPosition: true }
|
||||
}}
|
||||
>
|
||||
<img
|
||||
className={isSmallScreen ? styles.logo : styles.logoFull}
|
||||
src={isSmallScreen ? `${window.Radarr.urlBase}/Content/Images/logo.png` : `${window.Radarr.urlBase}/Content/Images/logo-full.png`}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { DndProvider } from 'react-dnd-multi-backend';
|
||||
import HTML5toTouch from 'react-dnd-multi-backend/dist/esm/HTML5toTouch';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
@@ -128,7 +128,7 @@ class TableOptionsModal extends Component {
|
||||
const isDraggingDown = isDragging && dropIndex > dragIndex;
|
||||
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<DndProvider options={HTML5toTouch}>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
|
||||
@@ -39,7 +39,8 @@ class VirtualTable extends Component {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
width: 0
|
||||
width: 0,
|
||||
scrollRestored: false
|
||||
};
|
||||
|
||||
this._grid = null;
|
||||
@@ -48,11 +49,13 @@ class VirtualTable extends Component {
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const {
|
||||
items,
|
||||
scrollIndex
|
||||
scrollIndex,
|
||||
scrollTop
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
width
|
||||
width,
|
||||
scrollRestored
|
||||
} = this.state;
|
||||
|
||||
if (this._grid &&
|
||||
@@ -68,6 +71,11 @@ class VirtualTable extends Component {
|
||||
columnIndex: 0
|
||||
});
|
||||
}
|
||||
|
||||
if (this._grid && scrollTop !== undefined && scrollTop !== 0 && !scrollRestored) {
|
||||
this.setState({ scrollRestored: true });
|
||||
this._grid.scrollToPosition({ scrollTop });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -96,6 +104,7 @@ class VirtualTable extends Component {
|
||||
items,
|
||||
scroller,
|
||||
focusScroller,
|
||||
scrollTop: ignored,
|
||||
header,
|
||||
headerHeight,
|
||||
rowRenderer,
|
||||
@@ -180,6 +189,7 @@ VirtualTable.propTypes = {
|
||||
className: PropTypes.string.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
scrollIndex: PropTypes.number,
|
||||
scrollTop: PropTypes.number,
|
||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
focusScroller: PropTypes.bool.isRequired,
|
||||
header: PropTypes.node.isRequired,
|
||||
|
||||
@@ -8,7 +8,7 @@ function withScrollPosition(WrappedComponent, scrollPositionKey) {
|
||||
history
|
||||
} = props;
|
||||
|
||||
const scrollTop = history.action === 'POP' ?
|
||||
const scrollTop = history.action === 'POP' || (history.location.state && history.location.state.restoreScrollPosition) ?
|
||||
scrollPositions[scrollPositionKey] :
|
||||
0;
|
||||
|
||||
|
||||
@@ -184,6 +184,7 @@ export const PAGE_LAST = fasFastForward;
|
||||
export const PARENT = fasLevelUpAlt;
|
||||
export const PAUSED = fasPause;
|
||||
export const PENDING = farClock;
|
||||
export const PLAY = fasPlay;
|
||||
export const PROFILE = fasUser;
|
||||
export const POSTER = fasTh;
|
||||
export const QUEUED = fasCloud;
|
||||
|
||||
@@ -3,6 +3,7 @@ export const AVAILABILITY_SELECT = 'availabilitySelect';
|
||||
export const CAPTCHA = 'captcha';
|
||||
export const CHECK = 'check';
|
||||
export const DEVICE = 'device';
|
||||
export const KEY_VALUE_LIST = 'keyValueList';
|
||||
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
|
||||
export const NUMBER = 'number';
|
||||
export const OAUTH = 'oauth';
|
||||
@@ -11,6 +12,7 @@ export const PATH = 'path';
|
||||
export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
|
||||
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
|
||||
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
|
||||
export const LANGUAGE_SELECT = 'languageSelect';
|
||||
export const SELECT = 'select';
|
||||
export const DYNAMIC_SELECT = 'dynamicSelect';
|
||||
export const TAG = 'tag';
|
||||
@@ -26,6 +28,7 @@ export const all = [
|
||||
CAPTCHA,
|
||||
CHECK,
|
||||
DEVICE,
|
||||
KEY_VALUE_LIST,
|
||||
MOVIE_MONITORED_SELECT,
|
||||
NUMBER,
|
||||
OAUTH,
|
||||
@@ -34,6 +37,7 @@ export const all = [
|
||||
QUALITY_PROFILE_SELECT,
|
||||
ROOT_FOLDER_SELECT,
|
||||
INDEXER_FLAGS_SELECT,
|
||||
LANGUAGE_SELECT,
|
||||
SELECT,
|
||||
DYNAMIC_SELECT,
|
||||
TAG,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const DANGER = 'danger';
|
||||
export const DEFAULT = 'default';
|
||||
export const DELETE = 'delete';
|
||||
export const DISABLED = 'disabled';
|
||||
export const INFO = 'info';
|
||||
export const INVERSE = 'inverse';
|
||||
@@ -13,6 +14,7 @@ export const QUEUE = 'queue';
|
||||
export const all = [
|
||||
DANGER,
|
||||
DEFAULT,
|
||||
DELETE,
|
||||
DISABLED,
|
||||
INFO,
|
||||
INVERSE,
|
||||
|
||||
@@ -22,6 +22,20 @@ const columns = [
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'releaseWeight',
|
||||
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'rejections',
|
||||
label: React.createElement(Icon, { name: icons.DANGER }),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
label: translate('Title'),
|
||||
@@ -85,20 +99,6 @@ const columns = [
|
||||
label: React.createElement(Icon, { name: icons.FLAG }),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'rejections',
|
||||
label: React.createElement(Icon, { name: icons.DANGER }),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'releaseWeight',
|
||||
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -145,6 +145,46 @@ class InteractiveSearchRow extends Component {
|
||||
{formatAge(age, ageHours, ageMinutes)}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.download}>
|
||||
<SpinnerIconButton
|
||||
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
||||
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
|
||||
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
||||
isDisabled={isGrabbed}
|
||||
isSpinning={isGrabbing}
|
||||
onPress={downloadAllowed ? this.onGrabPress : this.onConfirmGrabPress}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.rejected}>
|
||||
{
|
||||
!!rejections.length &&
|
||||
<Popover
|
||||
anchor={
|
||||
<Icon
|
||||
name={icons.DANGER}
|
||||
kind={kinds.DANGER}
|
||||
/>
|
||||
}
|
||||
title={translate('ReleaseRejected')}
|
||||
body={
|
||||
<ul>
|
||||
{
|
||||
rejections.map((rejection, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
{rejection}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
position={tooltipPositions.BOTTOM}
|
||||
/>
|
||||
}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.title}>
|
||||
<Link
|
||||
to={infoUrl}
|
||||
@@ -252,51 +292,11 @@ class InteractiveSearchRow extends Component {
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
position={tooltipPositions.LEFT}
|
||||
position={tooltipPositions.BOTTOM}
|
||||
/>
|
||||
}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.rejected}>
|
||||
{
|
||||
!!rejections.length &&
|
||||
<Popover
|
||||
anchor={
|
||||
<Icon
|
||||
name={icons.DANGER}
|
||||
kind={kinds.DANGER}
|
||||
/>
|
||||
}
|
||||
title={translate('ReleaseRejected')}
|
||||
body={
|
||||
<ul>
|
||||
{
|
||||
rejections.map((rejection, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
{rejection}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
position={tooltipPositions.LEFT}
|
||||
/>
|
||||
}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.download}>
|
||||
<SpinnerIconButton
|
||||
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
||||
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
|
||||
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
||||
isDisabled={isGrabbed}
|
||||
isSpinning={isGrabbing}
|
||||
onPress={downloadAllowed ? this.onGrabPress : this.onConfirmGrabPress}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={this.state.isConfirmGrabModalOpen}
|
||||
kind={kinds.WARNING}
|
||||
|
||||
@@ -41,6 +41,19 @@ function MovieDetailsLinks(props) {
|
||||
</Label>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={`https://letterboxd.com/tmdb/${tmdbId}`}
|
||||
>
|
||||
<Label
|
||||
className={styles.linkLabel}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
{translate('Letterboxd')}
|
||||
</Label>
|
||||
</Link>
|
||||
|
||||
{
|
||||
!!imdbId &&
|
||||
<Link
|
||||
@@ -61,7 +74,7 @@ function MovieDetailsLinks(props) {
|
||||
!!imdbId &&
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={` https://moviechat.org/${imdbId}/`}
|
||||
to={`https://moviechat.org/${imdbId}/`}
|
||||
>
|
||||
<Label
|
||||
className={styles.linkLabel}
|
||||
@@ -77,7 +90,7 @@ function MovieDetailsLinks(props) {
|
||||
!!youTubeTrailerId &&
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={` https://www.youtube.com/watch?v=${youTubeTrailerId}/`}
|
||||
to={`https://www.youtube.com/watch?v=${youTubeTrailerId}/`}
|
||||
>
|
||||
<Label
|
||||
className={styles.linkLabel}
|
||||
|
||||
@@ -60,7 +60,8 @@ class MovieIndexOverviews extends Component {
|
||||
columnCount: 1,
|
||||
posterWidth: 162,
|
||||
posterHeight: 238,
|
||||
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {})
|
||||
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {}),
|
||||
scrollRestored: false
|
||||
};
|
||||
|
||||
this._grid = null;
|
||||
@@ -72,13 +73,15 @@ class MovieIndexOverviews extends Component {
|
||||
sortKey,
|
||||
overviewOptions,
|
||||
jumpToCharacter,
|
||||
scrollTop,
|
||||
isMovieEditorActive,
|
||||
isSmallScreen
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
width,
|
||||
rowHeight
|
||||
rowHeight,
|
||||
scrollRestored
|
||||
} = this.state;
|
||||
|
||||
if (prevProps.sortKey !== sortKey ||
|
||||
@@ -97,6 +100,11 @@ class MovieIndexOverviews extends Component {
|
||||
this._grid.recomputeGridSize();
|
||||
}
|
||||
|
||||
if (this._grid && scrollTop !== 0 && !scrollRestored) {
|
||||
this.setState({ scrollRestored: true });
|
||||
this._grid.scrollToPosition({ scrollTop });
|
||||
}
|
||||
|
||||
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
|
||||
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
|
||||
|
||||
@@ -262,6 +270,7 @@ MovieIndexOverviews.propTypes = {
|
||||
sortKey: PropTypes.string,
|
||||
overviewOptions: PropTypes.object.isRequired,
|
||||
jumpToCharacter: PropTypes.string,
|
||||
scrollTop: PropTypes.number.isRequired,
|
||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
|
||||
@@ -104,7 +104,8 @@ class MovieIndexPosters extends Component {
|
||||
columnCount: 1,
|
||||
posterWidth: 162,
|
||||
posterHeight: 238,
|
||||
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {})
|
||||
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {}),
|
||||
scrollRestored: false
|
||||
};
|
||||
|
||||
this._isInitialized = false;
|
||||
@@ -119,14 +120,16 @@ class MovieIndexPosters extends Component {
|
||||
posterOptions,
|
||||
jumpToCharacter,
|
||||
isSmallScreen,
|
||||
isMovieEditorActive
|
||||
isMovieEditorActive,
|
||||
scrollTop
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
width,
|
||||
columnWidth,
|
||||
columnCount,
|
||||
rowHeight
|
||||
rowHeight,
|
||||
scrollRestored
|
||||
} = this.state;
|
||||
|
||||
if (prevProps.sortKey !== sortKey ||
|
||||
@@ -145,6 +148,11 @@ class MovieIndexPosters extends Component {
|
||||
this._grid.recomputeGridSize();
|
||||
}
|
||||
|
||||
if (this._grid && scrollTop !== 0 && !scrollRestored) {
|
||||
this.setState({ scrollRestored: true });
|
||||
this._grid.scrollToPosition({ scrollTop });
|
||||
}
|
||||
|
||||
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
|
||||
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
|
||||
|
||||
@@ -157,6 +165,10 @@ class MovieIndexPosters extends Component {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (this._grid && scrollTop !== 0) {
|
||||
this._grid.scrollToPosition({ scrollTop });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -332,6 +344,7 @@ MovieIndexPosters.propTypes = {
|
||||
sortKey: PropTypes.string,
|
||||
posterOptions: PropTypes.object.isRequired,
|
||||
jumpToCharacter: PropTypes.string,
|
||||
scrollTop: PropTypes.number.isRequired,
|
||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
|
||||
@@ -87,6 +87,7 @@ class MovieIndexTable extends Component {
|
||||
isSmallScreen,
|
||||
onSortPress,
|
||||
scroller,
|
||||
scrollTop,
|
||||
allSelected,
|
||||
allUnselected,
|
||||
onSelectAllChange,
|
||||
@@ -100,6 +101,7 @@ class MovieIndexTable extends Component {
|
||||
items={items}
|
||||
scrollIndex={this.state.scrollIndex}
|
||||
isSmallScreen={isSmallScreen}
|
||||
scrollTop={scrollTop}
|
||||
scroller={scroller}
|
||||
rowHeight={38}
|
||||
overscanRowCount={2}
|
||||
@@ -130,6 +132,7 @@ MovieIndexTable.propTypes = {
|
||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||
jumpToCharacter: PropTypes.string,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
scrollTop: PropTypes.number,
|
||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
onSortPress: PropTypes.func.isRequired,
|
||||
allSelected: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -155,7 +155,7 @@ class FileEditModalContent extends Component {
|
||||
<FormLabel>{translate('Languages')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
type={inputTypes.LANGUAGE_SELECT}
|
||||
name="languageIds"
|
||||
value={languageIds}
|
||||
values={languageOptions}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
@@ -47,7 +48,8 @@ function EditImportListModalContent(props) {
|
||||
rootFolderPath,
|
||||
searchOnAdd,
|
||||
tags,
|
||||
fields
|
||||
fields,
|
||||
message
|
||||
} = item;
|
||||
|
||||
return (
|
||||
@@ -74,6 +76,15 @@ function EditImportListModalContent(props) {
|
||||
<Form
|
||||
{...otherProps}
|
||||
>
|
||||
{
|
||||
!!message &&
|
||||
<Alert
|
||||
className={styles.message}
|
||||
kind={message.value.type}
|
||||
>
|
||||
{message.value.message}
|
||||
</Alert>
|
||||
}
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Name')}</FormLabel>
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ function EditDelayProfileModalContent(props) {
|
||||
enableTorrent,
|
||||
usenetDelay,
|
||||
torrentDelay,
|
||||
bypassIfHighestQuality,
|
||||
tags
|
||||
} = item;
|
||||
|
||||
@@ -110,6 +111,20 @@ function EditDelayProfileModalContent(props) {
|
||||
</FormGroup>
|
||||
}
|
||||
|
||||
{
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('BypassDelayIfHighestQuality')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="bypassIfHighestQuality"
|
||||
{...bypassIfHighestQuality}
|
||||
helpText={translate('BypassDelayIfHighestQualityHelpText')}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
}
|
||||
|
||||
{
|
||||
id === 1 ?
|
||||
<Alert>
|
||||
|
||||
@@ -254,7 +254,7 @@ class EditQualityProfileModalContent extends Component {
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
type={inputTypes.LANGUAGE_SELECT}
|
||||
name="language"
|
||||
values={languages}
|
||||
value={languageId}
|
||||
|
||||
@@ -96,12 +96,13 @@ function createLanguagesSelector() {
|
||||
(state) => state.settings.languages,
|
||||
(languages) => {
|
||||
const items = languages.items;
|
||||
const filterItems = ['Unknown'];
|
||||
|
||||
if (!items) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const newItems = items.map((item) => {
|
||||
const newItems = items.filter((lang) => !filterItems.includes(lang.name)).map((item) => {
|
||||
return {
|
||||
key: item.id,
|
||||
value: item.name
|
||||
|
||||
@@ -104,7 +104,7 @@ class QualityProfile extends Component {
|
||||
return (
|
||||
<Label
|
||||
key={item.quality.id}
|
||||
kind={isCutoff ? kinds.INFO : kinds.default}
|
||||
kind={isCutoff ? kinds.INFO : kinds.DEFAULT}
|
||||
title={isCutoff ? translate('UpgradeUntilThisQualityIsMetOrExceeded') : null}
|
||||
>
|
||||
{item.quality.name}
|
||||
@@ -120,7 +120,7 @@ class QualityProfile extends Component {
|
||||
className={styles.tooltipLabel}
|
||||
anchor={
|
||||
<Label
|
||||
kind={isCutoff ? kinds.INFO : kinds.default}
|
||||
kind={isCutoff ? kinds.INFO : kinds.DEFAULT}
|
||||
title={isCutoff ? translate('Cutoff') : null}
|
||||
>
|
||||
{item.name}
|
||||
@@ -133,7 +133,7 @@ class QualityProfile extends Component {
|
||||
return (
|
||||
<Label
|
||||
key={groupItem.quality.id}
|
||||
kind={isCutoff ? kinds.INFO : kinds.default}
|
||||
kind={isCutoff ? kinds.INFO : kinds.DEFAULT}
|
||||
title={isCutoff ? translate('Cutoff') : null}
|
||||
>
|
||||
{groupItem.quality.name}
|
||||
|
||||
@@ -200,7 +200,7 @@ class UISettings extends Component {
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('MovieInfoLanguage')}</FormLabel>
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
type={inputTypes.LANGUAGE_SELECT}
|
||||
name="movieInfoLanguage"
|
||||
values={languages}
|
||||
helpText={translate('MovieInfoLanguageHelpText')}
|
||||
@@ -213,7 +213,7 @@ class UISettings extends Component {
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('UILanguage')}</FormLabel>
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
type={inputTypes.LANGUAGE_SELECT}
|
||||
name="uiLanguage"
|
||||
values={uiLanguages}
|
||||
helpText={translate('UILanguageHelpText')}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
}
|
||||
|
||||
@define-mixin scrollbarTrack {
|
||||
&&::-webkit-scrollbar-track {
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ function getInternalLink(source) {
|
||||
<IconButton
|
||||
name={icons.PLAY}
|
||||
title={translate('MovieEditor')}
|
||||
to="/movieeditor"
|
||||
to="/"
|
||||
/>
|
||||
);
|
||||
case 'UpdateCheck':
|
||||
|
||||
@@ -10,6 +10,15 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.commandName {
|
||||
display: inline-block;
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
.userAgent {
|
||||
color: #b0b0b0;
|
||||
}
|
||||
|
||||
.queued,
|
||||
.started,
|
||||
.ended {
|
||||
|
||||
@@ -41,7 +41,7 @@ function getStatusIconProps(status, message) {
|
||||
case 'failed':
|
||||
return {
|
||||
name: icons.FATAL,
|
||||
kind: kinds.ERROR,
|
||||
kind: kinds.DANGER,
|
||||
title: `${title}: ${message}`
|
||||
};
|
||||
|
||||
@@ -157,6 +157,7 @@ class QueuedTaskRow extends Component {
|
||||
status,
|
||||
duration,
|
||||
message,
|
||||
clientUserAgent,
|
||||
longDateFormat,
|
||||
timeFormat,
|
||||
onCancelPress
|
||||
@@ -192,7 +193,18 @@ class QueuedTaskRow extends Component {
|
||||
</span>
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell>{commandName}</TableRowCell>
|
||||
<TableRowCell>
|
||||
<span className={styles.commandName}>
|
||||
{commandName}
|
||||
</span>
|
||||
{
|
||||
clientUserAgent ?
|
||||
<span className={styles.userAgent} title={translate('TaskUserAgentTooltip')}>
|
||||
{translate('from')}: {clientUserAgent}
|
||||
</span> :
|
||||
null
|
||||
}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell
|
||||
className={styles.queued}
|
||||
@@ -256,6 +268,7 @@ QueuedTaskRow.propTypes = {
|
||||
status: PropTypes.string.isRequired,
|
||||
duration: PropTypes.string,
|
||||
message: PropTypes.string,
|
||||
clientUserAgent: PropTypes.string,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
longDateFormat: PropTypes.string.isRequired,
|
||||
|
||||
@@ -226,6 +226,7 @@
|
||||
required
|
||||
title="User name is required"
|
||||
autoFocus="true"
|
||||
autoCapitalize="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
232
package.json
232
package.json
@@ -3,10 +3,11 @@
|
||||
"version": "1.0.0",
|
||||
"description": "Radarr is a PVR for Usenet and BitTorrent users",
|
||||
"scripts": {
|
||||
"build": "gulp build",
|
||||
"start": "gulp watch",
|
||||
"watch": "gulp watch",
|
||||
"clean": "git clean -fXd",
|
||||
"build": "webpack --config ./frontend/build/webpack.config.js",
|
||||
"prebuild": "yarn clean",
|
||||
"clean": "rimraf ./_output/UI && rimraf \"**/*.js.map\"",
|
||||
"start": "webpack --watch --config ./frontend/build/webpack.config.js",
|
||||
"watch": "webpack --watch --config ./frontend/build/webpack.config.js",
|
||||
"lint": "esprint check",
|
||||
"lint-fix": "esprint check --fix",
|
||||
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
|
||||
@@ -16,123 +17,120 @@
|
||||
"author": "Team Radarr",
|
||||
"license": "GPL-3.0",
|
||||
"readmeFilename": "readme.md",
|
||||
"dependencies": {
|
||||
"@babel/core": "7.11.6",
|
||||
"@babel/plugin-proposal-class-properties": "7.10.4",
|
||||
"@babel/plugin-proposal-decorators": "7.10.5",
|
||||
"@babel/plugin-proposal-export-default-from": "7.10.4",
|
||||
"@babel/plugin-proposal-export-namespace-from": "7.10.4",
|
||||
"@babel/plugin-proposal-function-sent": "7.10.4",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "7.10.4",
|
||||
"@babel/plugin-proposal-numeric-separator": "7.10.4",
|
||||
"@babel/plugin-proposal-optional-chaining": "7.11.0",
|
||||
"@babel/plugin-proposal-throw-expressions": "7.10.4",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
"@babel/preset-env": "7.11.5",
|
||||
"@babel/preset-react": "7.10.4",
|
||||
"@fortawesome/fontawesome-free": "5.15.0",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.31",
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.0",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.0",
|
||||
"@fortawesome/react-fontawesome": "0.1.11",
|
||||
"@microsoft/signalr": "5.0.5",
|
||||
"@sentry/browser": "5.29.2",
|
||||
"@sentry/integrations": "5.29.2",
|
||||
"ansi-colors": "4.1.1",
|
||||
"autoprefixer": "9.7.5",
|
||||
"babel-eslint": "10.1.0",
|
||||
"babel-loader": "8.1.0",
|
||||
"babel-plugin-inline-classnames": "2.0.1",
|
||||
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
||||
"classnames": "2.2.6",
|
||||
"clipboard": "2.0.6",
|
||||
"connected-react-router": "6.8.0",
|
||||
"core-js": "3.6.5",
|
||||
"css-loader": "3.4.2",
|
||||
"del": "6.0.0",
|
||||
"element-class": "0.2.2",
|
||||
"eslint": "7.10.0",
|
||||
"eslint-plugin-filenames": "1.3.2",
|
||||
"eslint-plugin-import": "2.22.0",
|
||||
"eslint-plugin-json": "2.1.2",
|
||||
"eslint-plugin-react": "7.21.3",
|
||||
"eslint-plugin-simple-import-sort": "5.0.3",
|
||||
"esprint": "0.7.0",
|
||||
"file-loader": "6.1.0",
|
||||
"filesize": "6.1.0",
|
||||
"fuse.js": "6.4.1",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-cached": "1.1.1",
|
||||
"gulp-concat": "2.6.1",
|
||||
"gulp-livereload": "4.0.2",
|
||||
"gulp-postcss": "8.0.0",
|
||||
"gulp-print": "5.0.2",
|
||||
"gulp-sourcemaps": "2.6.5",
|
||||
"gulp-watch": "5.0.1",
|
||||
"gulp-wrap": "0.15.0",
|
||||
"history": "4.10.1",
|
||||
"html-webpack-plugin": "4.5.0",
|
||||
"jdu": "1.0.0",
|
||||
"jquery": "3.5.1",
|
||||
"loader-utils": "^2.0.0",
|
||||
"lodash": "4.17.20",
|
||||
"mini-css-extract-plugin": "0.9.0",
|
||||
"mobile-detect": "1.4.4",
|
||||
"moment": "2.29.0",
|
||||
"mousetrap": "1.6.5",
|
||||
"normalize.css": "8.0.1",
|
||||
"optimize-css-assets-webpack-plugin": "5.0.3",
|
||||
"postcss-color-function": "4.1.0",
|
||||
"postcss-loader": "3.0.0",
|
||||
"postcss-mixins": "6.2.3",
|
||||
"postcss-nested": "4.2.1",
|
||||
"postcss-simple-vars": "5.0.2",
|
||||
"postcss-url": "8.0.0",
|
||||
"prop-types": "15.7.2",
|
||||
"qs": "6.9.4",
|
||||
"react": "16.13.1",
|
||||
"react-addons-shallow-compare": "15.6.2",
|
||||
"react-async-script": "1.2.0",
|
||||
"react-autosuggest": "10.0.2",
|
||||
"react-custom-scrollbars": "4.2.1",
|
||||
"react-dnd": "11.1.3",
|
||||
"react-dnd-html5-backend": "11.1.3",
|
||||
"react-document-title": "2.0.3",
|
||||
"react-dom": "16.13.1",
|
||||
"react-focus-lock": "2.4.1",
|
||||
"react-google-recaptcha": "2.1.0",
|
||||
"react-lazyload": "3.0.0",
|
||||
"react-measure": "1.4.7",
|
||||
"react-popper": "1.3.7",
|
||||
"react-redux": "7.2.1",
|
||||
"react-router": "5.2.0",
|
||||
"react-router-dom": "5.2.0",
|
||||
"react-slider": "1.0.11",
|
||||
"react-tabs": "3.1.1",
|
||||
"react-text-truncate": "0.16.0",
|
||||
"react-virtualized": "9.21.1",
|
||||
"redux": "4.0.5",
|
||||
"redux-actions": "2.6.5",
|
||||
"redux-batched-actions": "0.5.0",
|
||||
"redux-localstorage": "0.4.1",
|
||||
"redux-thunk": "2.3.0",
|
||||
"require-nocache": "1.0.0",
|
||||
"reselect": "4.0.0",
|
||||
"run-sequence": "2.2.1",
|
||||
"streamqueue": "1.1.2",
|
||||
"style-loader": "1.2.1",
|
||||
"stylelint": "13.7.2",
|
||||
"stylelint-order": "4.1.0",
|
||||
"url-loader": "4.1.0",
|
||||
"webpack": "4.44.2",
|
||||
"webpack-stream": "6.1.0",
|
||||
"worker-loader": "3.0.3"
|
||||
},
|
||||
"main": "index.js",
|
||||
"browserslist": [
|
||||
">0.25%",
|
||||
"not ie 11",
|
||||
"not op_mini all",
|
||||
"not chrome < 60"
|
||||
]
|
||||
],
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "5.15.3",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.35",
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.3",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.3",
|
||||
"@fortawesome/react-fontawesome": "0.1.14",
|
||||
"@microsoft/signalr": "5.0.5",
|
||||
"@sentry/browser": "6.3.1",
|
||||
"@sentry/integrations": "6.3.1",
|
||||
"classnames": "2.3.1",
|
||||
"clipboard": "2.0.8",
|
||||
"connected-react-router": "6.9.1",
|
||||
"element-class": "0.2.2",
|
||||
"filesize": "6.3.0",
|
||||
"fuse.js": "6.4.6",
|
||||
"history": "4.10.1",
|
||||
"https-browserify": "1.0.0",
|
||||
"jdu": "1.0.0",
|
||||
"jquery": "3.6.0",
|
||||
"lodash": "4.17.21",
|
||||
"mobile-detect": "1.4.5",
|
||||
"moment": "2.29.1",
|
||||
"mousetrap": "1.6.5",
|
||||
"normalize.css": "8.0.1",
|
||||
"prop-types": "15.7.2",
|
||||
"qs": "6.10.1",
|
||||
"react": "17.0.2",
|
||||
"react-addons-shallow-compare": "15.6.3",
|
||||
"react-async-script": "1.2.0",
|
||||
"react-autosuggest": "10.1.0",
|
||||
"react-custom-scrollbars": "4.2.1",
|
||||
"react-dnd": "14.0.2",
|
||||
"react-dnd-html5-backend": "14.0.0",
|
||||
"react-dnd-multi-backend": "6.0.2",
|
||||
"react-dnd-touch-backend": "14.0.0",
|
||||
"react-document-title": "2.0.3",
|
||||
"react-dom": "17.0.2",
|
||||
"react-focus-lock": "2.5.0",
|
||||
"react-google-recaptcha": "2.1.0",
|
||||
"react-lazyload": "3.2.0",
|
||||
"react-measure": "1.4.7",
|
||||
"react-popper": "1.3.7",
|
||||
"react-redux": "7.2.4",
|
||||
"react-router": "5.2.0",
|
||||
"react-router-dom": "5.2.0",
|
||||
"react-slider": "1.1.4",
|
||||
"react-tabs": "3.2.2",
|
||||
"react-text-truncate": "0.16.0",
|
||||
"react-virtualized": "9.21.1",
|
||||
"redux": "4.1.0",
|
||||
"redux-actions": "2.6.5",
|
||||
"redux-batched-actions": "0.5.0",
|
||||
"redux-localstorage": "0.4.1",
|
||||
"redux-thunk": "2.3.0",
|
||||
"reselect": "4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.13.16",
|
||||
"@babel/plugin-proposal-class-properties": "7.13.0",
|
||||
"@babel/plugin-proposal-decorators": "7.13.15",
|
||||
"@babel/plugin-proposal-export-default-from": "7.12.13",
|
||||
"@babel/plugin-proposal-export-namespace-from": "7.12.13",
|
||||
"@babel/plugin-proposal-function-sent": "7.12.13",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "7.13.8",
|
||||
"@babel/plugin-proposal-numeric-separator": "7.12.13",
|
||||
"@babel/plugin-proposal-optional-chaining": "7.13.12",
|
||||
"@babel/plugin-proposal-throw-expressions": "7.12.13",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
"@babel/preset-env": "7.13.15",
|
||||
"@babel/preset-react": "7.13.13",
|
||||
"@babel/eslint-parser": "7.13.14",
|
||||
"autoprefixer": "10.2.5",
|
||||
"babel-loader": "8.2.2",
|
||||
"babel-plugin-inline-classnames": "2.0.1",
|
||||
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
||||
"copy-webpack-plugin": "8.1.1",
|
||||
"core-js": "3.11.0",
|
||||
"css-loader": "5.2.4",
|
||||
"eslint": "7.25.0",
|
||||
"eslint-plugin-filenames": "1.3.2",
|
||||
"eslint-plugin-import": "2.22.1",
|
||||
"eslint-plugin-json": "3.0.0",
|
||||
"eslint-plugin-react": "7.23.2",
|
||||
"eslint-plugin-simple-import-sort": "7.0.0",
|
||||
"esprint": "2.0.0",
|
||||
"file-loader": "6.2.0",
|
||||
"html-webpack-plugin": "5.3.1",
|
||||
"loader-utils": "^2.0.0",
|
||||
"mini-css-extract-plugin": "1.5.0",
|
||||
"postcss": "8.2.12",
|
||||
"postcss-color-function": "4.1.0",
|
||||
"postcss-loader": "5.2.0",
|
||||
"postcss-mixins": "7.0.3",
|
||||
"postcss-nested": "5.0.5",
|
||||
"postcss-simple-vars": "6.0.3",
|
||||
"postcss-url": "10.1.3",
|
||||
"require-nocache": "1.0.0",
|
||||
"rimraf": "3.0.2",
|
||||
"run-sequence": "2.2.1",
|
||||
"streamqueue": "1.1.2",
|
||||
"style-loader": "2.0.0",
|
||||
"url-loader": "4.1.1",
|
||||
"webpack": "5.35.1",
|
||||
"webpack-cli": "4.6.0",
|
||||
"webpack-livereload-plugin": "3.0.1",
|
||||
"worker-loader": "3.0.8",
|
||||
"stylelint": "13.13.0",
|
||||
"stylelint-order": "4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TestProject)'=='true' and '$(TargetFramework)'=='net5.0'">
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.1" PrivateAssets="all" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Allow building net framework using mono -->
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
<add key="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" />
|
||||
<add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" />
|
||||
<add key="FluentMigrator" value="https://pkgs.dev.azure.com/fluentmigrator/fluentmigrator/_packaging/fluentmigrator/nuget/v3/index.json" />
|
||||
<add key="coverlet-nightly" value="https://pkgs.dev.azure.com/Servarr/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using Radarr.Http.REST;
|
||||
|
||||
@@ -21,6 +22,8 @@ namespace NzbDrone.Api.Commands
|
||||
public string Exception { get; set; }
|
||||
public CommandTrigger Trigger { get; set; }
|
||||
|
||||
public string ClientUserAgent { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string CompletionMessage { get; set; }
|
||||
|
||||
@@ -125,6 +128,8 @@ namespace NzbDrone.Api.Commands
|
||||
Exception = model.Exception,
|
||||
Trigger = model.Trigger,
|
||||
|
||||
ClientUserAgent = UserAgentParser.SimplifyUserAgent(model.Body.ClientUserAgent),
|
||||
|
||||
CompletionMessage = model.Body.CompletionMessage,
|
||||
LastExecutionTime = model.Body.LastExecutionTime
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace NzbDrone.Api.Profiles.Delay
|
||||
public DownloadProtocol PreferredProtocol { get; set; }
|
||||
public int UsenetDelay { get; set; }
|
||||
public int TorrentDelay { get; set; }
|
||||
public bool BypassIfHighestQuality { get; set; }
|
||||
public int Order { get; set; }
|
||||
public HashSet<int> Tags { get; set; }
|
||||
}
|
||||
@@ -35,6 +36,7 @@ namespace NzbDrone.Api.Profiles.Delay
|
||||
PreferredProtocol = model.PreferredProtocol,
|
||||
UsenetDelay = model.UsenetDelay,
|
||||
TorrentDelay = model.TorrentDelay,
|
||||
BypassIfHighestQuality = model.BypassIfHighestQuality,
|
||||
Order = model.Order,
|
||||
Tags = new HashSet<int>(model.Tags)
|
||||
};
|
||||
@@ -56,6 +58,7 @@ namespace NzbDrone.Api.Profiles.Delay
|
||||
PreferredProtocol = resource.PreferredProtocol,
|
||||
UsenetDelay = resource.UsenetDelay,
|
||||
TorrentDelay = resource.TorrentDelay,
|
||||
BypassIfHighestQuality = resource.BypassIfHighestQuality,
|
||||
Order = resource.Order,
|
||||
Tags = new HashSet<int>(resource.Tags)
|
||||
};
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace NzbDrone.Api.Queue
|
||||
throw new BadRequestException();
|
||||
}
|
||||
|
||||
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadId, true);
|
||||
downloadClient.RemoveItem(trackedDownload.DownloadItem, true);
|
||||
|
||||
if (blacklist)
|
||||
{
|
||||
|
||||
@@ -1,428 +0,0 @@
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"title": "Radarr",
|
||||
"description": "Movie Automation",
|
||||
"contact": {
|
||||
"url": "https://radarr.video"
|
||||
},
|
||||
"version": "0.2.0"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "{protocol}://{hostPath}/api",
|
||||
"variables": {
|
||||
"protocol": {
|
||||
"enum": [
|
||||
"https",
|
||||
"http"
|
||||
],
|
||||
"default": "https"
|
||||
},
|
||||
"hostPath": {
|
||||
"default": "localhost:7878",
|
||||
"description": "Your Radarr Server URL"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/movie": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Movie"
|
||||
],
|
||||
"summary": "Get all movies",
|
||||
"description": "Returns all movies",
|
||||
"operationId": "getMovie",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Movie"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"404": {
|
||||
"description": "Movie not found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"Movie"
|
||||
],
|
||||
"summary": "Add new movie",
|
||||
"requestBody": {
|
||||
"description": "Movie object that needs to be added",
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Movie"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"405": {
|
||||
"description": "Validation exception"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"tags": [
|
||||
"Movie"
|
||||
],
|
||||
"summary": "Edit existing movie",
|
||||
"requestBody": {
|
||||
"description": "Movie object that needs to be edited",
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Movie"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Movie"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Movie not found"
|
||||
},
|
||||
"405": {
|
||||
"description": "Validation exception"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/movie/{movieId}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Movie"
|
||||
],
|
||||
"summary": "Get movie by ID",
|
||||
"description": "Returns a single movie",
|
||||
"operationId": "getMovieById",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "movieId",
|
||||
"in": "path",
|
||||
"description": "ID of movie to return",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Movie"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"404": {
|
||||
"description": "Movie not found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"Movie"
|
||||
],
|
||||
"summary": "Deletes a Movie",
|
||||
"description": "",
|
||||
"operationId": "deleteMovie",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "movieId",
|
||||
"in": "path",
|
||||
"description": "Movie id to delete",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "addImportExclusion",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "deleteFiles",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"404": {
|
||||
"description": "Movie not found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Movie": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"title"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"example": "Dark Phoenix"
|
||||
},
|
||||
"sortTitle": {
|
||||
"type": "string",
|
||||
"example": "dark phoenix"
|
||||
},
|
||||
"sizeOnDisk": {
|
||||
"type": "number"
|
||||
},
|
||||
"overview": {
|
||||
"type": "string"
|
||||
},
|
||||
"inCinemas": {
|
||||
"type": "string"
|
||||
},
|
||||
"physicalRelease": {
|
||||
"type": "string"
|
||||
},
|
||||
"images": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Image"
|
||||
}
|
||||
},
|
||||
"website": {
|
||||
"type": "string",
|
||||
"example": "http://darkphoenix.com"
|
||||
},
|
||||
"year": {
|
||||
"type": "integer"
|
||||
},
|
||||
"hasFile": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"youTubeTrailerId": {
|
||||
"type": "string"
|
||||
},
|
||||
"studio": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"qualityProfileId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"monitored": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"minimumAcailability": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"announced",
|
||||
"inCinema",
|
||||
"released"
|
||||
]
|
||||
},
|
||||
"isAvailable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"folderName": {
|
||||
"type": "string"
|
||||
},
|
||||
"runtime": {
|
||||
"type": "integer"
|
||||
},
|
||||
"cleanTitle": {
|
||||
"type": "string"
|
||||
},
|
||||
"imdbId": {
|
||||
"type": "string"
|
||||
},
|
||||
"tmdbId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"titleSlug": {
|
||||
"type": "integer"
|
||||
},
|
||||
"certification": {
|
||||
"type": "string"
|
||||
},
|
||||
"genres": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"added": {
|
||||
"type": "string"
|
||||
},
|
||||
"ratings": {
|
||||
"$ref": "#/components/schemas/Rating"
|
||||
},
|
||||
"collection": {
|
||||
"$ref": "#/components/schemas/Collection"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "movie status",
|
||||
"enum": [
|
||||
"deleted",
|
||||
"tba",
|
||||
"announced",
|
||||
"inCinema",
|
||||
"released"
|
||||
]
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
"name": "Movie"
|
||||
}
|
||||
},
|
||||
"Image": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"coverType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"poster",
|
||||
"fanart"
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
"name": "Image"
|
||||
}
|
||||
},
|
||||
"Collection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"tmdbId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"images": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Image"
|
||||
}
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
"name": "Collection"
|
||||
}
|
||||
},
|
||||
"Rating": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"votes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"value": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
"name": "Rating"
|
||||
}
|
||||
}
|
||||
},
|
||||
"securitySchemes": {
|
||||
"api_key": {
|
||||
"type": "apiKey",
|
||||
"in": "query",
|
||||
"name": "apiKey"
|
||||
}
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
"description": "GitHub",
|
||||
"url": "https://github.com/Radarr/Radarr"
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
[TestCase(@"OutputPath=/home/mySecret/Downloads")]
|
||||
[TestCase("Hardlinking episode file: /home/mySecret/Downloads to /media/abc.mkv")]
|
||||
[TestCase("Hardlink '/home/mySecret/Downloads/abs.mkv' to '/media/abc.mkv' failed.")]
|
||||
[TestCase("https://discordnotifier.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")]
|
||||
[TestCase("https://notifiarr.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")]
|
||||
|
||||
// Announce URLs (passkeys) Magnet & Tracker
|
||||
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
|
||||
|
||||
21
src/NzbDrone.Common/Http/UserAgentParser.cs
Normal file
21
src/NzbDrone.Common/Http/UserAgentParser.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
public static class UserAgentParser
|
||||
{
|
||||
public static string SimplifyUserAgent(string userAgent)
|
||||
{
|
||||
if (userAgent == null || userAgent.StartsWith("Mozilla/5.0"))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return userAgent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
public static class NzbDroneLogger
|
||||
{
|
||||
private const string FILE_LOG_LAYOUT = @"${date:format=yyyy-M-d HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
|
||||
private const string FILE_LOG_LAYOUT = @"${date:format=yyyy-MM-dd HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
|
||||
|
||||
private static bool _isConfigured;
|
||||
|
||||
|
||||
@@ -104,10 +104,22 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_true_when_quality_is_last_allowed_in_profile()
|
||||
public void should_be_false_when_quality_is_last_allowed_in_profile_and_bypass_disabled()
|
||||
{
|
||||
_remoteMovie.ParsedMovieInfo.Quality = new QualityModel(Quality.Bluray720p);
|
||||
|
||||
_remoteMovie.Release.PublishDate = DateTime.UtcNow;
|
||||
|
||||
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_true_when_quality_is_last_allowed_in_profile_and_bypass_enabled()
|
||||
{
|
||||
_delayProfile.BypassIfHighestQuality = true;
|
||||
|
||||
_remoteMovie.ParsedMovieInfo.Quality = new QualityModel(Quality.Bluray720p);
|
||||
|
||||
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue();
|
||||
}
|
||||
|
||||
|
||||
@@ -87,5 +87,13 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
_parseResultSingle.ParsedMovieInfo.Quality = new QualityModel(Quality.WEBDL1080p);
|
||||
_upgradeDisk.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_be_upgradable_if_revision_downgrade_if_propers_are_preferred()
|
||||
{
|
||||
_firstFile.Quality = new QualityModel(Quality.WEBDL1080p, new Revision(2));
|
||||
_parseResultSingle.ParsedMovieInfo.Quality = new QualityModel(Quality.WEBDL1080p);
|
||||
_upgradeDisk.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -25,6 +26,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
protected string _blackholeFolder;
|
||||
protected string _filePath;
|
||||
protected string _magnetFilePath;
|
||||
protected DownloadClientItem _downloadClientItem;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
@@ -34,6 +36,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
_filePath = (@"c:\blackhole\torrent\" + _title + ".torrent").AsOsAgnostic();
|
||||
_magnetFilePath = Path.ChangeExtension(_filePath, ".magnet");
|
||||
|
||||
_downloadClientItem = Builder<DownloadClientItem>
|
||||
.CreateNew()
|
||||
.With(d => d.DownloadId = "_Droned.S01E01.Pilot.1080p.WEB-DL-DRONE_0")
|
||||
.With(d => d.OutputPath = new OsPath(Path.Combine(_completedDownloadFolder, _title)))
|
||||
.Build();
|
||||
|
||||
Mocker.SetConstant<IScanWatchFolder>(Mocker.Resolve<ScanWatchFolder>());
|
||||
|
||||
Subject.Definition = new DownloadClientDefinition();
|
||||
@@ -246,7 +254,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
.Setup(c => c.FileExists(It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
Subject.RemoveItem("_Droned.1998.1080p.WEB-DL-DRONE_0", true);
|
||||
Subject.RemoveItem(_downloadClientItem, true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(c => c.DeleteFile(It.IsAny<string>()), Times.Once());
|
||||
@@ -261,7 +269,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
.Setup(c => c.FolderExists(It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
Subject.RemoveItem("_Droned.1998.1080p.WEB-DL-DRONE_0", true);
|
||||
Subject.RemoveItem(_downloadClientItem, true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Once());
|
||||
@@ -270,7 +278,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
[Test]
|
||||
public void RemoveItem_should_ignore_if_unknown_item()
|
||||
{
|
||||
Subject.RemoveItem("_Droned.1998.1080p.WEB-DL-DRONE_0", true);
|
||||
Subject.RemoveItem(_downloadClientItem, true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(c => c.DeleteFile(It.IsAny<string>()), Times.Never());
|
||||
@@ -284,7 +292,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
{
|
||||
GivenCompletedItem();
|
||||
|
||||
Assert.Throws<NotSupportedException>(() => Subject.RemoveItem("_Droned.1998.1080p.WEB-DL-DRONE_0", false));
|
||||
Assert.Throws<NotSupportedException>(() => Subject.RemoveItem(_downloadClientItem, false));
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(c => c.DeleteFile(It.IsAny<string>()), Times.Never());
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -21,6 +22,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
protected string _completedDownloadFolder;
|
||||
protected string _blackholeFolder;
|
||||
protected string _filePath;
|
||||
protected DownloadClientItem _downloadClientItem;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
@@ -29,6 +31,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
_blackholeFolder = @"c:\blackhole\nzb".AsOsAgnostic();
|
||||
_filePath = (@"c:\blackhole\nzb\" + _title + ".nzb").AsOsAgnostic();
|
||||
|
||||
_downloadClientItem = Builder<DownloadClientItem>
|
||||
.CreateNew()
|
||||
.With(d => d.DownloadId = "_Droned.S01E01.Pilot.1080p.WEB-DL-DRONE_0")
|
||||
.With(d => d.OutputPath = new OsPath(Path.Combine(_completedDownloadFolder, _title)))
|
||||
.Build();
|
||||
|
||||
Mocker.SetConstant<IScanWatchFolder>(Mocker.Resolve<ScanWatchFolder>());
|
||||
|
||||
Subject.Definition = new DownloadClientDefinition();
|
||||
@@ -143,7 +151,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
.Setup(c => c.FileExists(It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
Subject.RemoveItem("_Droned.1998.1080p.WEB-DL-DRONE_0", true);
|
||||
Subject.RemoveItem(_downloadClientItem, true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(c => c.DeleteFile(It.IsAny<string>()), Times.Once());
|
||||
@@ -158,7 +166,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
.Setup(c => c.FolderExists(It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
Subject.RemoveItem("_Droned.1998.1080p.WEB-DL-DRONE_0", true);
|
||||
Subject.RemoveItem(_downloadClientItem, true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Once());
|
||||
@@ -167,7 +175,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
[Test]
|
||||
public void RemoveItem_should_ignore_if_unknown_item()
|
||||
{
|
||||
Subject.RemoveItem("_Droned.1998.1080p.WEB-DL-DRONE_0", true);
|
||||
Subject.RemoveItem(_downloadClientItem, true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(c => c.DeleteFile(It.IsAny<string>()), Times.Never());
|
||||
@@ -181,7 +189,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
{
|
||||
GivenCompletedItem();
|
||||
|
||||
Assert.Throws<NotSupportedException>(() => Subject.RemoveItem("_Droned.1998.1080p.WEB-DL-DRONE_0", false));
|
||||
Assert.Throws<NotSupportedException>(() => Subject.RemoveItem(_downloadClientItem, false));
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(c => c.DeleteFile(It.IsAny<string>()), Times.Never());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -20,6 +21,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
|
||||
private NzbgetHistoryItem _failed;
|
||||
private NzbgetHistoryItem _completed;
|
||||
private Dictionary<string, string> _configItems;
|
||||
private DownloadClientItem _downloadClientItem;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
@@ -74,6 +76,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
|
||||
MarkStatus = "NONE"
|
||||
};
|
||||
|
||||
_downloadClientItem = Builder<DownloadClientItem>
|
||||
.CreateNew()
|
||||
.With(d => d.DownloadId = "_Droned.S01E01.Pilot.1080p.WEB-DL-DRONE_0")
|
||||
.With(d => d.OutputPath = new OsPath("/remote/mount/tv/Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic()))
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<INzbgetProxy>()
|
||||
.Setup(s => s.GetGlobalStatus(It.IsAny<NzbgetSettings>()))
|
||||
.Returns(new NzbgetGlobalStatus
|
||||
@@ -155,7 +163,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
|
||||
.Setup(v => v.FolderExists(It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
Subject.RemoveItem("id", true);
|
||||
Subject.RemoveItem(_downloadClientItem, true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.DeleteFolder(It.IsAny<string>(), true), Times.Once());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using FizzWare.NBuilder;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
@@ -21,6 +22,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
||||
private string _strmFolder;
|
||||
private string _nzbPath;
|
||||
private RemoteMovie _remoteMovie;
|
||||
private DownloadClientItem _downloadClientItem;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
@@ -37,6 +39,10 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
||||
|
||||
_remoteMovie.ParsedMovieInfo = new ParsedMovieInfo();
|
||||
|
||||
_downloadClientItem = Builder<DownloadClientItem>
|
||||
.CreateNew().With(d => d.DownloadId = "_Droned.S01E01.Pilot.1080p.WEB-DL-DRONE_0")
|
||||
.Build();
|
||||
|
||||
Subject.Definition = new DownloadClientDefinition();
|
||||
Subject.Definition.Settings = new PneumaticSettings
|
||||
{
|
||||
@@ -69,7 +75,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
||||
[Test]
|
||||
public void should_throw_item_is_removed()
|
||||
{
|
||||
Assert.Throws<NotSupportedException>(() => Subject.RemoveItem("", true));
|
||||
Assert.Throws<NotSupportedException>(() => Subject.RemoveItem(_downloadClientItem, true));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -22,6 +23,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
private SabnzbdHistory _completed;
|
||||
private SabnzbdConfig _config;
|
||||
private SabnzbdFullStatus _fullStatus;
|
||||
private DownloadClientItem _downloadClientItem;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
@@ -99,6 +101,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
}
|
||||
};
|
||||
|
||||
_downloadClientItem = Builder<DownloadClientItem>
|
||||
.CreateNew()
|
||||
.With(d => d.Status = DownloadItemStatus.Completed)
|
||||
.With(d => d.DownloadId = _completed.Items.First().Id)
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<ISabnzbdProxy>()
|
||||
.Setup(v => v.GetVersion(It.IsAny<SabnzbdSettings>()))
|
||||
.Returns("1.2.3");
|
||||
@@ -582,6 +590,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
public void should_remove_output_path_folder_when_deleting_a_completed_item_and_delete_data_is_true()
|
||||
{
|
||||
var path = @"C:\Test\Series.Title.S01E01".AsOsAgnostic();
|
||||
_downloadClientItem.OutputPath = new OsPath(path);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.FolderExists(path))
|
||||
@@ -592,7 +601,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
GivenQueue(null);
|
||||
GivenHistory(_completed);
|
||||
|
||||
Subject.RemoveItem(_completed.Items.First().Id, true);
|
||||
Subject.RemoveItem(_downloadClientItem, true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.DeleteFolder(path, true), Times.Once);
|
||||
@@ -605,6 +614,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
public void should_remove_output_path_file_when_deleting_a_completed_item_and_delete_data_is_true()
|
||||
{
|
||||
var path = @"C:\Test\Series.Title.S01E01.mkv".AsOsAgnostic();
|
||||
_downloadClientItem.OutputPath = new OsPath(path);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.FolderExists(path))
|
||||
@@ -619,7 +629,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
GivenQueue(null);
|
||||
GivenHistory(_completed);
|
||||
|
||||
Subject.RemoveItem(_completed.Items.First().Id, true);
|
||||
Subject.RemoveItem(_downloadClientItem, true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.DeleteFolder(path, true), Times.Never);
|
||||
@@ -646,7 +656,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
GivenQueue(null);
|
||||
GivenHistory(_completed);
|
||||
|
||||
Subject.RemoveItem(_completed.Items.First().Id, true);
|
||||
Subject.RemoveItem(_downloadClientItem, true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.DeleteFolder(path, true), Times.Never);
|
||||
@@ -673,7 +683,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
GivenQueue(null);
|
||||
GivenHistory(_completed);
|
||||
|
||||
Subject.RemoveItem(_completed.Items.First().Id, false);
|
||||
Subject.RemoveItem(_downloadClientItem, false);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.FolderExists(path), Times.Never);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -7,6 +8,7 @@ using NzbDrone.Core.Download.TrackedDownloads;
|
||||
using NzbDrone.Core.History;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Movies.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
@@ -83,5 +85,123 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
|
||||
trackedDownload.RemoteMovie.Movie.Should().NotBeNull();
|
||||
trackedDownload.RemoteMovie.Movie.Id.Should().Be(3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_unmap_tracked_download_if_movie_deleted()
|
||||
{
|
||||
GivenDownloadHistory();
|
||||
|
||||
var remoteMovie = new RemoteMovie
|
||||
{
|
||||
Movie = new Movie() { Id = 3 },
|
||||
|
||||
ParsedMovieInfo = new ParsedMovieInfo()
|
||||
{
|
||||
MovieTitle = "A Movie",
|
||||
Year = 1998
|
||||
}
|
||||
};
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), null))
|
||||
.Returns(new MappingResult { RemoteMovie = remoteMovie });
|
||||
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
|
||||
.Returns(new List<MovieHistory>());
|
||||
|
||||
ParseMovieTitle();
|
||||
|
||||
var client = new DownloadClientDefinition()
|
||||
{
|
||||
Id = 1,
|
||||
Protocol = DownloadProtocol.Torrent
|
||||
};
|
||||
|
||||
var item = new DownloadClientItem()
|
||||
{
|
||||
Title = "A Movie 1998",
|
||||
DownloadId = "12345",
|
||||
DownloadClientInfo = new DownloadClientItemClientInfo
|
||||
{
|
||||
Id = 1,
|
||||
Type = "Blackhole",
|
||||
Name = "Blackhole Client",
|
||||
Protocol = DownloadProtocol.Torrent
|
||||
}
|
||||
};
|
||||
|
||||
Subject.TrackDownload(client, item);
|
||||
Subject.GetTrackedDownloads().Should().HaveCount(1);
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), null))
|
||||
.Returns(new MappingResult { MappingResultType = MappingResultType.Unknown });
|
||||
|
||||
Subject.Handle(new MoviesDeletedEvent(new List<Movie> { remoteMovie.Movie }, false, false));
|
||||
|
||||
var trackedDownloads = Subject.GetTrackedDownloads();
|
||||
trackedDownloads.Should().HaveCount(1);
|
||||
trackedDownloads.First().RemoteMovie.Should().BeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_throw_when_processing_deleted_movie()
|
||||
{
|
||||
GivenDownloadHistory();
|
||||
|
||||
var remoteMovie = new RemoteMovie
|
||||
{
|
||||
Movie = new Movie() { Id = 3 },
|
||||
|
||||
ParsedMovieInfo = new ParsedMovieInfo()
|
||||
{
|
||||
MovieTitle = "A Movie",
|
||||
Year = 1998
|
||||
}
|
||||
};
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), null))
|
||||
.Returns(new MappingResult { MappingResultType = MappingResultType.Unknown });
|
||||
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
|
||||
.Returns(new List<MovieHistory>());
|
||||
|
||||
ParseMovieTitle();
|
||||
|
||||
var client = new DownloadClientDefinition()
|
||||
{
|
||||
Id = 1,
|
||||
Protocol = DownloadProtocol.Torrent
|
||||
};
|
||||
|
||||
var item = new DownloadClientItem()
|
||||
{
|
||||
Title = "A Movie 1998",
|
||||
DownloadId = "12345",
|
||||
DownloadClientInfo = new DownloadClientItemClientInfo
|
||||
{
|
||||
Id = 1,
|
||||
Type = "Blackhole",
|
||||
Name = "Blackhole Client",
|
||||
Protocol = DownloadProtocol.Torrent
|
||||
}
|
||||
};
|
||||
|
||||
Subject.TrackDownload(client, item);
|
||||
Subject.GetTrackedDownloads().Should().HaveCount(1);
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), null))
|
||||
.Returns(new MappingResult { MappingResultType = MappingResultType.Unknown });
|
||||
|
||||
Subject.Handle(new MoviesDeletedEvent(new List<Movie> { remoteMovie.Movie }, false, false));
|
||||
|
||||
var trackedDownloads = Subject.GetTrackedDownloads();
|
||||
trackedDownloads.Should().HaveCount(1);
|
||||
trackedDownloads.First().RemoteMovie.Should().BeNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Processes;
|
||||
using NzbDrone.Core.HealthCheck.Checks;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
@@ -9,6 +11,14 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
[TestFixture]
|
||||
public class MonoNotNetCoreCheckFixture : CoreTest<MonoNotNetCoreCheck>
|
||||
{
|
||||
[SetUp]
|
||||
public void setup()
|
||||
{
|
||||
Mocker.GetMock<ILocalizationService>()
|
||||
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
|
||||
.Returns("Some Warning Message");
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Platform(Exclude = "Mono")]
|
||||
public void should_return_ok_if_net_core()
|
||||
@@ -18,14 +28,14 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
|
||||
[Test]
|
||||
[Platform("Mono")]
|
||||
public void should_log_warning_if_mono()
|
||||
public void should_log_error_if_mono()
|
||||
{
|
||||
Subject.Check().ShouldBeWarning();
|
||||
Subject.Check().ShouldBeError();
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Platform("Mono")]
|
||||
public void should_return_ok_if_otherbsd()
|
||||
public void should_return_error_if_otherbsd()
|
||||
{
|
||||
Mocker.GetMock<IProcessProvider>()
|
||||
.Setup(x => x.StartAndCapture("uname", null, null))
|
||||
@@ -36,12 +46,12 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
new ProcessOutputLine(ProcessOutputLevel.Standard, "OpenBSD")
|
||||
}
|
||||
});
|
||||
Subject.Check().ShouldBeOk();
|
||||
Subject.Check().ShouldBeError();
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Platform("Mono")]
|
||||
public void should_log_warning_if_freebsd()
|
||||
public void should_log_error_if_freebsd()
|
||||
{
|
||||
Mocker.GetMock<IProcessProvider>()
|
||||
.Setup(x => x.StartAndCapture("uname", null, null))
|
||||
@@ -52,7 +62,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
new ProcessOutputLine(ProcessOutputLevel.Standard, "FreeBSD")
|
||||
}
|
||||
});
|
||||
Subject.Check().ShouldBeWarning();
|
||||
Subject.Check().ShouldBeError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.Clients;
|
||||
using NzbDrone.Core.HealthCheck.Checks;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -84,6 +85,10 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
return false;
|
||||
});
|
||||
|
||||
Mocker.GetMock<ILocalizationService>()
|
||||
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
|
||||
.Returns("Some Warning Message");
|
||||
}
|
||||
|
||||
private void GivenFolderExists(string folder)
|
||||
|
||||
@@ -10,6 +10,9 @@ namespace NzbDrone.Core.Test.Languages
|
||||
{
|
||||
public static object[] FromIntCases =
|
||||
{
|
||||
new object[] { -2, Language.Original },
|
||||
new object[] { -1, Language.Any },
|
||||
new object[] { 0, Language.Unknown },
|
||||
new object[] { 1, Language.English },
|
||||
new object[] { 2, Language.French },
|
||||
new object[] { 3, Language.Spanish },
|
||||
@@ -45,6 +48,9 @@ namespace NzbDrone.Core.Test.Languages
|
||||
|
||||
public static object[] ToIntCases =
|
||||
{
|
||||
new object[] { Language.Original, -2 },
|
||||
new object[] { Language.Any, -1 },
|
||||
new object[] { Language.Unknown, 0 },
|
||||
new object[] { Language.English, 1 },
|
||||
new object[] { Language.French, 2 },
|
||||
new object[] { Language.Spanish, 3 },
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
|
||||
[TestCase("The Danish Movie 2015")]
|
||||
[TestCase("Movie.Title.2018.2160p.WEBRip.x265.10bit.HDR.DD5.1-GASMASK")]
|
||||
[TestCase("Movie.Title.2010.720p.BluRay.x264.-[YTS.LT]")]
|
||||
public void should_parse_language_unknown(string postTitle)
|
||||
{
|
||||
var result = Parser.Parser.ParseMovieTitle(postTitle, true);
|
||||
|
||||
@@ -27,6 +27,8 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||
private ParsedMovieInfo _translationTitleInfo;
|
||||
private ParsedMovieInfo _umlautInfo;
|
||||
private ParsedMovieInfo _umlautAltInfo;
|
||||
private ParsedMovieInfo _multiLanguageInfo;
|
||||
private ParsedMovieInfo _multiLanguageWithOriginalInfo;
|
||||
private MovieSearchCriteria _movieSearchCriteria;
|
||||
|
||||
[SetUp]
|
||||
@@ -97,6 +99,18 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||
Year = _movie.Year
|
||||
};
|
||||
|
||||
_multiLanguageInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitle = _movie.Title,
|
||||
Languages = new List<Language> { Language.Original, Language.French }
|
||||
};
|
||||
|
||||
_multiLanguageWithOriginalInfo = new ParsedMovieInfo
|
||||
{
|
||||
MovieTitle = _movie.Title,
|
||||
Languages = new List<Language> { Language.Original, Language.French, Language.English }
|
||||
};
|
||||
|
||||
_movieSearchCriteria = new MovieSearchCriteria
|
||||
{
|
||||
Movie = _movie
|
||||
@@ -180,5 +194,20 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||
Subject.Map(_umlautInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie);
|
||||
Subject.Map(_umlautAltInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_convert_original()
|
||||
{
|
||||
Subject.Map(_multiLanguageInfo, "", _movieSearchCriteria).RemoteMovie.ParsedMovieInfo.Languages.Should().Contain(Language.English);
|
||||
Subject.Map(_multiLanguageInfo, "", _movieSearchCriteria).RemoteMovie.ParsedMovieInfo.Languages.Should().Contain(Language.French);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_remove_original_as_already_exists()
|
||||
{
|
||||
Subject.Map(_multiLanguageWithOriginalInfo, "", _movieSearchCriteria).RemoteMovie.ParsedMovieInfo.Languages.Should().Contain(Language.English);
|
||||
Subject.Map(_multiLanguageWithOriginalInfo, "", _movieSearchCriteria).RemoteMovie.ParsedMovieInfo.Languages.Should().Contain(Language.French);
|
||||
Subject.Map(_multiLanguageWithOriginalInfo, "", _movieSearchCriteria).RemoteMovie.ParsedMovieInfo.Languages.Should().NotContain(Language.Original);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("SomeMovie.1080p.BluRay.DTS.x264.-FTW-HS.mkv", "FTW-HS")]
|
||||
[TestCase("SomeMovie.1080p.BluRay.DTS.x264.-VH-PROD.mkv", "VH-PROD")]
|
||||
[TestCase("Some.Dead.Movie.2006.1080p.BluRay.DTS.x264.D-Z0N3", "D-Z0N3")]
|
||||
[TestCase("Movie.Title.2010.720p.BluRay.x264.-[YTS.LT]", "YTS.LT")]
|
||||
|
||||
//[TestCase("", "")]
|
||||
|
||||
|
||||
@@ -6,5 +6,6 @@ namespace NzbDrone.Core.Annotations
|
||||
public string Name { get; set; }
|
||||
public int Order { get; set; }
|
||||
public string Hint { get; set; }
|
||||
public bool DividerAfter { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Lifecycle;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
@@ -24,12 +24,17 @@ namespace NzbDrone.Core.Authentication
|
||||
private readonly IUserRepository _repo;
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
|
||||
public UserService(IUserRepository repo, IAppFolderInfo appFolderInfo, IDiskProvider diskProvider)
|
||||
public UserService(IUserRepository repo,
|
||||
IAppFolderInfo appFolderInfo,
|
||||
IDiskProvider diskProvider,
|
||||
IConfigFileProvider configFileProvider)
|
||||
{
|
||||
_repo = repo;
|
||||
_appFolderInfo = appFolderInfo;
|
||||
_diskProvider = diskProvider;
|
||||
_configFileProvider = configFileProvider;
|
||||
}
|
||||
|
||||
public User Add(string username, string password)
|
||||
@@ -105,14 +110,7 @@ namespace NzbDrone.Core.Authentication
|
||||
return;
|
||||
}
|
||||
|
||||
var configFile = _appFolderInfo.GetConfigPath();
|
||||
|
||||
if (!_diskProvider.FileExists(configFile))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var xDoc = XDocument.Load(configFile);
|
||||
var xDoc = _configFileProvider.LoadConfigFile();
|
||||
var config = xDoc.Descendants("Config").Single();
|
||||
var usernameElement = config.Descendants("Username").FirstOrDefault();
|
||||
var passwordElement = config.Descendants("Password").FirstOrDefault();
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace NzbDrone.Core.Configuration
|
||||
public interface IConfigFileProvider : IHandleAsync<ApplicationStartedEvent>,
|
||||
IExecute<ResetApiKeyCommand>
|
||||
{
|
||||
XDocument LoadConfigFile();
|
||||
Dictionary<string, object> GetConfigDictionary();
|
||||
void SaveConfigDictionary(Dictionary<string, object> configValues);
|
||||
|
||||
@@ -310,7 +311,7 @@ namespace NzbDrone.Core.Configuration
|
||||
SaveConfigFile(xDoc);
|
||||
}
|
||||
|
||||
private XDocument LoadConfigFile()
|
||||
public XDocument LoadConfigFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(194)]
|
||||
public class add_bypass_to_delay_profile : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("DelayProfiles").AddColumn("BypassIfHighestQuality").AsBoolean().WithDefaultValue(false);
|
||||
|
||||
// Set to true for existing Delay Profiles to keep behavior the same.
|
||||
Execute.Sql("UPDATE DelayProfiles SET BypassIfHighestQuality = 1;");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using FluentMigrator;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(195)]
|
||||
public class update_notifiarr : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Execute.Sql("UPDATE Notifications SET Implementation = Replace(Implementation, 'DiscordNotifier', 'Notifiarr'),ConfigContract = Replace(ConfigContract, 'DiscordNotifierSettings', 'NotifiarrSettings') WHERE Implementation = 'DiscordNotifier';");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.CustomFormats;
|
||||
using NzbDrone.Core.Download.Pending;
|
||||
@@ -78,13 +77,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
}
|
||||
|
||||
// If quality meets or exceeds the best allowed quality in the profile accept it immediately
|
||||
var bestQualityInProfile = profile.LastAllowedQuality();
|
||||
var isBestInProfile = comparer.Compare(subject.ParsedMovieInfo.Quality.Quality, bestQualityInProfile) >= 0;
|
||||
|
||||
if (isBestInProfile && isPreferredProtocol)
|
||||
if (delayProfile.BypassIfHighestQuality)
|
||||
{
|
||||
_logger.Debug("Quality is highest in profile for preferred protocol, will not delay.");
|
||||
return Decision.Accept();
|
||||
var bestQualityInProfile = profile.LastAllowedQuality();
|
||||
var isBestInProfile = comparer.Compare(subject.ParsedMovieInfo.Quality.Quality, bestQualityInProfile) >= 0;
|
||||
|
||||
if (isBestInProfile && isPreferredProtocol)
|
||||
{
|
||||
_logger.Debug("Quality is highest in profile for preferred protocol, will not delay.");
|
||||
return Decision.Accept();
|
||||
}
|
||||
}
|
||||
|
||||
var oldest = _pendingReleaseService.OldestPendingRelease(subject.Movie.Id);
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
var qualityComparer = new QualityModelComparer(profile);
|
||||
var qualityCompare = qualityComparer.Compare(newQuality?.Quality, currentQuality.Quality);
|
||||
var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks;
|
||||
|
||||
if (qualityCompare > 0)
|
||||
{
|
||||
@@ -45,10 +46,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
return false;
|
||||
}
|
||||
|
||||
var qualityRevisionCompare = newQuality?.Revision.CompareTo(currentQuality.Revision);
|
||||
|
||||
// Accept unless the user doesn't want to prefer propers, optionally they can
|
||||
// use preferred words to prefer propers/repacks over non-propers/repacks.
|
||||
if (_configService.DownloadPropersAndRepacks != ProperDownloadTypes.DoNotPrefer &&
|
||||
newQuality?.Revision.CompareTo(currentQuality.Revision) > 0)
|
||||
if (downloadPropersAndRepacks != ProperDownloadTypes.DoNotPrefer &&
|
||||
qualityRevisionCompare > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -56,6 +59,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
var currentFormatScore = profile.CalculateCustomFormatScore(currentCustomFormats);
|
||||
var newFormatScore = profile.CalculateCustomFormatScore(newCustomFormats);
|
||||
|
||||
// Reject unless the user does not prefer propers/repacks and it's a revision downgrade.
|
||||
if (downloadPropersAndRepacks != ProperDownloadTypes.DoNotPrefer &&
|
||||
qualityRevisionCompare < 0)
|
||||
{
|
||||
_logger.Debug("Existing item has a better quality revision, skipping");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newFormatScore <= currentFormatScore)
|
||||
{
|
||||
_logger.Debug("New item's custom formats [{0}] do not improve on [{1}], skipping",
|
||||
|
||||
@@ -106,14 +106,14 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
public override void RemoveItem(DownloadClientItem item, bool deleteData)
|
||||
{
|
||||
if (!deleteData)
|
||||
{
|
||||
throw new NotSupportedException("Blackhole cannot remove DownloadItem without deleting the data as well, ignoring.");
|
||||
}
|
||||
|
||||
DeleteItemData(downloadId);
|
||||
DeleteItemData(item);
|
||||
}
|
||||
|
||||
public override DownloadClientInfo GetStatus()
|
||||
|
||||
@@ -78,14 +78,14 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
public override void RemoveItem(DownloadClientItem item, bool deleteData)
|
||||
{
|
||||
if (!deleteData)
|
||||
{
|
||||
throw new NotSupportedException("Blackhole cannot remove DownloadItem without deleting the data as well, ignoring.");
|
||||
}
|
||||
|
||||
DeleteItemData(downloadId);
|
||||
DeleteItemData(item);
|
||||
}
|
||||
|
||||
public override DownloadClientInfo GetStatus()
|
||||
|
||||
@@ -190,9 +190,9 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
return items;
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
public override void RemoveItem(DownloadClientItem item, bool deleteData)
|
||||
{
|
||||
_proxy.RemoveTorrent(downloadId.ToLower(), deleteData, Settings);
|
||||
_proxy.RemoveTorrent(item.DownloadId.ToLower(), deleteData, Settings);
|
||||
}
|
||||
|
||||
public override DownloadClientInfo GetStatus()
|
||||
|
||||
@@ -134,15 +134,15 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
public override void RemoveItem(DownloadClientItem item, bool deleteData)
|
||||
{
|
||||
if (deleteData)
|
||||
{
|
||||
DeleteItemData(downloadId);
|
||||
DeleteItemData(item);
|
||||
}
|
||||
|
||||
_dsTaskProxy.RemoveTask(ParseDownloadId(downloadId), Settings);
|
||||
_logger.Debug("{0} removed correctly", downloadId);
|
||||
_dsTaskProxy.RemoveTask(ParseDownloadId(item.DownloadId), Settings);
|
||||
_logger.Debug("{0} removed correctly", item.DownloadId);
|
||||
}
|
||||
|
||||
protected OsPath GetOutputPath(OsPath outputPath, DownloadStationTask torrent, string serialNumber)
|
||||
|
||||
@@ -158,15 +158,15 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
public override void RemoveItem(DownloadClientItem item, bool deleteData)
|
||||
{
|
||||
if (deleteData)
|
||||
{
|
||||
DeleteItemData(downloadId);
|
||||
DeleteItemData(item);
|
||||
}
|
||||
|
||||
_dsTaskProxy.RemoveTask(ParseDownloadId(downloadId), Settings);
|
||||
_logger.Debug("{0} removed correctly", downloadId);
|
||||
_dsTaskProxy.RemoveTask(ParseDownloadId(item.DownloadId), Settings);
|
||||
_logger.Debug("{0} removed correctly", item.DownloadId);
|
||||
}
|
||||
|
||||
protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContent)
|
||||
|
||||
@@ -201,9 +201,10 @@ namespace NzbDrone.Core.Download.Clients.Flood
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
public override void RemoveItem(DownloadClientItem item, bool deleteData)
|
||||
{
|
||||
_proxy.DeleteTorrent(downloadId, deleteData, Settings);
|
||||
_proxy.DeleteTorrent(item.DownloadId, deleteData, Settings);
|
||||
_proxy.DeleteTorrent(item.DownloadId, deleteData, Settings);
|
||||
}
|
||||
|
||||
public override DownloadClientInfo GetStatus()
|
||||
|
||||
@@ -109,15 +109,15 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
|
||||
return items;
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
public override void RemoveItem(DownloadClientItem item, bool deleteData)
|
||||
{
|
||||
if (deleteData)
|
||||
{
|
||||
_proxy.RemoveTorrentAndData(Settings, downloadId);
|
||||
_proxy.RemoveTorrentAndData(Settings, item.DownloadId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_proxy.RemoveTorrent(Settings, downloadId);
|
||||
_proxy.RemoveTorrent(Settings, item.DownloadId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -121,19 +121,19 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
return queueItems;
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
public override void RemoveItem(DownloadClientItem item, bool deleteData)
|
||||
{
|
||||
// Try to find the download by numerical ID, otherwise try by AddUUID
|
||||
int id;
|
||||
|
||||
if (int.TryParse(downloadId, out id))
|
||||
if (int.TryParse(item.DownloadId, out id))
|
||||
{
|
||||
_proxy.Remove(id, deleteData, Settings);
|
||||
}
|
||||
else
|
||||
{
|
||||
var queue = _proxy.GetQueue(30, Settings);
|
||||
var queueItem = queue.FirstOrDefault(c => c.AddUUID == downloadId);
|
||||
var queueItem = queue.FirstOrDefault(c => c.AddUUID == item.DownloadId);
|
||||
|
||||
if (queueItem != null)
|
||||
{
|
||||
|
||||
@@ -195,14 +195,14 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
return GetQueue().Concat(GetHistory()).Where(downloadClientItem => downloadClientItem.Category == Settings.MovieCategory);
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
public override void RemoveItem(DownloadClientItem item, bool deleteData)
|
||||
{
|
||||
if (deleteData)
|
||||
{
|
||||
DeleteItemData(downloadId);
|
||||
DeleteItemData(item);
|
||||
}
|
||||
|
||||
_proxy.RemoveItem(downloadId, Settings);
|
||||
_proxy.RemoveItem(item.DownloadId, Settings);
|
||||
}
|
||||
|
||||
public override DownloadClientInfo GetStatus()
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
|
||||
public int Port { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Sabnzbd")]
|
||||
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to NZBGet")]
|
||||
public bool UseSsl { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the nzbget url, e.g. http://[host]:[port]/[urlBase]/jsonrpc")]
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
public override void RemoveItem(DownloadClientItem item, bool deleteData)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
_logger.Warn(ex, "Failed to set the torrent seed criteria for {0}.", hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (moveToTop)
|
||||
{
|
||||
@@ -320,9 +320,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
return queueItems;
|
||||
}
|
||||
|
||||
public override void RemoveItem(string hash, bool deleteData)
|
||||
public override void RemoveItem(DownloadClientItem item, bool deleteData)
|
||||
{
|
||||
Proxy.RemoveTorrent(hash.ToLower(), deleteData, Settings);
|
||||
Proxy.RemoveTorrent(item.DownloadId.ToLower(), deleteData, Settings);
|
||||
}
|
||||
|
||||
public override DownloadClientItem GetImportItem(DownloadClientItem item, DownloadClientItem previousImportAttempt)
|
||||
|
||||
@@ -194,47 +194,22 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
public override void RemoveItem(DownloadClientItem item, bool deleteData)
|
||||
{
|
||||
var historyItem = GetHistory().SingleOrDefault(v => v.DownloadId == downloadId);
|
||||
var queueClientItem = GetQueue().SingleOrDefault(v => v.DownloadId == item.DownloadId);
|
||||
|
||||
if (historyItem == null)
|
||||
if (queueClientItem == null)
|
||||
{
|
||||
_proxy.RemoveFrom("queue", downloadId, deleteData, Settings);
|
||||
if (deleteData && item.Status == DownloadItemStatus.Completed)
|
||||
{
|
||||
DeleteItemData(item);
|
||||
}
|
||||
|
||||
_proxy.RemoveFrom("history", item.DownloadId, deleteData, Settings);
|
||||
}
|
||||
else
|
||||
{
|
||||
_proxy.RemoveFrom("history", downloadId, deleteData, Settings);
|
||||
|
||||
// Completed items in SAB's history do not remove the files from the file system when deleted, even if instructed to.
|
||||
// If the output path is valid delete the file(s), otherwise warn that they cannot be deleted.
|
||||
if (deleteData && historyItem.Status == DownloadItemStatus.Completed)
|
||||
{
|
||||
if (ValidatePath(historyItem))
|
||||
{
|
||||
var outputPath = historyItem.OutputPath;
|
||||
|
||||
try
|
||||
{
|
||||
if (_diskProvider.FolderExists(outputPath.FullPath))
|
||||
{
|
||||
_diskProvider.DeleteFolder(outputPath.FullPath.ToString(), true);
|
||||
}
|
||||
else if (_diskProvider.FileExists(outputPath.FullPath))
|
||||
{
|
||||
_diskProvider.DeleteFile(outputPath.FullPath.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error("Unable to delete output path: '{0}'. Delete file(s) manually", outputPath.FullPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("Invalid path '{0}'. Delete file(s) manually");
|
||||
}
|
||||
}
|
||||
_proxy.RemoveFrom("queue", item.DownloadId, deleteData, Settings);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -159,9 +159,9 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
public override void RemoveItem(DownloadClientItem item, bool deleteData)
|
||||
{
|
||||
_proxy.RemoveTorrent(downloadId.ToLower(), deleteData, Settings);
|
||||
_proxy.RemoveTorrent(item.DownloadId.ToLower(), deleteData, Settings);
|
||||
}
|
||||
|
||||
public override DownloadClientInfo GetStatus()
|
||||
|
||||
@@ -26,9 +26,9 @@ namespace NzbDrone.Core.Download.Clients.Vuze
|
||||
{
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
public override void RemoveItem(DownloadClientItem item, bool deleteData)
|
||||
{
|
||||
_proxy.RemoveTorrent(downloadId, deleteData, Settings);
|
||||
_proxy.RemoveTorrent(item.DownloadId, deleteData, Settings);
|
||||
}
|
||||
|
||||
protected override OsPath GetOutputPath(OsPath outputPath, TransmissionTorrent torrent)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user