Compare commits

...

109 Commits

Author SHA1 Message Date
Bogdan
ea635e685b Fix duplicate indexers with same name in add modal 2023-06-03 05:32:42 +03:00
Bogdan
73f23d56dc Use HelpText for non-URL values 2023-06-02 18:24:14 +03:00
Bogdan
f14ccebf3a Update magnet trackers 2023-06-02 15:16:56 +03:00
Qstick
9539e4d481 More mono cleanup 2023-06-02 00:20:18 -05:00
Bogdan
e40ccc49ad Fixed: (Apps) Prevent null reference exception on sync failure 2023-06-02 07:26:25 +03:00
Qstick
9fd3eb4d6b Extract useSelectState from SelectContext
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2023-05-31 19:41:55 -05:00
Qstick
78aab80703 New: Additional custom filter predicates for strings 2023-05-31 19:33:28 -05:00
Weblate
868394d588 Translations update from Servarr Weblate
Co-authored-by: Cc95459 <954591059@qq.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Jens <jensmahnke@me.com>
Co-authored-by: Thijs Waalen <contact@thijswaalen.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-06-01 01:58:57 +03:00
Bogdan
d5e5697db8 Remove Rarbg tracker from MagnetLinkBuilder 2023-05-31 21:06:14 +03:00
Bogdan
d1e39f206a New: (Rarbg) Obsolete due to tracker shutdown 2023-05-31 18:00:24 +03:00
Bogdan
b59d89f308 Fixed: Don't log handled exceptions in API 2023-05-31 06:53:51 +03:00
Bogdan
bf5855beb4 Revert "Fixed: Don't log handled exceptions in API"
This reverts commit 19ff73dad0.
2023-05-31 06:53:43 +03:00
Bogdan
2d36adf865 Fixed: (Cardigann): Use MissingAttributeEqualsNoResults for Search.Rows.Attribute 2023-05-29 17:35:37 +03:00
Bogdan
ef1ad59f59 Fixed: (Cardigann) Respect the categories from search paths 2023-05-29 00:57:12 +03:00
Servarr
59b6e8af27 Automated API Docs update 2023-05-28 22:31:36 +03:00
Bogdan
3ae1917d3b Fixed: Revert seed criteria validation to warnings
Closes #1692
2023-05-28 22:21:08 +03:00
Bogdan
5864a090e4 Fixed: Enforce validation warnings
(cherry picked from commit 48ee1158ad4213fd0690842e2672f52d08f7ad26)
2023-05-28 22:18:34 +03:00
Bogdan
fcfec1b859 Simplify ShouldHaveApiKey and HasErrors
(cherry picked from commit 7343616a47cd538bba4c9128d2c1094561f9b3a5)
2023-05-28 22:17:31 +03:00
Bogdan
65541017dd Fixed: (RuTracker) Update categories 2023-05-28 20:50:15 +03:00
SetekhZ
7fe9942c28 Fixed: (RuTracker) Use supported 200 categories per search request 2023-05-28 19:40:06 +03:00
Bogdan
360827708f Use 'var' instead of explicit type
(cherry picked from commit 12374f7f0038e5b25548f5ab3f71122410832393)
2023-05-28 18:52:10 +03:00
Bogdan
0509335387 Inline 'out' variable declarations
(cherry picked from commit 281add47de1d3940990156c841362125dea9cc7d)
2023-05-28 18:45:10 +03:00
Bogdan
f54212a809 Standardize variable declaration
(cherry picked from commit 909f2ded6b75998fa8e1addd0dcf849279e7b120)
2023-05-28 18:40:17 +03:00
Bogdan
ea0eb2efa7 Enforce rule IDE0005 on build
(cherry picked from commit 6b1e4ef81938d264a2ddc8b626b0502f799aa640)
2023-05-28 18:39:57 +03:00
Bogdan
ce430433e5 Bump version to 1.5.2 2023-05-28 18:21:49 +03:00
Matthew Strapp
5437aac346 Fixed: Use relative paths instead of absolute paths for webmanifest
(cherry picked from commit 8e771f95ade919a8f1ed7b48675f032a6c508cb2)
2023-05-28 17:33:23 +03:00
Bogdan
b02188acf4 Fixed: (HealthCheck) Check only enabled indexer proxies 2023-05-28 14:55:20 +03:00
Bogdan
6897ed0b3f Fixed: (Anidex) Search with all categories selected 2023-05-28 02:16:06 +03:00
Bogdan
b3ddf2f9cd Improve logging when no releases were found 2023-05-28 02:04:39 +03:00
Bogdan
d9ce9eb0b2 Add defaults definitions for indexer proxies 2023-05-28 01:52:38 +03:00
bakerboy448
29ab1801db Fixed a really important spelling mistake
(cherry picked from commit b510201b43f6bc5e6774119ebbd7b8a0d89ee487)
2023-05-27 13:08:16 +03:00
Mark McDowall
19ff73dad0 Fixed: Don't log handled exceptions in API
(cherry picked from commit 59f2e5b65dd7352aad92b33adefa6cf5ca79a0de)
2023-05-27 13:03:36 +03:00
Bogdan
c455f1a113 New: (BakaBT) Add freeleech only option 2023-05-26 20:45:23 +03:00
Qstick
b8793d8783 Remove mono process detection
(cherry picked from commit 5a046026725084bc880a7b63d7105dcf4d882128)
2023-05-26 16:51:33 +03:00
Bogdan
ce34940287 Ensuring backward compatibility with older versions on first sync 2023-05-26 09:54:51 +03:00
Bogdan
dcb19a66b0 New: Add minimum version checks for applications 2023-05-26 09:54:51 +03:00
Weblate
b3bc92e60e Translated using Weblate (Indonesian)
Currently translated at 3.5% (18 of 514 strings)

Co-authored-by: liimee <git.taaa@fedora.email>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/id/
Translation: Servarr/Prowlarr
2023-05-25 13:38:21 +03:00
bakerboy448
1b17d38564 Fixed: (Animedia) Description Language 2023-05-24 23:17:34 -05:00
Bogdan
d8c7361205 Convert typeof to nameof 2023-05-24 19:25:08 +03:00
Bogdan
7a0dd0bc0d Fixed: (AnimeTorrents) Replace non-word chars with wildcard in search term 2023-05-24 00:15:13 +03:00
Mark McDowall
c02bfb5930 Fixed: Don't rollback file move if destination already exists
(cherry picked from commit f05405fe1ce4c78a8c75e27920c863c5b83686bd)
(cherry picked from commit 8ab040f612ee04dac4813a08cdeaddd446a64dc9)
2023-05-23 20:16:53 +03:00
Weblate
d0fbb1f49a Translated using Weblate (French)
Currently translated at 98.6% (507 of 514 strings)

Co-authored-by: foXaCe <foxace66@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translation: Servarr/Prowlarr
2023-05-23 10:36:59 +03:00
Bogdan
aafdefe2f0 Fixed: (RuTracker) Improve the error message for failed logins 2023-05-22 14:12:41 +03:00
Bogdan
96234c0fe1 Fixed: (SceneTime) Update categories 2023-05-21 22:13:11 +03:00
Bogdan
8b5648d7bd Fix spelling "Use languages from Torznab/Newznab attributes if given"
(cherry picked from commit de3bfb7c5ab03e527dca1be3ef4a664dce266db6)
2023-05-21 21:15:20 +03:00
Qstick
1fc79f9e9b New: Use languages from Torznab/Newznab attributes if given
(cherry picked from commit 9c5a07f62a6e32832c10c80813cd3b98c5859989)
2023-05-21 21:13:48 +03:00
S0me6uy
ec40761757 New: Signal Notifications
(cherry picked from commit 59dd3b11271a63ea16f0e32a596dba8e9b9d1096)
2023-05-21 21:01:46 +03:00
Bogdan
0a8e4eb092 New: Improve validation messages
(cherry picked from commit a117001de673e80abd90d54a34a7c86292b3a649)
2023-05-21 20:59:31 +03:00
Bogdan
ade961fad5 Minor CS improvements in NzbDroneValidation
(cherry picked from commit 6118afa339621509aad55caf27b05e89bd0b8c74)
2023-05-21 20:58:25 +03:00
Bogdan
81b1c0e445 Add tests and ignore 0 in GetFullImdbId 2023-05-21 11:53:06 +03:00
Bogdan
0fe54ed36a Fix tests in IndexerServiceFixture 2023-05-21 11:52:54 +03:00
Bogdan
337828ff9c Bump version to 1.5.1 2023-05-21 10:15:52 +03:00
Mark McDowall
fb34294d2e Fixed: Exception when request to SABnzbd times out
(cherry picked from commit f946d78153b85ad726a06a1140143c8beac8766d)
2023-05-21 10:10:17 +03:00
Mark McDowall
931e3cf42d Cleanup TorrentDownloadStation
Fixed: Don't move seeding torrents in Synology Download Station

(cherry picked from commit 3cd33d3f44097b4cb4fb291bca70a0aa53c4b844)
2023-05-21 10:09:21 +03:00
Bogdan
051930455e Add tests for normalizing IMDb IDs in ReleaseSearchService 2023-05-20 19:46:38 +03:00
Bogdan
eba5413250 Format ImdbId to 7 digits in ReleaseSearchService
Fixes #1679
2023-05-20 14:47:28 +03:00
Bogdan
cc2f50544b New: Show tags in Applications and Notifications index 2023-05-20 03:04:50 +03:00
Bogdan
450c6d7af5 Fixed: (Cardigann) Ignore disabled or unchecked inputs in login 2023-05-20 02:02:36 +03:00
Bogdan
bdc0178e44 Limit ESLint configuration to this project 2023-05-20 01:56:41 +03:00
Bogdan
aa9705846e Fixed: (XSpeeds) Update categories 2023-05-19 05:35:08 +03:00
Servarr
7559a87bc8 Automated API Docs update 2023-05-19 05:11:39 +03:00
Bogdan
6a7fe30171 Fixed: Use indexer errors from response if Content-Type is XML before processing response
(cherry picked from commit 9bdc6183663a3510e53433a30ad701065e7ee9d9)
2023-05-19 04:21:10 +03:00
bakerboy448
2b0f4e18e7 Add forceSave to the OpenAPI docs (#1670) 2023-05-19 03:28:49 +03:00
Bogdan
4a5a986220 Replace UC preset with VC
(cherry picked from commit eca3776ddd4b12020833967ad9d98daa0117caff)
2023-05-19 01:56:51 +03:00
Bogdan
38ae17a99f Fixed: (GreatPosterWall) Fetch latest 50 releases when using grouped torrents 2023-05-18 04:32:37 +03:00
Bogdan
9a72da2803 Fixed: Log name of mount point failure
(cherry picked from commit b5050d02d6adbaaaa0f8ae9f8426551e5606fff1)
2023-05-18 04:32:37 +03:00
bakerboy448
3bba76caab Simplify new expression for Newznab categories (#1669)
IDE0090 - simplify newznab categories
2023-05-18 02:51:01 +03:00
Bogdan
47ceabc834 Replace API request with indexer request 2023-05-18 02:48:38 +03:00
Bogdan
48bb3196dd Fixed: (Cardigann) Check redirect for /login.php 2023-05-18 02:47:11 +03:00
Bogdan
4c4ebdf17c Fixed: (Gazelle) Don't use usetoken=0 when UseFreeleechToken is not enabled
Fixes #1668
2023-05-18 02:23:52 +03:00
Bogdan
b5706a0d55 Remove not in use ContentType header for auth requests for AvistaZ 2023-05-17 20:53:17 +03:00
Qstick
d946ef4a9e Convert method to static that doesn't use instance data
(cherry picked from commit a42f97229acb713719c616851db572100f319ad7)
2023-05-17 20:24:15 +03:00
Weblate
48ec5bbaa1 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (514 of 514 strings)

Translated using Weblate (French)

Currently translated at 98.6% (507 of 514 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: foXaCe <foxace66@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-05-17 17:45:30 +03:00
Bogdan
2bcdae44c7 Fixed: (HDTorrents) Use Accept Html for indexer requests 2023-05-17 17:39:50 +03:00
Bogdan
541b8b4f7f Increase Request Timeout in Download File
Closes #1655
2023-05-16 15:02:25 +03:00
Weblate
8dd79c38d5 Translated using Weblate (Indonesian)
Currently translated at 2.3% (12 of 514 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (514 of 514 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: liimee <git.taaa@fedora.email>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-05-15 16:55:48 +03:00
Weblate
615b85fffe Translated using Weblate (French)
Currently translated at 98.8% (508 of 514 strings)

Co-authored-by: Antoine <coderademii@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translation: Servarr/Prowlarr
2023-05-13 19:06:55 +03:00
Qstick
ceab19caf9 Fixed: Provider health checks persist after add until next scheduled check
(cherry picked from commit 202449c40c82c6dfd2d15844c578436bbe3c8872)
2023-05-13 18:10:59 +03:00
Bogdan
3d61719a2c Log removal of invalid definitions as warnings 2023-05-13 16:35:32 +03:00
Bogdan
befb354913 Add minimum length as const in ApiKeyValidationCheck 2023-05-13 15:43:19 +03:00
Bogdan
10bbaee55d Update UI dependencies 2023-05-12 22:33:45 +03:00
Benjamin Staneck
131550b92d Add inset to stylelintrc
(cherry picked from commit 6a49f3989a17898c957df8777f0cbb19af647804)
2023-05-12 22:28:54 +03:00
Benjamin Staneck
5f83da9725 Remove unused babel plugins and fix build with profiling
(cherry picked from commit d79f42351fd3d61d180a224d4b8fb51184eb347e)
2023-05-12 22:27:08 +03:00
Benjamin Staneck
1ca8ff5012 Update all relevant dev tool deps
Delete esformatter

Address lint failures

Delete unknown component property

remove deprecated stylelint rules

Address stylelint violation

Update rimraf

(cherry picked from commit 4aba540b894729c730640f03b2f96c451af2dba0)
2023-05-12 22:22:45 +03:00
Benjamin Staneck
061a0c0da8 Add VSCode extension recommendations
To make it easier for new contributors, suggest extensions for the tools we use

(cherry picked from commit 9ebd2f96adb19db7c7357336a37f7b989d21797d)
2023-05-12 22:01:48 +03:00
Benjamin Staneck
4cc2706ee5 Move vscode settings to the frontend folder
Since it applies to all of frontend, I think it makes more sense to have it here instead of src

(cherry picked from commit e12c679cd8961ec9d2ef744761303831b81e64fb)
2023-05-12 22:01:29 +03:00
Benjamin Staneck
32691832a5 Delete various old config files
Delete `jsconfig.json`

This file actually did nothing since we have a `tsconfig.json`. Behavior does not change since `checkJs` is `false` in both.

Delete `.jsbeautifyrc`

Was not used from what I could tell and we have a ESFormatter config file as well and that is basically the successor.

Delete `.csscomb.json`

Was not used from what I could tell, also the project seems dead, last publish 4 years ago. Also we have stylelint in place that covers CSS.

(cherry picked from commit 0da89478cc7a5eec7a35bff47e34b824487661a1)
2023-05-12 22:01:08 +03:00
Bogdan
48977de3b8 Add Pull Request Labeler
Closes #1658
2023-05-12 21:57:04 +03:00
Bogdan
34fbb3e135 Use await using in async methods 2023-05-12 16:32:21 +03:00
Bogdan
d38f2614d3 Remove unused imports 2023-05-11 18:48:51 +03:00
Bogdan
ecc5439464 Enforce code style on build 2023-05-11 18:46:42 +03:00
Bogdan
795274e7e1 Remove empty constructors 2023-05-11 18:44:40 +03:00
Bogdan
eb96fbe956 Fixed: (AnimeTorrents) Add current time of day if date added is today 2023-05-11 17:48:54 +03:00
Weblate
2f1fb396a5 Translated using Weblate (Chinese (Simplified) (zh_CN))
Currently translated at 97.4% (501 of 514 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (514 of 514 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Lithaway <478279934@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-05-11 15:31:22 +03:00
Bogdan
20c085a979 Fix migration running in PostgreSQL 2023-05-08 23:58:48 +03:00
Bogdan
4990e537eb Fixed: AudioBookBay removed 2023-05-08 15:54:47 +03:00
Servarr
f8111ac7ba Automated API Docs update 2023-05-08 15:19:31 +03:00
Bogdan
cb1fd39cb3 API key improvements
Fixed: Special characters in API key
New: Add heathcheck for API Key

(cherry picked from commit 9325140b90f8ac625ae5b26075748c22f6f06158)
2023-05-08 15:13:01 +03:00
Bogdan
5e9094b54c Fixed: custom script error when importing some downloads
Co-authored-by: Qstick <376117+Qstick@users.noreply.github.com>

(cherry picked from commit 8f482c534f15c14a9b3097313a4f5e9273549d88)
2023-05-08 15:04:22 +03:00
Mark McDowall
746d84cf83 Why rename many files when few file do trick
(cherry picked from commit eaa4a358e8eb93e15203001d16e868e22aded5c3)
2023-05-08 15:02:39 +03:00
Mark McDowall
bbe3241b83 GracePeriod not Graceperiod
(cherry picked from commit 993c69530ed34460800f40ecf8a0b7bc9a2f7d48)
2023-05-08 15:01:47 +03:00
The Dark
a86aa4c5d3 New: On Health Restored notification
(cherry picked from commit 5fdc8514da7c7ad98192f2ecb2415b3a7b5d0d05)
2023-05-08 15:01:03 +03:00
Devin Buhl
a753f721d1 New: Send additional information with Webhook and Custom Scripts
(cherry picked from commit e5d6e569cf05cbe431e7ffa98569017d5243d848)
2023-05-08 14:30:32 +03:00
Devin Buhl
202836110e New: Add application URL to host configuration settings
(cherry picked from commit 762042ba97c2ae689cee32d8e66a458f6d7a8adc)
2023-05-08 14:24:50 +03:00
Weblate
1a5e41d831 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (509 of 509 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-05-08 12:49:16 +03:00
Bogdan
e1d0e2c799 Log invalid config file exceptions
Fixes #1648
2023-05-07 16:51:00 +03:00
Bogdan
92e7a38bd0 Fixed: (Rarbg) Move check response by status code to parser 2023-05-07 15:54:31 +03:00
Mark McDowall
008f238dda New: Only add version header for API requests
(cherry picked from commit 453891e620459ff38f7bc43b207004b240fc5fb8)
2023-05-07 14:07:21 +03:00
Bogdan
40125046fa New: Add token authentication for ntfy.sh notifications
Co-authored-by: KucharczykL <lukas@kucharczyk.xyz>

(cherry picked from commit 5bb03a9ddf4d2d33976dfdc39fc70bcf56bf1b49)
2023-05-07 14:06:28 +03:00
353 changed files with 4987 additions and 4497 deletions

View File

@@ -36,9 +36,18 @@ dotnet_naming_style.instance_field_style.capitalization = camel_case
dotnet_naming_style.instance_field_style.required_prefix = _
# Prefer "var" everywhere
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
csharp_style_var_for_built_in_types = true
csharp_style_var_when_type_is_apparent = true
csharp_style_var_elsewhere = true
# Prefer "out" variables to be declared inline
csharp_style_inlined_variable_declaration = true
# Using directive is unnecessary.
dotnet_diagnostic.IDE0005.severity = error
# Use var instead of explicit type
dotnet_diagnostic.IDE0007.severity = error
# Inline variable declaration
dotnet_diagnostic.IDE0018.severity = error
# Stylecop Rules
dotnet_diagnostic.SA0001.severity = none

19
.github/labeler.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
'Area: API':
- src/Prowlarr.Api.V1/**/*
'Area: Db-migration':
- src/NzbDrone.Core/Datastore/Migration/*
'Area: Download Clients':
- src/NzbDrone.Core/Download/Clients/**/*
'Area: Indexer':
- src/NzbDrone.Core/Indexers/**/*
'Area: Notifications':
- src/NzbDrone.Core/Notifications/**/*
'Area: UI':
- frontend/**/*
- package.json
- yarn.lock

12
.github/workflows/labeler.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
name: "Pull Request Labeler"
on:
- pull_request_target
jobs:
triage:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '1.5.0'
majorVersion: '1.5.2'
minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'

View File

@@ -1,25 +0,0 @@
{
"remove-empty-rulesets": true,
"always-semicolon": true,
"color-case": "lower",
"block-indent": " ",
"color-shorthand": false,
"element-case": "lower",
"eof-newline": true,
"leading-zero": true,
"quotes": "double",
"sort-order-fallback": "abc",
"space-before-colon": "",
"space-after-colon": " ",
"space-before-combinator": " ",
"space-after-combinator": " ",
"space-between-declarations": "\n",
"space-before-opening-brace": " ",
"space-after-opening-brace": "\n",
"space-after-selector-delimiter": " ",
"space-before-selector-delimiter": "",
"space-before-closing-brace": "\n",
"strip-spaces": true,
"tab-size": true,
"unitless-zero": false
}

View File

@@ -1,335 +0,0 @@
{
"indent": {
"value": " ",
"FunctionExpression": 1,
"ArrayExpression": 1,
"ObjectExpression": 1
},
"lineBreak": {
"value": "\n",
"before": {
"ArrayPatternClosing": 0,
"ArrayPatternComma": 0,
"ArrayPatternOpening": 0,
"ArrowFunctionExpressionArrow": 0,
"ArrowFunctionExpressionClosingBrace": ">=1",
"ArrowFunctionExpressionOpeningBrace": 0,
"AssignmentExpression": ">=1",
"AssignmentOperator": 0,
"BlockStatement": 0,
"BreakKeyword": ">=1",
"CallExpression": -1,
"CallExpressionClosingParentheses": -1,
"CallExpressionOpeningParentheses": 0,
"CatchClosingBrace": ">=1",
"CatchKeyword": 0,
"CatchOpeningBrace": 0,
"ClassDeclaration": ">=1",
"ClassDeclarationClosingBrace": ">=1",
"ClassDeclarationOpeningBrace": 0,
"ConditionalExpression": ">=1",
"DeleteOperator": ">=1",
"DoWhileStatement": ">=1",
"DoWhileStatementClosingBrace": ">=1",
"DoWhileStatementOpeningBrace": 0,
"ElseIfStatement": 0,
"ElseIfStatementClosingBrace": ">=1",
"ElseIfStatementOpeningBrace": 0,
"ElseStatement": 0,
"ElseStatementClosingBrace": ">=1",
"ElseStatementOpeningBrace": 0,
"EmptyStatement": -1,
"EndOfFile": -1,
"FinallyClosingBrace": ">=1",
"FinallyKeyword": -1,
"FinallyOpeningBrace": 0,
"ForInStatement": ">=1",
"ForInStatementClosingBrace": ">=1",
"ForInStatementExpressionClosing": 0,
"ForInStatementExpressionOpening": 0,
"ForInStatementOpeningBrace": 0,
"ForStatement": ">=1",
"ForStatementClosingBrace": ">=1",
"ForStatementExpressionClosing": "<2",
"ForStatementExpressionOpening": 0,
"ForStatementOpeningBrace": 0,
"FunctionDeclaration": ">=1",
"FunctionDeclarationClosingBrace": ">=1",
"FunctionDeclarationOpeningBrace": 0,
"FunctionExpression": 0,
"FunctionExpressionClosingBrace": 1,
"FunctionExpressionOpeningBrace":0,
"IIFEClosingParentheses": 0,
"IfStatement": ">=1",
"IfStatementClosingBrace": ">=1",
"IfStatementOpeningBrace": 0,
"LogicalExpression": -1,
"MemberExpressionClosing": 0,
"MemberExpressionOpening": 0,
"MemberExpressionPeriod": -1,
"MethodDefinition": ">=1",
"ObjectExpressionClosingBrace": "<=1",
"ObjectPatternClosingBrace": 0,
"ObjectPatternComma": 0,
"ObjectPatternOpeningBrace": 0,
"ParameterDefault": 0,
"Property": "<=2",
"PropertyValue": 0,
"ReturnStatement": -1,
"SwitchClosingBrace": ">=1",
"SwitchOpeningBrace": 0,
"ThisExpression": -1,
"ThrowStatement": ">=1",
"TryClosingBrace": ">=1",
"TryKeyword": -1,
"TryOpeningBrace": 0,
"VariableDeclaration": ">=1",
"VariableDeclarationSemiColon": 0,
"VariableDeclarationWithoutInit": ">=1",
"VariableName": ">=1",
"VariableValue": 0,
"WhileStatement": ">=1",
"WhileStatementClosingBrace": ">=1",
"WhileStatementOpeningBrace": 0
},
"after": {
"ArrayPatternClosing": 0,
"ArrayPatternComma": 0,
"ArrayPatternOpening": 0,
"ArrowFunctionExpressionArrow": 0,
"ArrowFunctionExpressionClosingBrace": -1,
"ArrowFunctionExpressionOpeningBrace": ">=1",
"AssignmentExpression": ">=1",
"AssignmentOperator": 0,
"BlockStatement": 0,
"BreakKeyword": -1,
"CallExpression": -1,
"CallExpressionClosingParentheses": -1,
"CallExpressionOpeningParentheses": -1,
"CatchClosingBrace": ">=0",
"CatchKeyword": 0,
"CatchOpeningBrace": ">=1",
"ClassDeclaration": ">=1",
"ClassDeclarationClosingBrace": ">=1",
"ClassDeclarationOpeningBrace": ">=1",
"ConditionalExpression": ">=1",
"DeleteOperator": ">=1",
"DoWhileStatement": ">=1",
"DoWhileStatementClosingBrace": 0,
"DoWhileStatementOpeningBrace": ">=1",
"ElseIfStatement": ">=1",
"ElseIfStatementClosingBrace": ">=1",
"ElseIfStatementOpeningBrace": ">=1",
"ElseStatement": ">=1",
"ElseStatementClosingBrace": ">=1",
"ElseStatementOpeningBrace": ">=1",
"EmptyStatement": -1,
"FinallyClosingBrace": ">=1",
"FinallyKeyword": -1,
"FinallyOpeningBrace": ">=1",
"ForInStatement": ">=1",
"ForInStatementClosingBrace": ">=1",
"ForInStatementExpressionClosing": -1,
"ForInStatementExpressionOpening": "<2",
"ForInStatementOpeningBrace": ">=1",
"ForStatement": ">=1",
"ForStatementClosingBrace": ">=1",
"ForStatementExpressionClosing": -1,
"ForStatementExpressionOpening": "<2",
"ForStatementOpeningBrace": ">=1",
"FunctionDeclaration": ">=1",
"FunctionDeclarationClosingBrace": ">=1",
"FunctionDeclarationOpeningBrace": ">=1",
"FunctionExpression": 0,
"FunctionExpressionClosingBrace": -1,
"FunctionExpressionOpeningBrace": 1,
"IIFEOpeningParentheses": 0,
"IfStatement": ">=1",
"IfStatementClosingBrace": ">=1",
"IfStatementOpeningBrace": ">=1",
"LogicalExpression": -1,
"MemberExpressionClosing": 0,
"MemberExpressionOpening": 0,
"MemberExpressionPeriod": 0,
"MethodDefinition": ">=1",
"ObjectExpressionOpeningBrace": "<=1",
"ObjectPatternClosingBrace": 0,
"ObjectPatternComma": 0,
"ObjectPatternOpeningBrace": 0,
"ParameterDefault": 0,
"Property": -1,
"PropertyName": 0,
"ReturnStatement": -1,
"SwitchCaseColon": ">=1",
"SwitchClosingBrace": ">=1",
"SwitchOpeningBrace": ">=1",
"ThisExpression": 0,
"ThrowStatement": ">=1",
"TryClosingBrace": 0,
"TryKeyword": -1,
"TryOpeningBrace": ">=1",
"VariableDeclaration": ">=1",
"VariableDeclarationSemiColon": ">=1",
"VariableValue": -1,
"WhileStatement": ">=1",
"WhileStatementClosingBrace": ">=1",
"WhileStatementOpeningBrace": ">=1"
}
},
"whiteSpace": {
"value": " ",
"removeTrailing": 1,
"before": {
"ArgumentComma": 0,
"ArgumentList": 0,
"ArgumentListArrayExpression": 0,
"ArgumentListFunctionExpression": 1,
"ArgumentListObjectExpression": 0,
"ArrayExpressionClosing": 0,
"ArrayExpressionComma": 0,
"ArrayExpressionOpening": 1,
"AssignmentOperator": 1,
"BinaryExpression": 0,
"BinaryExpressionOperator": 1,
"BlockComment": 1,
"CallExpression": 1,
"CatchClosingBrace": 1,
"CatchKeyword": 1,
"CatchOpeningBrace": 1,
"CatchParameterList": 0,
"CommaOperator": 0,
"ConditionalExpressionAlternate": 1,
"ConditionalExpressionConsequent": 1,
"DoWhileStatementClosingBrace": 1,
"DoWhileStatementConditional": 1,
"DoWhileStatementOpeningBrace": 1,
"ElseIfStatementClosingBrace": 1,
"ElseIfStatementOpeningBrace": 1,
"ElseStatementClosingBrace": 1,
"ElseStatementOpeningBrace": 1,
"EmptyStatement": 0,
"ExpressionClosingParentheses": 0,
"FinallyClosingBrace": 1,
"FinallyKeyword": -1,
"FinallyOpeningBrace": 1,
"ForInStatement": 1,
"ForInStatementClosingBrace": 1,
"ForInStatementExpressionClosing": 0,
"ForInStatementExpressionOpening": 1,
"ForInStatementOpeningBrace": 1,
"ForStatement": 1,
"ForStatementClosingBrace": 1,
"ForStatementExpressionClosing": 0,
"ForStatementExpressionOpening": 1,
"ForStatementOpeningBrace": 1,
"ForStatementSemicolon": 0,
"FunctionDeclarationClosingBrace": 1,
"FunctionDeclarationOpeningBrace": 1,
"FunctionExpressionClosingBrace": 1,
"FunctionExpressionOpeningBrace": 1,
"IfStatementClosingBrace": 1,
"IfStatementConditionalClosing": 0,
"IfStatementConditionalOpening": 1,
"IfStatementOpeningBrace": 1,
"LineComment": 1,
"LogicalExpressionOperator": 1,
"MemberExpressionClosing": 0,
"ObjectExpressionClosingBrace": 1,
"ParameterComma": 0,
"ParameterList": 0,
"Property": 1,
"PropertyName": 1,
"PropertyValue": 1,
"SwitchDiscriminantClosing": 0,
"SwitchDiscriminantOpening": 1,
"ThrowKeyword": 1,
"TryClosingBrace": 1,
"TryKeyword": -1,
"TryOpeningBrace": 1,
"UnaryExpressionOperator": 0,
"VariableName": 1,
"VariableValue": 1,
"WhileStatementClosingBrace": 1,
"WhileStatementConditionalClosing": 0,
"WhileStatementConditionalOpening": 1,
"WhileStatementOpeningBrace": 1
},
"after": {
"ArgumentComma": 1,
"ArgumentList": 0,
"ArgumentListArrayExpression": 1,
"ArgumentListFunctionExpression": 1,
"ArgumentListObjectExpression": 0,
"ArrayExpressionClosing": 0,
"ArrayExpressionComma": 1,
"ArrayExpressionOpening": 0,
"AssignmentOperator": 1,
"BinaryExpression": 0,
"BinaryExpressionOperator": 1,
"BlockComment": 1,
"CallExpression": 0,
"CatchClosingBrace": 1,
"CatchKeyword": 1,
"CatchOpeningBrace": 1,
"CatchParameterList": 0,
"CommaOperator": 1,
"ConditionalExpressionConsequent": 1,
"ConditionalExpressionTest": 1,
"DoWhileStatementBody": 1,
"DoWhileStatementClosingBrace": 1,
"DoWhileStatementOpeningBrace": 1,
"ElseIfStatementClosingBrace": 1,
"ElseIfStatementOpeningBrace": 1,
"ElseStatementClosingBrace": 1,
"ElseStatementOpeningBrace": 1,
"EmptyStatement": 0,
"ExpressionOpeningParentheses": 0,
"FinallyClosingBrace": 1,
"FinallyKeyword": -1,
"FinallyOpeningBrace": 1,
"ForInStatement": 1,
"ForInStatementClosingBrace": 1,
"ForInStatementExpressionClosing": 1,
"ForInStatementExpressionOpening": 0,
"ForInStatementOpeningBrace": 1,
"ForStatement": 1,
"ForStatementClosingBrace": 1,
"ForStatementExpressionClosing": 1,
"ForStatementExpressionOpening": 0,
"ForStatementOpeningBrace": 1,
"ForStatementSemicolon": 1,
"FunctionDeclarationClosingBrace": 0,
"FunctionDeclarationOpeningBrace": 0,
"FunctionExpressionClosingBrace": 0,
"FunctionExpressionOpeningBrace": 0,
"FunctionName": 0,
"FunctionReservedWord": 0,
"IfStatementClosingBrace": 1,
"IfStatementConditionalClosing": 0,
"IfStatementConditionalOpening": 0,
"IfStatementOpeningBrace": 1,
"LogicalExpressionOperator": 1,
"MemberExpressionOpening": 0,
"ObjectExpressionClosingBrace": 0,
"ObjectExpressionOpeningBrace": 1,
"ParameterComma": 1,
"ParameterList": 0,
"PropertyName": 0,
"PropertyValue": 0,
"SwitchDiscriminantClosing": 1,
"SwitchDiscriminantOpening": 0,
"ThrowKeyword": 1,
"TryClosingBrace": 1,
"TryKeyword": -1,
"TryOpeningBrace": 1,
"UnaryExpressionOperator": 0,
"VariableName": 1,
"WhileStatementClosingBrace": 1,
"WhileStatementConditionalClosing": 1,
"WhileStatementConditionalOpening": 0,
"WhileStatementOpeningBrace": 1
}
}
}

View File

@@ -12,6 +12,8 @@ const dirs = fs
.join('|');
module.exports = {
root: true,
parser: '@babel/eslint-parser',
env: {

View File

@@ -1,12 +0,0 @@
{
"js": {
"indent_size": 2,
"indent_char": " ",
"indent_level": 2,
"indent_with_tabs": false,
"preserve_newlines": true,
"brace_style": "collapse",
"max_preserve_newlines": 2,
"jslint_happy": true
}
}

View File

@@ -1,12 +1,12 @@
{
"plugins": [
"stylelint-order"
],
"ignoreFiles": [
"frontend/src/Styles/scaffolding.css",
"**/*.js"
],
"rules": {
"plugins": [
"stylelint-order"
],
"ignoreFiles": [
"frontend/src/Styles/scaffolding.css",
"**/*.js"
],
"rules": {
"at-rule-empty-line-before": [
"always",
{
@@ -15,9 +15,6 @@
]
}
],
"at-rule-name-case": "lower",
"at-rule-name-newline-after": "always-multi-line",
"at-rule-name-space-after": "always",
"at-rule-no-unknown": [
true,
{
@@ -28,83 +25,36 @@
}
],
"at-rule-no-vendor-prefix": true,
"at-rule-semicolon-newline-after": "always",
"at-rule-semicolon-space-before": "never",
"block-closing-brace-empty-line-before": "never",
"block-closing-brace-newline-after": "always",
"block-closing-brace-newline-before": "always",
"block-closing-brace-space-after": "always-single-line",
"block-closing-brace-space-before": "always-single-line",
"block-no-empty": true,
"block-opening-brace-newline-after": "always",
"block-opening-brace-newline-before": "never-single-line",
"block-opening-brace-space-after": "always-single-line",
"block-opening-brace-space-before": "always",
"color-hex-case": "lower",
"color-hex-length": "short",
"color-named": "never",
"color-no-invalid-hex": true,
"comment-whitespace-inside": "always",
"declaration-bang-space-after": "never",
"declaration-bang-space-before": "always",
"declaration-block-no-duplicate-properties": [
true,
{
"ignoreProperties": [
"composes"
"composes"
]
}
],
"declaration-block-no-redundant-longhand-properties": true,
"declaration-block-no-shorthand-property-overrides": true,
"declaration-block-semicolon-newline-after": "always",
"declaration-block-semicolon-newline-before": "never-multi-line",
"declaration-block-semicolon-space-before": "never",
"declaration-block-single-line-max-declarations": 1,
"declaration-block-trailing-semicolon": "always",
"declaration-colon-space-after": "always",
"declaration-colon-space-before": "never",
"font-family-name-quotes": "always-unless-keyword",
"function-calc-no-unspaced-operator": true,
"function-comma-newline-after": "never-multi-line",
"function-comma-newline-before": "never-multi-line",
"function-comma-space-after": "always",
"function-comma-space-before": "never",
"function-linear-gradient-no-nonstandard-direction": true,
"function-name-case": "lower",
"function-parentheses-newline-inside": "never-multi-line",
"function-parentheses-space-inside": "never",
"function-url-quotes": "always",
"function-url-scheme-disallowed-list": [
"data"
],
"function-whitespace-after": "always",
"indentation": 2,
"keyframe-declaration-no-important": true,
"length-zero-no-unit": true,
"max-empty-lines": 1,
"max-line-length": [
100,
{
"ignore": [
"non-comments"
]
}
],
"max-nesting-depth": 2,
"media-feature-colon-space-after": "always",
"media-feature-colon-space-before": "never",
"media-feature-name-case": "lower",
"media-feature-name-no-vendor-prefix": true,
"media-feature-range-operator-space-after": "always",
"media-feature-range-operator-space-before": "always",
"no-empty-source": true,
"no-eol-whitespace": true,
"no-extra-semicolons": true,
"no-invalid-double-slash-comments": true,
"no-missing-end-of-source-newline": true,
"number-leading-zero": "always",
"number-no-trailing-zeros": true,
"order/order": [
"custom-properties",
"dollar-variables",
@@ -132,6 +82,7 @@
"right",
"bottom",
"left",
"inset",
"z-index",
"display",
"visibility",
@@ -343,54 +294,33 @@
]
}
],
"property-case": "lower",
"property-no-vendor-prefix": true,
"rule-empty-line-before": [
"always",
{
"except": [
"first-nested"
"first-nested"
],
"ignore": [
"after-comment"
"after-comment"
]
}
],
"selector-attribute-brackets-space-inside": "never",
"selector-attribute-operator-space-after": "never",
"selector-attribute-operator-space-before": "never",
"selector-attribute-quotes": "never",
"selector-class-pattern": "^[A-Za-z0-9]+$",
"selector-combinator-space-after": "always",
"selector-combinator-space-before": "always",
"selector-descendant-combinator-no-non-space": true,
"selector-list-comma-newline-after": "always",
"selector-list-comma-newline-before": "never-multi-line",
"selector-list-comma-space-before": "never",
"selector-max-attribute": 0,
"selector-max-class": 3,
"selector-max-compound-selectors": 3,
"selector-max-empty-lines": 0,
"selector-max-id": 0,
"selector-max-universal": 0,
"selector-pseudo-class-case": "lower",
"selector-pseudo-class-parentheses-space-inside": "never",
"selector-pseudo-element-case": "lower",
"selector-pseudo-element-colon-notation": "double",
"selector-pseudo-element-no-unknown": true,
"selector-type-case": "lower",
"selector-type-no-unknown": true,
"shorthand-property-no-redundant-values": true,
"string-no-newline": true,
"string-quotes": "single",
"time-min-milliseconds": 100,
"unit-case": "lower",
"unit-no-unknown": true,
"value-list-comma-newline-after": "never-multi-line",
"value-list-comma-newline-before": "never-multi-line",
"value-list-comma-space-after": "always",
"value-list-comma-space-before": "never",
"value-list-max-empty-lines": 0,
"value-no-vendor-prefix": true
}
}
}

7
frontend/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"recommendations": [
"stylelint.vscode-stylelint",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

View File

@@ -251,18 +251,19 @@ module.exports = (env) => {
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
}
})
];
config.optimization = {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
sourceMap: true, // Must be set to true if using source-maps in production
mangle: false,
keep_classnames: true,
keep_fnames: true
}
})
]
};
}
return config;

View File

@@ -1,58 +1,28 @@
import { cloneDeep } from 'lodash';
import React, { useEffect } from 'react';
import areAllSelected from 'Utilities/Table/areAllSelected';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import React, { useCallback, useEffect } from 'react';
import useSelectState, { SelectState } from 'Helpers/Hooks/useSelectState';
import ModelBase from './ModelBase';
export enum SelectActionType {
Reset,
SelectAll,
UnselectAll,
ToggleSelected,
RemoveItem,
UpdateItems,
}
type SelectedState = Record<number, boolean>;
interface SelectState {
selectedState: SelectedState;
lastToggled: number | null;
allSelected: boolean;
allUnselected: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
items: any[];
}
type SelectAction =
| { type: SelectActionType.Reset }
| { type: SelectActionType.SelectAll }
| { type: SelectActionType.UnselectAll }
export type SelectContextAction =
| { type: 'reset' }
| { type: 'selectAll' }
| { type: 'unselectAll' }
| {
type: SelectActionType.ToggleSelected;
type: 'toggleSelected';
id: number;
isSelected: boolean;
shiftKey: boolean;
}
| {
type: SelectActionType.RemoveItem;
type: 'removeItem';
id: number;
}
| {
type: SelectActionType.UpdateItems;
type: 'updateItems';
items: ModelBase[];
};
type Dispatch = (action: SelectAction) => void;
const initialState = {
selectedState: {},
lastToggled: null,
allSelected: false,
allUnselected: true,
items: [],
};
export type SelectDispatch = (action: SelectContextAction) => void;
interface SelectProviderOptions<T extends ModelBase> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -60,90 +30,40 @@ interface SelectProviderOptions<T extends ModelBase> {
items: Array<T>;
}
function getSelectedState(items: ModelBase[], existingState: SelectedState) {
return items.reduce((acc: SelectedState, item) => {
const id = item.id;
acc[id] = existingState[id] ?? false;
return acc;
}, {});
}
// TODO: Can this be reused?
const SelectContext = React.createContext<[SelectState, Dispatch] | undefined>(
cloneDeep(undefined)
);
function selectReducer(state: SelectState, action: SelectAction): SelectState {
const { items, selectedState } = state;
switch (action.type) {
case SelectActionType.Reset: {
return cloneDeep(initialState);
}
case SelectActionType.SelectAll: {
return {
items,
...selectAll(selectedState, true),
};
}
case SelectActionType.UnselectAll: {
return {
items,
...selectAll(selectedState, false),
};
}
case SelectActionType.ToggleSelected: {
const result = {
items,
...toggleSelected(
state,
items,
action.id,
action.isSelected,
action.shiftKey
),
};
return result;
}
case SelectActionType.UpdateItems: {
const nextSelectedState = getSelectedState(action.items, selectedState);
return {
...state,
...areAllSelected(nextSelectedState),
selectedState: nextSelectedState,
items: action.items,
};
}
default: {
throw new Error(`Unhandled action type: ${action.type}`);
}
}
}
const SelectContext = React.createContext<
[SelectState, SelectDispatch] | undefined
>(cloneDeep(undefined));
export function SelectProvider<T extends ModelBase>(
props: SelectProviderOptions<T>
) {
const { items } = props;
const selectedState = getSelectedState(items, {});
const [state, dispatch] = useSelectState();
const [state, dispatch] = React.useReducer(selectReducer, {
selectedState,
lastToggled: null,
allSelected: false,
allUnselected: true,
items,
});
const dispatchWrapper = useCallback(
(action: SelectContextAction) => {
switch (action.type) {
case 'reset':
case 'removeItem':
dispatch(action);
break;
const value: [SelectState, Dispatch] = [state, dispatch];
default:
dispatch({
...action,
items,
});
break;
}
},
[items, dispatch]
);
const value: [SelectState, SelectDispatch] = [state, dispatchWrapper];
useEffect(() => {
dispatch({ type: SelectActionType.UpdateItems, items });
}, [items]);
dispatch({ type: 'updateItems', items });
}, [items, dispatch]);
return (
<SelectContext.Provider value={value}>

View File

@@ -1,8 +1,5 @@
.inputContainer {
top: -1px;
right: -1px;
bottom: -1px;
left: -1px;
inset: -1px;
display: flex;
align-items: start;
flex-wrap: wrap;

View File

@@ -13,7 +13,7 @@ const messages = [
'Loading humorous message... Please Wait',
'I could\'ve been faster in Python',
'Don\'t forget to rewind your tracks',
'Congratulations! you are the 1000th visitor.',
'Congratulations! You are the 1000th visitor.',
'HELP! I\'m being held hostage and forced to write these stupid lines!',
'RE-calibrating the internet...',
'I\'ll be here all week',

View File

@@ -54,7 +54,7 @@ function Logger(minimumLogLevel) {
}
Logger.prototype.cleanse = function(message) {
const apikey = new RegExp(`access_token=${window.Prowlarr.apiKey}`, 'g');
const apikey = new RegExp(`access_token=${encodeURIComponent(window.Prowlarr.apiKey)}`, 'g');
return message.replace(apikey, 'access_token=(removed)');
};
@@ -98,7 +98,7 @@ class SignalRConnector extends Component {
this.connection = new signalR.HubConnectionBuilder()
.configureLogging(new Logger(signalR.LogLevel.Information))
.withUrl(`${url}?access_token=${window.Prowlarr.apiKey}`)
.withUrl(`${url}?access_token=${encodeURIComponent(window.Prowlarr.apiKey)}`)
.withAutomaticReconnect({
nextRetryDelayInMilliseconds: (retryContext) => {
if (retryContext.elapsedMilliseconds > 180000) {

View File

@@ -1,18 +1,19 @@
{
"name": "",
"name": "Prowlarr",
"icons": [
{
"src": "/Content/Images/Icons/android-chrome-192x192.png",
"src": "android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/Content/Images/Icons/android-chrome-512x512.png",
"src": "android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "../../../../",
"theme_color": "#3a3f51",
"background_color": "#3a3f51",
"display": "standalone"
}
}

View File

@@ -0,0 +1,113 @@
import { cloneDeep } from 'lodash';
import { useReducer } from 'react';
import ModelBase from 'App/ModelBase';
import areAllSelected from 'Utilities/Table/areAllSelected';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
export type SelectedState = Record<number, boolean>;
export interface SelectState {
selectedState: SelectedState;
lastToggled: number | null;
allSelected: boolean;
allUnselected: boolean;
}
export type SelectAction =
| { type: 'reset' }
| { type: 'selectAll'; items: ModelBase[] }
| { type: 'unselectAll'; items: ModelBase[] }
| {
type: 'toggleSelected';
id: number;
isSelected: boolean;
shiftKey: boolean;
items: ModelBase[];
}
| {
type: 'removeItem';
id: number;
}
| {
type: 'updateItems';
items: ModelBase[];
};
export type Dispatch = (action: SelectAction) => void;
const initialState = {
selectedState: {},
lastToggled: null,
allSelected: false,
allUnselected: true,
items: [],
};
function getSelectedState(items: ModelBase[], existingState: SelectedState) {
return items.reduce((acc: SelectedState, item) => {
const id = item.id;
acc[id] = existingState[id] ?? false;
return acc;
}, {});
}
function selectReducer(state: SelectState, action: SelectAction): SelectState {
const { selectedState } = state;
switch (action.type) {
case 'reset': {
return cloneDeep(initialState);
}
case 'selectAll': {
return {
...selectAll(selectedState, true),
};
}
case 'unselectAll': {
return {
...selectAll(selectedState, false),
};
}
case 'toggleSelected': {
const result = {
...toggleSelected(
state,
action.items,
action.id,
action.isSelected,
action.shiftKey
),
};
return result;
}
case 'updateItems': {
const nextSelectedState = getSelectedState(action.items, selectedState);
return {
...state,
...areAllSelected(nextSelectedState),
selectedState: nextSelectedState,
};
}
default: {
throw new Error(`Unhandled action type: ${action.type}`);
}
}
}
export default function useSelectState(): [SelectState, Dispatch] {
const selectedState = getSelectedState([], {});
const [state, dispatch] = useReducer(selectReducer, {
selectedState,
lastToggled: null,
allSelected: false,
allUnselected: true,
});
return [state, dispatch];
}

View File

@@ -1,14 +1,18 @@
import * as filterTypes from './filterTypes';
export const ARRAY = 'array';
export const CONTAINS = 'contains';
export const DATE = 'date';
export const EQUAL = 'equal';
export const EXACT = 'exact';
export const NUMBER = 'number';
export const STRING = 'string';
export const all = [
ARRAY,
CONTAINS,
DATE,
EQUAL,
EXACT,
NUMBER,
STRING
@@ -20,6 +24,10 @@ export const possibleFilterTypes = {
{ key: filterTypes.NOT_CONTAINS, value: 'does not contain' }
],
[CONTAINS]: [
{ key: filterTypes.CONTAINS, value: 'contains' }
],
[DATE]: [
{ key: filterTypes.LESS_THAN, value: 'is before' },
{ key: filterTypes.GREATER_THAN, value: 'is after' },
@@ -29,6 +37,10 @@ export const possibleFilterTypes = {
{ key: filterTypes.NOT_IN_NEXT, value: 'not in the next' }
],
[EQUAL]: [
{ key: filterTypes.EQUAL, value: 'is' }
],
[EXACT]: [
{ key: filterTypes.EQUAL, value: 'is' },
{ key: filterTypes.NOT_EQUAL, value: 'is not' }
@@ -47,6 +59,10 @@ export const possibleFilterTypes = {
{ key: filterTypes.CONTAINS, value: 'contains' },
{ key: filterTypes.NOT_CONTAINS, value: 'does not contain' },
{ key: filterTypes.EQUAL, value: 'equal' },
{ key: filterTypes.NOT_EQUAL, value: 'not equal' }
{ key: filterTypes.NOT_EQUAL, value: 'not equal' },
{ key: filterTypes.STARTS_WITH, value: 'starts with' },
{ key: filterTypes.NOT_STARTS_WITH, value: 'does not start with' },
{ key: filterTypes.ENDS_WITH, value: 'ends with' },
{ key: filterTypes.NOT_ENDS_WITH, value: 'does not end with' }
]
};

View File

@@ -39,6 +39,22 @@ const filterTypePredicates = {
[filterTypes.NOT_EQUAL]: function(itemValue, filterValue) {
return itemValue !== filterValue;
},
[filterTypes.STARTS_WITH]: function(itemValue, filterValue) {
return itemValue.toLowerCase().startsWith(filterValue.toLowerCase());
},
[filterTypes.NOT_STARTS_WITH]: function(itemValue, filterValue) {
return !itemValue.toLowerCase().startsWith(filterValue.toLowerCase());
},
[filterTypes.ENDS_WITH]: function(itemValue, filterValue) {
return itemValue.toLowerCase().endsWith(filterValue.toLowerCase());
},
[filterTypes.NOT_ENDS_WITH]: function(itemValue, filterValue) {
return !itemValue.toLowerCase().endsWith(filterValue.toLowerCase());
}
};

View File

@@ -10,6 +10,10 @@ export const LESS_THAN = 'lessThan';
export const LESS_THAN_OR_EQUAL = 'lessThanOrEqual';
export const NOT_CONTAINS = 'notContains';
export const NOT_EQUAL = 'notEqual';
export const STARTS_WITH = 'startsWith';
export const NOT_STARTS_WITH = 'notStartsWith';
export const ENDS_WITH = 'endsWith';
export const NOT_ENDS_WITH = 'notEndsWith';
export const all = [
CONTAINS,
@@ -23,5 +27,9 @@ export const all = [
IN_LAST,
NOT_IN_LAST,
IN_NEXT,
NOT_IN_NEXT
NOT_IN_NEXT,
STARTS_WITH,
NOT_STARTS_WITH,
ENDS_WITH,
NOT_ENDS_WITH
];

View File

@@ -226,7 +226,7 @@ class AddIndexerModalContent extends Component {
{
filteredIndexers.map((indexer) => (
<SelectIndexerRowConnector
key={indexer.name}
key={`${indexer.implementation}-${indexer.name}`}
implementation={indexer.implementation}
{...indexer}
onIndexerSelect={onIndexerSelect}

View File

@@ -1,5 +1,5 @@
import React, { useCallback } from 'react';
import { SelectActionType, useSelect } from 'App/SelectContext';
import { useSelect } from 'App/SelectContext';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
@@ -25,9 +25,7 @@ function IndexerIndexSelectAllButton(props: IndexerIndexSelectAllButtonProps) {
const onPress = useCallback(() => {
selectDispatch({
type: allSelected
? SelectActionType.UnselectAll
: SelectActionType.SelectAll,
type: allSelected ? 'unselectAll' : 'selectAll',
});
}, [allSelected, selectDispatch]);

View File

@@ -1,5 +1,5 @@
import React, { useCallback } from 'react';
import { SelectActionType, useSelect } from 'App/SelectContext';
import { useSelect } from 'App/SelectContext';
import PageToolbarOverflowMenuItem from 'Components/Page/Toolbar/PageToolbarOverflowMenuItem';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
@@ -26,9 +26,7 @@ function IndexerIndexSelectAllMenuItem(
const onPressWrapper = useCallback(() => {
selectDispatch({
type: allSelected
? SelectActionType.UnselectAll
: SelectActionType.SelectAll,
type: allSelected ? 'unselectAll' : 'selectAll',
});
}, [allSelected, selectDispatch]);

View File

@@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import { SelectActionType, useSelect } from 'App/SelectContext';
import { useSelect } from 'App/SelectContext';
import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter';
import { kinds } from 'Helpers/Props';
@@ -111,7 +111,7 @@ function IndexerIndexSelectFooter() {
useEffect(() => {
if (!isDeleting && !deleteError) {
selectDispatch({ type: SelectActionType.UnselectAll });
selectDispatch({ type: 'unselectAll' });
}
}, [isDeleting, deleteError, selectDispatch]);

View File

@@ -1,6 +1,6 @@
import { IconDefinition } from '@fortawesome/fontawesome-common-types';
import React, { useCallback } from 'react';
import { SelectActionType, useSelect } from 'App/SelectContext';
import { useSelect } from 'App/SelectContext';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
interface IndexerIndexSelectModeButtonProps {
@@ -20,7 +20,7 @@ function IndexerIndexSelectModeButton(
const onPressWrapper = useCallback(() => {
if (isSelectMode) {
selectDispatch({
type: SelectActionType.Reset,
type: 'reset',
});
}

View File

@@ -1,6 +1,6 @@
import { IconDefinition } from '@fortawesome/fontawesome-common-types';
import React, { useCallback } from 'react';
import { SelectActionType, useSelect } from 'App/SelectContext';
import { useSelect } from 'App/SelectContext';
import PageToolbarOverflowMenuItem from 'Components/Page/Toolbar/PageToolbarOverflowMenuItem';
interface IndexerIndexSelectModeMenuItemProps {
@@ -19,7 +19,7 @@ function IndexerIndexSelectModeMenuItem(
const onPressWrapper = useCallback(() => {
if (isSelectMode) {
selectDispatch({
type: SelectActionType.Reset,
type: 'reset',
});
}

View File

@@ -1,6 +1,6 @@
import React, { useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
import { SelectActionType, useSelect } from 'App/SelectContext';
import { useSelect } from 'App/SelectContext';
import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
@@ -55,7 +55,11 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
const vipExpiration =
fields.find((field) => field.name === 'vipExpiration')?.value ?? '';
const rssUrl = `${window.location.origin}${window.Prowlarr.urlBase}/${id}/api?t=search&extended=1&apikey=${window.Prowlarr.apiKey}`;
const rssUrl = `${window.location.origin}${
window.Prowlarr.urlBase
}/${id}/api?apikey=${encodeURIComponent(
window.Prowlarr.apiKey
)}&extended=1&t=search`;
const [isEditIndexerModalOpen, setIsEditIndexerModalOpen] = useState(false);
const [isDeleteIndexerModalOpen, setIsDeleteIndexerModalOpen] =
@@ -86,7 +90,7 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
const onSelectedChange = useCallback(
({ id, value, shiftKey }) => {
selectDispatch({
type: SelectActionType.ToggleSelected,
type: 'toggleSelected',
id,
isSelected: value,
shiftKey,

View File

@@ -1,7 +1,7 @@
import classNames from 'classnames';
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { SelectActionType, useSelect } from 'App/SelectContext';
import { useSelect } from 'App/SelectContext';
import IconButton from 'Components/Link/IconButton';
import Column from 'Components/Table/Column';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
@@ -47,7 +47,7 @@ function IndexerIndexTableHeader(props: IndexerIndexTableHeaderProps) {
const onSelectAllChange = useCallback(
({ value }) => {
selectDispatch({
type: value ? SelectActionType.SelectAll : SelectActionType.UnselectAll,
type: value ? 'selectAll' : 'unselectAll',
});
},
[selectDispatch]

View File

@@ -3,6 +3,7 @@ import React, { Component } from 'react';
import Card from 'Components/Card';
import Label from 'Components/Label';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TagList from 'Components/TagList';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditApplicationModalConnector from './EditApplicationModalConnector';
@@ -55,7 +56,9 @@ class Application extends Component {
const {
id,
name,
syncLevel
syncLevel,
tags,
tagList
} = this.props;
return (
@@ -92,6 +95,11 @@ class Application extends Component {
</Label>
}
<TagList
tags={tags}
tagList={tagList}
/>
<EditApplicationModalConnector
id={id}
isOpen={this.state.isEditApplicationModalOpen}
@@ -117,6 +125,8 @@ Application.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
syncLevel: PropTypes.string.isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteApplication: PropTypes.func
};

View File

@@ -49,6 +49,7 @@ class Applications extends Component {
render() {
const {
items,
tagList,
onConfirmDeleteApplication,
...otherProps
} = this.props;
@@ -71,6 +72,7 @@ class Applications extends Component {
<Application
key={item.id}
{...item}
tagList={tagList}
onConfirmDeleteApplication={onConfirmDeleteApplication}
/>
);
@@ -109,6 +111,7 @@ Applications.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteApplication: PropTypes.func.isRequired
};

View File

@@ -4,13 +4,20 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { deleteApplication, fetchApplications } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import sortByName from 'Utilities/Array/sortByName';
import Applications from './Applications';
function createMapStateToProps() {
return createSelector(
createSortedSectionSelector('settings.applications', sortByName),
(applications) => applications
createTagsSelector(),
(applications, tagList) => {
return {
...applications,
tagList
};
}
);
}

View File

@@ -21,6 +21,7 @@ function HostSettings(props) {
port,
urlBase,
instanceName,
applicationUrl,
enableSsl,
sslPort,
sslCertPath,
@@ -89,6 +90,21 @@ function HostSettings(props) {
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('ApplicationURL')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="applicationUrl"
helpText={translate('ApplicationUrlHelpText')}
onChange={onInputChange}
{...applicationUrl}
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}

View File

@@ -3,6 +3,7 @@ import React, { Component } from 'react';
import Card from 'Components/Card';
import Label from 'Components/Label';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TagList from 'Components/TagList';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditNotificationModalConnector from './EditNotificationModalConnector';
@@ -57,10 +58,14 @@ class Notification extends Component {
name,
onGrab,
onHealthIssue,
onHealthRestored,
onApplicationUpdate,
supportsOnGrab,
supportsOnHealthIssue,
supportsOnApplicationUpdate
supportsOnHealthRestored,
supportsOnApplicationUpdate,
tags,
tagList
} = this.props;
return (
@@ -74,17 +79,27 @@ class Notification extends Component {
</div>
{
supportsOnGrab && onGrab &&
supportsOnGrab && onGrab ?
<Label kind={kinds.SUCCESS}>
{translate('OnGrab')}
</Label>
</Label> :
null
}
{
supportsOnHealthIssue && onHealthIssue &&
supportsOnHealthIssue && onHealthIssue ?
<Label kind={kinds.SUCCESS}>
{translate('OnHealthIssue')}
</Label>
</Label> :
null
}
{
supportsOnHealthRestored && onHealthRestored ?
<Label kind={kinds.SUCCESS}>
{translate('OnHealthRestored')}
</Label> :
null
}
{
@@ -96,7 +111,7 @@ class Notification extends Component {
}
{
!onGrab && !onHealthIssue && !onApplicationUpdate ?
!onGrab && !onHealthIssue && !onHealthRestored && !onApplicationUpdate ?
<Label
kind={kinds.DISABLED}
outline={true}
@@ -106,6 +121,11 @@ class Notification extends Component {
null
}
<TagList
tags={tags}
tagList={tagList}
/>
<EditNotificationModalConnector
id={id}
isOpen={this.state.isEditNotificationModalOpen}
@@ -132,10 +152,14 @@ Notification.propTypes = {
name: PropTypes.string.isRequired,
onGrab: PropTypes.bool.isRequired,
onHealthIssue: PropTypes.bool.isRequired,
onHealthRestored: PropTypes.bool.isRequired,
onApplicationUpdate: PropTypes.bool.isRequired,
supportsOnGrab: PropTypes.bool.isRequired,
supportsOnHealthIssue: PropTypes.bool.isRequired,
supportsOnHealthRestored: PropTypes.bool.isRequired,
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteNotification: PropTypes.func.isRequired
};

View File

@@ -17,10 +17,12 @@ function NotificationEventItems(props) {
const {
onGrab,
onHealthIssue,
onHealthRestored,
onApplicationUpdate,
supportsOnGrab,
includeManualGrabs,
supportsOnHealthIssue,
supportsOnHealthRestored,
includeHealthWarnings,
supportsOnApplicationUpdate
} = item;
@@ -70,8 +72,19 @@ function NotificationEventItems(props) {
/>
</div>
<div>
<FormInputGroup
type={inputTypes.CHECK}
name="onHealthRestored"
helpText={translate('OnHealthRestoredHelpText')}
isDisabled={!supportsOnHealthRestored.value}
{...onHealthRestored}
onChange={onInputChange}
/>
</div>
{
onHealthIssue.value &&
(onHealthIssue.value || onHealthRestored.value) &&
<div>
<FormInputGroup
type={inputTypes.CHECK}

View File

@@ -49,6 +49,7 @@ class Notifications extends Component {
render() {
const {
items,
tagList,
onConfirmDeleteNotification,
...otherProps
} = this.props;
@@ -71,6 +72,7 @@ class Notifications extends Component {
<Notification
key={item.id}
{...item}
tagList={tagList}
onConfirmDeleteNotification={onConfirmDeleteNotification}
/>
);
@@ -109,6 +111,7 @@ Notifications.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteNotification: PropTypes.func.isRequired
};

View File

@@ -4,13 +4,20 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { deleteNotification, fetchNotifications } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import sortByName from 'Utilities/Array/sortByName';
import Notifications from './Notifications';
function createMapStateToProps() {
return createSelector(
createSortedSectionSelector('settings.notifications', sortByName),
(notifications) => notifications
createTagsSelector(),
(notifications, tagList) => {
return {
...notifications,
tagList
};
}
);
}

View File

@@ -32,9 +32,9 @@ function createSaveProviderHandler(section, url, options = {}) {
const params = { ...queryParams };
// If the user is re-saving the same provider without changes
// force it to be saved. Only applies to editing existing providers.
// force it to be saved.
if (id && _.isEqual(saveData, lastSaveData)) {
if (_.isEqual(saveData, lastSaveData)) {
params.forceSave = true;
}

View File

@@ -5,7 +5,7 @@
"scripts": {
"build": "webpack --config ./frontend/build/webpack.config.js",
"prebuild": "yarn clean",
"clean": "rimraf ./_output/UI && rimraf -g \"**/*.js.map\"",
"clean": "rimraf ./_output/UI && rimraf --glob \"**/*.js.map\"",
"start": "webpack --watch --config ./frontend/build/webpack.config.js",
"watch": "webpack --watch --config ./frontend/build/webpack.config.js",
"lint": "eslint --config frontend/.eslintrc.js --ignore-path frontend/.eslintignore frontend/",
@@ -30,13 +30,12 @@
"@fortawesome/react-fontawesome": "0.2.0",
"@juggle/resize-observer": "3.4.0",
"@microsoft/signalr": "6.0.16",
"@sentry/browser": "7.46.0",
"@sentry/integrations": "7.46.0",
"@types/jest": "29.5.0",
"@sentry/browser": "7.51.2",
"@sentry/integrations": "7.51.2",
"@types/node": "18.15.11",
"@types/react": "18.0.31",
"@types/react-dom": "18.0.11",
"chart.js": "4.2.1",
"@types/react": "18.2.6",
"@types/react-dom": "18.2.4",
"chart.js": "4.3.0",
"classnames": "2.3.2",
"clipboard": "2.0.11",
"connected-react-router": "6.9.3",
@@ -45,7 +44,7 @@
"history": "4.10.1",
"https-browserify": "1.0.0",
"jdu": "1.0.0",
"jquery": "3.6.4",
"jquery": "3.7.0",
"lodash": "4.17.21",
"mobile-detect": "1.4.5",
"moment": "2.29.4",
@@ -83,36 +82,33 @@
"redux-thunk": "2.4.2",
"reselect": "4.1.7",
"stacktrace-js": "2.0.2",
"typescript": "5.0.3"
"typescript": "5.0.4"
},
"devDependencies": {
"@babel/core": "7.21.3",
"@babel/eslint-parser": "7.21.3",
"@babel/core": "7.21.8",
"@babel/eslint-parser": "7.21.8",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-decorators": "7.21.0",
"@babel/plugin-proposal-export-default-from": "7.18.10",
"@babel/plugin-proposal-export-namespace-from": "7.18.9",
"@babel/plugin-proposal-function-sent": "7.18.6",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
"@babel/plugin-proposal-numeric-separator": "7.18.6",
"@babel/plugin-proposal-optional-chaining": "7.21.0",
"@babel/plugin-proposal-throw-expressions": "7.18.6",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.20.2",
"@babel/preset-env": "7.21.5",
"@babel/preset-react": "7.18.6",
"@babel/preset-typescript": "7.21.0",
"@babel/preset-typescript": "7.21.5",
"@types/react-window": "1.8.5",
"@typescript-eslint/eslint-plugin": "5.57.0",
"@typescript-eslint/parser": "5.57.0",
"@types/webpack-livereload-plugin": "2.3.3",
"@typescript-eslint/eslint-plugin": "5.59.5",
"@typescript-eslint/parser": "5.59.5",
"are-you-es5": "2.1.2",
"autoprefixer": "10.4.14",
"babel-loader": "9.1.2",
"babel-plugin-inline-classnames": "2.0.1",
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
"core-js": "3.30.1",
"core-js": "3.30.2",
"css-loader": "6.7.3",
"css-modules-typescript-loader": "4.0.1",
"eslint": "8.37.0",
"eslint": "8.40.0",
"eslint-config-prettier": "8.8.0",
"eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.27.5",
@@ -123,29 +119,30 @@
"file-loader": "6.2.0",
"filemanager-webpack-plugin": "8.0.0",
"fork-ts-checker-webpack-plugin": "8.0.0",
"html-webpack-plugin": "5.5.0",
"html-webpack-plugin": "5.5.1",
"loader-utils": "^3.2.1",
"mini-css-extract-plugin": "2.7.5",
"postcss": "8.4.21",
"postcss": "8.4.23",
"postcss-color-function": "4.1.0",
"postcss-loader": "7.1.0",
"postcss-loader": "7.3.0",
"postcss-mixins": "9.0.4",
"postcss-nested": "6.0.1",
"postcss-simple-vars": "7.0.1",
"postcss-url": "10.1.3",
"prettier": "2.8.7",
"prettier": "2.8.8",
"require-nocache": "1.0.0",
"rimraf": "4.4.1",
"run-sequence": "2.2.1",
"streamqueue": "1.1.2",
"style-loader": "3.3.2",
"stylelint": "14.16.0",
"stylelint-order": "5.0.0",
"stylelint": "15.6.1",
"stylelint-order": "6.0.3",
"terser-webpack-plugin": "5.3.8",
"ts-loader": "9.4.2",
"typescript-plugin-css-modules": "5.0.0",
"typescript-plugin-css-modules": "5.0.1",
"url-loader": "4.1.1",
"webpack": "5.82.0",
"webpack-cli": "5.0.2",
"webpack": "5.82.1",
"webpack-cli": "5.1.1",
"webpack-livereload-plugin": "3.0.2"
}
}

View File

@@ -3,7 +3,9 @@
<PropertyGroup>
<AnalysisLevel>6.0-all</AnalysisLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
<PlatformTarget>AnyCPU</PlatformTarget>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<RuntimeIdentifiers>win-x64;win-x86;osx-x64;osx-arm64;linux-x64;linux-musl-x64;linux-musl-arm;linux-arm;linux-arm64;linux-musl-arm64</RuntimeIdentifiers>
@@ -24,10 +26,17 @@
<ProwlarrProject Condition="$(MSBuildProjectName.StartsWith('Prowlarr'))">true</ProwlarrProject>
<ProwlarrProject Condition="$(MSBuildProjectName.StartsWith('ServiceInstall'))">true</ProwlarrProject>
<ProwlarrProject Condition="$(MSBuildProjectName.StartsWith('ServiceUninstall'))">true</ProwlarrProject>
<!-- A test project gets the test sdk packages automatically added -->
<TestProject>false</TestProject>
<TestProject Condition="$(MSBuildProjectName.EndsWith('.Test'))">true</TestProject>
<!-- XML documentation comments are needed to enforce rule IDE0005 on build -->
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!--
CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member'
-->
<NoWarn>$(NoWarn);CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup>
@@ -50,7 +59,7 @@
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<!-- Test projects need bindingRedirects -->
<PropertyGroup Condition="'$(ProwlarrOutputType)'=='Test'">
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
@@ -63,7 +72,7 @@
<Product>Prowlarr</Product>
<Company>prowlarr.com</Company>
<Copyright>Copyright 2014-$([System.DateTime]::Now.ToString('yyyy')) prowlarr.com (GNU General Public v3)</Copyright>
<!-- Should be replaced by CI -->
<AssemblyVersion>10.0.0.*</AssemblyVersion>
<AssemblyConfiguration>$(Configuration)-dev</AssemblyConfiguration>
@@ -72,7 +81,7 @@
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<Deterministic Condition="$(AssemblyVersion.EndsWith('*'))">False</Deterministic>
<PathMap>$(MSBuildProjectDirectory)=./$(MSBuildProjectName)/</PathMap>
@@ -94,7 +103,7 @@
<!-- FXCop Built into Net5 SDK now as NETAnalyzers, Enabled by default on net5 projects -->
<EnableNETAnalyzers>false</EnableNETAnalyzers>
</PropertyGroup>
<!-- Standard testing packages -->
<ItemGroup Condition="'$(TestProject)'=='true'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />

View File

@@ -11,7 +11,6 @@ using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Test.Common;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Remote;
namespace NzbDrone.Automation.Test
{
@@ -68,7 +67,7 @@ namespace NzbDrone.Automation.Test
{
try
{
Screenshot image = ((ITakesScreenshot)driver).GetScreenshot();
var image = ((ITakesScreenshot)driver).GetScreenshot();
image.SaveAsFile($"./{name}_test_screenshot.png", ScreenshotImageFormat.Png);
}
catch (Exception ex)

View File

@@ -36,7 +36,7 @@ namespace NzbDrone.Automation.Test.PageModel
{
try
{
IWebElement element = d.FindElement(By.ClassName("followingBalls"));
var element = d.FindElement(By.ClassName("followingBalls"));
return !element.Displayed;
}
catch (NoSuchElementException)

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Threading;
using FluentAssertions;
using NUnit.Framework;
@@ -65,9 +65,9 @@ namespace NzbDrone.Common.Test.CacheTests
[Test]
public void should_store_null()
{
int hitCount = 0;
var hitCount = 0;
for (int i = 0; i < 10; i++)
for (var i = 0; i < 10; i++)
{
_cachedString.Get("key", () =>
{
@@ -83,10 +83,10 @@ namespace NzbDrone.Common.Test.CacheTests
[Platform(Exclude = "MacOsX")]
public void should_honor_ttl()
{
int hitCount = 0;
var hitCount = 0;
_cachedString = new Cached<string>();
for (int i = 0; i < 10; i++)
for (var i = 0; i < 10; i++)
{
_cachedString.Get("key",
() =>

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using FluentAssertions;
using Moq;
using NUnit.Framework;
@@ -142,7 +142,7 @@ namespace NzbDrone.Common.Test
[Test]
public void SaveDictionary_should_save_proper_value()
{
int port = 20555;
var port = 20555;
var dic = Subject.GetConfigDictionary();
dic["Port"] = 20555;
@@ -155,9 +155,9 @@ namespace NzbDrone.Common.Test
[Test]
public void SaveDictionary_should_only_save_specified_values()
{
int port = 20555;
int origSslPort = 20551;
int sslPort = 20552;
var port = 20555;
var origSslPort = 20551;
var sslPort = 20552;
var dic = Subject.GetConfigDictionary();
dic["Port"] = port;

View File

@@ -42,7 +42,7 @@ namespace NzbDrone.Common.Test.DiskTests
[Test]
public void should_not_contain_recycling_bin_for_root_of_drive()
{
string root = @"C:\".AsOsAgnostic();
var root = @"C:\".AsOsAgnostic();
SetupFolders(root);
Mocker.GetMock<IDiskProvider>()
@@ -55,7 +55,7 @@ namespace NzbDrone.Common.Test.DiskTests
[Test]
public void should_not_contain_system_volume_information()
{
string root = @"C:\".AsOsAgnostic();
var root = @"C:\".AsOsAgnostic();
SetupFolders(root);
Mocker.GetMock<IDiskProvider>()
@@ -68,7 +68,7 @@ namespace NzbDrone.Common.Test.DiskTests
[Test]
public void should_not_contain_recycling_bin_or_system_volume_information_for_root_of_drive()
{
string root = @"C:\".AsOsAgnostic();
var root = @"C:\".AsOsAgnostic();
SetupFolders(root);
Mocker.GetMock<IDiskProvider>()

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -352,6 +351,26 @@ namespace NzbDrone.Common.Test.DiskTests
.Verify(v => v.DeleteFile(_targetPath), Times.Once());
}
[Test]
public void should_not_rollback_move_on_partial_if_destination_already_exists()
{
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.MoveFile(_sourcePath, _targetPath, false))
.Callback(() =>
{
WithExistingFile(_targetPath, true, 900);
});
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.MoveFile(_sourcePath, _targetPath, false))
.Throws(new FileAlreadyExistsException("File already exists", _targetPath));
Assert.Throws<FileAlreadyExistsException>(() => Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move));
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.DeleteFile(_targetPath), Times.Never());
}
[Test]
public void should_log_error_if_rollback_partialmove_fails()
{

View File

@@ -1,4 +1,3 @@
using System.Globalization;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Extensions;

View File

@@ -788,7 +788,7 @@ namespace NzbDrone.Common.Test.Http
try
{
// the date is bad in the below - should be 13-Jul-2026
string malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Mon, 13-Jul-26 16:19:50 GMT; path=/; HttpOnly";
var malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Mon, 13-Jul-26 16:19:50 GMT; path=/; HttpOnly";
var requestSet = new HttpRequestBuilder($"https://{_httpBinHost}/response-headers")
.AddQueryParam("Set-Cookie", malformedCookie)
.Build();
@@ -822,7 +822,7 @@ namespace NzbDrone.Common.Test.Http
{
try
{
string url = $"https://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeDataString(malformedCookie)}";
var url = $"https://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeDataString(malformedCookie)}";
var requestSet = new HttpRequest(url);
requestSet.AllowAutoRedirect = false;

View File

@@ -74,17 +74,17 @@ namespace NzbDrone.Common
continue; // Ignore directories
}
string entryFileName = zipEntry.Name;
var entryFileName = zipEntry.Name;
// to remove the folder from the entry:- entryFileName = Path.GetFileName(entryFileName);
// Optionally match entrynames against a selection list here to skip as desired.
// The unpacked length is available in the zipEntry.Size property.
byte[] buffer = new byte[4096]; // 4K is optimum
Stream zipStream = zipFile.GetInputStream(zipEntry);
var buffer = new byte[4096]; // 4K is optimum
var zipStream = zipFile.GetInputStream(zipEntry);
// Manipulate the output filename here as desired.
string fullZipToPath = Path.Combine(destination, entryFileName);
string directoryName = Path.GetDirectoryName(fullZipToPath);
var fullZipToPath = Path.Combine(destination, entryFileName);
var directoryName = Path.GetDirectoryName(fullZipToPath);
if (directoryName.Length > 0)
{
Directory.CreateDirectory(directoryName);
@@ -93,7 +93,7 @@ namespace NzbDrone.Common
// Unzip file in buffered chunks. This is just as fast as unpacking to a buffer the full size
// of the file, but does not waste memory.
// The "using" will close the stream even if an exception occurs.
using (FileStream streamWriter = File.Create(fullZipToPath))
using (var streamWriter = File.Create(fullZipToPath))
{
StreamUtils.Copy(zipStream, streamWriter, buffer);
}
@@ -106,7 +106,7 @@ namespace NzbDrone.Common
Stream inStream = File.OpenRead(compressedFile);
Stream gzipStream = new GZipInputStream(inStream);
TarArchive tarArchive = TarArchive.CreateInputTarArchive(gzipStream, null);
var tarArchive = TarArchive.CreateInputTarArchive(gzipStream, null);
tarArchive.ExtractContents(destination);
tarArchive.Close();

View File

@@ -3,7 +3,6 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Cache
{
@@ -48,8 +47,7 @@ namespace NzbDrone.Common.Cache
public T Find(string key)
{
CacheItem cacheItem;
if (!_store.TryGetValue(key, out cacheItem))
if (!_store.TryGetValue(key, out var cacheItem))
{
return default(T);
}
@@ -77,8 +75,7 @@ namespace NzbDrone.Common.Cache
public void Remove(string key)
{
CacheItem value;
_store.TryRemove(key, out value);
_store.TryRemove(key, out _);
}
public int Count => _store.Count;
@@ -89,9 +86,7 @@ namespace NzbDrone.Common.Cache
lifeTime = lifeTime ?? _defaultLifeTime;
CacheItem cacheItem;
if (_store.TryGetValue(key, out cacheItem) && !cacheItem.IsExpired())
if (_store.TryGetValue(key, out var cacheItem) && !cacheItem.IsExpired())
{
if (_rollingExpiry && lifeTime.HasValue)
{

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
@@ -86,9 +86,7 @@ namespace NzbDrone.Common.Cache
{
RefreshIfExpired();
TValue result;
if (!_items.TryGetValue(key, out result))
if (!_items.TryGetValue(key, out var result))
{
throw new KeyNotFoundException(string.Format("Item {0} not found in cache.", key));
}
@@ -100,9 +98,7 @@ namespace NzbDrone.Common.Cache
{
RefreshIfExpired();
TValue result;
_items.TryGetValue(key, out result);
_items.TryGetValue(key, out var result);
return result;
}
@@ -128,8 +124,7 @@ namespace NzbDrone.Common.Cache
public void Remove(string key)
{
TValue item;
_items.TryRemove(key, out item);
_items.TryRemove(key, out _);
}
}
}

View File

@@ -264,6 +264,11 @@ namespace NzbDrone.Common.Disk
protected virtual void MoveFileInternal(string source, string destination)
{
if (File.Exists(destination))
{
throw new FileAlreadyExistsException("File already exists", destination);
}
File.Move(source, destination);
}

View File

@@ -500,9 +500,13 @@ namespace NzbDrone.Common.Disk
throw new IOException(string.Format("File move incomplete, data loss may have occurred. [{0}] was {1} bytes long instead of the expected {2}.", targetPath, targetSize, originalSize));
}
}
catch
catch (Exception ex)
{
RollbackPartialMove(sourcePath, targetPath);
if (ex is not FileAlreadyExistsException)
{
RollbackPartialMove(sourcePath, targetPath);
}
throw;
}
}

View File

@@ -0,0 +1,15 @@
using System;
namespace NzbDrone.Common.Disk
{
public class FileAlreadyExistsException : Exception
{
public string Filename { get; set; }
public FileAlreadyExistsException(string message, string filename)
: base(message)
{
Filename = filename;
}
}
}

View File

@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;
namespace NzbDrone.Common.Disk
{

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Disk
@@ -256,7 +255,7 @@ namespace NzbDrone.Common.Disk
var stringComparison = (Kind == OsPathKind.Windows || other.Kind == OsPathKind.Windows) ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture;
for (int i = 0; i < leftFragments.Length; i++)
for (var i = 0; i < leftFragments.Length; i++)
{
if (!string.Equals(leftFragments[i], rightFragments[i], stringComparison))
{
@@ -373,12 +372,12 @@ namespace NzbDrone.Common.Disk
var newFragments = new List<string>();
for (int j = i; j < rightFragments.Length; j++)
for (var j = i; j < rightFragments.Length; j++)
{
newFragments.Add("..");
}
for (int j = i; j < leftFragments.Length; j++)
for (var j = i; j < leftFragments.Length; j++)
{
newFragments.Add(leftFragments[j]);
}

View File

@@ -1,8 +1,6 @@
using System;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Exceptions;

View File

@@ -36,14 +36,14 @@ namespace NzbDrone.Common.Extensions
public static bool IsValidDate(this string dateTime)
{
DateTime.TryParse(dateTime, out DateTime result);
DateTime.TryParse(dateTime, out var result);
return !result.Equals(default(DateTime));
}
public static bool IsFutureDate(this string dateTime)
{
DateTime.TryParse(dateTime, out DateTime result);
DateTime.TryParse(dateTime, out var result);
return !result.Equals(default(DateTime)) && result.After(DateTime.Now);
}

View File

@@ -126,9 +126,9 @@ namespace NzbDrone.Common.Extensions
private static IEnumerable<T> InternalDropLast<T>(IEnumerable<T> source, int n)
{
Queue<T> buffer = new Queue<T>(n + 1);
var buffer = new Queue<T>(n + 1);
foreach (T x in source)
foreach (var x in source)
{
buffer.Enqueue(x);

View File

@@ -21,7 +21,7 @@ namespace NzbDrone.Common.Extensions
return text.Length * costDelete;
}
int[] matrix = new int[other.Length + 1];
var matrix = new int[other.Length + 1];
for (var i = 1; i < matrix.Length; i++)
{
@@ -30,13 +30,13 @@ namespace NzbDrone.Common.Extensions
for (var i = 0; i < text.Length; i++)
{
int topLeft = matrix[0];
var topLeft = matrix[0];
matrix[0] = matrix[0] + costDelete;
for (var j = 0; j < other.Length; j++)
{
int top = matrix[j];
int left = matrix[j + 1];
var top = matrix[j];
var left = matrix[j + 1];
var sumIns = top + costInsert;
var sumDel = left + costDelete;

View File

@@ -198,13 +198,13 @@ namespace NzbDrone.Common.Extensions
public static string CleanFileName(this string name)
{
string result = name;
var result = name;
string[] badCharacters = { "\\", "/", "<", ">", "?", "*", ":", "|", "\"" };
string[] goodCharacters = { "+", "+", "", "", "!", "-", "-", "", "" };
result = result.Replace(": ", " - ");
for (int i = 0; i < badCharacters.Length; i++)
for (var i = 0; i < badCharacters.Length; i++)
{
result = result.Replace(badCharacters[i], goodCharacters[i]);
}

View File

@@ -6,9 +6,7 @@ namespace NzbDrone.Common.Extensions
{
public static int? ParseInt32(this string source)
{
int result;
if (int.TryParse(source, out result))
if (int.TryParse(source, out var result))
{
return result;
}
@@ -18,9 +16,7 @@ namespace NzbDrone.Common.Extensions
public static long? ParseInt64(this string source)
{
long result;
if (long.TryParse(source, out result))
if (long.TryParse(source, out var result))
{
return result;
}
@@ -30,9 +26,7 @@ namespace NzbDrone.Common.Extensions
public static double? ParseDouble(this string source)
{
double result;
if (double.TryParse(source.Replace(',', '.'), NumberStyles.Number, CultureInfo.InvariantCulture, out result))
if (double.TryParse(source.Replace(',', '.'), NumberStyles.Number, CultureInfo.InvariantCulture, out var result))
{
return result;
}

View File

@@ -8,9 +8,9 @@ namespace NzbDrone.Common
{
public static string CalculateCrc(string input)
{
uint mCrc = 0xffffffff;
byte[] bytes = Encoding.UTF8.GetBytes(input);
foreach (byte myByte in bytes)
var mCrc = 0xffffffff;
var bytes = Encoding.UTF8.GetBytes(input);
foreach (var myByte in bytes)
{
mCrc ^= (uint)myByte << 24;
for (var i = 0; i < 8; i++)

View File

@@ -1,4 +1,3 @@
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

View File

@@ -1,7 +1,6 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Security;
@@ -9,7 +8,6 @@ using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http.Proxy;
@@ -129,7 +127,7 @@ namespace NzbDrone.Common.Http.Dispatchers
headers.Add(responseMessage.Content.Headers.ToNameValueCollection());
CookieContainer responseCookies = new CookieContainer();
var responseCookies = new CookieContainer();
if (responseMessage.Headers.TryGetValues("Set-Cookie", out var cookieHeaders))
{
@@ -247,7 +245,7 @@ namespace NzbDrone.Common.Http.Dispatchers
}
}
private void AddContentHeader(HttpRequestMessage request, string header, string value)
private static void AddContentHeader(HttpRequestMessage request, string header, string value)
{
var headers = request.Content?.Headers;
if (headers == null)

View File

@@ -322,11 +322,12 @@ namespace NzbDrone.Common.Http
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName);
var stopWatch = Stopwatch.StartNew();
using (var fileStream = new FileStream(fileNamePart, FileMode.Create, FileAccess.ReadWrite))
await using (var fileStream = new FileStream(fileNamePart, FileMode.Create, FileAccess.ReadWrite))
{
var request = new HttpRequest(url);
request.AllowAutoRedirect = true;
request.ResponseStream = fileStream;
request.RequestTimeout = TimeSpan.FromSeconds(300);
var response = await GetAsync(request);
if (response.Headers.ContentType != null && response.Headers.ContentType.Contains("text/html"))

View File

@@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using NzbDrone.Common.Extensions;

View File

@@ -22,7 +22,7 @@ namespace NzbDrone.Common.Http
public HttpUri(string scheme, string host, int? port, string path, string query, string fragment)
{
StringBuilder builder = new StringBuilder();
var builder = new StringBuilder();
if (scheme.IsNotNullOrWhiteSpace())
{

View File

@@ -31,7 +31,7 @@ namespace NzbDrone.Common.Http.Proxy
if (!string.IsNullOrWhiteSpace(BypassFilter))
{
var hostlist = BypassFilter.Split(',');
for (int i = 0; i < hostlist.Length; i++)
for (var i = 0; i < hostlist.Length; i++)
{
if (hostlist[i].StartsWith("*"))
{

View File

@@ -1,4 +1,4 @@
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Linq;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Common.Instrumentation
@@ -16,7 +16,7 @@ namespace NzbDrone.Common.Instrumentation
}
}
foreach (JToken token in json)
foreach (var token in json)
{
Visit(token);
}

View File

@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NLog.Fluent;
namespace NzbDrone.Common.Instrumentation.Extensions
{

View File

@@ -1,7 +1,6 @@
using System;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Common.Instrumentation
{

View File

@@ -1,4 +1,3 @@
using System;
using System.Text;
using NLog;
using NLog.Targets;

View File

@@ -94,7 +94,7 @@ namespace NzbDrone.Common.Instrumentation
private static void RegisterDebugger()
{
DebuggerTarget target = new DebuggerTarget();
var target = new DebuggerTarget();
target.Name = "debuggerLogger";
target.Layout = "[${level}] [${threadid}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
@@ -108,9 +108,10 @@ namespace NzbDrone.Common.Instrumentation
{
LogManager.Setup().LoadConfiguration(c =>
{
c.ForLogger("System.*").WriteToNil(LogLevel.Warn);
c.ForLogger("Microsoft.*").WriteToNil(LogLevel.Warn);
c.ForLogger("Microsoft.Hosting.Lifetime*").WriteToNil(LogLevel.Info);
c.ForLogger("System*").WriteToNil(LogLevel.Warn);
c.ForLogger("Microsoft*").WriteToNil(LogLevel.Warn);
c.ForLogger("Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware").WriteToNil(LogLevel.Fatal);
});
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Linq;
using Sentry;
using Sentry.Protocol;
namespace NzbDrone.Common.Instrumentation.Sentry
{

View File

@@ -12,7 +12,6 @@ using Npgsql;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using Sentry;
using Sentry.Protocol;
namespace NzbDrone.Common.Instrumentation.Sentry
{

View File

@@ -29,7 +29,7 @@ namespace NzbDrone.Common.OAuth
public virtual string Version { get; set; }
public virtual string SessionHandle { get; set; }
/// <seealso cref="http://oauth.net/core/1.0#request_urls"/>
/// <seealso href="http://oauth.net/core/1.0#request_urls"/>
public virtual string RequestUrl { get; set; }
#if !WINRT

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;

View File

@@ -131,14 +131,7 @@ namespace NzbDrone.Common.Processes
var key = environmentVariable.Key.ToString();
var value = environmentVariable.Value?.ToString();
if (startInfo.EnvironmentVariables.ContainsKey(key))
{
startInfo.EnvironmentVariables[key] = value;
}
else
{
startInfo.EnvironmentVariables.Add(key, value);
}
startInfo.EnvironmentVariables[key] = value;
}
catch (Exception e)
{
@@ -320,7 +313,7 @@ namespace NzbDrone.Common.Processes
processInfo = new ProcessInfo();
processInfo.Id = process.Id;
processInfo.Name = process.ProcessName;
processInfo.StartPath = GetExeFileName(process);
processInfo.StartPath = process.MainModule.FileName;
if (process.Id != GetCurrentProcessId() && process.HasExited)
{
@@ -335,28 +328,9 @@ namespace NzbDrone.Common.Processes
return processInfo;
}
private static string GetExeFileName(Process process)
{
if (process.MainModule.FileName != "mono.exe")
{
return process.MainModule.FileName;
}
return process.Modules.Cast<ProcessModule>().FirstOrDefault(module => module.ModuleName.ToLower().EndsWith(".exe")).FileName;
}
private List<Process> GetProcessesByName(string name)
{
//TODO: move this to an OS specific class
var monoProcesses = Process.GetProcessesByName("mono")
.Union(Process.GetProcessesByName("mono-sgen"))
.Where(process =>
process.Modules.Cast<ProcessModule>()
.Any(module =>
module.ModuleName.ToLower() == name.ToLower() + ".exe"));
var processes = Process.GetProcessesByName(name)
.Union(monoProcesses).ToList();
var processes = Process.GetProcessesByName(name).ToList();
_logger.Debug("Found {0} processes with the name: {1}", processes.Count, name);

View File

@@ -1,4 +1,4 @@
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Linq;
namespace NzbDrone.Common.Serializer
{
@@ -60,7 +60,7 @@ namespace NzbDrone.Common.Serializer
public virtual void Visit(JArray json)
{
foreach (JToken token in json)
foreach (var token in json)
{
Visit(token);
}
@@ -72,7 +72,7 @@ namespace NzbDrone.Common.Serializer
public virtual void Visit(JObject json)
{
foreach (JProperty property in json.Properties())
foreach (var property in json.Properties())
{
Visit(property);
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Text;
using Newtonsoft.Json;
@@ -42,7 +42,7 @@ namespace NzbDrone.Common.Serializer
var enumText = value.ToString();
var builder = new StringBuilder(enumText.Length + 4);
builder.Append(char.ToLower(enumText[0]));
for (int i = 1; i < enumText.Length; i++)
for (var i = 1; i < enumText.Length; i++)
{
if (char.IsUpper(enumText[i]))
{

View File

@@ -18,7 +18,7 @@ namespace NzbDrone.Common.Serializer
{
try
{
Version v = new Version(reader.GetString());
var v = new Version(reader.GetString());
return v;
}
catch (Exception)

View File

@@ -1,5 +1,4 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using NLog;

View File

@@ -137,7 +137,7 @@ namespace NzbDrone.Common.TPL
/// <returns>An enumerable of the tasks currently scheduled.</returns>
protected sealed override IEnumerable<Task> GetScheduledTasks()
{
bool lockTaken = false;
var lockTaken = false;
try
{
Monitor.TryEnter(_tasks, ref lockTaken);

View File

@@ -110,7 +110,7 @@ namespace NzbDrone.Console
}
System.Console.WriteLine("Non-recoverable failure, waiting for user intervention...");
for (int i = 0; i < 3600; i++)
for (var i = 0; i < 3600; i++)
{
System.Threading.Thread.Sleep(1000);
if (!System.Console.IsInputRedirected && System.Console.KeyAvailable)

View File

@@ -197,7 +197,7 @@ namespace NzbDrone.Core.Test.Datastore
Subject.SetFields(_basicList, x => x.Interval);
for (int i = 0; i < _basicList.Count; i++)
for (var i = 0; i < _basicList.Count; i++)
{
_basicList[i].LastExecution = executionBackup[i];
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Text;
using FluentAssertions;
@@ -57,7 +57,7 @@ namespace NzbDrone.Core.Test
[Test]
public void ToBestDateTime_DayOfWeek()
{
for (int i = 2; i < 7; i++)
for (var i = 2; i < 7; i++)
{
var dateTime = DateTime.Today.AddDays(i);

View File

@@ -1,5 +1,4 @@
using System;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Cloud;
@@ -10,7 +9,6 @@ using NzbDrone.Common.Http.Proxy;
using NzbDrone.Common.TPL;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Http;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Security;
using NzbDrone.Test.Common;

View File

@@ -0,0 +1,91 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerSearchTests
{
public class ReleaseSearchServiceFixture : CoreTest<ReleaseSearchService>
{
private Mock<IIndexer> _mockIndexer;
[SetUp]
public void SetUp()
{
_mockIndexer = Mocker.GetMock<IIndexer>();
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition { Id = 1 });
_mockIndexer.SetupGet(s => s.SupportsSearch).Returns(true);
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.Enabled(It.IsAny<bool>()))
.Returns(new List<IIndexer> { _mockIndexer.Object });
}
private List<SearchCriteriaBase> WatchForSearchCriteria()
{
var result = new List<SearchCriteriaBase>();
_mockIndexer.Setup(v => v.Fetch(It.IsAny<MovieSearchCriteria>()))
.Callback<MovieSearchCriteria>(s => result.Add(s))
.Returns(Task.FromResult(new IndexerPageableQueryResult()));
_mockIndexer.Setup(v => v.Fetch(It.IsAny<TvSearchCriteria>()))
.Callback<TvSearchCriteria>(s => result.Add(s))
.Returns(Task.FromResult(new IndexerPageableQueryResult()));
return result;
}
[TestCase("tt0183790", "0183790")]
[TestCase("0183790", "0183790")]
[TestCase("183790", "0183790")]
[TestCase("tt10001870", "10001870")]
[TestCase("10001870", "10001870")]
public void should_normalize_imdbid_movie_search_criteria(string input, string expected)
{
var allCriteria = WatchForSearchCriteria();
var request = new NewznabRequest
{
t = "movie",
imdbid = input
};
Subject.Search(request, new List<int> { 1 }, false);
var criteria = allCriteria.OfType<MovieSearchCriteria>().ToList();
criteria.Count.Should().Be(1);
criteria[0].ImdbId.Should().Be(expected);
}
[TestCase("tt0183790", "0183790")]
[TestCase("0183790", "0183790")]
[TestCase("183790", "0183790")]
[TestCase("tt10001870", "10001870")]
[TestCase("10001870", "10001870")]
public void should_normalize_imdbid_tv_search_criteria(string input, string expected)
{
var allCriteria = WatchForSearchCriteria();
var request = new NewznabRequest
{
t = "tvsearch",
imdbid = input
};
Subject.Search(request, new List<int> { 1 }, false);
var criteria = allCriteria.OfType<TvSearchCriteria>().ToList();
criteria.Count.Should().Be(1);
criteria[0].ImdbId.Should().Be(expected);
}
}
}

View File

@@ -1,6 +1,3 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;

View File

@@ -1,9 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DryIoc;
using FluentAssertions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -12,7 +8,6 @@ using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.BroadcastheNet;
using NzbDrone.Core.Indexers.Definitions.HDBits;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;

View File

@@ -12,7 +12,6 @@ using NzbDrone.Core.Indexers.Definitions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
{

View File

@@ -6,6 +6,7 @@ using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.IndexerTests
{
@@ -31,13 +32,15 @@ namespace NzbDrone.Core.Test.IndexerTests
Mocker.SetConstant<IIndexerRepository>(repo);
var existingIndexers = Builder<IndexerDefinition>.CreateNew().BuildNew();
existingIndexers.ConfigContract = typeof(NewznabSettings).Name;
existingIndexers.ConfigContract = nameof(NewznabSettings);
repo.Insert(existingIndexers);
Subject.Handle(new ApplicationStartedEvent());
AllStoredModels.Should().NotContain(c => c.Id == existingIndexers.Id);
ExceptionVerification.ExpectedWarns(1);
}
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Xml;
using FluentAssertions;
using Moq;

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Test.IndexerTests.PTPTests
{
var authResponse = new PassThePopcornAuthResponse { Result = "Ok" };
System.IO.StringWriter authStream = new System.IO.StringWriter();
var authStream = new System.IO.StringWriter();
Json.Serialize(authResponse, authStream);
var responseJson = ReadAllText(fileName);

View File

@@ -17,6 +17,7 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
{
[Obsolete("Rarbg has shutdown 2023-05-31")]
[TestFixture]
public class RarbgFixture : CoreTest<Rarbg>
{

View File

@@ -46,7 +46,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RedactedTests
torrentInfo.Title.Should().Be("Red Hot Chili Peppers - Californication [1999] [Album] [US / Reissue 2020] [FLAC 24bit Lossless] [Vinyl]");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("https://redacted.ch/ajax.php?action=download&id=3892313&usetoken=0");
torrentInfo.DownloadUrl.Should().Be("https://redacted.ch/ajax.php?action=download&id=3892313");
torrentInfo.InfoUrl.Should().Be("https://redacted.ch/torrents.php?id=16720&torrentid=3892313");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);

View File

@@ -21,7 +21,7 @@ namespace NzbDrone.Core.Test.IndexerTests.SecretCinemaTests
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
Subject.Definition = new IndexerDefinition
{
Name = "SecretCinema",
Settings = new GazelleSettings { Username = "somekey", Password = "somekey" }
@@ -46,7 +46,7 @@ namespace NzbDrone.Core.Test.IndexerTests.SecretCinemaTests
torrentInfo.Title.Should().Be("Singin' in the Rain (1952) 2160p");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("https://secret-cinema.pw/torrents.php?action=download&useToken=0&id=45068");
torrentInfo.DownloadUrl.Should().Be("https://secret-cinema.pw/torrents.php?action=download&id=45068");
torrentInfo.InfoUrl.Should().Be("https://secret-cinema.pw/torrents.php?id=2497&torrentid=45068");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);

View File

@@ -1,5 +1,4 @@
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download;
using NzbDrone.Core.Indexers;

View File

@@ -47,7 +47,7 @@ namespace NzbDrone.Core.Test.InstrumentationTests
public void write_long_log()
{
var message = string.Empty;
for (int i = 0; i < 100; i++)
for (var i = 0; i < 100; i++)
{
message += Guid.NewGuid();
}

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