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
+12 -3
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
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
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
+1 -1
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)'
-25
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
}
-335
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
}
}
}
+2
View File
@@ -12,6 +12,8 @@ const dirs = fs
.join('|');
module.exports = {
root: true,
parser: '@babel/eslint-parser',
env: {
-12
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
}
}
+1 -71
View File
@@ -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,25 +25,11 @@
}
],
"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,
{
@@ -57,54 +40,21 @@
],
"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,7 +294,6 @@
]
}
],
"property-case": "lower",
"property-no-vendor-prefix": true,
"rule-empty-line-before": [
"always",
@@ -356,41 +306,21 @@
]
}
],
"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
View File
@@ -0,0 +1,7 @@
{
"recommendations": [
"stylelint.vscode-stylelint",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
+6 -5
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 = [
config.optimization = {
minimize: true,
minimizer: [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true, // Must be set to true if using source-maps in production
terserOptions: {
sourceMap: true, // Must be set to true if using source-maps in production
mangle: false,
keep_classnames: true,
keep_fnames: true
}
})
];
]
};
}
return config;
+33 -113
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,
const dispatchWrapper = useCallback(
(action: SelectContextAction) => {
switch (action.type) {
case 'reset':
case 'removeItem':
dispatch(action);
break;
default:
dispatch({
...action,
items,
});
break;
}
},
[items, dispatch]
);
const value: [SelectState, Dispatch] = [state, 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}>
@@ -1,8 +1,5 @@
.inputContainer {
top: -1px;
right: -1px;
bottom: -1px;
left: -1px;
inset: -1px;
display: flex;
align-items: start;
flex-wrap: wrap;
@@ -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',
+2 -2
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) {
@@ -1,17 +1,18 @@
{
"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"
@@ -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];
}
@@ -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' }
]
};
@@ -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());
}
};
+9 -1
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
];
@@ -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}
@@ -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]);
@@ -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]);
@@ -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]);
@@ -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',
});
}
@@ -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',
});
}
@@ -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,
@@ -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]
@@ -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
};
@@ -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
};
@@ -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
};
}
);
}
@@ -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}
@@ -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
};
@@ -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}
@@ -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
};
@@ -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
};
}
);
}
@@ -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;
}
+27 -30
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"
}
}
+9
View File
@@ -3,7 +3,9 @@
<PropertyGroup>
<AnalysisLevel>6.0-all</AnalysisLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<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>
@@ -28,6 +30,13 @@
<!-- 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>
@@ -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)
@@ -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)
@@ -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",
() =>
@@ -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;
@@ -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>()
@@ -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()
{
@@ -1,4 +1,3 @@
using System.Globalization;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
@@ -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;
+7 -7
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();
+3 -8
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)
{
@@ -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 _);
}
}
}
@@ -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);
}
@@ -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)
{
if (ex is not FileAlreadyExistsException)
{
RollbackPartialMove(sourcePath, targetPath);
}
throw;
}
}
@@ -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;
}
}
}
@@ -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
{
+3 -4
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]);
}
@@ -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;
@@ -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);
}
@@ -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);
@@ -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;
@@ -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]);
}
@@ -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;
}
+3 -3
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++)
@@ -1,4 +1,3 @@
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
@@ -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)
+2 -1
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"))
-1
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;
+1 -1
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())
{
@@ -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("*"))
{
@@ -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);
}
@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NLog.Fluent;
namespace NzbDrone.Common.Instrumentation.Extensions
{
@@ -1,7 +1,6 @@
using System;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Common.Instrumentation
{
@@ -1,4 +1,3 @@
using System;
using System.Text;
using NLog;
using NLog.Targets;
@@ -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);
});
}
@@ -1,7 +1,6 @@
using System;
using System.Linq;
using Sentry;
using Sentry.Protocol;
namespace NzbDrone.Common.Instrumentation.Sentry
{
@@ -12,7 +12,6 @@ using Npgsql;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using Sentry;
using Sentry.Protocol;
namespace NzbDrone.Common.Instrumentation.Sentry
{
+1 -1
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
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@@ -131,15 +131,8 @@ 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);
}
}
catch (Exception e)
{
if (environmentVariable.Value == null)
@@ -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);
@@ -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);
}
@@ -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]))
{
@@ -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)
-1
View File
@@ -1,5 +1,4 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using NLog;
@@ -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);
+1 -1
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)
@@ -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];
}
+2 -2
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);
@@ -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;
@@ -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);
}
}
}
@@ -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;
@@ -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;
@@ -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
{
@@ -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);
}
}
}
@@ -1,7 +1,6 @@
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Xml;
using FluentAssertions;
using Moq;
@@ -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);
@@ -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>
{
@@ -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);
@@ -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);
@@ -1,5 +1,4 @@
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download;
using NzbDrone.Core.Indexers;
@@ -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