From cd6002b5d6156ad5f576dca9f35f328d71191c70 Mon Sep 17 00:00:00 2001 From: Roger Far Date: Sat, 30 Apr 2022 11:22:15 -0600 Subject: [PATCH] Upgrade packages Moved C# code to file scoped namespaces Merged Startup.cs and Program.cs --- client/package-lock.json | 1650 +++++++++-------- client/package.json | 34 +- server/RdtClient.Data/Data/DataContext.cs | 394 ++-- server/RdtClient.Data/Data/DownloadData.cs | 435 +++-- server/RdtClient.Data/Data/SettingData.cs | 220 ++- server/RdtClient.Data/Data/TorrentData.cs | 604 +++--- server/RdtClient.Data/Data/UserData.cs | 30 +- server/RdtClient.Data/DiConfig.cs | 26 +- .../Enums/TorrentDownloadAction.cs | 15 +- .../Enums/TorrentFinishedAction.cs | 15 +- server/RdtClient.Data/Enums/TorrentStatus.cs | 23 +- server/RdtClient.Data/Models/Data/Download.cs | 60 +- server/RdtClient.Data/Models/Data/Setting.cs | 20 +- server/RdtClient.Data/Models/Data/Torrent.cs | 137 +- .../Models/Internal/AppSettings.cs | 49 +- .../Models/Internal/DbSettings.cs | 61 +- .../RdtClient.Data/Models/Internal/Profile.cs | 19 +- .../Models/QBittorrent/AppBuildInfo.cs | 34 +- .../Models/QBittorrent/AppPreferences.cs | 579 +++--- .../Models/QBittorrent/SyncMetaData.cs | 190 +- .../Models/QBittorrent/TorrentCategory.cs | 20 +- .../Models/QBittorrent/TorrentFileItem.cs | 14 +- .../Models/QBittorrent/TorrentInfo.cs | 189 +- .../Models/QBittorrent/TorrentProperties.cs | 142 +- .../TorrentClientAvailableFile.cs | 13 +- .../Models/TorrentClient/TorrentClientFile.cs | 17 +- .../TorrentClient/TorrentClientTorrent.cs | 44 +- .../Models/TorrentClient/TorrentClientUser.cs | 13 +- server/RdtClient.Data/RdtClient.Data.csproj | 15 +- server/RdtClient.Service/DiConfig.cs | 31 +- .../Helpers/DownloadHelper.cs | 56 +- .../RdtClient.Service/Helpers/FileHelper.cs | 57 +- .../Helpers/JsonModelBinder.cs | 51 +- server/RdtClient.Service/Helpers/Logger.cs | 43 +- .../Helpers/SettingsHelper.cs | 50 +- .../Middleware/AuthorizeMiddleware.cs | 43 +- .../ExceptionMiddlewareExtensions.cs | 40 +- .../RdtClient.Service.csproj | 13 +- .../Services/Authentication.cs | 145 +- .../Services/DownloadClient.cs | 205 +- .../Services/Downloaders/Aria2cDownloader.cs | 469 +++-- .../Services/Downloaders/IDownloader.cs | 44 +- .../Services/Downloaders/MultiDownloader.cs | 248 ++- .../Services/Downloaders/SimpleDownloader.cs | 276 ++- .../RdtClient.Service/Services/Downloads.cs | 182 +- .../RdtClient.Service/Services/QBittorrent.cs | 1109 ++++++----- server/RdtClient.Service/Services/RdtHub.cs | 40 +- .../Services/RemoteService.cs | 60 +- server/RdtClient.Service/Services/Settings.cs | 260 ++- server/RdtClient.Service/Services/Startup.cs | 73 +- .../RdtClient.Service/Services/TaskRunner.cs | 62 +- .../Services/ThrottledStream.cs | 370 ++-- .../TorrentClients/AllDebridTorrentClient.cs | 639 ++++--- .../Services/TorrentClients/ITorrentClient.cs | 36 +- .../TorrentClients/RealDebridTorrentClient.cs | 594 +++--- .../Services/TorrentRunner.cs | 1008 +++++----- server/RdtClient.Service/Services/Torrents.cs | 1413 +++++++------- .../Services/UnpackClient.cs | 238 ++- .../Services/UpdateChecker.cs | 135 +- .../Controllers/AuthController.cs | 194 +- .../Controllers/QBittorrentController.cs | 881 ++++----- .../Controllers/SettingsController.cs | 165 +- .../Controllers/TorrentsController.cs | 333 ++-- server/RdtClient.Web/Program.cs | 286 +-- server/RdtClient.Web/RdtClient.Web.csproj | 15 +- server/RdtClient.Web/Startup.cs | 137 -- .../appsettings.Development.json | 4 +- server/RdtClient.Web/appsettings.json | 4 +- server/RdtClient.sln.DotSettings | 4 +- 69 files changed, 7484 insertions(+), 7591 deletions(-) delete mode 100644 server/RdtClient.Web/Startup.cs diff --git a/client/package-lock.json b/client/package-lock.json index 0211f88..222fdb8 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,33 +8,33 @@ "name": "client", "version": "0.0.0", "dependencies": { - "@angular/animations": "~13.2.6", - "@angular/cdk": "^13.2.6", - "@angular/common": "~13.2.6", - "@angular/compiler": "~13.2.6", - "@angular/core": "~13.2.6", + "@angular/animations": "~13.3.5", + "@angular/cdk": "^13.3.5", + "@angular/common": "~13.3.5", + "@angular/compiler": "~13.3.5", + "@angular/core": "~13.3.5", "@angular/flex-layout": "^13.0.0-beta.38", - "@angular/forms": "~13.2.6", - "@angular/platform-browser": "~13.2.6", - "@angular/platform-browser-dynamic": "~13.2.6", - "@angular/router": "~13.2.6", - "@fortawesome/fontawesome-free": "^6.0.0", - "@microsoft/signalr": "^6.0.3", + "@angular/forms": "~13.3.5", + "@angular/platform-browser": "~13.3.5", + "@angular/platform-browser-dynamic": "~13.3.5", + "@angular/router": "~13.3.5", + "@fortawesome/fontawesome-free": "^6.1.1", + "@microsoft/signalr": "^6.0.4", "bulma": "^0.9.3", "curray": "^1.0.8", "file-saver": "^2.0.5", "ngx-filesize": "^2.0.16", "rxjs": "~7.5.5", - "tslib": "^2.3.1", + "tslib": "^2.4.0", "zone.js": "~0.11.5" }, "devDependencies": { - "@angular-devkit/build-angular": "~13.2.6", - "@angular/cli": "~13.2.6", - "@angular/compiler-cli": "~13.2.6", - "@angular/language-service": "~13.2.6", + "@angular-devkit/build-angular": "~13.3.4", + "@angular/cli": "~13.3.4", + "@angular/compiler-cli": "~13.3.5", + "@angular/language-service": "~13.3.5", "@types/file-saver": "^2.0.5", - "@types/node": "^17.0.21", + "@types/node": "^17.0.30", "typescript": "~4.5.5" } }, @@ -52,12 +52,12 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1302.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1302.6.tgz", - "integrity": "sha512-NztzorUMfwJeRaT7SY00Y8WSqc2lQYuF11yNoyEm7Dae3V7VZ28rW2Z9RwibP27rYQL0RjSMaz2wKITHX2vOAw==", + "version": "0.1303.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.4.tgz", + "integrity": "sha512-d6YmIWdYvwk6WaknHRcJgiXeJvX9K5i8uPMAaL2P2/LU8n3moIQ59C7SP0uULcHuuiREEmFWOyyrWnGxZCI9bg==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.2.6", + "@angular-devkit/core": "13.3.4", "rxjs": "6.6.7" }, "engines": { @@ -85,15 +85,15 @@ "dev": true }, "node_modules/@angular-devkit/build-angular": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.2.6.tgz", - "integrity": "sha512-Y2ojy6xbZ0kwScppcutLHBP8eW0qNOjburTISSBU/L5l/9FOeZ1E7yAreKuVu/qibZiLbSJfAhk+SLwhRHFSSQ==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.4.tgz", + "integrity": "sha512-z74cmDi2V+5XpvyZKFlUXxvQ446shxyZk5aGdToG6n+0/IJWkDXSiryQkCo8nblGMze7HKf75i3DsGWYQZLDnQ==", "dev": true, "dependencies": { "@ampproject/remapping": "1.1.1", - "@angular-devkit/architect": "0.1302.6", - "@angular-devkit/build-webpack": "0.1302.6", - "@angular-devkit/core": "13.2.6", + "@angular-devkit/architect": "0.1303.4", + "@angular-devkit/build-webpack": "0.1303.4", + "@angular-devkit/core": "13.3.4", "@babel/core": "7.16.12", "@babel/generator": "7.16.8", "@babel/helper-annotate-as-pure": "7.16.7", @@ -104,7 +104,7 @@ "@babel/runtime": "7.16.7", "@babel/template": "7.16.7", "@discoveryjs/json-ext": "0.5.6", - "@ngtools/webpack": "13.2.6", + "@ngtools/webpack": "13.3.4", "ansi-colors": "4.1.1", "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", @@ -126,7 +126,7 @@ "license-webpack-plugin": "4.0.2", "loader-utils": "3.2.0", "mini-css-extract-plugin": "2.5.3", - "minimatch": "3.0.4", + "minimatch": "3.0.5", "open": "8.4.0", "ora": "5.4.1", "parse5-html-rewriting-stream": "6.0.1", @@ -138,7 +138,7 @@ "regenerator-runtime": "0.13.9", "resolve-url-loader": "5.0.0", "rxjs": "6.6.7", - "sass": "1.49.0", + "sass": "1.49.9", "sass-loader": "12.4.0", "semver": "7.3.5", "source-map-loader": "3.0.1", @@ -149,7 +149,7 @@ "text-table": "0.2.0", "tree-kill": "1.2.2", "tslib": "2.3.1", - "webpack": "5.67.0", + "webpack": "5.70.0", "webpack-dev-middleware": "5.3.0", "webpack-dev-server": "4.7.3", "webpack-merge": "5.8.0", @@ -164,14 +164,14 @@ "esbuild": "0.14.22" }, "peerDependencies": { - "@angular/compiler-cli": "^13.0.0", - "@angular/localize": "^13.0.0", - "@angular/service-worker": "^13.0.0", + "@angular/compiler-cli": "^13.0.0 || ^13.3.0-rc.0", + "@angular/localize": "^13.0.0 || ^13.3.0-rc.0", + "@angular/service-worker": "^13.0.0 || ^13.3.0-rc.0", "karma": "^6.3.0", "ng-packagr": "^13.0.0", "protractor": "^7.0.0", "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=4.4.3 <4.6" + "typescript": ">=4.4.3 <4.7" }, "peerDependenciesMeta": { "@angular/localize": { @@ -194,22 +194,6 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@ngtools/webpack": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.2.6.tgz", - "integrity": "sha512-N8SvRV91+/57TcAfbghc0k0tKCukw/7KqbDaLPAQTGFekJ4xMGT3elMzOyBXTH3Hvp5HL8/hiBt2tG04qiMf+w==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "@angular/compiler-cli": "^13.0.0", - "typescript": ">=4.4.3 <4.6", - "webpack": "^5.30.0" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -228,13 +212,19 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/@angular-devkit/build-angular/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1302.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1302.6.tgz", - "integrity": "sha512-TYEh2n9tPe932rEIgdiSpojOqtDppW2jzb/empVqCkLF7WUZsXKvTanttZC34L6R2VD6SAGWhb6JDg75ghUVYA==", + "version": "0.1303.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.4.tgz", + "integrity": "sha512-3F10P9XshRXkI/PEmJUcgP4yK4sobaoInQfifzPNOemrS5nXs8y3uEiQuxzyswYx/dymZLV+19sV/eh1WfXnBA==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1302.6", + "@angular-devkit/architect": "0.1303.4", "rxjs": "6.6.7" }, "engines": { @@ -266,9 +256,9 @@ "dev": true }, "node_modules/@angular-devkit/core": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.2.6.tgz", - "integrity": "sha512-8h2mWdBTN/dYwZuzKMg2IODlOWMdbJcpQG4XVrkk9ejCPP+3aX5Aa3glCe/voN6eBNiRfs8YDM0jxmpN2aWVtg==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.4.tgz", + "integrity": "sha512-gj6i8ksPaT2bvYwI7wKJxLX53pHfTmZc1RaNbAGfZB1/zFNnb3MPj8utTcJSk4qMsGXuDDhiB7hpTKBw8ROaGA==", "dev": true, "dependencies": { "ajv": "8.9.0", @@ -333,12 +323,12 @@ "dev": true }, "node_modules/@angular-devkit/schematics": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.2.6.tgz", - "integrity": "sha512-mPgSqdnZRuPSMeUA+T+mwVCrq2yhXpcYm1/Rjbhy09CyHs4wSrFv21WHCrE6shlvXpcmwr0n+I0DIeagAPmjUA==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.4.tgz", + "integrity": "sha512-gKNpMMoZJjLKdXxjuVembic4GWa4AYV7kU1ou3ZuZoDKtKcig9URISr1wjS+nrhKYz+miFy0zIqSGMMattDlDQ==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.2.6", + "@angular-devkit/core": "13.3.4", "jsonc-parser": "3.0.0", "magic-string": "0.25.7", "ora": "5.4.1", @@ -369,9 +359,9 @@ "dev": true }, "node_modules/@angular/animations": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-13.2.6.tgz", - "integrity": "sha512-DrjpKo68uR3lSLQQXosoTCbjKQS6IKRCpR14E2t8fo0AX8i2hkB8je4SrhdCyB7FgFN7l2kgUYo4Qa8+BOB+aA==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-13.3.5.tgz", + "integrity": "sha512-BYXX80N1hxkATWqv2IZddfKvqxomktxH5fREv+KjwIYFkyK9KBnRBILqynyIJaXXut5KE6QNOf1zWEZxdOnc3A==", "dependencies": { "tslib": "^2.3.0" }, @@ -379,13 +369,13 @@ "node": "^12.20.0 || ^14.15.0 || >=16.10.0" }, "peerDependencies": { - "@angular/core": "13.2.6" + "@angular/core": "13.3.5" } }, "node_modules/@angular/cdk": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.2.6.tgz", - "integrity": "sha512-epuXmaHqfukwPsYvIksbuHLXDtb6GALV2Vgv6W2asj4TorD584CeQTs0EcdPGmCzhGUYI8U8QV63WOxu9YFcNA==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.5.tgz", + "integrity": "sha512-fA99fGgybup9ezyB/IzOa9Mk8g8LjejkqikLnC3mAeQ0lROOO7Vf9Rp1v7/ahe2lALTUbA1bzJeXzQYaffkIiA==", "dependencies": { "tslib": "^2.3.0" }, @@ -405,16 +395,16 @@ "optional": true }, "node_modules/@angular/cli": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.2.6.tgz", - "integrity": "sha512-xIjEaQI5sWemXXc7GXLm4u9UL5sjtrQL/y1PJvvk/Jsa8+kIT+MutOfZfC7zcdAh9fqHd8mokH3guFV8BJdFxA==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.4.tgz", + "integrity": "sha512-4S5FNjkZgq98zcBVgwkYtMgMRMSVsprCgq7dM8yTxIQh+Np3fYgj5eRJ1+mfFG/kankH2z/TFyuoYiILh2D9Uw==", "dev": true, "hasInstallScript": true, "dependencies": { - "@angular-devkit/architect": "0.1302.6", - "@angular-devkit/core": "13.2.6", - "@angular-devkit/schematics": "13.2.6", - "@schematics/angular": "13.2.6", + "@angular-devkit/architect": "0.1303.4", + "@angular-devkit/core": "13.3.4", + "@angular-devkit/schematics": "13.3.4", + "@schematics/angular": "13.3.4", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "4.3.3", @@ -441,9 +431,9 @@ } }, "node_modules/@angular/common": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-13.2.6.tgz", - "integrity": "sha512-t4XRb9db4UeRcPs5aHNtGuXRKSvGBlTEr0zzSeoKzHD9TCaO4dSDISh9obS9hThaPuBmPKRUHN5KE1HFmqnSHg==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-13.3.5.tgz", + "integrity": "sha512-teG+itdlw2sOMwYeXkeFe8h32SsNqN0qHHz/v6I9qKHgCLkC/or8A7NtsoCwYSTymIEJJ7DZ1w9VWhM7DSYd1w==", "dependencies": { "tslib": "^2.3.0" }, @@ -451,14 +441,14 @@ "node": "^12.20.0 || ^14.15.0 || >=16.10.0" }, "peerDependencies": { - "@angular/core": "13.2.6", + "@angular/core": "13.3.5", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-13.2.6.tgz", - "integrity": "sha512-LHU29J2/c/03WHkwtzUSElTBsXbzkKdYARodnNfsFdLPsWhyvzO3cqlcZYteFJxEy3dVH+ZrYDjqQ9Sp17aIgA==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-13.3.5.tgz", + "integrity": "sha512-iSQlYevMk5glwZSXTXf2GytykqZWdK3Rr8heIvEPqd8n88MSB3w1KnDc1fnHLF950q/nUR9K+3r4wWPwc8J2IQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -467,9 +457,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-13.2.6.tgz", - "integrity": "sha512-QtlLKj3m6a2nkewFxhg+a3tQ2gEIBzMfI2c1laWUfAfJJ56phj79k8Z/kf2HQxypphWixyTK+ugpTlMdvvOquA==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-13.3.5.tgz", + "integrity": "sha512-H7A+MZcbB4g5fa6O4giYgrCG1h5whJfIxr4txDtDfolygzwRzqH1PSMfjW/jYyIpaH6XqXMSDHvbXRFGKstboA==", "dev": true, "dependencies": { "@babel/core": "^7.17.2", @@ -492,42 +482,43 @@ "node": "^12.20.0 || ^14.15.0 || >=16.10.0" }, "peerDependencies": { - "@angular/compiler": "13.2.6", - "typescript": ">=4.4.2 <4.6" + "@angular/compiler": "13.3.5", + "typescript": ">=4.4.2 <4.7" } }, "node_modules/@angular/compiler-cli/node_modules/@ampproject/remapping": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", - "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.0" + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@angular/compiler-cli/node_modules/@babel/core": { - "version": "7.17.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", - "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.10.tgz", + "integrity": "sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.17.2", - "@babel/parser": "^7.17.3", + "@babel/generator": "^7.17.10", + "@babel/helper-compilation-targets": "^7.17.10", + "@babel/helper-module-transforms": "^7.17.7", + "@babel/helpers": "^7.17.9", + "@babel/parser": "^7.17.10", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", - "@babel/types": "^7.17.0", + "@babel/traverse": "^7.17.10", + "@babel/types": "^7.17.10", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", + "json5": "^2.2.1", "semver": "^6.3.0" }, "engines": { @@ -548,14 +539,14 @@ } }, "node_modules/@angular/compiler-cli/node_modules/@babel/generator": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", - "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.10.tgz", + "integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==", "dev": true, "dependencies": { - "@babel/types": "^7.17.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.17.10", + "@jridgewell/gen-mapping": "^0.1.0", + "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" @@ -573,19 +564,10 @@ "node": ">=12" } }, - "node_modules/@angular/compiler-cli/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@angular/core": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-13.2.6.tgz", - "integrity": "sha512-ctWsxuaSO3d3afLW+wkJSyiEIA2uhaTKNps9x5wz/oZJDaUDYVa4PM4x7/UHn2bXzBjXjN9LSW8h9F31iwmcTg==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-13.3.5.tgz", + "integrity": "sha512-lf+Be8dDRvz8J+QFR2RxS3BBfgGM4eWq4bI1+k/aqDnM6OW4pQXdq8Lzae8SxN48u1NxB1M/1bbc9LcrChrj2Q==", "dependencies": { "tslib": "^2.3.0" }, @@ -613,9 +595,9 @@ } }, "node_modules/@angular/forms": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-13.2.6.tgz", - "integrity": "sha512-3IikvNtO0RBiGb2AWl8aYcE3ivXHPIJz+JOn9Wz9XXSkYx75D3GjuJlz+fVIgz+7Q7tJuS6Q2E5qat/ldeDmoA==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-13.3.5.tgz", + "integrity": "sha512-jCxxAwf4HkDmKE76/yQmTsbqW3jsxiKyPy32Nh6Bt4r/ww8VDv+sv5YdYNuvvZcuuQ70K+/EPnKFpQgYttvS8A==", "dependencies": { "tslib": "^2.3.0" }, @@ -623,25 +605,25 @@ "node": "^12.20.0 || ^14.15.0 || >=16.10.0" }, "peerDependencies": { - "@angular/common": "13.2.6", - "@angular/core": "13.2.6", - "@angular/platform-browser": "13.2.6", + "@angular/common": "13.3.5", + "@angular/core": "13.3.5", + "@angular/platform-browser": "13.3.5", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/language-service": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-13.2.6.tgz", - "integrity": "sha512-nhF8GvEyUKIaDFTIQ4RSPQwRng7XjEWQl2zz0B8ShvQsgF/zMLo7mAci9MXxkYBIejU1I07w32r2J90rkjsARA==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-13.3.5.tgz", + "integrity": "sha512-IJawCyu4Zwk6GNPDkbSkY6sFYaBHtaHMwhaiRojrqiKA0n2bDNULLcHfYGSyA7UvkX8m9Nt0M5GaF66BIwuZSw==", "dev": true, "engines": { "node": "^12.20.0 || ^14.15.0 || >=16.10.0" } }, "node_modules/@angular/platform-browser": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-13.2.6.tgz", - "integrity": "sha512-Gc/1TqysW+P+K3NeQihmHWs4KF1mjJT20s06r+YcETnPP11uPk+UxQl7gJNsDV587DYO/wT3oISXVpRw4UJhdQ==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-13.3.5.tgz", + "integrity": "sha512-DPV1J3h1ua4GI9PuXDr8IlzJoC/TR0A/onPTaE6IFOzs1r28vB+vWRRbZURXrTYeWzWVB/2R9tPOqFNoi3zlzA==", "dependencies": { "tslib": "^2.3.0" }, @@ -649,9 +631,9 @@ "node": "^12.20.0 || ^14.15.0 || >=16.10.0" }, "peerDependencies": { - "@angular/animations": "13.2.6", - "@angular/common": "13.2.6", - "@angular/core": "13.2.6" + "@angular/animations": "13.3.5", + "@angular/common": "13.3.5", + "@angular/core": "13.3.5" }, "peerDependenciesMeta": { "@angular/animations": { @@ -660,9 +642,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-13.2.6.tgz", - "integrity": "sha512-m77+pzwZw+4kiRhxzrj1kE3N1K82I8Xt+vEBKSL2Xv42hCX0T37erC6KlztFEDj4A68s5+/0C5vfwhEiDcr/Cw==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-13.3.5.tgz", + "integrity": "sha512-Mko8/mRlcVsZJQ5zHfc/p7so/ZN16UMynTnksrD7cEgGxDuJosE8m+exqgoT03VCYaOjJtCFXSwdOb/8FOUDZQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -670,16 +652,16 @@ "node": "^12.20.0 || ^14.15.0 || >=16.10.0" }, "peerDependencies": { - "@angular/common": "13.2.6", - "@angular/compiler": "13.2.6", - "@angular/core": "13.2.6", - "@angular/platform-browser": "13.2.6" + "@angular/common": "13.3.5", + "@angular/compiler": "13.3.5", + "@angular/core": "13.3.5", + "@angular/platform-browser": "13.3.5" } }, "node_modules/@angular/router": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-13.2.6.tgz", - "integrity": "sha512-dA9vL4mPLp+iNegzuvm9FaEWirFI2ZK3WQgbdoxoIpneym+BxRTkNDzPcvEaqfUf7eDGRBqlWakFDrd+H2QEbg==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-13.3.5.tgz", + "integrity": "sha512-3yUNyBpUi0KUKP91a3dVQsr9Jfjs4wGxpiFYb3apc7lKT5R1LJqt2O0EchjOgvFyJ6TDOemdCAzKgvSRkDxpMw==", "dependencies": { "tslib": "^2.3.0" }, @@ -687,9 +669,9 @@ "node": "^12.20.0 || ^14.15.0 || >=16.10.0" }, "peerDependencies": { - "@angular/common": "13.2.6", - "@angular/core": "13.2.6", - "@angular/platform-browser": "13.2.6", + "@angular/common": "13.3.5", + "@angular/core": "13.3.5", + "@angular/platform-browser": "13.3.5", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -712,9 +694,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", - "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz", + "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==", "dev": true, "engines": { "node": ">=6.9.0" @@ -817,14 +799,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.10.tgz", + "integrity": "sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.16.4", + "@babel/compat-data": "^7.17.10", "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.5", + "browserslist": "^4.20.2", "semver": "^6.3.0" }, "engines": { @@ -933,26 +915,13 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", + "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", "dev": true, "dependencies": { - "@babel/helper-get-function-arity": "^7.16.7", "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.17.0" }, "engines": { "node": ">=6.9.0" @@ -995,19 +964,19 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", + "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", "@babel/helper-split-export-declaration": "^7.16.7", "@babel/helper-validator-identifier": "^7.16.7", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" }, "engines": { "node": ">=6.9.0" @@ -1065,12 +1034,12 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", + "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", "dev": true, "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.17.0" }, "engines": { "node": ">=6.9.0" @@ -1134,13 +1103,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", - "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.9.tgz", + "integrity": "sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==", "dev": true, "dependencies": { "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.0", + "@babel/traverse": "^7.17.9", "@babel/types": "^7.17.0" }, "engines": { @@ -1162,9 +1131,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", - "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.10.tgz", + "integrity": "sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -2305,19 +2274,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", - "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.10.tgz", + "integrity": "sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", + "@babel/generator": "^7.17.10", "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", + "@babel/helper-function-name": "^7.17.9", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.3", - "@babel/types": "^7.17.0", + "@babel/parser": "^7.17.10", + "@babel/types": "^7.17.10", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -2326,32 +2295,23 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", - "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.10.tgz", + "integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==", "dev": true, "dependencies": { - "@babel/types": "^7.17.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.17.10", + "@jridgewell/gen-mapping": "^0.1.0", + "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.10.tgz", + "integrity": "sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.16.7", @@ -2371,9 +2331,9 @@ } }, "node_modules/@fortawesome/fontawesome-free": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.0.0.tgz", - "integrity": "sha512-6LB4PYBST1Rx40klypw1SmSDArjFOcfBf2LeX9Zg5EKJT2eXiyiJq+CyBYKeXyK0sXS2FsCJWSPr/luyhuvh0Q==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.1.1.tgz", + "integrity": "sha512-J/3yg2AIXc9wznaVqpHVX3Wa5jwKovVF0AMYSnbmcXTiL3PpRPfF58pzWucCwEiCJBp+hCNRLWClTomD8SseKg==", "hasInstallScript": true, "engines": { "node": ">=6" @@ -2410,6 +2370,19 @@ "node": ">=8" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.4.tgz", @@ -2419,6 +2392,15 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.0.tgz", + "integrity": "sha512-SfJxIxNVYLTsKwzB3MoOQ1yxf4w/E6MdkvTgrgAt1bfxjSrLUoHMKrDOykwN14q65waezZIdqDneUIPh4/sKxg==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.11", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", @@ -2426,9 +2408,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", - "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", @@ -2436,14 +2418,14 @@ } }, "node_modules/@microsoft/signalr": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-6.0.3.tgz", - "integrity": "sha512-wWGVC2xi8OxNjyir8iQWuyxWHy3Dkakk2Q3VreCE7pDzFAgZ4pId6abJlRPMVIQxkUvUGc8knMW5l3sv2bJ/yw==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-6.0.4.tgz", + "integrity": "sha512-YeWRh4LxfYnq4I5CKw17/HOq8rY+ouTv6Bq+s55122StE3pK29j8j2OpP+1PA3D1ksHPfy7dFIgC33yr/E+01A==", "dependencies": { "abort-controller": "^3.0.0", "eventsource": "^1.0.7", "fetch-cookie": "^0.11.0", - "node-fetch": "^2.6.1", + "node-fetch": "^2.6.7", "ws": "^7.4.5" } }, @@ -2467,6 +2449,22 @@ } } }, + "node_modules/@ngtools/webpack": { + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.4.tgz", + "integrity": "sha512-dNDNeAOwtpX5A7TTEsgDbkg4jTmAJHD96qLqcpJqfBg8nZ4mqn6E0HinX9HZKaCST1/75T6GsFo1Muc4MsHYgA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^13.0.0", + "typescript": ">=4.4.3 <4.7", + "webpack": "^5.30.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2585,13 +2583,13 @@ } }, "node_modules/@schematics/angular": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.2.6.tgz", - "integrity": "sha512-8NzHMX9+FSgaB0lJYxlTJv9OcBuolwZJqo9M/yX3RPSqSHghA33jWwgVbV551hBJOpbVEePerG1DQkIC99DXKA==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.4.tgz", + "integrity": "sha512-Cta11k965Igz2kWj60KQ/9z6RFAg9FjZ8i1TH4nyROJs9nWemWPQNA+OJFuXrEy6Ldpk7yJ5cWgJsyryGB25PA==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.2.6", - "@angular-devkit/schematics": "13.2.6", + "@angular-devkit/core": "13.3.4", + "@angular-devkit/schematics": "13.3.4", "jsonc-parser": "3.0.0" }, "engines": { @@ -2648,9 +2646,9 @@ } }, "node_modules/@types/eslint": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.0.tgz", - "integrity": "sha512-74hbvsnc+7TEDa1z5YLSe4/q8hGYB3USNvCuzHUJrjPV6hXaq8IXcngCrHkuvFt0+8rFz7xYXrHgNayIX0UZvQ==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", + "integrity": "sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==", "dev": true, "dependencies": { "@types/estree": "*", @@ -2658,9 +2656,9 @@ } }, "node_modules/@types/eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha512-SCFeogqiptms4Fg29WpOTk5nHIzfpKCemSN63ksBQYKTcXoJEmJagV+DhVmbapZzY4/5YaOV1nZwrsU79fFm1g==", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", "dev": true, "dependencies": { "@types/eslint": "*", @@ -2668,9 +2666,9 @@ } }, "node_modules/@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", "dev": true }, "node_modules/@types/express": { @@ -2724,9 +2722,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "17.0.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", - "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "version": "17.0.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.30.tgz", + "integrity": "sha512-oNBIZjIqyHYP8VCNAV9uEytXVeXG2oR0w9lgAXro20eugRQfY002qr3CUl6BAe+Yf/z3CRjPdz27Pu6WWtuSRw==", "dev": true }, "node_modules/@types/parse-json": { @@ -2748,9 +2746,9 @@ "dev": true }, "node_modules/@types/retry": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", - "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, "node_modules/@types/serve-index": { @@ -3258,9 +3256,9 @@ } }, "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "dependencies": { "lodash": "^4.17.14" @@ -3485,24 +3483,27 @@ } }, "node_modules/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", "dev": true, "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/body-parser/node_modules/bytes": { @@ -3523,6 +3524,15 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -3572,15 +3582,25 @@ } }, "node_modules/browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "version": "4.20.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", + "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], "dependencies": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", + "caniuse-lite": "^1.0.30001332", + "electron-to-chromium": "^1.4.118", "escalade": "^3.1.1", - "node-releases": "^2.0.1", + "node-releases": "^2.0.3", "picocolors": "^1.0.0" }, "bin": { @@ -3588,10 +3608,6 @@ }, "engines": { "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" } }, "node_modules/buffer": { @@ -3711,14 +3727,20 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001307", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001307.tgz", - "integrity": "sha512-+MXEMczJ4FuxJAUp0jvAl6Df0NI/OfW1RWEE61eSmzS7hw6lz4IKutbhbXendwq8BljfFuHtu26VWsg4afQ7Ng==", + "version": "1.0.30001334", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001334.tgz", + "integrity": "sha512-kbaCEBRRVSoeNs74sCuq92MJyGrMtjWVfhltoHUCW4t4pXFvGjUBrfo47weBRViHkiV3eBYyIsfl956NtHGazw==", "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] }, "node_modules/chalk": { "version": "2.4.2", @@ -4038,9 +4060,9 @@ } }, "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "dev": true, "engines": { "node": ">= 0.6" @@ -4624,10 +4646,14 @@ } }, "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, "node_modules/detect-node": { "version": "2.1.0", @@ -4734,9 +4760,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.31", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.31.tgz", - "integrity": "sha512-t3XVQtk+Frkv6aTD4RRk0OqosU+VLe1dQFW83MDer78ZD6a52frgXuYOIsLYTQiH2Lm+JB2OKYcn7zrX+YGAiQ==", + "version": "1.4.129", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.129.tgz", + "integrity": "sha512-GgtN6bsDtHdtXJtlMYZWGB/uOyjZWjmRDumXTas7dGBaB9zUyCjzHet1DY2KhyHN8R0GLbzZWqm4efeddqqyRQ==", "dev": true }, "node_modules/emoji-regex": { @@ -4767,7 +4793,6 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, "optional": true, "dependencies": { "iconv-lite": "^0.6.2" @@ -4777,7 +4802,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -4787,9 +4811,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", - "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz", + "integrity": "sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -5364,38 +5388,39 @@ } }, "node_modules/express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", - "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.2", + "body-parser": "1.20.0", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.2", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.9.7", + "qs": "6.10.3", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", + "send": "0.18.0", + "serve-static": "1.15.0", "setprototypeof": "1.2.0", - "statuses": "~1.5.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -5419,6 +5444,15 @@ "ms": "2.0.0" } }, + "node_modules/express/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -5572,17 +5606,17 @@ } }, "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dev": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "engines": { @@ -5729,6 +5763,15 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gauge": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.0.tgz", @@ -6001,9 +6044,9 @@ } }, "node_modules/html-entities": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz", - "integrity": "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", "dev": true }, "node_modules/http-cache-semantics": { @@ -6019,19 +6062,28 @@ "dev": true }, "node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, "dependencies": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" } }, "node_modules/http-parser-js": { @@ -6069,9 +6121,9 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.3.tgz", - "integrity": "sha512-1bloEwnrHMnCoO/Gcwbz7eSVvW50KPES01PecpagI+YLNLci4AcuKJrujW4Mc3sBLpFxMSlsLNHS5Nl/lvrTPA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", "dev": true, "dependencies": { "@types/http-proxy": "^1.17.8", @@ -6764,13 +6816,10 @@ "dev": true }, "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, "bin": { "json5": "lib/cli.js" }, @@ -7308,9 +7357,9 @@ "dev": true }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -7320,9 +7369,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "node_modules/minipass": { @@ -7559,20 +7608,28 @@ "optional": true }, "node_modules/node-fetch": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", - "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "dependencies": { "whatwg-url": "^5.0.0" }, "engines": { "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, "node_modules/node-forge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", - "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "dev": true, "engines": { "node": ">= 6.13.0" @@ -7615,9 +7672,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.4.tgz", + "integrity": "sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==", "dev": true }, "node_modules/nopt": { @@ -7830,6 +7887,15 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-is": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", @@ -7880,9 +7946,9 @@ "dev": true }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "dependencies": { "ee-first": "1.1.1" @@ -8094,12 +8160,12 @@ } }, "node_modules/p-retry": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz", - "integrity": "sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", "dev": true, "dependencies": { - "@types/retry": "^0.12.0", + "@types/retry": "0.12.0", "retry": "^0.13.1" }, "engines": { @@ -8371,12 +8437,12 @@ } }, "node_modules/portfinder/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "dependencies": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" @@ -8989,10 +9055,13 @@ } }, "node_modules/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, "engines": { "node": ">=0.6" }, @@ -9044,13 +9113,13 @@ } }, "node_modules/raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dev": true, "dependencies": { "bytes": "3.1.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -9170,13 +9239,14 @@ "dev": true }, "node_modules/regexp.prototype.flags": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz", - "integrity": "sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" }, "engines": { "node": ">= 0.4" @@ -9414,12 +9484,12 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "devOptional": true }, "node_modules/sass": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.0.tgz", - "integrity": "sha512-TVwVdNDj6p6b4QymJtNtRS2YtLJ/CqZriGg0eIAbAKMlN8Xy6kbv33FsEZSF7FufFFM705SQviHjjThfaQ4VNw==", + "version": "1.49.9", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.9.tgz", + "integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -9430,7 +9500,7 @@ "sass": "sass.js" }, "engines": { - "node": ">=8.9.0" + "node": ">=12.0.0" } }, "node_modules/sass-loader": { @@ -9498,12 +9568,12 @@ "dev": true }, "node_modules/selfsigned": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.0.tgz", - "integrity": "sha512-cUdFiCbKoa1mZ6osuJs2uDHrs0k0oprsKveFiiaBKCNq3SYyb5gs2HxhQyDNLCmL51ZZThqi4YNDpCK6GOP1iQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.1.tgz", + "integrity": "sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==", "dev": true, "dependencies": { - "node-forge": "^1.2.0" + "node-forge": "^1" }, "engines": { "node": ">=10" @@ -9525,24 +9595,24 @@ } }, "node_modules/send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dev": true, "dependencies": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "engines": { "node": ">= 0.8.0" @@ -9563,6 +9633,15 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "node_modules/send/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -9638,16 +9717,25 @@ "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "dev": true }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dev": true, "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "0.18.0" }, "engines": { "node": ">= 0.8.0" @@ -9698,6 +9786,20 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", @@ -9900,12 +10002,12 @@ } }, "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/string_decoder": { @@ -10254,9 +10356,9 @@ } }, "node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "node_modules/type-fest": { "version": "0.21.3", @@ -10474,13 +10576,13 @@ "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "node_modules/webpack": { - "version": "5.67.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.67.0.tgz", - "integrity": "sha512-LjFbfMh89xBDpUMgA1W9Ur6Rn/gnr2Cq1jjHFPo4v6a79/ypznSYbAyPgGhwsxBtMIaEmDD1oJoA7BEYw/Fbrw==", + "version": "5.70.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz", + "integrity": "sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==", "dev": true, "dependencies": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.50", + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", @@ -10488,7 +10590,7 @@ "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.3", + "enhanced-resolve": "^5.9.2", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -10544,9 +10646,9 @@ } }, "node_modules/webpack-dev-middleware/node_modules/ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", @@ -10648,9 +10750,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", @@ -10952,9 +11054,9 @@ } }, "node_modules/yargs": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", - "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", + "version": "17.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.4.1.tgz", + "integrity": "sha512-WSZD9jgobAg3ZKuCQZSa3g9QOJeCCqLoLAykiWgmXnDo9EPnn4RPf5qVTtzgOx66o6/oqhcA5tHtJXpG8pMt3g==", "dev": true, "dependencies": { "cliui": "^7.0.2", @@ -10999,12 +11101,12 @@ } }, "@angular-devkit/architect": { - "version": "0.1302.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1302.6.tgz", - "integrity": "sha512-NztzorUMfwJeRaT7SY00Y8WSqc2lQYuF11yNoyEm7Dae3V7VZ28rW2Z9RwibP27rYQL0RjSMaz2wKITHX2vOAw==", + "version": "0.1303.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.4.tgz", + "integrity": "sha512-d6YmIWdYvwk6WaknHRcJgiXeJvX9K5i8uPMAaL2P2/LU8n3moIQ59C7SP0uULcHuuiREEmFWOyyrWnGxZCI9bg==", "dev": true, "requires": { - "@angular-devkit/core": "13.2.6", + "@angular-devkit/core": "13.3.4", "rxjs": "6.6.7" }, "dependencies": { @@ -11026,15 +11128,15 @@ } }, "@angular-devkit/build-angular": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.2.6.tgz", - "integrity": "sha512-Y2ojy6xbZ0kwScppcutLHBP8eW0qNOjburTISSBU/L5l/9FOeZ1E7yAreKuVu/qibZiLbSJfAhk+SLwhRHFSSQ==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.4.tgz", + "integrity": "sha512-z74cmDi2V+5XpvyZKFlUXxvQ446shxyZk5aGdToG6n+0/IJWkDXSiryQkCo8nblGMze7HKf75i3DsGWYQZLDnQ==", "dev": true, "requires": { "@ampproject/remapping": "1.1.1", - "@angular-devkit/architect": "0.1302.6", - "@angular-devkit/build-webpack": "0.1302.6", - "@angular-devkit/core": "13.2.6", + "@angular-devkit/architect": "0.1303.4", + "@angular-devkit/build-webpack": "0.1303.4", + "@angular-devkit/core": "13.3.4", "@babel/core": "7.16.12", "@babel/generator": "7.16.8", "@babel/helper-annotate-as-pure": "7.16.7", @@ -11045,7 +11147,7 @@ "@babel/runtime": "7.16.7", "@babel/template": "7.16.7", "@discoveryjs/json-ext": "0.5.6", - "@ngtools/webpack": "13.2.6", + "@ngtools/webpack": "13.3.4", "ansi-colors": "4.1.1", "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", @@ -11068,7 +11170,7 @@ "license-webpack-plugin": "4.0.2", "loader-utils": "3.2.0", "mini-css-extract-plugin": "2.5.3", - "minimatch": "3.0.4", + "minimatch": "3.0.5", "open": "8.4.0", "ora": "5.4.1", "parse5-html-rewriting-stream": "6.0.1", @@ -11080,7 +11182,7 @@ "regenerator-runtime": "0.13.9", "resolve-url-loader": "5.0.0", "rxjs": "6.6.7", - "sass": "1.49.0", + "sass": "1.49.9", "sass-loader": "12.4.0", "semver": "7.3.5", "source-map-loader": "3.0.1", @@ -11091,20 +11193,13 @@ "text-table": "0.2.0", "tree-kill": "1.2.2", "tslib": "2.3.1", - "webpack": "5.67.0", + "webpack": "5.70.0", "webpack-dev-middleware": "5.3.0", "webpack-dev-server": "4.7.3", "webpack-merge": "5.8.0", "webpack-subresource-integrity": "5.1.0" }, "dependencies": { - "@ngtools/webpack": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.2.6.tgz", - "integrity": "sha512-N8SvRV91+/57TcAfbghc0k0tKCukw/7KqbDaLPAQTGFekJ4xMGT3elMzOyBXTH3Hvp5HL8/hiBt2tG04qiMf+w==", - "dev": true, - "requires": {} - }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -11121,16 +11216,22 @@ "dev": true } } + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true } } }, "@angular-devkit/build-webpack": { - "version": "0.1302.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1302.6.tgz", - "integrity": "sha512-TYEh2n9tPe932rEIgdiSpojOqtDppW2jzb/empVqCkLF7WUZsXKvTanttZC34L6R2VD6SAGWhb6JDg75ghUVYA==", + "version": "0.1303.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.4.tgz", + "integrity": "sha512-3F10P9XshRXkI/PEmJUcgP4yK4sobaoInQfifzPNOemrS5nXs8y3uEiQuxzyswYx/dymZLV+19sV/eh1WfXnBA==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1302.6", + "@angular-devkit/architect": "0.1303.4", "rxjs": "6.6.7" }, "dependencies": { @@ -11152,9 +11253,9 @@ } }, "@angular-devkit/core": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.2.6.tgz", - "integrity": "sha512-8h2mWdBTN/dYwZuzKMg2IODlOWMdbJcpQG4XVrkk9ejCPP+3aX5Aa3glCe/voN6eBNiRfs8YDM0jxmpN2aWVtg==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.4.tgz", + "integrity": "sha512-gj6i8ksPaT2bvYwI7wKJxLX53pHfTmZc1RaNbAGfZB1/zFNnb3MPj8utTcJSk4qMsGXuDDhiB7hpTKBw8ROaGA==", "dev": true, "requires": { "ajv": "8.9.0", @@ -11201,12 +11302,12 @@ } }, "@angular-devkit/schematics": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.2.6.tgz", - "integrity": "sha512-mPgSqdnZRuPSMeUA+T+mwVCrq2yhXpcYm1/Rjbhy09CyHs4wSrFv21WHCrE6shlvXpcmwr0n+I0DIeagAPmjUA==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.4.tgz", + "integrity": "sha512-gKNpMMoZJjLKdXxjuVembic4GWa4AYV7kU1ou3ZuZoDKtKcig9URISr1wjS+nrhKYz+miFy0zIqSGMMattDlDQ==", "dev": true, "requires": { - "@angular-devkit/core": "13.2.6", + "@angular-devkit/core": "13.3.4", "jsonc-parser": "3.0.0", "magic-string": "0.25.7", "ora": "5.4.1", @@ -11231,17 +11332,17 @@ } }, "@angular/animations": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-13.2.6.tgz", - "integrity": "sha512-DrjpKo68uR3lSLQQXosoTCbjKQS6IKRCpR14E2t8fo0AX8i2hkB8je4SrhdCyB7FgFN7l2kgUYo4Qa8+BOB+aA==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-13.3.5.tgz", + "integrity": "sha512-BYXX80N1hxkATWqv2IZddfKvqxomktxH5fREv+KjwIYFkyK9KBnRBILqynyIJaXXut5KE6QNOf1zWEZxdOnc3A==", "requires": { "tslib": "^2.3.0" } }, "@angular/cdk": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.2.6.tgz", - "integrity": "sha512-epuXmaHqfukwPsYvIksbuHLXDtb6GALV2Vgv6W2asj4TorD584CeQTs0EcdPGmCzhGUYI8U8QV63WOxu9YFcNA==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.5.tgz", + "integrity": "sha512-fA99fGgybup9ezyB/IzOa9Mk8g8LjejkqikLnC3mAeQ0lROOO7Vf9Rp1v7/ahe2lALTUbA1bzJeXzQYaffkIiA==", "requires": { "parse5": "^5.0.0", "tslib": "^2.3.0" @@ -11256,15 +11357,15 @@ } }, "@angular/cli": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.2.6.tgz", - "integrity": "sha512-xIjEaQI5sWemXXc7GXLm4u9UL5sjtrQL/y1PJvvk/Jsa8+kIT+MutOfZfC7zcdAh9fqHd8mokH3guFV8BJdFxA==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.4.tgz", + "integrity": "sha512-4S5FNjkZgq98zcBVgwkYtMgMRMSVsprCgq7dM8yTxIQh+Np3fYgj5eRJ1+mfFG/kankH2z/TFyuoYiILh2D9Uw==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1302.6", - "@angular-devkit/core": "13.2.6", - "@angular-devkit/schematics": "13.2.6", - "@schematics/angular": "13.2.6", + "@angular-devkit/architect": "0.1303.4", + "@angular-devkit/core": "13.3.4", + "@angular-devkit/schematics": "13.3.4", + "@schematics/angular": "13.3.4", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "4.3.3", @@ -11283,25 +11384,25 @@ } }, "@angular/common": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-13.2.6.tgz", - "integrity": "sha512-t4XRb9db4UeRcPs5aHNtGuXRKSvGBlTEr0zzSeoKzHD9TCaO4dSDISh9obS9hThaPuBmPKRUHN5KE1HFmqnSHg==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-13.3.5.tgz", + "integrity": "sha512-teG+itdlw2sOMwYeXkeFe8h32SsNqN0qHHz/v6I9qKHgCLkC/or8A7NtsoCwYSTymIEJJ7DZ1w9VWhM7DSYd1w==", "requires": { "tslib": "^2.3.0" } }, "@angular/compiler": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-13.2.6.tgz", - "integrity": "sha512-LHU29J2/c/03WHkwtzUSElTBsXbzkKdYARodnNfsFdLPsWhyvzO3cqlcZYteFJxEy3dVH+ZrYDjqQ9Sp17aIgA==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-13.3.5.tgz", + "integrity": "sha512-iSQlYevMk5glwZSXTXf2GytykqZWdK3Rr8heIvEPqd8n88MSB3w1KnDc1fnHLF950q/nUR9K+3r4wWPwc8J2IQ==", "requires": { "tslib": "^2.3.0" } }, "@angular/compiler-cli": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-13.2.6.tgz", - "integrity": "sha512-QtlLKj3m6a2nkewFxhg+a3tQ2gEIBzMfI2c1laWUfAfJJ56phj79k8Z/kf2HQxypphWixyTK+ugpTlMdvvOquA==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-13.3.5.tgz", + "integrity": "sha512-H7A+MZcbB4g5fa6O4giYgrCG1h5whJfIxr4txDtDfolygzwRzqH1PSMfjW/jYyIpaH6XqXMSDHvbXRFGKstboA==", "dev": true, "requires": { "@babel/core": "^7.17.2", @@ -11317,34 +11418,35 @@ }, "dependencies": { "@ampproject/remapping": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", - "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", "dev": true, "requires": { - "@jridgewell/trace-mapping": "^0.3.0" + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" } }, "@babel/core": { - "version": "7.17.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", - "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.10.tgz", + "integrity": "sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA==", "dev": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.17.2", - "@babel/parser": "^7.17.3", + "@babel/generator": "^7.17.10", + "@babel/helper-compilation-targets": "^7.17.10", + "@babel/helper-module-transforms": "^7.17.7", + "@babel/helpers": "^7.17.9", + "@babel/parser": "^7.17.10", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", - "@babel/types": "^7.17.0", + "@babel/traverse": "^7.17.10", + "@babel/types": "^7.17.10", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", + "json5": "^2.2.1", "semver": "^6.3.0" }, "dependencies": { @@ -11357,14 +11459,14 @@ } }, "@babel/generator": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", - "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.10.tgz", + "integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==", "dev": true, "requires": { - "@babel/types": "^7.17.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.17.10", + "@jridgewell/gen-mapping": "^0.1.0", + "jsesc": "^2.5.1" } }, "magic-string": { @@ -11375,19 +11477,13 @@ "requires": { "sourcemap-codec": "^1.4.8" } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true } } }, "@angular/core": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-13.2.6.tgz", - "integrity": "sha512-ctWsxuaSO3d3afLW+wkJSyiEIA2uhaTKNps9x5wz/oZJDaUDYVa4PM4x7/UHn2bXzBjXjN9LSW8h9F31iwmcTg==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-13.3.5.tgz", + "integrity": "sha512-lf+Be8dDRvz8J+QFR2RxS3BBfgGM4eWq4bI1+k/aqDnM6OW4pQXdq8Lzae8SxN48u1NxB1M/1bbc9LcrChrj2Q==", "requires": { "tslib": "^2.3.0" } @@ -11401,39 +11497,39 @@ } }, "@angular/forms": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-13.2.6.tgz", - "integrity": "sha512-3IikvNtO0RBiGb2AWl8aYcE3ivXHPIJz+JOn9Wz9XXSkYx75D3GjuJlz+fVIgz+7Q7tJuS6Q2E5qat/ldeDmoA==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-13.3.5.tgz", + "integrity": "sha512-jCxxAwf4HkDmKE76/yQmTsbqW3jsxiKyPy32Nh6Bt4r/ww8VDv+sv5YdYNuvvZcuuQ70K+/EPnKFpQgYttvS8A==", "requires": { "tslib": "^2.3.0" } }, "@angular/language-service": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-13.2.6.tgz", - "integrity": "sha512-nhF8GvEyUKIaDFTIQ4RSPQwRng7XjEWQl2zz0B8ShvQsgF/zMLo7mAci9MXxkYBIejU1I07w32r2J90rkjsARA==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-13.3.5.tgz", + "integrity": "sha512-IJawCyu4Zwk6GNPDkbSkY6sFYaBHtaHMwhaiRojrqiKA0n2bDNULLcHfYGSyA7UvkX8m9Nt0M5GaF66BIwuZSw==", "dev": true }, "@angular/platform-browser": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-13.2.6.tgz", - "integrity": "sha512-Gc/1TqysW+P+K3NeQihmHWs4KF1mjJT20s06r+YcETnPP11uPk+UxQl7gJNsDV587DYO/wT3oISXVpRw4UJhdQ==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-13.3.5.tgz", + "integrity": "sha512-DPV1J3h1ua4GI9PuXDr8IlzJoC/TR0A/onPTaE6IFOzs1r28vB+vWRRbZURXrTYeWzWVB/2R9tPOqFNoi3zlzA==", "requires": { "tslib": "^2.3.0" } }, "@angular/platform-browser-dynamic": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-13.2.6.tgz", - "integrity": "sha512-m77+pzwZw+4kiRhxzrj1kE3N1K82I8Xt+vEBKSL2Xv42hCX0T37erC6KlztFEDj4A68s5+/0C5vfwhEiDcr/Cw==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-13.3.5.tgz", + "integrity": "sha512-Mko8/mRlcVsZJQ5zHfc/p7so/ZN16UMynTnksrD7cEgGxDuJosE8m+exqgoT03VCYaOjJtCFXSwdOb/8FOUDZQ==", "requires": { "tslib": "^2.3.0" } }, "@angular/router": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-13.2.6.tgz", - "integrity": "sha512-dA9vL4mPLp+iNegzuvm9FaEWirFI2ZK3WQgbdoxoIpneym+BxRTkNDzPcvEaqfUf7eDGRBqlWakFDrd+H2QEbg==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-13.3.5.tgz", + "integrity": "sha512-3yUNyBpUi0KUKP91a3dVQsr9Jfjs4wGxpiFYb3apc7lKT5R1LJqt2O0EchjOgvFyJ6TDOemdCAzKgvSRkDxpMw==", "requires": { "tslib": "^2.3.0" } @@ -11454,9 +11550,9 @@ } }, "@babel/compat-data": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", - "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz", + "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==", "dev": true }, "@babel/core": { @@ -11535,14 +11631,14 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.10.tgz", + "integrity": "sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.16.4", + "@babel/compat-data": "^7.17.10", "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.5", + "browserslist": "^4.20.2", "semver": "^6.3.0" }, "dependencies": { @@ -11622,23 +11718,13 @@ } }, "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", + "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.16.7", "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.17.0" } }, "@babel/helper-hoist-variables": { @@ -11669,19 +11755,19 @@ } }, "@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", + "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", "@babel/helper-split-export-declaration": "^7.16.7", "@babel/helper-validator-identifier": "^7.16.7", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" } }, "@babel/helper-optimise-call-expression": { @@ -11724,12 +11810,12 @@ } }, "@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", + "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", "dev": true, "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.17.0" } }, "@babel/helper-skip-transparent-expression-wrappers": { @@ -11775,13 +11861,13 @@ } }, "@babel/helpers": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", - "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.9.tgz", + "integrity": "sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==", "dev": true, "requires": { "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.0", + "@babel/traverse": "^7.17.9", "@babel/types": "^7.17.0" } }, @@ -11797,9 +11883,9 @@ } }, "@babel/parser": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", - "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.10.tgz", + "integrity": "sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==", "dev": true }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { @@ -12566,46 +12652,40 @@ } }, "@babel/traverse": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", - "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.10.tgz", + "integrity": "sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw==", "dev": true, "requires": { "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", + "@babel/generator": "^7.17.10", "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", + "@babel/helper-function-name": "^7.17.9", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.3", - "@babel/types": "^7.17.0", + "@babel/parser": "^7.17.10", + "@babel/types": "^7.17.10", "debug": "^4.1.0", "globals": "^11.1.0" }, "dependencies": { "@babel/generator": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", - "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.10.tgz", + "integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==", "dev": true, "requires": { - "@babel/types": "^7.17.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.17.10", + "@jridgewell/gen-mapping": "^0.1.0", + "jsesc": "^2.5.1" } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true } } }, "@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.10.tgz", + "integrity": "sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.16.7", @@ -12619,9 +12699,9 @@ "dev": true }, "@fortawesome/fontawesome-free": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.0.0.tgz", - "integrity": "sha512-6LB4PYBST1Rx40klypw1SmSDArjFOcfBf2LeX9Zg5EKJT2eXiyiJq+CyBYKeXyK0sXS2FsCJWSPr/luyhuvh0Q==" + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.1.1.tgz", + "integrity": "sha512-J/3yg2AIXc9wznaVqpHVX3Wa5jwKovVF0AMYSnbmcXTiL3PpRPfF58pzWucCwEiCJBp+hCNRLWClTomD8SseKg==" }, "@gar/promisify": { "version": "1.1.2", @@ -12648,12 +12728,28 @@ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "@jridgewell/resolve-uri": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.4.tgz", "integrity": "sha512-cz8HFjOFfUBtvN+NXYSFMHYRdxZMaEl0XypVrhzxBgadKIXhIkRd8aMeHhmF56Sl7SuS8OnUpQ73/k9LE4VnLg==", "dev": true }, + "@jridgewell/set-array": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.0.tgz", + "integrity": "sha512-SfJxIxNVYLTsKwzB3MoOQ1yxf4w/E6MdkvTgrgAt1bfxjSrLUoHMKrDOykwN14q65waezZIdqDneUIPh4/sKxg==", + "dev": true + }, "@jridgewell/sourcemap-codec": { "version": "1.4.11", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", @@ -12661,9 +12757,9 @@ "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", - "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", @@ -12671,14 +12767,14 @@ } }, "@microsoft/signalr": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-6.0.3.tgz", - "integrity": "sha512-wWGVC2xi8OxNjyir8iQWuyxWHy3Dkakk2Q3VreCE7pDzFAgZ4pId6abJlRPMVIQxkUvUGc8knMW5l3sv2bJ/yw==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-6.0.4.tgz", + "integrity": "sha512-YeWRh4LxfYnq4I5CKw17/HOq8rY+ouTv6Bq+s55122StE3pK29j8j2OpP+1PA3D1ksHPfy7dFIgC33yr/E+01A==", "requires": { "abort-controller": "^3.0.0", "eventsource": "^1.0.7", "fetch-cookie": "^0.11.0", - "node-fetch": "^2.6.1", + "node-fetch": "^2.6.7", "ws": "^7.4.5" }, "dependencies": { @@ -12690,6 +12786,13 @@ } } }, + "@ngtools/webpack": { + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.4.tgz", + "integrity": "sha512-dNDNeAOwtpX5A7TTEsgDbkg4jTmAJHD96qLqcpJqfBg8nZ4mqn6E0HinX9HZKaCST1/75T6GsFo1Muc4MsHYgA==", + "dev": true, + "requires": {} + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -12790,13 +12893,13 @@ } }, "@schematics/angular": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.2.6.tgz", - "integrity": "sha512-8NzHMX9+FSgaB0lJYxlTJv9OcBuolwZJqo9M/yX3RPSqSHghA33jWwgVbV551hBJOpbVEePerG1DQkIC99DXKA==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.4.tgz", + "integrity": "sha512-Cta11k965Igz2kWj60KQ/9z6RFAg9FjZ8i1TH4nyROJs9nWemWPQNA+OJFuXrEy6Ldpk7yJ5cWgJsyryGB25PA==", "dev": true, "requires": { - "@angular-devkit/core": "13.2.6", - "@angular-devkit/schematics": "13.2.6", + "@angular-devkit/core": "13.3.4", + "@angular-devkit/schematics": "13.3.4", "jsonc-parser": "3.0.0" } }, @@ -12845,9 +12948,9 @@ } }, "@types/eslint": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.0.tgz", - "integrity": "sha512-74hbvsnc+7TEDa1z5YLSe4/q8hGYB3USNvCuzHUJrjPV6hXaq8IXcngCrHkuvFt0+8rFz7xYXrHgNayIX0UZvQ==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", + "integrity": "sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==", "dev": true, "requires": { "@types/estree": "*", @@ -12855,9 +12958,9 @@ } }, "@types/eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha512-SCFeogqiptms4Fg29WpOTk5nHIzfpKCemSN63ksBQYKTcXoJEmJagV+DhVmbapZzY4/5YaOV1nZwrsU79fFm1g==", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", "dev": true, "requires": { "@types/eslint": "*", @@ -12865,9 +12968,9 @@ } }, "@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", "dev": true }, "@types/express": { @@ -12921,9 +13024,9 @@ "dev": true }, "@types/node": { - "version": "17.0.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", - "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "version": "17.0.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.30.tgz", + "integrity": "sha512-oNBIZjIqyHYP8VCNAV9uEytXVeXG2oR0w9lgAXro20eugRQfY002qr3CUl6BAe+Yf/z3CRjPdz27Pu6WWtuSRw==", "dev": true }, "@types/parse-json": { @@ -12945,9 +13048,9 @@ "dev": true }, "@types/retry": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", - "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, "@types/serve-index": { @@ -13379,9 +13482,9 @@ "dev": true }, "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "requires": { "lodash": "^4.17.14" @@ -13543,21 +13646,23 @@ } }, "body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", "dev": true, "requires": { "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "dependencies": { "bytes": { @@ -13575,6 +13680,12 @@ "ms": "2.0.0" } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -13623,15 +13734,15 @@ } }, "browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "version": "4.20.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", + "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", + "caniuse-lite": "^1.0.30001332", + "electron-to-chromium": "^1.4.118", "escalade": "^3.1.1", - "node-releases": "^2.0.1", + "node-releases": "^2.0.3", "picocolors": "^1.0.0" } }, @@ -13723,9 +13834,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001307", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001307.tgz", - "integrity": "sha512-+MXEMczJ4FuxJAUp0jvAl6Df0NI/OfW1RWEE61eSmzS7hw6lz4IKutbhbXendwq8BljfFuHtu26VWsg4afQ7Ng==", + "version": "1.0.30001334", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001334.tgz", + "integrity": "sha512-kbaCEBRRVSoeNs74sCuq92MJyGrMtjWVfhltoHUCW4t4pXFvGjUBrfo47weBRViHkiV3eBYyIsfl956NtHGazw==", "dev": true }, "chalk": { @@ -13977,9 +14088,9 @@ } }, "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "dev": true }, "cookie-signature": { @@ -14408,9 +14519,9 @@ "dev": true }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true }, "detect-node": { @@ -14497,9 +14608,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.4.31", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.31.tgz", - "integrity": "sha512-t3XVQtk+Frkv6aTD4RRk0OqosU+VLe1dQFW83MDer78ZD6a52frgXuYOIsLYTQiH2Lm+JB2OKYcn7zrX+YGAiQ==", + "version": "1.4.129", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.129.tgz", + "integrity": "sha512-GgtN6bsDtHdtXJtlMYZWGB/uOyjZWjmRDumXTas7dGBaB9zUyCjzHet1DY2KhyHN8R0GLbzZWqm4efeddqqyRQ==", "dev": true }, "emoji-regex": { @@ -14524,7 +14635,6 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, "optional": true, "requires": { "iconv-lite": "^0.6.2" @@ -14534,7 +14644,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "optional": true, "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -14543,9 +14652,9 @@ } }, "enhanced-resolve": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", - "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz", + "integrity": "sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -14880,38 +14989,39 @@ } }, "express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", - "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", "dev": true, "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.2", + "body-parser": "1.20.0", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.2", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.9.7", + "qs": "6.10.3", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", + "send": "0.18.0", + "serve-static": "1.15.0", "setprototypeof": "1.2.0", - "statuses": "~1.5.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -14932,6 +15042,12 @@ "ms": "2.0.0" } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -15048,17 +15164,17 @@ } }, "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dev": true, "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "dependencies": { @@ -15158,6 +15274,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, "gauge": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.0.tgz", @@ -15372,9 +15494,9 @@ } }, "html-entities": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz", - "integrity": "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", "dev": true }, "http-cache-semantics": { @@ -15390,16 +15512,24 @@ "dev": true }, "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, "requires": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + } } }, "http-parser-js": { @@ -15431,9 +15561,9 @@ } }, "http-proxy-middleware": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.3.tgz", - "integrity": "sha512-1bloEwnrHMnCoO/Gcwbz7eSVvW50KPES01PecpagI+YLNLci4AcuKJrujW4Mc3sBLpFxMSlsLNHS5Nl/lvrTPA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", "dev": true, "requires": { "@types/http-proxy": "^1.17.8", @@ -15927,13 +16057,10 @@ "dev": true }, "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true }, "jsonc-parser": { "version": "3.0.0", @@ -16323,18 +16450,18 @@ "dev": true }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "minipass": { @@ -16518,17 +16645,17 @@ "optional": true }, "node-fetch": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", - "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "requires": { "whatwg-url": "^5.0.0" } }, "node-forge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", - "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "dev": true }, "node-gyp": { @@ -16557,9 +16684,9 @@ "optional": true }, "node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.4.tgz", + "integrity": "sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==", "dev": true }, "nopt": { @@ -16729,6 +16856,12 @@ "boolbase": "^1.0.0" } }, + "object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "dev": true + }, "object-is": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", @@ -16764,9 +16897,9 @@ "dev": true }, "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "requires": { "ee-first": "1.1.1" @@ -16917,12 +17050,12 @@ } }, "p-retry": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz", - "integrity": "sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", "dev": true, "requires": { - "@types/retry": "^0.12.0", + "@types/retry": "0.12.0", "retry": "^0.13.1" }, "dependencies": { @@ -17137,12 +17270,12 @@ } }, "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "requires": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" } } } @@ -17553,10 +17686,13 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", - "dev": true + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } }, "querystringify": { "version": "2.2.0", @@ -17585,13 +17721,13 @@ "dev": true }, "raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dev": true, "requires": { "bytes": "3.1.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -17694,13 +17830,14 @@ "dev": true }, "regexp.prototype.flags": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz", - "integrity": "sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" } }, "regexpu-core": { @@ -17870,12 +18007,12 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "devOptional": true }, "sass": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.0.tgz", - "integrity": "sha512-TVwVdNDj6p6b4QymJtNtRS2YtLJ/CqZriGg0eIAbAKMlN8Xy6kbv33FsEZSF7FufFFM705SQviHjjThfaQ4VNw==", + "version": "1.49.9", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.9.tgz", + "integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", @@ -17917,12 +18054,12 @@ "dev": true }, "selfsigned": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.0.tgz", - "integrity": "sha512-cUdFiCbKoa1mZ6osuJs2uDHrs0k0oprsKveFiiaBKCNq3SYyb5gs2HxhQyDNLCmL51ZZThqi4YNDpCK6GOP1iQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.1.tgz", + "integrity": "sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==", "dev": true, "requires": { - "node-forge": "^1.2.0" + "node-forge": "^1" } }, "semver": { @@ -17935,24 +18072,24 @@ } }, "send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dev": true, "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "dependencies": { "debug": { @@ -17972,6 +18109,12 @@ } } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -18042,19 +18185,25 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true } } }, "serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dev": true, "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "0.18.0" } }, "set-blocking": { @@ -18093,6 +18242,17 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", @@ -18254,9 +18414,9 @@ } }, "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true }, "string_decoder": { @@ -18493,9 +18653,9 @@ "dev": true }, "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "type-fest": { "version": "0.21.3", @@ -18667,13 +18827,13 @@ "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "webpack": { - "version": "5.67.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.67.0.tgz", - "integrity": "sha512-LjFbfMh89xBDpUMgA1W9Ur6Rn/gnr2Cq1jjHFPo4v6a79/ypznSYbAyPgGhwsxBtMIaEmDD1oJoA7BEYw/Fbrw==", + "version": "5.70.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz", + "integrity": "sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==", "dev": true, "requires": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.50", + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", @@ -18681,7 +18841,7 @@ "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.3", + "enhanced-resolve": "^5.9.2", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -18725,9 +18885,9 @@ }, "dependencies": { "ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -18803,9 +18963,9 @@ }, "dependencies": { "ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -19002,9 +19162,9 @@ "dev": true }, "yargs": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", - "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", + "version": "17.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.4.1.tgz", + "integrity": "sha512-WSZD9jgobAg3ZKuCQZSa3g9QOJeCCqLoLAykiWgmXnDo9EPnn4RPf5qVTtzgOx66o6/oqhcA5tHtJXpG8pMt3g==", "dev": true, "requires": { "cliui": "^7.0.2", diff --git a/client/package.json b/client/package.json index 89c6f51..750e3be 100644 --- a/client/package.json +++ b/client/package.json @@ -10,33 +10,33 @@ }, "private": true, "dependencies": { - "@angular/animations": "~13.2.6", - "@angular/cdk": "^13.2.6", - "@angular/common": "~13.2.6", - "@angular/compiler": "~13.2.6", - "@angular/core": "~13.2.6", + "@angular/animations": "~13.3.5", + "@angular/cdk": "^13.3.5", + "@angular/common": "~13.3.5", + "@angular/compiler": "~13.3.5", + "@angular/core": "~13.3.5", "@angular/flex-layout": "^13.0.0-beta.38", - "@angular/forms": "~13.2.6", - "@angular/platform-browser": "~13.2.6", - "@angular/platform-browser-dynamic": "~13.2.6", - "@angular/router": "~13.2.6", - "@fortawesome/fontawesome-free": "^6.0.0", - "@microsoft/signalr": "^6.0.3", + "@angular/forms": "~13.3.5", + "@angular/platform-browser": "~13.3.5", + "@angular/platform-browser-dynamic": "~13.3.5", + "@angular/router": "~13.3.5", + "@fortawesome/fontawesome-free": "^6.1.1", + "@microsoft/signalr": "^6.0.4", "bulma": "^0.9.3", "curray": "^1.0.8", "file-saver": "^2.0.5", "ngx-filesize": "^2.0.16", "rxjs": "~7.5.5", - "tslib": "^2.3.1", + "tslib": "^2.4.0", "zone.js": "~0.11.5" }, "devDependencies": { - "@angular-devkit/build-angular": "~13.2.6", - "@angular/cli": "~13.2.6", - "@angular/compiler-cli": "~13.2.6", - "@angular/language-service": "~13.2.6", + "@angular-devkit/build-angular": "~13.3.4", + "@angular/cli": "~13.3.4", + "@angular/compiler-cli": "~13.3.5", + "@angular/language-service": "~13.3.5", "@types/file-saver": "^2.0.5", - "@types/node": "^17.0.21", + "@types/node": "^17.0.30", "typescript": "~4.5.5" } } \ No newline at end of file diff --git a/server/RdtClient.Data/Data/DataContext.cs b/server/RdtClient.Data/Data/DataContext.cs index 7ae2032..f6af667 100644 --- a/server/RdtClient.Data/Data/DataContext.cs +++ b/server/RdtClient.Data/Data/DataContext.cs @@ -1,225 +1,221 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using RdtClient.Data.Models.Data; -namespace RdtClient.Data.Data +namespace RdtClient.Data.Data; + +public class DataContext : IdentityDbContext { - public class DataContext : IdentityDbContext + public DataContext(DbContextOptions options) : base(options) { - public DataContext(DbContextOptions options) : base(options) - { - } + } - public DbSet Downloads { get; set; } - public DbSet Settings { get; set; } - public DbSet Torrents { get; set; } + public DbSet Downloads { get; set; } + public DbSet Settings { get; set; } + public DbSet Torrents { get; set; } - protected override void OnConfiguring(DbContextOptionsBuilder options) - { + protected override void OnConfiguring(DbContextOptionsBuilder options) + { - } + } - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); - var cascadeFKs = builder.Model.GetEntityTypes() - .SelectMany(t => t.GetForeignKeys()) - .Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade); + var cascadeFKs = builder.Model.GetEntityTypes() + .SelectMany(t => t.GetForeignKeys()) + .Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade); - foreach (var fk in cascadeFKs) - { - fk.DeleteBehavior = DeleteBehavior.Restrict; - } - } - - public async Task Seed() + foreach (var fk in cascadeFKs) { - var seedSettings = new List + fk.DeleteBehavior = DeleteBehavior.Restrict; + } + } + + public async Task Seed() + { + var seedSettings = new List + { + new Setting { - new Setting - { - SettingId = "Provider", - Type = "String", - Value = "RealDebrid" - }, - new Setting - { - SettingId = "ProviderAutoImport", - Type = "Int32", - Value = "0" - }, - new Setting - { - SettingId = "ProviderAutoImportCategory", - Type = "String", - Value = "" - }, - new Setting - { - SettingId = "ProviderAutoDelete", - Type = "Int32", - Value = "0" - }, - new Setting - { - SettingId = "DeleteOnError", - Type = "Int32", - Value = "0" - }, - new Setting - { - SettingId = "TorrentLifetime", - Type = "Int32", - Value = "0" - }, - new Setting - { - SettingId = "RealDebridApiKey", - Type = "String", - Value = "" - }, - new Setting - { - SettingId = "DownloadPath", - Type = "String", - #if DEBUG - Value = @"C:\Temp\rdtclient" - #else - Value = "/data/downloads" - #endif - }, - new Setting - { - SettingId = "DownloadClient", - Type = "String", - Value = @"Simple" - }, - new Setting - { - SettingId = "TempPath", - Type = "String", + SettingId = "Provider", + Type = "String", + Value = "RealDebrid" + }, + new Setting + { + SettingId = "ProviderAutoImport", + Type = "Int32", + Value = "0" + }, + new Setting + { + SettingId = "ProviderAutoImportCategory", + Type = "String", + Value = "" + }, + new Setting + { + SettingId = "ProviderAutoDelete", + Type = "Int32", + Value = "0" + }, + new Setting + { + SettingId = "DeleteOnError", + Type = "Int32", + Value = "0" + }, + new Setting + { + SettingId = "TorrentLifetime", + Type = "Int32", + Value = "0" + }, + new Setting + { + SettingId = "RealDebridApiKey", + Type = "String", + Value = "" + }, + new Setting + { + SettingId = "DownloadPath", + Type = "String", #if DEBUG - Value = @"C:\Temp\rdtclient" + Value = @"C:\Temp\rdtclient" #else Value = "/data/downloads" #endif - }, - new Setting - { - SettingId = "MappedPath", - Type = "String", + }, + new Setting + { + SettingId = "DownloadClient", + Type = "String", + Value = @"Simple" + }, + new Setting + { + SettingId = "TempPath", + Type = "String", #if DEBUG - Value = @"C:\Temp\rdtclient" + Value = @"C:\Temp\rdtclient" +#else + Value = "/data/downloads" +#endif + }, + new Setting + { + SettingId = "MappedPath", + Type = "String", +#if DEBUG + Value = @"C:\Temp\rdtclient" #else Value = @"C:\Downloads" #endif - }, - new Setting - { - SettingId = "DownloadLimit", - Type = "Int32", - Value = "2" - }, - new Setting - { - SettingId = "UnpackLimit", - Type = "Int32", - Value = "1" - }, - new Setting - { - SettingId = "MinFileSize", - Type = "Int32", - Value = "0" - }, - new Setting - { - SettingId = "OnlyDownloadAvailableFiles", - Type = "Int32", - Value = "1" - }, - new Setting - { - SettingId = "DownloadChunkCount", - Type = "Int32", - Value = "8" - }, - new Setting - { - SettingId = "DownloadMaxSpeed", - Type = "Int32", - Value = "0" - }, - new Setting - { - SettingId = "ProxyServer", - Type = "String", - Value = "" - }, - new Setting - { - SettingId = "LogLevel", - Type = "String", - Value = "Warning" - }, - new Setting - { - SettingId = "Categories", - Type = "String", - Value = "" - }, - new Setting - { - SettingId = "Aria2cUrl", - Type = "String", - Value = "http://127.0.0.1:6800/jsonrpc" - }, - new Setting - { - SettingId = "Aria2cSecret", - Type = "String", - Value = "" - }, - new Setting - { - SettingId = "DownloadRetryAttempts", - Type = "Int32", - Value = "3" - }, - new Setting - { - SettingId = "TorrentRetryAttempts", - Type = "Int32", - Value = "1" - }, - new Setting - { - SettingId = "RunOnTorrentCompleteFileName", - Type = "String", - Value = "" - }, - new Setting - { - SettingId = "RunOnTorrentCompleteArguments", - Type = "String", - Value = "" - } - }; - - var dbSettings = await Settings.ToListAsync(); - foreach (var seedSetting in seedSettings) + }, + new Setting { - var dbSetting = dbSettings.FirstOrDefault(m => m.SettingId == seedSetting.SettingId); + SettingId = "DownloadLimit", + Type = "Int32", + Value = "2" + }, + new Setting + { + SettingId = "UnpackLimit", + Type = "Int32", + Value = "1" + }, + new Setting + { + SettingId = "MinFileSize", + Type = "Int32", + Value = "0" + }, + new Setting + { + SettingId = "OnlyDownloadAvailableFiles", + Type = "Int32", + Value = "1" + }, + new Setting + { + SettingId = "DownloadChunkCount", + Type = "Int32", + Value = "8" + }, + new Setting + { + SettingId = "DownloadMaxSpeed", + Type = "Int32", + Value = "0" + }, + new Setting + { + SettingId = "ProxyServer", + Type = "String", + Value = "" + }, + new Setting + { + SettingId = "LogLevel", + Type = "String", + Value = "Warning" + }, + new Setting + { + SettingId = "Categories", + Type = "String", + Value = "" + }, + new Setting + { + SettingId = "Aria2cUrl", + Type = "String", + Value = "http://127.0.0.1:6800/jsonrpc" + }, + new Setting + { + SettingId = "Aria2cSecret", + Type = "String", + Value = "" + }, + new Setting + { + SettingId = "DownloadRetryAttempts", + Type = "Int32", + Value = "3" + }, + new Setting + { + SettingId = "TorrentRetryAttempts", + Type = "Int32", + Value = "1" + }, + new Setting + { + SettingId = "RunOnTorrentCompleteFileName", + Type = "String", + Value = "" + }, + new Setting + { + SettingId = "RunOnTorrentCompleteArguments", + Type = "String", + Value = "" + } + }; - if (dbSetting == null) - { - await Settings.AddAsync(seedSetting); - await SaveChangesAsync(); - } + var dbSettings = await Settings.ToListAsync(); + foreach (var seedSetting in seedSettings) + { + var dbSetting = dbSettings.FirstOrDefault(m => m.SettingId == seedSetting.SettingId); + + if (dbSetting == null) + { + await Settings.AddAsync(seedSetting); + await SaveChangesAsync(); } } } diff --git a/server/RdtClient.Data/Data/DownloadData.cs b/server/RdtClient.Data/Data/DownloadData.cs index 877a800..5bf1c6f 100644 --- a/server/RdtClient.Data/Data/DownloadData.cs +++ b/server/RdtClient.Data/Data/DownloadData.cs @@ -1,267 +1,262 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Download = RdtClient.Data.Models.Data.Download; -namespace RdtClient.Data.Data +namespace RdtClient.Data.Data; + +public class DownloadData { - public class DownloadData + private readonly DataContext _dataContext; + + public DownloadData(DataContext dataContext) { - private readonly DataContext _dataContext; + _dataContext = dataContext; + } - public DownloadData(DataContext dataContext) + public async Task> GetForTorrent(Guid torrentId) + { + return await _dataContext.Downloads + .AsNoTracking() + .Where(m => m.TorrentId == torrentId) + .ToListAsync(); + } + + public async Task GetById(Guid downloadId) + { + return await _dataContext.Downloads + .Include(m => m.Torrent) + .AsNoTracking() + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + } + + public async Task Get(Guid torrentId, String path) + { + return await _dataContext.Downloads + .Include(m => m.Torrent) + .AsNoTracking() + .FirstOrDefaultAsync(m => m.TorrentId == torrentId && m.Path == path); + } + + public async Task Add(Guid torrentId, String path) + { + var download = new Download { - _dataContext = dataContext; + DownloadId = Guid.NewGuid(), + TorrentId = torrentId, + Path = path, + Added = DateTimeOffset.UtcNow, + DownloadQueued = DateTimeOffset.UtcNow, + RetryCount = 0 + }; + + await _dataContext.Downloads.AddAsync(download); + + await _dataContext.SaveChangesAsync(); + + await TorrentData.VoidCache(); + + return download; + } + + public async Task UpdateUnrestrictedLink(Guid downloadId, String unrestrictedLink) + { + var dbDownload = await _dataContext.Downloads + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + + if (dbDownload == null) + { + return; } - public async Task> GetForTorrent(Guid torrentId) + dbDownload.Link = unrestrictedLink; + + await _dataContext.SaveChangesAsync(); + + await TorrentData.VoidCache(); + } + + public async Task UpdateDownloadStarted(Guid downloadId, DateTimeOffset? dateTime) + { + var dbDownload = await _dataContext.Downloads + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + + if (dbDownload == null) { - return await _dataContext.Downloads - .AsNoTracking() - .Where(m => m.TorrentId == torrentId) - .ToListAsync(); + return; } - public async Task GetById(Guid downloadId) + dbDownload.DownloadStarted = dateTime; + + await _dataContext.SaveChangesAsync(); + + await TorrentData.VoidCache(); + } + + public async Task UpdateDownloadFinished(Guid downloadId, DateTimeOffset? dateTime) + { + var dbDownload = await _dataContext.Downloads + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + + if (dbDownload == null) { - return await _dataContext.Downloads - .Include(m => m.Torrent) - .AsNoTracking() - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + return; } - - public async Task Get(Guid torrentId, String path) - { - return await _dataContext.Downloads - .Include(m => m.Torrent) - .AsNoTracking() - .FirstOrDefaultAsync(m => m.TorrentId == torrentId && m.Path == path); - } - - public async Task Add(Guid torrentId, String path) - { - var download = new Download - { - DownloadId = Guid.NewGuid(), - TorrentId = torrentId, - Path = path, - Added = DateTimeOffset.UtcNow, - DownloadQueued = DateTimeOffset.UtcNow, - RetryCount = 0 - }; - - await _dataContext.Downloads.AddAsync(download); - - await _dataContext.SaveChangesAsync(); - - await TorrentData.VoidCache(); - - return download; - } - - public async Task UpdateUnrestrictedLink(Guid downloadId, String unrestrictedLink) - { - var dbDownload = await _dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); - - if (dbDownload == null) - { - return; - } - - dbDownload.Link = unrestrictedLink; - - await _dataContext.SaveChangesAsync(); - - await TorrentData.VoidCache(); - } - - public async Task UpdateDownloadStarted(Guid downloadId, DateTimeOffset? dateTime) - { - var dbDownload = await _dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); - - if (dbDownload == null) - { - return; - } - - dbDownload.DownloadStarted = dateTime; - - await _dataContext.SaveChangesAsync(); - - await TorrentData.VoidCache(); - } - - public async Task UpdateDownloadFinished(Guid downloadId, DateTimeOffset? dateTime) - { - var dbDownload = await _dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); - - if (dbDownload == null) - { - return; - } - dbDownload.DownloadFinished = dateTime; + dbDownload.DownloadFinished = dateTime; - await _dataContext.SaveChangesAsync(); + await _dataContext.SaveChangesAsync(); - await TorrentData.VoidCache(); - } + await TorrentData.VoidCache(); + } - public async Task UpdateUnpackingQueued(Guid downloadId, DateTimeOffset? dateTime) + public async Task UpdateUnpackingQueued(Guid downloadId, DateTimeOffset? dateTime) + { + var dbDownload = await _dataContext.Downloads + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + + if (dbDownload == null) { - var dbDownload = await _dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); - - if (dbDownload == null) - { - return; - } - - dbDownload.UnpackingQueued = dateTime; - - await _dataContext.SaveChangesAsync(); - - await TorrentData.VoidCache(); + return; } - public async Task UpdateUnpackingStarted(Guid downloadId, DateTimeOffset? dateTime) + dbDownload.UnpackingQueued = dateTime; + + await _dataContext.SaveChangesAsync(); + + await TorrentData.VoidCache(); + } + + public async Task UpdateUnpackingStarted(Guid downloadId, DateTimeOffset? dateTime) + { + var dbDownload = await _dataContext.Downloads + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + + if (dbDownload == null) { - var dbDownload = await _dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); - - if (dbDownload == null) - { - return; - } - - dbDownload.UnpackingStarted = dateTime; - - await _dataContext.SaveChangesAsync(); - - await TorrentData.VoidCache(); + return; } - public async Task UpdateUnpackingFinished(Guid downloadId, DateTimeOffset? dateTime) + dbDownload.UnpackingStarted = dateTime; + + await _dataContext.SaveChangesAsync(); + + await TorrentData.VoidCache(); + } + + public async Task UpdateUnpackingFinished(Guid downloadId, DateTimeOffset? dateTime) + { + var dbDownload = await _dataContext.Downloads + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + + if (dbDownload == null) { - var dbDownload = await _dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); - - if (dbDownload == null) - { - return; - } - - dbDownload.UnpackingFinished = dateTime; - - await _dataContext.SaveChangesAsync(); - - await TorrentData.VoidCache(); + return; } + + dbDownload.UnpackingFinished = dateTime; + + await _dataContext.SaveChangesAsync(); + + await TorrentData.VoidCache(); + } - public async Task UpdateCompleted(Guid downloadId, DateTimeOffset? dateTime) + public async Task UpdateCompleted(Guid downloadId, DateTimeOffset? dateTime) + { + var dbDownload = await _dataContext.Downloads + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + + if (dbDownload == null) { - var dbDownload = await _dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); - - if (dbDownload == null) - { - return; - } - - dbDownload.Completed = dateTime; - - await _dataContext.SaveChangesAsync(); - - await TorrentData.VoidCache(); + return; } - public async Task UpdateError(Guid downloadId, String error) + dbDownload.Completed = dateTime; + + await _dataContext.SaveChangesAsync(); + + await TorrentData.VoidCache(); + } + + public async Task UpdateError(Guid downloadId, String error) + { + var dbDownload = await _dataContext.Downloads + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + + if (dbDownload == null) { - var dbDownload = await _dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); - - if (dbDownload == null) - { - return; - } - - dbDownload.Error = error; - - await _dataContext.SaveChangesAsync(); - - await TorrentData.VoidCache(); + return; } - public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount) + dbDownload.Error = error; + + await _dataContext.SaveChangesAsync(); + + await TorrentData.VoidCache(); + } + + public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount) + { + var dbDownload = await _dataContext.Downloads + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + + if (dbDownload == null) { - var dbDownload = await _dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); - - if (dbDownload == null) - { - return; - } - - dbDownload.RetryCount = retryCount; - - await _dataContext.SaveChangesAsync(); - - await TorrentData.VoidCache(); + return; } - public async Task UpdateRemoteId(Guid downloadId, String remoteId) + dbDownload.RetryCount = retryCount; + + await _dataContext.SaveChangesAsync(); + + await TorrentData.VoidCache(); + } + + public async Task UpdateRemoteId(Guid downloadId, String remoteId) + { + var dbDownload = await _dataContext.Downloads + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + + if (dbDownload == null) { - var dbDownload = await _dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); - - if (dbDownload == null) - { - return; - } - - dbDownload.RemoteId = remoteId; - - await _dataContext.SaveChangesAsync(); + return; } - public async Task DeleteForTorrent(Guid torrentId) - { - var downloads = await _dataContext.Downloads - .Where(m => m.TorrentId == torrentId) - .ToListAsync(); + dbDownload.RemoteId = remoteId; - _dataContext.Downloads.RemoveRange(downloads); + await _dataContext.SaveChangesAsync(); + } - await _dataContext.SaveChangesAsync(); + public async Task DeleteForTorrent(Guid torrentId) + { + var downloads = await _dataContext.Downloads + .Where(m => m.TorrentId == torrentId) + .ToListAsync(); - await TorrentData.VoidCache(); - } + _dataContext.Downloads.RemoveRange(downloads); - public async Task Reset(Guid downloadId) - { - var dbDownload = await _dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + await _dataContext.SaveChangesAsync(); - dbDownload.RetryCount = 0; - dbDownload.Link = null; - dbDownload.Added = DateTimeOffset.UtcNow; - dbDownload.DownloadQueued = DateTimeOffset.UtcNow; - dbDownload.DownloadStarted = null; - dbDownload.DownloadFinished = null; - dbDownload.UnpackingQueued = null; - dbDownload.UnpackingStarted = null; - dbDownload.UnpackingFinished = null; - dbDownload.Completed = null; - dbDownload.Error = null; + await TorrentData.VoidCache(); + } - await _dataContext.SaveChangesAsync(); + public async Task Reset(Guid downloadId) + { + var dbDownload = await _dataContext.Downloads + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); - await TorrentData.VoidCache(); - } + dbDownload.RetryCount = 0; + dbDownload.Link = null; + dbDownload.Added = DateTimeOffset.UtcNow; + dbDownload.DownloadQueued = DateTimeOffset.UtcNow; + dbDownload.DownloadStarted = null; + dbDownload.DownloadFinished = null; + dbDownload.UnpackingQueued = null; + dbDownload.UnpackingStarted = null; + dbDownload.UnpackingFinished = null; + dbDownload.Completed = null; + dbDownload.Error = null; + + await _dataContext.SaveChangesAsync(); + + await TorrentData.VoidCache(); } } \ No newline at end of file diff --git a/server/RdtClient.Data/Data/SettingData.cs b/server/RdtClient.Data/Data/SettingData.cs index 38ee9b9..722c4c8 100644 --- a/server/RdtClient.Data/Data/SettingData.cs +++ b/server/RdtClient.Data/Data/SettingData.cs @@ -1,138 +1,132 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using RdtClient.Data.Models.Data; using RdtClient.Data.Models.Internal; using Serilog; -namespace RdtClient.Data.Data +namespace RdtClient.Data.Data; + +public class SettingData { - public class SettingData + private static readonly SemaphoreSlim _settingCacheLock = new(1); + + private readonly DataContext _dataContext; + + public SettingData(DataContext dataContext) { - private static readonly SemaphoreSlim _settingCacheLock = new(1); + _dataContext = dataContext; + } - private readonly DataContext _dataContext; + public static DbSettings Get { get; private set; } - public SettingData(DataContext dataContext) + public async Task ResetCache() + { + var allSettings = await _dataContext.Settings.AsNoTracking().ToListAsync(); + + String GetString(String name) { - _dataContext = dataContext; + return allSettings.FirstOrDefault(m => m.SettingId == name)?.Value; } - public static DbSettings Get { get; private set; } - - public async Task ResetCache() + Int32 GetInt32(String name) { - var allSettings = await _dataContext.Settings.AsNoTracking().ToListAsync(); + var strVal = GetString(name); - String GetString(String name) + if (!Int32.TryParse(strVal, out var intVal)) { - return allSettings.FirstOrDefault(m => m.SettingId == name)?.Value; + Log.Error("Unable to parse setting {name} to Int32", name); + return 0; } - Int32 GetInt32(String name) - { - var strVal = GetString(name); + return intVal; - if (!Int32.TryParse(strVal, out var intVal)) + } + + Get = new DbSettings + { + Provider = GetString("Provider"), + ProviderAutoImport = GetInt32("ProviderAutoImport"), + ProviderAutoImportCategory = GetString("ProviderAutoImportCategory"), + ProviderAutoDelete = GetInt32("ProviderAutoDelete"), + RealDebridApiKey = GetString("RealDebridApiKey"), + DownloadPath = GetString("DownloadPath"), + DownloadClient = GetString("DownloadClient"), + TempPath = GetString("TempPath"), + MappedPath = GetString("MappedPath"), + DownloadLimit = GetInt32("DownloadLimit"), + UnpackLimit = GetInt32("UnpackLimit"), + MinFileSize = GetInt32("MinFileSize"), + OnlyDownloadAvailableFiles = GetInt32("OnlyDownloadAvailableFiles"), + DownloadChunkCount = GetInt32("DownloadChunkCount"), + DownloadMaxSpeed = GetInt32("DownloadMaxSpeed"), + ProxyServer = GetString("ProxyServer"), + LogLevel = GetString("LogLevel"), + Categories = GetString("Categories"), + Aria2cUrl = GetString("Aria2cUrl"), + Aria2cSecret = GetString("Aria2cSecret"), + DownloadRetryAttempts = GetInt32("DownloadRetryAttempts"), + TorrentRetryAttempts = GetInt32("TorrentRetryAttempts"), + DeleteOnError = GetInt32("DeleteOnError"), + TorrentLifetime = GetInt32("TorrentLifetime"), + RunOnTorrentCompleteFileName = GetString("RunOnTorrentCompleteFileName"), + RunOnTorrentCompleteArguments = GetString("RunOnTorrentCompleteArguments") + }; + } + + public async Task> GetAll() + { + return await _dataContext.Settings.AsNoTracking().ToListAsync(); + } + + public async Task Update(IList settings) + { + await _settingCacheLock.WaitAsync(); + + try + { + var dbSettings = await _dataContext.Settings.ToListAsync(); + + foreach (var dbSetting in dbSettings) + { + var setting = settings.FirstOrDefault(m => m.SettingId == dbSetting.SettingId); + + if (setting != null) { - Log.Error("Unable to parse setting {name} to Int32", name); - return 0; + dbSetting.Value = setting.Value; } - - return intVal; - } - Get = new DbSettings - { - Provider = GetString("Provider"), - ProviderAutoImport = GetInt32("ProviderAutoImport"), - ProviderAutoImportCategory = GetString("ProviderAutoImportCategory"), - ProviderAutoDelete = GetInt32("ProviderAutoDelete"), - RealDebridApiKey = GetString("RealDebridApiKey"), - DownloadPath = GetString("DownloadPath"), - DownloadClient = GetString("DownloadClient"), - TempPath = GetString("TempPath"), - MappedPath = GetString("MappedPath"), - DownloadLimit = GetInt32("DownloadLimit"), - UnpackLimit = GetInt32("UnpackLimit"), - MinFileSize = GetInt32("MinFileSize"), - OnlyDownloadAvailableFiles = GetInt32("OnlyDownloadAvailableFiles"), - DownloadChunkCount = GetInt32("DownloadChunkCount"), - DownloadMaxSpeed = GetInt32("DownloadMaxSpeed"), - ProxyServer = GetString("ProxyServer"), - LogLevel = GetString("LogLevel"), - Categories = GetString("Categories"), - Aria2cUrl = GetString("Aria2cUrl"), - Aria2cSecret = GetString("Aria2cSecret"), - DownloadRetryAttempts = GetInt32("DownloadRetryAttempts"), - TorrentRetryAttempts = GetInt32("TorrentRetryAttempts"), - DeleteOnError = GetInt32("DeleteOnError"), - TorrentLifetime = GetInt32("TorrentLifetime"), - RunOnTorrentCompleteFileName = GetString("RunOnTorrentCompleteFileName"), - RunOnTorrentCompleteArguments = GetString("RunOnTorrentCompleteArguments") - }; + await _dataContext.SaveChangesAsync(); + + await ResetCache(); } - - public async Task> GetAll() + finally { - return await _dataContext.Settings.AsNoTracking().ToListAsync(); - } - - public async Task Update(IList settings) - { - await _settingCacheLock.WaitAsync(); - - try - { - var dbSettings = await _dataContext.Settings.ToListAsync(); - - foreach (var dbSetting in dbSettings) - { - var setting = settings.FirstOrDefault(m => m.SettingId == dbSetting.SettingId); - - if (setting != null) - { - dbSetting.Value = setting.Value; - } - } - - await _dataContext.SaveChangesAsync(); - - await ResetCache(); - } - finally - { - _settingCacheLock.Release(); - } - } - - public async Task UpdateString(String key, String value) - { - await _settingCacheLock.WaitAsync(); - - try - { - var dbSetting = await _dataContext.Settings.FirstOrDefaultAsync(m => m.SettingId == key); - - if (dbSetting == null) - { - throw new Exception($"Cannot find setting with key {key}"); - } - - dbSetting.Value = value; - - await _dataContext.SaveChangesAsync(); - - await ResetCache(); - } - finally - { - _settingCacheLock.Release(); - } + _settingCacheLock.Release(); } } -} + + public async Task UpdateString(String key, String value) + { + await _settingCacheLock.WaitAsync(); + + try + { + var dbSetting = await _dataContext.Settings.FirstOrDefaultAsync(m => m.SettingId == key); + + if (dbSetting == null) + { + throw new Exception($"Cannot find setting with key {key}"); + } + + dbSetting.Value = value; + + await _dataContext.SaveChangesAsync(); + + await ResetCache(); + } + finally + { + _settingCacheLock.Release(); + } + } +} \ No newline at end of file diff --git a/server/RdtClient.Data/Data/TorrentData.cs b/server/RdtClient.Data/Data/TorrentData.cs index 1d01669..0191e66 100644 --- a/server/RdtClient.Data/Data/TorrentData.cs +++ b/server/RdtClient.Data/Data/TorrentData.cs @@ -1,316 +1,310 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using RdtClient.Data.Models.Data; -namespace RdtClient.Data.Data +namespace RdtClient.Data.Data; + +public class TorrentData { - public class TorrentData + private static IList _torrentCache; + private static readonly SemaphoreSlim TorrentCacheLock = new(1, 1); + + private readonly DataContext _dataContext; + + public TorrentData(DataContext dataContext) { - private static IList _torrentCache; - private static readonly SemaphoreSlim TorrentCacheLock = new(1, 1); + _dataContext = dataContext; + } - private readonly DataContext _dataContext; + public async Task> Get() + { + await TorrentCacheLock.WaitAsync(); - public TorrentData(DataContext dataContext) + try { - _dataContext = dataContext; + _torrentCache ??= await _dataContext.Torrents + .AsNoTracking() + .Include(m => m.Downloads) + .ToListAsync(); + + return _torrentCache.OrderBy(m => m.Priority ?? 9999).ThenBy(m => m.Added).ToList(); } - - public async Task> Get() + finally { - await TorrentCacheLock.WaitAsync(); - - try - { - _torrentCache ??= await _dataContext.Torrents - .AsNoTracking() - .Include(m => m.Downloads) - .ToListAsync(); - - return _torrentCache.OrderBy(m => m.Priority ?? 9999).ThenBy(m => m.Added).ToList(); - } - finally - { - TorrentCacheLock.Release(); - } - } - - public async Task GetById(Guid torrentId) - { - var dbTorrent = await _dataContext.Torrents - .AsNoTracking() - .Include(m => m.Downloads) - .FirstOrDefaultAsync(m => m.TorrentId == torrentId); - - if (dbTorrent == null) - { - return null; - } - - foreach (var file in dbTorrent.Downloads) - { - file.Torrent = null; - } - - return dbTorrent; - } - - public async Task GetByHash(String hash) - { - var dbTorrent = await _dataContext.Torrents - .AsNoTracking() - .Include(m => m.Downloads) - .FirstOrDefaultAsync(m => m.Hash.ToLower() == hash.ToLower()); - - if (dbTorrent == null) - { - return null; - } - - foreach (var file in dbTorrent.Downloads) - { - file.Torrent = null; - } - - return dbTorrent; - } - - public async Task Add(String realDebridId, - String hash, - String fileOrMagnetContents, - Boolean isFile, - Torrent torrent) - { - var newTorrent = new Torrent - { - TorrentId = Guid.NewGuid(), - Added = DateTimeOffset.UtcNow, - RdId = realDebridId, - Hash = hash.ToLower(), - Category = torrent.Category, - DownloadAction = torrent.DownloadAction, - FinishedAction = torrent.FinishedAction, - DownloadMinSize = torrent.DownloadMinSize, - DownloadManualFiles = torrent.DownloadManualFiles, - FileOrMagnet = fileOrMagnetContents, - IsFile = isFile, - Priority = torrent.Priority, - TorrentRetryAttempts = torrent.TorrentRetryAttempts, - DownloadRetryAttempts = torrent.DownloadRetryAttempts, - DeleteOnError = torrent.DeleteOnError, - Lifetime = torrent.Lifetime - }; - - await _dataContext.Torrents.AddAsync(newTorrent); - - await _dataContext.SaveChangesAsync(); - - await VoidCache(); - - return newTorrent; - } - - public async Task UpdateRdData(Torrent torrent) - { - var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrent.TorrentId); - - if (dbTorrent == null) - { - return; - } - - dbTorrent.RdName = torrent.RdName; - dbTorrent.RdSize = torrent.RdSize; - dbTorrent.RdHost = torrent.RdHost; - dbTorrent.RdSplit = torrent.RdSplit; - dbTorrent.RdProgress = torrent.RdProgress; - dbTorrent.RdStatus = torrent.RdStatus; - dbTorrent.RdStatusRaw = torrent.RdStatusRaw; - dbTorrent.RdAdded = torrent.RdAdded; - dbTorrent.RdEnded = torrent.RdEnded; - dbTorrent.RdSpeed = torrent.RdSpeed; - dbTorrent.RdSeeders = torrent.RdSeeders; - - if (torrent.Files != null) - { - dbTorrent.RdFiles = torrent.RdFiles; - } - - await _dataContext.SaveChangesAsync(); - - await VoidCache(); - } - - public async Task Update(Torrent torrent) - { - var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrent.TorrentId); - - if (dbTorrent == null) - { - return; - } - - dbTorrent.Priority = torrent.Priority; - dbTorrent.DownloadRetryAttempts = torrent.DownloadRetryAttempts; - dbTorrent.TorrentRetryAttempts = torrent.TorrentRetryAttempts; - dbTorrent.DeleteOnError = torrent.DeleteOnError; - dbTorrent.Lifetime = torrent.Lifetime; - - await _dataContext.SaveChangesAsync(); - - await VoidCache(); - } - - public async Task UpdateCategory(Guid torrentId, String category) - { - var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId); - - if (dbTorrent == null) - { - return; - } - - dbTorrent.Category = category; - - await _dataContext.SaveChangesAsync(); - - await VoidCache(); - } - - public async Task UpdateComplete(Guid torrentId, String error, DateTimeOffset? datetime, Boolean retry) - { - var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId); - - if (dbTorrent == null) - { - return; - } - - if (String.IsNullOrWhiteSpace(error)) - { - var downloads = await _dataContext.Downloads.AsNoTracking().Where(m => m.TorrentId == torrentId).ToListAsync(); - var downloadWithErrors = downloads.Where(m => !String.IsNullOrWhiteSpace(m.Error)).ToList(); - - if (downloadWithErrors.Any()) - { - error = $"{downloadWithErrors.Count}/{downloads.Count} downloads failed with errors"; - } - } - - if (!String.IsNullOrWhiteSpace(error) && retry) - { - if (dbTorrent.RetryCount < dbTorrent.TorrentRetryAttempts) - { - dbTorrent.RetryCount += 1; - dbTorrent.Retry = DateTime.UtcNow; - } - } - - dbTorrent.Completed = datetime; - dbTorrent.Error = error; - - await _dataContext.SaveChangesAsync(); - - await VoidCache(); - } - - public async Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime) - { - var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId); - - if (dbTorrent == null) - { - return; - } - - dbTorrent.FilesSelected = datetime; - - await _dataContext.SaveChangesAsync(); - - await VoidCache(); - } - - public async Task UpdatePriority(Guid torrentId, Int32? priority) - { - var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId); - - if (dbTorrent == null) - { - return; - } - - dbTorrent.Priority = priority; - - await _dataContext.SaveChangesAsync(); - - await VoidCache(); - } - - public async Task UpdateRetry(Guid torrentId, DateTimeOffset? dateTime, Int32 retryCount) - { - var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId); - - if (dbTorrent == null) - { - return; - } - - dbTorrent.RetryCount = retryCount; - dbTorrent.Retry = dateTime; - - await _dataContext.SaveChangesAsync(); - - await VoidCache(); - } - - public async Task UpdateError(Guid torrentId, String error) - { - var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId); - - if (dbTorrent == null) - { - return; - } - - dbTorrent.Error = error; - - await _dataContext.SaveChangesAsync(); - - await VoidCache(); - } - - public async Task Delete(Guid torrentId) - { - var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId); - - if (dbTorrent == null) - { - return; - } - - _dataContext.Torrents.Remove(dbTorrent); - - await _dataContext.SaveChangesAsync(); - - await VoidCache(); - } - - public static async Task VoidCache() - { - await TorrentCacheLock.WaitAsync(); - - try - { - _torrentCache = null; - } - finally - { - TorrentCacheLock.Release(); - } + TorrentCacheLock.Release(); } } -} + + public async Task GetById(Guid torrentId) + { + var dbTorrent = await _dataContext.Torrents + .AsNoTracking() + .Include(m => m.Downloads) + .FirstOrDefaultAsync(m => m.TorrentId == torrentId); + + if (dbTorrent == null) + { + return null; + } + + foreach (var file in dbTorrent.Downloads) + { + file.Torrent = null; + } + + return dbTorrent; + } + + public async Task GetByHash(String hash) + { + var dbTorrent = await _dataContext.Torrents + .AsNoTracking() + .Include(m => m.Downloads) + .FirstOrDefaultAsync(m => m.Hash.ToLower() == hash.ToLower()); + + if (dbTorrent == null) + { + return null; + } + + foreach (var file in dbTorrent.Downloads) + { + file.Torrent = null; + } + + return dbTorrent; + } + + public async Task Add(String realDebridId, + String hash, + String fileOrMagnetContents, + Boolean isFile, + Torrent torrent) + { + var newTorrent = new Torrent + { + TorrentId = Guid.NewGuid(), + Added = DateTimeOffset.UtcNow, + RdId = realDebridId, + Hash = hash.ToLower(), + Category = torrent.Category, + DownloadAction = torrent.DownloadAction, + FinishedAction = torrent.FinishedAction, + DownloadMinSize = torrent.DownloadMinSize, + DownloadManualFiles = torrent.DownloadManualFiles, + FileOrMagnet = fileOrMagnetContents, + IsFile = isFile, + Priority = torrent.Priority, + TorrentRetryAttempts = torrent.TorrentRetryAttempts, + DownloadRetryAttempts = torrent.DownloadRetryAttempts, + DeleteOnError = torrent.DeleteOnError, + Lifetime = torrent.Lifetime + }; + + await _dataContext.Torrents.AddAsync(newTorrent); + + await _dataContext.SaveChangesAsync(); + + await VoidCache(); + + return newTorrent; + } + + public async Task UpdateRdData(Torrent torrent) + { + var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrent.TorrentId); + + if (dbTorrent == null) + { + return; + } + + dbTorrent.RdName = torrent.RdName; + dbTorrent.RdSize = torrent.RdSize; + dbTorrent.RdHost = torrent.RdHost; + dbTorrent.RdSplit = torrent.RdSplit; + dbTorrent.RdProgress = torrent.RdProgress; + dbTorrent.RdStatus = torrent.RdStatus; + dbTorrent.RdStatusRaw = torrent.RdStatusRaw; + dbTorrent.RdAdded = torrent.RdAdded; + dbTorrent.RdEnded = torrent.RdEnded; + dbTorrent.RdSpeed = torrent.RdSpeed; + dbTorrent.RdSeeders = torrent.RdSeeders; + + if (torrent.Files != null) + { + dbTorrent.RdFiles = torrent.RdFiles; + } + + await _dataContext.SaveChangesAsync(); + + await VoidCache(); + } + + public async Task Update(Torrent torrent) + { + var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrent.TorrentId); + + if (dbTorrent == null) + { + return; + } + + dbTorrent.Priority = torrent.Priority; + dbTorrent.DownloadRetryAttempts = torrent.DownloadRetryAttempts; + dbTorrent.TorrentRetryAttempts = torrent.TorrentRetryAttempts; + dbTorrent.DeleteOnError = torrent.DeleteOnError; + dbTorrent.Lifetime = torrent.Lifetime; + + await _dataContext.SaveChangesAsync(); + + await VoidCache(); + } + + public async Task UpdateCategory(Guid torrentId, String category) + { + var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId); + + if (dbTorrent == null) + { + return; + } + + dbTorrent.Category = category; + + await _dataContext.SaveChangesAsync(); + + await VoidCache(); + } + + public async Task UpdateComplete(Guid torrentId, String error, DateTimeOffset? datetime, Boolean retry) + { + var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId); + + if (dbTorrent == null) + { + return; + } + + if (String.IsNullOrWhiteSpace(error)) + { + var downloads = await _dataContext.Downloads.AsNoTracking().Where(m => m.TorrentId == torrentId).ToListAsync(); + var downloadWithErrors = downloads.Where(m => !String.IsNullOrWhiteSpace(m.Error)).ToList(); + + if (downloadWithErrors.Any()) + { + error = $"{downloadWithErrors.Count}/{downloads.Count} downloads failed with errors"; + } + } + + if (!String.IsNullOrWhiteSpace(error) && retry) + { + if (dbTorrent.RetryCount < dbTorrent.TorrentRetryAttempts) + { + dbTorrent.RetryCount += 1; + dbTorrent.Retry = DateTime.UtcNow; + } + } + + dbTorrent.Completed = datetime; + dbTorrent.Error = error; + + await _dataContext.SaveChangesAsync(); + + await VoidCache(); + } + + public async Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime) + { + var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId); + + if (dbTorrent == null) + { + return; + } + + dbTorrent.FilesSelected = datetime; + + await _dataContext.SaveChangesAsync(); + + await VoidCache(); + } + + public async Task UpdatePriority(Guid torrentId, Int32? priority) + { + var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId); + + if (dbTorrent == null) + { + return; + } + + dbTorrent.Priority = priority; + + await _dataContext.SaveChangesAsync(); + + await VoidCache(); + } + + public async Task UpdateRetry(Guid torrentId, DateTimeOffset? dateTime, Int32 retryCount) + { + var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId); + + if (dbTorrent == null) + { + return; + } + + dbTorrent.RetryCount = retryCount; + dbTorrent.Retry = dateTime; + + await _dataContext.SaveChangesAsync(); + + await VoidCache(); + } + + public async Task UpdateError(Guid torrentId, String error) + { + var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId); + + if (dbTorrent == null) + { + return; + } + + dbTorrent.Error = error; + + await _dataContext.SaveChangesAsync(); + + await VoidCache(); + } + + public async Task Delete(Guid torrentId) + { + var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId); + + if (dbTorrent == null) + { + return; + } + + _dataContext.Torrents.Remove(dbTorrent); + + await _dataContext.SaveChangesAsync(); + + await VoidCache(); + } + + public static async Task VoidCache() + { + await TorrentCacheLock.WaitAsync(); + + try + { + _torrentCache = null; + } + finally + { + TorrentCacheLock.Release(); + } + } +} \ No newline at end of file diff --git a/server/RdtClient.Data/Data/UserData.cs b/server/RdtClient.Data/Data/UserData.cs index 0ddbb9e..d1d2964 100644 --- a/server/RdtClient.Data/Data/UserData.cs +++ b/server/RdtClient.Data/Data/UserData.cs @@ -1,21 +1,19 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; -namespace RdtClient.Data.Data +namespace RdtClient.Data.Data; + +public class UserData { - public class UserData + private readonly DataContext _dataContext; + + public UserData(DataContext dataContext) { - private readonly DataContext _dataContext; - - public UserData(DataContext dataContext) - { - _dataContext = dataContext; - } - - public async Task GetUser() - { - return await _dataContext.Users.FirstOrDefaultAsync(); - } + _dataContext = dataContext; } -} + + public async Task GetUser() + { + return await _dataContext.Users.FirstOrDefaultAsync(); + } +} \ No newline at end of file diff --git a/server/RdtClient.Data/DiConfig.cs b/server/RdtClient.Data/DiConfig.cs index c96f6ca..5d27e6c 100644 --- a/server/RdtClient.Data/DiConfig.cs +++ b/server/RdtClient.Data/DiConfig.cs @@ -1,16 +1,20 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; using RdtClient.Data.Data; +using RdtClient.Data.Models.Internal; -namespace RdtClient.Data +namespace RdtClient.Data; + +public static class DiConfig { - public static class DiConfig + public static void Config(IServiceCollection services, AppSettings appSettings) { - public static void Config(IServiceCollection services) - { - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - } + var connectionString = $"Data Source={appSettings.Database.Path}"; + services.AddDbContext(options => options.UseSqlite(connectionString)); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } -} +} \ No newline at end of file diff --git a/server/RdtClient.Data/Enums/TorrentDownloadAction.cs b/server/RdtClient.Data/Enums/TorrentDownloadAction.cs index 7210567..40a4094 100644 --- a/server/RdtClient.Data/Enums/TorrentDownloadAction.cs +++ b/server/RdtClient.Data/Enums/TorrentDownloadAction.cs @@ -1,9 +1,8 @@ -namespace RdtClient.Data.Enums +namespace RdtClient.Data.Enums; + +public enum TorrentDownloadAction { - public enum TorrentDownloadAction - { - DownloadAll = 0, - DownloadAvailableFiles = 1, - DownloadManual = 2 - } -} + DownloadAll = 0, + DownloadAvailableFiles = 1, + DownloadManual = 2 +} \ No newline at end of file diff --git a/server/RdtClient.Data/Enums/TorrentFinishedAction.cs b/server/RdtClient.Data/Enums/TorrentFinishedAction.cs index 2643419..777c605 100644 --- a/server/RdtClient.Data/Enums/TorrentFinishedAction.cs +++ b/server/RdtClient.Data/Enums/TorrentFinishedAction.cs @@ -1,9 +1,8 @@ -namespace RdtClient.Data.Enums +namespace RdtClient.Data.Enums; + +public enum TorrentFinishedAction { - public enum TorrentFinishedAction - { - None = 0, - RemoveAllTorrents = 1, - RemoveRealDebrid = 2 - } -} + None = 0, + RemoveAllTorrents = 1, + RemoveRealDebrid = 2 +} \ No newline at end of file diff --git a/server/RdtClient.Data/Enums/TorrentStatus.cs b/server/RdtClient.Data/Enums/TorrentStatus.cs index db40839..d4de133 100644 --- a/server/RdtClient.Data/Enums/TorrentStatus.cs +++ b/server/RdtClient.Data/Enums/TorrentStatus.cs @@ -1,13 +1,12 @@ -namespace RdtClient.Data.Enums -{ - public enum TorrentStatus - { - Processing = 0, - WaitingForFileSelection = 1, - Downloading = 2, - Finished = 3, - Uploading = 4, +namespace RdtClient.Data.Enums; - Error = 99 - } -} +public enum TorrentStatus +{ + Processing = 0, + WaitingForFileSelection = 1, + Downloading = 2, + Finished = 3, + Uploading = 4, + + Error = 99 +} \ No newline at end of file diff --git a/server/RdtClient.Data/Models/Data/Download.cs b/server/RdtClient.Data/Models/Data/Download.cs index 101dc1e..7de058b 100644 --- a/server/RdtClient.Data/Models/Data/Download.cs +++ b/server/RdtClient.Data/Models/Data/Download.cs @@ -1,44 +1,42 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace RdtClient.Data.Models.Data +namespace RdtClient.Data.Models.Data; + +public class Download { - public class Download - { - [Key] - public Guid DownloadId { get; set; } + [Key] + public Guid DownloadId { get; set; } - public Guid TorrentId { get; set; } + public Guid TorrentId { get; set; } - [ForeignKey("TorrentId")] - public Torrent Torrent { get; set; } + [ForeignKey("TorrentId")] + public Torrent Torrent { get; set; } - public String Path { get; set; } - public String Link { get; set; } + public String Path { get; set; } + public String Link { get; set; } - public DateTimeOffset Added { get; set; } - public DateTimeOffset? DownloadQueued { get; set; } - public DateTimeOffset? DownloadStarted { get; set; } - public DateTimeOffset? DownloadFinished { get; set; } - public DateTimeOffset? UnpackingQueued { get; set; } - public DateTimeOffset? UnpackingStarted { get; set; } - public DateTimeOffset? UnpackingFinished { get; set; } - public DateTimeOffset? Completed { get; set; } + public DateTimeOffset Added { get; set; } + public DateTimeOffset? DownloadQueued { get; set; } + public DateTimeOffset? DownloadStarted { get; set; } + public DateTimeOffset? DownloadFinished { get; set; } + public DateTimeOffset? UnpackingQueued { get; set; } + public DateTimeOffset? UnpackingStarted { get; set; } + public DateTimeOffset? UnpackingFinished { get; set; } + public DateTimeOffset? Completed { get; set; } - public Int32 RetryCount { get; set; } + public Int32 RetryCount { get; set; } - public String Error { get; set; } + public String Error { get; set; } - public String RemoteId { get; set; } + public String RemoteId { get; set; } - [NotMapped] - public Int64 BytesTotal { get; set; } + [NotMapped] + public Int64 BytesTotal { get; set; } - [NotMapped] - public Int64 BytesDone { get; set; } + [NotMapped] + public Int64 BytesDone { get; set; } - [NotMapped] - public Int64 Speed { get; set; } - } -} + [NotMapped] + public Int64 Speed { get; set; } +} \ No newline at end of file diff --git a/server/RdtClient.Data/Models/Data/Setting.cs b/server/RdtClient.Data/Models/Data/Setting.cs index 1a50983..5f92987 100644 --- a/server/RdtClient.Data/Models/Data/Setting.cs +++ b/server/RdtClient.Data/Models/Data/Setting.cs @@ -1,15 +1,13 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; -namespace RdtClient.Data.Models.Data +namespace RdtClient.Data.Models.Data; + +public class Setting { - public class Setting - { - [Key] - public String SettingId { get; set; } + [Key] + public String SettingId { get; set; } - public String Value { get; set; } + public String Value { get; set; } - public String Type { get; set; } - } -} + public String Type { get; set; } +} \ No newline at end of file diff --git a/server/RdtClient.Data/Models/Data/Torrent.cs b/server/RdtClient.Data/Models/Data/Torrent.cs index ba59835..bdc9480 100644 --- a/server/RdtClient.Data/Models/Data/Torrent.cs +++ b/server/RdtClient.Data/Models/Data/Torrent.cs @@ -1,94 +1,91 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json; using RdtClient.Data.Enums; using RdtClient.Data.Models.TorrentClient; -namespace RdtClient.Data.Models.Data +namespace RdtClient.Data.Models.Data; + +public class Torrent { - public class Torrent - { - [Key] - public Guid TorrentId { get; set; } + [Key] + public Guid TorrentId { get; set; } - public String Hash { get; set; } + public String Hash { get; set; } - public String Category { get; set; } + public String Category { get; set; } - public TorrentDownloadAction DownloadAction { get; set; } - public TorrentFinishedAction FinishedAction { get; set; } - public Int32 DownloadMinSize { get; set; } - public String DownloadManualFiles { get; set; } + public TorrentDownloadAction DownloadAction { get; set; } + public TorrentFinishedAction FinishedAction { get; set; } + public Int32 DownloadMinSize { get; set; } + public String DownloadManualFiles { get; set; } - public DateTimeOffset Added { get; set; } - public DateTimeOffset? FilesSelected { get; set; } - public DateTimeOffset? Completed { get; set; } - public DateTimeOffset? Retry { get; set; } + public DateTimeOffset Added { get; set; } + public DateTimeOffset? FilesSelected { get; set; } + public DateTimeOffset? Completed { get; set; } + public DateTimeOffset? Retry { get; set; } - public String FileOrMagnet { get; set; } - public Boolean IsFile { get; set; } + public String FileOrMagnet { get; set; } + public Boolean IsFile { get; set; } - public Int32? Priority { get; set; } - public Int32 RetryCount { get; set; } - public Int32 DownloadRetryAttempts { get; set; } - public Int32 TorrentRetryAttempts { get; set; } - public Int32 DeleteOnError { get; set; } - public Int32 Lifetime { get; set; } + public Int32? Priority { get; set; } + public Int32 RetryCount { get; set; } + public Int32 DownloadRetryAttempts { get; set; } + public Int32 TorrentRetryAttempts { get; set; } + public Int32 DeleteOnError { get; set; } + public Int32 Lifetime { get; set; } - public String Error { get; set; } + public String Error { get; set; } - [InverseProperty("Torrent")] - public IList Downloads { get; set; } + [InverseProperty("Torrent")] + public IList Downloads { get; set; } - public String RdId { get; set; } - public String RdName { get; set; } - public Int64 RdSize { get; set; } - public String RdHost { get; set; } - public Int64 RdSplit { get; set; } - public Int64 RdProgress { get; set; } - public TorrentStatus RdStatus { get; set; } - public String RdStatusRaw { get; set; } - public DateTimeOffset RdAdded { get; set; } - public DateTimeOffset? RdEnded { get; set; } - public Int64? RdSpeed { get; set; } - public Int64? RdSeeders { get; set; } - public String RdFiles { get; set; } + public String RdId { get; set; } + public String RdName { get; set; } + public Int64 RdSize { get; set; } + public String RdHost { get; set; } + public Int64 RdSplit { get; set; } + public Int64 RdProgress { get; set; } + public TorrentStatus RdStatus { get; set; } + public String RdStatusRaw { get; set; } + public DateTimeOffset RdAdded { get; set; } + public DateTimeOffset? RdEnded { get; set; } + public Int64? RdSpeed { get; set; } + public Int64? RdSeeders { get; set; } + public String RdFiles { get; set; } - [NotMapped] - public IList Files + [NotMapped] + public IList Files + { + get { - get + if (String.IsNullOrWhiteSpace(RdFiles)) { - if (String.IsNullOrWhiteSpace(RdFiles)) - { - return new List(); - } - - try - { - return JsonSerializer.Deserialize>(RdFiles); - } - catch - { - return new List(); - } + return new List(); } - } - [NotMapped] - public IList ManualFiles - { - get + try { - if (String.IsNullOrWhiteSpace(DownloadManualFiles)) - { - return new List(); - } - - return DownloadManualFiles.Split(","); + return JsonSerializer.Deserialize>(RdFiles); + } + catch + { + return new List(); } } } -} + + [NotMapped] + public IList ManualFiles + { + get + { + if (String.IsNullOrWhiteSpace(DownloadManualFiles)) + { + return new List(); + } + + return DownloadManualFiles.Split(","); + } + } +} \ No newline at end of file diff --git a/server/RdtClient.Data/Models/Internal/AppSettings.cs b/server/RdtClient.Data/Models/Internal/AppSettings.cs index 6620a97..fe03ef2 100644 --- a/server/RdtClient.Data/Models/Internal/AppSettings.cs +++ b/server/RdtClient.Data/Models/Internal/AppSettings.cs @@ -1,31 +1,26 @@ -using System; +namespace RdtClient.Data.Models.Internal; -namespace RdtClient.Data.Models.Internal +public class AppSettings { - public class AppSettings - { - public AppSettingsLogging Logging { get; set; } - public AppSettingsDatabase Database { get; set; } + public AppSettingsLogging Logging { get; set; } + public AppSettingsDatabase Database { get; set; } - public String AllowedHosts { get; set; } - public String HostUrl { get; set; } - } - - public class AppSettingsLogging - { - public AppSettingsLoggingFile File { get; set; } - } - - public class AppSettingsLoggingFile - { - public String Path { get; set; } - public String Append { get; set; } - public Int64 FileSizeLimitBytes { get; set; } - public Int32 MaxRollingFiles { get; set; } - } - - public class AppSettingsDatabase - { - public String Path { get; set; } - } + public Int32 Port { get; set; } } + +public class AppSettingsLogging +{ + public AppSettingsLoggingFile File { get; set; } +} + +public class AppSettingsLoggingFile +{ + public String Path { get; set; } + public Int64 FileSizeLimitBytes { get; set; } + public Int32 MaxRollingFiles { get; set; } +} + +public class AppSettingsDatabase +{ + public String Path { get; set; } +} \ No newline at end of file diff --git a/server/RdtClient.Data/Models/Internal/DbSettings.cs b/server/RdtClient.Data/Models/Internal/DbSettings.cs index cf71517..fadb390 100644 --- a/server/RdtClient.Data/Models/Internal/DbSettings.cs +++ b/server/RdtClient.Data/Models/Internal/DbSettings.cs @@ -1,34 +1,31 @@ -using System; +namespace RdtClient.Data.Models.Internal; -namespace RdtClient.Data.Models.Internal +public class DbSettings { - public class DbSettings - { - public String Provider { get; set; } - public Int32 ProviderAutoImport { get; set; } - public String ProviderAutoImportCategory { get; set; } - public Int32 ProviderAutoDelete { get; set; } - public String RealDebridApiKey { get; set; } - public String DownloadPath { get; set; } - public String DownloadClient { get; set; } - public String TempPath { get; set; } - public String MappedPath { get; set; } - public Int32 DownloadLimit { get; set; } - public Int32 UnpackLimit { get; set; } - public Int32 MinFileSize { get; set; } - public Int32 OnlyDownloadAvailableFiles { get; set; } - public Int32 DownloadChunkCount { get; set; } - public Int32 DownloadMaxSpeed { get; set; } - public String ProxyServer { get; set; } - public String LogLevel { get; set; } - public String Categories { get; set; } - public String Aria2cUrl { get; set; } - public String Aria2cSecret { get; set; } - public Int32 DownloadRetryAttempts { get; set; } - public Int32 TorrentRetryAttempts { get; set; } - public Int32 DeleteOnError { get; set; } - public Int32 TorrentLifetime { get; set; } - public String RunOnTorrentCompleteFileName { get; set; } - public String RunOnTorrentCompleteArguments { get; set; } - } -} + public String Provider { get; set; } + public Int32 ProviderAutoImport { get; set; } + public String ProviderAutoImportCategory { get; set; } + public Int32 ProviderAutoDelete { get; set; } + public String RealDebridApiKey { get; set; } + public String DownloadPath { get; set; } + public String DownloadClient { get; set; } + public String TempPath { get; set; } + public String MappedPath { get; set; } + public Int32 DownloadLimit { get; set; } + public Int32 UnpackLimit { get; set; } + public Int32 MinFileSize { get; set; } + public Int32 OnlyDownloadAvailableFiles { get; set; } + public Int32 DownloadChunkCount { get; set; } + public Int32 DownloadMaxSpeed { get; set; } + public String ProxyServer { get; set; } + public String LogLevel { get; set; } + public String Categories { get; set; } + public String Aria2cUrl { get; set; } + public String Aria2cSecret { get; set; } + public Int32 DownloadRetryAttempts { get; set; } + public Int32 TorrentRetryAttempts { get; set; } + public Int32 DeleteOnError { get; set; } + public Int32 TorrentLifetime { get; set; } + public String RunOnTorrentCompleteFileName { get; set; } + public String RunOnTorrentCompleteArguments { get; set; } +} \ No newline at end of file diff --git a/server/RdtClient.Data/Models/Internal/Profile.cs b/server/RdtClient.Data/Models/Internal/Profile.cs index 79007d0..e440a88 100644 --- a/server/RdtClient.Data/Models/Internal/Profile.cs +++ b/server/RdtClient.Data/Models/Internal/Profile.cs @@ -1,13 +1,10 @@ -using System; +namespace RdtClient.Data.Models.Internal; -namespace RdtClient.Data.Models.Internal +public class Profile { - public class Profile - { - public String Provider { get; set; } - public String UserName { get; set; } - public DateTimeOffset? Expiration { get; set; } - public String CurrentVersion { get; set; } - public String LatestVersion { get; set; } - } -} + public String Provider { get; set; } + public String UserName { get; set; } + public DateTimeOffset? Expiration { get; set; } + public String CurrentVersion { get; set; } + public String LatestVersion { get; set; } +} \ No newline at end of file diff --git a/server/RdtClient.Data/Models/QBittorrent/AppBuildInfo.cs b/server/RdtClient.Data/Models/QBittorrent/AppBuildInfo.cs index 85e94f9..6fb8e44 100644 --- a/server/RdtClient.Data/Models/QBittorrent/AppBuildInfo.cs +++ b/server/RdtClient.Data/Models/QBittorrent/AppBuildInfo.cs @@ -1,26 +1,24 @@ -using System; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; -namespace RdtClient.Data.Models.QBittorrent +namespace RdtClient.Data.Models.QBittorrent; + +public class AppBuildInfo { - public class AppBuildInfo - { - [JsonPropertyName("bitness")] - public Int64 Bitness { get; set; } + [JsonPropertyName("bitness")] + public Int64 Bitness { get; set; } - [JsonPropertyName("boost")] - public String Boost { get; set; } + [JsonPropertyName("boost")] + public String Boost { get; set; } - [JsonPropertyName("libtorrent")] - public String Libtorrent { get; set; } + [JsonPropertyName("libtorrent")] + public String Libtorrent { get; set; } - [JsonPropertyName("openssl")] - public String Openssl { get; set; } + [JsonPropertyName("openssl")] + public String Openssl { get; set; } - [JsonPropertyName("qt")] - public String Qt { get; set; } + [JsonPropertyName("qt")] + public String Qt { get; set; } - [JsonPropertyName("zlib")] - public String Zlib { get; set; } - } + [JsonPropertyName("zlib")] + public String Zlib { get; set; } } \ No newline at end of file diff --git a/server/RdtClient.Data/Models/QBittorrent/AppPreferences.cs b/server/RdtClient.Data/Models/QBittorrent/AppPreferences.cs index f9b62b2..2d761ea 100644 --- a/server/RdtClient.Data/Models/QBittorrent/AppPreferences.cs +++ b/server/RdtClient.Data/Models/QBittorrent/AppPreferences.cs @@ -1,435 +1,430 @@ -using System; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; -namespace RdtClient.Data.Models.QBittorrent +namespace RdtClient.Data.Models.QBittorrent; + +public class AppPreferences { - namespace QuickType - { - public class AppPreferences - { - [JsonPropertyName("add_trackers")] - public String AddTrackers { get; set; } + [JsonPropertyName("add_trackers")] + public String AddTrackers { get; set; } - [JsonPropertyName("add_trackers_enabled")] - public Boolean AddTrackersEnabled { get; set; } + [JsonPropertyName("add_trackers_enabled")] + public Boolean AddTrackersEnabled { get; set; } - [JsonPropertyName("alt_dl_limit")] - public Int64 AltDlLimit { get; set; } + [JsonPropertyName("alt_dl_limit")] + public Int64 AltDlLimit { get; set; } - [JsonPropertyName("alt_up_limit")] - public Int64 AltUpLimit { get; set; } + [JsonPropertyName("alt_up_limit")] + public Int64 AltUpLimit { get; set; } - [JsonPropertyName("alternative_webui_enabled")] - public Boolean AlternativeWebuiEnabled { get; set; } + [JsonPropertyName("alternative_webui_enabled")] + public Boolean AlternativeWebuiEnabled { get; set; } - [JsonPropertyName("alternative_webui_path")] - public String AlternativeWebuiPath { get; set; } + [JsonPropertyName("alternative_webui_path")] + public String AlternativeWebuiPath { get; set; } - [JsonPropertyName("announce_ip")] - public String AnnounceIp { get; set; } + [JsonPropertyName("announce_ip")] + public String AnnounceIp { get; set; } - [JsonPropertyName("announce_to_all_tiers")] - public Boolean AnnounceToAllTiers { get; set; } + [JsonPropertyName("announce_to_all_tiers")] + public Boolean AnnounceToAllTiers { get; set; } - [JsonPropertyName("announce_to_all_trackers")] - public Boolean AnnounceToAllTrackers { get; set; } + [JsonPropertyName("announce_to_all_trackers")] + public Boolean AnnounceToAllTrackers { get; set; } - [JsonPropertyName("anonymous_mode")] - public Boolean AnonymousMode { get; set; } + [JsonPropertyName("anonymous_mode")] + public Boolean AnonymousMode { get; set; } - [JsonPropertyName("async_io_threads")] - public Int64 AsyncIoThreads { get; set; } + [JsonPropertyName("async_io_threads")] + public Int64 AsyncIoThreads { get; set; } - [JsonPropertyName("auto_delete_mode")] - public Int64 AutoDeleteMode { get; set; } + [JsonPropertyName("auto_delete_mode")] + public Int64 AutoDeleteMode { get; set; } - [JsonPropertyName("auto_tmm_enabled")] - public Boolean AutoTmmEnabled { get; set; } + [JsonPropertyName("auto_tmm_enabled")] + public Boolean AutoTmmEnabled { get; set; } - [JsonPropertyName("autorun_enabled")] - public Boolean AutorunEnabled { get; set; } + [JsonPropertyName("autorun_enabled")] + public Boolean AutorunEnabled { get; set; } - [JsonPropertyName("autorun_program")] - public String AutorunProgram { get; set; } + [JsonPropertyName("autorun_program")] + public String AutorunProgram { get; set; } - [JsonPropertyName("banned_IPs")] - public String BannedIPs { get; set; } + [JsonPropertyName("banned_IPs")] + public String BannedIPs { get; set; } - [JsonPropertyName("bittorrent_protocol")] - public Int64 BittorrentProtocol { get; set; } + [JsonPropertyName("bittorrent_protocol")] + public Int64 BittorrentProtocol { get; set; } - [JsonPropertyName("bypass_auth_subnet_whitelist")] - public String BypassAuthSubnetWhitelist { get; set; } + [JsonPropertyName("bypass_auth_subnet_whitelist")] + public String BypassAuthSubnetWhitelist { get; set; } - [JsonPropertyName("bypass_auth_subnet_whitelist_enabled")] - public Boolean BypassAuthSubnetWhitelistEnabled { get; set; } + [JsonPropertyName("bypass_auth_subnet_whitelist_enabled")] + public Boolean BypassAuthSubnetWhitelistEnabled { get; set; } - [JsonPropertyName("bypass_local_auth")] - public Boolean BypassLocalAuth { get; set; } + [JsonPropertyName("bypass_local_auth")] + public Boolean BypassLocalAuth { get; set; } - [JsonPropertyName("category_changed_tmm_enabled")] - public Boolean CategoryChangedTmmEnabled { get; set; } + [JsonPropertyName("category_changed_tmm_enabled")] + public Boolean CategoryChangedTmmEnabled { get; set; } - [JsonPropertyName("checking_memory_use")] - public Int64 CheckingMemoryUse { get; set; } + [JsonPropertyName("checking_memory_use")] + public Int64 CheckingMemoryUse { get; set; } - [JsonPropertyName("create_subfolder_enabled")] - public Boolean CreateSubfolderEnabled { get; set; } + [JsonPropertyName("create_subfolder_enabled")] + public Boolean CreateSubfolderEnabled { get; set; } - [JsonPropertyName("current_interface_address")] - public String CurrentInterfaceAddress { get; set; } + [JsonPropertyName("current_interface_address")] + public String CurrentInterfaceAddress { get; set; } - [JsonPropertyName("current_network_interface")] - public String CurrentNetworkInterface { get; set; } + [JsonPropertyName("current_network_interface")] + public String CurrentNetworkInterface { get; set; } - [JsonPropertyName("dht")] - public Boolean Dht { get; set; } + [JsonPropertyName("dht")] + public Boolean Dht { get; set; } - [JsonPropertyName("disk_cache")] - public Int64 DiskCache { get; set; } + [JsonPropertyName("disk_cache")] + public Int64 DiskCache { get; set; } - [JsonPropertyName("disk_cache_ttl")] - public Int64 DiskCacheTtl { get; set; } + [JsonPropertyName("disk_cache_ttl")] + public Int64 DiskCacheTtl { get; set; } - [JsonPropertyName("dl_limit")] - public Int64 DlLimit { get; set; } + [JsonPropertyName("dl_limit")] + public Int64 DlLimit { get; set; } - [JsonPropertyName("dont_count_slow_torrents")] - public Boolean DontCountSlowTorrents { get; set; } + [JsonPropertyName("dont_count_slow_torrents")] + public Boolean DontCountSlowTorrents { get; set; } - [JsonPropertyName("dyndns_domain")] - public String DyndnsDomain { get; set; } + [JsonPropertyName("dyndns_domain")] + public String DyndnsDomain { get; set; } - [JsonPropertyName("dyndns_enabled")] - public Boolean DyndnsEnabled { get; set; } + [JsonPropertyName("dyndns_enabled")] + public Boolean DyndnsEnabled { get; set; } - [JsonPropertyName("dyndns_password")] - public String DyndnsPassword { get; set; } + [JsonPropertyName("dyndns_password")] + public String DyndnsPassword { get; set; } - [JsonPropertyName("dyndns_service")] - public Int64 DyndnsService { get; set; } + [JsonPropertyName("dyndns_service")] + public Int64 DyndnsService { get; set; } - [JsonPropertyName("dyndns_username")] - public String DyndnsUsername { get; set; } + [JsonPropertyName("dyndns_username")] + public String DyndnsUsername { get; set; } - [JsonPropertyName("embedded_tracker_port")] - public Int64 EmbeddedTrackerPort { get; set; } + [JsonPropertyName("embedded_tracker_port")] + public Int64 EmbeddedTrackerPort { get; set; } - [JsonPropertyName("enable_coalesce_read_write")] - public Boolean EnableCoalesceReadWrite { get; set; } + [JsonPropertyName("enable_coalesce_read_write")] + public Boolean EnableCoalesceReadWrite { get; set; } - [JsonPropertyName("enable_embedded_tracker")] - public Boolean EnableEmbeddedTracker { get; set; } + [JsonPropertyName("enable_embedded_tracker")] + public Boolean EnableEmbeddedTracker { get; set; } - [JsonPropertyName("enable_multi_connections_from_same_ip")] - public Boolean EnableMultiConnectionsFromSameIp { get; set; } + [JsonPropertyName("enable_multi_connections_from_same_ip")] + public Boolean EnableMultiConnectionsFromSameIp { get; set; } - [JsonPropertyName("enable_os_cache")] - public Boolean EnableOsCache { get; set; } + [JsonPropertyName("enable_os_cache")] + public Boolean EnableOsCache { get; set; } - [JsonPropertyName("enable_piece_extent_affinity")] - public Boolean EnablePieceExtentAffinity { get; set; } + [JsonPropertyName("enable_piece_extent_affinity")] + public Boolean EnablePieceExtentAffinity { get; set; } - [JsonPropertyName("enable_super_seeding")] - public Boolean EnableSuperSeeding { get; set; } + [JsonPropertyName("enable_super_seeding")] + public Boolean EnableSuperSeeding { get; set; } - [JsonPropertyName("enable_upload_suggestions")] - public Boolean EnableUploadSuggestions { get; set; } + [JsonPropertyName("enable_upload_suggestions")] + public Boolean EnableUploadSuggestions { get; set; } - [JsonPropertyName("encryption")] - public Int64 Encryption { get; set; } + [JsonPropertyName("encryption")] + public Int64 Encryption { get; set; } - [JsonPropertyName("export_dir")] - public String ExportDir { get; set; } + [JsonPropertyName("export_dir")] + public String ExportDir { get; set; } - [JsonPropertyName("export_dir_fin")] - public String ExportDirFin { get; set; } + [JsonPropertyName("export_dir_fin")] + public String ExportDirFin { get; set; } - [JsonPropertyName("file_pool_size")] - public Int64 FilePoolSize { get; set; } + [JsonPropertyName("file_pool_size")] + public Int64 FilePoolSize { get; set; } - [JsonPropertyName("incomplete_files_ext")] - public Boolean IncompleteFilesExt { get; set; } + [JsonPropertyName("incomplete_files_ext")] + public Boolean IncompleteFilesExt { get; set; } - [JsonPropertyName("ip_filter_enabled")] - public Boolean IpFilterEnabled { get; set; } + [JsonPropertyName("ip_filter_enabled")] + public Boolean IpFilterEnabled { get; set; } - [JsonPropertyName("ip_filter_path")] - public String IpFilterPath { get; set; } + [JsonPropertyName("ip_filter_path")] + public String IpFilterPath { get; set; } - [JsonPropertyName("ip_filter_trackers")] - public Boolean IpFilterTrackers { get; set; } + [JsonPropertyName("ip_filter_trackers")] + public Boolean IpFilterTrackers { get; set; } - [JsonPropertyName("limit_lan_peers")] - public Boolean LimitLanPeers { get; set; } + [JsonPropertyName("limit_lan_peers")] + public Boolean LimitLanPeers { get; set; } - [JsonPropertyName("limit_tcp_overhead")] - public Boolean LimitTcpOverhead { get; set; } + [JsonPropertyName("limit_tcp_overhead")] + public Boolean LimitTcpOverhead { get; set; } - [JsonPropertyName("limit_utp_rate")] - public Boolean LimitUtpRate { get; set; } + [JsonPropertyName("limit_utp_rate")] + public Boolean LimitUtpRate { get; set; } - [JsonPropertyName("listen_port")] - public Int64 ListenPort { get; set; } + [JsonPropertyName("listen_port")] + public Int64 ListenPort { get; set; } - [JsonPropertyName("locale")] - public String Locale { get; set; } + [JsonPropertyName("locale")] + public String Locale { get; set; } - [JsonPropertyName("lsd")] - public Boolean Lsd { get; set; } + [JsonPropertyName("lsd")] + public Boolean Lsd { get; set; } - [JsonPropertyName("mail_notification_auth_enabled")] - public Boolean MailNotificationAuthEnabled { get; set; } + [JsonPropertyName("mail_notification_auth_enabled")] + public Boolean MailNotificationAuthEnabled { get; set; } - [JsonPropertyName("mail_notification_email")] - public String MailNotificationEmail { get; set; } + [JsonPropertyName("mail_notification_email")] + public String MailNotificationEmail { get; set; } - [JsonPropertyName("mail_notification_enabled")] - public Boolean MailNotificationEnabled { get; set; } + [JsonPropertyName("mail_notification_enabled")] + public Boolean MailNotificationEnabled { get; set; } - [JsonPropertyName("mail_notification_password")] - public String MailNotificationPassword { get; set; } + [JsonPropertyName("mail_notification_password")] + public String MailNotificationPassword { get; set; } - [JsonPropertyName("mail_notification_sender")] - public String MailNotificationSender { get; set; } + [JsonPropertyName("mail_notification_sender")] + public String MailNotificationSender { get; set; } - [JsonPropertyName("mail_notification_smtp")] - public String MailNotificationSmtp { get; set; } + [JsonPropertyName("mail_notification_smtp")] + public String MailNotificationSmtp { get; set; } - [JsonPropertyName("mail_notification_ssl_enabled")] - public Boolean MailNotificationSslEnabled { get; set; } + [JsonPropertyName("mail_notification_ssl_enabled")] + public Boolean MailNotificationSslEnabled { get; set; } - [JsonPropertyName("mail_notification_username")] - public String MailNotificationUsername { get; set; } + [JsonPropertyName("mail_notification_username")] + public String MailNotificationUsername { get; set; } - [JsonPropertyName("max_active_downloads")] - public Int64 MaxActiveDownloads { get; set; } + [JsonPropertyName("max_active_downloads")] + public Int64 MaxActiveDownloads { get; set; } - [JsonPropertyName("max_active_torrents")] - public Int64 MaxActiveTorrents { get; set; } + [JsonPropertyName("max_active_torrents")] + public Int64 MaxActiveTorrents { get; set; } - [JsonPropertyName("max_active_uploads")] - public Int64 MaxActiveUploads { get; set; } + [JsonPropertyName("max_active_uploads")] + public Int64 MaxActiveUploads { get; set; } - [JsonPropertyName("max_connec")] - public Int64 MaxConnec { get; set; } + [JsonPropertyName("max_connec")] + public Int64 MaxConnec { get; set; } - [JsonPropertyName("max_connec_per_torrent")] - public Int64 MaxConnecPerTorrent { get; set; } + [JsonPropertyName("max_connec_per_torrent")] + public Int64 MaxConnecPerTorrent { get; set; } - [JsonPropertyName("max_ratio")] - public Int64 MaxRatio { get; set; } + [JsonPropertyName("max_ratio")] + public Int64 MaxRatio { get; set; } - [JsonPropertyName("max_ratio_act")] - public Int64 MaxRatioAct { get; set; } + [JsonPropertyName("max_ratio_act")] + public Int64 MaxRatioAct { get; set; } - [JsonPropertyName("max_ratio_enabled")] - public Boolean MaxRatioEnabled { get; set; } + [JsonPropertyName("max_ratio_enabled")] + public Boolean MaxRatioEnabled { get; set; } - [JsonPropertyName("max_seeding_time")] - public Int64 MaxSeedingTime { get; set; } + [JsonPropertyName("max_seeding_time")] + public Int64 MaxSeedingTime { get; set; } - [JsonPropertyName("max_seeding_time_enabled")] - public Boolean MaxSeedingTimeEnabled { get; set; } + [JsonPropertyName("max_seeding_time_enabled")] + public Boolean MaxSeedingTimeEnabled { get; set; } - [JsonPropertyName("max_uploads")] - public Int64 MaxUploads { get; set; } + [JsonPropertyName("max_uploads")] + public Int64 MaxUploads { get; set; } - [JsonPropertyName("max_uploads_per_torrent")] - public Int64 MaxUploadsPerTorrent { get; set; } + [JsonPropertyName("max_uploads_per_torrent")] + public Int64 MaxUploadsPerTorrent { get; set; } - [JsonPropertyName("outgoing_ports_max")] - public Int64 OutgoingPortsMax { get; set; } + [JsonPropertyName("outgoing_ports_max")] + public Int64 OutgoingPortsMax { get; set; } - [JsonPropertyName("outgoing_ports_min")] - public Int64 OutgoingPortsMin { get; set; } + [JsonPropertyName("outgoing_ports_min")] + public Int64 OutgoingPortsMin { get; set; } - [JsonPropertyName("pex")] - public Boolean Pex { get; set; } + [JsonPropertyName("pex")] + public Boolean Pex { get; set; } - [JsonPropertyName("preallocate_all")] - public Boolean PreallocateAll { get; set; } + [JsonPropertyName("preallocate_all")] + public Boolean PreallocateAll { get; set; } - [JsonPropertyName("proxy_auth_enabled")] - public Boolean ProxyAuthEnabled { get; set; } + [JsonPropertyName("proxy_auth_enabled")] + public Boolean ProxyAuthEnabled { get; set; } - [JsonPropertyName("proxy_ip")] - public String ProxyIp { get; set; } + [JsonPropertyName("proxy_ip")] + public String ProxyIp { get; set; } - [JsonPropertyName("proxy_password")] - public String ProxyPassword { get; set; } + [JsonPropertyName("proxy_password")] + public String ProxyPassword { get; set; } - [JsonPropertyName("proxy_peer_connections")] - public Boolean ProxyPeerConnections { get; set; } + [JsonPropertyName("proxy_peer_connections")] + public Boolean ProxyPeerConnections { get; set; } - [JsonPropertyName("proxy_port")] - public Int64 ProxyPort { get; set; } + [JsonPropertyName("proxy_port")] + public Int64 ProxyPort { get; set; } - [JsonPropertyName("proxy_torrents_only")] - public Boolean ProxyTorrentsOnly { get; set; } + [JsonPropertyName("proxy_torrents_only")] + public Boolean ProxyTorrentsOnly { get; set; } - [JsonPropertyName("proxy_type")] - public Int64 ProxyType { get; set; } + [JsonPropertyName("proxy_type")] + public Int64 ProxyType { get; set; } - [JsonPropertyName("proxy_username")] - public String ProxyUsername { get; set; } + [JsonPropertyName("proxy_username")] + public String ProxyUsername { get; set; } - [JsonPropertyName("queueing_enabled")] - public Boolean QueueingEnabled { get; set; } + [JsonPropertyName("queueing_enabled")] + public Boolean QueueingEnabled { get; set; } - [JsonPropertyName("random_port")] - public Boolean RandomPort { get; set; } + [JsonPropertyName("random_port")] + public Boolean RandomPort { get; set; } - [JsonPropertyName("recheck_completed_torrents")] - public Boolean RecheckCompletedTorrents { get; set; } + [JsonPropertyName("recheck_completed_torrents")] + public Boolean RecheckCompletedTorrents { get; set; } - [JsonPropertyName("resolve_peer_countries")] - public Boolean ResolvePeerCountries { get; set; } + [JsonPropertyName("resolve_peer_countries")] + public Boolean ResolvePeerCountries { get; set; } - [JsonPropertyName("rss_auto_downloading_enabled")] - public Boolean RssAutoDownloadingEnabled { get; set; } + [JsonPropertyName("rss_auto_downloading_enabled")] + public Boolean RssAutoDownloadingEnabled { get; set; } - [JsonPropertyName("rss_max_articles_per_feed")] - public Int64 RssMaxArticlesPerFeed { get; set; } + [JsonPropertyName("rss_max_articles_per_feed")] + public Int64 RssMaxArticlesPerFeed { get; set; } - [JsonPropertyName("rss_processing_enabled")] - public Boolean RssProcessingEnabled { get; set; } + [JsonPropertyName("rss_processing_enabled")] + public Boolean RssProcessingEnabled { get; set; } - [JsonPropertyName("rss_refresh_interval")] - public Int64 RssRefreshInterval { get; set; } + [JsonPropertyName("rss_refresh_interval")] + public Int64 RssRefreshInterval { get; set; } - [JsonPropertyName("save_path")] - public String SavePath { get; set; } + [JsonPropertyName("save_path")] + public String SavePath { get; set; } - [JsonPropertyName("save_path_changed_tmm_enabled")] - public Boolean SavePathChangedTmmEnabled { get; set; } + [JsonPropertyName("save_path_changed_tmm_enabled")] + public Boolean SavePathChangedTmmEnabled { get; set; } - [JsonPropertyName("save_resume_data_interval")] - public Int64 SaveResumeDataInterval { get; set; } + [JsonPropertyName("save_resume_data_interval")] + public Int64 SaveResumeDataInterval { get; set; } - [JsonPropertyName("scan_dirs")] - public ScanDirs ScanDirs { get; set; } + [JsonPropertyName("scan_dirs")] + public ScanDirs ScanDirs { get; set; } - [JsonPropertyName("schedule_from_hour")] - public Int64 ScheduleFromHour { get; set; } + [JsonPropertyName("schedule_from_hour")] + public Int64 ScheduleFromHour { get; set; } - [JsonPropertyName("schedule_from_min")] - public Int64 ScheduleFromMin { get; set; } + [JsonPropertyName("schedule_from_min")] + public Int64 ScheduleFromMin { get; set; } - [JsonPropertyName("schedule_to_hour")] - public Int64 ScheduleToHour { get; set; } + [JsonPropertyName("schedule_to_hour")] + public Int64 ScheduleToHour { get; set; } - [JsonPropertyName("schedule_to_min")] - public Int64 ScheduleToMin { get; set; } + [JsonPropertyName("schedule_to_min")] + public Int64 ScheduleToMin { get; set; } - [JsonPropertyName("scheduler_days")] - public Int64 SchedulerDays { get; set; } + [JsonPropertyName("scheduler_days")] + public Int64 SchedulerDays { get; set; } - [JsonPropertyName("scheduler_enabled")] - public Boolean SchedulerEnabled { get; set; } + [JsonPropertyName("scheduler_enabled")] + public Boolean SchedulerEnabled { get; set; } - [JsonPropertyName("send_buffer_low_watermark")] - public Int64 SendBufferLowWatermark { get; set; } + [JsonPropertyName("send_buffer_low_watermark")] + public Int64 SendBufferLowWatermark { get; set; } - [JsonPropertyName("send_buffer_watermark")] - public Int64 SendBufferWatermark { get; set; } + [JsonPropertyName("send_buffer_watermark")] + public Int64 SendBufferWatermark { get; set; } - [JsonPropertyName("send_buffer_watermark_factor")] - public Int64 SendBufferWatermarkFactor { get; set; } + [JsonPropertyName("send_buffer_watermark_factor")] + public Int64 SendBufferWatermarkFactor { get; set; } - [JsonPropertyName("slow_torrent_dl_rate_threshold")] - public Int64 SlowTorrentDlRateThreshold { get; set; } + [JsonPropertyName("slow_torrent_dl_rate_threshold")] + public Int64 SlowTorrentDlRateThreshold { get; set; } - [JsonPropertyName("slow_torrent_inactive_timer")] - public Int64 SlowTorrentInactiveTimer { get; set; } + [JsonPropertyName("slow_torrent_inactive_timer")] + public Int64 SlowTorrentInactiveTimer { get; set; } - [JsonPropertyName("slow_torrent_ul_rate_threshold")] - public Int64 SlowTorrentUlRateThreshold { get; set; } + [JsonPropertyName("slow_torrent_ul_rate_threshold")] + public Int64 SlowTorrentUlRateThreshold { get; set; } - [JsonPropertyName("socket_backlog_size")] - public Int64 SocketBacklogSize { get; set; } + [JsonPropertyName("socket_backlog_size")] + public Int64 SocketBacklogSize { get; set; } - [JsonPropertyName("start_paused_enabled")] - public Boolean StartPausedEnabled { get; set; } + [JsonPropertyName("start_paused_enabled")] + public Boolean StartPausedEnabled { get; set; } - [JsonPropertyName("stop_tracker_timeout")] - public Int64 StopTrackerTimeout { get; set; } + [JsonPropertyName("stop_tracker_timeout")] + public Int64 StopTrackerTimeout { get; set; } - [JsonPropertyName("temp_path")] - public String TempPath { get; set; } + [JsonPropertyName("temp_path")] + public String TempPath { get; set; } - [JsonPropertyName("temp_path_enabled")] - public Boolean TempPathEnabled { get; set; } + [JsonPropertyName("temp_path_enabled")] + public Boolean TempPathEnabled { get; set; } - [JsonPropertyName("torrent_changed_tmm_enabled")] - public Boolean TorrentChangedTmmEnabled { get; set; } + [JsonPropertyName("torrent_changed_tmm_enabled")] + public Boolean TorrentChangedTmmEnabled { get; set; } - [JsonPropertyName("up_limit")] - public Int64 UpLimit { get; set; } + [JsonPropertyName("up_limit")] + public Int64 UpLimit { get; set; } - [JsonPropertyName("upload_choking_algorithm")] - public Int64 UploadChokingAlgorithm { get; set; } + [JsonPropertyName("upload_choking_algorithm")] + public Int64 UploadChokingAlgorithm { get; set; } - [JsonPropertyName("upload_slots_behavior")] - public Int64 UploadSlotsBehavior { get; set; } + [JsonPropertyName("upload_slots_behavior")] + public Int64 UploadSlotsBehavior { get; set; } - [JsonPropertyName("upnp")] - public Boolean Upnp { get; set; } + [JsonPropertyName("upnp")] + public Boolean Upnp { get; set; } - [JsonPropertyName("upnp_lease_duration")] - public Int64 UpnpLeaseDuration { get; set; } + [JsonPropertyName("upnp_lease_duration")] + public Int64 UpnpLeaseDuration { get; set; } - [JsonPropertyName("use_https")] - public Boolean UseHttps { get; set; } + [JsonPropertyName("use_https")] + public Boolean UseHttps { get; set; } - [JsonPropertyName("utp_tcp_mixed_mode")] - public Int64 UtpTcpMixedMode { get; set; } + [JsonPropertyName("utp_tcp_mixed_mode")] + public Int64 UtpTcpMixedMode { get; set; } - [JsonPropertyName("web_ui_address")] - public String WebUiAddress { get; set; } + [JsonPropertyName("web_ui_address")] + public String WebUiAddress { get; set; } - [JsonPropertyName("web_ui_ban_duration")] - public Int64 WebUiBanDuration { get; set; } + [JsonPropertyName("web_ui_ban_duration")] + public Int64 WebUiBanDuration { get; set; } - [JsonPropertyName("web_ui_clickjacking_protection_enabled")] - public Boolean WebUiClickjackingProtectionEnabled { get; set; } + [JsonPropertyName("web_ui_clickjacking_protection_enabled")] + public Boolean WebUiClickjackingProtectionEnabled { get; set; } - [JsonPropertyName("web_ui_csrf_protection_enabled")] - public Boolean WebUiCsrfProtectionEnabled { get; set; } + [JsonPropertyName("web_ui_csrf_protection_enabled")] + public Boolean WebUiCsrfProtectionEnabled { get; set; } - [JsonPropertyName("web_ui_domain_list")] - public String WebUiDomainList { get; set; } + [JsonPropertyName("web_ui_domain_list")] + public String WebUiDomainList { get; set; } - [JsonPropertyName("web_ui_host_header_validation_enabled")] - public Boolean WebUiHostHeaderValidationEnabled { get; set; } + [JsonPropertyName("web_ui_host_header_validation_enabled")] + public Boolean WebUiHostHeaderValidationEnabled { get; set; } - [JsonPropertyName("web_ui_https_cert_path")] - public String WebUiHttpsCertPath { get; set; } + [JsonPropertyName("web_ui_https_cert_path")] + public String WebUiHttpsCertPath { get; set; } - [JsonPropertyName("web_ui_https_key_path")] - public String WebUiHttpsKeyPath { get; set; } + [JsonPropertyName("web_ui_https_key_path")] + public String WebUiHttpsKeyPath { get; set; } - [JsonPropertyName("web_ui_max_auth_fail_count")] - public Int64 WebUiMaxAuthFailCount { get; set; } + [JsonPropertyName("web_ui_max_auth_fail_count")] + public Int64 WebUiMaxAuthFailCount { get; set; } - [JsonPropertyName("web_ui_port")] - public Int64 WebUiPort { get; set; } + [JsonPropertyName("web_ui_port")] + public Int64 WebUiPort { get; set; } - [JsonPropertyName("web_ui_secure_cookie_enabled")] - public Boolean WebUiSecureCookieEnabled { get; set; } + [JsonPropertyName("web_ui_secure_cookie_enabled")] + public Boolean WebUiSecureCookieEnabled { get; set; } - [JsonPropertyName("web_ui_session_timeout")] - public Int64 WebUiSessionTimeout { get; set; } + [JsonPropertyName("web_ui_session_timeout")] + public Int64 WebUiSessionTimeout { get; set; } - [JsonPropertyName("web_ui_upnp")] - public Boolean WebUiUpnp { get; set; } + [JsonPropertyName("web_ui_upnp")] + public Boolean WebUiUpnp { get; set; } - [JsonPropertyName("web_ui_username")] - public String WebUiUsername { get; set; } - } + [JsonPropertyName("web_ui_username")] + public String WebUiUsername { get; set; } +} - public class ScanDirs - { - } - } +public class ScanDirs +{ } \ No newline at end of file diff --git a/server/RdtClient.Data/Models/QBittorrent/SyncMetaData.cs b/server/RdtClient.Data/Models/QBittorrent/SyncMetaData.cs index 8a43502..43ef650 100644 --- a/server/RdtClient.Data/Models/QBittorrent/SyncMetaData.cs +++ b/server/RdtClient.Data/Models/QBittorrent/SyncMetaData.cs @@ -1,106 +1,102 @@ -using System; -using System.Collections.Generic; -using System.Text.Json.Serialization; -using RdtClient.Data.Models.QBittorrent.QuickType; +using System.Text.Json.Serialization; -namespace RdtClient.Data.Models.QBittorrent +namespace RdtClient.Data.Models.QBittorrent; + +public class SyncMetaData { - public class SyncMetaData - { - [JsonPropertyName("categories")] - public IDictionary Categories { get; set; } + [JsonPropertyName("categories")] + public IDictionary Categories { get; set; } - [JsonPropertyName("full_update")] - public Boolean FullUpdate { get; set; } + [JsonPropertyName("full_update")] + public Boolean FullUpdate { get; set; } - [JsonPropertyName("rid")] - public Int64 Rid { get; set; } + [JsonPropertyName("rid")] + public Int64 Rid { get; set; } - [JsonPropertyName("server_state")] - public SyncMetaDataServerState ServerState { get; set; } + [JsonPropertyName("server_state")] + public SyncMetaDataServerState ServerState { get; set; } - [JsonPropertyName("tags")] - public IList Tags { get; set; } + [JsonPropertyName("tags")] + public IList Tags { get; set; } - [JsonPropertyName("torrents")] - public IDictionary Torrents { get; set; } + [JsonPropertyName("torrents")] + public IDictionary Torrents { get; set; } - [JsonPropertyName("trackers")] - public IDictionary> Trackers { get; set; } - } - - public class SyncMetaDataServerState - { - [JsonPropertyName("alltime_dl")] - public Int64 AlltimeDl { get; set; } - - [JsonPropertyName("alltime_ul")] - public Int64 AlltimeUl { get; set; } - - [JsonPropertyName("average_time_queue")] - public Int64 AverageTimeQueue { get; set; } - - [JsonPropertyName("connection_status")] - public String ConnectionStatus { get; set; } - - [JsonPropertyName("dht_nodes")] - public Int64 DhtNodes { get; set; } - - [JsonPropertyName("dl_info_data")] - public Int64 DlInfoData { get; set; } - - [JsonPropertyName("dl_info_speed")] - public Int64 DlInfoSpeed { get; set; } - - [JsonPropertyName("dl_rate_limit")] - public Int64 DlRateLimit { get; set; } - - [JsonPropertyName("free_space_on_disk")] - public Int64 FreeSpaceOnDisk { get; set; } - - [JsonPropertyName("global_ratio")] - public String GlobalRatio { get; set; } - - [JsonPropertyName("queued_io_jobs")] - public Int64 QueuedIoJobs { get; set; } - - [JsonPropertyName("queueing")] - public Boolean Queueing { get; set; } - - [JsonPropertyName("read_cache_hits")] - public String ReadCacheHits { get; set; } - - [JsonPropertyName("read_cache_overload")] - public String ReadCacheOverload { get; set; } - - [JsonPropertyName("refresh_interval")] - public Int64 RefreshInterval { get; set; } - - [JsonPropertyName("total_buffers_size")] - public Int64 TotalBuffersSize { get; set; } - - [JsonPropertyName("total_peer_connections")] - public Int64 TotalPeerConnections { get; set; } - - [JsonPropertyName("total_queued_size")] - public Int64 TotalQueuedSize { get; set; } - - [JsonPropertyName("total_wasted_session")] - public Int64 TotalWastedSession { get; set; } - - [JsonPropertyName("up_info_data")] - public Int64 UpInfoData { get; set; } - - [JsonPropertyName("up_info_speed")] - public Int64 UpInfoSpeed { get; set; } - - [JsonPropertyName("up_rate_limit")] - public Int64 UpRateLimit { get; set; } - - [JsonPropertyName("use_alt_speed_limits")] - public Boolean UseAltSpeedLimits { get; set; } - - [JsonPropertyName("write_cache_overload")] - public String WriteCacheOverload { get; set; } - } + [JsonPropertyName("trackers")] + public IDictionary> Trackers { get; set; } } + +public class SyncMetaDataServerState +{ + [JsonPropertyName("alltime_dl")] + public Int64 AlltimeDl { get; set; } + + [JsonPropertyName("alltime_ul")] + public Int64 AlltimeUl { get; set; } + + [JsonPropertyName("average_time_queue")] + public Int64 AverageTimeQueue { get; set; } + + [JsonPropertyName("connection_status")] + public String ConnectionStatus { get; set; } + + [JsonPropertyName("dht_nodes")] + public Int64 DhtNodes { get; set; } + + [JsonPropertyName("dl_info_data")] + public Int64 DlInfoData { get; set; } + + [JsonPropertyName("dl_info_speed")] + public Int64 DlInfoSpeed { get; set; } + + [JsonPropertyName("dl_rate_limit")] + public Int64 DlRateLimit { get; set; } + + [JsonPropertyName("free_space_on_disk")] + public Int64 FreeSpaceOnDisk { get; set; } + + [JsonPropertyName("global_ratio")] + public String GlobalRatio { get; set; } + + [JsonPropertyName("queued_io_jobs")] + public Int64 QueuedIoJobs { get; set; } + + [JsonPropertyName("queueing")] + public Boolean Queueing { get; set; } + + [JsonPropertyName("read_cache_hits")] + public String ReadCacheHits { get; set; } + + [JsonPropertyName("read_cache_overload")] + public String ReadCacheOverload { get; set; } + + [JsonPropertyName("refresh_interval")] + public Int64 RefreshInterval { get; set; } + + [JsonPropertyName("total_buffers_size")] + public Int64 TotalBuffersSize { get; set; } + + [JsonPropertyName("total_peer_connections")] + public Int64 TotalPeerConnections { get; set; } + + [JsonPropertyName("total_queued_size")] + public Int64 TotalQueuedSize { get; set; } + + [JsonPropertyName("total_wasted_session")] + public Int64 TotalWastedSession { get; set; } + + [JsonPropertyName("up_info_data")] + public Int64 UpInfoData { get; set; } + + [JsonPropertyName("up_info_speed")] + public Int64 UpInfoSpeed { get; set; } + + [JsonPropertyName("up_rate_limit")] + public Int64 UpRateLimit { get; set; } + + [JsonPropertyName("use_alt_speed_limits")] + public Boolean UseAltSpeedLimits { get; set; } + + [JsonPropertyName("write_cache_overload")] + public String WriteCacheOverload { get; set; } +} \ No newline at end of file diff --git a/server/RdtClient.Data/Models/QBittorrent/TorrentCategory.cs b/server/RdtClient.Data/Models/QBittorrent/TorrentCategory.cs index c7b7da0..7923e50 100644 --- a/server/RdtClient.Data/Models/QBittorrent/TorrentCategory.cs +++ b/server/RdtClient.Data/Models/QBittorrent/TorrentCategory.cs @@ -1,14 +1,12 @@ -using System; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; -namespace RdtClient.Data.Models.QBittorrent +namespace RdtClient.Data.Models.QBittorrent; + +public class TorrentCategory { - public class TorrentCategory - { - [JsonPropertyName("name")] - public String Name { get; set; } + [JsonPropertyName("name")] + public String Name { get; set; } - [JsonPropertyName("savePath")] - public String SavePath { get; set; } - } -} + [JsonPropertyName("savePath")] + public String SavePath { get; set; } +} \ No newline at end of file diff --git a/server/RdtClient.Data/Models/QBittorrent/TorrentFileItem.cs b/server/RdtClient.Data/Models/QBittorrent/TorrentFileItem.cs index 3461d94..cef9dca 100644 --- a/server/RdtClient.Data/Models/QBittorrent/TorrentFileItem.cs +++ b/server/RdtClient.Data/Models/QBittorrent/TorrentFileItem.cs @@ -1,11 +1,9 @@ -using System; using System.Text.Json.Serialization; -namespace RdtClient.Data.Models.QBittorrent +namespace RdtClient.Data.Models.QBittorrent; + +public class TorrentFileItem { - public class TorrentFileItem - { - [JsonPropertyName("name")] - public String Name { get; set; } - } -} + [JsonPropertyName("name")] + public String Name { get; set; } +} \ No newline at end of file diff --git a/server/RdtClient.Data/Models/QBittorrent/TorrentInfo.cs b/server/RdtClient.Data/Models/QBittorrent/TorrentInfo.cs index 8188211..882281c 100644 --- a/server/RdtClient.Data/Models/QBittorrent/TorrentInfo.cs +++ b/server/RdtClient.Data/Models/QBittorrent/TorrentInfo.cs @@ -1,143 +1,138 @@ -using System; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; -namespace RdtClient.Data.Models.QBittorrent +namespace RdtClient.Data.Models.QBittorrent; + +public class TorrentInfo { - namespace QuickType - { - public class TorrentInfo - { - [JsonPropertyName("added_on")] - public Int64 AddedOn { get; set; } + [JsonPropertyName("added_on")] + public Int64 AddedOn { get; set; } - [JsonPropertyName("amount_left")] - public Int64 AmountLeft { get; set; } + [JsonPropertyName("amount_left")] + public Int64 AmountLeft { get; set; } - [JsonPropertyName("auto_tmm")] - public Boolean AutoTmm { get; set; } + [JsonPropertyName("auto_tmm")] + public Boolean AutoTmm { get; set; } - [JsonPropertyName("availability")] - public Decimal Availability { get; set; } + [JsonPropertyName("availability")] + public Decimal Availability { get; set; } - [JsonPropertyName("category")] - public String Category { get; set; } + [JsonPropertyName("category")] + public String Category { get; set; } - [JsonPropertyName("completed")] - public Int64 Completed { get; set; } + [JsonPropertyName("completed")] + public Int64 Completed { get; set; } - [JsonPropertyName("completion_on")] - public Int64? CompletionOn { get; set; } + [JsonPropertyName("completion_on")] + public Int64? CompletionOn { get; set; } - [JsonPropertyName("content_path")] - public String ContentPath { get; set; } + [JsonPropertyName("content_path")] + public String ContentPath { get; set; } - [JsonPropertyName("dl_limit")] - public Int64 DlLimit { get; set; } + [JsonPropertyName("dl_limit")] + public Int64 DlLimit { get; set; } - [JsonPropertyName("dlspeed")] - public Int64 Dlspeed { get; set; } + [JsonPropertyName("dlspeed")] + public Int64 Dlspeed { get; set; } - [JsonPropertyName("downloaded")] - public Int64 Downloaded { get; set; } + [JsonPropertyName("downloaded")] + public Int64 Downloaded { get; set; } - [JsonPropertyName("downloaded_session")] - public Int64 DownloadedSession { get; set; } + [JsonPropertyName("downloaded_session")] + public Int64 DownloadedSession { get; set; } - [JsonPropertyName("eta")] - public Int64 Eta { get; set; } + [JsonPropertyName("eta")] + public Int64 Eta { get; set; } - [JsonPropertyName("f_l_piece_prio")] - public Boolean FlPiecePrio { get; set; } + [JsonPropertyName("f_l_piece_prio")] + public Boolean FlPiecePrio { get; set; } - [JsonPropertyName("force_start")] - public Boolean ForceStart { get; set; } + [JsonPropertyName("force_start")] + public Boolean ForceStart { get; set; } - [JsonPropertyName("hash")] - public String Hash { get; set; } + [JsonPropertyName("hash")] + public String Hash { get; set; } - [JsonPropertyName("last_activity")] - public Int64 LastActivity { get; set; } + [JsonPropertyName("last_activity")] + public Int64 LastActivity { get; set; } - [JsonPropertyName("magnet_uri")] - public String MagnetUri { get; set; } + [JsonPropertyName("magnet_uri")] + public String MagnetUri { get; set; } - [JsonPropertyName("max_ratio")] - public Int64 MaxRatio { get; set; } + [JsonPropertyName("max_ratio")] + public Int64 MaxRatio { get; set; } - [JsonPropertyName("max_seeding_time")] - public Int64 MaxSeedingTime { get; set; } + [JsonPropertyName("max_seeding_time")] + public Int64 MaxSeedingTime { get; set; } - [JsonPropertyName("name")] - public String Name { get; set; } + [JsonPropertyName("name")] + public String Name { get; set; } - [JsonPropertyName("num_complete")] - public Int64 NumComplete { get; set; } + [JsonPropertyName("num_complete")] + public Int64 NumComplete { get; set; } - [JsonPropertyName("num_incomplete")] - public Int64 NumIncomplete { get; set; } + [JsonPropertyName("num_incomplete")] + public Int64 NumIncomplete { get; set; } - [JsonPropertyName("num_leechs")] - public Int64 NumLeechs { get; set; } + [JsonPropertyName("num_leechs")] + public Int64 NumLeechs { get; set; } - [JsonPropertyName("num_seeds")] - public Int64 NumSeeds { get; set; } + [JsonPropertyName("num_seeds")] + public Int64 NumSeeds { get; set; } - [JsonPropertyName("priority")] - public Int64 Priority { get; set; } + [JsonPropertyName("priority")] + public Int64 Priority { get; set; } - [JsonPropertyName("progress")] - public Single Progress { get; set; } + [JsonPropertyName("progress")] + public Single Progress { get; set; } - [JsonPropertyName("ratio")] - public Int64 Ratio { get; set; } + [JsonPropertyName("ratio")] + public Int64 Ratio { get; set; } - [JsonPropertyName("ratio_limit")] - public Int64 RatioLimit { get; set; } + [JsonPropertyName("ratio_limit")] + public Int64 RatioLimit { get; set; } - [JsonPropertyName("save_path")] - public String SavePath { get; set; } + [JsonPropertyName("save_path")] + public String SavePath { get; set; } - [JsonPropertyName("seeding_time_limit")] - public Int64 SeedingTimeLimit { get; set; } + [JsonPropertyName("seeding_time_limit")] + public Int64 SeedingTimeLimit { get; set; } - [JsonPropertyName("seen_complete")] - public Int64 SeenComplete { get; set; } + [JsonPropertyName("seen_complete")] + public Int64 SeenComplete { get; set; } - [JsonPropertyName("seq_dl")] - public Boolean SeqDl { get; set; } + [JsonPropertyName("seq_dl")] + public Boolean SeqDl { get; set; } - [JsonPropertyName("size")] - public Int64 Size { get; set; } + [JsonPropertyName("size")] + public Int64 Size { get; set; } - [JsonPropertyName("state")] - public String State { get; set; } + [JsonPropertyName("state")] + public String State { get; set; } - [JsonPropertyName("super_seeding")] - public Boolean SuperSeeding { get; set; } + [JsonPropertyName("super_seeding")] + public Boolean SuperSeeding { get; set; } - [JsonPropertyName("tags")] - public String Tags { get; set; } + [JsonPropertyName("tags")] + public String Tags { get; set; } - [JsonPropertyName("time_active")] - public Int64 TimeActive { get; set; } + [JsonPropertyName("time_active")] + public Int64 TimeActive { get; set; } - [JsonPropertyName("total_size")] - public Int64 TotalSize { get; set; } + [JsonPropertyName("total_size")] + public Int64 TotalSize { get; set; } - [JsonPropertyName("tracker")] - public String Tracker { get; set; } + [JsonPropertyName("tracker")] + public String Tracker { get; set; } - [JsonPropertyName("up_limit")] - public Int64 UpLimit { get; set; } + [JsonPropertyName("up_limit")] + public Int64 UpLimit { get; set; } - [JsonPropertyName("uploaded")] - public Int64 Uploaded { get; set; } + [JsonPropertyName("uploaded")] + public Int64 Uploaded { get; set; } - [JsonPropertyName("uploaded_session")] - public Int64 UploadedSession { get; set; } + [JsonPropertyName("uploaded_session")] + public Int64 UploadedSession { get; set; } - [JsonPropertyName("upspeed")] - public Int64 Upspeed { get; set; } - } - } + [JsonPropertyName("upspeed")] + public Int64 Upspeed { get; set; } } \ No newline at end of file diff --git a/server/RdtClient.Data/Models/QBittorrent/TorrentProperties.cs b/server/RdtClient.Data/Models/QBittorrent/TorrentProperties.cs index 1bbaaf1..1486966 100644 --- a/server/RdtClient.Data/Models/QBittorrent/TorrentProperties.cs +++ b/server/RdtClient.Data/Models/QBittorrent/TorrentProperties.cs @@ -1,107 +1,105 @@ -using System; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; -namespace RdtClient.Data.Models.QBittorrent +namespace RdtClient.Data.Models.QBittorrent; + +public class TorrentProperties { - public class TorrentProperties - { - [JsonPropertyName("addition_date")] - public Int64 AdditionDate { get; set; } + [JsonPropertyName("addition_date")] + public Int64 AdditionDate { get; set; } - [JsonPropertyName("comment")] - public String Comment { get; set; } + [JsonPropertyName("comment")] + public String Comment { get; set; } - [JsonPropertyName("completion_date")] - public Int64 CompletionDate { get; set; } + [JsonPropertyName("completion_date")] + public Int64 CompletionDate { get; set; } - [JsonPropertyName("created_by")] - public String CreatedBy { get; set; } + [JsonPropertyName("created_by")] + public String CreatedBy { get; set; } - [JsonPropertyName("creation_date")] - public Int64 CreationDate { get; set; } + [JsonPropertyName("creation_date")] + public Int64 CreationDate { get; set; } - [JsonPropertyName("dl_limit")] - public Int64 DlLimit { get; set; } + [JsonPropertyName("dl_limit")] + public Int64 DlLimit { get; set; } - [JsonPropertyName("dl_speed")] - public Int64 DlSpeed { get; set; } + [JsonPropertyName("dl_speed")] + public Int64 DlSpeed { get; set; } - [JsonPropertyName("dl_speed_avg")] - public Int64 DlSpeedAvg { get; set; } + [JsonPropertyName("dl_speed_avg")] + public Int64 DlSpeedAvg { get; set; } - [JsonPropertyName("eta")] - public Int64 Eta { get; set; } + [JsonPropertyName("eta")] + public Int64 Eta { get; set; } - [JsonPropertyName("last_seen")] - public Int64 LastSeen { get; set; } + [JsonPropertyName("last_seen")] + public Int64 LastSeen { get; set; } - [JsonPropertyName("nb_connections")] - public Int64 NbConnections { get; set; } + [JsonPropertyName("nb_connections")] + public Int64 NbConnections { get; set; } - [JsonPropertyName("nb_connections_limit")] - public Int64 NbConnectionsLimit { get; set; } + [JsonPropertyName("nb_connections_limit")] + public Int64 NbConnectionsLimit { get; set; } - [JsonPropertyName("peers")] - public Int64 Peers { get; set; } + [JsonPropertyName("peers")] + public Int64 Peers { get; set; } - [JsonPropertyName("peers_total")] - public Int64 PeersTotal { get; set; } + [JsonPropertyName("peers_total")] + public Int64 PeersTotal { get; set; } - [JsonPropertyName("piece_size")] - public Int64 PieceSize { get; set; } + [JsonPropertyName("piece_size")] + public Int64 PieceSize { get; set; } - [JsonPropertyName("pieces_have")] - public Int64 PiecesHave { get; set; } + [JsonPropertyName("pieces_have")] + public Int64 PiecesHave { get; set; } - [JsonPropertyName("pieces_num")] - public Int64 PiecesNum { get; set; } + [JsonPropertyName("pieces_num")] + public Int64 PiecesNum { get; set; } - [JsonPropertyName("reannounce")] - public Int64 Reannounce { get; set; } + [JsonPropertyName("reannounce")] + public Int64 Reannounce { get; set; } - [JsonPropertyName("save_path")] - public String SavePath { get; set; } + [JsonPropertyName("save_path")] + public String SavePath { get; set; } - [JsonPropertyName("seeding_time")] - public Int64 SeedingTime { get; set; } + [JsonPropertyName("seeding_time")] + public Int64 SeedingTime { get; set; } - [JsonPropertyName("seeds")] - public Int64 Seeds { get; set; } + [JsonPropertyName("seeds")] + public Int64 Seeds { get; set; } - [JsonPropertyName("seeds_total")] - public Int64 SeedsTotal { get; set; } + [JsonPropertyName("seeds_total")] + public Int64 SeedsTotal { get; set; } - [JsonPropertyName("share_ratio")] - public Int64 ShareRatio { get; set; } + [JsonPropertyName("share_ratio")] + public Int64 ShareRatio { get; set; } - [JsonPropertyName("time_elapsed")] - public Int64 TimeElapsed { get; set; } + [JsonPropertyName("time_elapsed")] + public Int64 TimeElapsed { get; set; } - [JsonPropertyName("total_downloaded")] - public Int64 TotalDownloaded { get; set; } + [JsonPropertyName("total_downloaded")] + public Int64 TotalDownloaded { get; set; } - [JsonPropertyName("total_downloaded_session")] - public Int64 TotalDownloadedSession { get; set; } + [JsonPropertyName("total_downloaded_session")] + public Int64 TotalDownloadedSession { get; set; } - [JsonPropertyName("total_size")] - public Int64 TotalSize { get; set; } + [JsonPropertyName("total_size")] + public Int64 TotalSize { get; set; } - [JsonPropertyName("total_uploaded")] - public Int64 TotalUploaded { get; set; } + [JsonPropertyName("total_uploaded")] + public Int64 TotalUploaded { get; set; } - [JsonPropertyName("total_uploaded_session")] - public Int64 TotalUploadedSession { get; set; } + [JsonPropertyName("total_uploaded_session")] + public Int64 TotalUploadedSession { get; set; } - [JsonPropertyName("total_wasted")] - public Int64 TotalWasted { get; set; } + [JsonPropertyName("total_wasted")] + public Int64 TotalWasted { get; set; } - [JsonPropertyName("up_limit")] - public Int64 UpLimit { get; set; } + [JsonPropertyName("up_limit")] + public Int64 UpLimit { get; set; } - [JsonPropertyName("up_speed")] - public Int64 UpSpeed { get; set; } + [JsonPropertyName("up_speed")] + public Int64 UpSpeed { get; set; } - [JsonPropertyName("up_speed_avg")] - public Int64 UpSpeedAvg { get; set; } - } + [JsonPropertyName("up_speed_avg")] + public Int64 UpSpeedAvg { get; set; } } \ No newline at end of file diff --git a/server/RdtClient.Data/Models/TorrentClient/TorrentClientAvailableFile.cs b/server/RdtClient.Data/Models/TorrentClient/TorrentClientAvailableFile.cs index 822f4ef..77bab25 100644 --- a/server/RdtClient.Data/Models/TorrentClient/TorrentClientAvailableFile.cs +++ b/server/RdtClient.Data/Models/TorrentClient/TorrentClientAvailableFile.cs @@ -1,11 +1,8 @@ -using System; +namespace RdtClient.Data.Models.TorrentClient; -namespace RdtClient.Data.Models.TorrentClient +public class TorrentClientAvailableFile { - public class TorrentClientAvailableFile - { - public String Filename { get; set; } + public String Filename { get; set; } - public Int64 Filesize { get; set; } - } -} + public Int64 Filesize { get; set; } +} \ No newline at end of file diff --git a/server/RdtClient.Data/Models/TorrentClient/TorrentClientFile.cs b/server/RdtClient.Data/Models/TorrentClient/TorrentClientFile.cs index 9cba017..c8add53 100644 --- a/server/RdtClient.Data/Models/TorrentClient/TorrentClientFile.cs +++ b/server/RdtClient.Data/Models/TorrentClient/TorrentClientFile.cs @@ -1,12 +1,9 @@ -using System; +namespace RdtClient.Data.Models.TorrentClient; -namespace RdtClient.Data.Models.TorrentClient +public class TorrentClientFile { - public class TorrentClientFile - { - public Int64 Id { get; set; } - public String Path { get; set; } - public Int64 Bytes { get; set; } - public Boolean Selected { get; set; } - } -} + public Int64 Id { get; set; } + public String Path { get; set; } + public Int64 Bytes { get; set; } + public Boolean Selected { get; set; } +} \ No newline at end of file diff --git a/server/RdtClient.Data/Models/TorrentClient/TorrentClientTorrent.cs b/server/RdtClient.Data/Models/TorrentClient/TorrentClientTorrent.cs index a82985c..971faa3 100644 --- a/server/RdtClient.Data/Models/TorrentClient/TorrentClientTorrent.cs +++ b/server/RdtClient.Data/Models/TorrentClient/TorrentClientTorrent.cs @@ -1,26 +1,22 @@ -using System; -using System.Collections.Generic; +namespace RdtClient.Data.Models.TorrentClient; -namespace RdtClient.Data.Models.TorrentClient +public class TorrentClientTorrent { - public class TorrentClientTorrent - { - public String Id { get; set; } - public String Filename { get; set; } - public String OriginalFilename { get; set; } - public String Hash { get; set; } - public Int64 Bytes { get; set; } - public Int64 OriginalBytes { get; set; } - public String Host { get; set; } - public Int64 Split { get; set; } - public Int64 Progress { get; set; } - public String Status { get; set; } - public Int64 StatusCode { get; set; } - public DateTimeOffset Added { get; set; } - public List Files { get; set; } - public List Links { get; set; } - public DateTimeOffset? Ended { get; set; } - public Int64? Speed { get; set; } - public Int64? Seeders { get; set; } - } -} + public String Id { get; set; } + public String Filename { get; set; } + public String OriginalFilename { get; set; } + public String Hash { get; set; } + public Int64 Bytes { get; set; } + public Int64 OriginalBytes { get; set; } + public String Host { get; set; } + public Int64 Split { get; set; } + public Int64 Progress { get; set; } + public String Status { get; set; } + public Int64 StatusCode { get; set; } + public DateTimeOffset Added { get; set; } + public List Files { get; set; } + public List Links { get; set; } + public DateTimeOffset? Ended { get; set; } + public Int64? Speed { get; set; } + public Int64? Seeders { get; set; } +} \ No newline at end of file diff --git a/server/RdtClient.Data/Models/TorrentClient/TorrentClientUser.cs b/server/RdtClient.Data/Models/TorrentClient/TorrentClientUser.cs index cada3bc..5cf751e 100644 --- a/server/RdtClient.Data/Models/TorrentClient/TorrentClientUser.cs +++ b/server/RdtClient.Data/Models/TorrentClient/TorrentClientUser.cs @@ -1,10 +1,7 @@ -using System; +namespace RdtClient.Data.Models.TorrentClient; -namespace RdtClient.Data.Models.TorrentClient +public class TorrentClientUser { - public class TorrentClientUser - { - public String Username { get; set; } - public DateTimeOffset? Expiration { get; set; } - } -} + public String Username { get; set; } + public DateTimeOffset? Expiration { get; set; } +} \ No newline at end of file diff --git a/server/RdtClient.Data/RdtClient.Data.csproj b/server/RdtClient.Data/RdtClient.Data.csproj index 408262c..6d777a1 100644 --- a/server/RdtClient.Data/RdtClient.Data.csproj +++ b/server/RdtClient.Data/RdtClient.Data.csproj @@ -2,18 +2,21 @@ net6.0 + disable + enable + latest - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/server/RdtClient.Service/DiConfig.cs b/server/RdtClient.Service/DiConfig.cs index 02ec4d4..7b2bf41 100644 --- a/server/RdtClient.Service/DiConfig.cs +++ b/server/RdtClient.Service/DiConfig.cs @@ -2,23 +2,24 @@ using RdtClient.Service.Services; using RdtClient.Service.Services.TorrentClients; -namespace RdtClient.Service +namespace RdtClient.Service; + +public static class DiConfig { - public static class DiConfig + public static void Config(IServiceCollection services) { - public static void Config(IServiceCollection services) - { - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); - services.AddHostedService(); - } + services.AddHostedService(); + services.AddHostedService(); + services.AddHostedService(); } } \ No newline at end of file diff --git a/server/RdtClient.Service/Helpers/DownloadHelper.cs b/server/RdtClient.Service/Helpers/DownloadHelper.cs index 1989727..7a385ef 100644 --- a/server/RdtClient.Service/Helpers/DownloadHelper.cs +++ b/server/RdtClient.Service/Helpers/DownloadHelper.cs @@ -1,37 +1,33 @@ -using System; -using System.IO; -using System.Linq; -using System.Web; +using System.Web; using RdtClient.Data.Models.Data; -namespace RdtClient.Service.Helpers +namespace RdtClient.Service.Helpers; + +public static class DownloadHelper { - public static class DownloadHelper + public static String GetDownloadPath(String downloadPath, Torrent torrent, Download download) { - public static String GetDownloadPath(String downloadPath, Torrent torrent, Download download) + var fileUrl = download.Link; + + if (String.IsNullOrWhiteSpace(fileUrl)) { - var fileUrl = download.Link; - - if (String.IsNullOrWhiteSpace(fileUrl)) - { - return null; - } - - var uri = new Uri(fileUrl); - var torrentPath = Path.Combine(downloadPath, torrent.RdName); - - if (!Directory.Exists(torrentPath)) - { - Directory.CreateDirectory(torrentPath); - } - - var fileName = uri.Segments.Last(); - - fileName = HttpUtility.UrlDecode(fileName); - - var filePath = Path.Combine(torrentPath, fileName); - - return filePath; + return null; } + + var uri = new Uri(fileUrl); + var torrentPath = Path.Combine(downloadPath, torrent.RdName); + + if (!Directory.Exists(torrentPath)) + { + Directory.CreateDirectory(torrentPath); + } + + var fileName = uri.Segments.Last(); + + fileName = HttpUtility.UrlDecode(fileName); + + var filePath = Path.Combine(torrentPath, fileName); + + return filePath; } -} +} \ No newline at end of file diff --git a/server/RdtClient.Service/Helpers/FileHelper.cs b/server/RdtClient.Service/Helpers/FileHelper.cs index 830e73c..1766984 100644 --- a/server/RdtClient.Service/Helpers/FileHelper.cs +++ b/server/RdtClient.Service/Helpers/FileHelper.cs @@ -1,45 +1,40 @@ -using System; -using System.IO; -using System.Threading.Tasks; +namespace RdtClient.Service.Helpers; -namespace RdtClient.Service.Helpers +public static class FileHelper { - public static class FileHelper + public static async Task Delete(String path) { - public static async Task Delete(String path) + if (String.IsNullOrWhiteSpace(path)) { - if (String.IsNullOrWhiteSpace(path)) + return; + } + + if (!File.Exists(path)) + { + return; + } + + var retry = 0; + + while (true) + { + try { - return; + File.Delete(path); + + break; } - - if (!File.Exists(path)) + catch { - return; - } - - var retry = 0; - - while (true) - { - try + if (retry >= 3) { - File.Delete(path); - - break; + throw; } - catch - { - if (retry >= 3) - { - throw; - } - retry++; + retry++; - await Task.Delay(1000 * retry); - } + await Task.Delay(1000 * retry); } } } -} +} \ No newline at end of file diff --git a/server/RdtClient.Service/Helpers/JsonModelBinder.cs b/server/RdtClient.Service/Helpers/JsonModelBinder.cs index 1880465..9357090 100644 --- a/server/RdtClient.Service/Helpers/JsonModelBinder.cs +++ b/server/RdtClient.Service/Helpers/JsonModelBinder.cs @@ -1,33 +1,30 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding; -namespace RdtClient.Service.Helpers +namespace RdtClient.Service.Helpers; + +public class JsonModelBinder : IModelBinder { - public class JsonModelBinder : IModelBinder + public Task BindModelAsync(ModelBindingContext bindingContext) { - public Task BindModelAsync(ModelBindingContext bindingContext) + if (bindingContext == null) { - if (bindingContext == null) - { - throw new ArgumentNullException(nameof(bindingContext)); - } - - var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); - if (valueProviderResult != ValueProviderResult.None) - { - bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); - - var valueAsString = valueProviderResult.FirstValue; - var result = Newtonsoft.Json.JsonConvert.DeserializeObject(valueAsString, bindingContext.ModelType); - if (result != null) - { - bindingContext.Result = ModelBindingResult.Success(result); - return Task.CompletedTask; - } - } - - return Task.CompletedTask; + throw new ArgumentNullException(nameof(bindingContext)); } + + var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + if (valueProviderResult != ValueProviderResult.None) + { + bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); + + var valueAsString = valueProviderResult.FirstValue; + var result = Newtonsoft.Json.JsonConvert.DeserializeObject(valueAsString, bindingContext.ModelType); + if (result != null) + { + bindingContext.Result = ModelBindingResult.Success(result); + return Task.CompletedTask; + } + } + + return Task.CompletedTask; } -} +} \ No newline at end of file diff --git a/server/RdtClient.Service/Helpers/Logger.cs b/server/RdtClient.Service/Helpers/Logger.cs index 5d963d6..09f1e45 100644 --- a/server/RdtClient.Service/Helpers/Logger.cs +++ b/server/RdtClient.Service/Helpers/Logger.cs @@ -1,31 +1,28 @@ -using System; -using System.Linq; -using System.Web; +using System.Web; using RdtClient.Data.Models.Data; -namespace RdtClient.Service.Helpers +namespace RdtClient.Service.Helpers; + +public static class Logger { - public static class Logger + public static String ToLog(this Download download) { - public static String ToLog(this Download download) + var fileName = download.Path; + + if (!String.IsNullOrWhiteSpace(download.Link)) { - var fileName = download.Path; - - if (!String.IsNullOrWhiteSpace(download.Link)) - { - var uri = new Uri(download.Link); - fileName = uri.Segments.Last(); - fileName = HttpUtility.UrlDecode(fileName); - } - - var done = (Int32)((Double)download.BytesDone / download.BytesTotal) * 100; - - return $"for download {fileName} {done}% ({download.DownloadId})"; + var uri = new Uri(download.Link); + fileName = uri.Segments.Last(); + fileName = HttpUtility.UrlDecode(fileName); } - public static String ToLog(this Torrent torrent) - { - return $"for torrent {torrent.RdName} ({torrent.RdId} - {torrent.RdStatusRaw} {torrent.RdProgress}%) ({torrent.TorrentId})"; - } + var done = (Int32)((Double)download.BytesDone / download.BytesTotal) * 100; + + return $"for download {fileName} {done}% ({download.DownloadId})"; } -} + + public static String ToLog(this Torrent torrent) + { + return $"for torrent {torrent.RdName} ({torrent.RdId} - {torrent.RdStatusRaw} {torrent.RdProgress}%) ({torrent.TorrentId})"; + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Helpers/SettingsHelper.cs b/server/RdtClient.Service/Helpers/SettingsHelper.cs index b9c0459..4c11329 100644 --- a/server/RdtClient.Service/Helpers/SettingsHelper.cs +++ b/server/RdtClient.Service/Helpers/SettingsHelper.cs @@ -1,34 +1,30 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using RdtClient.Data.Models.Data; +using RdtClient.Data.Models.Data; -namespace RdtClient.Service.Helpers +namespace RdtClient.Service.Helpers; + +public static class SettingsHelper { - public static class SettingsHelper + public static String GetString(this IList settings, String key) { - public static String GetString(this IList settings, String key) + var setting = settings.FirstOrDefault(m => m.SettingId == key); + + if (setting == null) { - var setting = settings.FirstOrDefault(m => m.SettingId == key); - - if (setting == null) - { - throw new Exception($"Setting with key {key} not found"); - } - - return setting.Value; + throw new Exception($"Setting with key {key} not found"); } - public static Int32 GetNumber(this IList settings, String key) - { - var setting = settings.FirstOrDefault(m => m.SettingId == key); - - if (setting == null) - { - throw new Exception($"Setting with key {key} not found"); - } - - return Int32.Parse(setting.Value); - } + return setting.Value; } -} + + public static Int32 GetNumber(this IList settings, String key) + { + var setting = settings.FirstOrDefault(m => m.SettingId == key); + + if (setting == null) + { + throw new Exception($"Setting with key {key} not found"); + } + + return Int32.Parse(setting.Value); + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Middleware/AuthorizeMiddleware.cs b/server/RdtClient.Service/Middleware/AuthorizeMiddleware.cs index 85d02a3..fa6b994 100644 --- a/server/RdtClient.Service/Middleware/AuthorizeMiddleware.cs +++ b/server/RdtClient.Service/Middleware/AuthorizeMiddleware.cs @@ -1,32 +1,29 @@ -using System; -using System.Net; -using System.Threading.Tasks; +using System.Net; using Microsoft.AspNetCore.Http; -namespace RdtClient.Service.Middleware +namespace RdtClient.Service.Middleware; + +public class AuthorizeMiddleware { - public class AuthorizeMiddleware + private readonly RequestDelegate _next; + + public AuthorizeMiddleware(RequestDelegate next) { - private readonly RequestDelegate _next; + _next = next; + } - public AuthorizeMiddleware(RequestDelegate next) + /// + /// Return a 403 instead of a 401, it's quirk that QBittorrent has. + /// + /// + /// + public async Task Invoke(HttpContext context) + { + await _next(context); + + if (context.Response.StatusCode == (Int32) HttpStatusCode.Unauthorized) { - _next = next; - } - - /// - /// Return a 403 instead of a 401, it's quirk that QBittorrent has. - /// - /// - /// - public async Task Invoke(HttpContext context) - { - await _next(context); - - if (context.Response.StatusCode == (Int32) HttpStatusCode.Unauthorized) - { - context.Response.StatusCode = (Int32) HttpStatusCode.Forbidden; - } + context.Response.StatusCode = (Int32) HttpStatusCode.Forbidden; } } } \ No newline at end of file diff --git a/server/RdtClient.Service/Middleware/ExceptionMiddlewareExtensions.cs b/server/RdtClient.Service/Middleware/ExceptionMiddlewareExtensions.cs index 815f8c4..3f46326 100644 --- a/server/RdtClient.Service/Middleware/ExceptionMiddlewareExtensions.cs +++ b/server/RdtClient.Service/Middleware/ExceptionMiddlewareExtensions.cs @@ -1,29 +1,27 @@ -using System; -using System.Net; +using System.Net; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; -namespace RdtClient.Service.Middleware -{ - public static class ExceptionMiddlewareExtensions - { - public static void ConfigureExceptionHandler(this IApplicationBuilder app) - { - app.UseExceptionHandler(appError => - { - appError.Run(async context => - { - context.Response.StatusCode = (Int32) HttpStatusCode.InternalServerError; - context.Response.ContentType = "application/json"; +namespace RdtClient.Service.Middleware; - var contextFeature = context.Features.Get(); - if (contextFeature != null) - { - await context.Response.WriteAsync(contextFeature.Error.Message); - } - }); +public static class ExceptionMiddlewareExtensions +{ + public static void ConfigureExceptionHandler(this IApplicationBuilder app) + { + app.UseExceptionHandler(appError => + { + appError.Run(async context => + { + context.Response.StatusCode = (Int32) HttpStatusCode.InternalServerError; + context.Response.ContentType = "application/json"; + + var contextFeature = context.Features.Get(); + if (contextFeature != null) + { + await context.Response.WriteAsync(contextFeature.Error.Message); + } }); - } + }); } } \ No newline at end of file diff --git a/server/RdtClient.Service/RdtClient.Service.csproj b/server/RdtClient.Service/RdtClient.Service.csproj index fce50fe..7fe3d39 100644 --- a/server/RdtClient.Service/RdtClient.Service.csproj +++ b/server/RdtClient.Service/RdtClient.Service.csproj @@ -1,20 +1,23 @@ - + net6.0 - library + disable + enable + latest + - + - + - + diff --git a/server/RdtClient.Service/Services/Authentication.cs b/server/RdtClient.Service/Services/Authentication.cs index 06e8ab5..6489509 100644 --- a/server/RdtClient.Service/Services/Authentication.cs +++ b/server/RdtClient.Service/Services/Authentication.cs @@ -1,79 +1,76 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity; using RdtClient.Data.Data; -namespace RdtClient.Service.Services +namespace RdtClient.Service.Services; + +public class Authentication { - public class Authentication + private readonly SignInManager _signInManager; + private readonly UserManager _userManager; + private readonly UserData _userData; + + public Authentication(SignInManager signInManager, UserManager userManager, UserData userData) { - private readonly SignInManager _signInManager; - private readonly UserManager _userManager; - private readonly UserData _userData; - - public Authentication(SignInManager signInManager, UserManager userManager, UserData userData) - { - _signInManager = signInManager; - _userManager = userManager; - _userData = userData; - } - - public async Task Register(String userName, String password) - { - var user = new IdentityUser(userName); - - var result = await _userManager.CreateAsync(user, password); - - return result; - } - - public async Task Login(String userName, String password) - { - if (String.IsNullOrWhiteSpace(userName) || String.IsNullOrWhiteSpace(password)) - { - return SignInResult.Failed; - } - - var result = await _signInManager.PasswordSignInAsync(userName, password, true, false); - - return result; - } - - public async Task GetUser() - { - return await _userData.GetUser(); - } - - public async Task Logout() - { - await _signInManager.SignOutAsync(); - } - - public async Task Update(String newUserName, String newPassword) - { - var user = await GetUser(); - - if (user == null) - { - throw new Exception("No logged in user found"); - } - - if (!String.IsNullOrWhiteSpace(newUserName)) - { - user.UserName = newUserName; - } - - await _userManager.UpdateAsync(user); - - if (!String.IsNullOrWhiteSpace(newPassword)) - { - var token = await _userManager.GeneratePasswordResetTokenAsync(user); - var result = await _userManager.ResetPasswordAsync(user, token, newPassword); - - return result; - } - - return IdentityResult.Success; - } + _signInManager = signInManager; + _userManager = userManager; + _userData = userData; } -} + + public async Task Register(String userName, String password) + { + var user = new IdentityUser(userName); + + var result = await _userManager.CreateAsync(user, password); + + return result; + } + + public async Task Login(String userName, String password) + { + if (String.IsNullOrWhiteSpace(userName) || String.IsNullOrWhiteSpace(password)) + { + return SignInResult.Failed; + } + + var result = await _signInManager.PasswordSignInAsync(userName, password, true, false); + + return result; + } + + public async Task GetUser() + { + return await _userData.GetUser(); + } + + public async Task Logout() + { + await _signInManager.SignOutAsync(); + } + + public async Task Update(String newUserName, String newPassword) + { + var user = await GetUser(); + + if (user == null) + { + throw new Exception("No logged in user found"); + } + + if (!String.IsNullOrWhiteSpace(newUserName)) + { + user.UserName = newUserName; + } + + await _userManager.UpdateAsync(user); + + if (!String.IsNullOrWhiteSpace(newPassword)) + { + var token = await _userManager.GeneratePasswordResetTokenAsync(user); + var result = await _userManager.ResetPasswordAsync(user, token, newPassword); + + return result; + } + + return IdentityResult.Success; + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/DownloadClient.cs b/server/RdtClient.Service/Services/DownloadClient.cs index c28acb6..0f351dc 100644 --- a/server/RdtClient.Service/Services/DownloadClient.cs +++ b/server/RdtClient.Service/Services/DownloadClient.cs @@ -1,126 +1,123 @@ -using System; -using System.Threading.Tasks; -using RdtClient.Data.Models.Data; +using RdtClient.Data.Models.Data; using RdtClient.Data.Models.Internal; using RdtClient.Service.Helpers; using RdtClient.Service.Services.Downloaders; -namespace RdtClient.Service.Services +namespace RdtClient.Service.Services; + +public class DownloadClient { - public class DownloadClient + private readonly String _destinationPath; + + private readonly Download _download; + private readonly Torrent _torrent; + + public IDownloader Downloader; + + public DownloadClient(Download download, Torrent torrent, String destinationPath) { - private readonly String _destinationPath; + _download = download; + _torrent = torrent; + _destinationPath = destinationPath; + } - private readonly Download _download; - private readonly Torrent _torrent; + public String Type { get; set; } - public IDownloader Downloader; + public Boolean Finished { get; private set; } - public DownloadClient(Download download, Torrent torrent, String destinationPath) + public String Error { get; private set; } + + public Int64 Speed { get; private set; } + public Int64 BytesTotal { get; private set; } + public Int64 BytesDone { get; private set; } + + public async Task Start(DbSettings settings) + { + BytesDone = 0; + BytesTotal = 0; + Speed = 0; + + try { - _download = download; - _torrent = torrent; - _destinationPath = destinationPath; - } + var filePath = DownloadHelper.GetDownloadPath(_destinationPath, _torrent, _download); - public String Type { get; set; } - - public Boolean Finished { get; private set; } - - public String Error { get; private set; } - - public Int64 Speed { get; private set; } - public Int64 BytesTotal { get; private set; } - public Int64 BytesDone { get; private set; } - - public async Task Start(DbSettings settings) - { - BytesDone = 0; - BytesTotal = 0; - Speed = 0; - - try + if (filePath == null) { - var filePath = DownloadHelper.GetDownloadPath(_destinationPath, _torrent, _download); - - if (filePath == null) - { - throw new Exception("Invalid download path"); - } - - await FileHelper.Delete(filePath); - - Type = settings.DownloadClient; - - Downloader = settings.DownloadClient switch - { - "Simple" => new SimpleDownloader(_download.Link, filePath), - "MultiPart" => new MultiDownloader(_download.Link, filePath, settings), - "Aria2c" => new Aria2cDownloader(_download.RemoteId, _download.Link, filePath, settings), - _ => throw new Exception($"Unknown download client {settings.DownloadClient}") - }; - - Downloader.DownloadComplete += (_, args) => - { - Finished = true; - Error ??= args.Error; - }; - - Downloader.DownloadProgress += (_, args) => - { - Speed = args.Speed; - BytesDone = args.BytesDone; - BytesTotal = args.BytesTotal; - }; - - var result = await Downloader.Download(); - - await Task.Delay(1000); - - return result; + throw new Exception("Invalid download path"); } - catch (Exception ex) - { - if (Downloader != null) - { - await Downloader.Cancel(); - } + + await FileHelper.Delete(filePath); - Error = $"An unexpected error occurred preparing download {_download.Link} for torrent {_torrent.RdName}: {ex.Message}"; + Type = settings.DownloadClient; + + Downloader = settings.DownloadClient switch + { + "Simple" => new SimpleDownloader(_download.Link, filePath), + "MultiPart" => new MultiDownloader(_download.Link, filePath, settings), + "Aria2c" => new Aria2cDownloader(_download.RemoteId, _download.Link, filePath, settings), + _ => throw new Exception($"Unknown download client {settings.DownloadClient}") + }; + + Downloader.DownloadComplete += (_, args) => + { Finished = true; + Error ??= args.Error; + }; - return null; - } + Downloader.DownloadProgress += (_, args) => + { + Speed = args.Speed; + BytesDone = args.BytesDone; + BytesTotal = args.BytesTotal; + }; + + var result = await Downloader.Download(); + + await Task.Delay(1000); + + return result; } - - public async Task Cancel() + catch (Exception ex) { + if (Downloader != null) + { + await Downloader.Cancel(); + } + + Error = $"An unexpected error occurred preparing download {_download.Link} for torrent {_torrent.RdName}: {ex.Message}"; Finished = true; - Error = null; - if (Downloader == null) - { - return; - } - await Downloader.Cancel(); - } - - public async Task Pause() - { - if (Downloader == null) - { - return; - } - await Downloader.Pause(); - } - - public async Task Resume() - { - if (Downloader == null) - { - return; - } - await Downloader.Resume(); + return null; } } -} + + public async Task Cancel() + { + Finished = true; + Error = null; + + if (Downloader == null) + { + return; + } + await Downloader.Cancel(); + } + + public async Task Pause() + { + if (Downloader == null) + { + return; + } + await Downloader.Pause(); + } + + public async Task Resume() + { + if (Downloader == null) + { + return; + } + await Downloader.Resume(); + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs b/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs index 81cc464..3859d8d 100644 --- a/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs @@ -1,272 +1,265 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using Aria2NET; +using Aria2NET; using RdtClient.Data.Models.Internal; using Serilog; -namespace RdtClient.Service.Services.Downloaders +namespace RdtClient.Service.Services.Downloaders; + +public class Aria2cDownloader : IDownloader { - public class Aria2cDownloader : IDownloader + public event EventHandler DownloadComplete; + public event EventHandler DownloadProgress; + + private const Int32 RetryCount = 5; + + private readonly Aria2NetClient _aria2NetClient; + + private readonly ILogger _logger; + private readonly String _uri; + private readonly String _filePath; + + private String _gid; + + public Aria2cDownloader(String gid, String uri, String filePath, DbSettings settings) { - public event EventHandler DownloadComplete; - public event EventHandler DownloadProgress; + _logger = Log.ForContext(); + _gid = gid; + _uri = uri; + _filePath = filePath; - private const Int32 RetryCount = 5; - - private readonly Aria2NetClient _aria2NetClient; - - private readonly ILogger _logger; - private readonly String _uri; - private readonly String _filePath; - - private String _gid; - - public Aria2cDownloader(String gid, String uri, String filePath, DbSettings settings) + var httpClient = new HttpClient { - _logger = Log.ForContext(); - _gid = gid; - _uri = uri; - _filePath = filePath; + Timeout = TimeSpan.FromSeconds(10) + }; - var httpClient = new HttpClient - { - Timeout = TimeSpan.FromSeconds(10) - }; - - _aria2NetClient = new Aria2NetClient(settings.Aria2cUrl, settings.Aria2cSecret, httpClient, 10); - } + _aria2NetClient = new Aria2NetClient(settings.Aria2cUrl, settings.Aria2cSecret, httpClient, 10); + } - public async Task Download() + public async Task Download() + { + var path = Path.GetDirectoryName(_filePath); + var fileName = Path.GetFileName(_filePath); + + _logger.Debug($"Starting download of {_uri}, writing to path: {path}, fileName: {fileName}"); + + if (String.IsNullOrWhiteSpace(_gid)) { - var path = Path.GetDirectoryName(_filePath); - var fileName = Path.GetFileName(_filePath); + var isAlreadyAdded = await CheckIfAdded(); - _logger.Debug($"Starting download of {_uri}, writing to path: {path}, fileName: {fileName}"); - - if (String.IsNullOrWhiteSpace(_gid)) + if (isAlreadyAdded) { - var isAlreadyAdded = await CheckIfAdded(); - - if (isAlreadyAdded) - { - throw new Exception($"The download link {_uri} has already been added to Aria2"); - } + throw new Exception($"The download link {_uri} has already been added to Aria2"); } + } - var retryCount = 0; - while(true) + var retryCount = 0; + while(true) + { + try { - try + if (_gid != null) { - if (_gid != null) + try { - try - { - await _aria2NetClient.TellStatus(_gid); + await _aria2NetClient.TellStatus(_gid); - return _gid; - } - catch - { - _gid = null; - } + return _gid; } + catch + { + _gid = null; + } + } - _gid ??= await _aria2NetClient.AddUri(new List + _gid ??= await _aria2NetClient.AddUri(new List + { + _uri + }, + new Dictionary + { { - _uri + "dir", path }, - new Dictionary { - { - "dir", path - }, - { - "out", fileName - } - }); + "out", fileName + } + }); - _logger.Debug($"Added download to Aria2, received ID {_gid}"); + _logger.Debug($"Added download to Aria2, received ID {_gid}"); - await _aria2NetClient.TellStatus(_gid); + await _aria2NetClient.TellStatus(_gid); - _logger.Debug($"Download with ID {_gid} found in Aria2"); + _logger.Debug($"Download with ID {_gid} found in Aria2"); - return _gid; + return _gid; + } + catch (Exception ex) + { + if (retryCount >= RetryCount) + { + throw; } - catch (Exception ex) - { - if (retryCount >= RetryCount) - { - throw; - } - _logger.Debug($"Error starting download: {ex.Message}. Retrying {retryCount}/{RetryCount}"); + _logger.Debug($"Error starting download: {ex.Message}. Retrying {retryCount}/{RetryCount}"); - await Task.Delay(retryCount * 1000); + await Task.Delay(retryCount * 1000); - retryCount++; - } + retryCount++; } } - - public async Task Cancel() - { - await Remove(); - } - - public async Task Pause() - { - _logger.Debug($"Pausing download {_uri} {_gid}"); - - if (String.IsNullOrWhiteSpace(_gid)) - { - return; - } - - try - { - await _aria2NetClient.Pause(_gid); - } - catch - { - // ignored - } - } - - public async Task Resume() - { - _logger.Debug($"Resuming download {_uri} {_gid}"); - - if (String.IsNullOrWhiteSpace(_gid)) - { - return; - } - - try - { - await _aria2NetClient.Unpause(_gid); - } - catch - { - // ignored - } - } - - public async Task Update(IEnumerable allDownloads) - { - if (_gid == null) - { - return; - } - - var download = allDownloads.FirstOrDefault(m => m.Gid == _gid); - - if (download == null) - { - DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs - { - Error = $"Download was not found in Aria2" - }); - return; - } - - if (!String.IsNullOrWhiteSpace(download.ErrorMessage) || download.Status == "error") - { - await Remove(); - DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs - { - Error = $"{download.ErrorCode}: {download.ErrorMessage}" - }); - return; - } - - if (download.Status == "complete" || download.Status == "removed") - { - _logger.Debug($"Aria2 download found as complete {_gid}"); - - await Remove(); - - var retryCount = 0; - while (true) - { - if (retryCount >= 10) - { - DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs - { - Error = $"File not found at {_filePath}" - }); - break; - } - - if (File.Exists(_filePath)) - { - DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs()); - break; - } - - await Task.Delay(1000 * retryCount); - retryCount++; - } - return; - } - - DownloadProgress?.Invoke(this, new DownloadProgressEventArgs - { - BytesDone = download.CompletedLength, - BytesTotal = download.TotalLength, - Speed = download.DownloadSpeed - }); - } - - private async Task Remove() - { - if (String.IsNullOrWhiteSpace(_gid)) - { - return; - } - - _logger.Debug($"Remove download {_uri} {_gid} from Aria2"); - - try - { - await _aria2NetClient.ForceRemove(_gid); - } - catch - { - // ignored - } - - try - { - await _aria2NetClient.RemoveDownloadResult(_gid); - } - catch - { - // ignored - } - } - - private async Task CheckIfAdded() - { - var allDownloads = await _aria2NetClient.TellAll(); - - foreach (var download in allDownloads.Where(m => m.Files != null)) - { - foreach (var file in download.Files.Where(m => m.Uris != null)) - { - if (file.Uris.Any(uri => uri.Uri == _uri)) - { - return true; - } - } - } - - return false; - } } -} + + public async Task Cancel() + { + await Remove(); + } + + public async Task Pause() + { + _logger.Debug($"Pausing download {_uri} {_gid}"); + + if (String.IsNullOrWhiteSpace(_gid)) + { + return; + } + + try + { + await _aria2NetClient.Pause(_gid); + } + catch + { + // ignored + } + } + + public async Task Resume() + { + _logger.Debug($"Resuming download {_uri} {_gid}"); + + if (String.IsNullOrWhiteSpace(_gid)) + { + return; + } + + try + { + await _aria2NetClient.Unpause(_gid); + } + catch + { + // ignored + } + } + + public async Task Update(IEnumerable allDownloads) + { + if (_gid == null) + { + return; + } + + var download = allDownloads.FirstOrDefault(m => m.Gid == _gid); + + if (download == null) + { + DownloadComplete.Invoke(this, new DownloadCompleteEventArgs + { + Error = $"Download was not found in Aria2" + }); + return; + } + + if (!String.IsNullOrWhiteSpace(download.ErrorMessage) || download.Status == "error") + { + await Remove(); + DownloadComplete.Invoke(this, new DownloadCompleteEventArgs + { + Error = $"{download.ErrorCode}: {download.ErrorMessage}" + }); + return; + } + + if (download.Status == "complete" || download.Status == "removed") + { + _logger.Debug($"Aria2 download found as complete {_gid}"); + + await Remove(); + + var retryCount = 0; + while (true) + { + if (retryCount >= 10) + { + DownloadComplete.Invoke(this, new DownloadCompleteEventArgs + { + Error = $"File not found at {_filePath}" + }); + break; + } + + if (File.Exists(_filePath)) + { + DownloadComplete.Invoke(this, new DownloadCompleteEventArgs()); + break; + } + + await Task.Delay(1000 * retryCount); + retryCount++; + } + return; + } + + DownloadProgress.Invoke(this, new DownloadProgressEventArgs + { + BytesDone = download.CompletedLength, + BytesTotal = download.TotalLength, + Speed = download.DownloadSpeed + }); + } + + private async Task Remove() + { + if (String.IsNullOrWhiteSpace(_gid)) + { + return; + } + + _logger.Debug($"Remove download {_uri} {_gid} from Aria2"); + + try + { + await _aria2NetClient.ForceRemove(_gid); + } + catch + { + // ignored + } + + try + { + await _aria2NetClient.RemoveDownloadResult(_gid); + } + catch + { + // ignored + } + } + + private async Task CheckIfAdded() + { + var allDownloads = await _aria2NetClient.TellAll(); + + foreach (var download in allDownloads.Where(m => m.Files != null)) + { + foreach (var file in download.Files.Where(m => m.Uris != null)) + { + if (file.Uris.Any(uri => uri.Uri == _uri)) + { + return true; + } + } + } + + return false; + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/Downloaders/IDownloader.cs b/server/RdtClient.Service/Services/Downloaders/IDownloader.cs index e740b70..6354023 100644 --- a/server/RdtClient.Service/Services/Downloaders/IDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/IDownloader.cs @@ -1,27 +1,23 @@ -using System; -using System.Threading.Tasks; +namespace RdtClient.Service.Services.Downloaders; -namespace RdtClient.Service.Services.Downloaders +public class DownloadCompleteEventArgs { - public class DownloadCompleteEventArgs - { - public String Error { get; set; } - } - - public class DownloadProgressEventArgs - { - public Int64 Speed { get; set; } - public Int64 BytesDone { get; set; } - public Int64 BytesTotal { get; set; } - } - - public interface IDownloader - { - event EventHandler DownloadComplete; - event EventHandler DownloadProgress; - Task Download(); - Task Cancel(); - Task Pause(); - Task Resume(); - } + public String Error { get; set; } } + +public class DownloadProgressEventArgs +{ + public Int64 Speed { get; set; } + public Int64 BytesDone { get; set; } + public Int64 BytesTotal { get; set; } +} + +public interface IDownloader +{ + event EventHandler DownloadComplete; + event EventHandler DownloadProgress; + Task Download(); + Task Cancel(); + Task Pause(); + Task Resume(); +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/Downloaders/MultiDownloader.cs b/server/RdtClient.Service/Services/Downloaders/MultiDownloader.cs index 1d43b59..3ed1ec2 100644 --- a/server/RdtClient.Service/Services/Downloaders/MultiDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/MultiDownloader.cs @@ -1,147 +1,143 @@ -using System; -using System.IO; -using System.Net; -using System.Threading.Tasks; +using System.Net; using Downloader; using RdtClient.Data.Models.Internal; using Serilog; -namespace RdtClient.Service.Services.Downloaders +namespace RdtClient.Service.Services.Downloaders; + +public class MultiDownloader : IDownloader { - public class MultiDownloader : IDownloader + public event EventHandler DownloadComplete; + public event EventHandler DownloadProgress; + + private readonly DownloadService _downloadService; + private readonly String _filePath; + private readonly String _uri; + + private readonly ILogger _logger; + + public MultiDownloader(String uri, String filePath, DbSettings settings) { - public event EventHandler DownloadComplete; - public event EventHandler DownloadProgress; + _logger = Log.ForContext(); - private readonly DownloadService _downloadService; - private readonly String _filePath; - private readonly String _uri; + _uri = uri; + _filePath = filePath; - private readonly ILogger _logger; + var settingTempPath = settings.TempPath; - public MultiDownloader(String uri, String filePath, DbSettings settings) + if (String.IsNullOrWhiteSpace(settingTempPath)) { - _logger = Log.ForContext(); + settingTempPath = Path.GetTempPath(); + } - _uri = uri; - _filePath = filePath; + var settingDownloadChunkCount = settings.DownloadChunkCount; - var settingTempPath = settings.TempPath; + if (settingDownloadChunkCount <= 0) + { + settingDownloadChunkCount = 1; + } - if (String.IsNullOrWhiteSpace(settingTempPath)) + var settingDownloadMaxSpeed = settings.DownloadMaxSpeed; + + if (settingDownloadMaxSpeed <= 0) + { + settingDownloadMaxSpeed = 0; + } + + settingDownloadMaxSpeed = settingDownloadMaxSpeed * 1024 * 1024; + + var settingProxyServer = settings.ProxyServer; + + var downloadOpt = new DownloadConfiguration + { + MaxTryAgainOnFailover = Int32.MaxValue, + ParallelDownload = settingDownloadChunkCount > 1, + ChunkCount = settingDownloadChunkCount, + Timeout = 1000, + OnTheFlyDownload = false, + BufferBlockSize = 1024 * 8, + MaximumBytesPerSecond = settingDownloadMaxSpeed, + TempDirectory = settingTempPath, + RequestConfiguration = { - settingTempPath = Path.GetTempPath(); + Accept = "*/*", + UserAgent = $"rdt-client", + ProtocolVersion = HttpVersion.Version11, + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, + KeepAlive = true, + UseDefaultCredentials = false + } + }; + + if (!String.IsNullOrWhiteSpace(settingProxyServer)) + { + downloadOpt.RequestConfiguration.Proxy = new WebProxy(new Uri(settingProxyServer), false); + } + + _downloadService = new DownloadService(downloadOpt); + + _downloadService.DownloadProgressChanged += (_, args) => + { + if (DownloadProgress == null) + { + return; } - var settingDownloadChunkCount = settings.DownloadChunkCount; + DownloadProgress.Invoke(this, + new DownloadProgressEventArgs + { + Speed = (Int64)args.BytesPerSecondSpeed, + BytesDone = args.ReceivedBytesSize, + BytesTotal = args.TotalBytesToReceive + }); + }; - if (settingDownloadChunkCount <= 0) + _downloadService.DownloadFileCompleted += (_, args) => + { + String error = null; + + if (args.Cancelled) { - settingDownloadChunkCount = 1; + error = $"The download was cancelled"; + } + else if (args.Error != null) + { + error = args.Error.Message; } - var settingDownloadMaxSpeed = settings.DownloadMaxSpeed; - - if (settingDownloadMaxSpeed <= 0) - { - settingDownloadMaxSpeed = 0; - } - - settingDownloadMaxSpeed = settingDownloadMaxSpeed * 1024 * 1024; - - var settingProxyServer = settings.ProxyServer; - - var downloadOpt = new DownloadConfiguration - { - MaxTryAgainOnFailover = Int32.MaxValue, - ParallelDownload = settingDownloadChunkCount > 1, - ChunkCount = settingDownloadChunkCount, - Timeout = 1000, - OnTheFlyDownload = false, - BufferBlockSize = 1024 * 8, - MaximumBytesPerSecond = settingDownloadMaxSpeed, - TempDirectory = settingTempPath, - RequestConfiguration = - { - Accept = "*/*", - UserAgent = $"rdt-client", - ProtocolVersion = HttpVersion.Version11, - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, - KeepAlive = true, - UseDefaultCredentials = false - } - }; - - if (!String.IsNullOrWhiteSpace(settingProxyServer)) - { - downloadOpt.RequestConfiguration.Proxy = new WebProxy(new Uri(settingProxyServer), false); - } - - _downloadService = new DownloadService(downloadOpt); - - _downloadService.DownloadProgressChanged += (_, args) => - { - if (DownloadProgress == null) - { - return; - } - - DownloadProgress?.Invoke(this, - new DownloadProgressEventArgs - { - Speed = (Int64)args.BytesPerSecondSpeed, - BytesDone = args.ReceivedBytesSize, - BytesTotal = args.TotalBytesToReceive - }); - }; - - _downloadService.DownloadFileCompleted += (_, args) => - { - String error = null; - - if (args.Cancelled) - { - error = $"The download was cancelled"; - } - else if (args.Error != null) - { - error = args.Error.Message; - } - - DownloadComplete?.Invoke(this, - new DownloadCompleteEventArgs - { - Error = error - }); - }; - } - - public async Task Download() - { - _logger.Debug($"Starting download of {_uri}, writing to path: {_filePath}"); - - await _downloadService.DownloadFileTaskAsync(_uri, _filePath); - - return null; - } - - public Task Cancel() - { - _logger.Debug($"Cancelling download {_uri}"); - - _downloadService.CancelAsync(); - - return Task.CompletedTask; - } - - public Task Pause() - { - return Task.CompletedTask; - } - - public Task Resume() - { - return Task.CompletedTask; - } + DownloadComplete?.Invoke(this, + new DownloadCompleteEventArgs + { + Error = error + }); + }; } -} + + public async Task Download() + { + _logger.Debug($"Starting download of {_uri}, writing to path: {_filePath}"); + + await _downloadService.DownloadFileTaskAsync(_uri, _filePath); + + return null; + } + + public Task Cancel() + { + _logger.Debug($"Cancelling download {_uri}"); + + _downloadService.CancelAsync(); + + return Task.CompletedTask; + } + + public Task Pause() + { + return Task.CompletedTask; + } + + public Task Resume() + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/Downloaders/SimpleDownloader.cs b/server/RdtClient.Service/Services/Downloaders/SimpleDownloader.cs index 9a19b19..07da967 100644 --- a/server/RdtClient.Service/Services/Downloaders/SimpleDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/SimpleDownloader.cs @@ -1,185 +1,179 @@ -using System; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; +using System.Net; using Serilog; -namespace RdtClient.Service.Services.Downloaders +namespace RdtClient.Service.Services.Downloaders; + +public class SimpleDownloader : IDownloader { - public class SimpleDownloader : IDownloader + private const Int32 BufferSize = 8 * 1024; + + public event EventHandler DownloadComplete; + public event EventHandler DownloadProgress; + + private readonly String _uri; + private readonly String _filePath; + + private Int64 _bytesTotal; + private Int64 _bytesDone; + + private Boolean _cancelled; + + private DateTime _nextUpdate; + + private readonly ILogger _logger; + + public SimpleDownloader(String uri, String filePath) { - private const Int32 BufferSize = 8 * 1024; + _logger = Log.ForContext(); - public event EventHandler DownloadComplete; - public event EventHandler DownloadProgress; + _uri = uri; + _filePath = filePath; + } - private readonly String _uri; - private readonly String _filePath; + public Task Download() + { + _logger.Debug($"Starting download of {_uri}, writing to path: {_filePath}"); - private Int64 _bytesTotal; - private Int64 _bytesDone; - - private Boolean _cancelled; - - private DateTime _nextUpdate; - - private readonly ILogger _logger; - - public SimpleDownloader(String uri, String filePath) + _ = Task.Run(async () => { - _logger = Log.ForContext(); - - _uri = uri; - _filePath = filePath; - } - - public Task Download() - { - _logger.Debug($"Starting download of {_uri}, writing to path: {_filePath}"); - - _ = Task.Run(async () => - { - await StartDownloadTask(); - }); + await StartDownloadTask(); + }); - return Task.FromResult(null); - } + return Task.FromResult(null); + } - public Task Cancel() + public Task Cancel() + { + _logger.Debug($"Cancelling download {_uri}"); + + _cancelled = true; + + return Task.CompletedTask; + } + + private async Task StartDownloadTask() + { + try { - _logger.Debug($"Cancelling download {_uri}"); + _nextUpdate = DateTime.UtcNow.AddSeconds(1); - _cancelled = true; - - return Task.CompletedTask; - } - - private async Task StartDownloadTask() - { - try - { - _nextUpdate = DateTime.UtcNow.AddSeconds(1); - - _bytesTotal = await GetContentSize(); + _bytesTotal = await GetContentSize(); - var timeout = DateTimeOffset.UtcNow.AddHours(1); + var timeout = DateTimeOffset.UtcNow.AddHours(1); - var httpClient = new HttpClient(); + var httpClient = new HttpClient(); - while (timeout > DateTimeOffset.UtcNow && !_cancelled) + while (timeout > DateTimeOffset.UtcNow && !_cancelled) + { + try { - try + var responseStream = await httpClient.GetStreamAsync(_uri); + + if (responseStream == null) { - var responseStream = await httpClient.GetStreamAsync(_uri); + throw new IOException("No stream"); + } - if (responseStream == null) - { - throw new IOException("No stream"); - } - - var speedLimit = Settings.Get.DownloadMaxSpeed; + var speedLimit = Settings.Get.DownloadMaxSpeed; - await using var destinationStream = new ThrottledStream(responseStream, speedLimit * 1000L * 1000L); + await using var destinationStream = new ThrottledStream(responseStream, speedLimit * 1000L * 1000L); - await using var fileStream = new FileStream(_filePath, FileMode.Create, FileAccess.Write, FileShare.Write); + await using var fileStream = new FileStream(_filePath, FileMode.Create, FileAccess.Write, FileShare.Write); - var readSize = 1; - var buffer = new Byte[BufferSize * 8]; + var readSize = 1; + var buffer = new Byte[BufferSize * 8]; - while (readSize > 0 && !_cancelled) + while (readSize > 0 && !_cancelled) + { + // ReSharper disable once ConvertToUsingDeclaration + using (var innerCts = new CancellationTokenSource(1000)) { - // ReSharper disable once ConvertToUsingDeclaration - using (var innerCts = new CancellationTokenSource(1000)) + readSize = await destinationStream.ReadAsync(buffer.AsMemory(0, buffer.Length), innerCts.Token).ConfigureAwait(false); + + await fileStream.WriteAsync(buffer.AsMemory(0, readSize), innerCts.Token); + + _bytesDone = fileStream.Length; + + if (DateTime.UtcNow > _nextUpdate) { - readSize = await destinationStream.ReadAsync(buffer.AsMemory(0, buffer.Length), innerCts.Token).ConfigureAwait(false); + _nextUpdate = DateTime.UtcNow.AddSeconds(1); - await fileStream.WriteAsync(buffer.AsMemory(0, readSize), innerCts.Token); + timeout = DateTimeOffset.UtcNow.AddHours(1); - _bytesDone = fileStream.Length; + DownloadProgress?.Invoke(this, + new DownloadProgressEventArgs + { + Speed = destinationStream.Speed, + BytesDone = _bytesDone, + BytesTotal = _bytesTotal + }); - if (DateTime.UtcNow > _nextUpdate) + if (Settings.Get.DownloadMaxSpeed != speedLimit) { - _nextUpdate = DateTime.UtcNow.AddSeconds(1); - - timeout = DateTimeOffset.UtcNow.AddHours(1); - - DownloadProgress?.Invoke(this, - new DownloadProgressEventArgs - { - Speed = destinationStream.Speed, - BytesDone = _bytesDone, - BytesTotal = _bytesTotal - }); - - if (Settings.Get.DownloadMaxSpeed != speedLimit) - { - speedLimit = Settings.Get.DownloadMaxSpeed; - destinationStream.BandwidthLimit = speedLimit * 1000L * 1000L; - } + speedLimit = Settings.Get.DownloadMaxSpeed; + destinationStream.BandwidthLimit = speedLimit * 1000L * 1000L; } } } - - break; - } - catch (IOException) - { - await Task.Delay(1000); - } - catch (WebException) - { - await Task.Delay(1000); } + + break; } - - if (_cancelled) + catch (IOException) { - throw new Exception("Download cancelled"); + await Task.Delay(1000); } - - if (timeout <= DateTimeOffset.UtcNow) + catch (WebException) { - throw new Exception($"Download timed out"); + await Task.Delay(1000); } - - DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs()); - } - catch (Exception ex) - { - DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs - { - Error = ex.Message - }); - } - } - - private async Task GetContentSize() - { - var httpClient = new HttpClient - { - Timeout = TimeSpan.FromSeconds(5) - }; - - var responseHeaders = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, _uri)); - - if (!responseHeaders.IsSuccessStatusCode) - { - return -1; } - return responseHeaders.Content.Headers.ContentLength ?? -1; - } + if (_cancelled) + { + throw new Exception("Download cancelled"); + } - public Task Pause() - { - return Task.CompletedTask; - } + if (timeout <= DateTimeOffset.UtcNow) + { + throw new Exception($"Download timed out"); + } - public Task Resume() + DownloadComplete.Invoke(this, new DownloadCompleteEventArgs()); + } + catch (Exception ex) { - return Task.CompletedTask; + DownloadComplete.Invoke(this, new DownloadCompleteEventArgs + { + Error = ex.Message + }); } } -} + + private async Task GetContentSize() + { + var httpClient = new HttpClient + { + Timeout = TimeSpan.FromSeconds(5) + }; + + var responseHeaders = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, _uri)); + + if (!responseHeaders.IsSuccessStatusCode) + { + return -1; + } + + return responseHeaders.Content.Headers.ContentLength ?? -1; + } + + public Task Pause() + { + return Task.CompletedTask; + } + + public Task Resume() + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/Downloads.cs b/server/RdtClient.Service/Services/Downloads.cs index bddb8c5..be55e0b 100644 --- a/server/RdtClient.Service/Services/Downloads.cs +++ b/server/RdtClient.Service/Services/Downloads.cs @@ -1,98 +1,94 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using RdtClient.Data.Data; +using RdtClient.Data.Data; using Download = RdtClient.Data.Models.Data.Download; -namespace RdtClient.Service.Services +namespace RdtClient.Service.Services; + +public class Downloads { - public class Downloads + private readonly DownloadData _downloadData; + + public Downloads(DownloadData downloadData) { - private readonly DownloadData _downloadData; - - public Downloads(DownloadData downloadData) - { - _downloadData = downloadData; - } - - public async Task> GetForTorrent(Guid torrentId) - { - return await _downloadData.GetForTorrent(torrentId); - } - - public async Task GetById(Guid downloadId) - { - return await _downloadData.GetById(downloadId); - } - - public async Task Get(Guid torrentId, String path) - { - return await _downloadData.Get(torrentId, path); - } - - public async Task Add(Guid torrentId, String path) - { - return await _downloadData.Add(torrentId, path); - } - - public async Task UpdateUnrestrictedLink(Guid downloadId, String unrestrictedLink) - { - await _downloadData.UpdateUnrestrictedLink(downloadId, unrestrictedLink); - } - - public async Task UpdateDownloadStarted(Guid downloadId, DateTimeOffset? dateTime) - { - await _downloadData.UpdateDownloadStarted(downloadId, dateTime); - } - - public async Task UpdateDownloadFinished(Guid downloadId, DateTimeOffset? dateTime) - { - await _downloadData.UpdateDownloadFinished(downloadId, dateTime); - } - - public async Task UpdateUnpackingQueued(Guid downloadId, DateTimeOffset? dateTime) - { - await _downloadData.UpdateUnpackingQueued(downloadId, dateTime); - } - - public async Task UpdateUnpackingStarted(Guid downloadId, DateTimeOffset? dateTime) - { - await _downloadData.UpdateUnpackingStarted(downloadId, dateTime); - } - - public async Task UpdateUnpackingFinished(Guid downloadId, DateTimeOffset? dateTime) - { - await _downloadData.UpdateUnpackingFinished(downloadId, dateTime); - } - - public async Task UpdateCompleted(Guid downloadId, DateTimeOffset? dateTime) - { - await _downloadData.UpdateCompleted(downloadId, dateTime); - } - - public async Task UpdateError(Guid downloadId, String error) - { - await _downloadData.UpdateError(downloadId, error); - } - - public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount) - { - await _downloadData.UpdateRetryCount(downloadId, retryCount); - } - - public async Task UpdateRemoteId(Guid downloadId, String remoteId) - { - await _downloadData.UpdateRemoteId(downloadId, remoteId); - } - - public async Task DeleteForTorrent(Guid torrentId) - { - await _downloadData.DeleteForTorrent(torrentId); - } - - public async Task Reset(Guid downloadId) - { - await _downloadData.Reset(downloadId); - } + _downloadData = downloadData; } -} + + public async Task> GetForTorrent(Guid torrentId) + { + return await _downloadData.GetForTorrent(torrentId); + } + + public async Task GetById(Guid downloadId) + { + return await _downloadData.GetById(downloadId); + } + + public async Task Get(Guid torrentId, String path) + { + return await _downloadData.Get(torrentId, path); + } + + public async Task Add(Guid torrentId, String path) + { + return await _downloadData.Add(torrentId, path); + } + + public async Task UpdateUnrestrictedLink(Guid downloadId, String unrestrictedLink) + { + await _downloadData.UpdateUnrestrictedLink(downloadId, unrestrictedLink); + } + + public async Task UpdateDownloadStarted(Guid downloadId, DateTimeOffset? dateTime) + { + await _downloadData.UpdateDownloadStarted(downloadId, dateTime); + } + + public async Task UpdateDownloadFinished(Guid downloadId, DateTimeOffset? dateTime) + { + await _downloadData.UpdateDownloadFinished(downloadId, dateTime); + } + + public async Task UpdateUnpackingQueued(Guid downloadId, DateTimeOffset? dateTime) + { + await _downloadData.UpdateUnpackingQueued(downloadId, dateTime); + } + + public async Task UpdateUnpackingStarted(Guid downloadId, DateTimeOffset? dateTime) + { + await _downloadData.UpdateUnpackingStarted(downloadId, dateTime); + } + + public async Task UpdateUnpackingFinished(Guid downloadId, DateTimeOffset? dateTime) + { + await _downloadData.UpdateUnpackingFinished(downloadId, dateTime); + } + + public async Task UpdateCompleted(Guid downloadId, DateTimeOffset? dateTime) + { + await _downloadData.UpdateCompleted(downloadId, dateTime); + } + + public async Task UpdateError(Guid downloadId, String error) + { + await _downloadData.UpdateError(downloadId, error); + } + + public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount) + { + await _downloadData.UpdateRetryCount(downloadId, retryCount); + } + + public async Task UpdateRemoteId(Guid downloadId, String remoteId) + { + await _downloadData.UpdateRemoteId(downloadId, remoteId); + } + + public async Task DeleteForTorrent(Guid torrentId) + { + await _downloadData.DeleteForTorrent(torrentId); + } + + public async Task Reset(Guid downloadId) + { + await _downloadData.Reset(downloadId); + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/QBittorrent.cs b/server/RdtClient.Service/Services/QBittorrent.cs index 24b7427..5e42893 100644 --- a/server/RdtClient.Service/Services/QBittorrent.cs +++ b/server/RdtClient.Service/Services/QBittorrent.cs @@ -1,373 +1,232 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using RdtClient.Data.Enums; +using RdtClient.Data.Enums; using RdtClient.Data.Models.Data; using RdtClient.Data.Models.QBittorrent; -using RdtClient.Data.Models.QBittorrent.QuickType; -namespace RdtClient.Service.Services +namespace RdtClient.Service.Services; + +public class QBittorrent { - public class QBittorrent + private readonly Authentication _authentication; + private readonly Settings _settings; + private readonly Torrents _torrents; + private readonly Downloads _downloads; + + public QBittorrent(Settings settings, Authentication authentication, Torrents torrents, Downloads downloads) { - private readonly Authentication _authentication; - private readonly Settings _settings; - private readonly Torrents _torrents; - private readonly Downloads _downloads; + _settings = settings; + _authentication = authentication; + _torrents = torrents; + _downloads = downloads; + } - public QBittorrent(Settings settings, Authentication authentication, Torrents torrents, Downloads downloads) + public async Task AuthLogin(String userName, String password) + { + var login = await _authentication.Login(userName, password); + + return login.Succeeded; + } + + public async Task AuthLogout() + { + await _authentication.Logout(); + } + + public async Task AppPreferences() + { + var preferences = new AppPreferences { - _settings = settings; - _authentication = authentication; - _torrents = torrents; - _downloads = downloads; + AddTrackers = "", + AddTrackersEnabled = false, + AltDlLimit = 10240, + AltUpLimit = 10240, + AlternativeWebuiEnabled = false, + AlternativeWebuiPath = "", + AnnounceIp = "", + AnnounceToAllTiers = true, + AnnounceToAllTrackers = false, + AnonymousMode = false, + AsyncIoThreads = 4, + AutoDeleteMode = 0, + AutoTmmEnabled = false, + AutorunEnabled = false, + AutorunProgram = "", + BannedIPs = "", + BittorrentProtocol = 0, + BypassAuthSubnetWhitelist = "", + BypassAuthSubnetWhitelistEnabled = false, + BypassLocalAuth = false, + CategoryChangedTmmEnabled = false, + CheckingMemoryUse = 32, + CreateSubfolderEnabled = true, + CurrentInterfaceAddress = "", + CurrentNetworkInterface = "", + Dht = true, + DiskCache = -1, + DiskCacheTtl = 60, + DlLimit = 0, + DontCountSlowTorrents = false, + DyndnsDomain = "changeme.dyndns.org", + DyndnsEnabled = false, + DyndnsPassword = "", + DyndnsService = 0, + DyndnsUsername = "", + EmbeddedTrackerPort = 9000, + EnableCoalesceReadWrite = true, + EnableEmbeddedTracker = false, + EnableMultiConnectionsFromSameIp = false, + EnableOsCache = true, + EnablePieceExtentAffinity = false, + EnableSuperSeeding = false, + EnableUploadSuggestions = false, + Encryption = 0, + ExportDir = "", + ExportDirFin = "", + FilePoolSize = 40, + IncompleteFilesExt = false, + IpFilterEnabled = false, + IpFilterPath = "", + IpFilterTrackers = false, + LimitLanPeers = true, + LimitTcpOverhead = false, + LimitUtpRate = true, + ListenPort = 31193, + Locale = "en", + Lsd = true, + MailNotificationAuthEnabled = false, + MailNotificationEmail = "", + MailNotificationEnabled = false, + MailNotificationPassword = "", + MailNotificationSender = "qBittorrentNotification@example.com", + MailNotificationSmtp = "smtp.changeme.com", + MailNotificationSslEnabled = false, + MailNotificationUsername = "", + MaxActiveDownloads = 3, + MaxActiveTorrents = 5, + MaxActiveUploads = 3, + MaxConnec = 500, + MaxConnecPerTorrent = 100, + MaxRatio = -1, + MaxRatioAct = 0, + MaxRatioEnabled = false, + MaxSeedingTime = -1, + MaxSeedingTimeEnabled = false, + MaxUploads = -1, + MaxUploadsPerTorrent = -1, + OutgoingPortsMax = 0, + OutgoingPortsMin = 0, + Pex = true, + PreallocateAll = false, + ProxyAuthEnabled = false, + ProxyIp = "0.0.0.0", + ProxyPassword = "", + ProxyPeerConnections = false, + ProxyPort = 8080, + ProxyTorrentsOnly = false, + ProxyType = 0, + ProxyUsername = "", + QueueingEnabled = false, + RandomPort = false, + RecheckCompletedTorrents = false, + ResolvePeerCountries = true, + RssAutoDownloadingEnabled = false, + RssMaxArticlesPerFeed = 50, + RssProcessingEnabled = false, + RssRefreshInterval = 30, + SavePath = "", + SavePathChangedTmmEnabled = false, + SaveResumeDataInterval = 60, + ScanDirs = new ScanDirs(), + ScheduleFromHour = 8, + ScheduleFromMin = 0, + ScheduleToHour = 20, + ScheduleToMin = 0, + SchedulerDays = 0, + SchedulerEnabled = false, + SendBufferLowWatermark = 10, + SendBufferWatermark = 500, + SendBufferWatermarkFactor = 50, + SlowTorrentDlRateThreshold = 2, + SlowTorrentInactiveTimer = 60, + SlowTorrentUlRateThreshold = 2, + SocketBacklogSize = 30, + StartPausedEnabled = false, + StopTrackerTimeout = 1, + TempPath = "", + TempPathEnabled = false, + TorrentChangedTmmEnabled = true, + UpLimit = 0, + UploadChokingAlgorithm = 1, + UploadSlotsBehavior = 0, + Upnp = true, + UpnpLeaseDuration = 0, + UseHttps = false, + UtpTcpMixedMode = 0, + WebUiAddress = "*", + WebUiBanDuration = 3600, + WebUiClickjackingProtectionEnabled = true, + WebUiCsrfProtectionEnabled = true, + WebUiDomainList = "*", + WebUiHostHeaderValidationEnabled = true, + WebUiHttpsCertPath = "", + WebUiHttpsKeyPath = "", + WebUiMaxAuthFailCount = 5, + WebUiPort = 8080, + WebUiSecureCookieEnabled = true, + WebUiSessionTimeout = 3600, + WebUiUpnp = false, + WebUiUsername = "" + }; + + var savePath = AppDefaultSavePath(); + + preferences.SavePath = savePath; + preferences.TempPath = $"{savePath}temp{Path.DirectorySeparatorChar}"; + + var user = await _authentication.GetUser(); + + if (user != null) + { + preferences.WebUiUsername = user.UserName; } - public async Task AuthLogin(String userName, String password) + return preferences; + } + + public String AppDefaultSavePath() + { + var downloadPath = Settings.Get.MappedPath; + + downloadPath = downloadPath.TrimEnd('\\') + .TrimEnd('/'); + + downloadPath += Path.DirectorySeparatorChar; + + return downloadPath; + } + + public async Task> TorrentInfo() + { + var savePath = AppDefaultSavePath(); + + var results = new List(); + + var torrents = await _torrents.Get(); + + var prio = 0; + + foreach (var torrent in torrents) { - var login = await _authentication.Login(userName, password); - - return login.Succeeded; - } - - public async Task AuthLogout() - { - await _authentication.Logout(); - } - - public async Task AppPreferences() - { - var preferences = new AppPreferences - { - AddTrackers = "", - AddTrackersEnabled = false, - AltDlLimit = 10240, - AltUpLimit = 10240, - AlternativeWebuiEnabled = false, - AlternativeWebuiPath = "", - AnnounceIp = "", - AnnounceToAllTiers = true, - AnnounceToAllTrackers = false, - AnonymousMode = false, - AsyncIoThreads = 4, - AutoDeleteMode = 0, - AutoTmmEnabled = false, - AutorunEnabled = false, - AutorunProgram = "", - BannedIPs = "", - BittorrentProtocol = 0, - BypassAuthSubnetWhitelist = "", - BypassAuthSubnetWhitelistEnabled = false, - BypassLocalAuth = false, - CategoryChangedTmmEnabled = false, - CheckingMemoryUse = 32, - CreateSubfolderEnabled = true, - CurrentInterfaceAddress = "", - CurrentNetworkInterface = "", - Dht = true, - DiskCache = -1, - DiskCacheTtl = 60, - DlLimit = 0, - DontCountSlowTorrents = false, - DyndnsDomain = "changeme.dyndns.org", - DyndnsEnabled = false, - DyndnsPassword = "", - DyndnsService = 0, - DyndnsUsername = "", - EmbeddedTrackerPort = 9000, - EnableCoalesceReadWrite = true, - EnableEmbeddedTracker = false, - EnableMultiConnectionsFromSameIp = false, - EnableOsCache = true, - EnablePieceExtentAffinity = false, - EnableSuperSeeding = false, - EnableUploadSuggestions = false, - Encryption = 0, - ExportDir = "", - ExportDirFin = "", - FilePoolSize = 40, - IncompleteFilesExt = false, - IpFilterEnabled = false, - IpFilterPath = "", - IpFilterTrackers = false, - LimitLanPeers = true, - LimitTcpOverhead = false, - LimitUtpRate = true, - ListenPort = 31193, - Locale = "en", - Lsd = true, - MailNotificationAuthEnabled = false, - MailNotificationEmail = "", - MailNotificationEnabled = false, - MailNotificationPassword = "", - MailNotificationSender = "qBittorrentNotification@example.com", - MailNotificationSmtp = "smtp.changeme.com", - MailNotificationSslEnabled = false, - MailNotificationUsername = "", - MaxActiveDownloads = 3, - MaxActiveTorrents = 5, - MaxActiveUploads = 3, - MaxConnec = 500, - MaxConnecPerTorrent = 100, - MaxRatio = -1, - MaxRatioAct = 0, - MaxRatioEnabled = false, - MaxSeedingTime = -1, - MaxSeedingTimeEnabled = false, - MaxUploads = -1, - MaxUploadsPerTorrent = -1, - OutgoingPortsMax = 0, - OutgoingPortsMin = 0, - Pex = true, - PreallocateAll = false, - ProxyAuthEnabled = false, - ProxyIp = "0.0.0.0", - ProxyPassword = "", - ProxyPeerConnections = false, - ProxyPort = 8080, - ProxyTorrentsOnly = false, - ProxyType = 0, - ProxyUsername = "", - QueueingEnabled = false, - RandomPort = false, - RecheckCompletedTorrents = false, - ResolvePeerCountries = true, - RssAutoDownloadingEnabled = false, - RssMaxArticlesPerFeed = 50, - RssProcessingEnabled = false, - RssRefreshInterval = 30, - SavePath = "", - SavePathChangedTmmEnabled = false, - SaveResumeDataInterval = 60, - ScanDirs = new ScanDirs(), - ScheduleFromHour = 8, - ScheduleFromMin = 0, - ScheduleToHour = 20, - ScheduleToMin = 0, - SchedulerDays = 0, - SchedulerEnabled = false, - SendBufferLowWatermark = 10, - SendBufferWatermark = 500, - SendBufferWatermarkFactor = 50, - SlowTorrentDlRateThreshold = 2, - SlowTorrentInactiveTimer = 60, - SlowTorrentUlRateThreshold = 2, - SocketBacklogSize = 30, - StartPausedEnabled = false, - StopTrackerTimeout = 1, - TempPath = "", - TempPathEnabled = false, - TorrentChangedTmmEnabled = true, - UpLimit = 0, - UploadChokingAlgorithm = 1, - UploadSlotsBehavior = 0, - Upnp = true, - UpnpLeaseDuration = 0, - UseHttps = false, - UtpTcpMixedMode = 0, - WebUiAddress = "*", - WebUiBanDuration = 3600, - WebUiClickjackingProtectionEnabled = true, - WebUiCsrfProtectionEnabled = true, - WebUiDomainList = "*", - WebUiHostHeaderValidationEnabled = true, - WebUiHttpsCertPath = "", - WebUiHttpsKeyPath = "", - WebUiMaxAuthFailCount = 5, - WebUiPort = 8080, - WebUiSecureCookieEnabled = true, - WebUiSessionTimeout = 3600, - WebUiUpnp = false, - WebUiUsername = "" - }; - - var savePath = AppDefaultSavePath(); - - preferences.SavePath = savePath; - preferences.TempPath = $"{savePath}temp{Path.DirectorySeparatorChar}"; - - var user = await _authentication.GetUser(); - - if (user != null) - { - preferences.WebUiUsername = user.UserName; - } - - return preferences; - } - - public String AppDefaultSavePath() - { - var downloadPath = Settings.Get.MappedPath; - - downloadPath = downloadPath.TrimEnd('\\') - .TrimEnd('/'); - - downloadPath += Path.DirectorySeparatorChar; - - return downloadPath; - } - - public async Task> TorrentInfo() - { - var savePath = AppDefaultSavePath(); - - var results = new List(); - - var torrents = await _torrents.Get(); - - var prio = 0; - - foreach (var torrent in torrents) - { - var downloadPath = savePath; - - if (!String.IsNullOrWhiteSpace(torrent.Category)) - { - downloadPath = Path.Combine(downloadPath, torrent.Category); - } - - var torrentPath = downloadPath; - if (!String.IsNullOrWhiteSpace(torrent.RdName)) - { - torrentPath = Path.Combine(downloadPath, torrent.RdName) + Path.DirectorySeparatorChar; - } - - var bytesDone = torrent.RdProgress; - var bytesTotal = torrent.RdSize; - var speed = torrent.RdSpeed ?? 0; - - if (torrent.Downloads.Count > 0) - { - bytesDone = torrent.Downloads.Sum(m => m.BytesDone); - bytesTotal = torrent.Downloads.Sum(m => m.BytesTotal); - speed = (Int32) torrent.Downloads.Average(m => m.Speed); - } - - var progress = (bytesDone / (Single)bytesTotal); - - if (!Single.IsNormal(progress)) - { - progress = 0; - } - - var result = new TorrentInfo - { - AddedOn = torrent.Added.ToUnixTimeSeconds(), - AmountLeft = bytesTotal - bytesDone, - AutoTmm = false, - Availability = 2, - Category = torrent.Category ?? "", - Completed = bytesDone, - CompletionOn = torrent.Completed?.ToUnixTimeSeconds(), - ContentPath = torrentPath, - DlLimit = -1, - Dlspeed = speed, - Downloaded = bytesDone, - DownloadedSession = bytesDone, - Eta = 0, - FlPiecePrio = false, - ForceStart = false, - Hash = torrent.Hash, - LastActivity = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), - MagnetUri = "", - MaxRatio = -1, - MaxSeedingTime = -1, - Name = torrent.RdName, - NumComplete = 10, - NumIncomplete = 0, - NumLeechs = 100, - NumSeeds = 100, - Priority = ++prio, - Progress = progress, - Ratio = 1, - RatioLimit = 1, - SavePath = downloadPath, - SeedingTimeLimit = 1, - SeenComplete = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), - SeqDl = false, - Size = bytesTotal, - State = "downloading", - SuperSeeding = false, - Tags = "", - TimeActive = (Int64) (DateTimeOffset.UtcNow - torrent.Added).TotalSeconds, - TotalSize = bytesTotal, - Tracker = "udp://tracker.opentrackr.org:1337", - UpLimit = -1, - Uploaded = bytesDone, - UploadedSession = bytesDone, - Upspeed = speed - }; - - if (torrent.Retry != null) - { - result.State = "downloading"; - } - else if (!String.IsNullOrWhiteSpace(torrent.Error)) - { - result.State = "error"; - } - else if (torrent.Completed.HasValue) - { - var allDownloadsComplete = torrent.Downloads.All(m => m.Completed.HasValue); - var hasDownloadsWithErrors = torrent.Downloads.Any(m => m.Error != null); - - if (torrent.Downloads.Count == 0 || hasDownloadsWithErrors || torrent.RdStatus == TorrentStatus.Error) - { - result.State = "error"; - } - else if (allDownloadsComplete) - { - result.State = "pausedUP"; - } - } - - results.Add(result); - } - - return results; - } - - public async Task> TorrentFileContents(String hash) - { - var results = new List(); - - var torrent = await _torrents.GetByHash(hash); - - if (torrent == null) - { - return null; - } - - foreach (var file in torrent.Files.Where(m => m.Selected)) - { - var result = new TorrentFileItem - { - Name = file.Path - }; - - results.Add(result); - } - - return results; - } - - public async Task TorrentProperties(String hash) - { - var savePath = AppDefaultSavePath(); - - var torrent = await _torrents.GetByHash(hash); - - if (torrent == null) - { - return null; - } + var downloadPath = savePath; if (!String.IsNullOrWhiteSpace(torrent.Category)) { - savePath = Path.Combine(savePath, torrent.Category); + downloadPath = Path.Combine(downloadPath, torrent.Category); + } + + var torrentPath = downloadPath; + if (!String.IsNullOrWhiteSpace(torrent.RdName)) + { + torrentPath = Path.Combine(downloadPath, torrent.RdName) + Path.DirectorySeparatorChar; } var bytesDone = torrent.RdProgress; @@ -381,242 +240,376 @@ namespace RdtClient.Service.Services speed = (Int32) torrent.Downloads.Average(m => m.Speed); } - var result = new TorrentProperties + var progress = (bytesDone / (Single)bytesTotal); + + if (!Single.IsNormal(progress)) { - AdditionDate = torrent.Added.ToUnixTimeSeconds(), - Comment = "RealDebridClient ", - CompletionDate = torrent.Completed?.ToUnixTimeSeconds() ?? -1, - CreatedBy = "RealDebridClient ", - CreationDate = torrent.Added.ToUnixTimeSeconds(), + progress = 0; + } + + var result = new TorrentInfo + { + AddedOn = torrent.Added.ToUnixTimeSeconds(), + AmountLeft = bytesTotal - bytesDone, + AutoTmm = false, + Availability = 2, + Category = torrent.Category ?? "", + Completed = bytesDone, + CompletionOn = torrent.Completed?.ToUnixTimeSeconds(), + ContentPath = torrentPath, DlLimit = -1, - DlSpeed = speed, - DlSpeedAvg = speed, + Dlspeed = speed, + Downloaded = bytesDone, + DownloadedSession = bytesDone, Eta = 0, - LastSeen = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), - NbConnections = 0, - NbConnectionsLimit = 100, - Peers = 0, - PeersTotal = 2, - PieceSize = bytesTotal, - PiecesHave = torrent.Downloads.Count(m => m.Completed.HasValue), - PiecesNum = torrent.Downloads.Count, - Reannounce = 0, - SavePath = savePath, - SeedingTime = 1, - Seeds = 100, - SeedsTotal = 100, - ShareRatio = 9999, - TimeElapsed = (Int64) (DateTimeOffset.UtcNow - torrent.Added).TotalSeconds, - TotalDownloaded = bytesDone, - TotalDownloadedSession = bytesDone, + FlPiecePrio = false, + ForceStart = false, + Hash = torrent.Hash, + LastActivity = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + MagnetUri = "", + MaxRatio = -1, + MaxSeedingTime = -1, + Name = torrent.RdName, + NumComplete = 10, + NumIncomplete = 0, + NumLeechs = 100, + NumSeeds = 100, + Priority = ++prio, + Progress = progress, + Ratio = 1, + RatioLimit = 1, + SavePath = downloadPath, + SeedingTimeLimit = 1, + SeenComplete = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + SeqDl = false, + Size = bytesTotal, + State = "downloading", + SuperSeeding = false, + Tags = "", + TimeActive = (Int64) (DateTimeOffset.UtcNow - torrent.Added).TotalSeconds, TotalSize = bytesTotal, - TotalUploaded = bytesDone, - TotalUploadedSession = bytesDone, - TotalWasted = 0, + Tracker = "udp://tracker.opentrackr.org:1337", UpLimit = -1, - UpSpeed = speed, - UpSpeedAvg = speed + Uploaded = bytesDone, + UploadedSession = bytesDone, + Upspeed = speed }; - return result; - } - - public async Task TorrentsDelete(String hash, Boolean deleteFiles) - { - var torrent = await _torrents.GetByHash(hash); - - if (torrent == null) + if (torrent.Retry != null) { - return; + result.State = "downloading"; } - - await _torrents.Delete(torrent.TorrentId, true, true, deleteFiles); - } - - public async Task TorrentsAddMagnet(String magnetLink, String category, Int32? priority) - { - var torrent = new Torrent + else if (!String.IsNullOrWhiteSpace(torrent.Error)) { - Category = category, - DownloadAction = Settings.Get.OnlyDownloadAvailableFiles == 1 ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll, - FinishedAction = TorrentFinishedAction.None, - DownloadMinSize = Settings.Get.MinFileSize, - TorrentRetryAttempts = Settings.Get.TorrentRetryAttempts, - DownloadRetryAttempts = Settings.Get.DownloadRetryAttempts, - DeleteOnError = Settings.Get.DeleteOnError, - Lifetime = Settings.Get.TorrentLifetime, - Priority = priority - }; - - await _torrents.UploadMagnet(magnetLink, torrent); - } - - public async Task TorrentsAddFile(Byte[] fileBytes, String category, Int32? priority) - { - var torrent = new Torrent - { - Category = category, - DownloadAction = Settings.Get.OnlyDownloadAvailableFiles == 1 ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll, - FinishedAction = TorrentFinishedAction.None, - DownloadMinSize = Settings.Get.MinFileSize, - TorrentRetryAttempts = Settings.Get.TorrentRetryAttempts, - DownloadRetryAttempts = Settings.Get.DownloadRetryAttempts, - DeleteOnError = Settings.Get.DeleteOnError, - Lifetime = Settings.Get.TorrentLifetime, - Priority = priority - }; - - await _torrents.UploadFile(fileBytes, torrent); - } - - public async Task TorrentsSetCategory(String hash, String category) - { - await _torrents.UpdateCategory(hash, category); - } - - public async Task> TorrentsCategories() - { - var torrents = await _torrents.Get(); - - var torrentsToGroup = torrents.Where(m => !String.IsNullOrWhiteSpace(m.Category)) - .Select(m => m.Category.ToLower()) - .ToList(); - - var categories = Settings.Get - .Categories - .Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - - torrentsToGroup.AddRange(categories); - - var results = new Dictionary(); - - if (torrentsToGroup.Count > 0) - { - results = torrentsToGroup.Distinct(StringComparer.CurrentCultureIgnoreCase) - .ToDictionary(m => m, - m => new TorrentCategory - { - Name = m, - SavePath = Path.Combine(AppDefaultSavePath(), m) - }); + result.State = "error"; } - - return results; - } - - public async Task CategoryCreate(String category) - { - var categoriesSetting = Settings.Get.Categories; - - if (String.IsNullOrWhiteSpace(categoriesSetting)) + else if (torrent.Completed.HasValue) { - categoriesSetting = category; - } - else - { - var categoryList = categoriesSetting - .Split(",", StringSplitOptions.RemoveEmptyEntries) - .Distinct(StringComparer.CurrentCultureIgnoreCase) - .ToList(); + var allDownloadsComplete = torrent.Downloads.All(m => m.Completed.HasValue); + var hasDownloadsWithErrors = torrent.Downloads.Any(m => m.Error != null); - if (!categoryList.Contains(category)) + if (torrent.Downloads.Count == 0 || hasDownloadsWithErrors || torrent.RdStatus == TorrentStatus.Error) { - categoryList.Add(category); + result.State = "error"; + } + else if (allDownloadsComplete) + { + result.State = "pausedUP"; } - - categoriesSetting = String.Join(",", categoryList); } - await _settings.UpdateString("Categories", categoriesSetting); + results.Add(result); } - public async Task CategoryRemove(String category) + return results; + } + + public async Task> TorrentFileContents(String hash) + { + var results = new List(); + + var torrent = await _torrents.GetByHash(hash); + + if (torrent == null) { - var categoriesSetting = Settings.Get.Categories; + return null; + } - if (String.IsNullOrWhiteSpace(categoriesSetting)) + foreach (var file in torrent.Files.Where(m => m.Selected)) + { + var result = new TorrentFileItem { - return; + Name = file.Path + }; + + results.Add(result); + } + + return results; + } + + public async Task TorrentProperties(String hash) + { + var savePath = AppDefaultSavePath(); + + var torrent = await _torrents.GetByHash(hash); + + if (torrent == null) + { + return null; + } + + if (!String.IsNullOrWhiteSpace(torrent.Category)) + { + savePath = Path.Combine(savePath, torrent.Category); + } + + var bytesDone = torrent.RdProgress; + var bytesTotal = torrent.RdSize; + var speed = torrent.RdSpeed ?? 0; + + if (torrent.Downloads.Count > 0) + { + bytesDone = torrent.Downloads.Sum(m => m.BytesDone); + bytesTotal = torrent.Downloads.Sum(m => m.BytesTotal); + speed = (Int32) torrent.Downloads.Average(m => m.Speed); + } + + var result = new TorrentProperties + { + AdditionDate = torrent.Added.ToUnixTimeSeconds(), + Comment = "RealDebridClient ", + CompletionDate = torrent.Completed?.ToUnixTimeSeconds() ?? -1, + CreatedBy = "RealDebridClient ", + CreationDate = torrent.Added.ToUnixTimeSeconds(), + DlLimit = -1, + DlSpeed = speed, + DlSpeedAvg = speed, + Eta = 0, + LastSeen = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + NbConnections = 0, + NbConnectionsLimit = 100, + Peers = 0, + PeersTotal = 2, + PieceSize = bytesTotal, + PiecesHave = torrent.Downloads.Count(m => m.Completed.HasValue), + PiecesNum = torrent.Downloads.Count, + Reannounce = 0, + SavePath = savePath, + SeedingTime = 1, + Seeds = 100, + SeedsTotal = 100, + ShareRatio = 9999, + TimeElapsed = (Int64) (DateTimeOffset.UtcNow - torrent.Added).TotalSeconds, + TotalDownloaded = bytesDone, + TotalDownloadedSession = bytesDone, + TotalSize = bytesTotal, + TotalUploaded = bytesDone, + TotalUploadedSession = bytesDone, + TotalWasted = 0, + UpLimit = -1, + UpSpeed = speed, + UpSpeedAvg = speed + }; + + return result; + } + + public async Task TorrentsDelete(String hash, Boolean deleteFiles) + { + var torrent = await _torrents.GetByHash(hash); + + if (torrent == null) + { + return; + } + + await _torrents.Delete(torrent.TorrentId, true, true, deleteFiles); + } + + public async Task TorrentsAddMagnet(String magnetLink, String category, Int32? priority) + { + var torrent = new Torrent + { + Category = category, + DownloadAction = Settings.Get.OnlyDownloadAvailableFiles == 1 ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll, + FinishedAction = TorrentFinishedAction.None, + DownloadMinSize = Settings.Get.MinFileSize, + TorrentRetryAttempts = Settings.Get.TorrentRetryAttempts, + DownloadRetryAttempts = Settings.Get.DownloadRetryAttempts, + DeleteOnError = Settings.Get.DeleteOnError, + Lifetime = Settings.Get.TorrentLifetime, + Priority = priority + }; + + await _torrents.UploadMagnet(magnetLink, torrent); + } + + public async Task TorrentsAddFile(Byte[] fileBytes, String category, Int32? priority) + { + var torrent = new Torrent + { + Category = category, + DownloadAction = Settings.Get.OnlyDownloadAvailableFiles == 1 ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll, + FinishedAction = TorrentFinishedAction.None, + DownloadMinSize = Settings.Get.MinFileSize, + TorrentRetryAttempts = Settings.Get.TorrentRetryAttempts, + DownloadRetryAttempts = Settings.Get.DownloadRetryAttempts, + DeleteOnError = Settings.Get.DeleteOnError, + Lifetime = Settings.Get.TorrentLifetime, + Priority = priority + }; + + await _torrents.UploadFile(fileBytes, torrent); + } + + public async Task TorrentsSetCategory(String hash, String category) + { + await _torrents.UpdateCategory(hash, category); + } + + public async Task> TorrentsCategories() + { + var torrents = await _torrents.Get(); + + var torrentsToGroup = torrents.Where(m => !String.IsNullOrWhiteSpace(m.Category)) + .Select(m => m.Category.ToLower()) + .ToList(); + + var categories = Settings.Get + .Categories + .Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + torrentsToGroup.AddRange(categories); + + var results = new Dictionary(); + + if (torrentsToGroup.Count > 0) + { + results = torrentsToGroup.Distinct(StringComparer.CurrentCultureIgnoreCase) + .ToDictionary(m => m, + m => new TorrentCategory + { + Name = m, + SavePath = Path.Combine(AppDefaultSavePath(), m) + }); + } + + return results; + } + + public async Task CategoryCreate(String category) + { + var categoriesSetting = Settings.Get.Categories; + + if (String.IsNullOrWhiteSpace(categoriesSetting)) + { + categoriesSetting = category; + } + else + { + var categoryList = categoriesSetting + .Split(",", StringSplitOptions.RemoveEmptyEntries) + .Distinct(StringComparer.CurrentCultureIgnoreCase) + .ToList(); + + if (!categoryList.Contains(category)) + { + categoryList.Add(category); } - var categoryList = categoriesSetting.Split(",", StringSplitOptions.RemoveEmptyEntries) - .Distinct(StringComparer.CurrentCultureIgnoreCase) - .ToList(); - - categoryList = categoryList.Where(m => m != category).ToList(); - categoriesSetting = String.Join(",", categoryList); - - await _settings.UpdateString("Categories", categoriesSetting); } - public async Task TorrentsTopPrio(String hash) + await _settings.UpdateString("Categories", categoriesSetting); + } + + public async Task CategoryRemove(String category) + { + var categoriesSetting = Settings.Get.Categories; + + if (String.IsNullOrWhiteSpace(categoriesSetting)) { - await _torrents.UpdatePriority(hash, 1); + return; } - public async Task TorrentPause(String hash) + var categoryList = categoriesSetting.Split(",", StringSplitOptions.RemoveEmptyEntries) + .Distinct(StringComparer.CurrentCultureIgnoreCase) + .ToList(); + + categoryList = categoryList.Where(m => m != category).ToList(); + + categoriesSetting = String.Join(",", categoryList); + + await _settings.UpdateString("Categories", categoriesSetting); + } + + public async Task TorrentsTopPrio(String hash) + { + await _torrents.UpdatePriority(hash, 1); + } + + public async Task TorrentPause(String hash) + { + var torrent = await _torrents.GetByHash(hash); + + if (torrent == null) { - var torrent = await _torrents.GetByHash(hash); - - if (torrent == null) - { - return; - } - - var downloads = await _downloads.GetForTorrent(torrent.TorrentId); - - foreach (var download in downloads) - { - if (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient)) - { - await downloadClient.Pause(); - } - } + return; } - public async Task TorrentResume(String hash) + var downloads = await _downloads.GetForTorrent(torrent.TorrentId); + + foreach (var download in downloads) { - var torrent = await _torrents.GetByHash(hash); - - if (torrent == null) + if (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient)) { - return; + await downloadClient.Pause(); } - - var downloads = await _downloads.GetForTorrent(torrent.TorrentId); - - foreach (var download in downloads) - { - if (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient)) - { - await downloadClient.Resume(); - } - } - } - - public async Task SyncMainData() - { - var torrents = await TorrentInfo(); - - var categories = await TorrentsCategories(); - - var activeDownloads = TorrentRunner.ActiveDownloadClients.Sum(m => m.Value.Speed); - - return new SyncMetaData - { - Categories = categories, - FullUpdate = true, - Rid = 0, - Tags = null, - Trackers = new Dictionary>(), - Torrents = torrents.ToDictionary(m => m.Hash, m => m), - ServerState = new SyncMetaDataServerState - { - DlInfoSpeed = activeDownloads, - UpInfoSpeed = 0 - } - }; } } -} + + public async Task TorrentResume(String hash) + { + var torrent = await _torrents.GetByHash(hash); + + if (torrent == null) + { + return; + } + + var downloads = await _downloads.GetForTorrent(torrent.TorrentId); + + foreach (var download in downloads) + { + if (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient)) + { + await downloadClient.Resume(); + } + } + } + + public async Task SyncMainData() + { + var torrents = await TorrentInfo(); + + var categories = await TorrentsCategories(); + + var activeDownloads = TorrentRunner.ActiveDownloadClients.Sum(m => m.Value.Speed); + + return new SyncMetaData + { + Categories = categories, + FullUpdate = true, + Rid = 0, + Tags = null, + Trackers = new Dictionary>(), + Torrents = torrents.ToDictionary(m => m.Hash, m => m), + ServerState = new SyncMetaDataServerState + { + DlInfoSpeed = activeDownloads, + UpInfoSpeed = 0 + } + }; + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/RdtHub.cs b/server/RdtClient.Service/Services/RdtHub.cs index 2094308..3db5c33 100644 --- a/server/RdtClient.Service/Services/RdtHub.cs +++ b/server/RdtClient.Service/Services/RdtHub.cs @@ -1,27 +1,23 @@ -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Threading.Tasks; +using System.Collections.Concurrent; using Microsoft.AspNetCore.SignalR; -namespace RdtClient.Service.Services +namespace RdtClient.Service.Services; + +public class RdtHub : Hub { - public class RdtHub : Hub + private static readonly ConcurrentDictionary Users = new(); + + public static Boolean HasConnections => Users.Any(); + + public override async Task OnConnectedAsync() { - private static readonly ConcurrentDictionary Users = new(); - - public static Boolean HasConnections => Users.Any(); - - public override async Task OnConnectedAsync() - { - Users.TryAdd(Context.ConnectionId, Context.ConnectionId); - await base.OnConnectedAsync(); - } - - public override async Task OnDisconnectedAsync(Exception exception) - { - Users.TryRemove(Context.ConnectionId, out _); - await base.OnDisconnectedAsync(exception); - } + Users.TryAdd(Context.ConnectionId, Context.ConnectionId); + await base.OnConnectedAsync(); } -} + + public override async Task OnDisconnectedAsync(Exception exception) + { + Users.TryRemove(Context.ConnectionId, out _); + await base.OnDisconnectedAsync(exception); + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/RemoteService.cs b/server/RdtClient.Service/Services/RemoteService.cs index 5ff41b7..c1103c6 100644 --- a/server/RdtClient.Service/Services/RemoteService.cs +++ b/server/RdtClient.Service/Services/RemoteService.cs @@ -1,36 +1,32 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.SignalR; +using Microsoft.AspNetCore.SignalR; -namespace RdtClient.Service.Services +namespace RdtClient.Service.Services; + +public class RemoteService { - public class RemoteService + private readonly IHubContext _hub; + private readonly Torrents _torrents; + + public RemoteService(IHubContext hub, Torrents torrents) { - private readonly IHubContext _hub; - private readonly Torrents _torrents; - - public RemoteService(IHubContext hub, Torrents torrents) - { - _hub = hub; - _torrents = torrents; - } - - public async Task Update() - { - var torrents = await _torrents.Get(); - - // Prevent infinite recursion when serializing - foreach (var file in torrents.SelectMany(torrent => torrent.Downloads)) - { - file.Torrent = null; - } - - await _hub.Clients.All.SendCoreAsync("update", - new Object[] - { - torrents - }); - } + _hub = hub; + _torrents = torrents; } -} + + public async Task Update() + { + var torrents = await _torrents.Get(); + + // Prevent infinite recursion when serializing + foreach (var file in torrents.SelectMany(torrent => torrent.Downloads)) + { + file.Torrent = null; + } + + await _hub.Clients.All.SendCoreAsync("update", + new Object[] + { + torrents + }); + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/Settings.cs b/server/RdtClient.Service/Services/Settings.cs index 2c80e0b..e618588 100644 --- a/server/RdtClient.Service/Services/Settings.cs +++ b/server/RdtClient.Service/Services/Settings.cs @@ -1,182 +1,180 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Threading.Tasks; +using System.Diagnostics; using Aria2NET; using RdtClient.Data.Data; using RdtClient.Data.Models.Data; using RdtClient.Data.Models.Internal; using RdtClient.Service.Helpers; +using Serilog.Core; +using Serilog.Events; -namespace RdtClient.Service.Services +namespace RdtClient.Service.Services; + +public class Settings { - public class Settings + public static readonly LoggingLevelSwitch LoggingLevelSwitch = new(LogEventLevel.Debug); + + private readonly SettingData _settingData; + + public Settings(SettingData settingData) { - private readonly SettingData _settingData; + _settingData = settingData; + } - public Settings(SettingData settingData) + public static DbSettings Get => SettingData.Get; + + public async Task> GetAll() + { + return await _settingData.GetAll(); + } + + public async Task Update(IList settings) + { + await _settingData.Update(settings); + } + + public async Task UpdateString(String key, String value) + { + await _settingData.UpdateString(key, value); + } + + public async Task TestPath(String path) + { + if (String.IsNullOrWhiteSpace(path)) { - _settingData = settingData; + throw new Exception("Path is not set"); } - public static DbSettings Get => SettingData.Get; + path = path.TrimEnd('/').TrimEnd('\\'); - public async Task> GetAll() + if (!Directory.Exists(path)) { - return await _settingData.GetAll(); + throw new Exception($"Path {path} does not exist"); } - public async Task Update(IList settings) - { - await _settingData.Update(settings); - } + var testFile = $"{path}/test.txt"; - public async Task UpdateString(String key, String value) - { - await _settingData.UpdateString(key, value); - } - - public async Task TestPath(String path) - { - if (String.IsNullOrWhiteSpace(path)) - { - throw new Exception("Path is not set"); - } - - path = path.TrimEnd('/').TrimEnd('\\'); - - if (!Directory.Exists(path)) - { - throw new Exception($"Path {path} does not exist"); - } - - var testFile = $"{path}/test.txt"; - - await File.WriteAllTextAsync(testFile, "RealDebridClient Test File, you can remove this file."); + await File.WriteAllTextAsync(testFile, "RealDebridClient Test File, you can remove this file."); - await FileHelper.Delete(testFile); - } + await FileHelper.Delete(testFile); + } - public async Task TestDownloadSpeed(CancellationToken cancellationToken) + public async Task TestDownloadSpeed(CancellationToken cancellationToken) + { + var downloadPath = Get.DownloadPath; + + var testFilePath = Path.Combine(downloadPath, "testDefault.rar"); + + await FileHelper.Delete(testFilePath); + + var download = new Download { - var downloadPath = Get.DownloadPath; - - var testFilePath = Path.Combine(downloadPath, "testDefault.rar"); - - await FileHelper.Delete(testFilePath); - - var download = new Download + Link = "https://34.download.real-debrid.com/speedtest/testDefault.rar", + Torrent = new Torrent { - Link = "https://34.download.real-debrid.com/speedtest/testDefault.rar", - Torrent = new Torrent - { - RdName = "" - } - }; + RdName = "" + } + }; - var downloadClient = new DownloadClient(download, download.Torrent, downloadPath); + var downloadClient = new DownloadClient(download, download.Torrent, downloadPath); - await downloadClient.Start(Get); + await downloadClient.Start(Get); - while (!downloadClient.Finished) - { + while (!downloadClient.Finished) + { #pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods that take one - // ReSharper disable once MethodSupportsCancellation - await Task.Delay(10); + // ReSharper disable once MethodSupportsCancellation + await Task.Delay(10); #pragma warning restore CA2016 // Forward the 'CancellationToken' parameter to methods that take one - if (cancellationToken.IsCancellationRequested) - { - await downloadClient.Cancel(); - } - - if (downloadClient.BytesDone > 1024 * 1024 * 50) - { - await downloadClient.Cancel(); - - break; - } + if (cancellationToken.IsCancellationRequested) + { + await downloadClient.Cancel(); } - await FileHelper.Delete(testFilePath); + if (downloadClient.BytesDone > 1024 * 1024 * 50) + { + await downloadClient.Cancel(); - await Clean(); - - return downloadClient.Speed; + break; + } } - public async Task TestWriteSpeed() + await FileHelper.Delete(testFilePath); + + await Clean(); + + return downloadClient.Speed; + } + + public async Task TestWriteSpeed() + { + var downloadPath = Get.DownloadPath; + + var testFilePath = Path.Combine(downloadPath, "test.tmp"); + + await FileHelper.Delete(testFilePath); + + const Int32 testFileSize = 64 * 1024 * 1024; + + var watch = new Stopwatch(); + + watch.Start(); + + var rnd = new Random(); + + await using var fileStream = new FileStream(testFilePath, FileMode.Create, FileAccess.Write, FileShare.Write); + + var buffer = new Byte[64 * 1024]; + + while (fileStream.Length < testFileSize) { - var downloadPath = Get.DownloadPath; + rnd.NextBytes(buffer); - var testFilePath = Path.Combine(downloadPath, "test.tmp"); - - await FileHelper.Delete(testFilePath); - - const Int32 testFileSize = 64 * 1024 * 1024; - - var watch = new Stopwatch(); - - watch.Start(); - - var rnd = new Random(); - - await using var fileStream = new FileStream(testFilePath, FileMode.Create, FileAccess.Write, FileShare.Write); - - var buffer = new Byte[64 * 1024]; - - while (fileStream.Length < testFileSize) - { - rnd.NextBytes(buffer); - - await fileStream.WriteAsync(buffer.AsMemory(0, buffer.Length)); - } + await fileStream.WriteAsync(buffer.AsMemory(0, buffer.Length)); + } - watch.Stop(); + watch.Stop(); - var writeSpeed = fileStream.Length / watch.Elapsed.TotalSeconds; + var writeSpeed = fileStream.Length / watch.Elapsed.TotalSeconds; - fileStream.Close(); + fileStream.Close(); - await FileHelper.Delete(testFilePath); + await FileHelper.Delete(testFilePath); - return writeSpeed; - } + return writeSpeed; + } - public async Task GetAria2cVersion(String url, String secret) + public async Task GetAria2cVersion(String url, String secret) + { + var client = new Aria2NetClient(url, secret); + + return await client.GetVersion(); + } + + public async Task Clean() + { + try { - var client = new Aria2NetClient(url, secret); + var tempPath = Get.TempPath; - return await client.GetVersion(); - } - - public async Task Clean() - { - try + if (!String.IsNullOrWhiteSpace(tempPath)) { - var tempPath = Get.TempPath; + var files = Directory.GetFiles(tempPath, "*.dsc", SearchOption.TopDirectoryOnly); - if (!String.IsNullOrWhiteSpace(tempPath)) + foreach (var file in files) { - var files = Directory.GetFiles(tempPath, "*.dsc", SearchOption.TopDirectoryOnly); - - foreach (var file in files) - { - await FileHelper.Delete(file); - } + await FileHelper.Delete(file); } } - catch - { - // ignored - } } - - public async Task ResetCache() + catch { - await _settingData.ResetCache(); + // ignored } } -} + + public async Task ResetCache() + { + await _settingData.ResetCache(); + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/Startup.cs b/server/RdtClient.Service/Services/Startup.cs index 8acd183..fe06155 100644 --- a/server/RdtClient.Service/Services/Startup.cs +++ b/server/RdtClient.Service/Services/Startup.cs @@ -1,32 +1,55 @@ -using System; -using System.Threading; -using System.Threading.Tasks; +using System.Reflection; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using RdtClient.Data.Data; +using Serilog; +using Serilog.Events; -namespace RdtClient.Service.Services +namespace RdtClient.Service.Services; + +public class Startup : IHostedService { - public class Startup : IHostedService + private readonly IServiceProvider _serviceProvider; + + public Startup(IServiceProvider serviceProvider) { - private readonly IServiceProvider _serviceProvider; - - public Startup(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - using var scope = _serviceProvider.CreateScope(); - - var settings = scope.ServiceProvider.GetRequiredService(); - - await settings.ResetCache(); - - await settings.Clean(); - } - - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + _serviceProvider = serviceProvider; } -} + public async Task StartAsync(CancellationToken cancellationToken) + { + using var scope = _serviceProvider.CreateScope(); + + var dbContext = scope.ServiceProvider.GetRequiredService(); + await dbContext.Database.MigrateAsync(cancellationToken); + await dbContext.Seed(); + + var logLevelSettingDb = await dbContext.Settings.FirstOrDefaultAsync(m => m.SettingId == "LogLevel", cancellationToken); + + var logLevelSetting = "Warning"; + + if (logLevelSettingDb != null) + { + logLevelSetting = logLevelSettingDb.Value; + } + + if (!Enum.TryParse(logLevelSetting, out var logLevel)) + { + logLevel = LogEventLevel.Warning; + } + + Settings.LoggingLevelSwitch.MinimumLevel = logLevel; + + var version = Assembly.GetEntryAssembly()?.GetName().Version; + Log.Warning($"Starting host on version {version}"); + + var settings = scope.ServiceProvider.GetRequiredService(); + + await settings.ResetCache(); + + await settings.Clean(); + } + + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/TaskRunner.cs b/server/RdtClient.Service/Services/TaskRunner.cs index faa385e..41dc6b4 100644 --- a/server/RdtClient.Service/Services/TaskRunner.cs +++ b/server/RdtClient.Service/Services/TaskRunner.cs @@ -1,49 +1,45 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -namespace RdtClient.Service.Services +namespace RdtClient.Service.Services; + +public class TaskRunner : BackgroundService { - public class TaskRunner : BackgroundService + private readonly ILogger _logger; + private readonly IServiceProvider _serviceProvider; + + public TaskRunner(ILogger logger, IServiceProvider serviceProvider) { - private readonly ILogger _logger; - private readonly IServiceProvider _serviceProvider; + _logger = logger; + _serviceProvider = serviceProvider; + } - public TaskRunner(ILogger logger, IServiceProvider serviceProvider) - { - _logger = logger; - _serviceProvider = serviceProvider; - } + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); - - using var scope = _serviceProvider.CreateScope(); - var torrentRunner = scope.ServiceProvider.GetRequiredService(); + using var scope = _serviceProvider.CreateScope(); + var torrentRunner = scope.ServiceProvider.GetRequiredService(); - _logger.LogInformation("TaskRunner started."); + _logger.LogInformation("TaskRunner started."); - await torrentRunner.Initialize(); + await torrentRunner.Initialize(); - while (!stoppingToken.IsCancellationRequested) + while (!stoppingToken.IsCancellationRequested) + { + try { - try - { - await torrentRunner.Tick(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Unexpected error occurred in TorrentDownloadManager.Tick"); - } - - await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken); + await torrentRunner.Tick(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unexpected error occurred in TorrentDownloadManager.Tick"); } - _logger.LogInformation("TaskRunner stopped."); + await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken); } + + _logger.LogInformation("TaskRunner stopped."); } } \ No newline at end of file diff --git a/server/RdtClient.Service/Services/ThrottledStream.cs b/server/RdtClient.Service/Services/ThrottledStream.cs index 0853645..c625097 100644 --- a/server/RdtClient.Service/Services/ThrottledStream.cs +++ b/server/RdtClient.Service/Services/ThrottledStream.cs @@ -1,211 +1,205 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; +namespace RdtClient.Service.Services; -namespace RdtClient.Service.Services +/// +/// Class for streaming data with throttling support. +/// Taken from Downloader: https://github.com/bezzad/Downloader +/// +public class ThrottledStream : Stream { + public Int64 Speed => (Int64)_bandwidth.AverageSpeed; + + private Bandwidth _bandwidth; + private Int64 _bandwidthLimit; + private readonly Stream _baseStream; + /// - /// Class for streaming data with throttling support. - /// Taken from Downloader: https://github.com/bezzad/Downloader + /// Initializes a new instance of the class. /// - public class ThrottledStream : Stream + /// The base stream. + /// The maximum bytes per second that can be transferred through the base stream. + /// Thrown when is a null reference. + /// Thrown when is a negative value. + public ThrottledStream(Stream baseStream, Int64 bandwidthLimit) { - public Int64 Speed => (Int64)_bandwidth.AverageSpeed; - - private Bandwidth _bandwidth; - private Int64 _bandwidthLimit; - private readonly Stream _baseStream; - - /// - /// Initializes a new instance of the class. - /// - /// The base stream. - /// The maximum bytes per second that can be transferred through the base stream. - /// Thrown when is a null reference. - /// Thrown when is a negative value. - public ThrottledStream(Stream baseStream, Int64 bandwidthLimit) + if (bandwidthLimit < 0) { - if (bandwidthLimit < 0) - { - throw new ArgumentOutOfRangeException(nameof(bandwidthLimit), - bandwidthLimit, - "The maximum number of bytes per second can't be negative."); - } - - _baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream)); - BandwidthLimit = bandwidthLimit; + throw new ArgumentOutOfRangeException(nameof(bandwidthLimit), + bandwidthLimit, + "The maximum number of bytes per second can't be negative."); } - public static Int64 Infinite => Int64.MaxValue; + _baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream)); + BandwidthLimit = bandwidthLimit; + } - /// - /// Bandwidth Limit (in B/s) - /// - /// The maximum bytes per second. - public Int64 BandwidthLimit + public static Int64 Infinite => Int64.MaxValue; + + /// + /// Bandwidth Limit (in B/s) + /// + /// The maximum bytes per second. + public Int64 BandwidthLimit + { + get => _bandwidthLimit; + set { - get => _bandwidthLimit; - set - { - _bandwidthLimit = value <= 0 ? Infinite : value; - _bandwidth ??= new Bandwidth(); - _bandwidth.BandwidthLimit = _bandwidthLimit; - } - } - - /// - public override Boolean CanRead => _baseStream.CanRead; - - /// - public override Boolean CanSeek => _baseStream.CanSeek; - - /// - public override Boolean CanWrite => _baseStream.CanWrite; - - /// - public override Int64 Length => _baseStream.Length; - - /// - public override Int64 Position - { - get => _baseStream.Position; - set => _baseStream.Position = value; - } - - /// - public override void Flush() - { - _baseStream.Flush(); - } - - /// - public override Int64 Seek(Int64 offset, SeekOrigin origin) - { - return _baseStream.Seek(offset, origin); - } - - /// - public override void SetLength(Int64 value) - { - _baseStream.SetLength(value); - } - - /// - public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count) - { - Throttle(count).Wait(); - - return _baseStream.Read(buffer, offset, count); - } - - public override async Task ReadAsync(Byte[] buffer, - Int32 offset, - Int32 count, - CancellationToken cancellationToken) - { - await Throttle(count).ConfigureAwait(false); - - return await _baseStream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); - } - - /// - public override void Write(Byte[] buffer, Int32 offset, Int32 count) - { - Throttle(count).Wait(); - _baseStream.Write(buffer, offset, count); - } - - /// - public override async Task WriteAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken) - { - await Throttle(count).ConfigureAwait(false); - await _baseStream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); - } - - private async Task Throttle(Int32 transmissionVolume) - { - // Make sure the buffer isn't empty. - if (BandwidthLimit > 0 && transmissionVolume > 0) - { - // Calculate the time to sleep. - _bandwidth.CalculateSpeed(transmissionVolume); - - await Sleep(_bandwidth.PopSpeedRetrieveTime()).ConfigureAwait(false); - } - } - - private static async Task Sleep(Int32 time) - { - if (time > 0) - { - await Task.Delay(time).ConfigureAwait(false); - } - } - - /// - public override String ToString() - { - return _baseStream.ToString(); + _bandwidthLimit = value <= 0 ? Infinite : value; + _bandwidth ??= new Bandwidth(); + _bandwidth.BandwidthLimit = _bandwidthLimit; } } - internal class Bandwidth + /// + public override Boolean CanRead => _baseStream.CanRead; + + /// + public override Boolean CanSeek => _baseStream.CanSeek; + + /// + public override Boolean CanWrite => _baseStream.CanWrite; + + /// + public override Int64 Length => _baseStream.Length; + + /// + public override Int64 Position { - private const Double OneSecond = 1000; // millisecond - private Int64 _count; - private Int32 _lastSecondCheckpoint; - private Int64 _lastTransferredBytesCount; - private Int32 _speedRetrieveTime; + get => _baseStream.Position; + set => _baseStream.Position = value; + } + + /// + public override void Flush() + { + _baseStream.Flush(); + } - public Bandwidth() + /// + public override Int64 Seek(Int64 offset, SeekOrigin origin) + { + return _baseStream.Seek(offset, origin); + } + + /// + public override void SetLength(Int64 value) + { + _baseStream.SetLength(value); + } + + /// + public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count) + { + Throttle(count).Wait(); + + return _baseStream.Read(buffer, offset, count); + } + + public override async Task ReadAsync(Byte[] buffer, + Int32 offset, + Int32 count, + CancellationToken cancellationToken) + { + await Throttle(count).ConfigureAwait(false); + + return await _baseStream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); + } + + /// + public override void Write(Byte[] buffer, Int32 offset, Int32 count) + { + Throttle(count).Wait(); + _baseStream.Write(buffer, offset, count); + } + + /// + public override async Task WriteAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken) + { + await Throttle(count).ConfigureAwait(false); + await _baseStream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); + } + + private async Task Throttle(Int32 transmissionVolume) + { + // Make sure the buffer isn't empty. + if (BandwidthLimit > 0 && transmissionVolume > 0) { - BandwidthLimit = Int64.MaxValue; - Reset(); + // Calculate the time to sleep. + _bandwidth.CalculateSpeed(transmissionVolume); + + await Sleep(_bandwidth.PopSpeedRetrieveTime()).ConfigureAwait(false); } + } - public Double Speed { get; private set; } - public Double AverageSpeed { get; private set; } - public Int64 BandwidthLimit { get; set; } - - public void CalculateSpeed(Int64 receivedBytesCount) + private static async Task Sleep(Int32 time) + { + if (time > 0) { - var elapsedTime = (Environment.TickCount - _lastSecondCheckpoint) + 1; - receivedBytesCount = Interlocked.Add(ref _lastTransferredBytesCount, receivedBytesCount); - var momentSpeed = (receivedBytesCount * OneSecond) / elapsedTime; // B/s - - if (OneSecond < elapsedTime) - { - Speed = momentSpeed; - AverageSpeed = ((AverageSpeed * _count) + Speed) / (_count + 1); - _count++; - SecondCheckpoint(); - } - - if (momentSpeed >= BandwidthLimit) - { - var expectedTime = (receivedBytesCount * OneSecond) / BandwidthLimit; - Interlocked.Add(ref _speedRetrieveTime, (Int32) expectedTime - elapsedTime); - } + await Task.Delay(time).ConfigureAwait(false); } + } - public Int32 PopSpeedRetrieveTime() - { - return Interlocked.Exchange(ref _speedRetrieveTime, 0); - } - - public void Reset() - { - SecondCheckpoint(); - _count = 0; - Speed = 0; - AverageSpeed = 0; - } - - private void SecondCheckpoint() - { - Interlocked.Exchange(ref _lastSecondCheckpoint, Environment.TickCount); - Interlocked.Exchange(ref _lastTransferredBytesCount, 0); - } + /// + public override String ToString() + { + return _baseStream.ToString(); } } + +internal class Bandwidth +{ + private const Double OneSecond = 1000; // millisecond + private Int64 _count; + private Int32 _lastSecondCheckpoint; + private Int64 _lastTransferredBytesCount; + private Int32 _speedRetrieveTime; + + public Bandwidth() + { + BandwidthLimit = Int64.MaxValue; + Reset(); + } + + public Double Speed { get; private set; } + public Double AverageSpeed { get; private set; } + public Int64 BandwidthLimit { get; set; } + + public void CalculateSpeed(Int64 receivedBytesCount) + { + var elapsedTime = (Environment.TickCount - _lastSecondCheckpoint) + 1; + receivedBytesCount = Interlocked.Add(ref _lastTransferredBytesCount, receivedBytesCount); + var momentSpeed = (receivedBytesCount * OneSecond) / elapsedTime; // B/s + + if (OneSecond < elapsedTime) + { + Speed = momentSpeed; + AverageSpeed = ((AverageSpeed * _count) + Speed) / (_count + 1); + _count++; + SecondCheckpoint(); + } + + if (momentSpeed >= BandwidthLimit) + { + var expectedTime = (receivedBytesCount * OneSecond) / BandwidthLimit; + Interlocked.Add(ref _speedRetrieveTime, (Int32) expectedTime - elapsedTime); + } + } + + public Int32 PopSpeedRetrieveTime() + { + return Interlocked.Exchange(ref _speedRetrieveTime, 0); + } + + public void Reset() + { + SecondCheckpoint(); + _count = 0; + Speed = 0; + AverageSpeed = 0; + } + + private void SecondCheckpoint() + { + Interlocked.Exchange(ref _lastSecondCheckpoint, Environment.TickCount); + Interlocked.Exchange(ref _lastTransferredBytesCount, 0); + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs index 85f4919..058eca7 100644 --- a/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using AllDebridNET; +using AllDebridNET; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using RDNET; @@ -11,324 +6,324 @@ using RDNET.Exceptions; using RdtClient.Data.Enums; using RdtClient.Data.Models.TorrentClient; using RdtClient.Service.Helpers; +using File = AllDebridNET.File; using Torrent = RdtClient.Data.Models.Data.Torrent; -namespace RdtClient.Service.Services.TorrentClients +namespace RdtClient.Service.Services.TorrentClients; + +public class AllDebridTorrentClient : ITorrentClient { - public class AllDebridTorrentClient : ITorrentClient + private readonly ILogger _logger; + private readonly IHttpClientFactory _httpClientFactory; + + public AllDebridTorrentClient(ILogger logger, IHttpClientFactory httpClientFactory) { - private readonly ILogger _logger; - private readonly IHttpClientFactory _httpClientFactory; - - public AllDebridTorrentClient(ILogger logger, IHttpClientFactory httpClientFactory) - { - _logger = logger; - _httpClientFactory = httpClientFactory; - } - - private AllDebridNETClient GetClient() - { - var apiKey = Settings.Get.RealDebridApiKey; - - if (String.IsNullOrWhiteSpace(apiKey)) - { - throw new Exception("All-Debrid API Key not set in the settings"); - } - - var httpClient = _httpClientFactory.CreateClient(); - httpClient.Timeout = TimeSpan.FromSeconds(10); - - var allDebridNetClient = new AllDebridNETClient("RealDebridClient", apiKey); - - return allDebridNetClient; - } - - private static TorrentClientTorrent Map(Magnet torrent) - { - return new TorrentClientTorrent - { - Id = torrent.Id.ToString(), - Filename = torrent.Filename, - OriginalFilename = torrent.Filename, - Hash = torrent.Hash, - Bytes = torrent.Size, - OriginalBytes = torrent.Size, - Host = null, - Split = 0, - Progress = (Int64) Math.Round(torrent.Downloaded * 100.0 / torrent.Size), - Status = torrent.Status, - StatusCode = torrent.StatusCode, - Added = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.UploadDate), - Files = (torrent.Links ?? new List()).Select((m, i) => new TorrentClientFile - { - Path = m.Filename, - Bytes = m.Size, - Id = i, - Selected = true, - }).ToList(), - Links = (torrent.Links ?? new List()).Select(m => m.LinkUrl.ToString()).ToList(), - Ended = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.CompletionDate), - Speed = torrent.DownloadSpeed, - Seeders = torrent.Seeders - }; - } - - public async Task> GetTorrents() - { - var results = await GetClient().Magnet.StatusAllAsync(); - return results.Select(Map).ToList(); - } - - public async Task GetUser() - { - var user = await GetClient().User.GetAsync(); - - return new TorrentClientUser - { - Username = user.Username, - Expiration = user.PremiumUntil > 0 ? new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(user.PremiumUntil) : null - }; - } - - public async Task AddMagnet(String magnetLink) - { - var result = await GetClient().Magnet.UploadMagnetAsync(magnetLink); - - return result.Id.ToString(); - } - - public async Task AddFile(Byte[] bytes) - { - var result = await GetClient().Magnet.UploadFileAsync(bytes); - - return result.Id.ToString(); - } - - public async Task> GetAvailableFiles(String hash) - { - var isAvailable = await GetClient().Magnet.InstantAvailabilityAsync(hash); - - if (isAvailable) - { - return new List - { - new TorrentClientAvailableFile - { - Filename = "All files", - Filesize = 0 - } - }; - } - - return new List(); - } - - public Task SelectFiles(Torrent torrent) - { - return Task.CompletedTask; - } - - public async Task GetInfo(String torrentId) - { - var result = await GetClient().Magnet.StatusAsync(torrentId); - - return Map(result); - } - - public async Task Delete(String torrentId) - { - await GetClient().Magnet.DeleteAsync(torrentId); - } - - public async Task Unrestrict(String link) - { - var result = await GetClient().Links.DownloadLinkAsync(link); - - return result.Link; - } - - public async Task UpdateData(Torrent torrent, TorrentClientTorrent torrentClientTorrent) - { - try - { - torrentClientTorrent ??= await GetInfo(torrent.RdId); - - if (!String.IsNullOrWhiteSpace(torrentClientTorrent.Filename)) - { - torrent.RdName = torrentClientTorrent.Filename; - } - - if (torrentClientTorrent.Bytes > 0) - { - torrent.RdSize = torrentClientTorrent.Bytes; - } - - if (torrentClientTorrent.Files != null) - { - torrent.RdFiles = JsonConvert.SerializeObject(torrentClientTorrent.Files); - } - - torrent.RdHost = torrentClientTorrent.Host; - torrent.RdSplit = torrentClientTorrent.Split; - torrent.RdProgress = torrentClientTorrent.Progress; - torrent.RdAdded = torrentClientTorrent.Added; - torrent.RdEnded = torrentClientTorrent.Ended; - torrent.RdSpeed = torrentClientTorrent.Speed; - torrent.RdSeeders = torrentClientTorrent.Seeders; - torrent.RdStatusRaw = torrentClientTorrent.Status; - - torrent.RdStatus = torrentClientTorrent.StatusCode switch - { - 0 => TorrentStatus.Processing, // Processing In Queue. - 1 => TorrentStatus.Downloading, // Processing Downloading. - 2 => TorrentStatus.Downloading, // Processing Compressing / Moving. - 3 => TorrentStatus.Uploading, // Processing Uploading. - 4 => TorrentStatus.Finished, // Finished Ready. - 5 => TorrentStatus.Error, // Error Upload fail. - 6 => TorrentStatus.Error, // Error Internal error on unpacking. - 7 => TorrentStatus.Error, // Error Not downloaded in 20 min. - 8 => TorrentStatus.Error, // Error File too big. - 9 => TorrentStatus.Error, // Error Internal error. - 10 => TorrentStatus.Error, // Error Download took more than 72h. - 11 => TorrentStatus.Error, // Error Deleted on the hoster website - _ => TorrentStatus.Error - }; - } - catch (AllDebridException ex) - { - if (ex.ErrorCode == "MAGNET_INVALID_ID") - { - torrent.RdStatusRaw = "deleted"; - } - else - { - throw; - } - } - catch (RealDebridException ex) - { - if (ex.ErrorCode == 7) - { - torrent.RdStatusRaw = "deleted"; - } - else - { - throw; - } - } - - return torrent; - } - - public async Task> GetDownloadLinks(Torrent torrent) - { - var magnet = await GetClient().Magnet.StatusAsync(torrent.RdId); - - var links = magnet.Links; - - Log($"Getting download links", torrent); - - if (torrent.DownloadMinSize > 0) - { - var minFileSize = torrent.DownloadMinSize * 1024 * 1024; - - Log($"Determining which files are over {minFileSize} bytes", torrent); - - links = links.Where(m => m.Size > minFileSize) - .ToList(); - - Log($"Found {links.Count} files that match the minimum file size criterea", torrent); - } - - if (links.Count == 0) - { - Log($"Filtered all files out! Downloading ALL files instead!", torrent); - - links = magnet.Links; - } - - Log($"Selecting links:"); - - foreach (var link in links) - { - if (link.Files == null) - { - continue; - } - - var fileList = GetFiles(link.Files, ""); - - Log($"{link.Filename} ({link.Size}b) {link.LinkUrl}, contains files:{Environment.NewLine}{String.Join(Environment.NewLine, fileList)}"); - } - - Log("", torrent); - - return links.Select(m => m.LinkUrl.ToString()).ToList(); - } - - private static IEnumerable GetFiles(IList files, String parent) - { - var result = new List(); - - foreach (var file in files) - { - if (!String.IsNullOrWhiteSpace(file.N)) - { - result.Add($"{parent}/{file.N}"); - } - - if (file.E != null && file.E.Value.PurpleEArray != null && file.E.Value.PurpleEArray.Count > 0) - { - result.AddRange(GetFiles(file.E.Value.PurpleEArray, file.N)); - } - } - - return result; - } - - private static IEnumerable GetFiles(IList files, String parent) - { - var result = new List(); - - foreach (var file in files) - { - if (!String.IsNullOrWhiteSpace(file.N)) - { - result.Add($"{parent}/{file.N}"); - } - - if (file.E != null && file.E.Count > 0) - { - result.AddRange(GetFiles(file.E, file.N)); - } - } - - return result; - } - - private static IEnumerable GetFiles(IList files, String parent) - { - var result = new List(); - - foreach (var file in files) - { - if (!String.IsNullOrWhiteSpace(file.N)) - { - result.Add($"{parent}/{file.N}"); - } - } - - return result; - } - - private void Log(String message, Torrent torrent = null) - { - if (torrent != null) - { - message = $"{message} {torrent.ToLog()}"; - } - - _logger.LogDebug(message); - } + _logger = logger; + _httpClientFactory = httpClientFactory; } -} + + private AllDebridNETClient GetClient() + { + var apiKey = Settings.Get.RealDebridApiKey; + + if (String.IsNullOrWhiteSpace(apiKey)) + { + throw new Exception("All-Debrid API Key not set in the settings"); + } + + var httpClient = _httpClientFactory.CreateClient(); + httpClient.Timeout = TimeSpan.FromSeconds(10); + + var allDebridNetClient = new AllDebridNETClient("RealDebridClient", apiKey); + + return allDebridNetClient; + } + + private static TorrentClientTorrent Map(Magnet torrent) + { + return new TorrentClientTorrent + { + Id = torrent.Id.ToString(), + Filename = torrent.Filename, + OriginalFilename = torrent.Filename, + Hash = torrent.Hash, + Bytes = torrent.Size, + OriginalBytes = torrent.Size, + Host = null, + Split = 0, + Progress = (Int64) Math.Round(torrent.Downloaded * 100.0 / torrent.Size), + Status = torrent.Status, + StatusCode = torrent.StatusCode, + Added = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.UploadDate), + Files = (torrent.Links ?? new List()).Select((m, i) => new TorrentClientFile + { + Path = m.Filename, + Bytes = m.Size, + Id = i, + Selected = true, + }).ToList(), + Links = (torrent.Links ?? new List()).Select(m => m.LinkUrl.ToString()).ToList(), + Ended = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.CompletionDate), + Speed = torrent.DownloadSpeed, + Seeders = torrent.Seeders + }; + } + + public async Task> GetTorrents() + { + var results = await GetClient().Magnet.StatusAllAsync(); + return results.Select(Map).ToList(); + } + + public async Task GetUser() + { + var user = await GetClient().User.GetAsync(); + + return new TorrentClientUser + { + Username = user.Username, + Expiration = user.PremiumUntil > 0 ? new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(user.PremiumUntil) : null + }; + } + + public async Task AddMagnet(String magnetLink) + { + var result = await GetClient().Magnet.UploadMagnetAsync(magnetLink); + + return result.Id.ToString(); + } + + public async Task AddFile(Byte[] bytes) + { + var result = await GetClient().Magnet.UploadFileAsync(bytes); + + return result.Id.ToString(); + } + + public async Task> GetAvailableFiles(String hash) + { + var isAvailable = await GetClient().Magnet.InstantAvailabilityAsync(hash); + + if (isAvailable) + { + return new List + { + new TorrentClientAvailableFile + { + Filename = "All files", + Filesize = 0 + } + }; + } + + return new List(); + } + + public Task SelectFiles(Torrent torrent) + { + return Task.CompletedTask; + } + + public async Task GetInfo(String torrentId) + { + var result = await GetClient().Magnet.StatusAsync(torrentId); + + return Map(result); + } + + public async Task Delete(String torrentId) + { + await GetClient().Magnet.DeleteAsync(torrentId); + } + + public async Task Unrestrict(String link) + { + var result = await GetClient().Links.DownloadLinkAsync(link); + + return result.Link; + } + + public async Task UpdateData(Torrent torrent, TorrentClientTorrent torrentClientTorrent) + { + try + { + torrentClientTorrent ??= await GetInfo(torrent.RdId); + + if (!String.IsNullOrWhiteSpace(torrentClientTorrent.Filename)) + { + torrent.RdName = torrentClientTorrent.Filename; + } + + if (torrentClientTorrent.Bytes > 0) + { + torrent.RdSize = torrentClientTorrent.Bytes; + } + + if (torrentClientTorrent.Files != null) + { + torrent.RdFiles = JsonConvert.SerializeObject(torrentClientTorrent.Files); + } + + torrent.RdHost = torrentClientTorrent.Host; + torrent.RdSplit = torrentClientTorrent.Split; + torrent.RdProgress = torrentClientTorrent.Progress; + torrent.RdAdded = torrentClientTorrent.Added; + torrent.RdEnded = torrentClientTorrent.Ended; + torrent.RdSpeed = torrentClientTorrent.Speed; + torrent.RdSeeders = torrentClientTorrent.Seeders; + torrent.RdStatusRaw = torrentClientTorrent.Status; + + torrent.RdStatus = torrentClientTorrent.StatusCode switch + { + 0 => TorrentStatus.Processing, // Processing In Queue. + 1 => TorrentStatus.Downloading, // Processing Downloading. + 2 => TorrentStatus.Downloading, // Processing Compressing / Moving. + 3 => TorrentStatus.Uploading, // Processing Uploading. + 4 => TorrentStatus.Finished, // Finished Ready. + 5 => TorrentStatus.Error, // Error Upload fail. + 6 => TorrentStatus.Error, // Error Internal error on unpacking. + 7 => TorrentStatus.Error, // Error Not downloaded in 20 min. + 8 => TorrentStatus.Error, // Error File too big. + 9 => TorrentStatus.Error, // Error Internal error. + 10 => TorrentStatus.Error, // Error Download took more than 72h. + 11 => TorrentStatus.Error, // Error Deleted on the hoster website + _ => TorrentStatus.Error + }; + } + catch (AllDebridException ex) + { + if (ex.ErrorCode == "MAGNET_INVALID_ID") + { + torrent.RdStatusRaw = "deleted"; + } + else + { + throw; + } + } + catch (RealDebridException ex) + { + if (ex.ErrorCode == 7) + { + torrent.RdStatusRaw = "deleted"; + } + else + { + throw; + } + } + + return torrent; + } + + public async Task> GetDownloadLinks(Torrent torrent) + { + var magnet = await GetClient().Magnet.StatusAsync(torrent.RdId); + + var links = magnet.Links; + + Log($"Getting download links", torrent); + + if (torrent.DownloadMinSize > 0) + { + var minFileSize = torrent.DownloadMinSize * 1024 * 1024; + + Log($"Determining which files are over {minFileSize} bytes", torrent); + + links = links.Where(m => m.Size > minFileSize) + .ToList(); + + Log($"Found {links.Count} files that match the minimum file size criterea", torrent); + } + + if (links.Count == 0) + { + Log($"Filtered all files out! Downloading ALL files instead!", torrent); + + links = magnet.Links; + } + + Log($"Selecting links:"); + + foreach (var link in links) + { + if (link.Files == null) + { + continue; + } + + var fileList = GetFiles(link.Files, ""); + + Log($"{link.Filename} ({link.Size}b) {link.LinkUrl}, contains files:{Environment.NewLine}{String.Join(Environment.NewLine, fileList)}"); + } + + Log("", torrent); + + return links.Select(m => m.LinkUrl.ToString()).ToList(); + } + + private static IEnumerable GetFiles(IList files, String parent) + { + var result = new List(); + + foreach (var file in files) + { + if (!String.IsNullOrWhiteSpace(file.N)) + { + result.Add($"{parent}/{file.N}"); + } + + if (file.E != null && file.E.Value.PurpleEArray != null && file.E.Value.PurpleEArray.Count > 0) + { + result.AddRange(GetFiles(file.E.Value.PurpleEArray, file.N)); + } + } + + return result; + } + + private static IEnumerable GetFiles(IList files, String parent) + { + var result = new List(); + + foreach (var file in files) + { + if (!String.IsNullOrWhiteSpace(file.N)) + { + result.Add($"{parent}/{file.N}"); + } + + if (file.E != null && file.E.Count > 0) + { + result.AddRange(GetFiles(file.E, file.N)); + } + } + + return result; + } + + private static IEnumerable GetFiles(IList files, String parent) + { + var result = new List(); + + foreach (var file in files) + { + if (!String.IsNullOrWhiteSpace(file.N)) + { + result.Add($"{parent}/{file.N}"); + } + } + + return result; + } + + private void Log(String message, Torrent torrent = null) + { + if (torrent != null) + { + message = $"{message} {torrent.ToLog()}"; + } + + _logger.LogDebug(message); + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/TorrentClients/ITorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/ITorrentClient.cs index 8f0b2c9..b05cdd9 100644 --- a/server/RdtClient.Service/Services/TorrentClients/ITorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/ITorrentClient.cs @@ -1,23 +1,19 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using RdtClient.Data.Models.Data; +using RdtClient.Data.Models.Data; using RdtClient.Data.Models.TorrentClient; -namespace RdtClient.Service.Services.TorrentClients +namespace RdtClient.Service.Services.TorrentClients; + +public interface ITorrentClient { - public interface ITorrentClient - { - Task> GetTorrents(); - Task GetUser(); - Task AddMagnet(String magnetLink); - Task AddFile(Byte[] bytes); - Task> GetAvailableFiles(String hash); - Task SelectFiles(Torrent torrent); - Task GetInfo(String torrentId); - Task Delete(String torrentId); - Task Unrestrict(String link); - Task UpdateData(Torrent torrent, TorrentClientTorrent torrentClientTorrent); - Task> GetDownloadLinks(Torrent torrent); - } -} + Task> GetTorrents(); + Task GetUser(); + Task AddMagnet(String magnetLink); + Task AddFile(Byte[] bytes); + Task> GetAvailableFiles(String hash); + Task SelectFiles(Torrent torrent); + Task GetInfo(String torrentId); + Task Delete(String torrentId); + Task Unrestrict(String link); + Task UpdateData(Torrent torrent, TorrentClientTorrent torrentClientTorrent); + Task> GetDownloadLinks(Torrent torrent); +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs index 7698a92..d1aefef 100644 --- a/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs @@ -1,355 +1,349 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Newtonsoft.Json; using RDNET; using RdtClient.Data.Enums; using RdtClient.Data.Models.TorrentClient; using RdtClient.Service.Helpers; -namespace RdtClient.Service.Services.TorrentClients +namespace RdtClient.Service.Services.TorrentClients; + +public class RealDebridTorrentClient : ITorrentClient { - public class RealDebridTorrentClient : ITorrentClient + private readonly ILogger _logger; + private readonly IHttpClientFactory _httpClientFactory; + private TimeSpan _offset; + + public RealDebridTorrentClient(ILogger logger, IHttpClientFactory httpClientFactory) { - private readonly ILogger _logger; - private readonly IHttpClientFactory _httpClientFactory; - private TimeSpan _offset; + _logger = logger; + _httpClientFactory = httpClientFactory; + } - public RealDebridTorrentClient(ILogger logger, IHttpClientFactory httpClientFactory) + private RdNetClient GetClient() + { + var apiKey = Settings.Get.RealDebridApiKey; + + if (String.IsNullOrWhiteSpace(apiKey)) { - _logger = logger; - _httpClientFactory = httpClientFactory; + throw new Exception("Real-Debrid API Key not set in the settings"); } - private RdNetClient GetClient() - { - var apiKey = Settings.Get.RealDebridApiKey; + var httpClient = _httpClientFactory.CreateClient(); + httpClient.Timeout = TimeSpan.FromSeconds(10); - if (String.IsNullOrWhiteSpace(apiKey)) + var rdtNetClient = new RdNetClient(null, httpClient, 5); + rdtNetClient.UseApiAuthentication(apiKey); + + // Get the server time to fix up the timezones on results + var serverTime = rdtNetClient.Api.GetIsoTimeAsync().Result; + _offset = serverTime.Offset; + + return rdtNetClient; + } + + private TorrentClientTorrent Map(Torrent torrent) + { + return new TorrentClientTorrent + { + Id = torrent.Id, + Filename = torrent.Filename, + OriginalFilename = torrent.OriginalFilename, + Hash = torrent.Hash, + Bytes = torrent.Bytes, + OriginalBytes = torrent.OriginalBytes, + Host = torrent.Host, + Split = torrent.Split, + Progress = torrent.Progress, + Status = torrent.Status, + Added = ChangeTimeZone(torrent.Added).Value, + Files = (torrent.Files ?? new List()).Select(m => new TorrentClientFile { - throw new Exception("Real-Debrid API Key not set in the settings"); + Path = m.Path, + Bytes = m.Bytes, + Id = m.Id, + Selected = m.Selected + }).ToList(), + Links = torrent.Links, + Ended = ChangeTimeZone(torrent.Ended), + Speed = torrent.Speed, + Seeders = torrent.Seeders, + }; + } + + public async Task> GetTorrents() + { + var page = 0; + var results = new List(); + + while (true) + { + var pagedResults = await GetClient().Torrents.GetAsync(page, 100); + + results.AddRange(pagedResults); + + if (pagedResults.Count == 0) + { + break; } - var httpClient = _httpClientFactory.CreateClient(); - httpClient.Timeout = TimeSpan.FromSeconds(10); - - var rdtNetClient = new RdNetClient(null, httpClient, 5); - rdtNetClient.UseApiAuthentication(apiKey); - - // Get the server time to fix up the timezones on results - var serverTime = rdtNetClient.Api.GetIsoTimeAsync().Result; - _offset = serverTime.Offset; - - return rdtNetClient; + page += 100; } - private TorrentClientTorrent Map(Torrent torrent) - { - return new TorrentClientTorrent - { - Id = torrent.Id, - Filename = torrent.Filename, - OriginalFilename = torrent.OriginalFilename, - Hash = torrent.Hash, - Bytes = torrent.Bytes, - OriginalBytes = torrent.OriginalBytes, - Host = torrent.Host, - Split = torrent.Split, - Progress = torrent.Progress, - Status = torrent.Status, - Added = ChangeTimeZone(torrent.Added).Value, - Files = (torrent.Files ?? new List()).Select(m => new TorrentClientFile - { - Path = m.Path, - Bytes = m.Bytes, - Id = m.Id, - Selected = m.Selected - }).ToList(), - Links = torrent.Links, - Ended = ChangeTimeZone(torrent.Ended), - Speed = torrent.Speed, - Seeders = torrent.Seeders, - }; - } + return results.Select(Map).ToList(); + } - public async Task> GetTorrents() - { - var page = 0; - var results = new List(); - - while (true) - { - var pagedResults = await GetClient().Torrents.GetAsync(page, 100); - - results.AddRange(pagedResults); - - if (pagedResults.Count == 0) - { - break; - } - - page += 100; - } - - return results.Select(Map).ToList(); - } - - public async Task GetUser() - { - var user = await GetClient().User.GetAsync(); + public async Task GetUser() + { + var user = await GetClient().User.GetAsync(); - return new TorrentClientUser - { - Username = user.Username, - Expiration = user.Premium > 0 ? user.Expiration : null - }; + return new TorrentClientUser + { + Username = user.Username, + Expiration = user.Premium > 0 ? user.Expiration : null + }; + } + + public async Task AddMagnet(String magnetLink) + { + var result = await GetClient().Torrents.AddMagnetAsync(magnetLink); + + return result.Id; + } + + public async Task AddFile(Byte[] bytes) + { + var result = await GetClient().Torrents.AddFileAsync(bytes); + + return result.Id; + } + + public async Task> GetAvailableFiles(String hash) + { + var result = await GetClient().Torrents.GetAvailableFiles(hash); + + var files = result.SelectMany(m => m.Value).SelectMany(m => m.Value).SelectMany(m => m.Values); + + var groups = files.GroupBy(m => $"{m.Filename}-{m.Filesize}"); + + var torrentClientAvailableFiles = groups.Select(m => new TorrentClientAvailableFile + { + Filename = m.First().Filename, + Filesize = m.First().Filesize + } ).ToList(); + + return torrentClientAvailableFiles; + } + + public async Task SelectFiles(Data.Models.Data.Torrent torrent) + { + var files = torrent.Files; + + Log("Seleting files", torrent); + + if (torrent.DownloadAction == TorrentDownloadAction.DownloadAvailableFiles) + { + Log($"Determining which files are already available on RealDebrid", torrent); + + var availableFiles = await GetAvailableFiles(torrent.Hash); + + Log($"Found {files.Count}/{torrent.Files.Count} available files on RealDebrid", torrent); + + files = torrent.Files.Where(m => availableFiles.Any(f => m.Path.EndsWith(f.Filename))).ToList(); + } + else if (torrent.DownloadAction == TorrentDownloadAction.DownloadAll) + { + Log("Selecting all files", torrent); + files = torrent.Files.ToList(); + } + else if (torrent.DownloadAction == TorrentDownloadAction.DownloadManual) + { + Log("Selecting manual selected files", torrent); + files = torrent.Files.Where(m => torrent.ManualFiles.Any(f => m.Path.EndsWith(f))).ToList(); } - public async Task AddMagnet(String magnetLink) - { - var result = await GetClient().Torrents.AddMagnetAsync(magnetLink); + Log($"Selecting {files.Count}/{torrent.Files.Count} files", torrent); - return result.Id; + if (torrent.DownloadAction != TorrentDownloadAction.DownloadManual && torrent.DownloadMinSize > 0) + { + var minFileSize = torrent.DownloadMinSize * 1024 * 1024; + + Log($"Determining which files are over {minFileSize} bytes", torrent); + + files = files.Where(m => m.Bytes > minFileSize) + .ToList(); + + Log($"Found {files.Count} files that match the minimum file size criterea", torrent); } - public async Task AddFile(Byte[] bytes) + if (files.Count == 0) { - var result = await GetClient().Torrents.AddFileAsync(bytes); + Log($"Filtered all files out! Downloading ALL files instead!", torrent); - return result.Id; + files = torrent.Files; } - public async Task> GetAvailableFiles(String hash) + var fileIds = files.Select(m => m.Id.ToString()).ToArray(); + + Log($"Selecting files:"); + + foreach (var file in files) { - var result = await GetClient().Torrents.GetAvailableFiles(hash); - - var files = result.SelectMany(m => m.Value).SelectMany(m => m.Value).SelectMany(m => m.Values); - - var groups = files.GroupBy(m => $"{m.Filename}-{m.Filesize}"); - - var torrentClientAvailableFiles = groups.Select(m => new TorrentClientAvailableFile - { - Filename = m.First().Filename, - Filesize = m.First().Filesize - } ).ToList(); - - return torrentClientAvailableFiles; + Log($"{file.Id}: {file.Path} ({file.Bytes}b)"); } - public async Task SelectFiles(Data.Models.Data.Torrent torrent) + Log("", torrent); + + await GetClient().Torrents.SelectFilesAsync(torrent.RdId, fileIds.ToArray()); + } + + public async Task GetInfo(String torrentId) + { + var result = await GetClient().Torrents.GetInfoAsync(torrentId); + + return Map(result); + } + + public async Task Delete(String torrentId) + { + await GetClient().Torrents.DeleteAsync(torrentId); + } + + public async Task Unrestrict(String link) + { + var result = await GetClient().Unrestrict.LinkAsync(link); + + return result.Download; + } + + public async Task UpdateData(Data.Models.Data.Torrent torrent, TorrentClientTorrent torrentClientTorrent) + { + try { - var files = torrent.Files; - - Log("Seleting files", torrent); - - if (torrent.DownloadAction == TorrentDownloadAction.DownloadAvailableFiles) + if (torrent == null) { - Log($"Determining which files are already available on RealDebrid", torrent); - - var availableFiles = await GetAvailableFiles(torrent.Hash); - - Log($"Found {files.Count}/{torrent.Files.Count} available files on RealDebrid", torrent); - - files = torrent.Files.Where(m => availableFiles.Any(f => m.Path.EndsWith(f.Filename))).ToList(); - } - else if (torrent.DownloadAction == TorrentDownloadAction.DownloadAll) - { - Log("Selecting all files", torrent); - files = torrent.Files.ToList(); - } - else if (torrent.DownloadAction == TorrentDownloadAction.DownloadManual) - { - Log("Selecting manual selected files", torrent); - files = torrent.Files.Where(m => torrent.ManualFiles.Any(f => m.Path.EndsWith(f))).ToList(); + return null; } - Log($"Selecting {files.Count}/{torrent.Files.Count} files", torrent); - - if (torrent.DownloadAction != TorrentDownloadAction.DownloadManual && torrent.DownloadMinSize > 0) - { - var minFileSize = torrent.DownloadMinSize * 1024 * 1024; - - Log($"Determining which files are over {minFileSize} bytes", torrent); - - files = files.Where(m => m.Bytes > minFileSize) - .ToList(); - - Log($"Found {files.Count} files that match the minimum file size criterea", torrent); - } - - if (files.Count == 0) - { - Log($"Filtered all files out! Downloading ALL files instead!", torrent); - - files = torrent.Files; - } - - var fileIds = files.Select(m => m.Id.ToString()).ToArray(); - - Log($"Selecting files:"); - - foreach (var file in files) - { - Log($"{file.Id}: {file.Path} ({file.Bytes}b)"); - } - - Log("", torrent); - - await GetClient().Torrents.SelectFilesAsync(torrent.RdId, fileIds.ToArray()); - } - - public async Task GetInfo(String torrentId) - { - var result = await GetClient().Torrents.GetInfoAsync(torrentId); - - return Map(result); - } - - public async Task Delete(String torrentId) - { - await GetClient().Torrents.DeleteAsync(torrentId); - } - - public async Task Unrestrict(String link) - { - var result = await GetClient().Unrestrict.LinkAsync(link); - - return result.Download; - } - - public async Task UpdateData(Data.Models.Data.Torrent torrent, TorrentClientTorrent torrentClientTorrent) - { - try - { - if (torrent == null) - { - return null; - } - - var rdTorrent = await GetInfo(torrent.RdId); - - if (rdTorrent == null) - { - return torrent; - } - - if (!String.IsNullOrWhiteSpace(rdTorrent.Filename)) - { - torrent.RdName = rdTorrent.Filename; - } - - if (!String.IsNullOrWhiteSpace(rdTorrent.OriginalFilename)) - { - torrent.RdName = rdTorrent.OriginalFilename; - } - - if (rdTorrent.Bytes > 0) - { - torrent.RdSize = rdTorrent.Bytes; - } - else if (rdTorrent.OriginalBytes > 0) - { - torrent.RdSize = rdTorrent.OriginalBytes; - } - - if (rdTorrent.Files != null) - { - torrent.RdFiles = JsonConvert.SerializeObject(rdTorrent.Files); - } - - torrent.RdHost = rdTorrent.Host; - torrent.RdSplit = rdTorrent.Split; - torrent.RdProgress = rdTorrent.Progress; - torrent.RdAdded = rdTorrent.Added; - torrent.RdEnded = rdTorrent.Ended; - torrent.RdSpeed = rdTorrent.Speed; - torrent.RdSeeders = rdTorrent.Seeders; - torrent.RdStatusRaw = rdTorrent.Status; - - torrent.RdStatus = rdTorrent.Status switch - { - "magnet_error" => TorrentStatus.Error, - "magnet_conversion" => TorrentStatus.Processing, - "waiting_files_selection" => TorrentStatus.WaitingForFileSelection, - "queued" => TorrentStatus.Downloading, - "downloading" => TorrentStatus.Downloading, - "downloaded" => TorrentStatus.Finished, - "error" => TorrentStatus.Error, - "virus" => TorrentStatus.Error, - "compressing" => TorrentStatus.Downloading, - "uploading" => TorrentStatus.Uploading, - "dead" => TorrentStatus.Error, - _ => TorrentStatus.Error - }; - } - catch (Exception ex) - { - if (ex.Message == "Resource not found") - { - if (torrent != null) - { - torrent.RdStatusRaw = "deleted"; - } - } - else - { - throw; - } - } - - return torrent; - } - - public async Task> GetDownloadLinks(Data.Models.Data.Torrent torrent) - { var rdTorrent = await GetInfo(torrent.RdId); - var downloadLinks = rdTorrent.Links.Where(m => !String.IsNullOrWhiteSpace(m)).ToList(); - - Log($"Found {downloadLinks.Count} links", torrent); - - foreach (var link in downloadLinks) + if (rdTorrent == null) { - Log($"{link}", torrent); + return torrent; } - // Check if all the links are set that have been selected - if (torrent.Files.Count(m => m.Selected) == downloadLinks.Count) + if (!String.IsNullOrWhiteSpace(rdTorrent.Filename)) { - return downloadLinks; + torrent.RdName = rdTorrent.Filename; } - // Check if all all the links are set for manual selection - if (torrent.ManualFiles.Count == downloadLinks.Count) + if (!String.IsNullOrWhiteSpace(rdTorrent.OriginalFilename)) { - return downloadLinks; + torrent.RdName = rdTorrent.OriginalFilename; } - // If there is only 1 link, delay for 1 minute to see if more links pop up. - if (downloadLinks.Count == 1 && torrent.RdEnded.HasValue && DateTime.UtcNow > torrent.RdEnded.Value.ToUniversalTime().AddMinutes(1)) + if (rdTorrent.Bytes > 0) { - return downloadLinks; + torrent.RdSize = rdTorrent.Bytes; } - - return null; + else if (rdTorrent.OriginalBytes > 0) + { + torrent.RdSize = rdTorrent.OriginalBytes; + } + + if (rdTorrent.Files != null) + { + torrent.RdFiles = JsonConvert.SerializeObject(rdTorrent.Files); + } + + torrent.RdHost = rdTorrent.Host; + torrent.RdSplit = rdTorrent.Split; + torrent.RdProgress = rdTorrent.Progress; + torrent.RdAdded = rdTorrent.Added; + torrent.RdEnded = rdTorrent.Ended; + torrent.RdSpeed = rdTorrent.Speed; + torrent.RdSeeders = rdTorrent.Seeders; + torrent.RdStatusRaw = rdTorrent.Status; + + torrent.RdStatus = rdTorrent.Status switch + { + "magnet_error" => TorrentStatus.Error, + "magnet_conversion" => TorrentStatus.Processing, + "waiting_files_selection" => TorrentStatus.WaitingForFileSelection, + "queued" => TorrentStatus.Downloading, + "downloading" => TorrentStatus.Downloading, + "downloaded" => TorrentStatus.Finished, + "error" => TorrentStatus.Error, + "virus" => TorrentStatus.Error, + "compressing" => TorrentStatus.Downloading, + "uploading" => TorrentStatus.Uploading, + "dead" => TorrentStatus.Error, + _ => TorrentStatus.Error + }; } - - private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset) + catch (Exception ex) { - return dateTimeOffset?.Subtract(_offset).ToOffset(_offset); - } - - private void Log(String message, Data.Models.Data.Torrent torrent = null) - { - if (torrent != null) + if (ex.Message == "Resource not found") { - message = $"{message} {torrent.ToLog()}"; + if (torrent != null) + { + torrent.RdStatusRaw = "deleted"; + } + } + else + { + throw; } - - _logger.LogDebug(message); } + + return torrent; } -} + + public async Task> GetDownloadLinks(Data.Models.Data.Torrent torrent) + { + var rdTorrent = await GetInfo(torrent.RdId); + + var downloadLinks = rdTorrent.Links.Where(m => !String.IsNullOrWhiteSpace(m)).ToList(); + + Log($"Found {downloadLinks.Count} links", torrent); + + foreach (var link in downloadLinks) + { + Log($"{link}", torrent); + } + + // Check if all the links are set that have been selected + if (torrent.Files.Count(m => m.Selected) == downloadLinks.Count) + { + return downloadLinks; + } + + // Check if all all the links are set for manual selection + if (torrent.ManualFiles.Count == downloadLinks.Count) + { + return downloadLinks; + } + + // If there is only 1 link, delay for 1 minute to see if more links pop up. + if (downloadLinks.Count == 1 && torrent.RdEnded.HasValue && DateTime.UtcNow > torrent.RdEnded.Value.ToUniversalTime().AddMinutes(1)) + { + return downloadLinks; + } + + return null; + } + + private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset) + { + return dateTimeOffset?.Subtract(_offset).ToOffset(_offset); + } + + private void Log(String message, Data.Models.Data.Torrent torrent = null) + { + if (torrent != null) + { + message = $"{message} {torrent.ToLog()}"; + } + + _logger.LogDebug(message); + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/TorrentRunner.cs b/server/RdtClient.Service/Services/TorrentRunner.cs index 3dcd362..f58d7db 100644 --- a/server/RdtClient.Service/Services/TorrentRunner.cs +++ b/server/RdtClient.Service/Services/TorrentRunner.cs @@ -1,11 +1,6 @@ -using System; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net.Http; using System.Text.Json; -using System.Threading.Tasks; using System.Web; using Aria2NET; using Microsoft.Extensions.Logging; @@ -15,302 +10,302 @@ using RdtClient.Data.Models.Internal; using RdtClient.Service.Helpers; using RdtClient.Service.Services.Downloaders; -namespace RdtClient.Service.Services +namespace RdtClient.Service.Services; + +public class TorrentRunner { - public class TorrentRunner + private static DateTime _nextUpdate = DateTime.UtcNow; + + public static readonly ConcurrentDictionary ActiveDownloadClients = new(); + public static readonly ConcurrentDictionary ActiveUnpackClients = new(); + + private readonly ILogger _logger; + private readonly Torrents _torrents; + private readonly Downloads _downloads; + private readonly RemoteService _remoteService; + private readonly HttpClient _httpClient; + + public TorrentRunner(ILogger logger, Torrents torrents, Downloads downloads, RemoteService remoteService) { - private static DateTime _nextUpdate = DateTime.UtcNow; + _logger = logger; + _torrents = torrents; + _downloads = downloads; + _remoteService = remoteService; - public static readonly ConcurrentDictionary ActiveDownloadClients = new(); - public static readonly ConcurrentDictionary ActiveUnpackClients = new(); - - private readonly ILogger _logger; - private readonly Torrents _torrents; - private readonly Downloads _downloads; - private readonly RemoteService _remoteService; - private readonly HttpClient _httpClient; - - public TorrentRunner(ILogger logger, Torrents torrents, Downloads downloads, RemoteService remoteService) + _httpClient = new HttpClient { - _logger = logger; - _torrents = torrents; - _downloads = downloads; - _remoteService = remoteService; + Timeout = TimeSpan.FromSeconds(10) + }; + } - _httpClient = new HttpClient - { - Timeout = TimeSpan.FromSeconds(10) - }; + public async Task Initialize() + { + Log("Initializing TorrentRunner"); + + var settingsCopy = JsonSerializer.Deserialize(JsonSerializer.Serialize(Settings.Get)); + + if (settingsCopy != null) + { + settingsCopy.RealDebridApiKey = "*****"; + settingsCopy.Aria2cSecret = "*****"; + + Log(JsonSerializer.Serialize(settingsCopy)); } - public async Task Initialize() - { - Log("Initializing TorrentRunner"); - - var settingsCopy = JsonSerializer.Deserialize(JsonSerializer.Serialize(Settings.Get)); - - if (settingsCopy != null) - { - settingsCopy.RealDebridApiKey = "*****"; - settingsCopy.Aria2cSecret = "*****"; - - Log(JsonSerializer.Serialize(settingsCopy)); - } - - // When starting up reset any pending downloads or unpackings so that they are restarted. - var torrents = await _torrents.Get(); + // When starting up reset any pending downloads or unpackings so that they are restarted. + var torrents = await _torrents.Get(); - torrents = torrents.Where(m => m.Completed == null).ToList(); + torrents = torrents.Where(m => m.Completed == null).ToList(); - Log($"Found {torrents.Count} not completed torrents"); + Log($"Found {torrents.Count} not completed torrents"); - foreach (var torrent in torrents) + foreach (var torrent in torrents) + { + foreach (var download in torrent.Downloads) { - foreach (var download in torrent.Downloads) + if (download.DownloadQueued != null && download.DownloadStarted != null && download.DownloadFinished == null && download.Error == null) { - if (download.DownloadQueued != null && download.DownloadStarted != null && download.DownloadFinished == null && download.Error == null) - { - Log("Resetting download status", download, torrent); + Log("Resetting download status", download, torrent); - await _downloads.UpdateDownloadStarted(download.DownloadId, null); - } + await _downloads.UpdateDownloadStarted(download.DownloadId, null); + } - if (download.UnpackingQueued != null && download.UnpackingStarted != null && download.UnpackingFinished == null && download.Error == null) - { - Log("Resetting unpack status", download, torrent); + if (download.UnpackingQueued != null && download.UnpackingStarted != null && download.UnpackingFinished == null && download.Error == null) + { + Log("Resetting unpack status", download, torrent); - await _downloads.UpdateUnpackingStarted(download.DownloadId, null); - } + await _downloads.UpdateUnpackingStarted(download.DownloadId, null); + } + } + } + + Log("TorrentRunner Initialized"); + } + + public async Task Tick() + { + if (String.IsNullOrWhiteSpace(Settings.Get.RealDebridApiKey)) + { + Log($"No RealDebridApiKey set in settings"); + return; + } + + var settingDownloadLimit = Settings.Get.DownloadLimit; + if (settingDownloadLimit < 1) + { + settingDownloadLimit = 1; + } + + var settingUnpackLimit = Settings.Get.UnpackLimit; + if (settingUnpackLimit < 1) + { + settingUnpackLimit = 1; + } + + var settingDownloadPath = Settings.Get.DownloadPath; + if (String.IsNullOrWhiteSpace(settingDownloadPath)) + { + _logger.LogError("No DownloadPath set in settings"); + return; + } + + var sw = new Stopwatch(); + sw.Start(); + + if (!ActiveDownloadClients.IsEmpty || !ActiveUnpackClients.IsEmpty) + { + Log($"TorrentRunner Tick Start, {ActiveDownloadClients.Count} active downloads, {ActiveUnpackClients.Count} active unpacks"); + } + + if (ActiveDownloadClients.Any(m => m.Value.Type == "Aria2c")) + { + Log("Updating Aria2 status"); + + var aria2NetClient = new Aria2NetClient(Settings.Get.Aria2cUrl, Settings.Get.Aria2cSecret, _httpClient, 1); + + var allDownloads = await aria2NetClient.TellAll(); + + Log($"Found {allDownloads.Count} Aria2 downloads"); + + foreach (var activeDownload in ActiveDownloadClients) + { + if (activeDownload.Value.Downloader is Aria2cDownloader aria2Downloader) + { + await aria2Downloader.Update(allDownloads); } } - Log("TorrentRunner Initialized"); + Log("Finished updating Aria2 status"); } - public async Task Tick() + // Check if any torrents are finished downloading to the host, remove them from the active download list. + var completedActiveDownloads = ActiveDownloadClients.Where(m => m.Value.Finished).ToList(); + + if (completedActiveDownloads.Count > 0) { - if (String.IsNullOrWhiteSpace(Settings.Get.RealDebridApiKey)) + Log($"Processing {completedActiveDownloads.Count} completed downloads"); + + foreach (var (downloadId, downloadClient) in completedActiveDownloads) { - Log($"No RealDebridApiKey set in settings"); - return; - } - - var settingDownloadLimit = Settings.Get.DownloadLimit; - if (settingDownloadLimit < 1) - { - settingDownloadLimit = 1; - } + var download = await _downloads.GetById(downloadId); - var settingUnpackLimit = Settings.Get.UnpackLimit; - if (settingUnpackLimit < 1) - { - settingUnpackLimit = 1; - } - - var settingDownloadPath = Settings.Get.DownloadPath; - if (String.IsNullOrWhiteSpace(settingDownloadPath)) - { - _logger.LogError("No DownloadPath set in settings"); - return; - } - - var sw = new Stopwatch(); - sw.Start(); - - if (!ActiveDownloadClients.IsEmpty || !ActiveUnpackClients.IsEmpty) - { - Log($"TorrentRunner Tick Start, {ActiveDownloadClients.Count} active downloads, {ActiveUnpackClients.Count} active unpacks"); - } - - if (ActiveDownloadClients.Any(m => m.Value.Type == "Aria2c")) - { - Log("Updating Aria2 status"); - - var aria2NetClient = new Aria2NetClient(Settings.Get.Aria2cUrl, Settings.Get.Aria2cSecret, _httpClient, 1); - - var allDownloads = await aria2NetClient.TellAll(); - - Log($"Found {allDownloads.Count} Aria2 downloads"); - - foreach (var activeDownload in ActiveDownloadClients) + if (download == null) { - if (activeDownload.Value.Downloader is Aria2cDownloader aria2Downloader) - { - await aria2Downloader.Update(allDownloads); - } - } - - Log("Finished updating Aria2 status"); - } - - // Check if any torrents are finished downloading to the host, remove them from the active download list. - var completedActiveDownloads = ActiveDownloadClients.Where(m => m.Value.Finished).ToList(); - - if (completedActiveDownloads.Count > 0) - { - Log($"Processing {completedActiveDownloads.Count} completed downloads"); - - foreach (var (downloadId, downloadClient) in completedActiveDownloads) - { - var download = await _downloads.GetById(downloadId); - - if (download == null) - { - ActiveDownloadClients.TryRemove(downloadId, out _); - - Log($"Download with ID {downloadId} not found! Removed from download queue"); - - continue; - } - - Log("Processing download", download, download.Torrent); - - if (!String.IsNullOrWhiteSpace(downloadClient.Error)) - { - // Retry the download if an error is encountered. - Log($"Download reported an error: {downloadClient.Error}", download, download.Torrent); - Log($"Download retry count {download.RetryCount}/{download.Torrent.DownloadRetryAttempts}, torrent retry count {download.Torrent.RetryCount}/{download.Torrent.TorrentRetryAttempts}", download, download.Torrent); - - if (download.RetryCount < download.Torrent.DownloadRetryAttempts) - { - Log($"Retrying download", download, download.Torrent); - - await _downloads.Reset(downloadId); - await _downloads.UpdateRetryCount(downloadId, download.RetryCount + 1); - } - else - { - Log($"Not retrying download", download, download.Torrent); - - await _downloads.UpdateError(downloadId, downloadClient.Error); - await _downloads.UpdateCompleted(downloadId, DateTimeOffset.UtcNow); - } - } - else - { - Log($"Download finished successfully", download, download.Torrent); - - await _downloads.UpdateDownloadFinished(downloadId, DateTimeOffset.UtcNow); - await _downloads.UpdateUnpackingQueued(downloadId, DateTimeOffset.UtcNow); - } - ActiveDownloadClients.TryRemove(downloadId, out _); - Log($"Removed from ActiveDownloadClients", download, download.Torrent); + Log($"Download with ID {downloadId} not found! Removed from download queue"); + + continue; } - } - // Check if any torrents are finished unpacking, remove them from the active unpack list. - var completedUnpacks = ActiveUnpackClients.Where(m => m.Value.Finished).ToList(); + Log("Processing download", download, download.Torrent); - if (completedUnpacks.Count > 0) - { - Log($"Processing {completedUnpacks.Count} completed unpacks"); - - foreach (var (downloadId, unpackClient) in completedUnpacks) + if (!String.IsNullOrWhiteSpace(downloadClient.Error)) { - var download = await _downloads.GetById(downloadId); - - if (download == null) - { - ActiveUnpackClients.TryRemove(downloadId, out _); - - Log($"Download with ID {downloadId} not found! Removed from unpack queue"); - - continue; - } - - if (unpackClient.Error != null) - { - Log($"Unpack reported an error: {unpackClient.Error}", download, download.Torrent); + // Retry the download if an error is encountered. + Log($"Download reported an error: {downloadClient.Error}", download, download.Torrent); + Log($"Download retry count {download.RetryCount}/{download.Torrent.DownloadRetryAttempts}, torrent retry count {download.Torrent.RetryCount}/{download.Torrent.TorrentRetryAttempts}", download, download.Torrent); - await _downloads.UpdateError(downloadId, unpackClient.Error); - await _downloads.UpdateCompleted(downloadId, DateTimeOffset.UtcNow); + if (download.RetryCount < download.Torrent.DownloadRetryAttempts) + { + Log($"Retrying download", download, download.Torrent); + + await _downloads.Reset(downloadId); + await _downloads.UpdateRetryCount(downloadId, download.RetryCount + 1); } else { - Log($"Unpack finished successfully", download, download.Torrent); + Log($"Not retrying download", download, download.Torrent); - await _downloads.UpdateUnpackingFinished(downloadId, DateTimeOffset.UtcNow); + await _downloads.UpdateError(downloadId, downloadClient.Error); await _downloads.UpdateCompleted(downloadId, DateTimeOffset.UtcNow); } + } + else + { + Log($"Download finished successfully", download, download.Torrent); + await _downloads.UpdateDownloadFinished(downloadId, DateTimeOffset.UtcNow); + await _downloads.UpdateUnpackingQueued(downloadId, DateTimeOffset.UtcNow); + } + + ActiveDownloadClients.TryRemove(downloadId, out _); + + Log($"Removed from ActiveDownloadClients", download, download.Torrent); + } + } + + // Check if any torrents are finished unpacking, remove them from the active unpack list. + var completedUnpacks = ActiveUnpackClients.Where(m => m.Value.Finished).ToList(); + + if (completedUnpacks.Count > 0) + { + Log($"Processing {completedUnpacks.Count} completed unpacks"); + + foreach (var (downloadId, unpackClient) in completedUnpacks) + { + var download = await _downloads.GetById(downloadId); + + if (download == null) + { ActiveUnpackClients.TryRemove(downloadId, out _); - Log($"Removed from ActiveUnpackClients", download, download.Torrent); + Log($"Download with ID {downloadId} not found! Removed from unpack queue"); + + continue; } - } - var torrents = await _torrents.Get(); - - // Process torrent retries - foreach (var torrent in torrents.Where(m => m.Retry != null)) - { - try + if (unpackClient.Error != null) { - Log($"Retrying torrent {torrent.RetryCount}/{torrent.TorrentRetryAttempts}", torrent); - - if (torrent.RetryCount > torrent.TorrentRetryAttempts) - { - await _torrents.UpdateRetry(torrent.TorrentId, null, torrent.RetryCount); - Log($"Torrent reach max retry count"); - continue; - } - - await _torrents.RetryTorrent(torrent.TorrentId, torrent.RetryCount); + Log($"Unpack reported an error: {unpackClient.Error}", download, download.Torrent); + + await _downloads.UpdateError(downloadId, unpackClient.Error); + await _downloads.UpdateCompleted(downloadId, DateTimeOffset.UtcNow); } - catch (Exception ex) + else + { + Log($"Unpack finished successfully", download, download.Torrent); + + await _downloads.UpdateUnpackingFinished(downloadId, DateTimeOffset.UtcNow); + await _downloads.UpdateCompleted(downloadId, DateTimeOffset.UtcNow); + } + + ActiveUnpackClients.TryRemove(downloadId, out _); + + Log($"Removed from ActiveUnpackClients", download, download.Torrent); + } + } + + var torrents = await _torrents.Get(); + + // Process torrent retries + foreach (var torrent in torrents.Where(m => m.Retry != null)) + { + try + { + Log($"Retrying torrent {torrent.RetryCount}/{torrent.TorrentRetryAttempts}", torrent); + + if (torrent.RetryCount > torrent.TorrentRetryAttempts) { await _torrents.UpdateRetry(torrent.TorrentId, null, torrent.RetryCount); - await _torrents.UpdateError(torrent.TorrentId, ex.Message); + Log($"Torrent reach max retry count"); + continue; } - } - // Process torrent errors - foreach (var torrent in torrents.Where(m => m.Error != null && m.DeleteOnError > 0)) + await _torrents.RetryTorrent(torrent.TorrentId, torrent.RetryCount); + } + catch (Exception ex) { - if (torrent.Completed == null) - { - continue; - } - - if (torrent.Completed.Value.AddMinutes(torrent.DeleteOnError) > DateTime.UtcNow) - { - continue; - } - - Log($"Removing torrent because it has been {torrent.DeleteOnError} minutes in the error state", torrent); - - await _torrents.Delete(torrent.TorrentId, true, true, true); + await _torrents.UpdateRetry(torrent.TorrentId, null, torrent.RetryCount); + await _torrents.UpdateError(torrent.TorrentId, ex.Message); } + } + + // Process torrent errors + foreach (var torrent in torrents.Where(m => m.Error != null && m.DeleteOnError > 0)) + { + if (torrent.Completed == null) + { + continue; + } + + if (torrent.Completed.Value.AddMinutes(torrent.DeleteOnError) > DateTime.UtcNow) + { + continue; + } + + Log($"Removing torrent because it has been {torrent.DeleteOnError} minutes in the error state", torrent); + + await _torrents.Delete(torrent.TorrentId, true, true, true); + } - // Process torrent lifetime - foreach (var torrent in torrents.Where(m => m.Downloads.Count == 0 && m.Completed == null && m.Lifetime > 0)) + // Process torrent lifetime + foreach (var torrent in torrents.Where(m => m.Downloads.Count == 0 && m.Completed == null && m.Lifetime > 0)) + { + if (torrent.Added.AddMinutes(torrent.Lifetime) > DateTime.UtcNow) { - if (torrent.Added.AddMinutes(torrent.Lifetime) > DateTime.UtcNow) - { - continue; - } - - Log($"Torrent has reached its {torrent.Lifetime} minutes lifetime, marking as error", torrent); - - await _torrents.UpdateRetry(torrent.TorrentId, null, torrent.TorrentRetryAttempts); - await _torrents.UpdateComplete(torrent.TorrentId, $"Torrent lifetime of {torrent.Lifetime} minutes reached", DateTimeOffset.UtcNow, false); + continue; } - torrents = await _torrents.Get(); + Log($"Torrent has reached its {torrent.Lifetime} minutes lifetime, marking as error", torrent); - torrents = torrents.Where(m => m.Completed == null).ToList(); + await _torrents.UpdateRetry(torrent.TorrentId, null, torrent.TorrentRetryAttempts); + await _torrents.UpdateComplete(torrent.TorrentId, $"Torrent lifetime of {torrent.Lifetime} minutes reached", DateTimeOffset.UtcNow, false); + } - // Only poll Real-Debrid every second when a hub is connected, otherwise every 30 seconds - if (_nextUpdate < DateTime.UtcNow && ((torrents.Count > 0 && Settings.Get.ProviderAutoImport == 0) || Settings.Get.ProviderAutoImport == 1)) - { - Log($"Updating torrent info from Real-Debrid"); + torrents = await _torrents.Get(); + + torrents = torrents.Where(m => m.Completed == null).ToList(); + + // Only poll Real-Debrid every second when a hub is connected, otherwise every 30 seconds + if (_nextUpdate < DateTime.UtcNow && ((torrents.Count > 0 && Settings.Get.ProviderAutoImport == 0) || Settings.Get.ProviderAutoImport == 1)) + { + Log($"Updating torrent info from Real-Debrid"); #if DEBUG - var updateTime = 0; + var updateTime = 0; - _nextUpdate = DateTime.UtcNow; + _nextUpdate = DateTime.UtcNow; #else var updateTime = 30; @@ -322,306 +317,305 @@ namespace RdtClient.Service.Services _nextUpdate = DateTime.UtcNow.AddSeconds(updateTime); #endif - await _torrents.UpdateRdData(); + await _torrents.UpdateRdData(); - // Re-get torrents to account for updated info - torrents = await _torrents.Get(); - torrents = torrents.Where(m => m.Completed == null).ToList(); + // Re-get torrents to account for updated info + torrents = await _torrents.Get(); + torrents = torrents.Where(m => m.Completed == null).ToList(); - Log($"Finished updating torrent info from Real-Debrid, next update in {updateTime} seconds"); - } + Log($"Finished updating torrent info from Real-Debrid, next update in {updateTime} seconds"); + } - if (torrents.Count > 0) + if (torrents.Count > 0) + { + Log($"Processing {torrents.Count} torrents"); + } + + foreach (var torrent in torrents) + { + try { - Log($"Processing {torrents.Count} torrents"); - } + // Check if there are any downloads that are queued and can be started. + var queuedDownloads = torrent.Downloads + .Where(m => m.Completed == null && m.DownloadQueued != null && m.DownloadStarted == null && m.Error == null) + .OrderBy(m => m.DownloadQueued) + .ToList(); - foreach (var torrent in torrents) - { - try + foreach (var download in queuedDownloads) { - // Check if there are any downloads that are queued and can be started. - var queuedDownloads = torrent.Downloads - .Where(m => m.Completed == null && m.DownloadQueued != null && m.DownloadStarted == null && m.Error == null) - .OrderBy(m => m.DownloadQueued) - .ToList(); + Log($"Processing to download", download, torrent); - foreach (var download in queuedDownloads) + if (ActiveDownloadClients.Count >= settingDownloadLimit) { - Log($"Processing to download", download, torrent); - - if (ActiveDownloadClients.Count >= settingDownloadLimit) - { - Log($"Not starting download because there are already the max number of downloads active", download, torrent); - - continue; - } - - if (ActiveDownloadClients.ContainsKey(download.DownloadId)) - { - Log($"Not starting download because this download is already active", download, torrent); - - continue; - } - - try - { - Log($"Unrestricting links", download, torrent); - - var downloadLink = await _torrents.UnrestrictLink(download.DownloadId); - download.Link = downloadLink; - } - catch (Exception ex) - { - _logger.LogError(ex, "Cannot unrestrict link: {ex.Message}", ex.Message); - - await _downloads.UpdateError(download.DownloadId, ex.Message); - await _downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.UtcNow); - download.Error = ex.Message; - download.Completed = DateTimeOffset.UtcNow; - - continue; - } - - Log($"Marking download as started", download, torrent); - - download.DownloadStarted = DateTime.UtcNow; - await _downloads.UpdateDownloadStarted(download.DownloadId, download.DownloadStarted); - - var downloadPath = settingDownloadPath; - - if (!String.IsNullOrWhiteSpace(torrent.Category)) - { - downloadPath = Path.Combine(downloadPath, torrent.Category); - } - - Log($"Setting download path to {downloadPath}", download, torrent); - - // Start the download process - var downloadClient = new DownloadClient(download, torrent, downloadPath); - - if (ActiveDownloadClients.TryAdd(download.DownloadId, downloadClient)) - { - Log($"Starting download", download, torrent); - - var remoteId = await downloadClient.Start(Settings.Get); - - if (!String.IsNullOrWhiteSpace(remoteId) && download.RemoteId != remoteId) - { - Log($"Received ID {remoteId}", download, torrent); - - await _downloads.UpdateRemoteId(download.DownloadId, remoteId); - } - else - { - Log($"No ID received", download, torrent); - } - } - } - - // Check if there are any unpacks that are queued and can be started. - var queuedUnpacks = torrent.Downloads - .Where(m => m.Completed == null && m.UnpackingQueued != null && m.UnpackingStarted == null && m.Error == null) - .OrderBy(m => m.DownloadQueued) - .ToList(); - - foreach (var download in queuedUnpacks) - { - Log($"Starting unpack", download, torrent); - - if (download.Link == null) - { - Log($"No download link found", download, torrent); - - await _downloads.UpdateError(download.DownloadId, "Download Link cannot be null"); - await _downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.UtcNow); - - continue; - } - - // Check if the unpacking process is even needed - var uri = new Uri(download.Link); - var fileName = uri.Segments.Last(); - - fileName = HttpUtility.UrlDecode(fileName); - - Log($"Found file name {fileName}", download, torrent); - - var extension = Path.GetExtension(fileName); - - if (extension != ".rar") - { - Log($"No need to unpack, setting it as unpacked", download, torrent); - - download.UnpackingStarted = DateTimeOffset.UtcNow; - download.UnpackingFinished = DateTimeOffset.UtcNow; - download.Completed = DateTimeOffset.UtcNow; - - await _downloads.UpdateUnpackingStarted(download.DownloadId, download.UnpackingStarted); - await _downloads.UpdateUnpackingFinished(download.DownloadId, download.UnpackingFinished); - await _downloads.UpdateCompleted(download.DownloadId, download.Completed); - - continue; - } - - // Check if we have reached the download limit, if so queue the download, but don't start it. - if (TorrentRunner.ActiveUnpackClients.Count >= settingUnpackLimit) - { - Log($"Not starting unpack because there are already the max number of unpacks active", download, torrent); - - continue; - } - - if (TorrentRunner.ActiveUnpackClients.ContainsKey(download.DownloadId)) - { - Log($"Not starting unpack because this download is already active", download, torrent); - - continue; - } - - download.UnpackingStarted = DateTimeOffset.UtcNow; - await _downloads.UpdateUnpackingStarted(download.DownloadId, download.UnpackingStarted); - - var downloadPath = settingDownloadPath; - - if (!String.IsNullOrWhiteSpace(torrent.Category)) - { - downloadPath = Path.Combine(downloadPath, torrent.Category); - } - - Log($"Setting unpack path to {downloadPath}", download, torrent); - - // Start the unpacking process - var unpackClient = new UnpackClient(download, downloadPath); - - if (TorrentRunner.ActiveUnpackClients.TryAdd(download.DownloadId, unpackClient)) - { - Log($"Starting unpack", download, torrent); - - unpackClient.Start(); - } - } - - Log("Processing", torrent); - - // If torrent is erroring out on the Real-Debrid side. - if (torrent.RdStatus == TorrentStatus.Error) - { - Log($"Torrent reported an error: {torrent.RdStatusRaw}", torrent); - Log($"Torrent retry count {torrent.RetryCount}/{torrent.TorrentRetryAttempts}", torrent); - - Log($"Received RealDebrid error: {torrent.RdStatusRaw}, not processing further", torrent); - - await _torrents.UpdateComplete(torrent.TorrentId, $"Received RealDebrid error: {torrent.RdStatusRaw}.", DateTimeOffset.UtcNow, true); + Log($"Not starting download because there are already the max number of downloads active", download, torrent); continue; } - // Real-Debrid is waiting for file selection, select which files to download. - if ((torrent.RdStatus == TorrentStatus.WaitingForFileSelection || torrent.RdStatus == TorrentStatus.Finished) && - torrent.FilesSelected == null && - torrent.Downloads.Count == 0) + if (ActiveDownloadClients.ContainsKey(download.DownloadId)) { - Log($"Selecting files", torrent); + Log($"Not starting download because this download is already active", download, torrent); - await _torrents.SelectFiles(torrent.TorrentId); - - await _torrents.UpdateFilesSelected(torrent.TorrentId, DateTime.UtcNow); + continue; } - // Real-Debrid finished downloading the torrent, process the file to host. - if (torrent.RdStatus == TorrentStatus.Finished) + try { - // The files are selected but there are no downloads yet, check if Real-Debrid has generated links yet. - if (torrent.Downloads.Count == 0 && torrent.FilesSelected != null) - { - Log($"Creating downloads", torrent); + Log($"Unrestricting links", download, torrent); - await _torrents.CreateDownloads(torrent.TorrentId); - } + var downloadLink = await _torrents.UnrestrictLink(download.DownloadId); + download.Link = downloadLink; + } + catch (Exception ex) + { + _logger.LogError(ex, "Cannot unrestrict link: {ex.Message}", ex.Message); + + await _downloads.UpdateError(download.DownloadId, ex.Message); + await _downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.UtcNow); + download.Error = ex.Message; + download.Completed = DateTimeOffset.UtcNow; + + continue; } - // Check if torrent is complete - if (torrent.Downloads.Count > 0) + Log($"Marking download as started", download, torrent); + + download.DownloadStarted = DateTime.UtcNow; + await _downloads.UpdateDownloadStarted(download.DownloadId, download.DownloadStarted); + + var downloadPath = settingDownloadPath; + + if (!String.IsNullOrWhiteSpace(torrent.Category)) { - var allComplete = torrent.Downloads.Count(m => m.Completed != null); + downloadPath = Path.Combine(downloadPath, torrent.Category); + } - if (allComplete == torrent.Downloads.Count) + Log($"Setting download path to {downloadPath}", download, torrent); + + // Start the download process + var downloadClient = new DownloadClient(download, torrent, downloadPath); + + if (ActiveDownloadClients.TryAdd(download.DownloadId, downloadClient)) + { + Log($"Starting download", download, torrent); + + var remoteId = await downloadClient.Start(Settings.Get); + + if (!String.IsNullOrWhiteSpace(remoteId) && download.RemoteId != remoteId) { - Log($"All downloads complete, marking torrent as complete", torrent); + Log($"Received ID {remoteId}", download, torrent); - await _torrents.UpdateComplete(torrent.TorrentId, null, DateTimeOffset.UtcNow, true); - - switch (torrent.FinishedAction) - { - case TorrentFinishedAction.RemoveAllTorrents: - Log($"Removing torrents from Real-Debrid and Real-Debrid Client, no files", torrent); - await _torrents.Delete(torrent.TorrentId, true, true, false); - - break; - case TorrentFinishedAction.RemoveRealDebrid: - Log($"Removing torrents from Real-Debrid, no files", torrent); - await _torrents.Delete(torrent.TorrentId, false, true, false); - - break; - case TorrentFinishedAction.None: - Log($"Not removing torrents or files", torrent); - - break; - default: - Log($"Invalid torrent FinishedAction {torrent.FinishedAction}", torrent); - - break; - } - - await _torrents.RunTorrentComplete(torrent.TorrentId); + await _downloads.UpdateRemoteId(download.DownloadId, remoteId); } else { - Log($"Waiting for downloads to complete. {allComplete}/{torrent.Downloads.Count} complete", torrent); + Log($"No ID received", download, torrent); } } } - catch (Exception ex) + + // Check if there are any unpacks that are queued and can be started. + var queuedUnpacks = torrent.Downloads + .Where(m => m.Completed == null && m.UnpackingQueued != null && m.UnpackingStarted == null && m.Error == null) + .OrderBy(m => m.DownloadQueued) + .ToList(); + + foreach (var download in queuedUnpacks) { - _logger.LogError(ex.Message, "Torrent processing result in an unexpected exception: {Message}", ex.Message); - await _torrents.UpdateComplete(torrent.TorrentId, ex.Message, DateTimeOffset.UtcNow, true); + Log($"Starting unpack", download, torrent); + + if (download.Link == null) + { + Log($"No download link found", download, torrent); + + await _downloads.UpdateError(download.DownloadId, "Download Link cannot be null"); + await _downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.UtcNow); + + continue; + } + + // Check if the unpacking process is even needed + var uri = new Uri(download.Link); + var fileName = uri.Segments.Last(); + + fileName = HttpUtility.UrlDecode(fileName); + + Log($"Found file name {fileName}", download, torrent); + + var extension = Path.GetExtension(fileName); + + if (extension != ".rar") + { + Log($"No need to unpack, setting it as unpacked", download, torrent); + + download.UnpackingStarted = DateTimeOffset.UtcNow; + download.UnpackingFinished = DateTimeOffset.UtcNow; + download.Completed = DateTimeOffset.UtcNow; + + await _downloads.UpdateUnpackingStarted(download.DownloadId, download.UnpackingStarted); + await _downloads.UpdateUnpackingFinished(download.DownloadId, download.UnpackingFinished); + await _downloads.UpdateCompleted(download.DownloadId, download.Completed); + + continue; + } + + // Check if we have reached the download limit, if so queue the download, but don't start it. + if (TorrentRunner.ActiveUnpackClients.Count >= settingUnpackLimit) + { + Log($"Not starting unpack because there are already the max number of unpacks active", download, torrent); + + continue; + } + + if (TorrentRunner.ActiveUnpackClients.ContainsKey(download.DownloadId)) + { + Log($"Not starting unpack because this download is already active", download, torrent); + + continue; + } + + download.UnpackingStarted = DateTimeOffset.UtcNow; + await _downloads.UpdateUnpackingStarted(download.DownloadId, download.UnpackingStarted); + + var downloadPath = settingDownloadPath; + + if (!String.IsNullOrWhiteSpace(torrent.Category)) + { + downloadPath = Path.Combine(downloadPath, torrent.Category); + } + + Log($"Setting unpack path to {downloadPath}", download, torrent); + + // Start the unpacking process + var unpackClient = new UnpackClient(download, downloadPath); + + if (TorrentRunner.ActiveUnpackClients.TryAdd(download.DownloadId, unpackClient)) + { + Log($"Starting unpack", download, torrent); + + unpackClient.Start(); + } + } + + Log("Processing", torrent); + + // If torrent is erroring out on the Real-Debrid side. + if (torrent.RdStatus == TorrentStatus.Error) + { + Log($"Torrent reported an error: {torrent.RdStatusRaw}", torrent); + Log($"Torrent retry count {torrent.RetryCount}/{torrent.TorrentRetryAttempts}", torrent); + + Log($"Received RealDebrid error: {torrent.RdStatusRaw}, not processing further", torrent); + + await _torrents.UpdateComplete(torrent.TorrentId, $"Received RealDebrid error: {torrent.RdStatusRaw}.", DateTimeOffset.UtcNow, true); + + continue; + } + + // Real-Debrid is waiting for file selection, select which files to download. + if ((torrent.RdStatus == TorrentStatus.WaitingForFileSelection || torrent.RdStatus == TorrentStatus.Finished) && + torrent.FilesSelected == null && + torrent.Downloads.Count == 0) + { + Log($"Selecting files", torrent); + + await _torrents.SelectFiles(torrent.TorrentId); + + await _torrents.UpdateFilesSelected(torrent.TorrentId, DateTime.UtcNow); + } + + // Real-Debrid finished downloading the torrent, process the file to host. + if (torrent.RdStatus == TorrentStatus.Finished) + { + // The files are selected but there are no downloads yet, check if Real-Debrid has generated links yet. + if (torrent.Downloads.Count == 0 && torrent.FilesSelected != null) + { + Log($"Creating downloads", torrent); + + await _torrents.CreateDownloads(torrent.TorrentId); + } + } + + // Check if torrent is complete + if (torrent.Downloads.Count > 0) + { + var allComplete = torrent.Downloads.Count(m => m.Completed != null); + + if (allComplete == torrent.Downloads.Count) + { + Log($"All downloads complete, marking torrent as complete", torrent); + + await _torrents.UpdateComplete(torrent.TorrentId, null, DateTimeOffset.UtcNow, true); + + switch (torrent.FinishedAction) + { + case TorrentFinishedAction.RemoveAllTorrents: + Log($"Removing torrents from Real-Debrid and Real-Debrid Client, no files", torrent); + await _torrents.Delete(torrent.TorrentId, true, true, false); + + break; + case TorrentFinishedAction.RemoveRealDebrid: + Log($"Removing torrents from Real-Debrid, no files", torrent); + await _torrents.Delete(torrent.TorrentId, false, true, false); + + break; + case TorrentFinishedAction.None: + Log($"Not removing torrents or files", torrent); + + break; + default: + Log($"Invalid torrent FinishedAction {torrent.FinishedAction}", torrent); + + break; + } + + await _torrents.RunTorrentComplete(torrent.TorrentId); + } + else + { + Log($"Waiting for downloads to complete. {allComplete}/{torrent.Downloads.Count} complete", torrent); + } } } + catch (Exception ex) + { + _logger.LogError(ex.Message, "Torrent processing result in an unexpected exception: {Message}", ex.Message); + await _torrents.UpdateComplete(torrent.TorrentId, ex.Message, DateTimeOffset.UtcNow, true); + } + } - await _remoteService.Update(); + await _remoteService.Update(); - sw.Stop(); + sw.Stop(); - if (sw.ElapsedMilliseconds > 1000) - { - Log($"TorrentRunner Tick End (took {sw.ElapsedMilliseconds}ms)"); - } - } - - private void Log(String message, Download download, Torrent torrent) + if (sw.ElapsedMilliseconds > 1000) { - if (download != null) - { - message = $"{message} {download.ToLog()}"; - } - - if (torrent != null) - { - message = $"{message} {torrent.ToLog()}"; - } - - _logger.LogDebug(message); - } - - private void Log(String message, Torrent torrent = null) - { - if (torrent != null) - { - message = $"{message} {torrent.ToLog()}"; - } - - _logger.LogDebug(message); + Log($"TorrentRunner Tick End (took {sw.ElapsedMilliseconds}ms)"); } } -} + + private void Log(String message, Download download, Torrent torrent) + { + if (download != null) + { + message = $"{message} {download.ToLog()}"; + } + + if (torrent != null) + { + message = $"{message} {torrent.ToLog()}"; + } + + _logger.LogDebug(message); + } + + private void Log(String message, Torrent torrent = null) + { + if (torrent != null) + { + message = $"{message} {torrent.ToLog()}"; + } + + _logger.LogDebug(message); + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/Torrents.cs b/server/RdtClient.Service/Services/Torrents.cs index b4ab175..9b0f06a 100644 --- a/server/RdtClient.Service/Services/Torrents.cs +++ b/server/RdtClient.Service/Services/Torrents.cs @@ -1,13 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; -using System.IO; -using System.Linq; using System.Text; using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using MonoTorrent; using System.Text.Json.Serialization; @@ -19,545 +13,44 @@ using RdtClient.Service.Helpers; using RdtClient.Service.Services.TorrentClients; using Torrent = RdtClient.Data.Models.Data.Torrent; -namespace RdtClient.Service.Services +namespace RdtClient.Service.Services; + +public class Torrents { - public class Torrents + private static readonly SemaphoreSlim RealDebridUpdateLock = new(1, 1); + + private readonly ILogger _logger; + private readonly TorrentData _torrentData; + private readonly Downloads _downloads; + + private readonly ITorrentClient _torrentClient; + + private static readonly SemaphoreSlim TorrentResetLock = new(1, 1); + + public Torrents(ILogger logger, + TorrentData torrentData, + Downloads downloads, + AllDebridTorrentClient allDebridTorrentClient, + RealDebridTorrentClient realDebridTorrentClient) { - private static readonly SemaphoreSlim RealDebridUpdateLock = new(1, 1); - - private readonly ILogger _logger; - private readonly TorrentData _torrentData; - private readonly Downloads _downloads; - - private readonly ITorrentClient _torrentClient; - - private static readonly SemaphoreSlim TorrentResetLock = new(1, 1); - - public Torrents(ILogger logger, - TorrentData torrentData, - Downloads downloads, - AllDebridTorrentClient allDebridTorrentClient, - RealDebridTorrentClient realDebridTorrentClient) - { - _logger = logger; - _torrentData = torrentData; - _downloads = downloads; + _logger = logger; + _torrentData = torrentData; + _downloads = downloads; - _torrentClient = Settings.Get.Provider switch - { - "RealDebrid" => realDebridTorrentClient, - "AllDebrid" => allDebridTorrentClient, - _ => null - }; - } - - public async Task> Get() + _torrentClient = Settings.Get.Provider switch { - var torrents = await _torrentData.Get(); + "RealDebrid" => realDebridTorrentClient, + "AllDebrid" => allDebridTorrentClient, + _ => null + }; + } - foreach (var torrent in torrents) - { - foreach (var download in torrent.Downloads) - { - if (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient)) - { - download.Speed = downloadClient.Speed; - download.BytesTotal = downloadClient.BytesTotal; - download.BytesDone = downloadClient.BytesDone; - } + public async Task> Get() + { + var torrents = await _torrentData.Get(); - if (TorrentRunner.ActiveUnpackClients.TryGetValue(download.DownloadId, out var unpackClient)) - { - download.BytesTotal = unpackClient.BytesTotal; - download.BytesDone = unpackClient.BytesDone; - } - } - } - - return torrents; - } - - public async Task GetByHash(String hash) + foreach (var torrent in torrents) { - var torrent = await _torrentData.GetByHash(hash); - - if (torrent != null) - { - await UpdateTorrentClientData(torrent); - } - - return torrent; - } - - public async Task UpdateCategory(String hash, String category) - { - var torrent = await _torrentData.GetByHash(hash); - - if (torrent == null) - { - return; - } - - Log($"Update category to {category}", torrent); - - await _torrentData.UpdateCategory(torrent.TorrentId, category); - } - - public async Task UploadMagnet(String magnetLink, Torrent torrent) - { - MagnetLink magnet; - - try - { - magnet = MagnetLink.Parse(magnetLink); - } - catch (Exception ex) - { - _logger.LogError(ex, "{ex.Message}, trying to parse {magnetLink}", ex.Message, magnetLink); - throw new Exception($"{ex.Message}, trying to parse {magnetLink}"); - } - - var id = await _torrentClient.AddMagnet(magnetLink); - - var hash = magnet.InfoHash.ToHex(); - - var newTorrent = await Add(id, hash, magnetLink, false, torrent); - - Log($"Adding {hash} magnet link {magnetLink}", newTorrent); - - return newTorrent; - } - - public async Task UploadFile(Byte[] bytes, Torrent torrent) - { - MonoTorrent.Torrent monoTorrent; - - var fileAsBase64 = Convert.ToBase64String(bytes); - - try - { - monoTorrent = await MonoTorrent.Torrent.LoadAsync(bytes); - } - catch (Exception ex) - { - throw new Exception($"{ex.Message}, trying to parse {fileAsBase64}"); - } - - var id = await _torrentClient.AddFile(bytes); - - var hash = monoTorrent.InfoHash.ToHex(); - - var newTorrent = await Add(id, hash, fileAsBase64, true, torrent); - - Log($"Adding {hash} torrent file {fileAsBase64}", newTorrent); - - return newTorrent; - } - - public async Task> GetAvailableFiles(String hash) - { - var result = await _torrentClient.GetAvailableFiles(hash); - - return result; - } - - public async Task SelectFiles(Guid torrentId) - { - var torrent = await GetById(torrentId); - - if (torrent == null) - { - return; - } - - await _torrentClient.SelectFiles(torrent); - } - - public async Task CreateDownloads(Guid torrentId) - { - var torrent = await GetById(torrentId); - - if (torrent == null) - { - return; - } - - var downloadLinks = await _torrentClient.GetDownloadLinks(torrent); - - if (downloadLinks == null) - { - return; - } - - foreach (var downloadLink in downloadLinks) - { - // Make sure downloads don't get added multiple times - var downloadExists = await _downloads.Get(torrent.TorrentId, downloadLink); - - if (downloadExists == null && !String.IsNullOrWhiteSpace(downloadLink)) - { - await _downloads.Add(torrent.TorrentId, downloadLink); - } - } - } - - public async Task Delete(Guid torrentId, Boolean deleteData, Boolean deleteRdTorrent, Boolean deleteLocalFiles) - { - var torrent = await GetById(torrentId); - - if (torrent == null) - { - return; - } - - Log($"Deleting", torrent); - - await UpdateComplete(torrentId, "Torrent deleted", DateTimeOffset.UtcNow, false); - - foreach (var download in torrent.Downloads) - { - var retry = 10; - - while (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient)) - { - Log($"Cancelling download", download, torrent); - - await downloadClient.Cancel(); - - await Task.Delay(500); - - retry++; - - if (retry > 5) - { - break; - } - } - - retry = 10; - - while (TorrentRunner.ActiveUnpackClients.TryGetValue(download.DownloadId, out var unpackClient)) - { - Log($"Cancelling unpack", download, torrent); - - unpackClient.Cancel(); - - await Task.Delay(500); - - retry++; - - if (retry > 10) - { - break; - } - } - } - - if (deleteData) - { - Log($"Deleting RdtClient data", torrent); - - await _downloads.DeleteForTorrent(torrent.TorrentId); - await _torrentData.Delete(torrentId); - } - - if (deleteRdTorrent) - { - Log($"Deleting RealDebrid Torrent", torrent); - - try - { - await _torrentClient.Delete(torrent.RdId); - } - catch - { - // ignored - } - } - - if (deleteLocalFiles && !String.IsNullOrWhiteSpace(torrent.RdName)) - { - var downloadPath = DownloadPath(torrent); - downloadPath = Path.Combine(downloadPath, torrent.RdName); - - Log($"Deleting local files in {downloadPath}", torrent); - - if (Directory.Exists(downloadPath)) - { - var retry = 0; - - while (true) - { - try - { - Directory.Delete(downloadPath, true); - - break; - } - catch - { - retry++; - - if (retry >= 3) - { - throw; - } - - await Task.Delay(1000); - } - } - } - } - } - - public async Task UnrestrictLink(Guid downloadId) - { - var download = await _downloads.GetById(downloadId); - - if (download == null) - { - throw new Exception($"Download with ID {downloadId} not found"); - } - - Log($"Unrestricting link", download, download.Torrent); - - var unrestrictedLink = await _torrentClient.Unrestrict(download.Path); - - await _downloads.UpdateUnrestrictedLink(downloadId, unrestrictedLink); - - return unrestrictedLink; - } - - public async Task GetProfile() - { - var user = await _torrentClient.GetUser(); - - var profile = new Profile - { - Provider = Settings.Get.Provider, - UserName = user.Username, - Expiration = user.Expiration, - CurrentVersion = UpdateChecker.CurrentVersion, - LatestVersion = UpdateChecker.LatestVersion - }; - - return profile; - } - - public async Task UpdateRdData() - { - await RealDebridUpdateLock.WaitAsync(); - - var torrents = await Get(); - - try - { - var rdTorrents = await _torrentClient.GetTorrents(); - - foreach (var rdTorrent in rdTorrents) - { - var torrent = torrents.FirstOrDefault(m => m.RdId == rdTorrent.Id); - - // Auto import torrents only torrents that have their files selected - if (torrent == null && Settings.Get.ProviderAutoImport == 1) - { - var newTorrent = new Torrent - { - Category = Settings.Get.ProviderAutoImportCategory, - DownloadAction = TorrentDownloadAction.DownloadManual, - FinishedAction = TorrentFinishedAction.None, - DownloadMinSize = 0, - TorrentRetryAttempts = 0, - DownloadRetryAttempts = Settings.Get.DownloadRetryAttempts, - DeleteOnError = Settings.Get.DeleteOnError, - Lifetime = Settings.Get.TorrentLifetime, - Priority = 0, - RdId = rdTorrent.Id - }; - - if (newTorrent.RdStatus == TorrentStatus.WaitingForFileSelection) - { - continue; - } - - torrent = await _torrentData.Add(rdTorrent.Id, rdTorrent.Hash, null, false, newTorrent); - - await UpdateTorrentClientData(torrent, rdTorrent); - } - else if (torrent != null) - { - await UpdateTorrentClientData(torrent, rdTorrent); - } - } - - foreach (var torrent in torrents) - { - var rdTorrent = rdTorrents.FirstOrDefault(m => m.Id == torrent.RdId); - - if (rdTorrent == null && Settings.Get.ProviderAutoDelete == 1) - { - await Delete(torrent.TorrentId, true, false, true); - } - } - } - finally - { - RealDebridUpdateLock.Release(); - } - } - - public async Task RetryTorrent(Guid torrentId, Int32 retryCount) - { - await TorrentResetLock.WaitAsync(); - - try - { - var torrent = await _torrentData.GetById(torrentId); - - if (torrent?.Retry == null) - { - return; - } - - Log($"Retrying Torrent", torrent); - - await UpdateComplete(torrent.TorrentId, "Retrying Torrent", DateTimeOffset.UtcNow, false); - await UpdateRetry(torrent.TorrentId, null, 0); - - foreach (var download in torrent.Downloads) - { - await _downloads.UpdateError(download.DownloadId, null); - await _downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.UtcNow); - } - - foreach (var download in torrent.Downloads) - { - while (TorrentRunner.ActiveDownloadClients.TryRemove(download.DownloadId, out var downloadClient)) - { - await downloadClient.Cancel(); - - await Task.Delay(100); - } - - while (TorrentRunner.ActiveUnpackClients.TryRemove(download.DownloadId, out var unpackClient)) - { - unpackClient.Cancel(); - - await Task.Delay(100); - } - } - - await Delete(torrentId, true, true, true); - - if (String.IsNullOrWhiteSpace(torrent.FileOrMagnet)) - { - throw new Exception($"Cannot re-add this torrent, original magnet or file not found"); - } - - Torrent newTorrent; - - if (torrent.IsFile) - { - var bytes = Convert.FromBase64String(torrent.FileOrMagnet); - - newTorrent = await UploadFile(bytes, torrent); - } - else - { - newTorrent = await UploadMagnet(torrent.FileOrMagnet, torrent); - } - - await _torrentData.UpdateRetry(newTorrent.TorrentId, null, retryCount); - } - finally - { - TorrentResetLock.Release(); - } - } - - public async Task RetryDownload(Guid downloadId) - { - var download = await _downloads.GetById(downloadId); - - if (download == null) - { - return; - } - - Log($"Retrying Download", download, download.Torrent); - - while (TorrentRunner.ActiveDownloadClients.TryRemove(download.DownloadId, out var downloadClient)) - { - await downloadClient.Cancel(); - - await Task.Delay(100); - } - - while (TorrentRunner.ActiveUnpackClients.TryRemove(download.DownloadId, out var unpackClient)) - { - unpackClient.Cancel(); - - await Task.Delay(100); - } - - var downloadPath = DownloadPath(download.Torrent); - - var filePath = DownloadHelper.GetDownloadPath(downloadPath, download.Torrent, download); - - Log($"Deleting {filePath}", download, download.Torrent); - - await FileHelper.Delete(filePath); - - Log($"Resetting", download, download.Torrent); - - await _downloads.Reset(downloadId); - - await _torrentData.UpdateComplete(download.TorrentId, null, null, false); - } - - public async Task UpdateComplete(Guid torrentId, String error, DateTimeOffset datetime, Boolean retry) - { - await _torrentData.UpdateComplete(torrentId, error, datetime, retry); - } - - public async Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime) - { - await _torrentData.UpdateFilesSelected(torrentId, datetime); - } - - public async Task UpdatePriority(String hash, Int32 priority) - { - var torrent = await _torrentData.GetByHash(hash); - - if (torrent == null) - { - return; - } - - await _torrentData.UpdatePriority(torrent.TorrentId, priority); - } - - public async Task UpdateRetry(Guid torrentId, DateTimeOffset? datetime, Int32 retry) - { - await _torrentData.UpdateRetry(torrentId, datetime, retry); - } - - public async Task UpdateError(Guid torrentId, String error) - { - await _torrentData.UpdateError(torrentId, error); - } - - public async Task GetById(Guid torrentId) - { - var torrent = await _torrentData.GetById(torrentId); - - if (torrent == null) - { - return null; - } - - await UpdateTorrentClientData(torrent); - foreach (var download in torrent.Downloads) { if (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient)) @@ -573,167 +66,202 @@ namespace RdtClient.Service.Services download.BytesDone = unpackClient.BytesDone; } } - - return torrent; } - private static String DownloadPath(Torrent torrent) - { - var settingDownloadPath = Settings.Get.DownloadPath; + return torrents; + } - if (!String.IsNullOrWhiteSpace(torrent.Category)) + public async Task GetByHash(String hash) + { + var torrent = await _torrentData.GetByHash(hash); + + if (torrent != null) + { + await UpdateTorrentClientData(torrent); + } + + return torrent; + } + + public async Task UpdateCategory(String hash, String category) + { + var torrent = await _torrentData.GetByHash(hash); + + if (torrent == null) + { + return; + } + + Log($"Update category to {category}", torrent); + + await _torrentData.UpdateCategory(torrent.TorrentId, category); + } + + public async Task UploadMagnet(String magnetLink, Torrent torrent) + { + MagnetLink magnet; + + try + { + magnet = MagnetLink.Parse(magnetLink); + } + catch (Exception ex) + { + _logger.LogError(ex, "{ex.Message}, trying to parse {magnetLink}", ex.Message, magnetLink); + throw new Exception($"{ex.Message}, trying to parse {magnetLink}"); + } + + var id = await _torrentClient.AddMagnet(magnetLink); + + var hash = magnet.InfoHash.ToHex(); + + var newTorrent = await Add(id, hash, magnetLink, false, torrent); + + Log($"Adding {hash} magnet link {magnetLink}", newTorrent); + + return newTorrent; + } + + public async Task UploadFile(Byte[] bytes, Torrent torrent) + { + MonoTorrent.Torrent monoTorrent; + + var fileAsBase64 = Convert.ToBase64String(bytes); + + try + { + monoTorrent = await MonoTorrent.Torrent.LoadAsync(bytes); + } + catch (Exception ex) + { + throw new Exception($"{ex.Message}, trying to parse {fileAsBase64}"); + } + + var id = await _torrentClient.AddFile(bytes); + + var hash = monoTorrent.InfoHash.ToHex(); + + var newTorrent = await Add(id, hash, fileAsBase64, true, torrent); + + Log($"Adding {hash} torrent file {fileAsBase64}", newTorrent); + + return newTorrent; + } + + public async Task> GetAvailableFiles(String hash) + { + var result = await _torrentClient.GetAvailableFiles(hash); + + return result; + } + + public async Task SelectFiles(Guid torrentId) + { + var torrent = await GetById(torrentId); + + if (torrent == null) + { + return; + } + + await _torrentClient.SelectFiles(torrent); + } + + public async Task CreateDownloads(Guid torrentId) + { + var torrent = await GetById(torrentId); + + if (torrent == null) + { + return; + } + + var downloadLinks = await _torrentClient.GetDownloadLinks(torrent); + + if (downloadLinks == null) + { + return; + } + + foreach (var downloadLink in downloadLinks) + { + // Make sure downloads don't get added multiple times + var downloadExists = await _downloads.Get(torrent.TorrentId, downloadLink); + + if (downloadExists == null && !String.IsNullOrWhiteSpace(downloadLink)) { - settingDownloadPath = Path.Combine(settingDownloadPath, torrent.Category); + await _downloads.Add(torrent.TorrentId, downloadLink); + } + } + } + + public async Task Delete(Guid torrentId, Boolean deleteData, Boolean deleteRdTorrent, Boolean deleteLocalFiles) + { + var torrent = await GetById(torrentId); + + if (torrent == null) + { + return; + } + + Log($"Deleting", torrent); + + await UpdateComplete(torrentId, "Torrent deleted", DateTimeOffset.UtcNow, false); + + foreach (var download in torrent.Downloads) + { + var retry = 10; + + while (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient)) + { + Log($"Cancelling download", download, torrent); + + await downloadClient.Cancel(); + + await Task.Delay(500); + + retry++; + + if (retry > 5) + { + break; + } } - return settingDownloadPath; + retry = 10; + + while (TorrentRunner.ActiveUnpackClients.TryGetValue(download.DownloadId, out var unpackClient)) + { + Log($"Cancelling unpack", download, torrent); + + unpackClient.Cancel(); + + await Task.Delay(500); + + retry++; + + if (retry > 10) + { + break; + } + } } - private async Task Add(String rdTorrentId, - String infoHash, - String fileOrMagnetContents, - Boolean isFile, - Torrent torrent) + if (deleteData) { - await RealDebridUpdateLock.WaitAsync(); - + Log($"Deleting RdtClient data", torrent); + + await _downloads.DeleteForTorrent(torrent.TorrentId); + await _torrentData.Delete(torrentId); + } + + if (deleteRdTorrent) + { + Log($"Deleting RealDebrid Torrent", torrent); + try { - var existingTorrent = await _torrentData.GetByHash(infoHash); - - if (existingTorrent != null) - { - return existingTorrent; - } - - var newTorrent = await _torrentData.Add(rdTorrentId, - infoHash, - fileOrMagnetContents, - isFile, - torrent); - - await UpdateTorrentClientData(newTorrent); - - return newTorrent; - } - finally - { - RealDebridUpdateLock.Release(); - } - } - - public async Task Update(Torrent torrent) - { - await _torrentData.Update(torrent); - } - - public async Task RunTorrentComplete(Guid torrentId) - { - if (String.IsNullOrWhiteSpace(Settings.Get.RunOnTorrentCompleteFileName)) - { - return; - } - - var torrent = await _torrentData.GetById(torrentId); - var downloads = await _downloads.GetForTorrent(torrentId); - - var fileName = Settings.Get.RunOnTorrentCompleteFileName; - var arguments = Settings.Get.RunOnTorrentCompleteArguments ?? ""; - - Log($"Parsing external program {fileName} with arguments {arguments}", torrent); - - var downloadPath = DownloadPath(torrent); - var torrentPath = Path.Combine(downloadPath, torrent.RdName); - - arguments = arguments.Replace("%N", $"\"{torrent.RdName}\""); - arguments = arguments.Replace("%L", $"\"{torrent.Category}\""); - arguments = arguments.Replace("%F", $"\"{torrentPath}\""); - arguments = arguments.Replace("%R", $"\"{downloadPath}\""); - arguments = arguments.Replace("%D", $"\"{torrentPath}\""); - arguments = arguments.Replace("%C", downloads.Count.ToString(CultureInfo.InvariantCulture).Replace(",", "").Replace(".", "")); - arguments = arguments.Replace("%Z", torrent.RdSize.ToString(CultureInfo.InvariantCulture).Replace(",", "").Replace(".", "")); - arguments = arguments.Replace("%I", torrent.Hash); - - Log($"Executing external program {fileName} with arguments {arguments}", torrent); - - var errorSb = new StringBuilder(); - var outputSb = new StringBuilder(); - - using var process = new Process(); - - process.StartInfo.FileName = fileName; - process.StartInfo.Arguments = arguments; - process.StartInfo.CreateNoWindow = true; - process.StartInfo.UseShellExecute = false; - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.RedirectStandardError = true; - - process.OutputDataReceived += (_, data) => - { - if (data.Data == null) - { - return; - } - - outputSb.AppendLine(data.Data.Trim()); - }; - process.ErrorDataReceived += (_, data) => - { - if (data.Data == null) - { - return; - } - - errorSb.AppendLine(data.Data.Trim()); - }; - - process.Start(); - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - - var exited = process.WaitForExit(60000 * 10); - - var errors = errorSb.ToString(); - var output = outputSb.ToString(); - - if (errors.Length > 0) - { - Log($"External application exited with errors: {errors}", torrent); - } - - if (output.Length > 0) - { - Log($"External application exited with output: {output}", torrent); - } - - if (!exited) - { - Log("External application after a 60 second timeout", torrent); - } - } - - private async Task UpdateTorrentClientData(Torrent torrent, TorrentClientTorrent torrentClientTorrent = null) - { - try - { - var originalTorrent = JsonSerializer.Serialize(torrent, - new JsonSerializerOptions - { - ReferenceHandler = ReferenceHandler.IgnoreCycles - }); - - await _torrentClient.UpdateData(torrent, torrentClientTorrent); - - var newTorrent = JsonSerializer.Serialize(torrent, - new JsonSerializerOptions - { - ReferenceHandler = ReferenceHandler.IgnoreCycles - }); - - if (originalTorrent != newTorrent) - { - await _torrentData.UpdateRdData(torrent); - } + await _torrentClient.Delete(torrent.RdId); } catch { @@ -741,29 +269,494 @@ namespace RdtClient.Service.Services } } - private void Log(String message, Data.Models.Data.Download download, Torrent torrent) + if (deleteLocalFiles && !String.IsNullOrWhiteSpace(torrent.RdName)) { - if (download != null) + var downloadPath = DownloadPath(torrent); + downloadPath = Path.Combine(downloadPath, torrent.RdName); + + Log($"Deleting local files in {downloadPath}", torrent); + + if (Directory.Exists(downloadPath)) { - message = $"{message} {download.ToLog()}"; + var retry = 0; + + while (true) + { + try + { + Directory.Delete(downloadPath, true); + + break; + } + catch + { + retry++; + + if (retry >= 3) + { + throw; + } + + await Task.Delay(1000); + } + } } - - if (torrent != null) - { - message = $"{message} {torrent.ToLog()}"; - } - - _logger.LogDebug(message); - } - - private void Log(String message, Torrent torrent = null) - { - if (torrent != null) - { - message = $"{message} {torrent.ToLog()}"; - } - - _logger.LogDebug(message); } } -} + + public async Task UnrestrictLink(Guid downloadId) + { + var download = await _downloads.GetById(downloadId); + + if (download == null) + { + throw new Exception($"Download with ID {downloadId} not found"); + } + + Log($"Unrestricting link", download, download.Torrent); + + var unrestrictedLink = await _torrentClient.Unrestrict(download.Path); + + await _downloads.UpdateUnrestrictedLink(downloadId, unrestrictedLink); + + return unrestrictedLink; + } + + public async Task GetProfile() + { + var user = await _torrentClient.GetUser(); + + var profile = new Profile + { + Provider = Settings.Get.Provider, + UserName = user.Username, + Expiration = user.Expiration, + CurrentVersion = UpdateChecker.CurrentVersion, + LatestVersion = UpdateChecker.LatestVersion + }; + + return profile; + } + + public async Task UpdateRdData() + { + await RealDebridUpdateLock.WaitAsync(); + + var torrents = await Get(); + + try + { + var rdTorrents = await _torrentClient.GetTorrents(); + + foreach (var rdTorrent in rdTorrents) + { + var torrent = torrents.FirstOrDefault(m => m.RdId == rdTorrent.Id); + + // Auto import torrents only torrents that have their files selected + if (torrent == null && Settings.Get.ProviderAutoImport == 1) + { + var newTorrent = new Torrent + { + Category = Settings.Get.ProviderAutoImportCategory, + DownloadAction = TorrentDownloadAction.DownloadManual, + FinishedAction = TorrentFinishedAction.None, + DownloadMinSize = 0, + TorrentRetryAttempts = 0, + DownloadRetryAttempts = Settings.Get.DownloadRetryAttempts, + DeleteOnError = Settings.Get.DeleteOnError, + Lifetime = Settings.Get.TorrentLifetime, + Priority = 0, + RdId = rdTorrent.Id + }; + + if (newTorrent.RdStatus == TorrentStatus.WaitingForFileSelection) + { + continue; + } + + torrent = await _torrentData.Add(rdTorrent.Id, rdTorrent.Hash, null, false, newTorrent); + + await UpdateTorrentClientData(torrent, rdTorrent); + } + else if (torrent != null) + { + await UpdateTorrentClientData(torrent, rdTorrent); + } + } + + foreach (var torrent in torrents) + { + var rdTorrent = rdTorrents.FirstOrDefault(m => m.Id == torrent.RdId); + + if (rdTorrent == null && Settings.Get.ProviderAutoDelete == 1) + { + await Delete(torrent.TorrentId, true, false, true); + } + } + } + finally + { + RealDebridUpdateLock.Release(); + } + } + + public async Task RetryTorrent(Guid torrentId, Int32 retryCount) + { + await TorrentResetLock.WaitAsync(); + + try + { + var torrent = await _torrentData.GetById(torrentId); + + if (torrent?.Retry == null) + { + return; + } + + Log($"Retrying Torrent", torrent); + + await UpdateComplete(torrent.TorrentId, "Retrying Torrent", DateTimeOffset.UtcNow, false); + await UpdateRetry(torrent.TorrentId, null, 0); + + foreach (var download in torrent.Downloads) + { + await _downloads.UpdateError(download.DownloadId, null); + await _downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.UtcNow); + } + + foreach (var download in torrent.Downloads) + { + while (TorrentRunner.ActiveDownloadClients.TryRemove(download.DownloadId, out var downloadClient)) + { + await downloadClient.Cancel(); + + await Task.Delay(100); + } + + while (TorrentRunner.ActiveUnpackClients.TryRemove(download.DownloadId, out var unpackClient)) + { + unpackClient.Cancel(); + + await Task.Delay(100); + } + } + + await Delete(torrentId, true, true, true); + + if (String.IsNullOrWhiteSpace(torrent.FileOrMagnet)) + { + throw new Exception($"Cannot re-add this torrent, original magnet or file not found"); + } + + Torrent newTorrent; + + if (torrent.IsFile) + { + var bytes = Convert.FromBase64String(torrent.FileOrMagnet); + + newTorrent = await UploadFile(bytes, torrent); + } + else + { + newTorrent = await UploadMagnet(torrent.FileOrMagnet, torrent); + } + + await _torrentData.UpdateRetry(newTorrent.TorrentId, null, retryCount); + } + finally + { + TorrentResetLock.Release(); + } + } + + public async Task RetryDownload(Guid downloadId) + { + var download = await _downloads.GetById(downloadId); + + if (download == null) + { + return; + } + + Log($"Retrying Download", download, download.Torrent); + + while (TorrentRunner.ActiveDownloadClients.TryRemove(download.DownloadId, out var downloadClient)) + { + await downloadClient.Cancel(); + + await Task.Delay(100); + } + + while (TorrentRunner.ActiveUnpackClients.TryRemove(download.DownloadId, out var unpackClient)) + { + unpackClient.Cancel(); + + await Task.Delay(100); + } + + var downloadPath = DownloadPath(download.Torrent); + + var filePath = DownloadHelper.GetDownloadPath(downloadPath, download.Torrent, download); + + Log($"Deleting {filePath}", download, download.Torrent); + + await FileHelper.Delete(filePath); + + Log($"Resetting", download, download.Torrent); + + await _downloads.Reset(downloadId); + + await _torrentData.UpdateComplete(download.TorrentId, null, null, false); + } + + public async Task UpdateComplete(Guid torrentId, String error, DateTimeOffset datetime, Boolean retry) + { + await _torrentData.UpdateComplete(torrentId, error, datetime, retry); + } + + public async Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime) + { + await _torrentData.UpdateFilesSelected(torrentId, datetime); + } + + public async Task UpdatePriority(String hash, Int32 priority) + { + var torrent = await _torrentData.GetByHash(hash); + + if (torrent == null) + { + return; + } + + await _torrentData.UpdatePriority(torrent.TorrentId, priority); + } + + public async Task UpdateRetry(Guid torrentId, DateTimeOffset? datetime, Int32 retry) + { + await _torrentData.UpdateRetry(torrentId, datetime, retry); + } + + public async Task UpdateError(Guid torrentId, String error) + { + await _torrentData.UpdateError(torrentId, error); + } + + public async Task GetById(Guid torrentId) + { + var torrent = await _torrentData.GetById(torrentId); + + if (torrent == null) + { + return null; + } + + await UpdateTorrentClientData(torrent); + + foreach (var download in torrent.Downloads) + { + if (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient)) + { + download.Speed = downloadClient.Speed; + download.BytesTotal = downloadClient.BytesTotal; + download.BytesDone = downloadClient.BytesDone; + } + + if (TorrentRunner.ActiveUnpackClients.TryGetValue(download.DownloadId, out var unpackClient)) + { + download.BytesTotal = unpackClient.BytesTotal; + download.BytesDone = unpackClient.BytesDone; + } + } + + return torrent; + } + + private static String DownloadPath(Torrent torrent) + { + var settingDownloadPath = Settings.Get.DownloadPath; + + if (!String.IsNullOrWhiteSpace(torrent.Category)) + { + settingDownloadPath = Path.Combine(settingDownloadPath, torrent.Category); + } + + return settingDownloadPath; + } + + private async Task Add(String rdTorrentId, + String infoHash, + String fileOrMagnetContents, + Boolean isFile, + Torrent torrent) + { + await RealDebridUpdateLock.WaitAsync(); + + try + { + var existingTorrent = await _torrentData.GetByHash(infoHash); + + if (existingTorrent != null) + { + return existingTorrent; + } + + var newTorrent = await _torrentData.Add(rdTorrentId, + infoHash, + fileOrMagnetContents, + isFile, + torrent); + + await UpdateTorrentClientData(newTorrent); + + return newTorrent; + } + finally + { + RealDebridUpdateLock.Release(); + } + } + + public async Task Update(Torrent torrent) + { + await _torrentData.Update(torrent); + } + + public async Task RunTorrentComplete(Guid torrentId) + { + if (String.IsNullOrWhiteSpace(Settings.Get.RunOnTorrentCompleteFileName)) + { + return; + } + + var torrent = await _torrentData.GetById(torrentId); + var downloads = await _downloads.GetForTorrent(torrentId); + + var fileName = Settings.Get.RunOnTorrentCompleteFileName; + var arguments = Settings.Get.RunOnTorrentCompleteArguments ?? ""; + + Log($"Parsing external program {fileName} with arguments {arguments}", torrent); + + var downloadPath = DownloadPath(torrent); + var torrentPath = Path.Combine(downloadPath, torrent.RdName); + + arguments = arguments.Replace("%N", $"\"{torrent.RdName}\""); + arguments = arguments.Replace("%L", $"\"{torrent.Category}\""); + arguments = arguments.Replace("%F", $"\"{torrentPath}\""); + arguments = arguments.Replace("%R", $"\"{downloadPath}\""); + arguments = arguments.Replace("%D", $"\"{torrentPath}\""); + arguments = arguments.Replace("%C", downloads.Count.ToString(CultureInfo.InvariantCulture).Replace(",", "").Replace(".", "")); + arguments = arguments.Replace("%Z", torrent.RdSize.ToString(CultureInfo.InvariantCulture).Replace(",", "").Replace(".", "")); + arguments = arguments.Replace("%I", torrent.Hash); + + Log($"Executing external program {fileName} with arguments {arguments}", torrent); + + var errorSb = new StringBuilder(); + var outputSb = new StringBuilder(); + + using var process = new Process(); + + process.StartInfo.FileName = fileName; + process.StartInfo.Arguments = arguments; + process.StartInfo.CreateNoWindow = true; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + + process.OutputDataReceived += (_, data) => + { + if (data.Data == null) + { + return; + } + + outputSb.AppendLine(data.Data.Trim()); + }; + process.ErrorDataReceived += (_, data) => + { + if (data.Data == null) + { + return; + } + + errorSb.AppendLine(data.Data.Trim()); + }; + + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + var exited = process.WaitForExit(60000 * 10); + + var errors = errorSb.ToString(); + var output = outputSb.ToString(); + + if (errors.Length > 0) + { + Log($"External application exited with errors: {errors}", torrent); + } + + if (output.Length > 0) + { + Log($"External application exited with output: {output}", torrent); + } + + if (!exited) + { + Log("External application after a 60 second timeout", torrent); + } + } + + private async Task UpdateTorrentClientData(Torrent torrent, TorrentClientTorrent torrentClientTorrent = null) + { + try + { + var originalTorrent = JsonSerializer.Serialize(torrent, + new JsonSerializerOptions + { + ReferenceHandler = ReferenceHandler.IgnoreCycles + }); + + await _torrentClient.UpdateData(torrent, torrentClientTorrent); + + var newTorrent = JsonSerializer.Serialize(torrent, + new JsonSerializerOptions + { + ReferenceHandler = ReferenceHandler.IgnoreCycles + }); + + if (originalTorrent != newTorrent) + { + await _torrentData.UpdateRdData(torrent); + } + } + catch + { + // ignored + } + } + + private void Log(String message, Data.Models.Data.Download download, Torrent torrent) + { + if (download != null) + { + message = $"{message} {download.ToLog()}"; + } + + if (torrent != null) + { + message = $"{message} {torrent.ToLog()}"; + } + + _logger.LogDebug(message); + } + + private void Log(String message, Torrent torrent = null) + { + if (torrent != null) + { + message = $"{message} {torrent.ToLog()}"; + } + + _logger.LogDebug(message); + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/UnpackClient.cs b/server/RdtClient.Service/Services/UnpackClient.cs index 082deb9..5d16a45 100644 --- a/server/RdtClient.Service/Services/UnpackClient.cs +++ b/server/RdtClient.Service/Services/UnpackClient.cs @@ -1,149 +1,143 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using RdtClient.Data.Models.Data; +using RdtClient.Data.Models.Data; using RdtClient.Service.Helpers; using SharpCompress.Archives; using SharpCompress.Archives.Rar; using SharpCompress.Common; -namespace RdtClient.Service.Services +namespace RdtClient.Service.Services; + +public class UnpackClient { - public class UnpackClient + public Boolean Finished { get; private set; } + + public String Error { get; private set; } + + public Int64 BytesTotal { get; private set; } + public Int64 BytesDone { get; private set; } + + private readonly Download _download; + private readonly String _destinationPath; + private readonly Torrent _torrent; + + private Boolean _cancelled; + + private RarArchiveEntry _rarCurrentEntry; + private Dictionary _rarfileStatus; + + public UnpackClient(Download download, String destinationPath) { - public Boolean Finished { get; private set; } - - public String Error { get; private set; } - - public Int64 BytesTotal { get; private set; } - public Int64 BytesDone { get; private set; } - - private readonly Download _download; - private readonly String _destinationPath; - private readonly Torrent _torrent; + _download = download; + _destinationPath = destinationPath; + _torrent = download.Torrent; + } - private Boolean _cancelled; - - private RarArchiveEntry _rarCurrentEntry; - private Dictionary _rarfileStatus; + public void Start() + { + BytesDone = 0; + BytesTotal = 0; - public UnpackClient(Download download, String destinationPath) + try { - _download = download; - _destinationPath = destinationPath; - _torrent = download.Torrent; - } + var filePath = DownloadHelper.GetDownloadPath(_destinationPath, _torrent, _download); - public void Start() - { - BytesDone = 0; - BytesTotal = 0; - - try + if (filePath == null) { - var filePath = DownloadHelper.GetDownloadPath(_destinationPath, _torrent, _download); + throw new Exception("Invalid download path"); + } - if (filePath == null) + Task.Run(async delegate + { + if (!_cancelled) { - throw new Exception("Invalid download path"); + await Unpack(filePath); } - - Task.Run(async delegate - { - if (!_cancelled) - { - await Unpack(filePath); - } - }); - } - catch (Exception ex) - { - Error = $"An unexpected error occurred preparing download {_download.Link} for torrent {_torrent.RdName}: {ex.Message}"; - Finished = true; - } + }); } - - public void Cancel() + catch (Exception ex) { - _cancelled = true; + Error = $"An unexpected error occurred preparing download {_download.Link} for torrent {_torrent.RdName}: {ex.Message}"; + Finished = true; } + } - private async Task Unpack(String filePath) + public void Cancel() + { + _cancelled = true; + } + + private async Task Unpack(String filePath) + { + try { - try - { - if (!File.Exists(filePath)) - { - return; - } - - await using (Stream stream = File.OpenRead(filePath)) - { - using var archive = RarArchive.Open(stream); - - BytesTotal = archive.TotalSize; - - var entries = archive.Entries.Where(entry => !entry.IsDirectory) - .ToList(); - - _rarfileStatus = entries.ToDictionary(entry => entry.Key, _ => 0L); - _rarCurrentEntry = null; - archive.CompressedBytesRead += ArchiveOnCompressedBytesRead; - - var extractPath = _destinationPath; - - if (!entries.Any(m => m.Key.StartsWith(_torrent.RdName + @"\")) && !entries.Any(m => m.Key.StartsWith(_torrent.RdName + @"/"))) - { - extractPath = Path.Combine(_destinationPath, _torrent.RdName); - } - - if (entries.Any(m => m.Key.Contains(".r00"))) - { - extractPath = Path.Combine(extractPath, "Temp"); - } - - foreach (var entry in entries) - { - if (_cancelled) - { - return; - } - - _rarCurrentEntry = entry; - - entry.WriteToDirectory(extractPath, - new ExtractionOptions - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - - await FileHelper.Delete(filePath); - } - catch (Exception ex) - { - Error = $"An unexpected error occurred unpacking {_download.Link} for torrent {_torrent.RdName}: {ex.Message}"; - } - finally - { - Finished = true; - } - } - - private void ArchiveOnCompressedBytesRead(Object sender, CompressedBytesReadEventArgs e) - { - if (_rarCurrentEntry == null) + if (!File.Exists(filePath)) { return; } - _rarfileStatus[_rarCurrentEntry.Key] = e.CompressedBytesRead; + await using (Stream stream = File.OpenRead(filePath)) + { + using var archive = RarArchive.Open(stream); - BytesDone = _rarfileStatus.Sum(m => m.Value); + BytesTotal = archive.TotalSize; + + var entries = archive.Entries.Where(entry => !entry.IsDirectory) + .ToList(); + + _rarfileStatus = entries.ToDictionary(entry => entry.Key, _ => 0L); + _rarCurrentEntry = null; + archive.CompressedBytesRead += ArchiveOnCompressedBytesRead; + + var extractPath = _destinationPath; + + if (!entries.Any(m => m.Key.StartsWith(_torrent.RdName + @"\")) && !entries.Any(m => m.Key.StartsWith(_torrent.RdName + @"/"))) + { + extractPath = Path.Combine(_destinationPath, _torrent.RdName); + } + + if (entries.Any(m => m.Key.Contains(".r00"))) + { + extractPath = Path.Combine(extractPath, "Temp"); + } + + foreach (var entry in entries) + { + if (_cancelled) + { + return; + } + + _rarCurrentEntry = entry; + + entry.WriteToDirectory(extractPath, + new ExtractionOptions + { + ExtractFullPath = true, + Overwrite = true + }); + } + } + + await FileHelper.Delete(filePath); + } + catch (Exception ex) + { + Error = $"An unexpected error occurred unpacking {_download.Link} for torrent {_torrent.RdName}: {ex.Message}"; + } + finally + { + Finished = true; } } -} + + private void ArchiveOnCompressedBytesRead(Object sender, CompressedBytesReadEventArgs e) + { + if (_rarCurrentEntry == null) + { + return; + } + + _rarfileStatus[_rarCurrentEntry.Key] = e.CompressedBytesRead; + + BytesDone = _rarfileStatus.Sum(m => m.Value); + } +} \ No newline at end of file diff --git a/server/RdtClient.Service/Services/UpdateChecker.cs b/server/RdtClient.Service/Services/UpdateChecker.cs index 78d9519..c28f26e 100644 --- a/server/RdtClient.Service/Services/UpdateChecker.cs +++ b/server/RdtClient.Service/Services/UpdateChecker.cs @@ -1,83 +1,76 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; +using System.Net.Http.Headers; using System.Reflection; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Newtonsoft.Json; -namespace RdtClient.Service.Services +namespace RdtClient.Service.Services; + +public class UpdateChecker : BackgroundService { - public class UpdateChecker : BackgroundService + public static String CurrentVersion { get; private set; } + public static String LatestVersion { get; private set; } + + private readonly ILogger _logger; + + public UpdateChecker(ILogger logger) { - public static String CurrentVersion { get; private set; } - public static String LatestVersion { get; private set; } - - private readonly ILogger _logger; - - public UpdateChecker(ILogger logger) - { - _logger = logger; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - var version = Assembly.GetEntryAssembly()?.GetName().Version?.ToString(); - - if (String.IsNullOrWhiteSpace(version)) - { - CurrentVersion = ""; - - return; - } - - CurrentVersion = $"v{version[..version.LastIndexOf(".", StringComparison.Ordinal)]}"; - - _logger.LogInformation("UpdateChecker started, currently on version {CurrentVersion}.", CurrentVersion); - - while (!stoppingToken.IsCancellationRequested) - { - try - { - var httpClient = new HttpClient(); - httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("RdtClient", CurrentVersion)); - var response = await httpClient.GetStringAsync($"https://api.github.com/repos/rogerfar/rdt-client/tags?per_page=1", stoppingToken); - - var gitHubReleases = JsonConvert.DeserializeObject>(response); - - if (gitHubReleases == null || gitHubReleases.Count == 0) - { - return; - } - - var latestRelease = gitHubReleases.First().Name; - - if (latestRelease != CurrentVersion) - { - _logger.LogInformation("New version found on GitHub: {latestRelease}", latestRelease); - } - - LatestVersion = latestRelease; - } - catch (Exception ex) - { - _logger.LogError(ex, "Unexpected error occurred in TorrentDownloadManager.Tick"); - } - - await Task.Delay(TimeSpan.FromHours(1), stoppingToken); - } - - _logger.LogInformation("UpdateChecker stopped."); - } + _logger = logger; } - public class GitHubReleasesResponse + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - [JsonProperty("name")] - public String Name { get; set; } + var version = Assembly.GetEntryAssembly()?.GetName().Version?.ToString(); + + if (String.IsNullOrWhiteSpace(version)) + { + CurrentVersion = ""; + + return; + } + + CurrentVersion = $"v{version[..version.LastIndexOf(".", StringComparison.Ordinal)]}"; + + _logger.LogInformation("UpdateChecker started, currently on version {CurrentVersion}.", CurrentVersion); + + while (!stoppingToken.IsCancellationRequested) + { + try + { + var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("RdtClient", CurrentVersion)); + var response = await httpClient.GetStringAsync($"https://api.github.com/repos/rogerfar/rdt-client/tags?per_page=1", stoppingToken); + + var gitHubReleases = JsonConvert.DeserializeObject>(response); + + if (gitHubReleases == null || gitHubReleases.Count == 0) + { + return; + } + + var latestRelease = gitHubReleases.First().Name; + + if (latestRelease != CurrentVersion) + { + _logger.LogInformation("New version found on GitHub: {latestRelease}", latestRelease); + } + + LatestVersion = latestRelease; + } + catch (Exception ex) + { + _logger.LogError(ex, "Unexpected error occurred in TorrentDownloadManager.Tick"); + } + + await Task.Delay(TimeSpan.FromHours(1), stoppingToken); + } + + _logger.LogInformation("UpdateChecker stopped."); } } + +public class GitHubReleasesResponse +{ + [JsonProperty("name")] + public String Name { get; set; } +} \ No newline at end of file diff --git a/server/RdtClient.Web/Controllers/AuthController.cs b/server/RdtClient.Web/Controllers/AuthController.cs index 1d358f4..7b75ac9 100644 --- a/server/RdtClient.Web/Controllers/AuthController.cs +++ b/server/RdtClient.Web/Controllers/AuthController.cs @@ -1,120 +1,116 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using RdtClient.Service.Services; -namespace RdtClient.Web.Controllers +namespace RdtClient.Web.Controllers; + +[Route("Api/Authentication")] +public class AuthController : Controller { - [Route("Api/Authentication")] - public class AuthController : Controller + private readonly Authentication _authentication; + + public AuthController(Authentication authentication) { - private readonly Authentication _authentication; - - public AuthController(Authentication authentication) - { - _authentication = authentication; - } + _authentication = authentication; + } - [AllowAnonymous] - [Route("IsLoggedIn")] - [HttpGet] - public async Task IsLoggedIn() - { - if (User.Identity?.IsAuthenticated == false) - { - var user = await _authentication.GetUser(); - - if (user == null) - { - return StatusCode(402, "Setup required"); - } - - return Unauthorized(); - } - - return Ok(); - } - - [AllowAnonymous] - [Route("Create")] - [HttpPost] - public async Task Create([FromBody] AuthControllerLoginRequest request) - { - var user = await _authentication.GetUser(); - - if (user != null) - { - return StatusCode(401); - } - - var registerResult = await _authentication.Register(request.UserName, request.Password); - - if (!registerResult.Succeeded) - { - return BadRequest(registerResult.Errors.First().Description); - } - - await _authentication.Login(request.UserName, request.Password); - - return Ok(); - } - - [AllowAnonymous] - [Route("Login")] - [HttpPost] - public async Task Login([FromBody] AuthControllerLoginRequest request) + [AllowAnonymous] + [Route("IsLoggedIn")] + [HttpGet] + public async Task IsLoggedIn() + { + if (User.Identity?.IsAuthenticated == false) { var user = await _authentication.GetUser(); if (user == null) { - return StatusCode(402); + return StatusCode(402, "Setup required"); } - - var result = await _authentication.Login(request.UserName, request.Password); - - if (!result.Succeeded) - { - return BadRequest("Invalid credentials"); - } - - return Ok(); - } - - [Route("Logout")] - [HttpPost] - public async Task Logout() - { - await _authentication.Logout(); - return Ok(); - } - [Route("Update")] - [HttpPost] - public async Task Update([FromBody] AuthControllerUpdateRequest request) - { - var updateResult = await _authentication.Update(request.UserName, request.Password); - - if (!updateResult.Succeeded) - { - return BadRequest(updateResult.Errors.First().Description); - } - - return Ok(); + return Unauthorized(); } + + return Ok(); + } + + [AllowAnonymous] + [Route("Create")] + [HttpPost] + public async Task Create([FromBody] AuthControllerLoginRequest request) + { + var user = await _authentication.GetUser(); + + if (user != null) + { + return StatusCode(401); + } + + var registerResult = await _authentication.Register(request.UserName, request.Password); + + if (!registerResult.Succeeded) + { + return BadRequest(registerResult.Errors.First().Description); + } + + await _authentication.Login(request.UserName, request.Password); + + return Ok(); } - public class AuthControllerLoginRequest + [AllowAnonymous] + [Route("Login")] + [HttpPost] + public async Task Login([FromBody] AuthControllerLoginRequest request) { - public String UserName { get; set; } - public String Password { get; set; } - } + var user = await _authentication.GetUser(); - public class AuthControllerUpdateRequest + if (user == null) + { + return StatusCode(402); + } + + var result = await _authentication.Login(request.UserName, request.Password); + + if (!result.Succeeded) + { + return BadRequest("Invalid credentials"); + } + + return Ok(); + } + + [Route("Logout")] + [HttpPost] + public async Task Logout() { - public String UserName { get; set; } - public String Password { get; set; } + await _authentication.Logout(); + return Ok(); + } + + [Route("Update")] + [HttpPost] + public async Task Update([FromBody] AuthControllerUpdateRequest request) + { + var updateResult = await _authentication.Update(request.UserName, request.Password); + + if (!updateResult.Succeeded) + { + return BadRequest(updateResult.Errors.First().Description); + } + + return Ok(); } } + +public class AuthControllerLoginRequest +{ + public String UserName { get; set; } + public String Password { get; set; } +} + +public class AuthControllerUpdateRequest +{ + public String UserName { get; set; } + public String Password { get; set; } +} \ No newline at end of file diff --git a/server/RdtClient.Web/Controllers/QBittorrentController.cs b/server/RdtClient.Web/Controllers/QBittorrentController.cs index 6747120..7432eca 100644 --- a/server/RdtClient.Web/Controllers/QBittorrentController.cs +++ b/server/RdtClient.Web/Controllers/QBittorrentController.cs @@ -1,500 +1,501 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.IO; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using RdtClient.Data.Models.QBittorrent; -using RdtClient.Data.Models.QBittorrent.QuickType; using RdtClient.Service.Services; -namespace RdtClient.Web.Controllers +namespace RdtClient.Web.Controllers; + +/// +/// This API behaves as a regular QBittorrent 4+ API +/// Documentation is found here: https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1) +/// +[ApiController] +[Route("api/v2")] +public class QBittorrentController : Controller { - /// - /// This API behaves as a regular QBittorrent 4+ API - /// Documentation is found here: https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1) - /// - [ApiController] - [Route("api/v2")] - public class QBittorrentController : Controller + private readonly QBittorrent _qBittorrent; + + public QBittorrentController(QBittorrent qBittorrent) { - private readonly QBittorrent _qBittorrent; + _qBittorrent = qBittorrent; + } - public QBittorrentController(QBittorrent qBittorrent) + [AllowAnonymous] + [Route("auth/login")] + [HttpGet] + public async Task AuthLogin([FromQuery] QBAuthLoginRequest request) + { + var result = await _qBittorrent.AuthLogin(request.UserName, request.Password); + + if (result) { - _qBittorrent = qBittorrent; + return Ok("Ok."); } - [AllowAnonymous] - [Route("auth/login")] - [HttpGet] - public async Task AuthLogin([FromQuery] QBAuthLoginRequest request) - { - var result = await _qBittorrent.AuthLogin(request.UserName, request.Password); - - if (result) - { - return Ok("Ok."); - } - - return Ok("Fails."); - } + return Ok("Fails."); + } - [AllowAnonymous] - [Route("auth/login")] - [HttpPost] - public async Task AuthLoginPost([FromForm] QBAuthLoginRequest request) - { - return await AuthLogin(request); - } + [AllowAnonymous] + [Route("auth/login")] + [HttpPost] + public async Task AuthLoginPost([FromForm] QBAuthLoginRequest request) + { + return await AuthLogin(request); + } - [Authorize] - [Route("auth/logout")] - [HttpGet] - [HttpPost] - public async Task AuthLogout() - { - await _qBittorrent.AuthLogout(); - return Ok(); - } + [Authorize] + [Route("auth/logout")] + [HttpGet] + [HttpPost] + public async Task AuthLogout() + { + await _qBittorrent.AuthLogout(); + return Ok(); + } - [Route("app/version")] - [HttpGet] - [HttpPost] - public ActionResult AppVersion() - { - return Ok("v4.3.2"); - } + [Route("app/version")] + [HttpGet] + [HttpPost] + public ActionResult AppVersion() + { + return Ok("v4.3.2"); + } - [Route("app/webapiVersion")] - [HttpGet] - [HttpPost] - public ActionResult AppWebVersion() - { - return Ok("2.7"); - } + [Route("app/webapiVersion")] + [HttpGet] + [HttpPost] + public ActionResult AppWebVersion() + { + return Ok("2.7"); + } - [Route("app/buildInfo")] - [HttpGet] - [HttpPost] - public ActionResult AppBuildInfo() + [Route("app/buildInfo")] + [HttpGet] + [HttpPost] + public ActionResult AppBuildInfo() + { + var result = new AppBuildInfo { - var result = new AppBuildInfo - { - Bitness = 64, - Boost = "1.75.0", - Libtorrent = "1.2.11.0", - Openssl = "1.1.1i", - Qt = "5.15.2", - Zlib = "1.2.11" - }; - return Ok(result); - } + Bitness = 64, + Boost = "1.75.0", + Libtorrent = "1.2.11.0", + Openssl = "1.1.1i", + Qt = "5.15.2", + Zlib = "1.2.11" + }; + return Ok(result); + } - [Authorize] - [Route("app/shutdown")] - [HttpGet] - [HttpPost] - public ActionResult AppShutdown() - { - return Ok(); - } + [Authorize] + [Route("app/shutdown")] + [HttpGet] + [HttpPost] + public ActionResult AppShutdown() + { + return Ok(); + } - [AllowAnonymous] - [Route("app/preferences")] - [HttpGet] - [HttpPost] - public async Task> AppPreferences() - { - var result = await _qBittorrent.AppPreferences(); - return Ok(result); - } + [AllowAnonymous] + [Route("app/preferences")] + [HttpGet] + [HttpPost] + public async Task> AppPreferences() + { + var result = await _qBittorrent.AppPreferences(); + return Ok(result); + } - [Authorize] - [Route("app/setPreferences")] - [HttpGet] - [HttpPost] - public ActionResult AppSetPreferences() - { - return Ok(); - } + [Authorize] + [Route("app/setPreferences")] + [HttpGet] + [HttpPost] + public ActionResult AppSetPreferences() + { + return Ok(); + } - [Authorize] - [Route("app/defaultSavePath")] - [HttpGet] - [HttpPost] - public ActionResult AppDefaultSavePath() - { - var result = _qBittorrent.AppDefaultSavePath(); - return Ok(result); - } + [Authorize] + [Route("app/defaultSavePath")] + [HttpGet] + [HttpPost] + public ActionResult AppDefaultSavePath() + { + var result = _qBittorrent.AppDefaultSavePath(); + return Ok(result); + } - [Authorize] - [Route("torrents/info")] - [HttpGet] - [HttpPost] - public async Task>> TorrentsInfo([FromQuery] QBTorrentsHashRequest request) + [Authorize] + [Route("torrents/info")] + [HttpGet] + [HttpPost] + public async Task>> TorrentsInfo([FromQuery] QBTorrentsInfoRequest request) + { + var results = await _qBittorrent.TorrentInfo(); + + if (!String.IsNullOrWhiteSpace(request.Category)) { - var results = await _qBittorrent.TorrentInfo(); - IList filteredResults = new List(); - - if (request.Category == null) { - filteredResults = results; - } else if (request.Category == "") { - filteredResults = results.Where(m => m.Category == null).ToList(); - } else { - filteredResults = results.Where(m => m.Category == request.Category).ToList(); - } - - return Ok(filteredResults); + results = results.Where(m => m.Category == request.Category).ToList(); } - [Authorize] - [Route("torrents/files")] - [HttpGet] - public async Task>> TorrentsFiles([FromQuery] QBTorrentsHashRequest request) - { - var result = await _qBittorrent.TorrentFileContents(request.Hash); + return Ok(results); + } - if (result == null) + [Authorize] + [Route("torrents/info")] + [HttpPost] + public async Task>> TorrentsFilesPost([FromForm] QBTorrentsInfoRequest request) + { + return await TorrentsInfo(request); + } + + [Authorize] + [Route("torrents/files")] + [HttpGet] + public async Task>> TorrentsFiles([FromQuery] QBTorrentsHashRequest request) + { + var result = await _qBittorrent.TorrentFileContents(request.Hash); + + if (result == null) + { + return NotFound(); + } + + return Ok(result); + } + + [Authorize] + [Route("torrents/files")] + [HttpPost] + public async Task>> TorrentsFilesPost([FromForm] QBTorrentsHashRequest request) + { + return await TorrentsFiles(request); + } + + [Authorize] + [Route("torrents/properties")] + [HttpGet] + public async Task>> TorrentsProperties([FromQuery] QBTorrentsHashRequest request) + { + var result = await _qBittorrent.TorrentProperties(request.Hash); + + if (result == null) + { + return NotFound(); + } + + return Ok(result); + } + + [Authorize] + [Route("torrents/properties")] + [HttpPost] + public async Task>> TorrentsPropertiesPost([FromForm] QBTorrentsHashRequest request) + { + return await TorrentsProperties(request); + } + + [Authorize] + [Route("torrents/pause")] + [HttpGet] + public async Task TorrentsPause([FromQuery] QBTorrentsHashesRequest request) + { + var hashes = request.Hashes.Split("|"); + + foreach (var hash in hashes) + { + await _qBittorrent.TorrentPause(hash); + } + + return Ok(); + } + + [Authorize] + [Route("torrents/topPrio")] + [HttpPost] + public async Task TorrentsPausePost([FromForm] QBTorrentsHashesRequest request) + { + return await TorrentsPause(request); + } + + [Authorize] + [Route("torrents/resume")] + [HttpGet] + public async Task TorrentsResume([FromQuery] QBTorrentsHashesRequest request) + { + var hashes = request.Hashes.Split("|"); + + foreach (var hash in hashes) + { + await _qBittorrent.TorrentResume(hash); + } + + return Ok(); + } + + [Authorize] + [Route("torrents/topPrio")] + [HttpPost] + public async Task TorrentsResumePost([FromForm] QBTorrentsHashesRequest request) + { + return await TorrentsResume(request); + } + + [Authorize] + [Route("torrents/setShareLimits")] + [HttpGet] + [HttpPost] + public ActionResult TorrentsSetShareLimits() + { + return Ok(); + } + + [Authorize] + [Route("torrents/delete")] + [HttpGet] + public async Task TorrentsDelete([FromQuery] QBTorrentsDeleteRequest request) + { + var hashes = request.Hashes.Split("|"); + + foreach (var hash in hashes) + { + await _qBittorrent.TorrentsDelete(hash, request.DeleteFiles); + } + + return Ok(); + } + + [Authorize] + [Route("torrents/delete")] + [HttpPost] + public async Task TorrentsDeletePost([FromForm] QBTorrentsDeleteRequest request) + { + return await TorrentsDelete(request); + } + + [Authorize] + [Route("torrents/add")] + [HttpGet] + public async Task TorrentsAdd([FromQuery] QBTorrentsAddRequest request) + { + var urls = request.Urls.Split("\n"); + + foreach (var url in urls) + { + if (url.StartsWith("magnet")) { - return NotFound(); + await _qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category, null); } - - return Ok(result); - } - - [Authorize] - [Route("torrents/files")] - [HttpPost] - public async Task>> TorrentsFilesPost([FromForm] QBTorrentsHashRequest request) - { - return await TorrentsFiles(request); - } - - [Authorize] - [Route("torrents/properties")] - [HttpGet] - public async Task>> TorrentsProperties([FromQuery] QBTorrentsHashRequest request) - { - var result = await _qBittorrent.TorrentProperties(request.Hash); - - if (result == null) + else if (url.StartsWith("http")) { - return NotFound(); + var httpClient = new HttpClient(); + var result = await httpClient.GetByteArrayAsync(url); + await _qBittorrent.TorrentsAddFile(result, request.Category, null); } - - return Ok(result); - } - - [Authorize] - [Route("torrents/properties")] - [HttpPost] - public async Task>> TorrentsPropertiesPost([FromForm] QBTorrentsHashRequest request) - { - return await TorrentsProperties(request); - } - - [Authorize] - [Route("torrents/pause")] - [HttpGet] - public async Task TorrentsPause([FromQuery] QBTorrentsHashesRequest request) - { - var hashes = request.Hashes.Split("|"); - - foreach (var hash in hashes) + else { - await _qBittorrent.TorrentPause(hash); + throw new Exception($"Invalid torrent link format {url}"); } - - return Ok(); } - [Authorize] - [Route("torrents/topPrio")] - [HttpPost] - public async Task TorrentsPausePost([FromForm] QBTorrentsHashesRequest request) - { - return await TorrentsPause(request); - } + return Ok(); + } - [Authorize] - [Route("torrents/resume")] - [HttpGet] - public async Task TorrentsResume([FromQuery] QBTorrentsHashesRequest request) + [Authorize] + [Route("torrents/add")] + [HttpPost] + public async Task TorrentsAddPost([FromForm] QBTorrentsAddRequest request) + { + foreach (var file in Request.Form.Files) { - var hashes = request.Hashes.Split("|"); - - foreach (var hash in hashes) + if (file.Length > 0) { - await _qBittorrent.TorrentResume(hash); + await using var target = new MemoryStream(); + + await file.CopyToAsync(target); + var fileBytes = target.ToArray(); + + await _qBittorrent.TorrentsAddFile(fileBytes, request.Category, request.Priority); } - - return Ok(); } - - [Authorize] - [Route("torrents/topPrio")] - [HttpPost] - public async Task TorrentsResumePost([FromForm] QBTorrentsHashesRequest request) - { - return await TorrentsResume(request); - } - - [Authorize] - [Route("torrents/setShareLimits")] - [HttpGet] - [HttpPost] - public ActionResult TorrentsSetShareLimits() - { - return Ok(); - } - - [Authorize] - [Route("torrents/delete")] - [HttpGet] - public async Task TorrentsDelete([FromQuery] QBTorrentsDeleteRequest request) - { - var hashes = request.Hashes.Split("|"); - - foreach (var hash in hashes) - { - await _qBittorrent.TorrentsDelete(hash, request.DeleteFiles); - } - - return Ok(); - } - - [Authorize] - [Route("torrents/delete")] - [HttpPost] - public async Task TorrentsDeletePost([FromForm] QBTorrentsDeleteRequest request) - { - return await TorrentsDelete(request); - } - - [Authorize] - [Route("torrents/add")] - [HttpGet] - public async Task TorrentsAdd([FromQuery] QBTorrentsAddRequest request) - { - var urls = request.Urls.Split("\n"); - - foreach (var url in urls) - { - if (url.StartsWith("magnet")) - { - await _qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category, null); - } - else if (url.StartsWith("http")) - { - var httpClient = new HttpClient(); - var result = await httpClient.GetByteArrayAsync(url); - await _qBittorrent.TorrentsAddFile(result, request.Category, null); - } - else - { - throw new Exception($"Invalid torrent link format {url}"); - } - } - - return Ok(); - } - - [Authorize] - [Route("torrents/add")] - [HttpPost] - public async Task TorrentsAddPost([FromForm] QBTorrentsAddRequest request) - { - foreach (var file in Request.Form.Files) - { - if (file.Length > 0) - { - await using var target = new MemoryStream(); - - await file.CopyToAsync(target); - var fileBytes = target.ToArray(); - - await _qBittorrent.TorrentsAddFile(fileBytes, request.Category, request.Priority); - } - } - if (request.Urls != null) - { - return await TorrentsAdd(request); - } - - return Ok(); + if (request.Urls != null) + { + return await TorrentsAdd(request); } + + return Ok(); + } - [Authorize] - [Route("torrents/setCategory")] - [HttpGet] - public async Task TorrentsSetCategory([FromQuery] QBTorrentsSetCategoryRequest request) + [Authorize] + [Route("torrents/setCategory")] + [HttpGet] + public async Task TorrentsSetCategory([FromQuery] QBTorrentsSetCategoryRequest request) + { + var hashes = request.Hashes.Split("|"); + + foreach (var hash in hashes) { - var hashes = request.Hashes.Split("|"); - - foreach (var hash in hashes) - { - await _qBittorrent.TorrentsSetCategory(hash, request.Category); - } - - return Ok(); + await _qBittorrent.TorrentsSetCategory(hash, request.Category); } - [Authorize] - [Route("torrents/setCategory")] - [HttpPost] - public async Task TorrentsSetCategoryPost([FromForm] QBTorrentsSetCategoryRequest request) + return Ok(); + } + + [Authorize] + [Route("torrents/setCategory")] + [HttpPost] + public async Task TorrentsSetCategoryPost([FromForm] QBTorrentsSetCategoryRequest request) + { + return await TorrentsSetCategory(request); + } + + [Authorize] + [Route("torrents/categories")] + [HttpGet] + [HttpPost] + public async Task>> TorrentsCategories() + { + var categories = await _qBittorrent.TorrentsCategories(); + + return Ok(categories); + } + + [Authorize] + [Route("torrents/createCategory")] + [HttpGet] + [HttpPost] + public async Task TorrentsCreateCategory([FromForm] QBTorrentsCreateCategoryRequest request) + { + if (String.IsNullOrWhiteSpace(request.Category)) { - return await TorrentsSetCategory(request); + return BadRequest("category name is empty"); } - [Authorize] - [Route("torrents/categories")] - [HttpGet] - [HttpPost] - public async Task>> TorrentsCategories() - { - var categories = await _qBittorrent.TorrentsCategories(); + await _qBittorrent.CategoryCreate(request.Category.Trim()); - return Ok(categories); - } - - [Authorize] - [Route("torrents/createCategory")] - [HttpGet] - [HttpPost] - public async Task TorrentsCreateCategory([FromForm] QBTorrentsCreateCategoryRequest request) - { - if (String.IsNullOrWhiteSpace(request.Category)) - { - return BadRequest("category name is empty"); - } - - await _qBittorrent.CategoryCreate(request.Category.Trim()); - - return Ok(); - } + return Ok(); + } - [Authorize] - [Route("torrents/removeCategories")] - [HttpGet] - [HttpPost] - public async Task TorrentsRemoveCategories([FromForm] QBTorrentsRemoveCategoryRequest request) - { - if (String.IsNullOrWhiteSpace(request.Categories)) - { - return Ok(); - } - - var categories = request.Categories.Split('\n', StringSplitOptions.RemoveEmptyEntries); - - foreach (var category in categories) - { - await _qBittorrent.CategoryRemove(category.Trim()); - } - - return Ok(); - } - - [Authorize] - [Route("torrents/setForcestart")] - [HttpGet] - [HttpPost] - public ActionResult TorrentsSetForceStart() - { - return Ok(); - } - - [Authorize] - [Route("torrents/topPrio")] - [HttpGet] - public async Task TorrentsTopPrio([FromQuery] QBTorrentsHashesRequest request) - { - var hashes = request.Hashes.Split("|"); - - foreach (var hash in hashes) - { - await _qBittorrent.TorrentsTopPrio(hash); - } - - return Ok(); - } - - [Authorize] - [Route("torrents/topPrio")] - [HttpPost] - public async Task TorrentsTopPrioPost([FromForm] QBTorrentsHashesRequest request) - { - return await TorrentsTopPrio(request); - } - - [Authorize] - [Route("sync/maindata")] - [HttpGet] - public async Task SyncMainData() - { - var result = await _qBittorrent.SyncMainData(); - - return Ok(result); - } - - [Authorize] - [Route("sync/maindata")] - [HttpPost] - public async Task SyncMainDataPost() - { - return await SyncMainData(); - } - } - - public class QBAuthLoginRequest + [Authorize] + [Route("torrents/removeCategories")] + [HttpGet] + [HttpPost] + public async Task TorrentsRemoveCategories([FromForm] QBTorrentsRemoveCategoryRequest request) { - public String UserName { get; set; } - public String Password { get; set; } + if (String.IsNullOrWhiteSpace(request.Categories)) + { + return Ok(); + } + + var categories = request.Categories.Split('\n', StringSplitOptions.RemoveEmptyEntries); + + foreach (var category in categories) + { + await _qBittorrent.CategoryRemove(category.Trim()); + } + + return Ok(); } + + [Authorize] + [Route("torrents/setForcestart")] + [HttpGet] + [HttpPost] + public ActionResult TorrentsSetForceStart() + { + return Ok(); + } + + [Authorize] + [Route("torrents/topPrio")] + [HttpGet] + public async Task TorrentsTopPrio([FromQuery] QBTorrentsHashesRequest request) + { + var hashes = request.Hashes.Split("|"); + + foreach (var hash in hashes) + { + await _qBittorrent.TorrentsTopPrio(hash); + } + + return Ok(); + } + + [Authorize] + [Route("torrents/topPrio")] + [HttpPost] + public async Task TorrentsTopPrioPost([FromForm] QBTorrentsHashesRequest request) + { + return await TorrentsTopPrio(request); + } + + [Authorize] + [Route("sync/maindata")] + [HttpGet] + public async Task SyncMainData() + { + var result = await _qBittorrent.SyncMainData(); + + return Ok(result); + } + + [Authorize] + [Route("sync/maindata")] + [HttpPost] + public async Task SyncMainDataPost() + { + return await SyncMainData(); + } +} + +public class QBAuthLoginRequest +{ + public String UserName { get; set; } + public String Password { get; set; } +} + +public class QBTorrentsInfoRequest +{ + public String Category { get; set; } +} - public class QBTorrentsHashRequest - { - public String Hash { get; set; } - public String Category { get; set; } - } +public class QBTorrentsHashRequest +{ + public String Hash { get; set; } + public String Category { get; set; } +} - public class QBTorrentsDeleteRequest - { - public String Hashes { get; set; } - public Boolean DeleteFiles { get; set; } - } +public class QBTorrentsDeleteRequest +{ + public String Hashes { get; set; } + public Boolean DeleteFiles { get; set; } +} - public class QBTorrentsAddRequest - { - public String Urls { get; set; } - public String Category { get; set; } - public Int32? Priority { get; set; } - } +public class QBTorrentsAddRequest +{ + public String Urls { get; set; } + public String Category { get; set; } + public Int32? Priority { get; set; } +} - public class QBTorrentsSetCategoryRequest - { - public String Hashes { get; set; } - public String Category { get; set; } - } +public class QBTorrentsSetCategoryRequest +{ + public String Hashes { get; set; } + public String Category { get; set; } +} - public class QBTorrentsCreateCategoryRequest - { - public String Category { get; set; } - } +public class QBTorrentsCreateCategoryRequest +{ + public String Category { get; set; } +} - public class QBTorrentsRemoveCategoryRequest - { - public String Categories { get; set; } - } +public class QBTorrentsRemoveCategoryRequest +{ + public String Categories { get; set; } +} - public class QBTorrentsHashesRequest - { - public String Hashes { get; set; } - } +public class QBTorrentsHashesRequest +{ + public String Hashes { get; set; } } \ No newline at end of file diff --git a/server/RdtClient.Web/Controllers/SettingsController.cs b/server/RdtClient.Web/Controllers/SettingsController.cs index 2089bf3..959dccd 100644 --- a/server/RdtClient.Web/Controllers/SettingsController.cs +++ b/server/RdtClient.Web/Controllers/SettingsController.cs @@ -1,111 +1,106 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using RdtClient.Data.Models.Data; using RdtClient.Data.Models.Internal; using RdtClient.Service.Services; using Serilog.Events; -namespace RdtClient.Web.Controllers +namespace RdtClient.Web.Controllers; + +[Authorize] +[Route("Api/Settings")] +public class SettingsController : Controller { - [Authorize] - [Route("Api/Settings")] - public class SettingsController : Controller + private readonly Settings _settings; + private readonly Torrents _torrents; + + public SettingsController(Settings settings, Torrents torrents) { - private readonly Settings _settings; - private readonly Torrents _torrents; + _settings = settings; + _torrents = torrents; + } - public SettingsController(Settings settings, Torrents torrents) - { - _settings = settings; - _torrents = torrents; - } + [HttpGet] + [Route("")] + public async Task>> Get() + { + var result = await _settings.GetAll(); + return Ok(result); + } - [HttpGet] - [Route("")] - public async Task>> Get() - { - var result = await _settings.GetAll(); - return Ok(result); - } - - [HttpPut] - [Route("")] - public async Task Update([FromBody] SettingsControllerUpdateRequest request) - { - await _settings.Update(request.Settings); + [HttpPut] + [Route("")] + public async Task Update([FromBody] SettingsControllerUpdateRequest request) + { + await _settings.Update(request.Settings); - if (!Enum.TryParse(Settings.Get.LogLevel, out var logLevel)) - { - logLevel = LogEventLevel.Information; - } - - Program.LoggingLevelSwitch.MinimumLevel = logLevel; - - return Ok(); - } - - [HttpGet] - [Route("Profile")] - public async Task> Profile() + if (!Enum.TryParse(Settings.Get.LogLevel, out var logLevel)) { - var profile = await _torrents.GetProfile(); - return Ok(profile); + logLevel = LogEventLevel.Information; } + + Settings.LoggingLevelSwitch.MinimumLevel = logLevel; + + return Ok(); + } + + [HttpGet] + [Route("Profile")] + public async Task> Profile() + { + var profile = await _torrents.GetProfile(); + return Ok(profile); + } - [HttpPost] - [Route("TestPath")] - public async Task TestPath([FromBody] SettingsControllerTestPathRequest request) - { - await _settings.TestPath(request.Path); + [HttpPost] + [Route("TestPath")] + public async Task TestPath([FromBody] SettingsControllerTestPathRequest request) + { + await _settings.TestPath(request.Path); - return Ok(); - } + return Ok(); + } - [HttpGet] - [Route("TestDownloadSpeed")] - public async Task TestDownloadSpeed(CancellationToken cancellationToken) - { - var downloadSpeed = await _settings.TestDownloadSpeed(cancellationToken); + [HttpGet] + [Route("TestDownloadSpeed")] + public async Task TestDownloadSpeed(CancellationToken cancellationToken) + { + var downloadSpeed = await _settings.TestDownloadSpeed(cancellationToken); - return Ok(downloadSpeed); - } + return Ok(downloadSpeed); + } - [HttpGet] - [Route("TestWriteSpeed")] - public async Task TestWriteSpeed() - { - var writeSpeed = await _settings.TestWriteSpeed(); + [HttpGet] + [Route("TestWriteSpeed")] + public async Task TestWriteSpeed() + { + var writeSpeed = await _settings.TestWriteSpeed(); - return Ok(writeSpeed); - } - - [HttpPost] - [Route("TestAria2cConnection")] - public async Task> TestAria2cConnection([FromBody] SettingsControllerTestAria2cConnectionRequest request) - { - var version = await _settings.GetAria2cVersion(request.Url, request.Secret); - - return Ok(version); - } + return Ok(writeSpeed); } - public class SettingsControllerUpdateRequest + [HttpPost] + [Route("TestAria2cConnection")] + public async Task> TestAria2cConnection([FromBody] SettingsControllerTestAria2cConnectionRequest request) { - public IList Settings { get; set; } - } + var version = await _settings.GetAria2cVersion(request.Url, request.Secret); - public class SettingsControllerTestPathRequest - { - public String Path { get; set; } + return Ok(version); } +} - public class SettingsControllerTestAria2cConnectionRequest - { - public String Url { get; set; } - public String Secret { get; set; } - } +public class SettingsControllerUpdateRequest +{ + public IList Settings { get; set; } +} + +public class SettingsControllerTestPathRequest +{ + public String Path { get; set; } +} + +public class SettingsControllerTestAria2cConnectionRequest +{ + public String Url { get; set; } + public String Secret { get; set; } } \ No newline at end of file diff --git a/server/RdtClient.Web/Controllers/TorrentsController.cs b/server/RdtClient.Web/Controllers/TorrentsController.cs index ebfffe4..1c97183 100644 --- a/server/RdtClient.Web/Controllers/TorrentsController.cs +++ b/server/RdtClient.Web/Controllers/TorrentsController.cs @@ -1,220 +1,213 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MonoTorrent; using RdtClient.Service.Helpers; using RdtClient.Service.Services; using Torrent = RdtClient.Data.Models.Data.Torrent; -namespace RdtClient.Web.Controllers -{ - [Authorize] - [Route("Api/Torrents")] - public class TorrentsController : Controller - { - private readonly TorrentRunner _torrentRunner; - private readonly Torrents _torrents; +namespace RdtClient.Web.Controllers; - public TorrentsController(Torrents torrents, TorrentRunner torrentRunner) +[Authorize] +[Route("Api/Torrents")] +public class TorrentsController : Controller +{ + private readonly TorrentRunner _torrentRunner; + private readonly Torrents _torrents; + + public TorrentsController(Torrents torrents, TorrentRunner torrentRunner) + { + _torrents = torrents; + _torrentRunner = torrentRunner; + } + + [HttpGet] + [Route("")] + public async Task>> GetAll() + { + var results = await _torrents.Get(); + + // Prevent infinite recursion when serializing + foreach (var file in results.SelectMany(torrent => torrent.Downloads)) { - _torrents = torrents; - _torrentRunner = torrentRunner; + file.Torrent = null; } - [HttpGet] - [Route("")] - public async Task>> GetAll() - { - var results = await _torrents.Get(); + return Ok(results); + } - // Prevent infinite recursion when serializing - foreach (var file in results.SelectMany(torrent => torrent.Downloads)) + [HttpGet] + [Route("Get/{torrentId:guid}")] + public async Task> GetById(Guid torrentId) + { + var torrent = await _torrents.GetById(torrentId); + + if (torrent?.Downloads != null) + { + foreach (var file in torrent.Downloads) { file.Torrent = null; } - - return Ok(results); } - [HttpGet] - [Route("Get/{torrentId:guid}")] - public async Task> GetById(Guid torrentId) + return Ok(torrent); + } + + /// + /// Used for debugging only. Force a tick. + /// + /// + [HttpGet] + [Route("Tick")] + public async Task Tick() + { + await _torrentRunner.Tick(); + + return Ok(); + } + + [HttpPost] + [Route("UploadFile")] + public async Task UploadFile([FromForm] IFormFile file, + [ModelBinder(BinderType = typeof(JsonModelBinder))] + TorrentControllerUploadFileRequest formData) + { + if (!ModelState.IsValid) { - var torrent = await _torrents.GetById(torrentId); + var errors = ModelState.Select(x => x.Value.Errors) + .Where(y => y.Count > 0) + .ToList(); - if (torrent?.Downloads != null) - { - foreach (var file in torrent.Downloads) - { - file.Torrent = null; - } - } - - return Ok(torrent); + return BadRequest(errors); } - /// - /// Used for debugging only. Force a tick. - /// - /// - [HttpGet] - [Route("Tick")] - public async Task Tick() + if (file == null || file.Length <= 0) { - await _torrentRunner.Tick(); - - return Ok(); + throw new Exception("Invalid torrent file"); } - [HttpPost] - [Route("UploadFile")] - public async Task UploadFile([FromForm] IFormFile file, - [ModelBinder(BinderType = typeof(JsonModelBinder))] - TorrentControllerUploadFileRequest formData) + var fileStream = file.OpenReadStream(); + + await using var memoryStream = new MemoryStream(); + + await fileStream.CopyToAsync(memoryStream); + + var bytes = memoryStream.ToArray(); + + await _torrents.UploadFile(bytes, formData.Torrent); + + return Ok(); + } + + [HttpPost] + [Route("UploadMagnet")] + public async Task UploadMagnet([FromBody] TorrentControllerUploadMagnetRequest request) + { + if (!ModelState.IsValid) { - if (!ModelState.IsValid) - { - var errors = ModelState.Select(x => x.Value.Errors) - .Where(y => y.Count > 0) - .ToList(); + var errors = ModelState.Select(x => x.Value.Errors) + .Where(y => y.Count > 0) + .ToList(); - return BadRequest(errors); - } - - if (file == null || file.Length <= 0) - { - throw new Exception("Invalid torrent file"); - } - - var fileStream = file.OpenReadStream(); - - await using var memoryStream = new MemoryStream(); - - await fileStream.CopyToAsync(memoryStream); - - var bytes = memoryStream.ToArray(); - - await _torrents.UploadFile(bytes, formData.Torrent); - - return Ok(); + return BadRequest(errors); } - [HttpPost] - [Route("UploadMagnet")] - public async Task UploadMagnet([FromBody] TorrentControllerUploadMagnetRequest request) + await _torrents.UploadMagnet(request.MagnetLink, request.Torrent); + + return Ok(); + } + + [HttpPost] + [Route("CheckFiles")] + public async Task CheckFiles([FromForm] IFormFile file) + { + if (file == null || file.Length <= 0) { - if (!ModelState.IsValid) - { - var errors = ModelState.Select(x => x.Value.Errors) - .Where(y => y.Count > 0) - .ToList(); - - return BadRequest(errors); - } - - await _torrents.UploadMagnet(request.MagnetLink, request.Torrent); - - return Ok(); + throw new Exception("Invalid torrent file"); } - [HttpPost] - [Route("CheckFiles")] - public async Task CheckFiles([FromForm] IFormFile file) - { - if (file == null || file.Length <= 0) - { - throw new Exception("Invalid torrent file"); - } + var fileStream = file.OpenReadStream(); - var fileStream = file.OpenReadStream(); + await using var memoryStream = new MemoryStream(); - await using var memoryStream = new MemoryStream(); + await fileStream.CopyToAsync(memoryStream); - await fileStream.CopyToAsync(memoryStream); + var bytes = memoryStream.ToArray(); - var bytes = memoryStream.ToArray(); + var torrent = await MonoTorrent.Torrent.LoadAsync(bytes); - var torrent = await MonoTorrent.Torrent.LoadAsync(bytes); + var result = await _torrents.GetAvailableFiles(torrent.InfoHash.ToHex()); - var result = await _torrents.GetAvailableFiles(torrent.InfoHash.ToHex()); + return Ok(result); + } - return Ok(result); - } + [HttpPost] + [Route("CheckFilesMagnet")] + public async Task CheckFilesMagnet([FromBody] TorrentControllerCheckFilesRequest request) + { + var magnet = MagnetLink.Parse(request.MagnetLink); - [HttpPost] - [Route("CheckFilesMagnet")] - public async Task CheckFilesMagnet([FromBody] TorrentControllerCheckFilesRequest request) - { - var magnet = MagnetLink.Parse(request.MagnetLink); + var result = await _torrents.GetAvailableFiles(magnet.InfoHash.ToHex()); - var result = await _torrents.GetAvailableFiles(magnet.InfoHash.ToHex()); + return Ok(result); + } - return Ok(result); - } + [HttpPost] + [Route("Delete/{torrentId:guid}")] + public async Task Delete(Guid torrentId, [FromBody] TorrentControllerDeleteRequest request) + { + await _torrents.Delete(torrentId, request.DeleteData, request.DeleteRdTorrent, request.DeleteLocalFiles); - [HttpPost] - [Route("Delete/{torrentId:guid}")] - public async Task Delete(Guid torrentId, [FromBody] TorrentControllerDeleteRequest request) - { - await _torrents.Delete(torrentId, request.DeleteData, request.DeleteRdTorrent, request.DeleteLocalFiles); + return Ok(); + } - return Ok(); - } + [HttpPost] + [Route("Retry/{torrentId:guid}")] + public async Task Retry(Guid torrentId) + { + await _torrents.UpdateRetry(torrentId, DateTimeOffset.UtcNow, 0); + await _torrents.RetryTorrent(torrentId, 0); - [HttpPost] - [Route("Retry/{torrentId:guid}")] - public async Task Retry(Guid torrentId) - { - await _torrents.UpdateRetry(torrentId, DateTimeOffset.UtcNow, 0); - await _torrents.RetryTorrent(torrentId, 0); + return Ok(); + } - return Ok(); - } + [HttpPost] + [Route("RetryDownload/{downloadId:guid}")] + public async Task RetryDownload(Guid downloadId) + { + await _torrents.RetryDownload(downloadId); - [HttpPost] - [Route("RetryDownload/{downloadId:guid}")] - public async Task RetryDownload(Guid downloadId) - { - await _torrents.RetryDownload(downloadId); - - return Ok(); - } + return Ok(); + } - [HttpPut] - [Route("Update")] - public async Task Update([FromBody] Torrent torrent) - { - await _torrents.Update(torrent); - - return Ok(); - } - } - - public class TorrentControllerUploadFileRequest + [HttpPut] + [Route("Update")] + public async Task Update([FromBody] Torrent torrent) { - public Torrent Torrent { get; set; } - } + await _torrents.Update(torrent); - public class TorrentControllerUploadMagnetRequest - { - public String MagnetLink { get; set; } - public Torrent Torrent { get; set; } - } - - public class TorrentControllerDeleteRequest - { - public Boolean DeleteData { get; set; } - public Boolean DeleteRdTorrent { get; set; } - public Boolean DeleteLocalFiles { get; set; } - } - - public class TorrentControllerCheckFilesRequest - { - public String MagnetLink { get; set; } + return Ok(); } } + +public class TorrentControllerUploadFileRequest +{ + public Torrent Torrent { get; set; } +} + +public class TorrentControllerUploadMagnetRequest +{ + public String MagnetLink { get; set; } + public Torrent Torrent { get; set; } +} + +public class TorrentControllerDeleteRequest +{ + public Boolean DeleteData { get; set; } + public Boolean DeleteRdTorrent { get; set; } + public Boolean DeleteLocalFiles { get; set; } +} + +public class TorrentControllerCheckFilesRequest +{ + public String MagnetLink { get; set; } +} \ No newline at end of file diff --git a/server/RdtClient.Web/Program.cs b/server/RdtClient.Web/Program.cs index 161edb8..1f423ce 100644 --- a/server/RdtClient.Web/Program.cs +++ b/server/RdtClient.Web/Program.cs @@ -1,126 +1,180 @@ -using System; using System.Diagnostics; -using System.Reflection; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; +using System.Net; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Identity; using RdtClient.Data.Data; using RdtClient.Data.Models.Internal; +using RdtClient.Service.Middleware; using RdtClient.Service.Services; using Serilog; -using Serilog.Core; using Serilog.Events; using Serilog.Exceptions; -namespace RdtClient.Web +var builder = WebApplication.CreateBuilder(args); + +// Bind the AppSettings from the appsettings.json files. +builder.Configuration.AddJsonFile("appsettings.json", false, false); +builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", true, false); + +// Bind AppSettings +var appSettings = new AppSettings(); +builder.Configuration.Bind(appSettings); +builder.Services.AddSingleton(appSettings); + +// Configure URLs +if (appSettings.Port <= 0) { - public class Program - { - public static readonly LoggingLevelSwitch LoggingLevelSwitch = new(LogEventLevel.Debug); - - public static async Task Main(String[] args) - { - try - { - var host = CreateHostBuilder(args).Build(); - - // Perform migrations - using (var scope = host.Services.CreateScope()) - { - var dbContext = scope.ServiceProvider.GetRequiredService(); - await dbContext.Database.MigrateAsync(); - await dbContext.Seed(); - - var logLevelSettingDb = await dbContext.Settings.FirstOrDefaultAsync(m => m.SettingId == "LogLevel"); - - var logLevelSetting = "Warning"; - - if (logLevelSettingDb != null) - { - logLevelSetting = logLevelSettingDb.Value; - } - - if (!Enum.TryParse(logLevelSetting, out var logLevel)) - { - logLevel = LogEventLevel.Warning; - } - - LoggingLevelSwitch.MinimumLevel = logLevel; - } - - var version = Assembly.GetEntryAssembly()?.GetName().Version; - Log.Warning($"Starting host on version {version}"); - - await host.RunAsync(); - } - catch (Exception ex) - { - Log.Fatal(ex, "Host terminated unexpectedly"); - } - finally - { - Log.CloseAndFlush(); - } - } - - // ReSharper disable once MemberCanBePrivate.Global - public static IHostBuilder CreateHostBuilder(String[] args) - { - var configuration = new ConfigurationBuilder() -#if DEBUG - .AddJsonFile("appsettings.Development.json", true, false) -#else - .AddJsonFile("appsettings.json", true, false) -#endif - .Build(); - - var appSettings = new AppSettings(); - configuration.Bind(appSettings); - - if (String.IsNullOrWhiteSpace(appSettings.HostUrl)) - { - appSettings.HostUrl = "http://0.0.0.0:6500"; - } - - Log.Logger = new LoggerConfiguration() - .Enrich.FromLogContext() - .Enrich.WithExceptionDetails() - .WriteTo.File(appSettings.Logging.File.Path, - rollOnFileSizeLimit: true, - fileSizeLimitBytes: appSettings.Logging.File.FileSizeLimitBytes, - retainedFileCountLimit: appSettings.Logging.File.MaxRollingFiles, - outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}") - .WriteTo.Console() - .MinimumLevel.ControlledBy(LoggingLevelSwitch) - .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) - .MinimumLevel.Override("System.Net.Http", LogEventLevel.Warning) - .CreateLogger(); - - Serilog.Debugging.SelfLog.Enable(msg => - { - Debug.Print(msg); - Debugger.Break(); - Console.WriteLine(msg); - Debug.WriteLine(msg); - }); - - return Host.CreateDefaultBuilder(args) - .UseWindowsService() - .ConfigureServices((_, services) => - { - services.AddHostedService(); - services.AddHostedService(); - }) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseUrls(appSettings.HostUrl) - .UseKestrel() - .UseStartup(); - }) - .UseSerilog(); - } - } + appSettings.Port = 6500; } + +builder.WebHost.ConfigureKestrel(options => +{ + options.ListenAnyIP(appSettings.Port); +}); + +builder.Host.UseSerilog((_, lc) => lc.Enrich.FromLogContext() + .Enrich.WithExceptionDetails() + .WriteTo.File(appSettings.Logging.File.Path, + rollOnFileSizeLimit: true, + fileSizeLimitBytes: appSettings.Logging.File.FileSizeLimitBytes, + retainedFileCountLimit: appSettings.Logging.File.MaxRollingFiles, + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}") + .WriteTo.Console() + .MinimumLevel.ControlledBy(Settings.LoggingLevelSwitch) + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .MinimumLevel.Override("System.Net.Http", LogEventLevel.Warning)); + +Serilog.Debugging.SelfLog.Enable(msg => +{ + Debug.Print(msg); + Debugger.Break(); + Console.WriteLine(msg); + Debug.WriteLine(msg); +}); + +Log.Information("Starting RealDebridClient host"); + +builder.Services.AddControllers(); + +builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddCookie(options => + { + options.SlidingExpiration = true; + }); + +builder.Services.AddAuthorization(); + +builder.Services.AddIdentity(options => + { + options.User.RequireUniqueEmail = false; + options.Password.RequiredLength = 10; + options.Password.RequireUppercase = false; + options.Password.RequireLowercase = false; + options.Password.RequireNonAlphanumeric = false; + options.Password.RequiredUniqueChars = 5; + }) + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + +builder.Services.ConfigureApplicationCookie(options => +{ + options.Events.OnRedirectToLogin = context => + { + context.Response.StatusCode = StatusCodes.Status401Unauthorized; + return Task.CompletedTask; + }; + options.Cookie.Name = "SID"; +}); + +// Configure development cors. +builder.Services.AddCors(options => +{ + options.AddPolicy("Dev", + corsBuilder => corsBuilder.AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials()); +}); + +// Configure misc services. +builder.Services.AddResponseCaching(); +builder.Services.AddMemoryCache(); +builder.Services.AddDistributedMemoryCache(); +builder.Services.AddHttpClient(); +builder.Services.AddHttpContextAccessor(); +builder.Services.AddSession(); + +builder.Services.AddSpaStaticFiles(spaBuilder => +{ + spaBuilder.RootPath = "wwwroot"; +}); + +builder.Services.AddSignalR(hubOptions => +{ + hubOptions.EnableDetailedErrors = true; +}); + +builder.Host.UseWindowsService(); + +RdtClient.Data.DiConfig.Config(builder.Services, appSettings); +RdtClient.Service.DiConfig.Config(builder.Services); + +ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + +try +{ + // Build the app + var app = builder.Build(); + + if (builder.Environment.IsDevelopment()) + { + app.UseCors("Dev"); + app.UseDeveloperExceptionPage(); + } + + app.ConfigureExceptionHandler(); + + app.UseMiddleware(); + + app.Use(async (context, next) => + { + await next.Invoke(); + + if (context.Response.StatusCode != 200) + { + Log.Warning("{StatusCode}: {Value}", context.Response.StatusCode, context.Request.Path.Value); + } + }); + + app.UseRouting(); + + app.UseAuthentication(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapHub("/hub"); + endpoints.MapControllers(); + }); + + app.MapWhen(x => !x.Request.Path.StartsWithSegments("/api"), routeBuilder => + { + routeBuilder.UseSpaStaticFiles(); + routeBuilder.UseSpa(spa => + { + spa.Options.SourcePath = "wwwroot"; + spa.Options.DefaultPage = "/index.html"; + }); + }); + + // Run the app + app.Run(); +} +catch (Exception ex) +{ + Log.Fatal(ex, "Host terminated unexpectedly"); +} +finally +{ + Log.CloseAndFlush(); +} \ No newline at end of file diff --git a/server/RdtClient.Web/RdtClient.Web.csproj b/server/RdtClient.Web/RdtClient.Web.csproj index 0545018..37e3ef2 100644 --- a/server/RdtClient.Web/RdtClient.Web.csproj +++ b/server/RdtClient.Web/RdtClient.Web.csproj @@ -5,6 +5,9 @@ Exe 94c24cba-f03f-4453-a671-3640b517c573 2.0.12 + disable + enable + latest @@ -26,16 +29,16 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/server/RdtClient.Web/Startup.cs b/server/RdtClient.Web/Startup.cs deleted file mode 100644 index 97b1a37..0000000 --- a/server/RdtClient.Web/Startup.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using RdtClient.Data.Data; -using RdtClient.Data.Models.Internal; -using RdtClient.Service.Middleware; -using RdtClient.Service.Services; - -namespace RdtClient.Web -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - private IConfiguration Configuration { get; } - - public void ConfigureServices(IServiceCollection services) - { - var appSettings = new AppSettings(); - Configuration.Bind(appSettings); - - services.AddSingleton(appSettings); - - var connectionString = $"Data Source={appSettings.Database.Path}"; - services.AddDbContext(options => options.UseSqlite(connectionString)); - - services.AddControllers(); - - services.AddSpaStaticFiles(configuration => { configuration.RootPath = "wwwroot"; }); - - services.AddSignalR(options => - { - options.EnableDetailedErrors = true; - }); - - services.AddHttpContextAccessor(); - - services.AddCors(options => - { - options.AddPolicy("Dev", - builder => - { - builder.AllowAnyHeader() - .AllowAnyMethod() - .AllowAnyOrigin(); - }); - }); - - services.AddHttpClient(); - - services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) - .AddCookie(options => { options.SlidingExpiration = true; }); - - services.AddAuthorization(); - - services.AddIdentity(options => - { - options.User.RequireUniqueEmail = false; - options.Password.RequiredLength = 10; - options.Password.RequireUppercase = false; - options.Password.RequireLowercase = false; - options.Password.RequireNonAlphanumeric = false; - options.Password.RequiredUniqueChars = 5; - }) - .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); - - services.ConfigureApplicationCookie(options => - { - options.Events.OnRedirectToLogin = context => - { - context.Response.StatusCode = StatusCodes.Status401Unauthorized; - return Task.CompletedTask; - }; - options.Cookie.Name = "SID"; - }); - - Data.DiConfig.Config(services); - Service.DiConfig.Config(services); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger logger) - { - if (env.IsDevelopment()) - { - app.UseCors("Dev"); - } - - app.ConfigureExceptionHandler(); - - app.UseMiddleware(); - - app.Use(async (context, next) => - { - await next.Invoke(); - - if (context.Response.StatusCode != 200) - { - logger.LogWarning("{StatusCode}: {Value}", context.Response.StatusCode, context.Request.Path.Value); - } - }); - - app.UseRouting(); - - app.UseAuthentication(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapHub("/hub"); - endpoints.MapControllers(); - }); - - app.MapWhen(x => !x.Request.Path.Value.StartsWith("/api"), builder => - { - builder.UseSpaStaticFiles(); - builder.UseSpa(spa => - { - spa.Options.SourcePath = "wwwroot"; - spa.Options.DefaultPage = "/index.html"; - }); - }); - } - } -} \ No newline at end of file diff --git a/server/RdtClient.Web/appsettings.Development.json b/server/RdtClient.Web/appsettings.Development.json index 38b9b8c..d7861c3 100644 --- a/server/RdtClient.Web/appsettings.Development.json +++ b/server/RdtClient.Web/appsettings.Development.json @@ -2,7 +2,6 @@ "Logging": { "File": { "Path": "C:/Temp/rdtclient/rdtclient.log", - "Append": "True", "FileSizeLimitBytes": 5242880, "MaxRollingFiles": 5 } @@ -10,6 +9,5 @@ "Database": { "Path": "C:/Temp/rdtclient/rdtclient.db" }, - "AllowedHosts": "*", - "HostUrl": "http://0.0.0.0:6500" + "Port": "6500" } \ No newline at end of file diff --git a/server/RdtClient.Web/appsettings.json b/server/RdtClient.Web/appsettings.json index e06bee2..f8088bd 100644 --- a/server/RdtClient.Web/appsettings.json +++ b/server/RdtClient.Web/appsettings.json @@ -2,7 +2,6 @@ "Logging": { "File": { "Path": "/data/db/rdtclient.log", - "Append": "True", "FileSizeLimitBytes": 5242880, "MaxRollingFiles": 5 } @@ -10,6 +9,5 @@ "Database": { "Path": "/data/db/rdtclient.db" }, - "AllowedHosts": "*", - "HostUrl": "http://0.0.0.0:6500" + "Port": "6500" } \ No newline at end of file diff --git a/server/RdtClient.sln.DotSettings b/server/RdtClient.sln.DotSettings index 8f514b9..6a860b7 100644 --- a/server/RdtClient.sln.DotSettings +++ b/server/RdtClient.sln.DotSettings @@ -1,7 +1,5 @@  False - ExplicitlyExcluded - ExplicitlyExcluded *.appxmanifest *.d.ts *.debug.js @@ -29,6 +27,7 @@ SUGGESTION SUGGESTION DO_NOT_SHOW + ERROR DO_NOT_SHOW ERROR ERROR @@ -178,6 +177,7 @@ HCP QA QB + US XML <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />